diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..257221d23d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index 818cb6aa23..20fceeb346 100644 --- a/.gitattributes +++ b/.gitattributes @@ -21,3 +21,23 @@ *.jpg binary *.gif binary *.ttf binary +<<<<<<< .merge_file_a01752 +======= + +# Ignore all test and documentation for archive +/.github export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.scrutinizer.yml export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore +/docs export-ignore +/build export-ignore + +# Avoid merge conflicts in CHANGELOG +# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/ +/framework/CHANGELOG.md merge=union + +>>>>>>> .merge_file_a04068 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..cf063e6470 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,7 @@ +Contributing to Yii2 +==================== + +- [Report an issue](../docs/internals/report-an-issue.md) +- [Translate documentation or messages](../docs/internals/translation-workflow.md) +- [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) +- [Contribute to the core code or fix bugs](../docs/internals/git-workflow.md) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..33d7ddf824 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,14 @@ +### What steps will reproduce the problem? + +### What is the expected result? + +### What do you get instead? + + +### Additional info + +| Q | A +| ---------------- | --- +| Yii version | +| PHP version | +| Operating system | diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..968a845de2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +| Q | A +| ------------- | --- +| Is bugfix? | yes/no +| New feature? | yes/no +| Breaks BC? | yes/no +| Tests pass? | yes/no +| Fixed issues | comma-separated list of tickets # fixed by the PR, if any diff --git a/.gitignore b/.gitignore index 4e4a6b9d03..3196564d63 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,13 @@ nbproject .project .settings +<<<<<<< .merge_file_a06844 +======= +# sublime text project / workspace files +*.sublime-project +*.sublime-workspace + +>>>>>>> .merge_file_a06648 # windows thumbnail cache Thumbs.db @@ -30,3 +37,10 @@ composer.phar phpunit.phar # local phpunit config /phpunit.xml +<<<<<<< .merge_file_a06844 +======= + +# ignore sub directory for dev installed apps and extensions +/apps +/extensions +>>>>>>> .merge_file_a06648 diff --git a/.travis.yml b/.travis.yml index acdb0af52a..621bde1378 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,11 @@ php: ======= >>>>>>> yiichina/master - hhvm - - hhvm-nightly env: - CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001 +<<<<<<< .merge_file_a02932 # run build against hhvm but allow them to fail # http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail matrix: @@ -25,11 +25,10 @@ matrix: ======= >>>>>>> yiichina/master +======= +>>>>>>> .merge_file_a06108 services: - - redis-server - memcached - - elasticsearch - - mongodb # faster builds on new travis setup not using sudo sudo: false @@ -37,7 +36,7 @@ sudo: false # cache vendor dirs cache: directories: -# - cubrid # caching cubrid breaks the build on a regular basis and has nearly no speedup +# - cubrid/9.3.0 - vendor - $HOME/.composer/cache @@ -47,6 +46,7 @@ addons: install: - travis_retry composer self-update && composer --version +<<<<<<< .merge_file_a02932 <<<<<<< HEAD - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0" - export PATH="$HOME/.composer/vendor/bin:$PATH" @@ -83,12 +83,21 @@ install: - travis_retry composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" # basic and advanced application: - tests/unit/data/travis/setup-apps.sh +======= + - travis_retry composer global require "fxp/composer-asset-plugin:~1.1.1" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + - tests/data/travis/apc-setup.sh + - tests/data/travis/memcache-setup.sh +# - tests/data/travis/cubrid-setup.sh +>>>>>>> .merge_file_a06108 before_script: <<<<<<< HEAD # show some versions and env information - - echo 'elasticsearch version ' && curl http://localhost:9200/ - php -r "echo INTL_ICU_VERSION . \"\n\";" + - php -r "echo INTL_ICU_DATA_VERSION . \"\n\";" - mysql --version - psql --version @@ -98,11 +107,15 @@ before_script: >>>>>>> yiichina/master - mysql -e 'CREATE DATABASE yiitest;'; - psql -U postgres -c 'CREATE DATABASE yiitest;'; +<<<<<<< .merge_file_a02932 - tests/unit/data/travis/sphinx-setup.sh - mongo yii2test --eval 'db.addUser("travis", "test");' # basic and advanced application: - tests/unit/data/travis/init-apps.sh <<<<<<< HEAD +======= + +>>>>>>> .merge_file_a06108 - | if [ $TRAVIS_PHP_VERSION = '5.6' ]; then PHPUNIT_FLAGS="--coverage-clover=coverage.clover" @@ -110,6 +123,7 @@ before_script: script: +<<<<<<< .merge_file_a02932 - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata ======= @@ -134,12 +148,14 @@ script: cd tests codecept run fi +======= + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata,cubrid +>>>>>>> .merge_file_a06108 after_script: <<<<<<< HEAD - | if [ $TRAVIS_PHP_VERSION = '5.6' ]; then - cd ../../.. travis_retry wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover fi diff --git a/.travis_BACKUP_5640.yml b/.travis_BACKUP_5640.yml new file mode 100644 index 0000000000..56e251ef11 --- /dev/null +++ b/.travis_BACKUP_5640.yml @@ -0,0 +1,166 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 +<<<<<<< HEAD + - 7.0 +======= +>>>>>>> yiichina/master + - hhvm + +env: + - CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001 + +<<<<<<< HEAD +# run build against hhvm but allow them to fail +# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail +matrix: + fast_finish: true + allow_failures: + - php: hhvm-nightly +<<<<<<< HEAD + - php: 7.0 +======= +>>>>>>> yiichina/master + +======= +>>>>>>> master +services: + - memcached + +# faster builds on new travis setup not using sudo +sudo: false + +# cache vendor dirs +cache: + directories: +# - cubrid/9.3.0 + - vendor + - $HOME/.composer/cache + +# try running against postgres 9.3 +addons: + postgresql: "9.3" + +install: + - travis_retry composer self-update && composer --version +<<<<<<< HEAD +<<<<<<< HEAD + - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + # install php extensions + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping imagick and gmagick tests on HHVM" + else + pear config-set preferred_state beta + printf "\n" | pecl install imagick + # gmagick is not installed on travis currently + #printf "\n" | pecl install gmagick + fi + - tests/unit/data/travis/mongodb-setup.sh + - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh +# - tests/unit/data/travis/cubrid-setup.sh +======= + - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0-beta4" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + - tests/unit/data/travis/mongodb-setup.sh +# TODO: APC currently fails composer. https://github.com/composer/composer/issues/3405 +# - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh + - tests/unit/data/travis/cubrid-setup.sh +# print elasticsearch version + - curl http://localhost:9200 +>>>>>>> yiichina/master +# codeception + - travis_retry composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" +# basic and advanced application: + - tests/unit/data/travis/setup-apps.sh +======= + - travis_retry composer global require "fxp/composer-asset-plugin:~1.1.1" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + - tests/data/travis/apc-setup.sh + - tests/data/travis/memcache-setup.sh +# - tests/data/travis/cubrid-setup.sh +>>>>>>> master + +before_script: +<<<<<<< HEAD + # show some versions and env information + - php -r "echo INTL_ICU_VERSION . \"\n\";" + - php -r "echo INTL_ICU_DATA_VERSION . \"\n\";" + - mysql --version + - psql --version + + # initialize databases +======= + - echo 'elasticsearch version ' && curl http://localhost:9200/ +>>>>>>> yiichina/master + - mysql -e 'CREATE DATABASE yiitest;'; + - psql -U postgres -c 'CREATE DATABASE yiitest;'; +<<<<<<< HEAD + - tests/unit/data/travis/sphinx-setup.sh + - mongo yii2test --eval 'db.addUser("travis", "test");' +# basic and advanced application: + - tests/unit/data/travis/init-apps.sh +<<<<<<< HEAD +======= + +>>>>>>> master + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + PHPUNIT_FLAGS="--coverage-clover=coverage.clover" + fi + + +script: +<<<<<<< HEAD + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata +======= + +script: + - vendor/bin/phpunit --verbose --coverage-clover=coverage.clover --exclude-group mssql,oci,wincache,xcache,zenddata +>>>>>>> yiichina/master + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping basic application tests on HHVM" + else + cd apps/basic/web + php -S localhost:8080 > /dev/null 2>&1 & + cd ../tests + codecept run + fi + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping advanced application tests on HHVM" + else + cd apps/advanced + php -S localhost:8080 > /dev/null 2>&1 & + cd tests + codecept run + fi +======= + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata,cubrid +>>>>>>> master + +after_script: +<<<<<<< HEAD + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + travis_retry wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover + fi +======= + - cd ../../.. + - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover +>>>>>>> yiichina/master diff --git a/.travis_BASE_5640.yml b/.travis_BASE_5640.yml new file mode 100644 index 0000000000..85a0672479 --- /dev/null +++ b/.travis_BASE_5640.yml @@ -0,0 +1,114 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + - hhvm-nightly + +env: + - CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001 + +# run build against hhvm but allow them to fail +# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail +matrix: + fast_finish: true + allow_failures: + - php: hhvm-nightly + - php: 7.0 + +services: + - redis-server + - memcached + - elasticsearch + - mongodb + +# faster builds on new travis setup not using sudo +sudo: false + +# cache vendor dirs +cache: + directories: +# - cubrid # caching cubrid breaks the build on a regular basis and has nearly no speedup + - vendor + - $HOME/.composer/cache + +# try running against postgres 9.3 +addons: + postgresql: "9.3" + +install: + - travis_retry composer self-update && composer --version + - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + # install php extensions + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping imagick and gmagick tests on HHVM" + else + pear config-set preferred_state beta + printf "\n" | pecl install imagick + # gmagick is not installed on travis currently + #printf "\n" | pecl install gmagick + fi + - tests/unit/data/travis/mongodb-setup.sh + - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh +# - tests/unit/data/travis/cubrid-setup.sh +# codeception + - travis_retry composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" +# basic and advanced application: + - tests/unit/data/travis/setup-apps.sh + +before_script: + # show some versions and env information + - echo 'elasticsearch version ' && curl http://localhost:9200/ + - php -r "echo INTL_ICU_VERSION . \"\n\";" + - mysql --version + - psql --version + + # initialize databases + - mysql -e 'CREATE DATABASE yiitest;'; + - psql -U postgres -c 'CREATE DATABASE yiitest;'; + - tests/unit/data/travis/sphinx-setup.sh + - mongo yii2test --eval 'db.addUser("travis", "test");' +# basic and advanced application: + - tests/unit/data/travis/init-apps.sh + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + PHPUNIT_FLAGS="--coverage-clover=coverage.clover" + fi + + +script: + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping basic application tests on HHVM" + else + cd apps/basic/web + php -S localhost:8080 > /dev/null 2>&1 & + cd ../tests + codecept run + fi + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping advanced application tests on HHVM" + else + cd apps/advanced + php -S localhost:8080 > /dev/null 2>&1 & + cd tests + codecept run + fi + +after_script: + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + cd ../../.. + travis_retry wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover + fi diff --git a/.travis_LOCAL_5640.yml b/.travis_LOCAL_5640.yml new file mode 100644 index 0000000000..acdb0af52a --- /dev/null +++ b/.travis_LOCAL_5640.yml @@ -0,0 +1,150 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 +<<<<<<< HEAD + - 7.0 +======= +>>>>>>> yiichina/master + - hhvm + - hhvm-nightly + +env: + - CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001 + +# run build against hhvm but allow them to fail +# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail +matrix: + fast_finish: true + allow_failures: + - php: hhvm-nightly +<<<<<<< HEAD + - php: 7.0 +======= +>>>>>>> yiichina/master + +services: + - redis-server + - memcached + - elasticsearch + - mongodb + +# faster builds on new travis setup not using sudo +sudo: false + +# cache vendor dirs +cache: + directories: +# - cubrid # caching cubrid breaks the build on a regular basis and has nearly no speedup + - vendor + - $HOME/.composer/cache + +# try running against postgres 9.3 +addons: + postgresql: "9.3" + +install: + - travis_retry composer self-update && composer --version +<<<<<<< HEAD + - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + # install php extensions + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping imagick and gmagick tests on HHVM" + else + pear config-set preferred_state beta + printf "\n" | pecl install imagick + # gmagick is not installed on travis currently + #printf "\n" | pecl install gmagick + fi + - tests/unit/data/travis/mongodb-setup.sh + - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh +# - tests/unit/data/travis/cubrid-setup.sh +======= + - travis_retry composer global require "fxp/composer-asset-plugin:1.0.0-beta4" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + - tests/unit/data/travis/mongodb-setup.sh +# TODO: APC currently fails composer. https://github.com/composer/composer/issues/3405 +# - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh + - tests/unit/data/travis/cubrid-setup.sh +# print elasticsearch version + - curl http://localhost:9200 +>>>>>>> yiichina/master +# codeception + - travis_retry composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" +# basic and advanced application: + - tests/unit/data/travis/setup-apps.sh + +before_script: +<<<<<<< HEAD + # show some versions and env information + - echo 'elasticsearch version ' && curl http://localhost:9200/ + - php -r "echo INTL_ICU_VERSION . \"\n\";" + - mysql --version + - psql --version + + # initialize databases +======= + - echo 'elasticsearch version ' && curl http://localhost:9200/ +>>>>>>> yiichina/master + - mysql -e 'CREATE DATABASE yiitest;'; + - psql -U postgres -c 'CREATE DATABASE yiitest;'; + - tests/unit/data/travis/sphinx-setup.sh + - mongo yii2test --eval 'db.addUser("travis", "test");' +# basic and advanced application: + - tests/unit/data/travis/init-apps.sh +<<<<<<< HEAD + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + PHPUNIT_FLAGS="--coverage-clover=coverage.clover" + fi + + +script: + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata +======= + +script: + - vendor/bin/phpunit --verbose --coverage-clover=coverage.clover --exclude-group mssql,oci,wincache,xcache,zenddata +>>>>>>> yiichina/master + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping basic application tests on HHVM" + else + cd apps/basic/web + php -S localhost:8080 > /dev/null 2>&1 & + cd ../tests + codecept run + fi + - | + if (php --version | grep -i HipHop > /dev/null); then + echo "Skipping advanced application tests on HHVM" + else + cd apps/advanced + php -S localhost:8080 > /dev/null 2>&1 & + cd tests + codecept run + fi + +after_script: +<<<<<<< HEAD + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + cd ../../.. + travis_retry wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover + fi +======= + - cd ../../.. + - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover +>>>>>>> yiichina/master diff --git a/.travis_REMOTE_5640.yml b/.travis_REMOTE_5640.yml new file mode 100644 index 0000000000..7cf17974ca --- /dev/null +++ b/.travis_REMOTE_5640.yml @@ -0,0 +1,65 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +env: + - CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001 + +services: + - memcached + +# faster builds on new travis setup not using sudo +sudo: false + +# cache vendor dirs +cache: + directories: +# - cubrid/9.3.0 + - vendor + - $HOME/.composer/cache + +# try running against postgres 9.3 +addons: + postgresql: "9.3" + +install: + - travis_retry composer self-update && composer --version + - travis_retry composer global require "fxp/composer-asset-plugin:~1.1.1" + - export PATH="$HOME/.composer/vendor/bin:$PATH" +# core framework: + - travis_retry composer install --prefer-dist --no-interaction + - tests/data/travis/apc-setup.sh + - tests/data/travis/memcache-setup.sh +# - tests/data/travis/cubrid-setup.sh + +before_script: + # show some versions and env information + - php -r "echo INTL_ICU_VERSION . \"\n\";" + - php -r "echo INTL_ICU_DATA_VERSION . \"\n\";" + - mysql --version + - psql --version + + # initialize databases + - mysql -e 'CREATE DATABASE yiitest;'; + - psql -U postgres -c 'CREATE DATABASE yiitest;'; + + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + PHPUNIT_FLAGS="--coverage-clover=coverage.clover" + fi + + +script: + - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata,cubrid + +after_script: + - | + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + travis_retry wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover + fi diff --git a/README.md b/README.md index 7341402e61..5550417cba 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,18 @@ Yii 2 requires PHP 5.4 and embraces the best practices and protocols found in mo [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2/v/stable.png)](https://packagist.org/packages/yiisoft/yii2) [![Total Downloads](https://poser.pugx.org/yiisoft/yii2/downloads.png)](https://packagist.org/packages/yiisoft/yii2) [![Reference Status](https://www.versioneye.com/php/yiisoft:yii2/reference_badge.svg)](https://www.versioneye.com/php/yiisoft:yii2/references) -[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) +[![Build Status](https://img.shields.io/travis/yiisoft/yii2.svg)](http://travis-ci.org/yiisoft/yii2) [![Dependency Status](https://www.versioneye.com/php/yiisoft:yii2/dev-master/badge.png)](https://www.versioneye.com/php/yiisoft:yii2/dev-master) -[![HHVM Status](http://hhvm.h4cc.de/badge/yiisoft/yii2-dev.png)](http://hhvm.h4cc.de/package/yiisoft/yii2-dev) +[![HHVM Status](https://img.shields.io/hhvm/yiisoft/yii2-dev.svg)](http://hhvm.h4cc.de/package/yiisoft/yii2-dev) [![Code Coverage](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/coverage.png?s=31d80f1036099e9d6a3e4d7738f6b000b3c3d10e)](https://scrutinizer-ci.com/g/yiisoft/yii2/) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/quality-score.png?s=b1074a1ff6d0b214d54fa5ab7abbb90fc092471d)](https://scrutinizer-ci.com/g/yiisoft/yii2/) -[![Code Climate](https://codeclimate.com/github/yiisoft/yii2.png)](https://codeclimate.com/github/yiisoft/yii2) +[![Code Climate](https://img.shields.io/codeclimate/github/yiisoft/yii2.svg)](https://codeclimate.com/github/yiisoft/yii2) DIRECTORY STRUCTURE ------------------- ``` +<<<<<<< .merge_file_a06476 <<<<<<< HEAD apps/ ready-to-use application templates advanced/ a template suitable for building sophisticated Web applications @@ -34,6 +35,10 @@ extensions/ extensions build/ internally used build tools docs/ documentation >>>>>>> yiichina/master +======= +build/ internally used build tools +docs/ documentation +>>>>>>> .merge_file_a04128 framework/ core framework code tests/ tests of the core framework code ``` @@ -51,12 +56,17 @@ DOCUMENTATION Yii 2.0 has a [Definitive Guide](http://www.yiiframework.com/doc-2.0/guide-index.html) and a [Class Reference](http://www.yiiframework.com/doc-2.0/index.html) which cover every detail of Yii. +<<<<<<< .merge_file_a06476 There is also a [PDF version](http://stuff.cebe.cc/yii2-guide.pdf) of the Definitive Guide <<<<<<< HEAD and a [Definitive Guide Mirror](http://stuff.cebe.cc/yii2docs/) which update every 15 minutes. ======= and a [Definitive Guide Mirror](http://stuff.cebe.cc/yii2docs/) which is updated every 15 minutes. >>>>>>> yiichina/master +======= +There is also a [PDF version](http://stuff.cebe.cc/yii2-guide.en.pdf) of the Definitive Guide +and a [Definitive Guide Mirror](http://stuff.cebe.cc/yii2docs/) which is updated every 15 minutes. +>>>>>>> .merge_file_a04128 For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/intro-upgrade-from-v1.md) to have a general idea of what has changed in 2.0. @@ -65,10 +75,11 @@ to have a general idea of what has changed in 2.0. HOW TO PARTICIPATE ------------------ -**Your participation to Yii 2 development is very welcome!** +### Your participation to Yii 2 development is very welcome! You may participate in the following ways: +<<<<<<< .merge_file_a06476 <<<<<<< HEAD * [Report issues](https://github.com/yiisoft/yii2/issues) * [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-design-discussions-for-yii-20/) @@ -83,4 +94,29 @@ In order to make it easier we've prepared [special `yii2-dev` Composer package]( - [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) - [Contribute to the core code or fix bugs](docs/internals/git-workflow.md) >>>>>>> yiichina/master +======= +- [Report an issue](docs/internals/report-an-issue.md) +- [Translate documentation or messages](docs/internals/translation-workflow.md) +- [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) +- [Contribute to the core code or fix bugs](docs/internals/git-workflow.md) +### Acknowledging or citing Yii 2 +>>>>>>> .merge_file_a04128 + +**In presentations** + +If you are giving a presentation or talk featuring work that makes use of Yii 2 and would like to acknowledge it, +we suggest using [our logo](http://www.yiiframework.com/logo/) on your title slide. + +**In projects** + +If you are using Yii 2 as part of an OpenSource project, a way to acknowledge it is to +[use a special badge](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat) in your README: + +![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat) + +If your code is hosted at GitHub, you can place the following in your README.md file to get the badge: + +``` +[![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](http://www.yiiframework.com/) +``` diff --git a/apps/advanced/.bowerrc b/apps/advanced/.bowerrc deleted file mode 100644 index 1669168f29..0000000000 --- a/apps/advanced/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory" : "vendor/bower" -} diff --git a/apps/advanced/.gitignore b/apps/advanced/.gitignore deleted file mode 100644 index 346d3b2cba..0000000000 --- a/apps/advanced/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# yii console command -/yii - -# phpstorm project files -.idea - -# netbeans project files -nbproject - -# zend studio for eclipse project files -.buildpath -.project -.settings - -# windows thumbnail cache -Thumbs.db - -# composer vendor dir -/vendor - -# composer itself is not needed -composer.phar - -# Mac DS_Store Files -.DS_Store - -# phpunit itself is not needed -phpunit.phar -# local phpunit config -/phpunit.xml diff --git a/apps/advanced/LICENSE.md b/apps/advanced/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/apps/advanced/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/advanced/README.md b/apps/advanced/README.md deleted file mode 100644 index 229a6ea138..0000000000 --- a/apps/advanced/README.md +++ /dev/null @@ -1,97 +0,0 @@ -Yii 2 Advanced Application Template -=================================== - -Yii 2 Advanced Application Template is a skeleton Yii 2 application best for -developing complex Web applications with multiple tiers. - -The template includes three tiers: front end, back end, and console, each of which -is a separate Yii application. - -The template is designed to work in a team development environment. It supports -deploying the application in different environments. - - -DIRECTORY STRUCTURE -------------------- - -``` -common - config/ contains shared configurations - mail/ contains view files for e-mails - models/ contains model classes used in both backend and frontend -console - config/ contains console configurations - controllers/ contains console controllers (commands) - migrations/ contains database migrations - models/ contains console-specific model classes - runtime/ contains files generated during runtime -backend - assets/ contains application assets such as JavaScript and CSS - config/ contains backend configurations - controllers/ contains Web controller classes - models/ contains backend-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - web/ contains the entry script and Web resources -frontend - assets/ contains application assets such as JavaScript and CSS - config/ contains frontend configurations - controllers/ contains Web controller classes - models/ contains frontend-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - web/ contains the entry script and Web resources - widgets/ contains frontend widgets -vendor/ contains dependent 3rd-party packages -environments/ contains environment-based overrides -tests contains various tests for the advanced application - codeception/ contains tests developed with Codeception PHP Testing Framework -``` - - -REQUIREMENTS ------------- - -The minimum requirement by this application template that your Web server supports PHP 5.4.0. - - -INSTALLATION ------------- - -### Install from an Archive File - -Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to -a directory named `advanced` that is directly under the Web root. - -Then follow the instructions given in "GETTING STARTED". - - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions -at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix). - -You can then install the application using the following command: - -~~~ -php composer.phar global require "fxp/composer-asset-plugin:1.0.0" -php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-app-advanced advanced -~~~ - - -GETTING STARTED ---------------- - -After you install the application, you have to conduct the following steps to initialize -the installed application. You only need to do these once for all. - -1. Run command `init` to initialize the application with a specific environment. -2. Create a new database and adjust the `components['db']` configuration in `common/config/main-local.php` accordingly. -3. Apply migrations with console command `yii migrate`. This will create tables needed for the application to work. -4. Set document roots of your Web server: - -- for frontend `/path/to/yii-application/frontend/web/` and using the URL `http://frontend/` -- for backend `/path/to/yii-application/backend/web/` and using the URL `http://backend/` - -To login into the application, you need to first sign up, with any of your email address, username and password. -Then, you can login into the application with same email address and password at any time. diff --git a/apps/advanced/backend/assets/AppAsset.php b/apps/advanced/backend/assets/AppAsset.php deleted file mode 100644 index c2621429e2..0000000000 --- a/apps/advanced/backend/assets/AppAsset.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class AppAsset extends AssetBundle -{ - public $basePath = '@webroot'; - public $baseUrl = '@web'; - public $css = [ - 'css/site.css', - ]; - public $js = [ - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/apps/advanced/backend/config/.gitignore b/apps/advanced/backend/config/.gitignore deleted file mode 100644 index 20da318cb2..0000000000 --- a/apps/advanced/backend/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/backend/config/bootstrap.php b/apps/advanced/backend/config/bootstrap.php deleted file mode 100644 index b3d9bbc7f3..0000000000 --- a/apps/advanced/backend/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-backend', - 'basePath' => dirname(__DIR__), - 'controllerNamespace' => 'backend\controllers', - 'bootstrap' => ['log'], - 'modules' => [], - 'components' => [ - 'user' => [ - 'identityClass' => 'common\models\User', - 'enableAutoLogin' => true, - ], - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'errorHandler' => [ - 'errorAction' => 'site/error', - ], - ], - 'params' => $params, -]; diff --git a/apps/advanced/backend/config/params.php b/apps/advanced/backend/config/params.php deleted file mode 100644 index 7f754b91fe..0000000000 --- a/apps/advanced/backend/config/params.php +++ /dev/null @@ -1,4 +0,0 @@ - 'admin@example.com', -]; diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php deleted file mode 100644 index db3259a752..0000000000 --- a/apps/advanced/backend/controllers/SiteController.php +++ /dev/null @@ -1,83 +0,0 @@ - [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'actions' => ['login', 'error'], - 'allow' => true, - ], - [ - 'actions' => ['logout', 'index'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'logout' => ['post'], - ], - ], - ]; - } - - /** - * @inheritdoc - */ - public function actions() - { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - ]; - } - - public function actionIndex() - { - return $this->render('index'); - } - - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - - public function actionLogout() - { - Yii::$app->user->logout(); - - return $this->goHome(); - } -} diff --git a/apps/advanced/backend/models/.gitkeep b/apps/advanced/backend/models/.gitkeep deleted file mode 100644 index 72e8ffc0db..0000000000 --- a/apps/advanced/backend/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backend/runtime/.gitignore b/apps/advanced/backend/runtime/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/apps/advanced/backend/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php deleted file mode 100644 index f046488f6b..0000000000 --- a/apps/advanced/backend/views/layouts/main.php +++ /dev/null @@ -1,71 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> -
- 'My Company', - 'brandUrl' => Yii::$app->homeUrl, - 'options' => [ - 'class' => 'navbar-inverse navbar-fixed-top', - ], - ]); - $menuItems = [ - ['label' => 'Home', 'url' => ['/site/index']], - ]; - if (Yii::$app->user->isGuest) { - $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; - } else { - $menuItems[] = [ - 'label' => 'Logout (' . Yii::$app->user->identity->username . ')', - 'url' => ['/site/logout'], - 'linkOptions' => ['data-method' => 'post'] - ]; - } - echo Nav::widget([ - 'options' => ['class' => 'navbar-nav navbar-right'], - 'items' => $menuItems, - ]); - NavBar::end(); - ?> - -
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], - ]) ?> - -
-
- - - - endBody() ?> - - -endPage() ?> diff --git a/apps/advanced/backend/views/site/error.php b/apps/advanced/backend/views/site/error.php deleted file mode 100644 index b9812c488e..0000000000 --- a/apps/advanced/backend/views/site/error.php +++ /dev/null @@ -1,27 +0,0 @@ -title = $name; -?> -
- -

title) ?>

- -
- -
- -

- The above error occurred while the Web server was processing your request. -

-

- Please contact us if you think this is a server error. Thank you. -

- -
diff --git a/apps/advanced/backend/views/site/index.php b/apps/advanced/backend/views/site/index.php deleted file mode 100644 index 0159befe3c..0000000000 --- a/apps/advanced/backend/views/site/index.php +++ /dev/null @@ -1,52 +0,0 @@ -title = 'My Yii Application'; -?> -
- -
-

Congratulations!

- -

You have successfully created your Yii-powered application.

- -

Get started with Yii

-
- -
- -
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Documentation »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Forum »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Extensions »

-
-
- -
-
diff --git a/apps/advanced/backend/views/site/login.php b/apps/advanced/backend/views/site/login.php deleted file mode 100644 index a4041a9631..0000000000 --- a/apps/advanced/backend/views/site/login.php +++ /dev/null @@ -1,29 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please fill out the following fields to login:

- -
-
- 'login-form']); ?> - field($model, 'username') ?> - field($model, 'password')->passwordInput() ?> - field($model, 'rememberMe')->checkbox() ?> -
- 'btn btn-primary', 'name' => 'login-button']) ?> -
- -
-
-
diff --git a/apps/advanced/backend/web/.gitignore b/apps/advanced/backend/web/.gitignore deleted file mode 100644 index 25c74e61ed..0000000000 --- a/apps/advanced/backend/web/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/index.php -/index-test.php diff --git a/apps/advanced/backend/web/assets/.gitignore b/apps/advanced/backend/web/assets/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/apps/advanced/backend/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/apps/advanced/backend/web/css/site.css b/apps/advanced/backend/web/css/site.css deleted file mode 100644 index 698be709c7..0000000000 --- a/apps/advanced/backend/web/css/site.css +++ /dev/null @@ -1,91 +0,0 @@ -html, -body { - height: 100%; -} - -.wrap { - min-height: 100%; - height: auto; - margin: 0 auto -60px; - padding: 0 0 60px; -} - -.wrap > .container { - padding: 70px 15px 20px; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding-top: 20px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.not-set { - color: #c55; - font-style: italic; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.grid-view th { - white-space: nowrap; -} - -.hint-block { - display: block; - margin-top: 5px; - color: #999; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} diff --git a/apps/advanced/backend/web/favicon.ico b/apps/advanced/backend/web/favicon.ico deleted file mode 100644 index 580ed732e8..0000000000 Binary files a/apps/advanced/backend/web/favicon.ico and /dev/null differ diff --git a/apps/advanced/backend/web/robots.txt b/apps/advanced/backend/web/robots.txt deleted file mode 100644 index c6742d8a8c..0000000000 --- a/apps/advanced/backend/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-Agent: * -Disallow: / diff --git a/apps/advanced/common/config/.gitignore b/apps/advanced/common/config/.gitignore deleted file mode 100644 index 97c0f016ab..0000000000 --- a/apps/advanced/common/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php diff --git a/apps/advanced/common/config/bootstrap.php b/apps/advanced/common/config/bootstrap.php deleted file mode 100644 index ecc13e54a0..0000000000 --- a/apps/advanced/common/config/bootstrap.php +++ /dev/null @@ -1,5 +0,0 @@ - dirname(dirname(__DIR__)) . '/vendor', - 'components' => [ - 'cache' => [ - 'class' => 'yii\caching\FileCache', - ], - ], -]; diff --git a/apps/advanced/common/config/params.php b/apps/advanced/common/config/params.php deleted file mode 100644 index 4ec9ba6d00..0000000000 --- a/apps/advanced/common/config/params.php +++ /dev/null @@ -1,6 +0,0 @@ - 'admin@example.com', - 'supportEmail' => 'support@example.com', - 'user.passwordResetTokenExpire' => 3600, -]; diff --git a/apps/advanced/common/mail/layouts/html.php b/apps/advanced/common/mail/layouts/html.php deleted file mode 100644 index bddbc61290..0000000000 --- a/apps/advanced/common/mail/layouts/html.php +++ /dev/null @@ -1,22 +0,0 @@ - -beginPage() ?> - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> - - endBody() ?> - - -endPage() ?> diff --git a/apps/advanced/common/mail/layouts/text.php b/apps/advanced/common/mail/layouts/text.php deleted file mode 100644 index 7087ceaf60..0000000000 --- a/apps/advanced/common/mail/layouts/text.php +++ /dev/null @@ -1,12 +0,0 @@ - -beginPage() ?> -beginBody() ?> - -endBody() ?> -endPage() ?> diff --git a/apps/advanced/common/mail/passwordResetToken-html.php b/apps/advanced/common/mail/passwordResetToken-html.php deleted file mode 100644 index f3daf49f68..0000000000 --- a/apps/advanced/common/mail/passwordResetToken-html.php +++ /dev/null @@ -1,15 +0,0 @@ -urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); -?> -
-

Hello username) ?>,

- -

Follow the link below to reset your password:

- -

-
diff --git a/apps/advanced/common/mail/passwordResetToken-text.php b/apps/advanced/common/mail/passwordResetToken-text.php deleted file mode 100644 index 244c0cb401..0000000000 --- a/apps/advanced/common/mail/passwordResetToken-text.php +++ /dev/null @@ -1,12 +0,0 @@ -urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); -?> -Hello username ?>, - -Follow the link below to reset your password: - - diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php deleted file mode 100644 index 6ec31f21c8..0000000000 --- a/apps/advanced/common/models/LoginForm.php +++ /dev/null @@ -1,78 +0,0 @@ -hasErrors()) { - $user = $this->getUser(); - if (!$user || !$user->validatePassword($this->password)) { - $this->addError($attribute, 'Incorrect username or password.'); - } - } - } - - /** - * Logs in a user using the provided username and password. - * - * @return boolean whether the user is logged in successfully - */ - public function login() - { - if ($this->validate()) { - return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); - } else { - return false; - } - } - - /** - * Finds user by [[username]] - * - * @return User|null - */ - public function getUser() - { - if ($this->_user === false) { - $this->_user = User::findByUsername($this->username); - } - - return $this->_user; - } -} diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php deleted file mode 100644 index d51d7ec819..0000000000 --- a/apps/advanced/common/models/User.php +++ /dev/null @@ -1,188 +0,0 @@ - self::STATUS_ACTIVE], - ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], - ]; - } - - /** - * @inheritdoc - */ - public static function findIdentity($id) - { - return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); - } - - /** - * @inheritdoc - */ - public static function findIdentityByAccessToken($token, $type = null) - { - throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); - } - - /** - * Finds user by username - * - * @param string $username - * @return static|null - */ - public static function findByUsername($username) - { - return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]); - } - - /** - * Finds user by password reset token - * - * @param string $token password reset token - * @return static|null - */ - public static function findByPasswordResetToken($token) - { - if (!static::isPasswordResetTokenValid($token)) { - return null; - } - - return static::findOne([ - 'password_reset_token' => $token, - 'status' => self::STATUS_ACTIVE, - ]); - } - - /** - * Finds out if password reset token is valid - * - * @param string $token password reset token - * @return boolean - */ - public static function isPasswordResetTokenValid($token) - { - if (empty($token)) { - return false; - } - $expire = Yii::$app->params['user.passwordResetTokenExpire']; - $parts = explode('_', $token); - $timestamp = (int) end($parts); - return $timestamp + $expire >= time(); - } - - /** - * @inheritdoc - */ - public function getId() - { - return $this->getPrimaryKey(); - } - - /** - * @inheritdoc - */ - public function getAuthKey() - { - return $this->auth_key; - } - - /** - * @inheritdoc - */ - public function validateAuthKey($authKey) - { - return $this->getAuthKey() === $authKey; - } - - /** - * Validates password - * - * @param string $password password to validate - * @return boolean if password provided is valid for current user - */ - public function validatePassword($password) - { - return Yii::$app->security->validatePassword($password, $this->password_hash); - } - - /** - * Generates password hash from password and sets it to the model - * - * @param string $password - */ - public function setPassword($password) - { - $this->password_hash = Yii::$app->security->generatePasswordHash($password); - } - - /** - * Generates "remember me" authentication key - */ - public function generateAuthKey() - { - $this->auth_key = Yii::$app->security->generateRandomString(); - } - - /** - * Generates new password reset token - */ - public function generatePasswordResetToken() - { - $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); - } - - /** - * Removes password reset token - */ - public function removePasswordResetToken() - { - $this->password_reset_token = null; - } -} diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json deleted file mode 100644 index 14487e3ccb..0000000000 --- a/apps/advanced/composer.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "yiisoft/yii2-app-advanced", - "description": "Yii 2 Advanced Application Template", - "keywords": ["yii2", "framework", "advanced", "application template"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "*", - "yiisoft/yii2-bootstrap": "*", - "yiisoft/yii2-swiftmailer": "*" - }, - "require-dev": { - "yiisoft/yii2-codeception": "*", - "yiisoft/yii2-debug": "*", - "yiisoft/yii2-gii": "*", - "yiisoft/yii2-faker": "*" - }, - "config": { - "process-timeout": 1800 - }, - "extra": { - "asset-installer-paths": { - "npm-asset-library": "vendor/npm", - "bower-asset-library": "vendor/bower" - } - } -} diff --git a/apps/advanced/console/config/.gitignore b/apps/advanced/console/config/.gitignore deleted file mode 100644 index 20da318cb2..0000000000 --- a/apps/advanced/console/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/console/config/bootstrap.php b/apps/advanced/console/config/bootstrap.php deleted file mode 100644 index b3d9bbc7f3..0000000000 --- a/apps/advanced/console/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-console', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], - 'controllerNamespace' => 'console\controllers', - 'components' => [ - 'log' => [ - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - ], - 'params' => $params, -]; diff --git a/apps/advanced/console/config/params.php b/apps/advanced/console/config/params.php deleted file mode 100644 index 7f754b91fe..0000000000 --- a/apps/advanced/console/config/params.php +++ /dev/null @@ -1,4 +0,0 @@ - 'admin@example.com', -]; diff --git a/apps/advanced/console/controllers/.gitkeep b/apps/advanced/console/controllers/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/advanced/console/migrations/m130524_201442_init.php b/apps/advanced/console/migrations/m130524_201442_init.php deleted file mode 100644 index 38d0cfecfb..0000000000 --- a/apps/advanced/console/migrations/m130524_201442_init.php +++ /dev/null @@ -1,34 +0,0 @@ -db->driverName === 'mysql') { - // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci - $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; - } - - $this->createTable('{{%user}}', [ - 'id' => Schema::TYPE_PK, - 'username' => Schema::TYPE_STRING . ' NOT NULL', - 'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL', - 'password_hash' => Schema::TYPE_STRING . ' NOT NULL', - 'password_reset_token' => Schema::TYPE_STRING, - 'email' => Schema::TYPE_STRING . ' NOT NULL', - - 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', - 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', - 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', - ], $tableOptions); - } - - public function down() - { - $this->dropTable('{{%user}}'); - } -} diff --git a/apps/advanced/console/models/.gitkeep b/apps/advanced/console/models/.gitkeep deleted file mode 100644 index 72e8ffc0db..0000000000 --- a/apps/advanced/console/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/console/runtime/.gitignore b/apps/advanced/console/runtime/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/apps/advanced/console/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php deleted file mode 100644 index d9e380947c..0000000000 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ /dev/null @@ -1,21 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; - -if (!YII_ENV_TEST) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; -} - -return $config; diff --git a/apps/advanced/environments/dev/backend/config/params-local.php b/apps/advanced/environments/dev/backend/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/dev/backend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/backend/web/index.php b/apps/advanced/environments/dev/backend/web/index.php deleted file mode 100644 index 60381674f2..0000000000 --- a/apps/advanced/environments/dev/backend/web/index.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/common/config/main-local.php b/apps/advanced/environments/dev/common/config/main-local.php deleted file mode 100644 index 43db30eab2..0000000000 --- a/apps/advanced/environments/dev/common/config/main-local.php +++ /dev/null @@ -1,20 +0,0 @@ - [ - 'db' => [ - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', - 'username' => 'root', - 'password' => '', - 'charset' => 'utf8', - ], - 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', - 'viewPath' => '@common/mail', - // send all mails to a file by default. You have to set - // 'useFileTransport' to false and configure a transport - // for the mailer to send real emails. - 'useFileTransport' => true, - ], - ], -]; diff --git a/apps/advanced/environments/dev/common/config/params-local.php b/apps/advanced/environments/dev/common/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/dev/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - ['gii'], - 'modules' => [ - 'gii' => 'yii\gii\Module', - ], -]; diff --git a/apps/advanced/environments/dev/console/config/params-local.php b/apps/advanced/environments/dev/console/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/dev/console/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; - -if (!YII_ENV_TEST) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; -} - -return $config; diff --git a/apps/advanced/environments/dev/frontend/config/params-local.php b/apps/advanced/environments/dev/frontend/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/dev/frontend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/frontend/web/index.php b/apps/advanced/environments/dev/frontend/web/index.php deleted file mode 100644 index 60381674f2..0000000000 --- a/apps/advanced/environments/dev/frontend/web/index.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii deleted file mode 100644 index 8cc5827503..0000000000 --- a/apps/advanced/environments/dev/yii +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env php -run(); -exit($exitCode); diff --git a/apps/advanced/environments/index.php b/apps/advanced/environments/index.php deleted file mode 100644 index 4f35ce5954..0000000000 --- a/apps/advanced/environments/index.php +++ /dev/null @@ -1,61 +0,0 @@ - [ - * 'path' => 'directory storing the local files', - * 'setWritable' => [ - * // list of directories that should be set writable - * ], - * 'setExecutable' => [ - * // list of files that should be set executable - * ], - * 'setCookieValidationKey' => [ - * // list of config files that need to be inserted with automatically generated cookie validation keys - * ], - * 'createSymlink' => [ - * // list of symlinks to be created. Keys are symlinks, and values are the targets. - * ], - * ], - * ]; - * ``` - */ -return [ - 'Development' => [ - 'path' => 'dev', - 'setWritable' => [ - 'backend/runtime', - 'backend/web/assets', - 'frontend/runtime', - 'frontend/web/assets', - ], - 'setExecutable' => [ - 'yii', - ], - 'setCookieValidationKey' => [ - 'backend/config/main-local.php', - 'frontend/config/main-local.php', - ], - ], - 'Production' => [ - 'path' => 'prod', - 'setWritable' => [ - 'backend/runtime', - 'backend/web/assets', - 'frontend/runtime', - 'frontend/web/assets', - ], - 'setExecutable' => [ - 'yii', - ], - 'setCookieValidationKey' => [ - 'backend/config/main-local.php', - 'frontend/config/main-local.php', - ], - ], -]; diff --git a/apps/advanced/environments/prod/backend/config/main-local.php b/apps/advanced/environments/prod/backend/config/main-local.php deleted file mode 100644 index af46ba338b..0000000000 --- a/apps/advanced/environments/prod/backend/config/main-local.php +++ /dev/null @@ -1,9 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; diff --git a/apps/advanced/environments/prod/backend/config/params-local.php b/apps/advanced/environments/prod/backend/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/prod/backend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/prod/common/config/main-local.php b/apps/advanced/environments/prod/common/config/main-local.php deleted file mode 100644 index 84c4d9f1f7..0000000000 --- a/apps/advanced/environments/prod/common/config/main-local.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'db' => [ - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', - 'username' => 'root', - 'password' => '', - 'charset' => 'utf8', - ], - 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', - 'viewPath' => '@common/mail', - ], - ], -]; diff --git a/apps/advanced/environments/prod/common/config/params-local.php b/apps/advanced/environments/prod/common/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/prod/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; diff --git a/apps/advanced/environments/prod/frontend/config/params-local.php b/apps/advanced/environments/prod/frontend/config/params-local.php deleted file mode 100644 index d0b9c34f7d..0000000000 --- a/apps/advanced/environments/prod/frontend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii deleted file mode 100644 index c8b6f3fe8a..0000000000 --- a/apps/advanced/environments/prod/yii +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env php -run(); -exit($exitCode); diff --git a/apps/advanced/frontend/assets/AppAsset.php b/apps/advanced/frontend/assets/AppAsset.php deleted file mode 100644 index 995e3dc301..0000000000 --- a/apps/advanced/frontend/assets/AppAsset.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class AppAsset extends AssetBundle -{ - public $basePath = '@webroot'; - public $baseUrl = '@web'; - public $css = [ - 'css/site.css', - ]; - public $js = [ - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/apps/advanced/frontend/config/.gitignore b/apps/advanced/frontend/config/.gitignore deleted file mode 100644 index 20da318cb2..0000000000 --- a/apps/advanced/frontend/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/frontend/config/bootstrap.php b/apps/advanced/frontend/config/bootstrap.php deleted file mode 100644 index b3d9bbc7f3..0000000000 --- a/apps/advanced/frontend/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-frontend', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], - 'controllerNamespace' => 'frontend\controllers', - 'components' => [ - 'user' => [ - 'identityClass' => 'common\models\User', - 'enableAutoLogin' => true, - ], - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'errorHandler' => [ - 'errorAction' => 'site/error', - ], - ], - 'params' => $params, -]; diff --git a/apps/advanced/frontend/config/params.php b/apps/advanced/frontend/config/params.php deleted file mode 100644 index 7f754b91fe..0000000000 --- a/apps/advanced/frontend/config/params.php +++ /dev/null @@ -1,4 +0,0 @@ - 'admin@example.com', -]; diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php deleted file mode 100644 index c160a45603..0000000000 --- a/apps/advanced/frontend/controllers/SiteController.php +++ /dev/null @@ -1,171 +0,0 @@ - [ - 'class' => AccessControl::className(), - 'only' => ['logout', 'signup'], - 'rules' => [ - [ - 'actions' => ['signup'], - 'allow' => true, - 'roles' => ['?'], - ], - [ - 'actions' => ['logout'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'logout' => ['post'], - ], - ], - ]; - } - - /** - * @inheritdoc - */ - public function actions() - { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - 'captcha' => [ - 'class' => 'yii\captcha\CaptchaAction', - 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ], - ]; - } - - public function actionIndex() - { - return $this->render('index'); - } - - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - - public function actionLogout() - { - Yii::$app->user->logout(); - - return $this->goHome(); - } - - public function actionContact() - { - $model = new ContactForm(); - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - if ($model->sendEmail(Yii::$app->params['adminEmail'])) { - Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.'); - } else { - Yii::$app->session->setFlash('error', 'There was an error sending email.'); - } - - return $this->refresh(); - } else { - return $this->render('contact', [ - 'model' => $model, - ]); - } - } - - public function actionAbout() - { - return $this->render('about'); - } - - public function actionSignup() - { - $model = new SignupForm(); - if ($model->load(Yii::$app->request->post())) { - if ($user = $model->signup()) { - if (Yii::$app->getUser()->login($user)) { - return $this->goHome(); - } - } - } - - return $this->render('signup', [ - 'model' => $model, - ]); - } - - public function actionRequestPasswordReset() - { - $model = new PasswordResetRequestForm(); - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - if ($model->sendEmail()) { - Yii::$app->getSession()->setFlash('success', 'Check your email for further instructions.'); - - return $this->goHome(); - } else { - Yii::$app->getSession()->setFlash('error', 'Sorry, we are unable to reset password for email provided.'); - } - } - - return $this->render('requestPasswordResetToken', [ - 'model' => $model, - ]); - } - - public function actionResetPassword($token) - { - try { - $model = new ResetPasswordForm($token); - } catch (InvalidParamException $e) { - throw new BadRequestHttpException($e->getMessage()); - } - - if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->resetPassword()) { - Yii::$app->getSession()->setFlash('success', 'New password was saved.'); - - return $this->goHome(); - } - - return $this->render('resetPassword', [ - 'model' => $model, - ]); - } -} diff --git a/apps/advanced/frontend/models/ContactForm.php b/apps/advanced/frontend/models/ContactForm.php deleted file mode 100644 index 613abb568d..0000000000 --- a/apps/advanced/frontend/models/ContactForm.php +++ /dev/null @@ -1,59 +0,0 @@ - 'Verification Code', - ]; - } - - /** - * Sends an email to the specified email address using the information collected by this model. - * - * @param string $email the target email address - * @return boolean whether the email was sent - */ - public function sendEmail($email) - { - return Yii::$app->mailer->compose() - ->setTo($email) - ->setFrom([$this->email => $this->name]) - ->setSubject($this->subject) - ->setTextBody($this->body) - ->send(); - } -} diff --git a/apps/advanced/frontend/models/PasswordResetRequestForm.php b/apps/advanced/frontend/models/PasswordResetRequestForm.php deleted file mode 100644 index 20c6810268..0000000000 --- a/apps/advanced/frontend/models/PasswordResetRequestForm.php +++ /dev/null @@ -1,60 +0,0 @@ - 'trim'], - ['email', 'required'], - ['email', 'email'], - ['email', 'exist', - 'targetClass' => '\common\models\User', - 'filter' => ['status' => User::STATUS_ACTIVE], - 'message' => 'There is no user with such email.' - ], - ]; - } - - /** - * Sends an email with a link, for resetting the password. - * - * @return boolean whether the email was send - */ - public function sendEmail() - { - /* @var $user User */ - $user = User::findOne([ - 'status' => User::STATUS_ACTIVE, - 'email' => $this->email, - ]); - - if ($user) { - if (!User::isPasswordResetTokenValid($user->password_reset_token)) { - $user->generatePasswordResetToken(); - } - - if ($user->save()) { - return \Yii::$app->mailer->compose(['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'], ['user' => $user]) - ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot']) - ->setTo($this->email) - ->setSubject('Password reset for ' . \Yii::$app->name) - ->send(); - } - } - - return false; - } -} diff --git a/apps/advanced/frontend/models/ResetPasswordForm.php b/apps/advanced/frontend/models/ResetPasswordForm.php deleted file mode 100644 index 46198f55b1..0000000000 --- a/apps/advanced/frontend/models/ResetPasswordForm.php +++ /dev/null @@ -1,65 +0,0 @@ -_user = User::findByPasswordResetToken($token); - if (!$this->_user) { - throw new InvalidParamException('Wrong password reset token.'); - } - parent::__construct($config); - } - - /** - * @inheritdoc - */ - public function rules() - { - return [ - ['password', 'required'], - ['password', 'string', 'min' => 6], - ]; - } - - /** - * Resets password. - * - * @return boolean if password was reset. - */ - public function resetPassword() - { - $user = $this->_user; - $user->setPassword($this->password); - $user->removePasswordResetToken(); - - return $user->save(); - } -} diff --git a/apps/advanced/frontend/models/SignupForm.php b/apps/advanced/frontend/models/SignupForm.php deleted file mode 100644 index c071f83d01..0000000000 --- a/apps/advanced/frontend/models/SignupForm.php +++ /dev/null @@ -1,58 +0,0 @@ - 'trim'], - ['username', 'required'], - ['username', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This username has already been taken.'], - ['username', 'string', 'min' => 2, 'max' => 255], - - ['email', 'filter', 'filter' => 'trim'], - ['email', 'required'], - ['email', 'email'], - ['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'], - - ['password', 'required'], - ['password', 'string', 'min' => 6], - ]; - } - - /** - * Signs user up. - * - * @return User|null the saved model or null if saving fails - */ - public function signup() - { - if ($this->validate()) { - $user = new User(); - $user->username = $this->username; - $user->email = $this->email; - $user->setPassword($this->password); - $user->generateAuthKey(); - if ($user->save()) { - return $user; - } - } - - return null; - } -} diff --git a/apps/advanced/frontend/runtime/.gitignore b/apps/advanced/frontend/runtime/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/apps/advanced/frontend/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php deleted file mode 100644 index 39d55bdb16..0000000000 --- a/apps/advanced/frontend/views/layouts/main.php +++ /dev/null @@ -1,76 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> -
- 'My Company', - 'brandUrl' => Yii::$app->homeUrl, - 'options' => [ - 'class' => 'navbar-inverse navbar-fixed-top', - ], - ]); - $menuItems = [ - ['label' => 'Home', 'url' => ['/site/index']], - ['label' => 'About', 'url' => ['/site/about']], - ['label' => 'Contact', 'url' => ['/site/contact']], - ]; - if (Yii::$app->user->isGuest) { - $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; - $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; - } else { - $menuItems[] = [ - 'label' => 'Logout (' . Yii::$app->user->identity->username . ')', - 'url' => ['/site/logout'], - 'linkOptions' => ['data-method' => 'post'] - ]; - } - echo Nav::widget([ - 'options' => ['class' => 'navbar-nav navbar-right'], - 'items' => $menuItems, - ]); - NavBar::end(); - ?> - -
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], - ]) ?> - - -
-
- - - - endBody() ?> - - -endPage() ?> diff --git a/apps/advanced/frontend/views/site/about.php b/apps/advanced/frontend/views/site/about.php deleted file mode 100644 index 813ed30424..0000000000 --- a/apps/advanced/frontend/views/site/about.php +++ /dev/null @@ -1,14 +0,0 @@ -title = 'About'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

This is the About page. You may modify the following file to customize its content:

- - -
diff --git a/apps/advanced/frontend/views/site/contact.php b/apps/advanced/frontend/views/site/contact.php deleted file mode 100644 index 60bddd3ff3..0000000000 --- a/apps/advanced/frontend/views/site/contact.php +++ /dev/null @@ -1,37 +0,0 @@ -title = 'Contact'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

- If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. -

- -
-
- 'contact-form']); ?> - field($model, 'name') ?> - field($model, 'email') ?> - field($model, 'subject') ?> - field($model, 'body')->textArea(['rows' => 6]) ?> - field($model, 'verifyCode')->widget(Captcha::className(), [ - 'template' => '
{image}
{input}
', - ]) ?> -
- 'btn btn-primary', 'name' => 'contact-button']) ?> -
- -
-
- -
diff --git a/apps/advanced/frontend/views/site/error.php b/apps/advanced/frontend/views/site/error.php deleted file mode 100644 index b9812c488e..0000000000 --- a/apps/advanced/frontend/views/site/error.php +++ /dev/null @@ -1,27 +0,0 @@ -title = $name; -?> -
- -

title) ?>

- -
- -
- -

- The above error occurred while the Web server was processing your request. -

-

- Please contact us if you think this is a server error. Thank you. -

- -
diff --git a/apps/advanced/frontend/views/site/index.php b/apps/advanced/frontend/views/site/index.php deleted file mode 100644 index a00ee4da64..0000000000 --- a/apps/advanced/frontend/views/site/index.php +++ /dev/null @@ -1,51 +0,0 @@ -title = 'My Yii Application'; -?> -
- -
-

Congratulations!

- -

You have successfully created your Yii-powered application.

- -

Get started with Yii

-
- -
- -
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Documentation »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Forum »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Extensions »

-
-
- -
-
diff --git a/apps/advanced/frontend/views/site/login.php b/apps/advanced/frontend/views/site/login.php deleted file mode 100644 index 647e04bdce..0000000000 --- a/apps/advanced/frontend/views/site/login.php +++ /dev/null @@ -1,32 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please fill out the following fields to login:

- -
-
- 'login-form']); ?> - field($model, 'username') ?> - field($model, 'password')->passwordInput() ?> - field($model, 'rememberMe')->checkbox() ?> -
- If you forgot your password you can . -
-
- 'btn btn-primary', 'name' => 'login-button']) ?> -
- -
-
-
diff --git a/apps/advanced/frontend/views/site/requestPasswordResetToken.php b/apps/advanced/frontend/views/site/requestPasswordResetToken.php deleted file mode 100644 index fa5055c66a..0000000000 --- a/apps/advanced/frontend/views/site/requestPasswordResetToken.php +++ /dev/null @@ -1,27 +0,0 @@ -title = 'Request password reset'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please fill out your email. A link to reset password will be sent there.

- -
-
- 'request-password-reset-form']); ?> - field($model, 'email') ?> -
- 'btn btn-primary']) ?> -
- -
-
-
diff --git a/apps/advanced/frontend/views/site/resetPassword.php b/apps/advanced/frontend/views/site/resetPassword.php deleted file mode 100644 index 45461d032f..0000000000 --- a/apps/advanced/frontend/views/site/resetPassword.php +++ /dev/null @@ -1,27 +0,0 @@ -title = 'Reset password'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please choose your new password:

- -
-
- 'reset-password-form']); ?> - field($model, 'password')->passwordInput() ?> -
- 'btn btn-primary']) ?> -
- -
-
-
diff --git a/apps/advanced/frontend/views/site/signup.php b/apps/advanced/frontend/views/site/signup.php deleted file mode 100644 index cdf0143d86..0000000000 --- a/apps/advanced/frontend/views/site/signup.php +++ /dev/null @@ -1,29 +0,0 @@ -title = 'Signup'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please fill out the following fields to signup:

- -
-
- 'form-signup']); ?> - field($model, 'username') ?> - field($model, 'email') ?> - field($model, 'password')->passwordInput() ?> -
- 'btn btn-primary', 'name' => 'signup-button']) ?> -
- -
-
-
diff --git a/apps/advanced/frontend/web/.gitignore b/apps/advanced/frontend/web/.gitignore deleted file mode 100644 index 25c74e61ed..0000000000 --- a/apps/advanced/frontend/web/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/index.php -/index-test.php diff --git a/apps/advanced/frontend/web/assets/.gitignore b/apps/advanced/frontend/web/assets/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/apps/advanced/frontend/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/apps/advanced/frontend/web/css/site.css b/apps/advanced/frontend/web/css/site.css deleted file mode 100644 index 698be709c7..0000000000 --- a/apps/advanced/frontend/web/css/site.css +++ /dev/null @@ -1,91 +0,0 @@ -html, -body { - height: 100%; -} - -.wrap { - min-height: 100%; - height: auto; - margin: 0 auto -60px; - padding: 0 0 60px; -} - -.wrap > .container { - padding: 70px 15px 20px; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding-top: 20px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.not-set { - color: #c55; - font-style: italic; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.grid-view th { - white-space: nowrap; -} - -.hint-block { - display: block; - margin-top: 5px; - color: #999; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} diff --git a/apps/advanced/frontend/web/favicon.ico b/apps/advanced/frontend/web/favicon.ico deleted file mode 100644 index 580ed732e8..0000000000 Binary files a/apps/advanced/frontend/web/favicon.ico and /dev/null differ diff --git a/apps/advanced/frontend/web/robots.txt b/apps/advanced/frontend/web/robots.txt deleted file mode 100644 index 6f27bb66a3..0000000000 --- a/apps/advanced/frontend/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: \ No newline at end of file diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php deleted file mode 100644 index 5b204889df..0000000000 --- a/apps/advanced/frontend/widgets/Alert.php +++ /dev/null @@ -1,79 +0,0 @@ -getSession()->setFlash('error', 'This is the message'); - * \Yii::$app->getSession()->setFlash('success', 'This is the message'); - * \Yii::$app->getSession()->setFlash('info', 'This is the message'); - * ``` - * - * Multiple messages could be set as follows: - * - * ```php - * \Yii::$app->getSession()->setFlash('error', ['Error 1', 'Error 2']); - * ``` - * - * @author Kartik Visweswaran - * @author Alexander Makarov - */ -class Alert extends \yii\bootstrap\Widget -{ - /** - * @var array the alert types configuration for the flash messages. - * This array is setup as $key => $value, where: - * - $key is the name of the session flash variable - * - $value is the bootstrap alert type (i.e. danger, success, info, warning) - */ - public $alertTypes = [ - 'error' => 'alert-danger', - 'danger' => 'alert-danger', - 'success' => 'alert-success', - 'info' => 'alert-info', - 'warning' => 'alert-warning' - ]; - - /** - * @var array the options for rendering the close button tag. - */ - public $closeButton = []; - - public function init() - { - parent::init(); - - $session = \Yii::$app->getSession(); - $flashes = $session->getAllFlashes(); - $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; - - foreach ($flashes as $type => $data) { - if (isset($this->alertTypes[$type])) { - $data = (array) $data; - foreach ($data as $i => $message) { - /* initialize css class for each alert box */ - $this->options['class'] = $this->alertTypes[$type] . $appendCss; - - /* assign unique id to each alert box */ - $this->options['id'] = $this->getId() . '-' . $type . '-' . $i; - - echo \yii\bootstrap\Alert::widget([ - 'body' => $message, - 'closeButton' => $this->closeButton, - 'options' => $this->options, - ]); - } - - $session->removeFlash($type); - } - } - } -} diff --git a/apps/advanced/init b/apps/advanced/init deleted file mode 100755 index 1eb37fb02c..0000000000 --- a/apps/advanced/init +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env php - - * - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -if (!extension_loaded('openssl')) { - die('The OpenSSL PHP extension is required by Yii2.'); -} - -$params = getParams(); -$root = str_replace('\\', '/', __DIR__); -$envs = require("$root/environments/index.php"); -$envNames = array_keys($envs); - -echo "Yii Application Initialization Tool v1.0\n\n"; - -$envName = null; -if (empty($params['env']) || $params['env'] === '1') { - echo "Which environment do you want the application to be initialized in?\n\n"; - foreach ($envNames as $i => $name) { - echo " [$i] $name\n"; - } - echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; - $answer = trim(fgets(STDIN)); - - if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { - echo "\n Quit initialization.\n"; - exit(0); - } - - if (isset($envNames[$answer])) { - $envName = $envNames[$answer]; - } -} else { - $envName = $params['env']; -} - -if (!in_array($envName, $envNames)) { - $envsList = implode(', ', $envNames); - echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; - exit(2); -} - -$env = $envs[$envName]; - -if (empty($params['env'])) { - echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; - $answer = trim(fgets(STDIN)); - if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit initialization.\n"; - exit(0); - } -} - -echo "\n Start initialization ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) { - break; - } -} - -$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable']; -foreach ($callbacks as $callback) { - if (!empty($env[$callback])) { - $callback($root, $env[$callback]); - } -} - -echo "\n ... initialization completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = []; - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all, $params) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - - - $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} - -function getParams() -{ - $rawParams = []; - if (isset($_SERVER['argv'])) { - $rawParams = $_SERVER['argv']; - array_shift($rawParams); - } - - $params = []; - foreach ($rawParams as $param) { - if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { - $name = $matches[1]; - $params[$name] = isset($matches[3]) ? $matches[3] : true; - } else { - $params[] = $param; - } - } - return $params; -} - -function setWritable($root, $paths) -{ - foreach ($paths as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -function setExecutable($root, $paths) -{ - foreach ($paths as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -function setCookieValidationKey($root, $paths) -{ - foreach ($paths as $file) { - echo " generate cookie validation key in $file\n"; - $file = $root . '/' . $file; - $length = 32; - $bytes = openssl_random_pseudo_bytes($length); - $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); - $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file)); - file_put_contents($file, $content); - } -} - -function createSymlink($links) -{ - foreach ($links as $link => $target) { - echo " symlink $target as $link\n"; - if (!is_link($link)) { - symlink($target, $link); - } - } -} diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat deleted file mode 100644 index e50c242c69..0000000000 --- a/apps/advanced/init.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line init script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%init" %* - -@endlocal diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php deleted file mode 100644 index fd84f479de..0000000000 --- a/apps/advanced/requirements.php +++ /dev/null @@ -1,132 +0,0 @@ -Error'; - echo '

The path to yii framework seems to be incorrect.

'; - echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

'; - echo '

Please refer to the README on how to install Yii.

'; -} - -require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); -$requirementsChecker = new YiiRequirementChecker(); - -$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; -$gdOK = $imagickOK = false; - -if (extension_loaded('imagick')) { - $imagick = new Imagick(); - $imagickFormats = $imagick->queryFormats('PNG'); - if (in_array('PNG', $imagickFormats)) { - $imagickOK = true; - } else { - $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; - } -} - -if (extension_loaded('gd')) { - $gdInfo = gd_info(); - if (!empty($gdInfo['FreeType Support'])) { - $gdOK = true; - } else { - $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; - } -} - -/** - * Adjust requirements according to your application specifics. - */ -$requirements = array( - // Database : - array( - 'name' => 'PDO extension', - 'mandatory' => true, - 'condition' => extension_loaded('pdo'), - 'by' => 'All DB-related classes', - ), - array( - 'name' => 'PDO SQLite extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_sqlite'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for SQLite database.', - ), - array( - 'name' => 'PDO MySQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_mysql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for MySQL database.', - ), - array( - 'name' => 'PDO PostgreSQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_pgsql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for PostgreSQL database.', - ), - // Cache : - array( - 'name' => 'Memcache extension', - 'mandatory' => false, - 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), - 'by' => 'MemCache', - 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' - ), - array( - 'name' => 'APC extension', - 'mandatory' => false, - 'condition' => extension_loaded('apc'), - 'by' => 'ApcCache', - ), - // CAPTCHA: - array( - 'name' => 'GD PHP extension with FreeType support', - 'mandatory' => false, - 'condition' => $gdOK, - 'by' => 'Captcha', - 'memo' => $gdMemo, - ), - array( - 'name' => 'ImageMagick PHP extension with PNG support', - 'mandatory' => false, - 'condition' => $imagickOK, - 'by' => 'Captcha', - 'memo' => $imagickMemo, - ), - // PHP ini : - 'phpExposePhp' => array( - 'name' => 'Expose PHP', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), - 'by' => 'Security reasons', - 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( - 'name' => 'PHP allow url include', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), - 'by' => 'Security reasons', - 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( - 'name' => 'PHP mail SMTP', - 'mandatory' => false, - 'condition' => strlen(ini_get('SMTP')) > 0, - 'by' => 'Email sending', - 'memo' => 'PHP mail SMTP server required', - ), -); -$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/advanced/tests/README.md b/apps/advanced/tests/README.md deleted file mode 100644 index ad7f016727..0000000000 --- a/apps/advanced/tests/README.md +++ /dev/null @@ -1,58 +0,0 @@ -This directory contains various tests for the advanced applications. - -Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). - -After creating and setting up the advanced application, follow these steps to prepare for the tests: - -1. Install Codeception if it's not yet installed: - - ``` - composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" - ``` - - If you've never used Composer for global packages run `composer global status`. It should output: - - ``` - Changed current directory to - ``` - - Then add `/vendor/bin` to you `PATH` environment variable. Now you're able to use `codecept` from command - line globally. - -2. Install faker extension by running the following from template root directory where `composer.json` is: - - ``` - composer require --dev yiisoft/yii2-faker:* - ``` - -3. Create `yii2_advanced_tests` database then update it by applying migrations: - - ``` - codeception/bin/yii migrate - ``` - -4. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in - webserver. In the root directory where `common`, `frontend` etc. are execute the following: - - ``` - php -S localhost:8080 - ``` - -5. Now you can run the tests with the following commands, assuming you are in the `tests/codeception` directory: - - ``` - # frontend tests - cd frontend - codecept build - codecept run - - # backend tests - - cd backend - codecept build - codecept run - - # etc. - ``` - - If you already have run `codecept build` for each application, you can skip that step and run all tests by a single `codecept run`. diff --git a/apps/advanced/tests/codeception.yml b/apps/advanced/tests/codeception.yml deleted file mode 100644 index 1a793edbcf..0000000000 --- a/apps/advanced/tests/codeception.yml +++ /dev/null @@ -1,11 +0,0 @@ -include: - - codeception/common - - codeception/console - - codeception/backend - - codeception/frontend - -paths: - log: codeception/_output - -settings: - colors: true diff --git a/apps/advanced/tests/codeception/_output/.gitignore b/apps/advanced/tests/codeception/_output/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/apps/advanced/tests/codeception/_output/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/apps/advanced/tests/codeception/backend/.gitignore b/apps/advanced/tests/codeception/backend/.gitignore deleted file mode 100644 index 985dbb4239..0000000000 --- a/apps/advanced/tests/codeception/backend/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# these files are auto generated by codeception build -/unit/UnitTester.php -/functional/FunctionalTester.php -/acceptance/AcceptanceTester.php diff --git a/apps/advanced/tests/codeception/backend/_bootstrap.php b/apps/advanced/tests/codeception/backend/_bootstrap.php deleted file mode 100644 index a28a3d27fd..0000000000 --- a/apps/advanced/tests/codeception/backend/_bootstrap.php +++ /dev/null @@ -1,23 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginPage::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.', '.help-block'); -$I->see('Password cannot be blank.', '.help-block'); - -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('admin', 'wrong'); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); -/** Uncomment if using WebDriver - * $I->click('Logout (erau)'); - * $I->dontSeeLink('Logout (erau)'); - * $I->seeLink('Login'); - */ diff --git a/apps/advanced/tests/codeception/backend/acceptance/_bootstrap.php b/apps/advanced/tests/codeception/backend/acceptance/_bootstrap.php deleted file mode 100644 index 411855e3de..0000000000 --- a/apps/advanced/tests/codeception/backend/acceptance/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginPage::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.', '.help-block'); -$I->see('Password cannot be blank.', '.help-block'); - -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('admin', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); diff --git a/apps/advanced/tests/codeception/backend/functional/_bootstrap.php b/apps/advanced/tests/codeception/backend/functional/_bootstrap.php deleted file mode 100644 index 94f3fbd5d7..0000000000 --- a/apps/advanced/tests/codeception/backend/functional/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -run(); -exit($exitCode); diff --git a/apps/advanced/tests/codeception/bin/yii.bat b/apps/advanced/tests/codeception/bin/yii.bat deleted file mode 100644 index d516b3a192..0000000000 --- a/apps/advanced/tests/codeception/bin/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/advanced/tests/codeception/common/.gitignore b/apps/advanced/tests/codeception/common/.gitignore deleted file mode 100644 index 985dbb4239..0000000000 --- a/apps/advanced/tests/codeception/common/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# these files are auto generated by codeception build -/unit/UnitTester.php -/functional/FunctionalTester.php -/acceptance/AcceptanceTester.php diff --git a/apps/advanced/tests/codeception/common/_bootstrap.php b/apps/advanced/tests/codeception/common/_bootstrap.php deleted file mode 100644 index cea3ee57d4..0000000000 --- a/apps/advanced/tests/codeception/common/_bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -actor->fillField('input[name="LoginForm[username]"]', $username); - $this->actor->fillField('input[name="LoginForm[password]"]', $password); - $this->actor->click('login-button'); - } -} diff --git a/apps/advanced/tests/codeception/common/_support/FixtureHelper.php b/apps/advanced/tests/codeception/common/_support/FixtureHelper.php deleted file mode 100644 index cc2a3112b2..0000000000 --- a/apps/advanced/tests/codeception/common/_support/FixtureHelper.php +++ /dev/null @@ -1,60 +0,0 @@ -loadFixtures(); - } - - /** - * Method is called after all suite tests run - */ - public function _afterSuite() - { - $this->unloadFixtures(); - } - - /** - * @inheritdoc - */ - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php', - ], - ]; - } -} diff --git a/apps/advanced/tests/codeception/common/codeception.yml b/apps/advanced/tests/codeception/common/codeception.yml deleted file mode 100644 index e8a3407a48..0000000000 --- a/apps/advanced/tests/codeception/common/codeception.yml +++ /dev/null @@ -1,13 +0,0 @@ -namespace: tests\codeception\common -actor: Tester -paths: - tests: . - log: _output - data: _data - helpers: _support -settings: - bootstrap: _bootstrap.php - suite_class: \PHPUnit_Framework_TestSuite - colors: true - memory_limit: 1024M - log: true diff --git a/apps/advanced/tests/codeception/common/fixtures/UserFixture.php b/apps/advanced/tests/codeception/common/fixtures/UserFixture.php deleted file mode 100644 index 7153c8c0b3..0000000000 --- a/apps/advanced/tests/codeception/common/fixtures/UserFixture.php +++ /dev/null @@ -1,13 +0,0 @@ - 'erau', - 'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI', - // password_0 - 'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne', - 'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490', - 'created_at' => '1392559490', - 'updated_at' => '1392559490', - 'email' => 'sfriesen@jenkins.info', - ], -]; diff --git a/apps/advanced/tests/codeception/common/templates/fixtures/user.php b/apps/advanced/tests/codeception/common/templates/fixtures/user.php deleted file mode 100644 index d3f83b5135..0000000000 --- a/apps/advanced/tests/codeception/common/templates/fixtures/user.php +++ /dev/null @@ -1,17 +0,0 @@ -getSecurity(); - -return [ - 'username' => $faker->userName, - 'email' => $faker->email, - 'auth_key' => $security->generateRandomString(), - 'password_hash' => $security->generatePasswordHash('password_' . $index), - 'password_reset_token' => $security->generateRandomString() . '_' . time(), - 'created_at' => time(), - 'updated_at' => time(), -]; diff --git a/apps/advanced/tests/codeception/common/unit.suite.yml b/apps/advanced/tests/codeception/common/unit.suite.yml deleted file mode 100644 index a0582a5363..0000000000 --- a/apps/advanced/tests/codeception/common/unit.suite.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Codeception Test Suite Configuration - -# suite for unit (internal) tests. -# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. - -class_name: UnitTester diff --git a/apps/advanced/tests/codeception/common/unit/DbTestCase.php b/apps/advanced/tests/codeception/common/unit/DbTestCase.php deleted file mode 100644 index 2159a69caa..0000000000 --- a/apps/advanced/tests/codeception/common/unit/DbTestCase.php +++ /dev/null @@ -1,11 +0,0 @@ - 'bayer.hudson', - 'auth_key' => 'HP187Mvq7Mmm3CTU80dLkGmni_FUH_lR', - //password_0 - 'password_hash' => '$2y$13$EjaPFBnZOQsHdGuHI.xvhuDp1fHpo8hKRSk6yshqa9c5EG8s3C3lO', - 'password_reset_token' => 'ExzkCOaYc1L8IOBs4wdTGGbgNiG3Wz1I_1402312317', - 'created_at' => '1402312317', - 'updated_at' => '1402312317', - 'email' => 'nicole.paucek@schultz.info', - ], -]; diff --git a/apps/advanced/tests/codeception/common/unit/models/LoginFormTest.php b/apps/advanced/tests/codeception/common/unit/models/LoginFormTest.php deleted file mode 100644 index 7fd64f6582..0000000000 --- a/apps/advanced/tests/codeception/common/unit/models/LoginFormTest.php +++ /dev/null @@ -1,94 +0,0 @@ - [ - 'user' => [ - 'class' => 'yii\web\User', - 'identityClass' => 'common\models\User', - ], - ], - ]); - } - - protected function tearDown() - { - Yii::$app->user->logout(); - parent::tearDown(); - } - - public function testLoginNoUser() - { - $model = new LoginForm([ - 'username' => 'not_existing_username', - 'password' => 'not_existing_password', - ]); - - $this->specify('user should not be able to login, when there is no identity', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginWrongPassword() - { - $model = new LoginForm([ - 'username' => 'bayer.hudson', - 'password' => 'wrong_password', - ]); - - $this->specify('user should not be able to login with wrong password', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('error message should be set', $model->errors)->hasKey('password'); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginCorrect() - { - - $model = new LoginForm([ - 'username' => 'bayer.hudson', - 'password' => 'password_0', - ]); - - $this->specify('user should be able to login with correct credentials', function () use ($model) { - expect('model should login user', $model->login())->true(); - expect('error message should not be set', $model->errors)->hasntKey('password'); - expect('user should be logged in', Yii::$app->user->isGuest)->false(); - }); - } - - /** - * @inheritdoc - */ - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/common/unit/fixtures/data/models/user.php' - ], - ]; - } - -} diff --git a/apps/advanced/tests/codeception/config/acceptance.php b/apps/advanced/tests/codeception/config/acceptance.php deleted file mode 100644 index 9318da5b64..0000000000 --- a/apps/advanced/tests/codeception/config/acceptance.php +++ /dev/null @@ -1,7 +0,0 @@ - 'app-common', - 'basePath' => dirname(__DIR__), - ] -); diff --git a/apps/advanced/tests/codeception/config/config.php b/apps/advanced/tests/codeception/config/config.php deleted file mode 100644 index 6ebf80bff1..0000000000 --- a/apps/advanced/tests/codeception/config/config.php +++ /dev/null @@ -1,25 +0,0 @@ - [ - 'fixture' => [ - 'class' => 'yii\faker\FixtureController', - 'fixtureDataPath' => '@tests/codeception/common/fixtures/data', - 'templatePath' => '@tests/codeception/common/templates/fixtures', - 'namespace' => 'tests\codeception\common\fixtures', - ], - ], - 'components' => [ - 'db' => [ - 'dsn' => 'mysql:host=localhost;dbname=yii2_advanced_tests', - ], - 'mailer' => [ - 'useFileTransport' => true, - ], - 'urlManager' => [ - 'showScriptName' => true, - ], - ], -]; diff --git a/apps/advanced/tests/codeception/config/console/unit.php b/apps/advanced/tests/codeception/config/console/unit.php deleted file mode 100644 index 4d3aeb0f5a..0000000000 --- a/apps/advanced/tests/codeception/config/console/unit.php +++ /dev/null @@ -1,14 +0,0 @@ - [ - 'request' => [ - // it's not recommended to run functional tests with CSRF validation enabled - 'enableCsrfValidation' => false, - // but if you absolutely need it set cookie domain to localhost - /* - 'csrfCookie' => [ - 'domain' => 'localhost', - ], - */ - ], - ], -]; \ No newline at end of file diff --git a/apps/advanced/tests/codeception/config/unit.php b/apps/advanced/tests/codeception/config/unit.php deleted file mode 100644 index 6bd08d3acc..0000000000 --- a/apps/advanced/tests/codeception/config/unit.php +++ /dev/null @@ -1,7 +0,0 @@ - $value) { - $inputType = $field === 'body' ? 'textarea' : 'input'; - $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); - } - $this->actor->click('contact-button'); - } -} diff --git a/apps/advanced/tests/codeception/frontend/_pages/SignupPage.php b/apps/advanced/tests/codeception/frontend/_pages/SignupPage.php deleted file mode 100644 index 0e1cefa94c..0000000000 --- a/apps/advanced/tests/codeception/frontend/_pages/SignupPage.php +++ /dev/null @@ -1,27 +0,0 @@ - $value) { - $inputType = $field === 'body' ? 'textarea' : 'input'; - $this->actor->fillField($inputType . '[name="SignupForm[' . $field . ']"]', $value); - } - $this->actor->click('signup-button'); - } -} diff --git a/apps/advanced/tests/codeception/frontend/acceptance.suite.yml b/apps/advanced/tests/codeception/frontend/acceptance.suite.yml deleted file mode 100644 index 1828a04145..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance.suite.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Codeception Test Suite Configuration - -# suite for acceptance tests. -# perform tests in browser using the Selenium-like tools. -# powered by Mink (http://mink.behat.org). -# (tip: that's what your customer will see). -# (tip: test your ajax and javascript by one of Mink drivers). - -# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. - -class_name: AcceptanceTester -modules: - enabled: - - PhpBrowser - - tests\codeception\common\_support\FixtureHelper -# you can use WebDriver instead of PhpBrowser to test javascript and ajax. -# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium -# "restart" option is used by the WebDriver to start each time per test-file new session and cookies, -# it is useful if you want to login in your app in each test. -# - WebDriver - config: - PhpBrowser: -# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO - url: http://localhost:8080 -# WebDriver: -# url: http://localhost:8080 -# browser: firefox -# restart: true diff --git a/apps/advanced/tests/codeception/frontend/acceptance/AboutCept.php b/apps/advanced/tests/codeception/frontend/acceptance/AboutCept.php deleted file mode 100644 index 50bf9bad50..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/AboutCept.php +++ /dev/null @@ -1,10 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/apps/advanced/tests/codeception/frontend/acceptance/ContactCept.php b/apps/advanced/tests/codeception/frontend/acceptance/ContactCept.php deleted file mode 100644 index f7a6bc89e3..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/ContactCept.php +++ /dev/null @@ -1,56 +0,0 @@ -wantTo('ensure that contact works'); - -$contactPage = ContactPage::openBy($I); - -$I->see('Contact', 'h1'); - -$I->amGoingTo('submit contact form with no data'); -$contactPage->submit([]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Contact', 'h1'); -$I->see('Name cannot be blank', '.help-block'); -$I->see('Email cannot be blank', '.help-block'); -$I->see('Subject cannot be blank', '.help-block'); -$I->see('Body cannot be blank', '.help-block'); -$I->see('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with not correct email'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester.email', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see that email adress is wrong'); -$I->dontSee('Name cannot be blank', '.help-block'); -$I->see('Email is not a valid email address.', '.help-block'); -$I->dontSee('Subject cannot be blank', '.help-block'); -$I->dontSee('Body cannot be blank', '.help-block'); -$I->dontSee('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with correct data'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester@example.com', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/advanced/tests/codeception/frontend/acceptance/HomeCept.php b/apps/advanced/tests/codeception/frontend/acceptance/HomeCept.php deleted file mode 100644 index 9566a2ee2f..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/HomeCept.php +++ /dev/null @@ -1,12 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/apps/advanced/tests/codeception/frontend/acceptance/LoginCept.php b/apps/advanced/tests/codeception/frontend/acceptance/LoginCept.php deleted file mode 100644 index 599e24f496..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/LoginCept.php +++ /dev/null @@ -1,34 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginPage::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.', '.help-block'); -$I->see('Password cannot be blank.', '.help-block'); - -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('admin', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); -/** Uncomment if using WebDriver - * $I->click('Logout (erau)'); - * $I->dontSeeLink('Logout (erau)'); - * $I->seeLink('Login'); - */ diff --git a/apps/advanced/tests/codeception/frontend/acceptance/SignupCest.php b/apps/advanced/tests/codeception/frontend/acceptance/SignupCest.php deleted file mode 100644 index ab4b7bba28..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/SignupCest.php +++ /dev/null @@ -1,82 +0,0 @@ - 'tester.email@example.com', - 'username' => 'tester', - ]); - } - - /** - * This method is called when test fails. - * @param \Codeception\Event\FailEvent $event - */ - public function _fail($event) - { - } - - /** - * @param \codeception_frontend\AcceptanceTester $I - * @param \Codeception\Scenario $scenario - */ - public function testUserSignup($I, $scenario) - { - $I->wantTo('ensure that signup works'); - - $signupPage = SignupPage::openBy($I); - $I->see('Signup', 'h1'); - $I->see('Please fill out the following fields to signup:'); - - $I->amGoingTo('submit signup form with no data'); - - $signupPage->submit([]); - - $I->expectTo('see validation errors'); - $I->see('Username cannot be blank.', '.help-block'); - $I->see('Email cannot be blank.', '.help-block'); - $I->see('Password cannot be blank.', '.help-block'); - - $I->amGoingTo('submit signup form with not correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that email address is wrong'); - $I->dontSee('Username cannot be blank.', '.help-block'); - $I->dontSee('Password cannot be blank.', '.help-block'); - $I->see('Email is not a valid email address.', '.help-block'); - - $I->amGoingTo('submit signup form with correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that user logged in'); - $I->seeLink('Logout (tester)'); - } -} diff --git a/apps/advanced/tests/codeception/frontend/acceptance/_bootstrap.php b/apps/advanced/tests/codeception/frontend/acceptance/_bootstrap.php deleted file mode 100644 index b0a40eff2c..0000000000 --- a/apps/advanced/tests/codeception/frontend/acceptance/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/apps/advanced/tests/codeception/frontend/functional/ContactCept.php b/apps/advanced/tests/codeception/frontend/functional/ContactCept.php deleted file mode 100644 index c61d4c2d58..0000000000 --- a/apps/advanced/tests/codeception/frontend/functional/ContactCept.php +++ /dev/null @@ -1,47 +0,0 @@ -wantTo('ensure that contact works'); - -$contactPage = ContactPage::openBy($I); - -$I->see('Contact', 'h1'); - -$I->amGoingTo('submit contact form with no data'); -$contactPage->submit([]); -$I->expectTo('see validations errors'); -$I->see('Contact', 'h1'); -$I->see('Name cannot be blank', '.help-block'); -$I->see('Email cannot be blank', '.help-block'); -$I->see('Subject cannot be blank', '.help-block'); -$I->see('Body cannot be blank', '.help-block'); -$I->see('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with not correct email'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester.email', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -$I->expectTo('see that email adress is wrong'); -$I->dontSee('Name cannot be blank', '.help-block'); -$I->see('Email is not a valid email address.', '.help-block'); -$I->dontSee('Subject cannot be blank', '.help-block'); -$I->dontSee('Body cannot be blank', '.help-block'); -$I->dontSee('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with correct data'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester@example.com', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/advanced/tests/codeception/frontend/functional/HomeCept.php b/apps/advanced/tests/codeception/frontend/functional/HomeCept.php deleted file mode 100644 index f3400614f6..0000000000 --- a/apps/advanced/tests/codeception/frontend/functional/HomeCept.php +++ /dev/null @@ -1,12 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/apps/advanced/tests/codeception/frontend/functional/LoginCept.php b/apps/advanced/tests/codeception/frontend/functional/LoginCept.php deleted file mode 100644 index daca12cf8c..0000000000 --- a/apps/advanced/tests/codeception/frontend/functional/LoginCept.php +++ /dev/null @@ -1,29 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginPage::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.', '.help-block'); -$I->see('Password cannot be blank.', '.help-block'); - -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('admin', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); diff --git a/apps/advanced/tests/codeception/frontend/functional/SignupCest.php b/apps/advanced/tests/codeception/frontend/functional/SignupCest.php deleted file mode 100644 index 525b0373fa..0000000000 --- a/apps/advanced/tests/codeception/frontend/functional/SignupCest.php +++ /dev/null @@ -1,90 +0,0 @@ - 'tester.email@example.com', - 'username' => 'tester', - ]); - } - - /** - * This method is called when test fails. - * @param \Codeception\Event\FailEvent $event - */ - public function _fail($event) - { - - } - - /** - * - * @param \codeception_frontend\FunctionalTester $I - * @param \Codeception\Scenario $scenario - */ - public function testUserSignup($I, $scenario) - { - $I->wantTo('ensure that signup works'); - - $signupPage = SignupPage::openBy($I); - $I->see('Signup', 'h1'); - $I->see('Please fill out the following fields to signup:'); - - $I->amGoingTo('submit signup form with no data'); - - $signupPage->submit([]); - - $I->expectTo('see validation errors'); - $I->see('Username cannot be blank.', '.help-block'); - $I->see('Email cannot be blank.', '.help-block'); - $I->see('Password cannot be blank.', '.help-block'); - - $I->amGoingTo('submit signup form with not correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that email address is wrong'); - $I->dontSee('Username cannot be blank.', '.help-block'); - $I->dontSee('Password cannot be blank.', '.help-block'); - $I->see('Email is not a valid email address.', '.help-block'); - - $I->amGoingTo('submit signup form with correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that user is created'); - $I->seeRecord('common\models\User', [ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - ]); - - $I->expectTo('see that user logged in'); - $I->seeLink('Logout (tester)'); - } -} diff --git a/apps/advanced/tests/codeception/frontend/functional/_bootstrap.php b/apps/advanced/tests/codeception/frontend/functional/_bootstrap.php deleted file mode 100644 index 1abc491110..0000000000 --- a/apps/advanced/tests/codeception/frontend/functional/_bootstrap.php +++ /dev/null @@ -1,3 +0,0 @@ - 'okirlin', - 'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv', - 'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi', - 'password_reset_token' => 't5GU9NwpuGYSfb7FEZMAxqtuz2PkEvv_' . time(), - 'created_at' => '1391885313', - 'updated_at' => '1391885313', - 'email' => 'brady.renner@rutherford.com', - ], - [ - 'username' => 'troy.becker', - 'auth_key' => 'EdKfXrx88weFMV0vIxuTMWKgfK2tS3Lp', - 'password_hash' => '$2y$13$g5nv41Px7VBqhS3hVsVN2.MKfgT3jFdkXEsMC4rQJLfaMa7VaJqL2', - 'password_reset_token' => '4BSNyiZNAuxjs5Mty990c47sVrgllIi_' . time(), - 'created_at' => '1391885313', - 'updated_at' => '1391885313', - 'email' => 'nicolas.dianna@hotmail.com', - 'status' => '0', - ], -]; diff --git a/apps/advanced/tests/codeception/frontend/unit/models/ContactFormTest.php b/apps/advanced/tests/codeception/frontend/unit/models/ContactFormTest.php deleted file mode 100644 index 9aaf595f8b..0000000000 --- a/apps/advanced/tests/codeception/frontend/unit/models/ContactFormTest.php +++ /dev/null @@ -1,59 +0,0 @@ -mailer->fileTransportCallback = function ($mailer, $message) { - return 'testing_message.eml'; - }; - } - - protected function tearDown() - { - unlink($this->getMessageFile()); - parent::tearDown(); - } - - public function testContact() - { - $model = new ContactForm(); - - $model->attributes = [ - 'name' => 'Tester', - 'email' => 'tester@example.com', - 'subject' => 'very important letter subject', - 'body' => 'body of current message', - ]; - - $model->sendEmail('admin@example.com'); - - $this->specify('email should be send', function () { - expect('email file should exist', file_exists($this->getMessageFile()))->true(); - }); - - $this->specify('message should contain correct data', function () use ($model) { - $emailMessage = file_get_contents($this->getMessageFile()); - - expect('email should contain user name', $emailMessage)->contains($model->name); - expect('email should contain sender email', $emailMessage)->contains($model->email); - expect('email should contain subject', $emailMessage)->contains($model->subject); - expect('email should contain body', $emailMessage)->contains($model->body); - }); - } - - private function getMessageFile() - { - return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; - } -} diff --git a/apps/advanced/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php b/apps/advanced/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php deleted file mode 100644 index ec65a0eae3..0000000000 --- a/apps/advanced/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php +++ /dev/null @@ -1,88 +0,0 @@ -mailer->fileTransportCallback = function ($mailer, $message) { - return 'testing_message.eml'; - }; - } - - protected function tearDown() - { - @unlink($this->getMessageFile()); - - parent::tearDown(); - } - - public function testSendEmailWrongUser() - { - $this->specify('no user with such email, message should not be send', function () { - - $model = new PasswordResetRequestForm(); - $model->email = 'not-existing-email@example.com'; - - expect('email not send', $model->sendEmail())->false(); - - }); - - $this->specify('user is not active, message should not be send', function () { - - $model = new PasswordResetRequestForm(); - $model->email = $this->user[1]['email']; - - expect('email not send', $model->sendEmail())->false(); - - }); - } - - public function testSendEmailCorrectUser() - { - $model = new PasswordResetRequestForm(); - $model->email = $this->user[0]['email']; - $user = User::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]); - - expect('email sent', $model->sendEmail())->true(); - expect('user has valid token', $user->password_reset_token)->notNull(); - - $this->specify('message has correct format', function () use ($model) { - - expect('message file exists', file_exists($this->getMessageFile()))->true(); - - $message = file_get_contents($this->getMessageFile()); - expect('message "from" is correct', $message)->contains(Yii::$app->params['supportEmail']); - expect('message "to" is correct', $message)->contains($model->email); - - }); - } - - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php' - ], - ]; - } - - private function getMessageFile() - { - return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; - } - -} diff --git a/apps/advanced/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php b/apps/advanced/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php deleted file mode 100644 index a2f5012400..0000000000 --- a/apps/advanced/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php +++ /dev/null @@ -1,44 +0,0 @@ -user[0]['password_reset_token']); - expect('password should be resetted', $form->resetPassword())->true(); - } - - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php' - ], - ]; - } - -} diff --git a/apps/advanced/tests/codeception/frontend/unit/models/SignupFormTest.php b/apps/advanced/tests/codeception/frontend/unit/models/SignupFormTest.php deleted file mode 100644 index 4d869f1122..0000000000 --- a/apps/advanced/tests/codeception/frontend/unit/models/SignupFormTest.php +++ /dev/null @@ -1,53 +0,0 @@ - 'some_username', - 'email' => 'some_email@example.com', - 'password' => 'some_password', - ]); - - $user = $model->signup(); - - $this->assertInstanceOf('common\models\User', $user, 'user should be valid'); - - expect('username should be correct', $user->username)->equals('some_username'); - expect('email should be correct', $user->email)->equals('some_email@example.com'); - expect('password should be correct', $user->validatePassword('some_password'))->true(); - } - - public function testNotCorrectSignup() - { - $model = new SignupForm([ - 'username' => 'troy.becker', - 'email' => 'nicolas.dianna@hotmail.com', - 'password' => 'some_password', - ]); - - expect('username and email are in use, user should not be created', $model->signup())->null(); - } - - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php', - ], - ]; - } - -} diff --git a/apps/advanced/yii.bat b/apps/advanced/yii.bat deleted file mode 100644 index d516b3a192..0000000000 --- a/apps/advanced/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/basic/.bowerrc b/apps/basic/.bowerrc deleted file mode 100644 index 1669168f29..0000000000 --- a/apps/basic/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory" : "vendor/bower" -} diff --git a/apps/basic/.gitignore b/apps/basic/.gitignore deleted file mode 100644 index 45bf7bf41e..0000000000 --- a/apps/basic/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# phpstorm project files -.idea - -# netbeans project files -nbproject - -# zend studio for eclipse project files -.buildpath -.project -.settings - -# windows thumbnail cache -Thumbs.db - -# composer vendor dir -/vendor - -# composer itself is not needed -composer.phar - -# Mac DS_Store Files -.DS_Store - -# phpunit itself is not needed -phpunit.phar -# local phpunit config -/phpunit.xml diff --git a/apps/basic/LICENSE.md b/apps/basic/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/apps/basic/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/basic/README.md b/apps/basic/README.md deleted file mode 100644 index c5bec59a4e..0000000000 --- a/apps/basic/README.md +++ /dev/null @@ -1,89 +0,0 @@ -Yii 2 Basic Application Template -================================ - -Yii 2 Basic Application Template is a skeleton Yii 2 application best for -rapidly creating small projects. - -The template contains the basic features including user login/logout and a contact page. -It includes all commonly used configurations that would allow you to focus on adding new -features to your application. - - -DIRECTORY STRUCTURE -------------------- - - assets/ contains assets definition - commands/ contains console commands (controllers) - config/ contains application configurations - controllers/ contains Web controller classes - mail/ contains view files for e-mails - models/ contains model classes - runtime/ contains files generated during runtime - tests/ contains various tests for the basic application - vendor/ contains dependent 3rd-party packages - views/ contains view files for the Web application - web/ contains the entry script and Web resources - - - -REQUIREMENTS ------------- - -The minimum requirement by this application template that your Web server supports PHP 5.4.0. - - -INSTALLATION ------------- - -### Install from an Archive File - -Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to -a directory named `basic` that is directly under the Web root. - -You can then access the application through the following URL: - -~~~ -http://localhost/basic/web/ -~~~ - - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions -at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix). - -You can then install this application template using the following command: - -~~~ -php composer.phar global require "fxp/composer-asset-plugin:1.0.0" -php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic -~~~ - -Now you should be able to access the application through the following URL, assuming `basic` is the directory -directly under the Web root. - -~~~ -http://localhost/basic/web/ -~~~ - - -CONFIGURATION -------------- - -### Database - -Edit the file `config/db.php` with real data, for example: - -```php -return [ - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2basic', - 'username' => 'root', - 'password' => '1234', - 'charset' => 'utf8', -]; -``` - -**NOTE:** Yii won't create the database for you, this has to be done manually before you can access it. - -Also check and edit the other files in the `config/` directory to customize your application. diff --git a/apps/basic/assets/AppAsset.php b/apps/basic/assets/AppAsset.php deleted file mode 100644 index 0e495a8f8a..0000000000 --- a/apps/basic/assets/AppAsset.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class AppAsset extends AssetBundle -{ - public $basePath = '@webroot'; - public $baseUrl = '@web'; - public $css = [ - 'css/site.css', - ]; - public $js = [ - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/apps/basic/commands/HelloController.php b/apps/basic/commands/HelloController.php deleted file mode 100644 index 86ab8b8534..0000000000 --- a/apps/basic/commands/HelloController.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @since 2.0 - */ -class HelloController extends Controller -{ - /** - * This command echoes what you have entered as the message. - * @param string $message the message to be echoed. - */ - public function actionIndex($message = 'hello world') - { - echo $message . "\n"; - } -} diff --git a/apps/basic/composer.json b/apps/basic/composer.json deleted file mode 100644 index 56756ec817..0000000000 --- a/apps/basic/composer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "yiisoft/yii2-app-basic", - "description": "Yii 2 Basic Application Template", - "keywords": ["yii2", "framework", "basic", "application template"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "*", - "yiisoft/yii2-bootstrap": "*", - "yiisoft/yii2-swiftmailer": "*" - }, - "require-dev": { - "yiisoft/yii2-codeception": "*", - "yiisoft/yii2-debug": "*", - "yiisoft/yii2-gii": "*", - "yiisoft/yii2-faker": "*" - }, - "config": { - "process-timeout": 1800 - }, - "scripts": { - "post-create-project-cmd": [ - "yii\\composer\\Installer::postCreateProject" - ] - }, - "extra": { - "yii\\composer\\Installer::postCreateProject": { - "setPermission": [ - { - "runtime": "0777", - "web/assets": "0777", - "yii": "0755" - } - ], - "generateCookieValidationKey": [ - "config/web.php" - ] - }, - "asset-installer-paths": { - "npm-asset-library": "vendor/npm", - "bower-asset-library": "vendor/bower" - } - } -} diff --git a/apps/basic/config/console.php b/apps/basic/config/console.php deleted file mode 100644 index 31ed9c720b..0000000000 --- a/apps/basic/config/console.php +++ /dev/null @@ -1,31 +0,0 @@ - 'basic-console', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log', 'gii'], - 'controllerNamespace' => 'app\commands', - 'modules' => [ - 'gii' => 'yii\gii\Module', - ], - 'components' => [ - 'cache' => [ - 'class' => 'yii\caching\FileCache', - ], - 'log' => [ - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'db' => $db, - ], - 'params' => $params, -]; diff --git a/apps/basic/config/db.php b/apps/basic/config/db.php deleted file mode 100644 index c4c12529c1..0000000000 --- a/apps/basic/config/db.php +++ /dev/null @@ -1,9 +0,0 @@ - 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2basic', - 'username' => 'root', - 'password' => '', - 'charset' => 'utf8', -]; diff --git a/apps/basic/config/params.php b/apps/basic/config/params.php deleted file mode 100644 index 6ebf2792b8..0000000000 --- a/apps/basic/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -]; diff --git a/apps/basic/config/web.php b/apps/basic/config/web.php deleted file mode 100644 index 1632a5aab2..0000000000 --- a/apps/basic/config/web.php +++ /dev/null @@ -1,54 +0,0 @@ - 'basic', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], - 'components' => [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - 'cache' => [ - 'class' => 'yii\caching\FileCache', - ], - 'user' => [ - 'identityClass' => 'app\models\User', - 'enableAutoLogin' => true, - ], - 'errorHandler' => [ - 'errorAction' => 'site/error', - ], - 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', - // send all mails to a file by default. You have to set - // 'useFileTransport' to false and configure a transport - // for the mailer to send real emails. - 'useFileTransport' => true, - ], - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'db' => require(__DIR__ . '/db.php'), - ], - 'params' => $params, -]; - -if (YII_ENV_DEV) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; -} - -return $config; diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php deleted file mode 100644 index f95994160e..0000000000 --- a/apps/basic/controllers/SiteController.php +++ /dev/null @@ -1,96 +0,0 @@ - [ - 'class' => AccessControl::className(), - 'only' => ['logout'], - 'rules' => [ - [ - 'actions' => ['logout'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'logout' => ['post'], - ], - ], - ]; - } - - public function actions() - { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - 'captcha' => [ - 'class' => 'yii\captcha\CaptchaAction', - 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ], - ]; - } - - public function actionIndex() - { - return $this->render('index'); - } - - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - - public function actionLogout() - { - Yii::$app->user->logout(); - - return $this->goHome(); - } - - public function actionContact() - { - $model = new ContactForm(); - if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { - Yii::$app->session->setFlash('contactFormSubmitted'); - - return $this->refresh(); - } else { - return $this->render('contact', [ - 'model' => $model, - ]); - } - } - - public function actionAbout() - { - return $this->render('about'); - } -} diff --git a/apps/basic/mail/layouts/html.php b/apps/basic/mail/layouts/html.php deleted file mode 100644 index bddbc61290..0000000000 --- a/apps/basic/mail/layouts/html.php +++ /dev/null @@ -1,22 +0,0 @@ - -beginPage() ?> - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> - - endBody() ?> - - -endPage() ?> diff --git a/apps/basic/models/ContactForm.php b/apps/basic/models/ContactForm.php deleted file mode 100644 index d4052ee938..0000000000 --- a/apps/basic/models/ContactForm.php +++ /dev/null @@ -1,64 +0,0 @@ - 'Verification Code', - ]; - } - - /** - * Sends an email to the specified email address using the information collected by this model. - * @param string $email the target email address - * @return boolean whether the model passes validation - */ - public function contact($email) - { - if ($this->validate()) { - Yii::$app->mailer->compose() - ->setTo($email) - ->setFrom([$this->email => $this->name]) - ->setSubject($this->subject) - ->setTextBody($this->body) - ->send(); - - return true; - } else { - return false; - } - } -} diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php deleted file mode 100644 index 7bd44d402b..0000000000 --- a/apps/basic/models/LoginForm.php +++ /dev/null @@ -1,79 +0,0 @@ -hasErrors()) { - $user = $this->getUser(); - - if (!$user || !$user->validatePassword($this->password)) { - $this->addError($attribute, 'Incorrect username or password.'); - } - } - } - - /** - * Logs in a user using the provided username and password. - * @return boolean whether the user is logged in successfully - */ - public function login() - { - if ($this->validate()) { - return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); - } else { - return false; - } - } - - /** - * Finds user by [[username]] - * - * @return User|null - */ - public function getUser() - { - if ($this->_user === false) { - $this->_user = User::findByUsername($this->username); - } - - return $this->_user; - } -} diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php deleted file mode 100644 index cbfb9fefca..0000000000 --- a/apps/basic/models/User.php +++ /dev/null @@ -1,103 +0,0 @@ - [ - 'id' => '100', - 'username' => 'admin', - 'password' => 'admin', - 'authKey' => 'test100key', - 'accessToken' => '100-token', - ], - '101' => [ - 'id' => '101', - 'username' => 'demo', - 'password' => 'demo', - 'authKey' => 'test101key', - 'accessToken' => '101-token', - ], - ]; - - /** - * @inheritdoc - */ - public static function findIdentity($id) - { - return isset(self::$users[$id]) ? new static(self::$users[$id]) : null; - } - - /** - * @inheritdoc - */ - public static function findIdentityByAccessToken($token, $type = null) - { - foreach (self::$users as $user) { - if ($user['accessToken'] === $token) { - return new static($user); - } - } - - return null; - } - - /** - * Finds user by username - * - * @param string $username - * @return static|null - */ - public static function findByUsername($username) - { - foreach (self::$users as $user) { - if (strcasecmp($user['username'], $username) === 0) { - return new static($user); - } - } - - return null; - } - - /** - * @inheritdoc - */ - public function getId() - { - return $this->id; - } - - /** - * @inheritdoc - */ - public function getAuthKey() - { - return $this->authKey; - } - - /** - * @inheritdoc - */ - public function validateAuthKey($authKey) - { - return $this->authKey === $authKey; - } - - /** - * Validates password - * - * @param string $password password to validate - * @return boolean if password provided is valid for current user - */ - public function validatePassword($password) - { - return $this->password === $password; - } -} diff --git a/apps/basic/requirements.php b/apps/basic/requirements.php deleted file mode 100644 index ee6b2cb533..0000000000 --- a/apps/basic/requirements.php +++ /dev/null @@ -1,132 +0,0 @@ -Error'; - echo '

The path to yii framework seems to be incorrect.

'; - echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

'; - echo '

Please refer to the README on how to install Yii.

'; -} - -require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); -$requirementsChecker = new YiiRequirementChecker(); - -$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; -$gdOK = $imagickOK = false; - -if (extension_loaded('imagick')) { - $imagick = new Imagick(); - $imagickFormats = $imagick->queryFormats('PNG'); - if (in_array('PNG', $imagickFormats)) { - $imagickOK = true; - } else { - $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; - } -} - -if (extension_loaded('gd')) { - $gdInfo = gd_info(); - if (!empty($gdInfo['FreeType Support'])) { - $gdOK = true; - } else { - $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; - } -} - -/** - * Adjust requirements according to your application specifics. - */ -$requirements = array( - // Database : - array( - 'name' => 'PDO extension', - 'mandatory' => true, - 'condition' => extension_loaded('pdo'), - 'by' => 'All DB-related classes', - ), - array( - 'name' => 'PDO SQLite extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_sqlite'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for SQLite database.', - ), - array( - 'name' => 'PDO MySQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_mysql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for MySQL database.', - ), - array( - 'name' => 'PDO PostgreSQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_pgsql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for PostgreSQL database.', - ), - // Cache : - array( - 'name' => 'Memcache extension', - 'mandatory' => false, - 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), - 'by' => 'MemCache', - 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' - ), - array( - 'name' => 'APC extension', - 'mandatory' => false, - 'condition' => extension_loaded('apc'), - 'by' => 'ApcCache', - ), - // CAPTCHA: - array( - 'name' => 'GD PHP extension with FreeType support', - 'mandatory' => false, - 'condition' => $gdOK, - 'by' => 'Captcha', - 'memo' => $gdMemo, - ), - array( - 'name' => 'ImageMagick PHP extension with PNG support', - 'mandatory' => false, - 'condition' => $imagickOK, - 'by' => 'Captcha', - 'memo' => $imagickMemo, - ), - // PHP ini : - 'phpExposePhp' => array( - 'name' => 'Expose PHP', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), - 'by' => 'Security reasons', - 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( - 'name' => 'PHP allow url include', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), - 'by' => 'Security reasons', - 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( - 'name' => 'PHP mail SMTP', - 'mandatory' => false, - 'condition' => strlen(ini_get('SMTP'))>0, - 'by' => 'Email sending', - 'memo' => 'PHP mail SMTP server required', - ), -); -$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/runtime/.gitignore b/apps/basic/runtime/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/apps/basic/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/basic/tests/README.md b/apps/basic/tests/README.md deleted file mode 100644 index 8134fb3a7a..0000000000 --- a/apps/basic/tests/README.md +++ /dev/null @@ -1,111 +0,0 @@ -This directory contains various tests for the basic application. - -Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). - -After creating the basic application, follow these steps to prepare for the tests: - -1. Install Codeception if it's not yet installed: - -``` -composer global require "codeception/codeception=2.0.*" -composer global require "codeception/specify=*" -composer global require "codeception/verify=*" -``` - -If you've never used Composer for global packages run `composer global status`. It should output: - -``` -Changed current directory to -``` - -Then add `/vendor/bin` to you `PATH` environment variable. Now we're able to use `codecept` from command -line globally. - -2. Install faker extension by running the following from template root directory where `composer.json` is: - -``` -composer require --dev yiisoft/yii2-faker:* -``` - -3. Create `yii2_basic_tests` database and update it by applying migrations: - -``` -codeception/bin/yii migrate -``` - -4. Build the test suites: - -``` -codecept build -``` - -5. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in -webserver. In the `web` directory execute the following: - -``` -php -S localhost:8080 -``` - -6. Now you can run the tests with the following commands: - -``` -# run all available tests -codecept run -# run acceptance tests -codecept run acceptance -# run functional tests -codecept run functional -# run unit tests -codecept run unit -``` - -Code coverage support ---------------------- - -By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able -to collect code coverage. You can run your tests and collect coverage with the following command: - -``` -#collect coverage for all tests -codecept run --coverage-html --coverage-xml - -#collect coverage only for unit tests -codecept run unit --coverage-html --coverage-xml - -#collect coverage for unit and functional tests -codecept run functional,unit --coverage-html --coverage-xml -``` - -You can see code coverage output under the `tests/_output` directory. - -###Remote code coverage - -When you run your tests not in the same process where code coverage is collected, then you should uncomment `remote` option and its -related options, to be able to collect code coverage correctly. To setup remote code coverage you should follow [instructions](http://codeception.com/docs/11-Codecoverage) -from codeception site. - -1. install `Codeception c3` remote support `composer require "codeception/c3:*"`; - -2. copy `c3.php` file under your `web` directory; - -3. include `c3.php` file in your `index-test.php` file before application run, so it can catch needed requests. - -Configuration options that are used by remote code coverage: - -- c3_url: url pointing to entry script that includes `c3.php` file, so `Codeception` will be able to produce code coverage; -- remote: whether to enable remote code coverage or not; -- remote_config: path to the `codeception.yml` configuration file, from the directory where `c3.php` file is located. This is needed - so that `Codeception` can create itself instance and collect code coverage correctly. - -By default `c3_url` and `remote_config` setup correctly, you only need to copy and include `c3.php` file in your `index-test.php` - -After that you should be able to collect code coverage from tests that run through `PhpBrowser` or `WebDriver` with same command -as for other tests: - -``` -#collect coverage from remote -codecept run acceptance --coverage-html --coverage-xml -``` - -Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for -more details about writing and running acceptance, functional and unit tests. diff --git a/apps/basic/tests/codeception.yml b/apps/basic/tests/codeception.yml deleted file mode 100644 index b71ba31c1e..0000000000 --- a/apps/basic/tests/codeception.yml +++ /dev/null @@ -1,36 +0,0 @@ -actor: Tester -#coverage: -# #c3_url: http://localhost:8080/index-test.php/ -# enabled: true -# #remote: true -# #remote_config: '../tests/codeception.yml' -# white_list: -# include: -# - ../models/* -# - ../controllers/* -# - ../commands/* -# - ../mail/* -# blacklist: -# include: -# - ../assets/* -# - ../config/* -# - ../runtime/* -# - ../vendor/* -# - ../views/* -# - ../web/* -# - ../tests/* -paths: - tests: codeception - log: codeception/_output - data: codeception/_data - helpers: codeception/_support -settings: - bootstrap: _bootstrap.php - suite_class: \PHPUnit_Framework_TestSuite - memory_limit: 1024M - log: true - colors: true -config: - # the entry script URL (with host info) for functional and acceptance tests - # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL - test_entry_url: http://localhost:8080/index-test.php \ No newline at end of file diff --git a/apps/basic/tests/codeception/.gitignore b/apps/basic/tests/codeception/.gitignore deleted file mode 100644 index 985dbb4239..0000000000 --- a/apps/basic/tests/codeception/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# these files are auto generated by codeception build -/unit/UnitTester.php -/functional/FunctionalTester.php -/acceptance/AcceptanceTester.php diff --git a/apps/basic/tests/codeception/_bootstrap.php b/apps/basic/tests/codeception/_bootstrap.php deleted file mode 100644 index 755029ef33..0000000000 --- a/apps/basic/tests/codeception/_bootstrap.php +++ /dev/null @@ -1,16 +0,0 @@ - $value) { - $inputType = $field === 'body' ? 'textarea' : 'input'; - $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); - } - $this->actor->click('contact-button'); - } -} diff --git a/apps/basic/tests/codeception/_pages/LoginPage.php b/apps/basic/tests/codeception/_pages/LoginPage.php deleted file mode 100644 index c3a2ef2dd7..0000000000 --- a/apps/basic/tests/codeception/_pages/LoginPage.php +++ /dev/null @@ -1,25 +0,0 @@ -actor->fillField('input[name="LoginForm[username]"]', $username); - $this->actor->fillField('input[name="LoginForm[password]"]', $password); - $this->actor->click('login-button'); - } -} diff --git a/apps/basic/tests/codeception/acceptance.suite.yml b/apps/basic/tests/codeception/acceptance.suite.yml deleted file mode 100644 index 1781b00771..0000000000 --- a/apps/basic/tests/codeception/acceptance.suite.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Codeception Test Suite Configuration - -# suite for acceptance tests. -# perform tests in browser using the Selenium-like tools. -# powered by Mink (http://mink.behat.org). -# (tip: that's what your customer will see). -# (tip: test your ajax and javascript by one of Mink drivers). - -# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. - -class_name: AcceptanceTester -modules: - enabled: - - PhpBrowser -# you can use WebDriver instead of PhpBrowser to test javascript and ajax. -# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium -# "restart" option is used by the WebDriver to start each time per test-file new session and cookies, -# it is useful if you want to login in your app in each test. -# - WebDriver - config: - PhpBrowser: -# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO - url: http://localhost:8080 -# WebDriver: -# url: http://localhost:8080 -# browser: firefox -# restart: true diff --git a/apps/basic/tests/codeception/acceptance/AboutCept.php b/apps/basic/tests/codeception/acceptance/AboutCept.php deleted file mode 100644 index 88a550698c..0000000000 --- a/apps/basic/tests/codeception/acceptance/AboutCept.php +++ /dev/null @@ -1,10 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/apps/basic/tests/codeception/acceptance/ContactCept.php b/apps/basic/tests/codeception/acceptance/ContactCept.php deleted file mode 100644 index b51d1c085c..0000000000 --- a/apps/basic/tests/codeception/acceptance/ContactCept.php +++ /dev/null @@ -1,57 +0,0 @@ -wantTo('ensure that contact works'); - -$contactPage = ContactPage::openBy($I); - -$I->see('Contact', 'h1'); - -$I->amGoingTo('submit contact form with no data'); -$contactPage->submit([]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Contact', 'h1'); -$I->see('Name cannot be blank'); -$I->see('Email cannot be blank'); -$I->see('Subject cannot be blank'); -$I->see('Body cannot be blank'); -$I->see('The verification code is incorrect'); - -$I->amGoingTo('submit contact form with not correct email'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester.email', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see that email adress is wrong'); -$I->dontSee('Name cannot be blank', '.help-inline'); -$I->see('Email is not a valid email address.'); -$I->dontSee('Subject cannot be blank', '.help-inline'); -$I->dontSee('Body cannot be blank', '.help-inline'); -$I->dontSee('The verification code is incorrect', '.help-inline'); - -$I->amGoingTo('submit contact form with correct data'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester@example.com', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->dontSeeElement('#contact-form'); -$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/codeception/acceptance/HomeCept.php b/apps/basic/tests/codeception/acceptance/HomeCept.php deleted file mode 100644 index 1f93535958..0000000000 --- a/apps/basic/tests/codeception/acceptance/HomeCept.php +++ /dev/null @@ -1,11 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/apps/basic/tests/codeception/acceptance/LoginCept.php b/apps/basic/tests/codeception/acceptance/LoginCept.php deleted file mode 100644 index 90d0635836..0000000000 --- a/apps/basic/tests/codeception/acceptance/LoginCept.php +++ /dev/null @@ -1,37 +0,0 @@ -wantTo('ensure that login works'); - -$loginPage = LoginPage::openBy($I); - -$I->see('Login', 'h1'); - -$I->amGoingTo('try to login with empty credentials'); -$loginPage->login('', ''); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.'); -$I->see('Password cannot be blank.'); - -$I->amGoingTo('try to login with wrong credentials'); -$loginPage->login('admin', 'wrong'); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('admin', 'admin'); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see user info'); -$I->see('Logout (admin)'); diff --git a/apps/basic/tests/codeception/acceptance/_bootstrap.php b/apps/basic/tests/codeception/acceptance/_bootstrap.php deleted file mode 100644 index 36f9f1d113..0000000000 --- a/apps/basic/tests/codeception/acceptance/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -run(); -exit($exitCode); diff --git a/apps/basic/tests/codeception/bin/yii.bat b/apps/basic/tests/codeception/bin/yii.bat deleted file mode 100644 index d516b3a192..0000000000 --- a/apps/basic/tests/codeception/bin/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/basic/tests/codeception/config/acceptance.php b/apps/basic/tests/codeception/config/acceptance.php deleted file mode 100644 index c688575b60..0000000000 --- a/apps/basic/tests/codeception/config/acceptance.php +++ /dev/null @@ -1,11 +0,0 @@ - [ - 'fixture' => [ - 'class' => 'yii\faker\FixtureController', - 'fixtureDataPath' => '@tests/codeception/fixtures', - 'templatePath' => '@tests/codeception/templates', - 'namespace' => 'tests\codeception\fixtures', - ], - ], - 'components' => [ - 'db' => [ - 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests', - ], - 'mailer' => [ - 'useFileTransport' => true, - ], - 'urlManager' => [ - 'showScriptName' => true, - ], - ], -]; diff --git a/apps/basic/tests/codeception/config/functional.php b/apps/basic/tests/codeception/config/functional.php deleted file mode 100644 index 6d22bd97ba..0000000000 --- a/apps/basic/tests/codeception/config/functional.php +++ /dev/null @@ -1,25 +0,0 @@ - [ - 'request' => [ - // it's not recommended to run functional tests with CSRF validation enabled - 'enableCsrfValidation' => false, - // but if you absolutely need it set cookie domain to localhost - /* - 'csrfCookie' => [ - 'domain' => 'localhost', - ], - */ - ], - ], - ] -); diff --git a/apps/basic/tests/codeception/config/unit.php b/apps/basic/tests/codeception/config/unit.php deleted file mode 100644 index 5bab5eaec0..0000000000 --- a/apps/basic/tests/codeception/config/unit.php +++ /dev/null @@ -1,11 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/apps/basic/tests/codeception/functional/ContactCept.php b/apps/basic/tests/codeception/functional/ContactCept.php deleted file mode 100644 index 074820a702..0000000000 --- a/apps/basic/tests/codeception/functional/ContactCept.php +++ /dev/null @@ -1,48 +0,0 @@ -wantTo('ensure that contact works'); - -$contactPage = ContactPage::openBy($I); - -$I->see('Contact', 'h1'); - -$I->amGoingTo('submit contact form with no data'); -$contactPage->submit([]); -$I->expectTo('see validations errors'); -$I->see('Contact', 'h1'); -$I->see('Name cannot be blank'); -$I->see('Email cannot be blank'); -$I->see('Subject cannot be blank'); -$I->see('Body cannot be blank'); -$I->see('The verification code is incorrect'); - -$I->amGoingTo('submit contact form with not correct email'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester.email', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -$I->expectTo('see that email adress is wrong'); -$I->dontSee('Name cannot be blank', '.help-inline'); -$I->see('Email is not a valid email address.'); -$I->dontSee('Subject cannot be blank', '.help-inline'); -$I->dontSee('Body cannot be blank', '.help-inline'); -$I->dontSee('The verification code is incorrect', '.help-inline'); - -$I->amGoingTo('submit contact form with correct data'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester@example.com', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -$I->dontSeeElement('#contact-form'); -$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/codeception/functional/HomeCept.php b/apps/basic/tests/codeception/functional/HomeCept.php deleted file mode 100644 index 94efb09cdb..0000000000 --- a/apps/basic/tests/codeception/functional/HomeCept.php +++ /dev/null @@ -1,11 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/apps/basic/tests/codeception/functional/LoginCept.php b/apps/basic/tests/codeception/functional/LoginCept.php deleted file mode 100644 index 66e760989a..0000000000 --- a/apps/basic/tests/codeception/functional/LoginCept.php +++ /dev/null @@ -1,28 +0,0 @@ -wantTo('ensure that login works'); - -$loginPage = LoginPage::openBy($I); - -$I->see('Login', 'h1'); - -$I->amGoingTo('try to login with empty credentials'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.'); -$I->see('Password cannot be blank.'); - -$I->amGoingTo('try to login with wrong credentials'); -$loginPage->login('admin', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('admin', 'admin'); -$I->expectTo('see user info'); -$I->see('Logout (admin)'); diff --git a/apps/basic/tests/codeception/functional/_bootstrap.php b/apps/basic/tests/codeception/functional/_bootstrap.php deleted file mode 100644 index 8aac0915b2..0000000000 --- a/apps/basic/tests/codeception/functional/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -mailer->fileTransportCallback = function ($mailer, $message) { - return 'testing_message.eml'; - }; - } - - protected function tearDown() - { - unlink($this->getMessageFile()); - parent::tearDown(); - } - - public function testContact() - { - $model = $this->getMock('app\models\ContactForm', ['validate']); - $model->expects($this->once())->method('validate')->will($this->returnValue(true)); - - $model->attributes = [ - 'name' => 'Tester', - 'email' => 'tester@example.com', - 'subject' => 'very important letter subject', - 'body' => 'body of current message', - ]; - - $model->contact('admin@example.com'); - - $this->specify('email should be send', function () { - expect('email file should exist', file_exists($this->getMessageFile()))->true(); - }); - - $this->specify('message should contain correct data', function () use ($model) { - $emailMessage = file_get_contents($this->getMessageFile()); - - expect('email should contain user name', $emailMessage)->contains($model->name); - expect('email should contain sender email', $emailMessage)->contains($model->email); - expect('email should contain subject', $emailMessage)->contains($model->subject); - expect('email should contain body', $emailMessage)->contains($model->body); - }); - } - - private function getMessageFile() - { - return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; - } - -} diff --git a/apps/basic/tests/codeception/unit/models/LoginFormTest.php b/apps/basic/tests/codeception/unit/models/LoginFormTest.php deleted file mode 100644 index c7f971a06f..0000000000 --- a/apps/basic/tests/codeception/unit/models/LoginFormTest.php +++ /dev/null @@ -1,61 +0,0 @@ -user->logout(); - parent::tearDown(); - } - - public function testLoginNoUser() - { - $model = new LoginForm([ - 'username' => 'not_existing_username', - 'password' => 'not_existing_password', - ]); - - $this->specify('user should not be able to login, when there is no identity', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginWrongPassword() - { - $model = new LoginForm([ - 'username' => 'demo', - 'password' => 'wrong_password', - ]); - - $this->specify('user should not be able to login with wrong password', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('error message should be set', $model->errors)->hasKey('password'); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginCorrect() - { - $model = new LoginForm([ - 'username' => 'demo', - 'password' => 'demo', - ]); - - $this->specify('user should be able to login with correct credentials', function () use ($model) { - expect('model should login user', $model->login())->true(); - expect('error message should not be set', $model->errors)->hasntKey('password'); - expect('user should be logged in', Yii::$app->user->isGuest)->false(); - }); - } - -} diff --git a/apps/basic/tests/codeception/unit/models/UserTest.php b/apps/basic/tests/codeception/unit/models/UserTest.php deleted file mode 100644 index f4f4f4bb9b..0000000000 --- a/apps/basic/tests/codeception/unit/models/UserTest.php +++ /dev/null @@ -1,17 +0,0 @@ -loadFixtures(['user']); - } - - // TODO add test methods here -} diff --git a/apps/basic/tests/codeception/unit/templates/fixtures/.gitkeep b/apps/basic/tests/codeception/unit/templates/fixtures/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php deleted file mode 100644 index d5bba3a048..0000000000 --- a/apps/basic/views/layouts/main.php +++ /dev/null @@ -1,69 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - -beginBody() ?> -
- 'My Company', - 'brandUrl' => Yii::$app->homeUrl, - 'options' => [ - 'class' => 'navbar-inverse navbar-fixed-top', - ], - ]); - echo Nav::widget([ - 'options' => ['class' => 'navbar-nav navbar-right'], - 'items' => [ - ['label' => 'Home', 'url' => ['/site/index']], - ['label' => 'About', 'url' => ['/site/about']], - ['label' => 'Contact', 'url' => ['/site/contact']], - Yii::$app->user->isGuest ? - ['label' => 'Login', 'url' => ['/site/login']] : - ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', - 'url' => ['/site/logout'], - 'linkOptions' => ['data-method' => 'post']], - ], - ]); - NavBar::end(); - ?> - -
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], - ]) ?> - -
-
- -
-
-

© My Company

-

-
-
- -endBody() ?> - - -endPage() ?> diff --git a/apps/basic/views/site/about.php b/apps/basic/views/site/about.php deleted file mode 100644 index 13d85a618d..0000000000 --- a/apps/basic/views/site/about.php +++ /dev/null @@ -1,16 +0,0 @@ -title = 'About'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

- This is the About page. You may modify the following file to customize its content: -

- - -
diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php deleted file mode 100644 index e964a34261..0000000000 --- a/apps/basic/views/site/contact.php +++ /dev/null @@ -1,57 +0,0 @@ -title = 'Contact'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- - session->hasFlash('contactFormSubmitted')): ?> - -
- Thank you for contacting us. We will respond to you as soon as possible. -
- -

- Note that if you turn on the Yii debugger, you should be able - to view the mail message on the mail panel of the debugger. - mailer->useFileTransport): ?> - Because the application is in development mode, the email is not sent but saved as - a file under mailer->fileTransportPath) ?>. - Please configure the useFileTransport property of the mail - application component to be false to enable email sending. - -

- - - -

- If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. -

- -
-
- 'contact-form']); ?> - field($model, 'name') ?> - field($model, 'email') ?> - field($model, 'subject') ?> - field($model, 'body')->textArea(['rows' => 6]) ?> - field($model, 'verifyCode')->widget(Captcha::className(), [ - 'template' => '
{image}
{input}
', - ]) ?> -
- 'btn btn-primary', 'name' => 'contact-button']) ?> -
- -
-
- - -
diff --git a/apps/basic/views/site/error.php b/apps/basic/views/site/error.php deleted file mode 100644 index b9812c488e..0000000000 --- a/apps/basic/views/site/error.php +++ /dev/null @@ -1,27 +0,0 @@ -title = $name; -?> -
- -

title) ?>

- -
- -
- -

- The above error occurred while the Web server was processing your request. -

-

- Please contact us if you think this is a server error. Thank you. -

- -
diff --git a/apps/basic/views/site/index.php b/apps/basic/views/site/index.php deleted file mode 100644 index a00ee4da64..0000000000 --- a/apps/basic/views/site/index.php +++ /dev/null @@ -1,51 +0,0 @@ -title = 'My Yii Application'; -?> -
- -
-

Congratulations!

- -

You have successfully created your Yii-powered application.

- -

Get started with Yii

-
- -
- -
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Documentation »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Forum »

-
-
-

Heading

- -

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

- -

Yii Extensions »

-
-
- -
-
diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php deleted file mode 100644 index 916be9808d..0000000000 --- a/apps/basic/views/site/login.php +++ /dev/null @@ -1,46 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> - diff --git a/apps/basic/web/assets/.gitignore b/apps/basic/web/assets/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/apps/basic/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/apps/basic/web/css/site.css b/apps/basic/web/css/site.css deleted file mode 100644 index 698be709c7..0000000000 --- a/apps/basic/web/css/site.css +++ /dev/null @@ -1,91 +0,0 @@ -html, -body { - height: 100%; -} - -.wrap { - min-height: 100%; - height: auto; - margin: 0 auto -60px; - padding: 0 0 60px; -} - -.wrap > .container { - padding: 70px 15px 20px; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding-top: 20px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.not-set { - color: #c55; - font-style: italic; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.grid-view th { - white-space: nowrap; -} - -.hint-block { - display: block; - margin-top: 5px; - color: #999; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} diff --git a/apps/basic/web/favicon.ico b/apps/basic/web/favicon.ico deleted file mode 100644 index 580ed732e8..0000000000 Binary files a/apps/basic/web/favicon.ico and /dev/null differ diff --git a/apps/basic/web/index-test.php b/apps/basic/web/index-test.php deleted file mode 100644 index 32b4ce3e1e..0000000000 --- a/apps/basic/web/index-test.php +++ /dev/null @@ -1,16 +0,0 @@ -run(); diff --git a/apps/basic/web/index.php b/apps/basic/web/index.php deleted file mode 100644 index d1e070a39e..0000000000 --- a/apps/basic/web/index.php +++ /dev/null @@ -1,12 +0,0 @@ -run(); diff --git a/apps/basic/web/robots.txt b/apps/basic/web/robots.txt deleted file mode 100644 index 6f27bb66a3..0000000000 --- a/apps/basic/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: \ No newline at end of file diff --git a/apps/basic/yii b/apps/basic/yii deleted file mode 100755 index b032ebdbc5..0000000000 --- a/apps/basic/yii +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env php -run(); -exit($exitCode); diff --git a/apps/basic/yii.bat b/apps/basic/yii.bat deleted file mode 100644 index d516b3a192..0000000000 --- a/apps/basic/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/benchmark/LICENSE.md b/apps/benchmark/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/apps/benchmark/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md deleted file mode 100644 index 27f598ad7d..0000000000 --- a/apps/benchmark/README.md +++ /dev/null @@ -1,58 +0,0 @@ -Yii 2 Benchmark Application -=========================== - -**NOTE** Yii 2 and the relevant applications and extensions are still under heavy -development. We may make significant changes without prior notices. Please do not -use them for production. Please consider using [Yii v1.1](https://github.com/yiisoft/yii) -if you have a project to be deployed for production soon. - - -Yii 2 Benchmark Application is an application built to demonstrate the minimal overhead -introduced by the Yii framework. The application contains a single page which only renders -the "hello world" string. - -The application attempts to simulate the scenario in which you can achieve the best performance -when using Yii. It does so by assuming that both of the main application configuration and the page -content are cached in memory, and the application enables pretty URLs. - - -DIRECTORY STRUCTURE -------------------- - - protected/ contains application source code - controllers/ contains Web controller classes - index.php the entry script - - -REQUIREMENTS ------------- - -The minimum requirement by Yii is that your Web server supports PHP 5.4.0. - - -INSTALLATION ------------- - -If you do not have [Composer](http://getcomposer.org/), you may download it from -[http://getcomposer.org/](http://getcomposer.org/) or run the following command on Linux/Unix/MacOS: - -~~~ -curl -s http://getcomposer.org/installer | php -~~~ - -You can then install the Bootstrap Application using the following command: - -~~~ -php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-app-benchmark yii-benchmark -~~~ - -Now you should be able to access the benchmark page using the URL - -~~~ -http://localhost/yii-benchmark/index.php/site/hello -~~~ - -In the above, we assume `yii-benchmark` is directly under the document root of your Web server. - -Note that in order to install some dependencies you must have `php_openssl` extension enabled. - diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json deleted file mode 100644 index c8ed58953a..0000000000 --- a/apps/benchmark/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "yiisoft/yii2-app-benchmark", - "description": "Yii 2 Benchmark Application", - "keywords": ["yii2", "framework", "benchmark", "application"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "config": { - "vendor-dir": "protected/vendor" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "*" - } -} diff --git a/apps/benchmark/index.php b/apps/benchmark/index.php deleted file mode 100644 index c278f1d7e0..0000000000 --- a/apps/benchmark/index.php +++ /dev/null @@ -1,18 +0,0 @@ - 'benchmark', - 'basePath' => __DIR__ . '/protected', - 'components' => [ - 'urlManager' => [ - 'enablePrettyUrl' => true, - ], - ], -]; - -$application = new yii\web\Application($config); -$application->run(); diff --git a/apps/benchmark/protected/.htaccess b/apps/benchmark/protected/.htaccess deleted file mode 100644 index 8d2f25636d..0000000000 --- a/apps/benchmark/protected/.htaccess +++ /dev/null @@ -1 +0,0 @@ -deny from all diff --git a/apps/benchmark/protected/controllers/SiteController.php b/apps/benchmark/protected/controllers/SiteController.php deleted file mode 100644 index 9b08da8711..0000000000 --- a/apps/benchmark/protected/controllers/SiteController.php +++ /dev/null @@ -1,15 +0,0 @@ - - * @since 2.0 - */ -class AppController extends Controller -{ - public $defaultAction = 'link'; - - /** - * Properly removes symlinked directory under Windows, MacOS and Linux - * - * @param string $file path to symlink - */ - protected function unlink($file) - { - if (is_dir($file) && DIRECTORY_SEPARATOR === '\\') { - rmdir($file); - } else { - unlink($file); - } - } - - /** - * This command runs the following shell commands in the dev repo root: - * - * - Run `composer update` - * - `rm -rf apps/basic/vendor/yiisoft/yii2` - * - `rm -rf apps/basic/vendor/yiisoft/yii2-*` - * - * And replaces them with symbolic links to the extensions and framework path in the dev repo. - * @param string $app the application name `basic` or `advanced`. - */ - public function actionLink($app) - { - // root of the dev repo - $base = dirname(dirname(__DIR__)); - $appDir = "$base/apps/$app"; - - // cleanup - if (is_link($link = "$appDir/vendor/yiisoft/yii2")) { - $this->stdout("Removing symlink $link.\n"); - $this->unlink($link); - } - $extensions = $this->findDirs("$appDir/vendor/yiisoft"); - foreach($extensions as $ext) { - if (is_link($link = "$appDir/vendor/yiisoft/yii2-$ext")) { - $this->stdout("Removing symlink $link.\n"); - $this->unlink($link); - } - } - - // composer update - chdir($appDir); - passthru('composer update --prefer-dist'); - - // link directories - if (is_dir($link = "$appDir/vendor/yiisoft/yii2")) { - $this->stdout("Removing dir $link.\n"); - FileHelper::removeDirectory($link); - $this->stdout("Creating symlink for $link.\n"); - symlink("$base/framework", $link); - } - $extensions = $this->findDirs("$appDir/vendor/yiisoft"); - foreach($extensions as $ext) { - if (is_dir($link = "$appDir/vendor/yiisoft/yii2-$ext")) { - $this->stdout("Removing dir $link.\n"); - FileHelper::removeDirectory($link); - $this->stdout("Creating symlink for $link.\n"); - symlink("$base/extensions/$ext", $link); - } - } - - $this->stdout("done.\n"); - } - - /** - * Finds linkable applications - * - * @param string $dir directory to search in - * @return array list of applications command can link - */ - protected function findDirs($dir) - { - $list = []; - $handle = @opendir($dir); - if ($handle === false) { - return []; - } - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (is_dir($path) && preg_match('/^yii2-(.*)$/', $file, $matches)) { - $list[] = $matches[1]; - } - } - closedir($handle); - - foreach($list as $i => $e) { - if ($e == 'composer') { // skip composer to not break composer update - unset($list[$i]); - } - } - - return $list; - } -} diff --git a/build/controllers/DevController.php b/build/controllers/DevController.php index 092adaf5ce..93dc0b6bb9 100644 --- a/build/controllers/DevController.php +++ b/build/controllers/DevController.php @@ -26,6 +26,14 @@ class DevController extends Controller { public $defaultAction = 'all'; +<<<<<<< HEAD +======= + /** + * @var bool whether to use HTTP when cloning github repositories + */ + public $useHttp = false; + +>>>>>>> master public $apps = [ 'basic' => 'git@github.com:yiisoft/yii2-app-basic.git', 'advanced' => 'git@github.com:yiisoft/yii2-app-advanced.git', @@ -42,10 +50,20 @@ class DevController extends Controller 'elasticsearch' => 'git@github.com:yiisoft/yii2-elasticsearch.git', 'faker' => 'git@github.com:yiisoft/yii2-faker.git', 'gii' => 'git@github.com:yiisoft/yii2-gii.git', +<<<<<<< HEAD 'imagine' => 'git@github.com:yiisoft/yii2-imagine.git', 'jui' => 'git@github.com:yiisoft/yii2-jui.git', 'mongodb' => 'git@github.com:yiisoft/yii2-mongodb.git', 'redis' => 'git@github.com:yiisoft/yii2-redis.git', +======= + 'httpclient' => 'git@github.com:yiisoft/yii2-httpclient.git', + 'imagine' => 'git@github.com:yiisoft/yii2-imagine.git', + 'jui' => 'git@github.com:yiisoft/yii2-jui.git', + 'mongodb' => 'git@github.com:yiisoft/yii2-mongodb.git', + 'queue' => 'git@github.com:yiisoft/yii2-queue.git', + 'redis' => 'git@github.com:yiisoft/yii2-redis.git', + 'shell' => 'git@github.com:yiisoft/yii2-shell.git', +>>>>>>> master 'smarty' => 'git@github.com:yiisoft/yii2-smarty.git', 'sphinx' => 'git@github.com:yiisoft/yii2-sphinx.git', 'swiftmailer' => 'git@github.com:yiisoft/yii2-swiftmailer.git', @@ -63,14 +81,22 @@ class DevController extends Controller } foreach($this->extensions as $ext => $repo) { +<<<<<<< HEAD $ret = $this->actionExt($ext, $repo); +======= + $ret = $this->actionExt($ext); +>>>>>>> master if ($ret !== 0) { return $ret; } } foreach($this->apps as $app => $repo) { +<<<<<<< HEAD $ret = $this->actionApp($app, $repo); +======= + $ret = $this->actionApp($app); +>>>>>>> master if ($ret !== 0) { return $ret; } @@ -136,6 +162,12 @@ class DevController extends Controller if (empty($repo)) { if (isset($this->apps[$app])) { $repo = $this->apps[$app]; +<<<<<<< HEAD +======= + if ($this->useHttp) { + $repo = str_replace('git@github.com:', 'https://github.com/', $repo); + } +>>>>>>> master } else { $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED); return 1; @@ -171,6 +203,11 @@ class DevController extends Controller * * @param string $extension the application name e.g. `basic` or `advanced`. * @param string $repo url of the git repo to clone if it does not already exist. +<<<<<<< HEAD +======= + * + * @return int +>>>>>>> master */ public function actionExt($extension, $repo = null) { @@ -182,6 +219,12 @@ class DevController extends Controller if (empty($repo)) { if (isset($this->extensions[$extension])) { $repo = $this->extensions[$extension]; +<<<<<<< HEAD +======= + if ($this->useHttp) { + $repo = str_replace('git@github.com:', 'https://github.com/', $repo); + } +>>>>>>> master } else { $this->stderr("Repo argument is required for extension '$extension'.\n", Console::FG_RED); return 1; @@ -212,7 +255,27 @@ class DevController extends Controller return 0; } +<<<<<<< HEAD +======= + /** + * @inheritdoc + */ + public function options($actionID) + { + $options = parent::options($actionID); + if (in_array($actionID, ['ext', 'app', 'all'], true)) { + $options[] = 'useHttp'; + } + return $options; + } + + + /** + * Remove all symlinks in the vendor subdirectory of the directory specified + * @param string $dir base directory + */ +>>>>>>> master protected function cleanupVendorDir($dir) { if (is_link($link = "$dir/vendor/yiisoft/yii2")) { @@ -228,6 +291,16 @@ class DevController extends Controller } } +<<<<<<< HEAD +======= + /** + * Creates symlinks to freamework and extension sources for the application + * @param string $dir application directory + * @param string $base Yii sources base directory + * + * @return int + */ +>>>>>>> master protected function linkFrameworkAndExtensions($dir, $base) { if (is_dir($link = "$dir/vendor/yiisoft/yii2")) { @@ -267,6 +340,15 @@ class DevController extends Controller } } +<<<<<<< HEAD +======= + /** + * Get a list of subdirectories for directory specified + * @param string $dir directory to read + * + * @return array list of subdirectories + */ +>>>>>>> master protected function listSubDirs($dir) { $list = []; @@ -315,7 +397,11 @@ class DevController extends Controller closedir($handle); foreach($list as $i => $e) { +<<<<<<< HEAD if ($e == 'composer') { // skip composer to not break composer update +======= + if ($e === 'composer') { // skip composer to not break composer update +>>>>>>> master unset($list[$i]); } } diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index c0b390c33c..bd7f6abcdb 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -27,6 +27,10 @@ class PhpDocController extends Controller * for copy and paste. */ public $updateFiles = true; + /** + * @var bool whether to add copyright header to php files. This should be skipped in application code. + */ + public $skipFrameworkRequirements = false; /** @@ -72,7 +76,7 @@ class PhpDocController extends Controller */ public function actionFix($root = null) { - $files = $this->findFiles($root); + $files = $this->findFiles($root, false); $nFilesTotal = 0; $nFilesUpdated = 0; @@ -83,7 +87,9 @@ class PhpDocController extends Controller // fix line endings $lines = preg_split('/(\r\n|\n|\r)/', $contents); - $this->fixFileDoc($lines); + if (!$this->skipFrameworkRequirements) { + $this->fixFileDoc($lines); + } $this->fixDocBlockIndentation($lines); $lines = array_values($this->fixLineSpacing($lines)); @@ -105,12 +111,42 @@ class PhpDocController extends Controller */ public function options($actionID) { - return array_merge(parent::options($actionID), ['updateFiles']); + return array_merge(parent::options($actionID), ['updateFiles', 'skipFrameworkRequirements']); } - protected function findFiles($root) + protected function findFiles($root, $needsInclude = true) { $except = []; + if ($needsInclude) { + $extensionExcept = [ + 'apidoc' => [ + '/helpers/PrettyPrinter.php', + '/extensions/apidoc/helpers/ApiIndexer.php', + '/extensions/apidoc/helpers/ApiMarkdownLaTeX.php', + ], + 'codeception' => [ + '/TestCase.php', + '/DbTestCase.php', + ], + 'gii' => [ + '/components/DiffRendererHtmlInline.php', + '/generators/extension/default/AutoloadExample.php', + ], + 'swiftmailer' => [ + '/Logger.php', + ], + 'twig' => [ + '/Extension.php', + '/Optimizer.php', + '/Template.php', + '/TwigSimpleFileLoader.php', + '/ViewRendererStaticClassProxy.php', + ], + ]; + } else { + $extensionExcept = []; + } + if ($root === null) { $root = dirname(YII2_PATH); $extensionPath = "$root/extensions"; @@ -121,25 +157,61 @@ class PhpDocController extends Controller } $except = [ - '.git/', '/apps/', '/build/', '/docs/', - '/extensions/apidoc/helpers/PrettyPrinter.php', - '/extensions/apidoc/helpers/ApiIndexer.php', - '/extensions/apidoc/helpers/ApiMarkdownLaTeX.php', - '/extensions/codeception/TestCase.php', - '/extensions/codeception/DbTestCase.php', '/extensions/composer/', - '/extensions/gii/components/DiffRendererHtmlInline.php', - '/extensions/gii/generators/extension/default/*', - '/extensions/twig/TwigSimpleFileLoader.php', '/framework/BaseYii.php', '/framework/Yii.php', 'assets/', 'tests/', 'vendor/', ]; + foreach($extensionExcept as $ext => $paths) { + foreach($paths as $path) { + $except[] = "/extensions/$ext$path"; + } + } + } elseif (preg_match('~extensions/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { + + $extensionPath = dirname(rtrim($root, '\\/')); + $this->setUpExtensionAliases($extensionPath); + + list(, $extension) = $matches; + Yii::setAlias("@yii/$extension", "$root"); + if (is_file($autoloadFile = Yii::getAlias("@yii/$extension/vendor/autoload.php"))) { + include($autoloadFile); + } + + if (isset($extensionExcept[$extension])) { + foreach($extensionExcept[$extension] as $path) { + $except[] = $path; + } + } + $except[] = '/vendor/'; + $except[] = '/tests/'; + $except[] = '/docs/'; + +// // composer extension does not contain yii code +// if ($extension === 'composer') { +// return []; +// } + } elseif (preg_match('~apps/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { + + $extensionPath = dirname(dirname(rtrim($root, '\\/'))) . '/extensions'; + $this->setUpExtensionAliases($extensionPath); + + list(, $appName) = $matches; + Yii::setAlias("@app-$appName", "$root"); + if (is_file($autoloadFile = Yii::getAlias("@app-$appName/vendor/autoload.php"))) { + include($autoloadFile); + } + + $except[] = '/runtime/'; + $except[] = '/vendor/'; + $except[] = '/tests/'; + $except[] = '/docs/'; + } $root = FileHelper::normalizePath($root); $options = [ @@ -155,6 +227,7 @@ class PhpDocController extends Controller }, 'only' => ['*.php'], 'except' => array_merge($except, [ + '.git/', 'views/', 'requirements/', 'gii/generators/', @@ -164,6 +237,15 @@ class PhpDocController extends Controller return FileHelper::findFiles($root, $options); } + private function setUpExtensionAliases($extensionPath) + { + foreach (scandir($extensionPath) as $extension) { + if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { + Yii::setAlias("@yii/$extension", "$extensionPath/$extension"); + } + } + } + /** * Fix file PHPdoc */ @@ -243,6 +325,7 @@ class PhpDocController extends Controller $codeBlock = false; $tag = true; $docLine = preg_replace('/\s+/', ' ', $docLine); + $docLine = $this->fixParamTypes($docLine); } elseif (preg_match('/^(~~~|```)/', $docLine)) { $codeBlock = !$codeBlock; $listIndent = ''; @@ -261,6 +344,20 @@ class PhpDocController extends Controller } } + protected function fixParamTypes($line) + { + return preg_replace_callback('~@(param|return) ([\w\\|]+)~i', function($matches) { + $types = explode('|', $matches[2]); + foreach($types as $i => $type) { + switch($type){ + case 'int': $types[$i] = 'integer'; break; + case 'bool': $types[$i] = 'boolean'; break; + } + } + return '@' . $matches[1] . ' ' . implode('|', $types); + }, $line); + } + /** * Fixes line spacing code style for properties and constants */ @@ -269,6 +366,7 @@ class PhpDocController extends Controller $propertiesOnly = false; // remove blank lines between properties $skip = true; + $level = 0; foreach($lines as $i => $line) { if (strpos($line, 'class ') !== false) { $skip = false; @@ -276,8 +374,16 @@ class PhpDocController extends Controller if ($skip) { continue; } + + // keep spaces in multi line arrays + if (strpos($line, '*') === false && strncmp(trim($line), "'SQLSTATE[", 10) !== 0) { + $level += substr_count($line, '[') - substr_count($line, ']'); + } + if (trim($line) === '') { - unset($lines[$i]); + if ($level == 0) { + unset($lines[$i]); + } } elseif (ltrim($line)[0] !== '*' && strpos($line, 'function ') !== false) { break; } elseif (trim($line) === '}') { @@ -380,7 +486,11 @@ class PhpDocController extends Controller if (!$ref->isSubclassOf('yii\base\Object') && $className != 'yii\base\Object') { $this->stderr("[INFO] Skipping class $className as it is not a subclass of yii\\base\\Object.\n", Console::FG_BLUE, Console::BOLD); + return false; + } + if ($ref->isSubclassOf('yii\db\BaseActiveRecord')) { + $this->stderr("[INFO] Skipping class $className as it is an ActiveRecord class, property handling is not supported yet.\n", Console::FG_BLUE, Console::BOLD); return false; } @@ -405,11 +515,13 @@ class PhpDocController extends Controller } } - if (!$seenSince) { - $this->stderr("[ERR] No @since found in class doc in file: $file\n", Console::FG_RED); - } - if (!$seenAuthor) { - $this->stderr("[ERR] No @author found in class doc in file: $file\n", Console::FG_RED); + if (!$this->skipFrameworkRequirements) { + if (!$seenSince) { + $this->stderr("[ERR] No @since found in class doc in file: $file\n", Console::FG_RED); + } + if (!$seenAuthor) { + $this->stderr("[ERR] No @author found in class doc in file: $file\n", Console::FG_RED); + } } if (trim($oldDoc) != trim($newDoc)) { @@ -484,6 +596,12 @@ class PhpDocController extends Controller unset($lines[$i]); } } + + // if no properties or other tags where present add properties at the end + if ($propertyPosition === false) { + $propertyPosition = count($lines) - 2; + } + $finalDoc = ''; foreach ($lines as $i => $line) { $finalDoc .= $line . "\n"; @@ -510,13 +628,13 @@ class PhpDocController extends Controller return false; } if (count($classes) < 1) { - $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.+)\n\}(\n|$)#', $file); + $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.*)\n\}(\n|$)#', $file); if (count($interfaces) == 1) { return false; } elseif (count($interfaces) > 1) { $this->stderr("[ERR] There should be only one interface in a file: $fileName\n", Console::FG_RED); } else { - $traits = $this->match('#\ntrait (?\w+)\n\{(?.+)\n\}(\n|$)#', $file); + $traits = $this->match('#\ntrait (?\w+)\n\{(?.*)\n\}(\n|$)#', $file); if (count($traits) == 1) { return false; } elseif (count($traits) > 1) { diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index 6e5f65f24d..8560b868e2 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -10,65 +10,753 @@ namespace yii\build\controllers; use Yii; use yii\base\Exception; use yii\console\Controller; +use yii\helpers\ArrayHelper; +use yii\helpers\Console; +use yii\helpers\FileHelper; /** - * ReleaseController is there to help preparing releases + * ReleaseController is there to help preparing releases. + * + * Get a version overview: + * + * ./build release/info + * + * run it with `--update` to fetch tags for all repos: + * + * ./build release/info --update + * + * Make a framework release (apps are always in line with framework): + * + * ./build release framework + * ./build release app-basic + * ./build release app-advanced + * + * Make an extension release (e.g. for redis): + * + * ./build release redis + * + * Be sure to check the help info for individual sub-commands: * * @author Carsten Brandt * @since 2.0 */ class ReleaseController extends Controller { - public $defaultAction = 'help'; + public $defaultAction = 'release'; /** - * Usage: - * - * ``` - * ./build/build release/prepare 2.0.0-beta - * ``` - * + * @var string base path to use for releases. */ - public function actionPrepare($version) + public $basePath; + /** + * @var bool whether to make actual changes. If true, it will run without changing or pushing anything. + */ + public $dryRun = false; + /** + * @var bool whether to fetch latest tags. + */ + public $update = false; + + + public function options($actionID) { - $this->resortChangelogs($version); - $this->mergeChangelogs($version); - $this->closeChangelogs($version); - $this->composerSetStability($version); - $this->updateYiiVersion($version); + $options = ['basePath']; + if ($actionID === 'release') { + $options[] = 'dryRun'; + } elseif ($actionID === 'info') { + $options[] = 'update'; + } + return array_merge(parent::options($actionID), $options); + } + + + public function beforeAction($action) + { + if (!$this->interactive) { + throw new Exception('Sorry, but releases should be run interactively to ensure you actually verify what you are doing ;)'); + } + if ($this->basePath === null) { + $this->basePath = dirname(dirname(__DIR__)); + } + $this->basePath = rtrim($this->basePath, '\\/'); + return parent::beforeAction($action); } /** + * Shows information about current framework and extension versions. + */ + public function actionInfo() + { + $items = [ + 'framework', + 'app-basic', + 'app-advanced', + ]; + $extensionPath = "{$this->basePath}/extensions"; + foreach (scandir($extensionPath) as $extension) { + if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { + $items[] = $extension; + } + } + + if ($this->update) { + foreach($items as $item) { + $this->stdout("fetching tags for $item..."); + if ($item === 'framework') { + $this->gitFetchTags("{$this->basePath}"); + } elseif (strncmp('app-', $item, 4) === 0) { + $this->gitFetchTags("{$this->basePath}/apps/" . substr($item, 4)); + } else { + $this->gitFetchTags("{$this->basePath}/extensions/$item"); + } + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + } + } else { + $this->stdout("\nInformation may be outdated, re-run with `--update` to fetch latest tags.\n\n"); + } + + $versions = $this->getCurrentVersions($items); + $nextVersions = $this->getNextVersions($versions, self::PATCH); + + // print version table + $w = $this->minWidth(array_keys($versions)); + $this->stdout(str_repeat(' ', $w + 2) . "Current Version Next Version\n", Console::BOLD); + foreach($versions as $ext => $version) { + $this->stdout($ext . str_repeat(' ', $w + 3 - mb_strlen($ext)) . $version . ""); + $this->stdout(str_repeat(' ', 17 - mb_strlen($version)) . $nextVersions[$ext] . "\n"); + } + + } + + private function minWidth($a) + { + $w = 1; + foreach($a as $s) { + if (($l = mb_strlen($s)) > $w) { + $w = $l; + } + } + return $w; + } + + /** + * Automation tool for making Yii framework and official extension releases. + * + * Usage: + * + * To make a release, make sure your git is clean (no uncommitted changes) and run the following command in + * the yii dev repo root: + * + * ``` + * ./build/build release framework + * ``` + * + * or + * + * ``` + * ./build/build release redis,bootstrap,apidoc + * ``` + * + * You may use the `--dryRun` switch to test the command without changing or pushing anything: + * + * ``` + * ./build/build release redis --dryRun + * ``` + * + * The command will guide you through the complete release process including changing of files, + * committing and pushing them. Each git command must be confirmed and can be skipped individually. + * You may adjust changes in a separate shell or your IDE while the command is waiting for confirmation. + * + * @param array $what what do you want to release? this can either be: + * + * - an extension name such as `redis` or `bootstrap`, + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * - or `framework` if you want to release a new version of the framework itself. + * + * @return int + */ + public function actionRelease(array $what) + { + if (count($what) > 1) { + $this->stdout("Currently only one simultaneous release is supported.\n"); + return 1; + } + + $this->stdout("This is the Yii release manager\n\n", Console::BOLD); + + if ($this->dryRun) { + $this->stdout("Running in \"dry-run\" mode, nothing will actually be changed.\n\n", Console::BOLD, Console::FG_GREEN); + } + + $this->validateWhat($what); + $versions = $this->getCurrentVersions($what); + $newVersions = $this->getNextVersions($versions, self::PATCH);// TODO add support for minor + + $this->stdout("You are about to prepare a new release for the following things:\n\n"); + $this->printWhat($what, $newVersions, $versions); + $this->stdout("\n"); + + $this->stdout("Before you make a release briefly go over the changes and check if you spot obvious mistakes:\n\n", Console::BOLD); + if (strncmp('app-', reset($what), 4) !== 0) { + $this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n"); + $this->stdout("- are all new `@since` tags for this relase version?\n"); + } + $travisUrl = reset($what) === 'framework' ? '' : '-'.reset($what); + $this->stdout("- are unit tests passing on travis? https://travis-ci.org/yiisoft/yii2$travisUrl/builds\n"); + $this->stdout("- other issues with code changes?\n"); + $this->stdout("- also make sure the milestone on github is complete and no issues or PRs are left open.\n\n"); + $this->printWhatUrls($what, $versions); + $this->stdout("\n"); + + if (!$this->confirm('When you continue, this tool will run cleanup jobs and update the changelog as well as other files (locally). Continue?', false)) { + $this->stdout("Canceled.\n"); + return 1; + } + + foreach($what as $ext) { + if ($ext === 'framework') { + $this->releaseFramework("{$this->basePath}/framework", $newVersions['framework']); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->releaseApplication(substr($ext, 4), "{$this->basePath}/apps/" . substr($ext, 4), $newVersions[$ext]); + } else { + $this->releaseExtension($ext, "{$this->basePath}/extensions/$ext", $newVersions[$ext]); + } + } + + return 0; + } + + /** + * This will generate application packages for download page. + * * Usage: * * ``` - * ./build/build release/done 2.0.0-dev 2.0.0-rc + * ./build/build release/package app-basic * ``` + * + * @param array $what what do you want to package? this can either be: + * + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * + * @return int */ - public function actionDone($devVersion, $nextVersion) + public function actionPackage(array $what) { - $this->openChangelogs($nextVersion); - $this->composerSetStability('dev'); - $this->updateYiiVersion($devVersion); + $this->validateWhat($what, ['app']); + $versions = $this->getCurrentVersions($what); + + $this->stdout("You are about to generate packages for the following things:\n\n"); + foreach($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(" - "); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(" application version "); + } elseif ($ext === 'framework') { + $this->stdout(" - Yii Framework version "); + } else { + $this->stdout(" - "); + $this->stdout($ext, Console::FG_RED); + $this->stdout(" extension version "); + } + $this->stdout($versions[$ext], Console::BOLD); + $this->stdout("\n"); + } + $this->stdout("\n"); + + $packagePath = "{$this->basePath}/packages"; + $this->stdout("Packages will be stored in $packagePath\n\n"); + + if (!$this->confirm('Continue?', false)) { + $this->stdout("Canceled.\n"); + return 1; + } + + foreach($what as $ext) { + if ($ext === 'framework') { + throw new Exception('Can not package framework.'); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->packageApplication(substr($ext, 4), $versions[$ext], $packagePath); + } else { + throw new Exception('Can not package extension.'); + } + } + + $this->stdout("\ndone. verify the versions composer installed above and push it to github!\n\n"); + + return 0; } - protected function closeChangelogs($version) + protected function printWhat(array $what, $newVersions, $versions) + { + foreach($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(" - "); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(" application version "); + } elseif ($ext === 'framework') { + $this->stdout(" - Yii Framework version "); + } else { + $this->stdout(" - "); + $this->stdout($ext, Console::FG_RED); + $this->stdout(" extension version "); + } + $this->stdout($newVersions[$ext], Console::BOLD); + $this->stdout(", last release was {$versions[$ext]}\n"); + } + } + + protected function printWhatUrls(array $what, $oldVersions) + { + foreach($what as $ext) { + if ($ext === 'framework') { + $this->stdout("framework: https://github.com/yiisoft/yii2-framework/compare/{$oldVersions[$ext]}...master\n"); + $this->stdout("app-basic: https://github.com/yiisoft/yii2-app-basic/compare/{$oldVersions[$ext]}...master\n"); + $this->stdout("app-advanced: https://github.com/yiisoft/yii2-app-advanced/compare/{$oldVersions[$ext]}...master\n"); + } else { + $this->stdout($ext, Console::FG_RED); + $this->stdout(": https://github.com/yiisoft/yii2-$ext/compare/{$oldVersions[$ext]}...master\n"); + } + } + } + + /** + * @param array $what list of items + * @param array $limit list of things to allow, or empty to allow any, can be `app`, `framework`, `extension` + * @throws \yii\base\Exception + */ + protected function validateWhat(array $what, $limit = []) + { + foreach($what as $w) { + if (strncmp('app-', $w, 4) === 0) { + if (!empty($limit) && !in_array('app', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } + if (!is_dir($appPath = "{$this->basePath}/apps/" . substr($w, 4))) { + throw new Exception("Application path does not exist: \"{$appPath}\"\n"); + } + $this->ensureGitClean($appPath); + } elseif ($w === 'framework') { + if (!empty($limit) && !in_array('framework', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } + if (!is_dir($fwPath = "{$this->basePath}/framework")) { + throw new Exception("Framework path does not exist: \"{$this->basePath}/framework\"\n"); + } + $this->ensureGitClean($fwPath); + } else { + if (!empty($limit) && !in_array('ext', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } + if (!is_dir($extPath = "{$this->basePath}/extensions/$w")) { + throw new Exception("Extension path for \"$w\" does not exist: \"{$this->basePath}/extensions/$w\"\n"); + } + $this->ensureGitClean($extPath); + } + } + } + + + protected function releaseFramework($frameworkPath, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing framework release version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + + $this->runGit('git checkout master', $frameworkPath); // TODO add compatibility for other release branches + $this->runGit('git pull', $frameworkPath); // TODO add compatibility for other release branches + + // checks + + $this->stdout('check if framework composer.json matches yii2-dev composer.json...'); + $this->checkComposer($frameworkPath); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + // adjustments + + $this->stdout('prepare classmap...', Console::BOLD); + $this->dryRun || Yii::$app->runAction('classmap', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating mimetype magic file...', Console::BOLD); + $this->dryRun || Yii::$app->runAction('mime-type', ["$frameworkPath/helpers/mimeTypes.php"]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("fixing various PHPdoc style issues...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPdoc @property annotations...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('sorting changelogs...', Console::BOLD); + $this->dryRun || $this->resortChangelogs(['framework'], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('closing changelogs...', Console::BOLD); + $this->dryRun || $this->closeChangelogs(['framework'], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating Yii version...'); + $this->dryRun || $this->updateYiiVersion($frameworkPath, $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit("git diff --color", $frameworkPath); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while(!$this->confirm("Type `yes` to continue, `no` to view git diff again. Continue?")); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->runGit("git commit -a -m \"release version $version\"", $frameworkPath); + $this->runGit("git tag -a $version -m\"version $version\"", $frameworkPath); + $this->runGit("git push origin master", $frameworkPath); + $this->runGit("git push --tags", $frameworkPath); + + $this->stdout("\n\n"); + $this->stdout("CONGRATULATIONS! You have just released extension ", Console::FG_YELLOW, Console::BOLD); + $this->stdout('framework', Console::FG_RED, Console::BOLD); + $this->stdout(" version ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // TODO release applications + // $this->composerSetStability($what, $version); + + +// $this->resortChangelogs($what, $version); + // $this->closeChangelogs($what, $version); + // $this->composerSetStability($what, $version); + // if (in_array('framework', $what)) { + // $this->updateYiiVersion($version); + // } + + + // if done: + // * ./build/build release/done framework 2.0.0-dev 2.0.0-rc + // * ./build/build release/done redis 2.0.0-dev 2.0.0-rc +// $this->openChangelogs($what, $nextVersion); +// $this->composerSetStability($what, 'dev'); +// if (in_array('framework', $what)) { +// $this->updateYiiVersion($devVersion); +// } + + + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout('opening changelogs...', Console::BOLD); + $nextVersion = $this->getNextVersions(['framework' => $version], self::PATCH); // TODO support other versions + $this->dryRun || $this->openChangelogs(['framework'], $nextVersion['framework']); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating Yii version...'); + $this->dryRun || $this->updateYiiVersion($frameworkPath, $nextVersion['framework'] . '-dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + + $this->stdout("\n"); + $this->runGit("git diff --color", $frameworkPath); + $this->stdout("\n\n"); + $this->runGit("git commit -a -m \"prepare for next release\"", $frameworkPath); + $this->runGit("git push origin master", $frameworkPath); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- wait for your changes to be propagated to the repo and create a tag $version on https://github.com/yiisoft/yii2-framework\n\n"); + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion['framework']} and {$nextVersion2['framework']}: https://github.com/yiisoft/yii2/milestones\n"); + $this->stdout("- create a release on github.\n"); + $this->stdout("- release news and announcement.\n"); + $this->stdout("- update the website (will be automated soon and is only relevant for the new website).\n"); + $this->stdout("\n"); + $this->stdout("- release applications: ./build/build release app-basic\n"); + $this->stdout("- release applications: ./build/build release app-advanced\n"); + + $this->stdout("\n"); + + } + + protected function releaseApplication($name, $path, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing release for application $name version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + + $this->runGit('git checkout master', $path); // TODO add compatibility for other release branches + $this->runGit('git pull', $path); // TODO add compatibility for other release branches + + // adjustments + + $this->stdout("fixing various PHPdoc style issues...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPdoc @property annotations...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit("git diff --color", $path); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while(!$this->confirm("Type `yes` to continue, `no` to view git diff again. Continue?")); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->runGit("git commit -a -m \"release version $version\"", $path); + $this->runGit("git tag -a $version -m\"version $version\"", $path); + $this->runGit("git push origin master", $path); + $this->runGit("git push --tags", $path); + + $this->stdout("\n\n"); + $this->stdout("CONGRATULATIONS! You have just released application ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($name, Console::FG_RED, Console::BOLD); + $this->stdout(" version ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], 'dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $nextVersion = $this->getNextVersions(["app-$name" => $version], self::PATCH); // TODO support other versions + + $this->stdout("\n"); + $this->runGit("git diff --color", $path); + $this->stdout("\n\n"); + $this->runGit("git commit -a -m \"prepare for next release\"", $path); + $this->runGit("git push origin master", $path); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion["app-$name"]} and {$nextVersion2["app-$name"]}: https://github.com/yiisoft/yii2-app-$name/milestones\n"); + $this->stdout("- Create Application packages and upload them to github: ./build release/package app-$name\n"); + + $this->stdout("\n"); + } + + private $_oldAlias; + + protected function setAppAliases($app, $path) + { + $this->_oldAlias = Yii::getAlias('@app'); + switch($app) { + case 'basic': + Yii::setAlias('@app', $path); + break; + case 'advanced': + // setup @frontend, @backend etc... + require("$path/common/config/bootstrap.php"); + break; + } + } + + protected function resetAppAliases() + { + Yii::setAlias('@app', $this->_oldAlias); + } + + protected function packageApplication($name, $version, $packagePath) + { + FileHelper::createDirectory($packagePath); + + $this->runCommand("composer create-project yiisoft/yii2-app-$name $name $version", $packagePath); + // clear cookie validation key in basic app + if (is_file($configFile = "$packagePath/$name/config/web.php")) { + $this->sed( + "/'cookieValidationKey' => '.*?',/", + "'cookieValidationKey' => '',", + $configFile + ); + } + $this->runCommand("tar zcf yii-$name-app-$version.tgz $name", $packagePath); + } + + protected function releaseExtension($name, $path, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing release for extension $name version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + + $this->runGit('git checkout master', $path); // TODO add compatibility for other release branches + $this->runGit('git pull', $path); // TODO add compatibility for other release branches + + // adjustments + + $this->stdout("fixing various PHPdoc style issues...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$path]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPdoc @property annotations...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$path]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('sorting changelogs...', Console::BOLD); + $this->dryRun || $this->resortChangelogs([$name], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('closing changelogs...', Console::BOLD); + $this->dryRun || $this->closeChangelogs([$name], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit("git diff --color", $path); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while(!$this->confirm("Type `yes` to continue, `no` to view git diff again. Continue?")); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->runGit("git commit -a -m \"release version $version\"", $path); + $this->runGit("git tag -a $version -m\"version $version\"", $path); + $this->runGit("git push origin master", $path); + $this->runGit("git push --tags", $path); + + $this->stdout("\n\n"); + $this->stdout("CONGRATULATIONS! You have just released extension ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($name, Console::FG_RED, Console::BOLD); + $this->stdout(" version ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout('opening changelogs...', Console::BOLD); + $nextVersion = $this->getNextVersions([$name => $version], self::PATCH); // TODO support other versions + $this->dryRun || $this->openChangelogs([$name], $nextVersion[$name]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\n"); + $this->runGit("git diff --color", $path); + $this->stdout("\n\n"); + $this->runGit("git commit -a -m \"prepare for next release\"", $path); + $this->runGit("git push origin master", $path); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion[$name]} and {$nextVersion2[$name]}: https://github.com/yiisoft/yii2-$name/milestones\n"); + $this->stdout("- release news and announcement.\n"); + $this->stdout("- update the website (will be automated soon and is only relevant for the new website).\n"); + + $this->stdout("\n"); + } + + + protected function runCommand($cmd, $path) + { + $this->stdout("running $cmd ...", Console::BOLD); + if ($this->dryRun) { + $this->stdout("dry run, command `$cmd` not executed.\n"); + return; + } + chdir($path); + exec($cmd, $output, $ret); + if ($ret != 0) { + echo implode("\n", $output); + throw new Exception("Command \"$cmd\" failed with code " . $ret); + } + $this->stdout("\ndone.\n", Console::BOLD, Console::FG_GREEN); + } + + protected function runGit($cmd, $path) + { + if ($this->confirm("Run `$cmd`?", true)) { + if ($this->dryRun) { + $this->stdout("dry run, command `$cmd` not executed.\n"); + return; + } + chdir($path); + exec($cmd, $output, $ret); + echo implode("\n", $output); + if ($ret != 0) { + throw new Exception("Command \"$cmd\" failed with code " . $ret); + } + echo "\n"; + } + } + + protected function ensureGitClean($path) + { + chdir($path); + exec('git status --porcelain -uno', $changes, $ret); + if ($ret != 0) { + throw new Exception('Command "git status --porcelain -uno" failed with code ' . $ret); + } + if (!empty($changes)) { + throw new Exception("You have uncommitted changes in $path: " . print_r($changes, true)); + } + } + + protected function gitFetchTags($path) + { + chdir($path); + exec('git fetch --tags', $output, $ret); + if ($ret != 0) { + throw new Exception('Command "git fetch --tags" failed with code ' . $ret); + } + } + + + protected function checkComposer($fwPath) + { + if (!$this->confirm("\nNot yet automated: Please check if composer.json dependencies in framework dir match the one in repo root. Continue?", false)) { + exit; + } + } + + + protected function closeChangelogs($what, $version) { $v = str_replace('\\-', '[\\- ]', preg_quote($version, '/')); $headline = $version . ' ' . date('F d, Y'); $this->sed( '/'.$v.' under development\n(-+?)\n/', $headline . "\n" . str_repeat('-', strlen($headline)) . "\n", - $this->getChangelogs() + $this->getChangelogs($what) ); } - protected function openChangelogs($version) + protected function openChangelogs($what, $version) { $headline = "\n$version under development\n"; - $headline .= str_repeat('-', strlen($headline) - 2) . "\n\n"; - $headline .= "- no changes in this release.\n"; - foreach($this->getChangelogs() as $file) { + $headline .= str_repeat('-', strlen($headline) - 2) . "\n\n- no changes in this release.\n"; + foreach($this->getChangelogs($what) as $file) { $lines = explode("\n", file_get_contents($file)); $hl = [ array_shift($lines), @@ -80,9 +768,9 @@ class ReleaseController extends Controller } } - protected function resortChangelogs($version) + protected function resortChangelogs($what, $version) { - foreach($this->getChangelogs() as $file) { + foreach($this->getChangelogs($what) as $file) { // split the file into relevant parts list($start, $changelog, $end) = $this->splitChangelog($file, $version); $changelog = $this->resortChangelog($changelog); @@ -90,31 +778,6 @@ class ReleaseController extends Controller } } - protected function mergeChangelogs($version) - { - $file = $this->getFrameworkChangelog(); - // split the file into relevant parts - list($start, $changelog, $end) = $this->splitChangelog($file, $version); - - $changelog = $this->resortChangelog($changelog); - - $changelog[] = ''; - $extensions = $this->getExtensionChangelogs(); - asort($extensions); - foreach($extensions as $changelogFile) { - if (!preg_match('~extensions/([a-z]+)/CHANGELOG\\.md~', $changelogFile, $m)) { - throw new Exception("Illegal extension changelog file: " . $changelogFile); - } - list( , $extensionChangelog, ) = $this->splitChangelog($changelogFile, $version); - $name = $m[1]; - $ucname = ucfirst($name); - $changelog[] = "### $ucname Extension (yii2-$name)"; - $changelog = array_merge($changelog, $extensionChangelog); - } - - file_put_contents($file, implode("\n", array_merge($start, $changelog, $end))); - } - /** * Extract changelog content for a specific version */ @@ -151,28 +814,68 @@ class ReleaseController extends Controller foreach($changelog as $i => $line) { $changelog[$i] = rtrim($line); } + $changelog = array_filter($changelog); + + $i = 0; + ArrayHelper::multisort($changelog, function($line) use (&$i) { + if (preg_match('/^- (Chg|Enh|Bug|New)( #\d+(, #\d+)*)?: .+$/', $line, $m)) { + $o = ['Bug' => 'C', 'Enh' => 'D', 'Chg' => 'E', 'New' => 'F']; + return $o[$m[1]] . ' ' . (!empty($m[2]) ? $m[2] : 'AAAA' . $i++); + } + return 'B' . $i++; + }, SORT_ASC, SORT_NATURAL); + + // re-add leading and trailing lines + array_unshift($changelog, ''); + $changelog[] = ''; + $changelog[] = ''; - // TODO sorting return $changelog; } - protected function getChangelogs() + protected function getChangelogs($what) { - return array_merge([$this->getFrameworkChangelog()], $this->getExtensionChangelogs()); + $changelogs = []; + if (in_array('framework', $what)) { + $changelogs[] = $this->getFrameworkChangelog(); + } + + return array_merge($changelogs, $this->getExtensionChangelogs($what)); } protected function getFrameworkChangelog() { - return YII2_PATH . '/CHANGELOG.md'; + return $this->basePath . '/framework/CHANGELOG.md'; } - protected function getExtensionChangelogs() + protected function getExtensionChangelogs($what) { - return glob(dirname(YII2_PATH) . '/extensions/*/CHANGELOG.md'); + return array_filter(glob($this->basePath . '/extensions/*/CHANGELOG.md'), function($elem) use ($what) { + foreach($what as $ext) { + if (strpos($elem, "extensions/$ext/CHANGELOG.md") !== false) { + return true; + } + } + return false; + }); } - protected function composerSetStability($version) + protected function composerSetStability($what, $version) { + $apps = []; + if (in_array('app-advanced', $what)) { + $apps[] = $this->basePath . '/apps/advanced/composer.json'; + } + if (in_array('app-basic', $what)) { + $apps[] = $this->basePath . '/apps/basic/composer.json'; + } + if (in_array('app-benchmark', $what)) { + $apps[] = $this->basePath . '/apps/benchmark/composer.json'; + } + if (empty($apps)) { + return; + } + $stability = 'stable'; if (strpos($version, 'alpha') !== false) { $stability = 'alpha'; @@ -187,20 +890,16 @@ class ReleaseController extends Controller $this->sed( '/"minimum-stability": "(.+?)",/', '"minimum-stability": "' . $stability . '",', - [ - dirname(YII2_PATH) . '/apps/advanced/composer.json', - dirname(YII2_PATH) . '/apps/basic/composer.json', - dirname(YII2_PATH) . '/apps/benchmark/composer.json', - ] + $apps ); } - protected function updateYiiVersion($version) + protected function updateYiiVersion($frameworkPath, $version) { $this->sed( '/function getVersion\(\)\n \{\n return \'(.+?)\';/', "function getVersion()\n {\n return '$version';", - YII2_PATH . '/BaseYii.php'); + $frameworkPath . '/BaseYii.php'); } protected function sed($pattern, $replace, $files) @@ -209,4 +908,52 @@ class ReleaseController extends Controller file_put_contents($file, preg_replace($pattern, $replace, file_get_contents($file))); } } + + protected function getCurrentVersions(array $what) + { + $versions = []; + foreach($what as $ext) { + if ($ext === 'framework') { + chdir("{$this->basePath}/framework"); + } elseif (strncmp('app-', $ext, 4) === 0) { + chdir("{$this->basePath}/apps/" . substr($ext, 4)); + } else { + chdir("{$this->basePath}/extensions/$ext"); + } + $tags = []; + exec('git tag', $tags, $ret); + if ($ret != 0) { + throw new Exception('Command "git tag" failed with code ' . $ret); + } + rsort($tags, SORT_NATURAL); // TODO this can not deal with alpha/beta/rc... + $versions[$ext] = reset($tags); + } + return $versions; + } + + const MINOR = 'minor'; + const PATCH = 'patch'; + + protected function getNextVersions(array $versions, $type) + { + foreach($versions as $k => $v) { + if (empty($v)) { + $versions[$k] = '2.0.0'; + continue; + } + $parts = explode('.', $v); + switch($type) { + case self::MINOR: + $parts[1]++; + break; + case self::PATCH: + $parts[2]++; + break; + default: + throw new Exception('Unknown version type.'); + } + $versions[$k] = implode('.', $parts); + } + return $versions; + } } diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000000..0082eb6e08 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,45 @@ +Yii Contributor Code of Conduct +======================= + +As contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to respect all community members. + +We are committed to making participation in this project a good experience for everyone. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting core team members. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/composer.json b/composer.json index fdf62c3198..b2b1b87431 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,11 @@ "name": "Paul Klimov", "email": "klimov.paul@gmail.com", "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" } ], "support": { @@ -53,6 +58,7 @@ }, "minimum-stability": "dev", "replace": { +<<<<<<< HEAD <<<<<<< HEAD "yiisoft/yii2-apidoc": "self.version", "yiisoft/yii2-authclient": "self.version", @@ -72,24 +78,25 @@ "yiisoft/yii2-twig": "self.version", ======= >>>>>>> yiichina/master +======= +>>>>>>> master "yiisoft/yii2": "self.version" }, "require": { "php": ">=5.4.0", "ext-mbstring": "*", + "ext-ctype": "*", "lib-pcre": "*", - "yiisoft/yii2-composer": "*", - "ezyang/htmlpurifier": "4.6.*", + "yiisoft/yii2-composer": "~2.0.4", + "ezyang/htmlpurifier": "~4.6", "cebe/markdown": "~1.0.0 | ~1.1.0", - "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", - "bower-asset/jquery.inputmask": "3.1.*", + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2", "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": "2.0.*", - "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", - "bower-asset/jquery-ui": "1.11.*@stable", - "bower-asset/typeahead.js": "0.10.*" + "bower-asset/yii2-pjax": "~2.0.1" }, "require-dev": { +<<<<<<< HEAD <<<<<<< HEAD "phpunit/phpunit": "3.7.*", ======= @@ -100,24 +107,17 @@ "imagine/imagine": "0.5.*", "swiftmailer/swiftmailer": "*", "fzaninotto/faker": "*", +======= + "phpunit/phpunit": "~4.4", +>>>>>>> master "cebe/indent": "*" }, "suggest": { - "phpdocumentor/reflection": "required by yii2-apidoc extension", - "ext-curl": "required by yii2-elasticsearch extension", - "ext-mongo": "required by yii2-mongo extension", - "ext-pdo": "required by yii2-sphinx extension", - "ext-pdo_mysql": "required by yii2-sphinx extension", - "fzaninotto/faker": "required by yii2-faker extension", - "imagine/imagine": "required by yii2-imagine extension", - "phpspec/php-diff": "required by yii2-gii extension", - "smarty/smarty": "required by yii2-smarty extension", - "swiftmailer/swiftmailer": "required by yii2-swiftmailer extension", - "twig/twig": "required by yii2-twig extension", "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii" }, "autoload": { "psr-4": { +<<<<<<< HEAD <<<<<<< HEAD "yii\\": "framework/", "yii\\apidoc\\": "extensions/apidoc/", @@ -139,6 +139,9 @@ ======= "yii\\": "framework/" >>>>>>> yiichina/master +======= + "yii\\": "framework/" +>>>>>>> master } }, "bin": [ diff --git a/composer.lock b/composer.lock index 756f55d7ca..de2da35091 100644 --- a/composer.lock +++ b/composer.lock @@ -1,9 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], +<<<<<<< HEAD <<<<<<< HEAD "hash": "c3090fa59c9356e92b35f8b13b0348de", "packages": [ @@ -83,13 +84,22 @@ "name": "bower-asset/jquery", <<<<<<< HEAD "version": "2.1.1", +======= + "hash": "d2005b487c5ff761d806b9a94bfe4cac", + "content-hash": "de99885237d7d9364d74fb5f93389801", + "packages": [ + { + "name": "bower-asset/jquery", + "version": "2.2.3", +>>>>>>> master "source": { "type": "git", - "url": "https://github.com/jquery/jquery.git", - "reference": "4dec426aa2a6cbabb1b064319ba7c272d594a688" + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/jquery/jquery/zipball/4dec426aa2a6cbabb1b064319ba7c272d594a688", "reference": "4dec426aa2a6cbabb1b064319ba7c272d594a688", ======= @@ -116,17 +126,16 @@ "bower-asset/sizzle": "2.1.1-patch2" >>>>>>> yiichina/master }, +======= + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198", + "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198", + "shasum": "" + }, +>>>>>>> master "type": "bower-asset-library", "extra": { "bower-asset-main": "dist/jquery.js", "bower-asset-ignore": [ - "**/.*", - "build", - "speed", - "test", - "*.md", - "AUTHORS.txt", - "Gruntfile.js", "package.json" ] }, @@ -134,12 +143,14 @@ "MIT" ], "keywords": [ + "browser", "javascript", "jquery", "library" ] }, { +<<<<<<< HEAD "name": "bower-asset/jquery-ui", <<<<<<< HEAD "version": "1.11.2", @@ -181,13 +192,18 @@ "name": "bower-asset/jquery.inputmask", <<<<<<< HEAD "version": "3.1.38", +======= + "name": "bower-asset/jquery.inputmask", + "version": "3.2.7", +>>>>>>> master "source": { "type": "git", "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "464cddbdea65411896468f40321e68abcc75a34c" + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/464cddbdea65411896468f40321e68abcc75a34c", "reference": "464cddbdea65411896468f40321e68abcc75a34c", ======= @@ -202,6 +218,10 @@ "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/f2c086411d2557fc485c47afb3cecfa6c1de9ee2", "reference": "f2c086411d2557fc485c47afb3cecfa6c1de9ee2", >>>>>>> yiichina/master +======= + "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", +>>>>>>> master "shasum": "" }, "require": { @@ -210,23 +230,17 @@ "type": "bower-asset-library", "extra": { "bower-asset-main": [ - "./dist/inputmask/jquery.inputmask.js", - "./dist/inputmask/jquery.inputmask.extensions.js", - "./dist/inputmask/jquery.inputmask.date.extensions.js", - "./dist/inputmask/jquery.inputmask.numeric.extensions.js", - "./dist/inputmask/jquery.inputmask.phone.extensions.js", - "./dist/inputmask/jquery.inputmask.regex.extensions.js" + "./dist/inputmask/inputmask.js" ], "bower-asset-ignore": [ - "**/.*", - "qunit/", - "nuget/", - "tools/", - "js/", - "*.md", - "build.properties", - "build.xml", - "jquery.inputmask.jquery.json" + "**/*", + "!dist/*", + "!dist/inputmask/*", + "!dist/min/*", + "!dist/min/inputmask/*", + "!extra/bindings/*", + "!extra/dependencyLibs/*", + "!extra/phone-codes/*" ] }, "license": [ @@ -237,11 +251,15 @@ "form", "input", "inputmask", +<<<<<<< HEAD <<<<<<< HEAD "jQuery", ======= "jquery", >>>>>>> yiichina/master +======= + "jquery", +>>>>>>> master "mask", "plugins" ] @@ -274,44 +292,22 @@ ] } }, - { - "name": "bower-asset/typeahead.js", - "version": "v0.10.5", - "source": { - "type": "git", - "url": "https://github.com/twitter/typeahead.js.git", - "reference": "5f198b87d1af845da502ea9df93a5e84801ce742" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/5f198b87d1af845da502ea9df93a5e84801ce742", - "reference": "5f198b87d1af845da502ea9df93a5e84801ce742", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.7" - }, - "require-dev": { - "bower-asset/jasmine-ajax": ">=1.3.1,<1.4", - "bower-asset/jasmine-jquery": ">=1.5.2,<1.6", - "bower-asset/jquery": ">=1.7,<1.8" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "dist/typeahead.bundle.js" - } - }, { "name": "bower-asset/yii2-pjax", +<<<<<<< HEAD <<<<<<< HEAD "version": "v2.0.1", +======= + "version": "v2.0.6", +>>>>>>> master "source": { "type": "git", "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "f07ce95f6098c0bd5421789a20789f39a19be73b" + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/f07ce95f6098c0bd5421789a20789f39a19be73b", "reference": "f07ce95f6098c0bd5421789a20789f39a19be73b", ======= @@ -326,6 +322,10 @@ "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/3f20897307cca046fca5323b318475ae9dac0ca0", "reference": "3f20897307cca046fca5323b318475ae9dac0ca0", >>>>>>> yiichina/master +======= + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", +>>>>>>> master "shasum": "" }, "require": { @@ -336,6 +336,7 @@ "bower-asset-main": "./jquery.pjax.js", "bower-asset-ignore": [ ".travis.yml", +<<<<<<< HEAD <<<<<<< HEAD "test/" ] @@ -347,11 +348,23 @@ "script/", "test/" ] +======= + "Gemfile", + "Gemfile.lock", + "CONTRIBUTING.md", + "vendor/", + "script/", + "test/" + ] +>>>>>>> master }, "license": [ "MIT" ] +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master }, { "name": "cebe/markdown", @@ -359,6 +372,7 @@ "source": { "type": "git", "url": "https://github.com/cebe/markdown.git", +<<<<<<< HEAD <<<<<<< HEAD "reference": "77ba2daada49fc851b397028d26fb2078ec4a3cf" }, @@ -374,6 +388,14 @@ "url": "https://api.github.com/repos/cebe/markdown/zipball/e14d3da8f84eefa3792fd22b5b5ecba9c98d2e18", "reference": "e14d3da8f84eefa3792fd22b5b5ecba9c98d2e18", >>>>>>> yiichina/master +======= + "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", + "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", +>>>>>>> master "shasum": "" }, "require": { @@ -383,11 +405,15 @@ "require-dev": { "cebe/indent": "*", "facebook/xhprof": "*@dev", +<<<<<<< HEAD <<<<<<< HEAD "phpunit/phpunit": "3.7.*" ======= "phpunit/phpunit": "4.1.*" >>>>>>> yiichina/master +======= + "phpunit/phpunit": "4.1.*" +>>>>>>> master }, "bin": [ "bin/markdown" @@ -395,11 +421,15 @@ "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "1.0.x-dev" ======= "dev-master": "1.1.x-dev" >>>>>>> yiichina/master +======= + "dev-master": "1.1.x-dev" +>>>>>>> master } }, "autoload": { @@ -428,24 +458,28 @@ "markdown", "markdown-extra" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-11-07 12:53:11" ======= "time": "2015-03-20 11:07:08" >>>>>>> yiichina/master +======= + "time": "2016-03-18 13:28:11" +>>>>>>> master }, { "name": "ezyang/htmlpurifier", - "version": "v4.6.0", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" + "reference": "ae1828d955112356f7677c465f94f7deb7d27a40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40", + "reference": "ae1828d955112356f7677c465f94f7deb7d27a40", "shasum": "" }, "require": { @@ -468,8 +502,7 @@ { "name": "Edward Z. Yang", "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com", - "role": "Developer" + "homepage": "http://ezyang.com" } ], "description": "Standards compliant HTML filter written in PHP", @@ -477,8 +510,12 @@ "keywords": [ "html" ], +<<<<<<< HEAD "time": "2013-11-30 08:25:19" <<<<<<< HEAD +======= + "time": "2015-08-05 01:03:42" +>>>>>>> master }, { "name": "yiisoft/yii2-composer", @@ -486,16 +523,16 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "0ed315f4b450604d5c2fa5fcacaf27fdde73d062" + "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/0ed315f4b450604d5c2fa5fcacaf27fdde73d062", - "reference": "0ed315f4b450604d5c2fa5fcacaf27fdde73d062", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", + "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", "shasum": "" }, "require": { - "composer-plugin-api": "1.0.0" + "composer-plugin-api": "^1.0" }, "type": "composer-plugin", "extra": { @@ -525,9 +562,13 @@ "extension installer", "yii2" ], +<<<<<<< HEAD "time": "2014-11-07 16:17:16" ======= >>>>>>> yiichina/master +======= + "time": "2016-04-14 08:46:37" +>>>>>>> master } ], "packages-dev": [ @@ -537,12 +578,12 @@ "source": { "type": "git", "url": "https://github.com/cebe/indent.git", - "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776" + "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/indent/zipball/c500ed74d30ed2d7e085f9cf07f8092d32d70776", - "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776", + "url": "https://api.github.com/repos/cebe/indent/zipball/0f33ba3cb567726a726e7024072232839a0d7cd0", + "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0", "shasum": "" }, "bin": [ @@ -556,16 +597,15 @@ "authors": [ { "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" + "email": "mail@cebe.cc" } ], "description": "a small tool to convert text file indentation", - "time": "2014-05-23 14:40:08" + "time": "2015-11-22 14:46:59" }, { <<<<<<< HEAD +<<<<<<< HEAD ======= "name": "doctrine/instantiator", "version": "dev-master", @@ -642,14 +682,30 @@ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/81f8e9439d0041866849c05d334584ea31c7b05e", "reference": "81f8e9439d0041866849c05d334584ea31c7b05e", >>>>>>> yiichina/master +======= + "name": "doctrine/instantiator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/416fb8ad1d095a87f1d21bc40711843cd122fd4a", + "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a", +>>>>>>> master "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.3,<8.0-DEV" }, "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" + "squizlabs/php_codesniffer": "~2.0" }, <<<<<<< HEAD ======= @@ -660,12 +716,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -674,55 +730,62 @@ ], "authors": [ { - "name": "François Zaninotto" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", "keywords": [ - "data", - "faker", - "fixtures" + "constructor", + "instantiate" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-11-12 17:27:49" ======= "time": "2015-03-18 16:46:58" >>>>>>> yiichina/master +======= + "time": "2016-03-31 10:24:22" +>>>>>>> master }, { - "name": "imagine/imagine", - "version": "0.5.x-dev", + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/avalanche123/Imagine.git", - "reference": "343580fceed1f89220481ac98480e92f47d91e6c" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/avalanche123/Imagine/zipball/343580fceed1f89220481ac98480e92f47d91e6c", - "reference": "343580fceed1f89220481ac98480e92f47d91e6c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.3.3" }, "require-dev": { - "sami/sami": "dev-master" + "phpunit/phpunit": "~4.0" }, "suggest": { - "ext-gd": "to use the GD implementation", - "ext-gmagick": "to use the Gmagick implementation", - "ext-imagick": "to use the Imagick implementation" + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.5-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-0": { - "Imagine": "lib/" + "phpDocumentor": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -731,32 +794,86 @@ ], "authors": [ { - "name": "Bulat Shakirzyanov", - "email": "mallluhuct@gmail.com", - "homepage": "http://avalanche123.com" + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" } ], - "description": "Image processing for PHP 5.3", - "homepage": "http://imagine.readthedocs.org/", - "keywords": [ - "drawing", - "graphics", - "image manipulation", - "image processing" + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b02221e42163be673f9b44a0bc92a8b4907a7c6d", + "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "time": "2014-06-13 10:54:04" + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-02-21 17:41:21" }, { <<<<<<< HEAD "name": "phpunit/php-code-coverage", - "version": "1.2.x-dev", + "version": "2.2.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", ======= @@ -882,20 +999,34 @@ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4676604b851bfc6fc02bf3394bf350c727bcebf4", "reference": "4676604b851bfc6fc02bf3394bf350c727bcebf4", >>>>>>> yiichina/master +======= + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", +>>>>>>> master "shasum": "" }, "require": { "php": ">=5.3.3", +<<<<<<< HEAD <<<<<<< HEAD "phpunit/php-file-iterator": ">=1.3.0@stable", "phpunit/php-text-template": ">=1.2.0@stable", "phpunit/php-token-stream": ">=1.1.3,<1.3.0" +======= + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" +>>>>>>> master }, "require-dev": { - "phpunit/phpunit": "3.7.*@dev" + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" }, "suggest": { "ext-dom": "*", +<<<<<<< HEAD "ext-xdebug": ">=2.0.5" ======= "phpunit/php-file-iterator": "~1.3", @@ -913,19 +1044,28 @@ "ext-xdebug": ">=2.2.1", "ext-xmlwriter": "*" >>>>>>> yiichina/master +======= + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" +>>>>>>> master }, "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "1.2.x-dev" ======= "dev-master": "2.0.x-dev" >>>>>>> yiichina/master +======= + "dev-master": "2.2.x-dev" +>>>>>>> master } }, "autoload": { "classmap": [ +<<<<<<< HEAD <<<<<<< HEAD "PHP/" ] @@ -940,6 +1080,12 @@ }, "notification-url": "https://packagist.org/downloads/", >>>>>>> yiichina/master +======= + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", +>>>>>>> master "license": [ "BSD-3-Clause" ], @@ -957,39 +1103,45 @@ "testing", "xunit" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-09-02 10:13:14" ======= "time": "2015-03-19 05:49:08" >>>>>>> yiichina/master +======= + "time": "2015-10-06 15:47:00" +>>>>>>> master }, { "name": "phpunit/php-file-iterator", - "version": "1.3.4", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", "shasum": "" }, "require": { "php": ">=5.3.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, "autoload": { "classmap": [ - "File/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], @@ -1006,20 +1158,20 @@ "filesystem", "iterator" ], - "time": "2013-10-10 15:34:57" + "time": "2015-06-21 13:08:43" }, { "name": "phpunit/php-text-template", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", "shasum": "" }, "require": { @@ -1028,20 +1180,17 @@ "type": "library", "autoload": { "classmap": [ - "Text/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1050,20 +1199,20 @@ "keywords": [ "template" ], - "time": "2014-01-30 17:20:04" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", - "version": "1.0.5", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", "shasum": "" }, "require": { @@ -1072,13 +1221,10 @@ "type": "library", "autoload": { "classmap": [ - "PHP/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], @@ -1094,19 +1240,24 @@ "keywords": [ "timer" ], - "time": "2013-08-02 07:42:54" + "time": "2015-06-21 08:01:12" }, { "name": "phpunit/php-token-stream", +<<<<<<< HEAD <<<<<<< HEAD "version": "1.2.2", +======= + "version": "dev-master", +>>>>>>> master "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", ======= @@ -1121,12 +1272,17 @@ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db32c18eba00b121c145575fcbcd4d4d24e6db74", "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74", >>>>>>> yiichina/master +======= + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cab6c6fefee93d7b7c3a01292a0fe0884ea66644", + "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644", +>>>>>>> master "shasum": "" }, "require": { "ext-tokenizer": "*", "php": ">=5.3.3" }, +<<<<<<< HEAD <<<<<<< HEAD "type": "library", "extra": { @@ -1141,10 +1297,20 @@ "branch-alias": { "dev-master": "1.4-dev" >>>>>>> yiichina/master +======= + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" +>>>>>>> master } }, "autoload": { "classmap": [ +<<<<<<< HEAD <<<<<<< HEAD "PHP/" ] @@ -1159,18 +1325,28 @@ }, "notification-url": "https://packagist.org/downloads/", >>>>>>> yiichina/master +======= + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", +>>>>>>> master "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", +<<<<<<< HEAD <<<<<<< HEAD "email": "sb@sebastian-bergmann.de", "role": "lead" ======= "email": "sebastian@phpunit.de" >>>>>>> yiichina/master +======= + "email": "sebastian@phpunit.de" +>>>>>>> master } ], "description": "Wrapper around PHP's tokenizer extension.", @@ -1178,24 +1354,29 @@ "keywords": [ "tokenizer" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-03-03 05:10:30" +======= + "time": "2015-09-23 14:46:55" +>>>>>>> master }, { "name": "phpunit/phpunit", - "version": "3.7.x-dev", + "version": "4.8.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" + "reference": "3c4becbce99732549949904c47b76ffe602a7595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", - "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c4becbce99732549949904c47b76ffe602a7595", + "reference": "3c4becbce99732549949904c47b76ffe602a7595", "shasum": "" }, "require": { +<<<<<<< HEAD "ext-ctype": "*", ======= "time": "2015-01-17 09:51:32" @@ -1216,12 +1397,15 @@ }, "require": { >>>>>>> yiichina/master +======= +>>>>>>> master "ext-dom": "*", "ext-json": "*", "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", +<<<<<<< HEAD <<<<<<< HEAD "phpunit/php-code-coverage": "~1.2", "phpunit/php-file-iterator": "~1.3", @@ -1232,6 +1416,21 @@ }, "require-dev": { "pear-pear.php.net/pear": "1.9.4" +======= + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" +>>>>>>> master }, ======= "phpspec/prophecy": "~1.3.1", @@ -1253,24 +1452,33 @@ "phpunit/php-invoker": "~1.1" }, "bin": [ +<<<<<<< HEAD <<<<<<< HEAD "composer/bin/phpunit" ======= "phpunit" >>>>>>> yiichina/master +======= + "phpunit" +>>>>>>> master ], "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "3.7.x-dev" ======= "dev-master": "4.5.x-dev" >>>>>>> yiichina/master +======= + "dev-master": "4.8.x-dev" +>>>>>>> master } }, "autoload": { "classmap": [ +<<<<<<< HEAD <<<<<<< HEAD "PHPUnit/" ] @@ -1286,6 +1494,12 @@ }, "notification-url": "https://packagist.org/downloads/", >>>>>>> yiichina/master +======= + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", +>>>>>>> master "license": [ "BSD-3-Clause" ], @@ -1297,38 +1511,49 @@ } ], "description": "The PHP Unit Testing framework.", +<<<<<<< HEAD <<<<<<< HEAD "homepage": "http://www.phpunit.de/", ======= "homepage": "https://phpunit.de/", >>>>>>> yiichina/master +======= + "homepage": "https://phpunit.de/", +>>>>>>> master "keywords": [ "phpunit", "testing", "xunit" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-10-17 09:04:17" +======= + "time": "2016-04-25 09:17:33" +>>>>>>> master }, { "name": "phpunit/phpunit-mock-objects", - "version": "1.2.x-dev", + "version": "2.3.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "c39c4511c3b007539eb170c32cbc2af49a07351a" + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c39c4511c3b007539eb170c32cbc2af49a07351a", - "reference": "c39c4511c3b007539eb170c32cbc2af49a07351a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.0.2", "php": ">=5.3.3", - "phpunit/php-text-template": ">=1.1.1@stable" + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" }, "require-dev": { +<<<<<<< HEAD "phpunit/phpunit": "3.7.*@dev" ======= "time": "2015-03-02 06:58:30" @@ -1355,6 +1580,9 @@ "require-dev": { "phpunit/phpunit": "4.4.*@dev" >>>>>>> yiichina/master +======= + "phpunit/phpunit": "~4.4" +>>>>>>> master }, "suggest": { "ext-soap": "*" @@ -1362,15 +1590,20 @@ "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "1.2.x-dev" ======= "dev-master": "2.4.x-dev" >>>>>>> yiichina/master +======= + "dev-master": "2.3.x-dev" +>>>>>>> master } }, "autoload": { "classmap": [ +<<<<<<< HEAD <<<<<<< HEAD "PHPUnit/" ] @@ -1385,6 +1618,12 @@ }, "notification-url": "https://packagist.org/downloads/", >>>>>>> yiichina/master +======= + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", +>>>>>>> master "license": [ "BSD-3-Clause" ], @@ -1401,6 +1640,7 @@ "mock", "xunit" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-02-16 12:43:56" ======= @@ -1777,12 +2017,16 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2014-12-15 14:25:24" >>>>>>> yiichina/master +======= + "time": "2015-10-02 06:51:40" +>>>>>>> master }, { - "name": "smarty/smarty", + "name": "sebastian/comparator", "version": "dev-master", "source": { "type": "git", +<<<<<<< HEAD "url": "https://github.com/smarty-php/smarty.git", <<<<<<< HEAD "reference": "de7310b702a2024f60c0af384b6617d0c5f406bb" @@ -1799,60 +2043,83 @@ "url": "https://api.github.com/repos/smarty-php/smarty/zipball/443ae8c2167add9c7e605f115955816efeb86f1e", "reference": "443ae8c2167add9c7e605f115955816efeb86f1e", >>>>>>> yiichina/master +======= + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", +>>>>>>> master "shasum": "" }, "require": { - "php": ">=5.2" + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { "classmap": [ - "libs/Smarty.class.php", - "libs/SmartyBC.class.php", - "libs/sysplugins/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "BSD-3-Clause" ], "authors": [ { - "name": "Monte Ohrt", - "email": "monte@ohrt.com" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" }, { - "name": "Uwe Tews", - "email": "uwe.tews@googlemail.com" + "name": "Volker Dusch", + "email": "github@wallbash.com" }, { - "name": "Rodney Rehm", - "email": "rodney.rehm@medialize.de" + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Smarty - the compiling PHP template engine", - "homepage": "http://www.smarty.net", + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", "keywords": [ - "templating" + "comparator", + "compare", + "equality" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-11-13 18:52:41" +======= + "time": "2015-07-26 15:48:44" +>>>>>>> master }, { - "name": "swiftmailer/swiftmailer", + "name": "sebastian/diff", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "d0f361d88e5de851bbb8c542d3b6aa46d61a9ffc" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" }, "dist": { "type": "zip", +<<<<<<< HEAD "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d0f361d88e5de851bbb8c542d3b6aa46d61a9ffc", "reference": "d0f361d88e5de851bbb8c542d3b6aa46d61a9ffc", ======= @@ -1871,68 +2138,84 @@ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/048be6f7c19944d604e321347990543bc2bbe10b", "reference": "048be6f7c19944d604e321347990543bc2bbe10b", >>>>>>> yiichina/master +======= + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", +>>>>>>> master "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "mockery/mockery": "~0.9.1" + "phpunit/phpunit": "~4.8" }, "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "5.3-dev" ======= "dev-master": "5.4-dev" >>>>>>> yiichina/master +======= + "dev-master": "1.4-dev" +>>>>>>> master } }, "autoload": { - "files": [ - "lib/swift_required.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Chris Corbyn" + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "http://swiftmailer.org", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "mail", - "mailer" + "diff" ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-10-30 13:41:35" ======= "time": "2015-03-14 06:07:26" >>>>>>> yiichina/master +======= + "time": "2015-12-08 07:14:41" +>>>>>>> master }, { - "name": "symfony/yaml", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/Yaml", + "name": "sebastian/environment", + "version": "dev-master", "source": { "type": "git", +<<<<<<< HEAD "url": "https://github.com/symfony/Yaml.git", <<<<<<< HEAD "reference": "75deb1b183d2bbf12f4b54c644c0caafca338fbe" +======= + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" +>>>>>>> master }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/75deb1b183d2bbf12f4b54c644c0caafca338fbe", - "reference": "75deb1b183d2bbf12f4b54c644c0caafca338fbe", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", "shasum": "" }, "require": { @@ -1953,51 +2236,129 @@ "symfony/phpunit-bridge": "~2.7|~3.0.0" >>>>>>> yiichina/master }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-02-26 18:40:46" + }, + { + "name": "sebastian/exporter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "f88f8936517d54ae6d589166810877fb2015d0a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f88f8936517d54ae6d589166810877fb2015d0a2", + "reference": "f88f8936517d54ae6d589166810877fb2015d0a2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], +<<<<<<< HEAD "description": "Symfony Yaml Component", "homepage": "http://symfony.com", <<<<<<< HEAD "time": "2014-11-20 13:24:30" +======= + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-08-09 04:23:41" +>>>>>>> master }, { - "name": "twig/twig", - "version": "dev-master", + "name": "sebastian/global-state", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "efed4fa3b10f8ad6f856d817f4e4a6d9ac527915" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/efed4fa3b10f8ad6f856d817f4e4a6d9ac527915", - "reference": "efed4fa3b10f8ad6f856d817f4e4a6d9ac527915", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", "shasum": "" }, "require": { +<<<<<<< HEAD "php": ">=5.2.4" ======= "time": "2015-03-22 16:57:18" @@ -2019,21 +2380,34 @@ "require": { "php": ">=5.2.7" >>>>>>> yiichina/master +======= + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" +>>>>>>> master }, "type": "library", "extra": { "branch-alias": { +<<<<<<< HEAD <<<<<<< HEAD "dev-master": "1.16-dev" ======= "dev-master": "1.18-dev" >>>>>>> yiichina/master +======= + "dev-master": "1.0-dev" +>>>>>>> master } }, "autoload": { - "psr-0": { - "Twig_": "lib/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2041,47 +2415,181 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "http://twig.sensiolabs.org/contributors", - "role": "Contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "http://twig.sensiolabs.org", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ - "templating" + "global state" ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", + "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-01-28 05:39:29" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], +<<<<<<< HEAD <<<<<<< HEAD "time": "2014-11-20 10:35:14" ======= "time": "2015-03-17 17:21:28" >>>>>>> yiichina/master +======= + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940", + "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-03-29 19:00:15" +>>>>>>> master } ], "aliases": [], "minimum-stability": "dev", "stability-flags": { - "bower-asset/jquery-ui": 0 + "bower-asset/jquery": 0 }, "prefer-stable": false, <<<<<<< HEAD +<<<<<<< HEAD ======= "prefer-lowest": false, >>>>>>> yiichina/master +======= + "prefer-lowest": false, +>>>>>>> master "platform": { "php": ">=5.4.0", "ext-mbstring": "*", + "ext-ctype": "*", "lib-pcre": "*" }, "platform-dev": [] diff --git a/docs/documentation_style_guide.md b/docs/documentation_style_guide.md index 1444f98f3b..846faded91 100644 --- a/docs/documentation_style_guide.md +++ b/docs/documentation_style_guide.md @@ -29,6 +29,20 @@ Blocks use the Markdown `> Type: `. There are four block types: The sentence after the colon should begin with a capital letter. +When translating documentation, these Block indicators should not be translated. +Keeps them intact as they are and only translate the block content. +For translating the `Type` word, each guide translation should have a `blocktypes.json` file +containing the translations. The following shows an example for german: + +```json +{ + "Warning:": "Achtung:", + "Note:": "Hinweis:", + "Info:": "Info:", + "Tip:": "Tipp:" +} +``` + ## References * Yii 2.0 or Yii 2 (not Yii2 or Yii2.0) @@ -37,4 +51,15 @@ The sentence after the colon should begin with a capital letter. ## Capitalizations * Web, not web -* the guide or this guide, not the Guide \ No newline at end of file +* the guide or this guide, not the Guide + +## validating the docs + +The following are some scripts that help find broken links and other issues in the guide: + +Find broken links (some false-positives may occur): + + grep -rniP "\[\[[^\],']+?\][^\]]" docs/guide* + grep -rniP "[^\[]\[[^\]\[,']+?\]\]" docs/guide* + + \ No newline at end of file diff --git a/docs/guide-de/blocktypes.json b/docs/guide-de/blocktypes.json new file mode 100644 index 0000000000..333037db3b --- /dev/null +++ b/docs/guide-de/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Achtung:", + "Note:": "Hinweis:", + "Info:": "Info:", + "Tip:": "Tipp:" +} \ No newline at end of file diff --git a/docs/guide-es/README.md b/docs/guide-es/README.md index bef9ef12c8..5929f38905 100644 --- a/docs/guide-es/README.md +++ b/docs/guide-es/README.md @@ -1,4 +1,4 @@ -Guía Definitiva de Yii 2.0 +Guía Definitiva de Yii 2.0 ========================== Este tutorial se publica bajo los [Términos de Documentación Yii](http://www.yiiframework.com/doc/terms/). @@ -147,7 +147,7 @@ Servicios Web RESTful Herramientas de Desarrollo -------------------------- -* **TBD** [Depurador y Barra de Herramientas de Depuración](tool-debugger.md) +* [Depurador y Barra de Herramientas de Depuración](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-es/README.md) * **TBD** [Generación de códigos con Gii](tool-gii.md) * **TBD** [Generación de documentación de API](tool-api-doc.md) @@ -189,8 +189,8 @@ Widgets * Menu: **TBD** link to demo page * LinkPager: **TBD** link to demo page * LinkSorter: **TBD** link to demo page -* **TBD** [Bootstrap Widgets](bootstrap-widgets.md) -* **TBD** [Jquery UI Widgets](jui-widgets.md) +* [Bootstrap Widgets](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-es/README.md) +* [Jquery UI Widgets](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-es/README.md) Clases auxiliares diff --git a/docs/guide-es/blocktypes.json b/docs/guide-es/blocktypes.json new file mode 100644 index 0000000000..7779e0aa5d --- /dev/null +++ b/docs/guide-es/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Aviso:", + "Note:": "Nota:", + "Info:": "Información:", + "Tip:": "Consejo:" +} diff --git a/docs/guide-es/caching-data.md b/docs/guide-es/caching-data.md index 20c8073a1e..7ee6a3c493 100644 --- a/docs/guide-es/caching-data.md +++ b/docs/guide-es/caching-data.md @@ -1,4 +1,4 @@ -Almacenamiento de Datos en Caché +Almacenamiento de Datos en Caché ================================ El almacenamiento de datos en caché trata del almacenamiento de alguna variable PHP en caché y recuperarla más tarde del mismo. También es la base de algunas de las características avanzadas de almacenamiento en caché, tales como [el almacenamiento en caché de consultas a la base de datos](#query-caching) y [el almacenamiento en caché de contenido](caching-page.md). @@ -64,7 +64,7 @@ el código que utiliza la caché. Por ejemplo, podrías modificar la configuraci ], ``` -> Consejo: Puedes registrar múltiples componentes de aplicación de caché. El componente llamado `cache` es usado por defecto por muchas clases caché-dependiente (ej. [[yii\web\UrlManager]]). +> Tip: Puedes registrar múltiples componentes de aplicación de caché. El componente llamado `cache` es usado por defecto por muchas clases caché-dependiente (ej. [[yii\web\UrlManager]]). ### Almacenamientos de Caché Soportados @@ -82,7 +82,7 @@ se muestra un listado con los componentes de caché disponibles: * [[yii\caching\XCache]]: utiliza la extensión de PHP [XCache](http://xcache.lighttpd.net/). * [[yii\caching\ZendDataCache]]: utiliza [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) como el medio fundamental de caché. -> Consejo: Puedes utilizar diferentes tipos de almacenamiento de caché en la misma aplicación. Una estrategia común es la de usar almacenamiento de caché en memoria para almacenar datos que son pequeños pero que son utilizados constantemente (ej. datos estadísticos), y utilizar el almacenamiento de caché en archivos o en base de datos para guardar datos que son grandes y utilizados con menor frecuencia (ej. contenido de página). +> Tip: Puedes utilizar diferentes tipos de almacenamiento de caché en la misma aplicación. Una estrategia común es la de usar almacenamiento de caché en memoria para almacenar datos que son pequeños pero que son utilizados constantemente (ej. datos estadísticos), y utilizar el almacenamiento de caché en archivos o en base de datos para guardar datos que son grandes y utilizados con menor frecuencia (ej. contenido de página). ## API de Caché @@ -100,7 +100,7 @@ Todos los componentes de almacenamiento de caché provienen de la misma clase "p * [[yii\caching\Cache::delete()|delete()]]: elimina un elemento de datos identificado por una clave de la caché. * [[yii\caching\Cache::flush()|flush()]]: elimina todos los elementos de datos de la cache. -> Nota: No Almacenes el valor boolean `false` en caché directamente porque el método [[yii\caching\Cache::get()|get()]] devuelve +> Note: No Almacenes el valor boolean `false` en caché directamente porque el método [[yii\caching\Cache::get()|get()]] devuelve el valor `false` para indicar que el dato no ha sido encontrado en la caché. Puedes poner `false` dentro de un array y cachear este array para evitar este problema. @@ -221,7 +221,7 @@ $result = Customer::getDb()->cache(function ($db) { }); ``` -> Nota: Algunos DBMS (ej. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) también soporta el almacenamiento en caché desde el mismo servidor de la BD. Puedes optar por utilizar cualquiera de los mecanismos de memoria caché. El almacenamiento en caché de consultas previamente descrito tiene la ventaja que de que se puede especificar dependencias de caché de una forma flexible y son potencialmente mucho más eficientes. +> Note: Algunos DBMS (ej. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) también soporta el almacenamiento en caché desde el mismo servidor de la BD. Puedes optar por utilizar cualquiera de los mecanismos de memoria caché. El almacenamiento en caché de consultas previamente descrito tiene la ventaja que de que se puede especificar dependencias de caché de una forma flexible y son potencialmente mucho más eficientes. ### Configuraciones diff --git a/docs/guide-es/caching-http.md b/docs/guide-es/caching-http.md index 391da78a38..7ea1bfbbfc 100644 --- a/docs/guide-es/caching-http.md +++ b/docs/guide-es/caching-http.md @@ -110,7 +110,7 @@ La generación de un ETag que requiera muchos recursos puede echar por tierra el introducir una sobrecarga innecesaria, ya que debe ser re-evaluada en cada solicitud (request). Trata de encontrar una expresión sencilla para invalidar la caché si la página ha sido modificada. -> Nota: En cumplimiento con [RFC 7232](http://tools.ietf.org/html/rfc7232#section-2.4), +> Note: En cumplimiento con [RFC 7232](http://tools.ietf.org/html/rfc7232#section-2.4), `HttpCache` enviará ambas cabeceras `ETag` y `Last-Modified` si ambas están configuradas. Y si el clientes envía tanto la cabecera `If-None-Match` como la cabecera `If-Modified-Since`, solo la primera será respetada. ## La Cabecera `Cache-Control` diff --git a/docs/guide-es/caching-overview.md b/docs/guide-es/caching-overview.md index 4a2504f726..f943e311e8 100644 --- a/docs/guide-es/caching-overview.md +++ b/docs/guide-es/caching-overview.md @@ -5,12 +5,6 @@ El almacenamiento en caché es una forma económica y eficaz para mejorar el ren el almacenamiento de datos relativamente estáticos en la memoria caché y su correspondiente recuperación cuando éstos sean solicidatos, la aplicación salvaría todo ese tiempo y recursos necesarios para volver a generarlos cada vez desde cero. -El almacenamiento puede ocurrir en diferentes niveles y lugar en una aplicación Web. En el lado del servidor, en el -nivel inferior, la memoria caché puede ser utilizada para guardar algunos datos básicos, tales como la lista más reciente -de artículos que han sido extraídos de la base de datos; y en el nivel superior, la memoria caché puede utilizarse para -almacenar fragmentos o un conjuto de páginas Web, tales como el resultado de la representación de los artículos más -recientes. - El almacenamiento en caché se puede usar en diferentes niveles y lugares en una aplicación web. En el lado del servidor, al más bajo nivel, la caché puede ser usada para almacenar datos básicos, tales como una una lista de los artículos más recientes obtenidos de una base de datos; y en el más alto nivel, la caché puede ser usada para almacenar fragmentos o la totalidad de las páginas web, tales como el resultado del renderizado de los artículos más recientes. En el lado del cliente, el almacenamiento en caché HTTP puede ser utilizado para mantener diff --git a/docs/guide-es/concept-aliases.md b/docs/guide-es/concept-aliases.md index ecaee1a160..a44566d39b 100644 --- a/docs/guide-es/concept-aliases.md +++ b/docs/guide-es/concept-aliases.md @@ -1,15 +1,17 @@ Alias ===== -Los alias son utilizados para representar las rutas de archivos o URLs para evitar su [hard-coding](http://es.wikipedia.org/wiki/Hard_code) -en tu código. Un alias debe comenzar con un cáracter `@` para que así pueda ser diferenciado de las rutas de archivos y URLs. -Por ejemplo, el alias `@yii` representa la ruta de instalación de la librería Yii, mientras que `@web` representa la -URL base la aplicación que actualmente se está ejecutando. +Loa alias son utilizados para representar rutas o URLs de manera que no tengas que escribir explícitamente rutas absolutas o URLs en tu +proyecto. Un alias debe comenzar con el signo `@` para ser diferenciado de una ruta normal de archivo y de URLs. Los alias definidos +sin el `@` del principio, serán prefijados con el signo `@`. -Definiendo Alias ----------------- +Yii trae disponibles varios alias predefinidos. Por ejemplo, el alias `@yii` representa la ruta de instalación del +framework Yii; `@web` representa la URL base para la aplicación Web ejecutándose. -Puedes llamar a [[Yii::setAlias()]] para definir un alias para una determinada ruta de archivo o URL. Por ejemplo, +Definir Alias +------------- + +Para definir un alias puedes llamar a [[Yii::setAlias()]] para una determinada ruta de archivo o URL. Por ejemplo, ```php // un alias de una ruta de archivos @@ -19,7 +21,7 @@ Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@bar', 'http://www.example.com'); ``` -> Nota: Una ruta de archivo o URL en alias NO debe necesariamente referirse a un archivo o recurso existente. +> Note: Una ruta de archivo o URL en alias NO debe necesariamente referirse a un archivo o recurso existente. Dado un alias, puedes derivar un nuevo alias (sin necesidad de llamar [[Yii::setAlias()]]) anexando una barra diagonal `/` seguida por uno o varios segmentos de la ruta. Llamamos los alias definidos a través de [[Yii::setAlias()]] @@ -63,7 +65,7 @@ echo Yii::getAlias('@foo/bar/file.php'); // muestra: /path/to/foo/bar/file.php La ruta de archivo/URL representado por un alias derivado está determinado por la sustitución de la parte de su alias raíz con su correspondiente ruta/Url en el alias derivado. -> Nota: El método [[Yii::getAlias()]] no comprueba si la ruta/URL resultante hacer referencia a un archivo o recurso existente. +> Note: El método [[Yii::getAlias()]] no comprueba si la ruta/URL resultante hacer referencia a un archivo o recurso existente. Un alias de raíz puede contener carácteres `/`. El método [[Yii::getAlias()]] es lo suficientemente inteligente para saber @@ -83,9 +85,10 @@ Si `@foo/bar` no está definido como un alias de raíz, la última declaración Usando Alias ------------ -Los alias son utilizados en muchos lugares en Yii sin necesidad de llamar [[Yii::getAlias()]] para convertirlos en rutas/URLs. -Por ejemplo, [[yii\caching\FileCache::cachePath]] puede aceptar tanto una ruta de archivo como un alias que represente -la ruta de archivo, gracias al prefijo `@` el cual permite diferenciar una ruta de archivo de un alias. +Los alias son utilizados en muchos lugares en Yii sin necesidad de llamar [[Yii::getAlias()]] para convertirlos +en rutas/URLs. Por ejemplo, [[yii\caching\FileCache::cachePath]] puede aceptar tanto una ruta de archivo como un alias +que represente la ruta de archivo, gracias al prefijo `@` el cual permite diferenciar una ruta de archivo +de un alias. ```php use yii\caching\FileCache; @@ -109,7 +112,7 @@ utilizadas regularmente. La siguiente es la lista de alias predefinidos por Yii: - `@runtime`: la [[yii\base\Application::runtimePath|ruta de ejecución]] de la aplicación en ejecución. Por defecto `@app/runtime`. - `@webroot`: el directorio raíz Web de la aplicación Web se está ejecutando actualmente. - `@web`: la URL base de la aplicación web se ejecuta actualmente. Tiene el mismo valor que [[yii\web\Request::baseUrl]]. -- `@vendor`: el [[yii\base\Application::vendorPath|directorio vendor de Composer]. Por defecto `@app/vendor`. +- `@vendor`: el [[yii\base\Application::vendorPath|directorio vendor de Composer]]. Por defecto `@app/vendor`. - `@bower`, el directorio raíz que contiene [paquetes bower](http://bower.io/). Por defecto `@vendor/bower`. - `@npm`, el directorio raíz que contiene [paquetes npm](https://www.npmjs.org/). Por defecto `@vendor/npm`. @@ -121,10 +124,10 @@ mientras que el resto de los alias están definidos en el constructor de la apli Alias en Extensiones -------------------- -Un alias se define automaticamente por cada [extensión](structure-extensions.md) que ha sido instalada a través de Composer. +Un alias se define automáticamente por cada [extensión](structure-extensions.md) que ha sido instalada a través de Composer. El alias es nombrado tras el `namespace` de raíz de la extensión instalada tal y como está declarada en su archivo `composer.json`, y representa el directorio raíz de la extensión. Por ejemplo, si instalas la extensión `yiisoft/yii2-jui`, tendrás -automaticamente definido el alias `@yii/jui` durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación: +automáticamente definido el alias `@yii/jui` durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación: ```php Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); diff --git a/docs/guide-es/concept-autoloading.md b/docs/guide-es/concept-autoloading.md index 900ad258c6..5c58e514a2 100644 --- a/docs/guide-es/concept-autoloading.md +++ b/docs/guide-es/concept-autoloading.md @@ -6,7 +6,7 @@ e incluir los archivos de las clases requiridas. Proporciona un cargador de clas [estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md). El cargador se instala cuando incluyes el archivo `Yii.php`. -> Nota: Para simplificar la descripción, en esta sección sólo hablaremos de la carga automática de clases. Sin embargo, +> Note: Para simplificar la descripción, en esta sección sólo hablaremos de la carga automática de clases. Sin embargo, ten en cuenta que el contenido que describimos aquí también se aplica a la autocarga de interfaces y rasgos (Traits). @@ -81,7 +81,7 @@ Puedes usar el autocargador de Composer sin el autocargador de Yii. Sin embargo, tus clases puede que se degrade, y además deberías seguir las reglas establecidas por Composer para que tus clases pudieran ser autocargables. -> Nota: Si no deseas utilizar el autocargador de Yii, tendrás que crear tu propia versión del archivo `Yii.php` e +> Note: Si no deseas utilizar el autocargador de Yii, tendrás que crear tu propia versión del archivo `Yii.php` e incluirlo en tu [script de entrada](structure-entry-scripts.md). diff --git a/docs/guide-es/concept-behaviors.md b/docs/guide-es/concept-behaviors.md index fbbbf1676d..e534aeafea 100644 --- a/docs/guide-es/concept-behaviors.md +++ b/docs/guide-es/concept-behaviors.md @@ -48,7 +48,7 @@ se define a través de la getter `getProp2()` y el setter `setProp2()`. Este cas Debido a que esta clase es un comportamiento, cuando está unido a un componente, el componente también tienen la propiedad `prop1` y `prop2` y el método `foo()`. -> Consejo: Dentro de un comportamiento, puede acceder al componente que el comportamiento está unido a través de la propiedad [[yii\base\Behavior::owner]]. +> Tip: Dentro de un comportamiento, puede acceder al componente que el comportamiento está unido a través de la propiedad [[yii\base\Behavior::owner]]. Gestión de eventos de componentes diff --git a/docs/guide-es/concept-components.md b/docs/guide-es/concept-components.md index b00ca2613d..0bb43eee5b 100644 --- a/docs/guide-es/concept-components.md +++ b/docs/guide-es/concept-components.md @@ -1,4 +1,4 @@ -Componentes +Componentes =========== Los componentes son los principales bloques de construcción de las aplicaciones Yii. Los componentes son instancias de [[yii\base\Component]] o de una clase extendida. Las tres características principales que los componentes proporcionan @@ -73,7 +73,7 @@ $component = \Yii::createObject([ ], [1, 2]); ``` -> Información: Mientras que el enfoque de llamar [[Yii::createObject()]] parece mucho más complicado, es mucho más potente debido al hecho de que se implementa en la parte superior de un [contenedor de inyección de dependencia](concept-di-container.md). +> Info: Mientras que el enfoque de llamar [[Yii::createObject()]] parece mucho más complicado, es mucho más potente debido al hecho de que se implementa en la parte superior de un [contenedor de inyección de dependencia](concept-di-container.md). La clase [[yii\base\Object]] hace cumplir el siguiente ciclo de vida del objeto: diff --git a/docs/guide-es/concept-configurations.md b/docs/guide-es/concept-configurations.md index a59447f1c9..3027a8266c 100644 --- a/docs/guide-es/concept-configurations.md +++ b/docs/guide-es/concept-configurations.md @@ -1,4 +1,4 @@ -Configuración +Configuración ============== Las configuraciones se utilizan ampliamente en Yii al crear nuevos objetos o inicializar los objetos existentes. Las configuraciones por lo general incluyen el nombre de la clase del objeto que se está creando, y una lista de los valores iniciales que deberían ser asignadas a las del [propiedades](concept-properties.md) objeto. Las configuraciones también pueden incluir una lista de manipuladores que deban imponerse a del objeto [eventos](concept-events.md) y/o una lista de [comportamientos](concept-behaviors.md) que también ha de atribuirse al objeto. diff --git a/docs/guide-es/concept-di-container.md b/docs/guide-es/concept-di-container.md index 80eea95fce..baa3a755ec 100644 --- a/docs/guide-es/concept-di-container.md +++ b/docs/guide-es/concept-di-container.md @@ -1,4 +1,4 @@ -Contenedor de Inyección de Dependencias +Contenedor de Inyección de Dependencias ======================================= Un contenedor de Inyección de Dependencias (ID), es un objeto que sabe como instancias y configurar objetos y sus @@ -135,7 +135,7 @@ $container->set('db', function ($container, $params, $config) { $container->set('pageCache', new FileCache); ``` -> Consejo: Si un nombre de dependencia es el mismo que la definición de dependencia, no es necesario registrarlo con +> Tip: Si un nombre de dependencia es el mismo que la definición de dependencia, no es necesario registrarlo con el contenedor de ID. Una dependencia registrada mediante `set()` generará una instancia cada vez que se necesite la dependencia. Se puede diff --git a/docs/guide-es/concept-events.md b/docs/guide-es/concept-events.md index 741cc5d980..c8faca5c3d 100644 --- a/docs/guide-es/concept-events.md +++ b/docs/guide-es/concept-events.md @@ -1,4 +1,4 @@ -Eventos +Eventos ======= Los eventos permiten inyectar código dentro de otro código existente en ciertos puntos de ejecución. Se pueden adjuntar @@ -130,7 +130,7 @@ class Foo extends Component Con el código anterior, cada llamada a `bar()` lanzará un evento llamado `hello` -> Consejo: Se recomienda usar las constantes de clase para representar nombres de eventos. En el anterior ejemplo, la +> Tip: Se recomienda usar las constantes de clase para representar nombres de eventos. En el anterior ejemplo, la constante `EVENT_HELLO` representa el evento `hello`. Este enfoque proporciona tres beneficios. Primero, previene errores tipográficos. Segundo, puede hacer que los IDEs reconozcan los eventos en las funciones de auto-completado. Tercero, se puede ver que eventos soporta una clase simplemente revisando la declaración de constantes. @@ -246,7 +246,7 @@ Event::trigger(Foo::className(), Foo::EVENT_HELLO); Tenga en cuenta que en este caso, el `$event->sender` hace referencia al nombre de la clase que lanza el evento en lugar de a la instancia del objeto. -> Nota: Debido a que los gestores a nivel de clase responderán a los eventos lanzados por cualquier instancia de la +> Note: Debido a que los gestores a nivel de clase responderán a los eventos lanzados por cualquier instancia de la clase, o cualquier clase hija, se debe usar con cuidado, especialmente en las clases de bajo nivel (low-level), tales como [[yii\base\Object]]. diff --git a/docs/guide-es/concept-properties.md b/docs/guide-es/concept-properties.md index 82ba1867a0..81cf1aa7d3 100644 --- a/docs/guide-es/concept-properties.md +++ b/docs/guide-es/concept-properties.md @@ -1,4 +1,4 @@ -Propiedades +Propiedades =========== En PHP, las variables miembro de clases también llamadas *propiedades*, son parte de la definición de la clase, y se @@ -21,7 +21,7 @@ Para solventar este problema, Yii introduce la clase base llamada [[yii\base\Obj de propiedades basada en los métodos de clase *getter* y *setter*. Si una clase necesita más funcionalidad, debe extender a la clase [[yii\base\Object]] o a alguna de sus hijas. -> Información: Casi todas las clases del núcleo (core) en el framework Yii extienden a [[yii\base\Object]] o a una de +> Info: Casi todas las clases del núcleo (core) en el framework Yii extienden a [[yii\base\Object]] o a una de sus clases hijas. Esto significa que siempre que se encuentre un getter o un setter en una clase del núcleo, se puede utilizar como una propiedad. @@ -85,4 +85,4 @@ Hay varias reglas especiales y limitaciones en las propiedades definidas mediant Volviendo de nuevo al problema descrito al principio de la guía, en lugar de ejecutar `trim()` cada vez que se asigne un valor a `label`, ahora `trim()` sólo necesita ser invocado dentro del setter `setLabel()`. I si se tiene que añadir un nuevo requerimiento, para que `label` empiece con una letra mayúscula, se puede modificar rápidamente el método ` -setLabel()` sin tener que modificar más secciones de código. El cambio afectará a cada asignación de `label`. \ No newline at end of file +setLabel()` sin tener que modificar más secciones de código. El cambio afectará a cada asignación de `label`. diff --git a/docs/guide-es/db-dao.md b/docs/guide-es/db-dao.md index 40e1cf729d..aa05c5edb7 100644 --- a/docs/guide-es/db-dao.md +++ b/docs/guide-es/db-dao.md @@ -1,4 +1,4 @@ -Objetos de Acceso a Bases de Datos +Objetos de Acceso a Bases de Datos ================================== Construido sobre [PDO](http://php.net/manual/es/book.pdo.php), Yii DAO (Objetos de Acceso a Bases de Datos) proporciona una @@ -22,7 +22,7 @@ Yii DAO soporta las siguientes bases de datos: ## Creando Conexiones DB Para acceder a una base de datos, primero necesitas conectarte a tu bases de datos mediante la creación -de una instancia de [yii\db\Connection]]: +de una instancia de [[yii\db\Connection]]: ```php $db = new yii\db\Connection([ @@ -56,7 +56,7 @@ return [ Puedes acceder a la conexión DB mediante la expresión `Yii::$app->db`. -> Consejo: Puedes configurar múltiples componentes de aplicación DB si tu aplicación necesita acceder a múltiples bases de datos. +> Tip: Puedes configurar múltiples componentes de aplicación DB si tu aplicación necesita acceder a múltiples bases de datos. Cuando configuras una conexión DB, deberías siempre especificar el Nombre de Origen de Datos (DSN) mediante la propiedad [[yii\db\Connection::dsn|dsn]]. El formato del DSN varia para cada diferente base de datos. Por favor consulte el @@ -87,7 +87,7 @@ para que Yii pueda conocer el tipo de base de datos actual. Por ejemplo, Además de la propiedad [[yii\db\Connection::dsn|dsn]], a menudo es necesario configurar el [[yii\db\Connection::username|username]] y [[yii\db\Connection::password|password]]. Por favor consulta [[yii\db\Connection]] para ver la lista completa de propiedades configurables. -> Información: Cuando se crea una instancia de conexión DB, la conexión actual a la base de datos no se establece hasta que +> Info: Cuando se crea una instancia de conexión DB, la conexión actual a la base de datos no se establece hasta que ejecutes el primer SQL o llames explícitamente al método [[yii\db\Connection::open()|open()]]. @@ -126,10 +126,10 @@ $count = $db->createCommand('SELECT COUNT(*) FROM post') ->queryScalar(); ``` -> Nota: Para preservar la precisión, los datos obtenidos de las bases de datos son todos representados como cadenas, incluso si el tipo de columna correspondiente +> Note: Para preservar la precisión, los datos obtenidos de las bases de datos son todos representados como cadenas, incluso si el tipo de columna correspondiente a la base de datos es numérico. -> Consejo: Si necesitas ejecutar una consulta SQL inmediatamente después de establecer una conexión (ej., para establecer una zona horaria o un conjunto de caracteres), +> Tip: Si necesitas ejecutar una consulta SQL inmediatamente después de establecer una conexión (ej., para establecer una zona horaria o un conjunto de caracteres), > puedes hacerlo con el evento [[yii\db\Connection::EVENT_AFTER_OPEN]]. Por ejemplo, > ```php @@ -389,10 +389,10 @@ es posible que necesites ajustar el nivel de aislamiento para todas las transacc en las configuraciones. En el momento de escribir esto, solo MSSQL y SQLite serán afectadas. -> Nota: SQLite solo soporta dos niveles de aislamiento, por lo que solo se puede usar `READ UNCOMMITTED` y +> Note: SQLite solo soporta dos niveles de aislamiento, por lo que solo se puede usar `READ UNCOMMITTED` y `SERIALIZABLE`. El uso de otros niveles causará el lanzamiento de una excepción. -> Nota: PostgreSQL no permite configurar el nivel de aislamiento antes que la transacción empiece por lo que no se +> Note: PostgreSQL no permite configurar el nivel de aislamiento antes que la transacción empiece por lo que no se puede especificar el nivel de aislamiento directamente cuando empieza la transacción. Se tiene que llamar a [[yii\db\Transaction::setIsolationLevel()]] después de que la transacción haya empezado. @@ -490,7 +490,7 @@ $rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); ``` -> Información: Las consultas realizadas llamando a [[yii\db\Command::execute()]] se consideran consultas de escritura, +> Info: Las consultas realizadas llamando a [[yii\db\Command::execute()]] se consideran consultas de escritura, mientras que todas las demás se ejecutan mediante alguno de los métodos "query" de [[yii\db\Command]] son consultas de lectura. Se puede obtener la conexión de esclavo activa mediante `$db->slave`. @@ -500,7 +500,7 @@ una conexión a este. Si el esclavo se encuentra "muerto", se intentará con otr "muertos" por lo que no se intentará volver a conectar a ellos durante [[yii\db\Connection::serverRetryInterval|certain period of time]]. -> Información: En la configuración anterior, se especifica un tiempo de espera (timeout) de conexión de 10 segundos +> Info: En la configuración anterior, se especifica un tiempo de espera (timeout) de conexión de 10 segundos para cada esclavo. Esto significa que si no se puede conectar a un esclavo en 10 segundos, este será considerado como "muerto". Se puede ajustar el parámetro basado en el entorno actual. @@ -550,7 +550,7 @@ La configuración anterior especifica dos maestros y cuatro esclavos. El compone balanceo de carga y la conmutación de errores entre maestros igual que hace con los esclavos. La diferencia es que cuando no se encuentra ningún maestro disponible se lanza una excepción. -> Nota: cuando se usa la propiedad [[yii\db\Connection::masters|masters]] para configurar uno o múltiples maestros, se +> Note: cuando se usa la propiedad [[yii\db\Connection::masters|masters]] para configurar uno o múltiples maestros, se ignorarán todas las otras propiedades que especifiquen una conexión de base de datos (ej. `dsn`, `username`, `password`), junto con el mismo objeto `Connection`. diff --git a/docs/guide-es/db-migrations.md b/docs/guide-es/db-migrations.md new file mode 100644 index 0000000000..8387efaffa --- /dev/null +++ b/docs/guide-es/db-migrations.md @@ -0,0 +1,939 @@ +Migración de Base de Datos +========================== + +Durante el curso de desarrollo y mantenimiento de una aplicación con base de datos, la estructura de dicha base de datos +evoluciona tanto como el código fuente. Por ejemplo, durante el desarrollo de una aplicación, +una nueva tabla podría ser necesaria; una vez que la aplicación se encuentra en producción, podría descrubrirse +que debería crearse un índice para mejorar el tiempo de ejecución de una consulta; y así sucesivamente. Debido a los cambios en la estructura de la base de datos +a menudo se requieren cambios en el código, Yii soporta la característica llamada *migración de base de datos*, la cual permite +tener un seguimiento de esos cambios en término de *migración de base de datos*, cuyo versionado es controlado +junto al del código fuente. + +Los siguientes pasos muestran cómo una migración puede ser utilizada por un equipo durante el desarrollo: + +1. Tim crea una nueva migración (por ej. crea una nueva table, cambia la definición de una columna, etc.). +2. Tim hace un commit con la nueva migración al sistema de control de versiones (por ej. Git, Mercurial). +3. Doug actualiza su repositorio desde el sistema de control de versiones y recibe la nueva migración. +4. Doug aplica dicha migración a su base de datos local de desarrollo, de ese modo sincronizando su base de datos + y reflejando los cambios que hizo Tim. + +Los siguientes pasos muestran cómo hacer una puesta en producción con una migración de base de datos: + +1. Scott crea un tag de lanzamiento en el repositorio del proyecto que contiene algunas migraciones de base de datos. +2. Scott actualiza el código fuente en el servidor de producción con el tag de lanzamiento. +3. Scott aplica cualquier migración de base de datos acumulada a la base de datos de producción. + +Yii provee un grupo de herramientas de línea de comandos que te permite: + +* crear nuevas migraciones; +* aplicar migraciones; +* revertir migraciones; +* re-aplicar migraciones; +* mostrar el historial y estado de migraciones. + +Todas esas herramientas son accesibles a través del comando `yii migrate`. En esta sección describiremos en detalle +cómo lograr varias tareas utilizando dichas herramientas. Puedes a su vez ver el uso de cada herramienta a través del comando +de ayuda `yii help migrate`. + +> Tip: las migraciones pueden no sólo afectar un esquema de base de datos sino también ajustar datos existentes para que encajen en el nuevo esquema, crear herencia RBAC + o también limpiar el cache. + + +## Creando Migraciones + +Para crear una nueva migración, ejecuta el siguiente comando: + +``` +yii migrate/create +``` + +El argumento requerido `name` da una pequeña descripción de la nueva migración. Por ejemplo, si +la migración se trata acerca de crear una nueva tabla llamada *news*, podrías utilizar el nombre `create_news_table` +y ejecutar el siguiente comando: + +``` +yii migrate/create create_news_table +``` + +> Note: Debido a que el argumento `name` será utilizado como parte del nombre de clase de la migración generada, + sólo debería contener letras, dígitos, y/o guines bajos. + +El comando anterior un nuevo archivo de clase PHP llamado `m150101_185401_create_news_table.php` +en el directorio `@app/migrations`. El archivo contendrá el siguiente código, que principalmente declara +una clase de tipo migración `m150101_185401_create_news_table` con el siguiente esqueleto de código: + +```php +_`, donde + +* `` se refiere a la marca de tiempo UTC en la cual el comando de migración fue ejecutado. +* `` es el mismo valor del argumento `name` provisto al ejecutar el comando. + +En la clase de la migración, se espera que tu escribas código en el método `up()`, que realiza los cambios en la base de datos. +Podrías también querer introducir código en el método `down()`, que debería revertir los cambios realizados por `up()`. El método `up()` es llamado +cuando actualizas la base de datos con esta migración, mientras que el método `down()` es llamado cuando reviertes dicha migración. +El siguiente código muestra cómo podrías implementar la clase de migración para crear la tabla `news`: + +```php +createTable('news', [ + 'id' => Schema::TYPE_PK, + 'title' => Schema::TYPE_STRING . ' NOT NULL', + 'content' => Schema::TYPE_TEXT, + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +> Info: No todas las migraciones son reversibles. Por ejemplo, si el método `up()` elimina un registro en una tabla, podrías + no ser capáz de recuperarla en el método `down()`. A veces, podrías ser simplemente demasiado perezoso para implementar + el método `down()`, debido a que no es muy común revertir migraciones de base de datos. En este caso, deberías devolver + `false` en el método `down()` para indicar que dicha migración no es reversible. + +La clase de migración de base de datos [[yii\db\Migration]] expone una conexión a la base de datos mediante la propiedad [[yii\db\Migration::db|db]]. +Puedes utilizar esto para manipular el esquema de la base de datos utilizando métodos como se describen en +[Trabajando con Esquemas de Base de Datos](db-dao.md#working-with-database-schema-). + +En vez de utilizar tipos físicos, al crear tablas o columnas deberías utilizar los *tipos abstractos* +así las migraciones son independientes de algún DBMS específico. La clase [[yii\db\Schema]] define +un grupo de constantes que representan los tipos abstractos soportados. Dichas constantes son llamadas utilizando el formato +de `TYPE_`. Por ejemplo, `TYPE_PK` se refiere al tipo clave primaria auto-incremental; `TYPE_STRING` +se refiere al tipo string. Cuando se aplica una migración a una base de datos en particular, los tipos abstractos +serán traducidos a los tipos físicos correspondientes. En el caso de MySQL, `TYPE_PK` será transformado +en `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, mientras `TYPE_STRING` se vuelve `varchar(255)`. + +Puedes agregar restricciones adicionales al utilizar tipos abstractos. En el ejemplo anterior, ` NOT NULL` es agregado +a `Schema::TYPE_STRING` para especificar que la columna no puede ser null. + +> Info: El mapeo entre tipos abstractos y tipos físicos es especificado en + la propiedad [[yii\db\QueryBuilder::$typeMap|$typeMap]] en cada clase concreta `QueryBuilder`. + +Desde la versión 2.0.6, puedes hacer uso del recientemente introducido generador de esquemas, el cual provee una forma más conveniente de definir las columnas. +De esta manera, la migración anterior podría ser escrita así: + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +Existe una lista de todos los métodos disponibles para la definición de tipos de columna en la API de la documentación de [[yii\db\SchemaBuilderTrait]]. + + +## Generar Migraciones + +Desde la versión 2.0.7 la consola provee una manera muy conveniente de generar migraciones. + +Si el nombre de la migración tiene una forma especial, por ejemplo `create_xxx` o `drop_xxx` entonces el archivo de la migración generada +contendrá código extra, en este caso para crear/eliminar tablas. +A continuación se describen todas estas variantes. + +### Crear Tabla + +```php +yii migrate/create create_post +``` + +esto genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +Para crear las columnas en ese momento, las puedes especificar vía la opción `--fields`. + +```php +yii migrate/create create_post --fields="title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} + +``` + +Puedes especificar más parámetros para las columnas. + +```php +yii migrate/create create_post --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +> Note: la clave primaria es automáticamente agragada y llamada `id` por defecto. Si quieres utilizar otro nombre puedes +> especificarlo así `--fields="name:primaryKey"`. + +#### Claves Foráneas + +Desde 2.0.8 el generador soporta claves foráneas utilizando la palabra clave `foreignKey`. + +```php +yii migrate/create create_post --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + * Has foreign keys to the tables: + * + * - `user` + * - `category` + */ +class m160328_040430_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'author_id' => $this->integer()->notNull(), + 'category_id' => $this->integer()->defaultValue(1), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + + // creates index for column `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); + + // add foreign key for table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); + + // add foreign key for table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // drops foreign key for table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // drops index for column `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} +``` + +La posición de la palabra clave `foreignKey` en la descripción de la columna +no cambia el código generado. Esto significa: + +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +Todas generan el mismo código. + +La palabra clave `foreignKey` puede tomar un parámetro entre paréntesis el cual +será el nombre de la tabla relacionada por la clave foránea generada. Si no se pasa ningún parámetro +el nombre de la tabla será deducido en base al nombre de la columna. + +En el ejemplo anterior `author_id:integer:notNull:foreignKey(user)` generará +una columna llamada `author_id` con una clave foránea a la tabla `user` mientras +`category_id:integer:defaultValue(1):foreignKey` generará +`category_id` con una clave foránea a la tabla `category`. + +### Eliminar Tabla + +```php +yii migrate/create drop_post --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +class m150811_220037_drop_post extends Migration +{ + public function up() + { + $this->dropTable('post'); + } + + public function down() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } +} +``` + +### Agregar Columna + +Si el nombre de la migración está en la forma `add_xxx_to_yyy` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +Para agregar una columna: + +```php +yii migrate/create add_position_to_post --fields="position:integer" +``` + +genera + +```php +class m150811_220037_add_position_to_post extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### Eliminar Columna + +Si el nombre de la migración está en la forma `drop_xxx_from_yyy` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +```php +yii migrate/create drop_position_from_post --fields="position:integer" +``` + +genera + +```php +class m150811_220037_drop_position_from_post extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +### Agregar Tabla de Unión + +Si el nombre de la migración está en la forma `create_junction_xxx_and_yyy` entonces se generará el código necesario +para una tabla de unión. + +```php +yii migrate/create create_junction_post_and_tag --fields="created_at:dateTime" +``` + +genera + +```php +/** + * Handles the creation for table `post_tag`. + * Has foreign keys to the tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_post_and_tag extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post_tag', [ + 'post_id' => $this->integer(), + 'tag_id' => $this->integer(), + 'created_at' => $this->dateTime(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +``` + +### Migraciones Transaccionales + +Al ejecutar migraciones complejas de BD, es importante asegurarse que todas las migraciones funcionen o fallen como una unidad +así la base de datos puede mantener integridad y consistencia. Para alcanzar este objetivo, se recomienda que +encierres las operación de la BD de cada migración en una [transacción](db-dao.md#performing-transactions). + +Una manera simple de implementar migraciones transaccionales es poniendo el código de las migraciones en los métodos `safeUp()` y `safeDown()`. +Estos métodos se diferencias con `up()` y `down()` en que son encerrados implícitamente en una transacción. +Como resultado, si alguna de las operaciones dentro de estos métodos falla, todas las operaciones previas son automáticamente revertidas. + +En el siguiente ejemplo, además de crear la tabla `news` también insertamos un registro inicial dentro de la dicha tabla. + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + + $this->insert('news', [ + 'title' => 'test 1', + 'content' => 'content 1', + ]); + } + + public function safeDown() + { + $this->delete('news', ['id' => 1]); + $this->dropTable('news'); + } +} +``` + +Ten en cuenta que usualmente cuando ejecutas múltiples operaciones en la BD en `safeUp()`, deberías revertir su orden de ejecución +en `safeDown()`. En el ejemplo anterior primero creamos la tabla y luego insertamos la finla en `safeUp()`; mientras +que en `safeDown()` primero eliminamos el registro y posteriormente eliminamos la tabla. + +> Note: No todos los DBMS soportan transacciones. Y algunas consultas a la BD no pueden ser puestas en transacciones. Para algunos ejemplos, + por favor lee acerca de [commits implícitos](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). En estos casos, + deberías igualmente implementar `up()` y `down()`. + + +### Métodos de Acceso a la Base de Datos + +La clase base [[yii\db\Migration]] provee un grupo de métodos que te permiten acceder y manipular bases de datos. +Podrías encontrar que estos métodos son nombrados de forma similar a los [métodos DAO](db-dao.md) provistos por la clase [[yii\db\Command]]. +Por ejemplo, el método [[yii\db\Migration::createTable()]] te permite crear una nueva tabla, +tal como lo hace [[yii\db\Command::createTable()]]. + +El beneficio de utilizar lo métodos provistos por [[yii\db\Migration]] es que no necesitas explícitamente +crear instancias de [[yii\db\Command]], y la ejecución de cada método mostrará automáticamente mensajes útiles +diciéndote qué operaciones de la base de datos se realizaron y cuánto tiempo tomaron. + +Debajo hay una lista de todos los métodos de acceso a la base de datos: + +* [[yii\db\Migration::execute()|execute()]]: ejecuta una declaración SQL +* [[yii\db\Migration::insert()|insert()]]: inserta un único registro +* [[yii\db\Migration::batchInsert()|batchInsert()]]: inserta múltiples registros +* [[yii\db\Migration::update()|update()]]: actualiza registros +* [[yii\db\Migration::delete()|delete()]]: elimina registros +* [[yii\db\Migration::createTable()|createTable()]]: crea una nueva tabla +* [[yii\db\Migration::renameTable()|renameTable()]]: renombra una tabla +* [[yii\db\Migration::dropTable()|dropTable()]]: elimina una tabla +* [[yii\db\Migration::truncateTable()|truncateTable()]]: elimina todos los registros de una tabla +* [[yii\db\Migration::addColumn()|addColumn()]]: agrega una columna +* [[yii\db\Migration::renameColumn()|renameColumn()]]: renombra una columna +* [[yii\db\Migration::dropColumn()|dropColumn()]]: elimina una columna +* [[yii\db\Migration::alterColumn()|alterColumn()]]: modifica una columna +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: agrega una clave primaria +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: elimina una clave primaria +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: agrega una clave foránea +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: elimina una clave foránea +* [[yii\db\Migration::createIndex()|createIndex()]]: crea un índice +* [[yii\db\Migration::dropIndex()|dropIndex()]]: elimina un índice +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: agrega un comentario a una columna +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: elimina un comentario de una columna +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: agrega un comentario a una tabla +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: elimina un comentario de una tabla + +> Info: [[yii\db\Migration]] no provee un método de consulta a la base de datos. Esto es porque normalmente no necesitas + mostrar mensajes detallados al traer datos de una base de datos. También se debe a que puedes utilizar el poderoso + [Query Builder](db-query-builder.md) para generar y ejecutar consultas complejas. + +> Note: Al manipular datos utilizando una migración podrías encontrar que utilizando tus clases [Active Record](db-active-record.md) +> para esto podría ser útil ya que algo de la lógica ya está implementada ahí. Ten en cuenta de todos modos, que en contraste con +> el código escrito en las migraciones, cuya naturaleza es permanecer constante por siempre, la lógica de la aplicación está sujeta a cambios. +> Entonces al utilizar Active Record en migraciones, los cambios en la lógica en la capa Active Record podrían accidentalmente romper +> migraciones existentes. Por esta razón, el código de las migraciones debería permanecer independiente de determinada lógica de la aplicación +> tal como clases Active Record. + + +## Aplicar Migraciones + +To upgrade a database to its latest structure, you should apply all available new migrations using the following command: +Para actualizar una base de datos a su última estructura, deberías aplicar todas las nuevas migraciones utilizando el siguiente comando: + +``` +yii migrate +``` + +Este comando listará todas las migraciones que no han sido aplicadas hasta el momento. Si confirmas que quieres aplicar +dichas migraciones, se correrá el método `up()` o `safeUp()` en cada clase de migración nueva, una tras otra, +en el orden de su valor de marca temporal. Si alguna de las migraciones falla, el comando terminará su ejecución sin aplicar +el resto de las migraciones. + +> Tip: En caso de no disponer de la línea de comandos en el servidor, podrías intentar utilizar +> la extensión [web shell](https://github.com/samdark/yii2-webshell). + +Por cada migración aplicada correctamente, el comando insertará un registro en la base de datos, en la tabla llamada +`migration` para registrar la correcta aplicación de la migración. Esto permitirá a la herramienta de migración identificar +cuáles migraciones han sido aplicadas y cuáles no. + +> Info: La herramienta de migración creará automáticamente la tabla `migration` en la base de datos especificada + en la opción [[yii\console\controllers\MigrateController::db|db]] del comando. Por defecto, la base de datos + es especificada en el [componente de aplicación](structure-application-components.md) `db`. + +A veces, podrías sólo querer aplicar una o algunas pocas migraciones, en vez de todas las migraciones disponibles. +Puedes hacer esto el número de migraciones que quieres aplicar al ejecutar el comando. +Por ejemplo, el siguiente comando intentará aplicar las tres siguientes migraciones disponibles: + +``` +yii migrate 3 +``` + +Puedes además explícitamente especificar una migración en particular a la cual la base de datos debería migrar +utilizando el comando `migrate/to` de acuerdo a uno de los siguientes formatos: + +``` +yii migrate/to 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/to "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/to m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/to 1392853618 # utiliza el tiempo UNIX +``` + +Si hubiera migraciones previas a la especificada sin aplicar, estas serán aplicadas antes de que la migración especificada +sea aplicada. + +Si la migración especificada ha sido aplicada previamente, cualquier migración aplicada posteriormente será revertida. + + +## Revertir Migraciones + +Para revertir (deshacer) una o varias migraciones ya aplicadas, puedes ejecutar el siguiente comando: + +``` +yii migrate/down # revierte la más reciente migración aplicada +yii migrate/down 3 # revierte las 3 últimas migraciones aplicadas +``` + +> Note: No todas las migraciones son reversibles. Intentar revertir tales migraciones producirá un error y detendrá + completamente el proceso de reversión. + + +## Rehacer Migraciones + +Rehacer (re-ejecutar) migraciones significa primero revertir las migraciones especificadas y luego aplicarlas nuevamente. Esto puede hacerse +de esta manera: + +``` +yii migrate/redo # rehace la más reciente migración aplicada +yii migrate/redo 3 # rehace las 3 últimas migraciones aplicadas +``` + +> Note: Si una migración no es reversible, no tendrás posibilidades de rehacerla. + + +## Listar Migraciones + +Para listar cuáles migraciones han sido aplicadas y cuáles no, puedes utilizar los siguientes comandos: + +``` +yii migrate/history # muestra las últimas 10 migraciones aplicadas +yii migrate/history 5 # muestra las últimas 5 migraciones aplicadas +yii migrate/history all # muestra todas las migraciones aplicadas + +yii migrate/new # muestra las primeras 10 nuevas migraciones +yii migrate/new 5 # muestra las primeras 5 nuevas migraciones +yii migrate/new all # muestra todas las nuevas migraciones +``` + + +## Modificar el Historial de Migraciones + +En vez de aplicar o revertir migraciones, a veces simplemente quieres marcar que tu base de datos +ha sido actualizada a una migración en particular. Esto sucede normalmente cuando cambias manualmente la base de datos +a un estado particular y no quieres que la/s migración/es de ese cambio sean re-aplicadas posteriormente. Puedes alcanzar este objetivo +con el siguiente comando: + +``` +yii migrate/mark 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/mark "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/mark m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/mark 1392853618 # utiliza el tiempo UNIX +``` + +El comando modificará la tabla `migration` agregando o eliminado ciertos registros para indicar que en la base de datos +han sido aplicadas las migraciones hasta la especificada. Ninguna migración será aplicada ni revertida por este comando. + + +## Personalizar Migraciones + +Hay varias maneras de personalizar el comando de migración. + + +### Utilizar Opciones de la Línea de Comandos + +El comando de migración trae algunas opciones de línea de comandos que pueden ser utilizadas para personalizar su comportamiento: + +* `interactive`: boolean (por defecto true), especificar si se debe ejecutar la migración en modo interactivo. + Cuando se indica true, se le pedirá confirmación al usuario antes de ejecutar ciertas acciones. + Puedes querer definirlo como false si el comando está siendo utilizado como un proceso de fondo. + +* `migrationPath`: string (por defecto `@app/migrations`), especifica el directorio que contiene todos los archivos + de clase de las migraciones. Este puede ser especificado tanto como una ruta a un directorio un [alias](concept-aliases.md) de ruta. + Ten en cuenta que el directorio debe existir, o el comando disparará un error. + +* `migrationTable`: string (por defecto `migration`), especifica el nombre de la tabla de la base de datos que almacena + información del historial de migraciones. Dicha tabla será creada por el comando en caso de que no exista. + Puedes también crearla manualmente utilizando la estructura `version varchar(255) primary key, apply_time integer`. + +* `db`: string (por defecto `db`), especifica el ID del [componente de aplicación](structure-application-components.md) de la base de datos. + Esto representa la base de datos que será migrada en este comando. + +* `templateFile`: string (por defecto `@yii/views/migration.php`), especifica la ruta al template + utilizado para generar el esqueleto de los archivos de clases de migración. Puede ser especificado tanto como una ruta a un archivo + como una [alias](concept-aliases.md) de una ruta. El template es un archivo PHP en el cual puedes utilizar una variable predefinida + llamada `$className` para obtener el nombre de clase de la migración. + +* `generatorTemplateFiles`: array (por defecto `[ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createJunctionMigration.php' + ]`), especifica los templates utilizados para generar las migraciones. Ver "[Generar Migraciones](#generating-migrations)" + para más detalles. + +* `fields`: array de strings de definiciones de columna utilizado por el código de migración. Por defecto `[]`. El formato de cada + definición es `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Por ejemplo, `--fields=name:string(12):notNull` produce + una columna string de tamaño 12 que es not null. + +El siguiente ejemplo muestra cómo se pueden utilizar estas opciones. + +Por ejemplo, si queremos migrar un módulo `forum` cuyos arhivos de migración +están ubicados dentro del directorio `migrations` del módulo, podemos utilizar el siguientedocs/guide-es/db-migrations.md +comando: + +``` +# realiza las migraciones de un módulo forum sin interacción del usuario +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 +``` + + +### Configurar el Comando Globalmente + +En vez de introducir los valores de las opciones cada vez que ejecutas un comandod e migración, podrías configurarlos +de una vez por todas en la configuración de la aplicación como se muestra a continuación: + +```php +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], + ], +]; +``` + +Con esta configuración, cada vez que ejecutes un comando de migración, la tabla `backend_migration` +será utilizada para registrar el historial de migraciones. No necesitarás volver a especificarla con la opción `migrationTable` +de la línea de comandos. + + +## Migrar Múltiples Bases de Datos + +Por defecto, las migraciones son aplicadas en la misma base de datos especificada en el [componente de aplicación](structure-application-components.md) `db`. +Si quieres que sean aplicadas en una base de datos diferente, puedes especificar la opción `db` como se muestra a continuación, + +``` +yii migrate --db=db2 +``` + +El comando anterior aplicará las migraciones en la base de datos `db2`. + +A veces puede suceder que quieras aplicar *algunas* de las migraciones a una base de datos, mientras algunas otras +a una base de datos distinta. Para lograr esto, al implementar una clase de migración debes especificar explícitamente el ID del componente DB +que la migración debe utilizar, como a continuación: + +```php +db = 'db2'; + parent::init(); + } +} +``` + +La migración anterior se aplicará a `db2`, incluso si especificas una base de datos diferente en la opción `db` de la +línea de comandos. Ten en cuenta que el historial aún será registrado in la base de datos especificada en la opción `db` de la línea de comandos. + +Si tienes múltiples migraciones que utilizan la misma base de datos, es recomandable que crees una clase base de migración +con el código `init()` mostrado. Entonces cada clase de migración puede extender de esa clase base. + +> Tip: Aparte de definir la propiedad [[yii\db\Migration::db|db]], puedes también operar en diferentes bases de datos + creando nuevas conexiones de base de datos en tus clases de migración. También puedes utilizar [métodos DAO](db-dao.md) + con esas conexiones para manipular diferentes bases de datos. + +Another strategy that you can take to migrate multiple databases is to keep migrations for different databases in +different migration paths. Then you can migrate these databases in separate commands like the following: +Otra estrategia que puedes seguir para migrar múltiples bases de datos es mantener las migraciones para diferentes bases de datos en +distintas rutas de migración. Entonces podrías migrar esas bases de datos en comandos separados como a continuación: + +``` +yii migrate --migrationPath=@app/migrations/db1 --db=db1 +yii migrate --migrationPath=@app/migrations/db2 --db=db2 +... +``` + +El primer comando aplicará las migraciones que se encuentran en `@app/migrations/db1` en la base de datos `db1`, el segundo comando +aplicará las migraciones que se encuentran en `@app/migrations/db2` en `db2`, y así sucesivamente. diff --git a/docs/guide-es/db-query-builder.md b/docs/guide-es/db-query-builder.md index a7670ff93d..d09c5e3516 100644 --- a/docs/guide-es/db-query-builder.md +++ b/docs/guide-es/db-query-builder.md @@ -1,7 +1,7 @@ -Constructor de Consultas +Constructor de Consultas ======================== -> Nota: Esta sección está en desarrollo. +> Note: Esta sección está en desarrollo. Yii proporciona una capa de acceso básico a bases de datos como se describe en la sección [Objetos de Acceso a Bases de Datos](db-dao.md). La capa de acceso a bases de datos proporciona un método de bajo @@ -76,7 +76,7 @@ $query->select(['id', 'name']) ->from('user'); ``` -> Información: Se debe usar siempre el formato array si la clausula `SELECT` contiene expresiones SQL. Esto se debe a +> Info: Se debe usar siempre el formato array si la clausula `SELECT` contiene expresiones SQL. Esto se debe a que una expresión SQL como `CONCAT(first_name, last_name) AS full_name` puede contener comas. Si se junta con otra cadena de texto de otra columna, puede ser que la expresión se divida en varias partes por comas, esto puede conllevar a errores. @@ -240,7 +240,7 @@ El operando puede ser uno de los siguientes (ver también [[yii\db\QueryInterfac cuenta que cuando se usa un el mapeo de filtrado (o no se especifica el tercer operando), los valores se encerraran automáticamente entre un par de caracteres de porcentaje. -> Nota: Cuando se usa PostgreSQL también se puede usar +> Note: Cuando se usa PostgreSQL también se puede usar [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) en lugar de `like` para filtrar resultados insensibles a mayúsculas (case-insensitive). @@ -467,4 +467,4 @@ foreach ($query->batch() as $users) { foreach ($query->each() as $username => $user) { } -``` \ No newline at end of file +``` diff --git a/docs/guide-es/glossary.md b/docs/guide-es/glossary.md new file mode 100644 index 0000000000..4e26210850 --- /dev/null +++ b/docs/guide-es/glossary.md @@ -0,0 +1,67 @@ +# A + +## alias + +Alias es un string utilizado por Yii para referirse a una clase o directorio tal como `@app/vendor`. + +## aplicación + +La aplicación es el objeto central durante la solicitud HTTP. Contiene un número de componentes con los que toma información de la solicitud y la envía al controlador apropiado para posterior procesamiento. + +El objeto de la aplicación es instanciado como un singleton por el script de entrada. El singleton de la aplicación puede ser accedido desde cualquier lugar a través de `\Yii::$app`. + +## assets + +Asset se refiere a un archivo de recurso. Típicamente contiene JavaScript o CSS pero puede ser cualquier otra cosa que sea accesible vía HTTP. + +## atributo + +Un atributo es una propiedad de un modelo (una variable miembro de clase o una propiedad mágica definida vía `__get()`/`__set()`) que almacena **datos de negocio**. + +# B + +## bundle + +Bundle, conocido como paquete en Yii 1.1, se refiere a un número de recursos y un archivo de configuración que describe dependencias y lista recursos. + +# C + +## configuración + +Configuración puede referirse tanto al proceso de establecer propiedades de un objeto como a un archivo de configuración que almacena la definición de propiedades para un objeto o clase de objetos. + +# E + +## extensión + +Extensión es un grupo de clases, paquete de recursos y configuraciones que agrega más características a la aplicación. + +# I + +## instalación + +Instalación es el proceso de preparar algo para trabajar, desde seguir un archivo léame hasta ejecutar un script preparado especialmente para tal fin. En el caso de Yii, define permisos y chequea los requerimientos para el funcionamiento del software. + +# M + +## módulo + +Módulo es una sub-aplicación que contiene elementos MVC en sí mismo, como modelos, vistas, controladores, etc. y puede ser utilizado dentro de la aplicación principal. Típicamente remitiendo las solicitudes al módulo en vez de manejándolo desde controladores. + +# N + +## namespace + +Namespace (espacio de nombres) se refiere a una [característica de PHP](http://php.net/manual/es/language.namespaces.php) activamente utilizada en Yii 2. + +# P + +## paquete + +[Ver bundle](#bundle). + +# V + +## vendor + +Vendor (proveedor) es una organización o un desarrollador individual que provee código en forma de extensiones, módulos o librerías. diff --git a/docs/guide-es/helper-array.md b/docs/guide-es/helper-array.md index 0bfb01c026..5c8fe62821 100644 --- a/docs/guide-es/helper-array.md +++ b/docs/guide-es/helper-array.md @@ -1,7 +1,7 @@ ArrayHelper =========== -Adicionalmente al [rico conjunto de funciones para arrays de PHP](http://php.net/manual/es/book.array.php) Yii array helper proporciona +Adicionalmente al [rico conjunto de funciones para arrays de PHP](http://php.net/manual/es/book.array.php), el array helper de Yii proporciona métodos estáticos adicionales permitiendo trabajar con arrays de manera más eficiente. @@ -109,30 +109,94 @@ $result = ArrayHelper::getColumn($array, function ($element) { ## Re-indexar Arrays -Con el fin de indexar un array según una clave especificada, se puede usar el método `index`. La entrada del array debe ser -multidimensional o un array de objetos. La clave puede ser un nombre clave del sub-array, un nombre de una propiedad del objeto, o -una función anónima que retorne el valor de la clave dado el elemento del array. +Con el fin de indexar un array según una clave especificada, se puede usar el método `index`. La entrada debería ser +un array multidimensional o un array de objetos. `$key` puede ser tanto una clave del sub-array, un nombre de una propiedad +del objeto, o una función anónima que debe devolver el valor que será utilizado como clave. -Si el valor de la clave es null, el correspondiente elemento del array será desechado y no se pondrá en el resultado. Por ejemplo, +El atributo `$groups` es un array de claves, que será utilizado para agrupar el array de entrada en uno o más sub-arrays +basado en la clave especificada. + +Si el atributo `$key` o su valor por el elemento en particular es null y `$groups` no está definido, dicho elemento del array +será descartado. De otro modo, si `$groups` es especificado, el elemento del array será agregado al array resultante +sin una clave. + +Por ejemplo: ```php $array = [ - ['id' => '123', 'data' => 'abc'], - ['id' => '345', 'data' => 'def'], + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], ]; -$result = ArrayHelper::index($array, 'id'); -// el resultado es: -// [ -// '123' => ['id' => '123', 'data' => 'abc'], -// '345' => ['id' => '345', 'data' => 'def'], -// ] +$result = ArrayHelper::index($array, 'id');'); +``` -// usando función anónima +El resultado será un array asociativo, donde la clave es el valor del atributo `id` + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // El segundo elemento del array original es sobrescrito por el último elemento debido a que tiene el mismo id +] +``` + +Pasando una función anónima en `$key`, da el mismo resultado. + +```php $result = ArrayHelper::index($array, function ($element) { return $element['id']; }); ``` +Pasando `id` como tercer argumento, agrupará `$array` mediante `id`: + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel y no indexado en su segundo nivel: + +```php +[ + '123' => [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ], + '345' => [ // todos los elementos con este índice están presentes en el array resultante + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ] +] +``` + +Una función anónima puede ser usada también en el array agrupador: + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel, por `device` en su segundo nivel e +indexado por `data` en su tercer nivel: + +```php +[ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ] + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + ] + ] +] +``` ## Construyendo Mapas (Maps) @@ -241,31 +305,30 @@ La codificación utilizará el charset de la aplicación y podría ser cambiado ```php /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ + * Fusiona recursivamente dos o más arrays en uno. + * Si cada array tiene un elemento con el mismo valor string de clave, el último + * sobrescribirá el anterior (difiere de array_merge_recursive). + * Se llegará a una fusión recursiva si ambos arrays tienen un elemento tipo array + * y comparten la misma clave. + * Para elementos cuyas claves son enteros, los elementos del array final + * serán agregados al array anterior. + * @param array $a array al que se va a fusionar + * @param array $b array desde el cual fusionar. Puedes especificar + * arrays adicionales mediante el tercer argumento, cuarto argumento, etc. + * @return array el array fusionado (los arrays originales no sufren cambios) + */ public static function merge($a, $b) ``` ## Convirtiendo Objetos a Arrays -A menudo necesitas convertir un objeto o un array de objetos a un array. El caso más común es convertir los modelos de -active record con el fin de servir los arrays de datos vía API REST o utilizarlos de otra manera. El siguiente código -se podría utilizar para hacerlo: +A menudo necesitas convertir un objeto o un array de objetos a un array. El caso más común es convertir los modelos de active record +con el fin de servir los arrays de datos vía API REST o utilizarlos de otra manera. El siguiente código se podría utilizar para hacerlo: ```php $posts = Post::find()->limit(10)->all(); -$data = ArrayHelper::toArray($post, [ +$data = ArrayHelper::toArray($posts, [ 'app\models\Post' => [ 'id', 'title', @@ -302,3 +365,22 @@ El resultado de la conversión anterior será: Es posible proporcionar una manera predeterminada de convertir un objeto a un array para una clase especifica mediante la implementación de la interfaz [[yii\base\Arrayable|Arrayable]] en esa clase. + +## Haciendo pruebas con Arrays + +A menudo necesitarás comprobar está en un array o un grupo de elementos es un sub-grupo de otro. +A pesar de que PHP ofrece `in_array()`, este no soporta sub-grupos u objetos de tipo `\Traversable`. + +Para ayudar en este tipo de pruebas, [[yii\base\ArrayHelper]] provee [[yii\base\ArrayHelper::isIn()|isIn()]] +y [[yii\base\ArrayHelper::isSubset()|isSubset()]] con la misma firma del método [[in_array()]]. + +```php +// true +ArrayHelper::isIn('a', ['a']); +// true +ArrayHelper::isIn('a', new(ArrayObject['a'])); + +// true +ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) + +``` diff --git a/docs/guide-es/helper-html.md b/docs/guide-es/helper-html.md index e2cf2bbe01..d807c3b5f9 100644 --- a/docs/guide-es/helper-html.md +++ b/docs/guide-es/helper-html.md @@ -7,7 +7,7 @@ puede realizar de forma efectiva generan dinámicamente empieza a complicarse su gestión sin ayuda extra. Yii ofrece esta ayuda en forma de una clase auxiliar Html que proporciona un conjunto de métodos estáticos para gestionar las etiquetas HTML más comúnmente usadas, sus opciones y contenidos. -> Nota: Si el marcado es casi estático, es preferible usar HTML directamente. No es necesario encapsularlo todo con +> Note: Si el marcado es casi estático, es preferible usar HTML directamente. No es necesario encapsularlo todo con llamadas a la clase auxiliar Html. ## Lo fundamental @@ -115,7 +115,7 @@ $decodedUserName = Html::decode($userName); El trato con el marcado de formularios es una tarea repetitiva y propensa a errores. Por esto hay un grupo de métodos para ayudar a gestionarlos. -> Nota: hay que considerar la opción de usar [[yii\widgets\ActiveForm|ActiveForm]] en caso de que se gestionen +> Note: hay que considerar la opción de usar [[yii\widgets\ActiveForm|ActiveForm]] en caso de que se gestionen formularios que requieran validaciones. ### Creando formularios diff --git a/docs/guide-es/helper-overview.md b/docs/guide-es/helper-overview.md index 3bf4354d4c..97a114f6c1 100644 --- a/docs/guide-es/helper-overview.md +++ b/docs/guide-es/helper-overview.md @@ -1,7 +1,7 @@ Helpers ======= -> Nota: Esta sección está en desarrollo. +> Note: Esta sección está en desarrollo. Yii ofrece muchas clases que ayudan a simplificar las tareas comunes de codificación, como manipulación de string o array, generación de código HTML, y más. Estas clases helper están organizadas bajo el namespace `yii\helpers` y @@ -15,7 +15,7 @@ use yii\helpers\Html; echo Html::encode('Test > test'); ``` -> Nota: Para soportar la [personalización de clases helper](#customizing-helper-classes), Yii separa cada clase helper del núcleo +> Note: Para soportar la [personalización de clases helper](#customizing-helper-classes), Yii separa cada clase helper del núcleo en dos clases: una clase base (ej. `BaseArrayHelper`) y una clase concreta (ej. `ArrayHelper`). Cuando uses un helper, deberías sólo usar la versión concreta y nunca usar la clase base. diff --git a/docs/guide-es/helper-url.md b/docs/guide-es/helper-url.md index e834f3f8f8..ad5f923a61 100644 --- a/docs/guide-es/helper-url.md +++ b/docs/guide-es/helper-url.md @@ -3,8 +3,8 @@ Clase Auxiliar URL (URL Helper) La clase auxiliar URL proporciona un conjunto de métodos estáticos para gestionar URLs. -Obtener URLs Comunes --------------------- + +## Obtener URLs comúnes Se pueden usar dos métodos para obtener URLs comunes: URL de inicio (home URL) y URL base (base URL) de la petición (request) actual. Para obtener la URL de inicio se puede usar el siguiente código: @@ -15,7 +15,7 @@ $absoluteHomeUrl = Url::home(true); $httpsAbsoluteHomeUrl = Url::home('https'); ``` -Si no se pasan parámetros, las URLs generadas son relativas. Se puede pasar `true`para obtener la URL absoluta del +Si no se pasan parámetros, la URL generada es relativa. Se puede pasar `true`para obtener la URL absoluta del esquema actual o especificar el esquema explícitamente (`https`, `http`). Para obtener la URL base de la petición actual, se puede usar el siguiente código: @@ -28,11 +28,11 @@ $httpsAbsoluteBaseUrl = Url::base('https'); El único parámetro del método funciona exactamente igual que para `Url::home()`. -Creación de URLs ----------------- -Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El metodo utiliza [[\yii\web\UrlManager]] -para crear una URL: +## Creación de URLs + +Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El método utiliza [[\yii\web\UrlManager]] +para crear la URL: ```php $url = Url::toRoute(['product/view', 'id' => 42]); @@ -42,7 +42,7 @@ Se puede especificar la ruta como una cadena de texto, ej. `site/index`. Tambié quieren especificar parámetros para la URL que se esta generando. El formato del array debe ser: ```php -// genera: /index.php?r=site/index¶m1=value1¶m2=value2 +// genera: /index.php?r=site%2Findex¶m1=value1¶m2=value2 ['site/index', 'param1' => 'value1', 'param2' => 'value2'] ``` @@ -53,9 +53,8 @@ Si se quiere crear una URL con un enlace, se puede usar el formato de array con ['site/index', 'param1' => 'value1', '#' => 'name'] ``` -Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (ej. `/site/index`), -mientras que una ruta relativa no la tiene (ej. `site/index` o `index`). Una ruta relativa se convertirá en una -ruta absoluta siguiendo las siguientes normas: +Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (ej. `/site/index`), mientras que una ruta relativa +no la tiene (ej. `site/index` o `index`). Una ruta relativa se convertirá en una ruta absoluta siguiendo las siguientes reglas: - Si la ruta es una cadena vacía, se usará la [[\yii\web\Controller::route|route]] actual; - Si la ruta no contiene barras (ej. `index`), se considerará que es el ID de una acción del controlador actual y @@ -63,19 +62,26 @@ ruta absoluta siguiendo las siguientes normas: - Si la ruta no tiene barra inicial (ej. `site/index`), se considerará que es una ruta relativa del modulo actual y se le antepondrá el [[\yii\base\Module::uniqueId|uniqueId]] del modulo. +Desde la versión 2.0.2, puedes especificar una ruta en términos de [alias](concept-aliases.md). Si este es el caso, +el alias será convertido primero en la ruta real, la cual será entonces transformada en una ruta absoluta de acuerdo +a las reglas mostradas arriba. + A continuación se muestran varios ejemplos del uso de este método: ```php -// /index?r=site/index +// /index.php?r=site%2Findex echo Url::toRoute('site/index'); -// /index?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); -// http://www.example.com/index.php?r=site/index +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::toRoute(['@postEdit', 'id' => 100]); + +// http://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', true); -// https://www.example.com/index.php?r=site/index +// https://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', 'https'); ``` @@ -99,13 +105,16 @@ el especificado. A continuación se muestran algunos ejemplos de uso: ```php -// /index?r=site/index +// /index.php?r=site%2Findex echo Url::to(['site/index']); -// /index?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); -// la URL solicitada actualmente +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::to(['@postEdit', 'id' => 100]); + +// the currently requested URL echo Url::to(); // /images/logo.gif @@ -121,8 +130,24 @@ echo Url::to('@web/images/logo.gif', true); echo Url::to('@web/images/logo.gif', 'https'); ``` -Recordar la URL para utilizarla más adelante --------------------------------------------- +Desde la versión 2.0.3, puedes utilizar [[yii\helpers\Url::current()]] para crear una URL a partir de la ruta +solicitada y los parámetros GET. Puedes modificar o eliminar algunos de los parámetros GET, o también agregar nuevos +pasando un parámetro `$params` al método. Por ejemplo, + +```php +// asume que $_GET = ['id' => 123, 'src' => 'google'], la ruta actual es "post/view" + +// /index.php?r=post%2Fview&id=123&src=google +echo Url::current(); + +// /index.php?r=post%2Fview&id=123 +echo Url::current(['src' => null]); +// /index.php?r=post%2Fview&id=100&src=google +echo Url::current(['id' => 100]); +``` + + +## Recordar URLs Hay casos en que se necesita recordar la URL y después usarla durante el procesamiento de una de las peticiones secuenciales. Se puede logar de la siguiente manera: @@ -145,11 +170,9 @@ $url = Url::previous(); $productUrl = Url::previous('product'); ``` -Reconocer la relatividad de URLs --------------------------------- +## Chequear URLs relativas -Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente -código: +Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente código: ```php $isRelative = Url::isRelative('test/it'); diff --git a/docs/guide-es/images/application-lifecycle.graphml b/docs/guide-es/images/application-lifecycle.graphml new file mode 100644 index 0000000000..850863ab26 --- /dev/null +++ b/docs/guide-es/images/application-lifecycle.graphml @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Entry script (index.php or yii) + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Load application config + + + + + + + + + + + + + + + + + + + + + Create application instance + + + + + + + + + + Folder 5 + + + + + + + + + + + + + + + + preInit() + + + + + + + + + + + + + + + + + Register error handler + + + + + + + + + + + + + + + + + Configure application properties + + + + + + + + + + + + + + + + + init() + + + + + + + + + + + + + + + + + bootstrap() + + + + + + + + + + + + + + + + + + + + + + + Run application + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + EVENT_BEFORE_REQUEST + + + + + + + + + + + + + + + + + + + + + Handle request + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Resolve request into route and parameters + + + + + + + + + + + + + + + + + Create module, controller and action + + + + + + + + + + + + + + + + + Run action + + + + + + + + + + + + + + + + + + + EVENT_AFTER_REQUEST + + + + + + + + + + + + + + + + + Send response to end user + + + + + + + + + + + + + + + + + + + Complete request processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration array + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exit status + + + + + + + + + + + + + + + + diff --git a/docs/guide-es/images/application-lifecycle.png b/docs/guide-es/images/application-lifecycle.png new file mode 100644 index 0000000000..6a505ccefb Binary files /dev/null and b/docs/guide-es/images/application-lifecycle.png differ diff --git a/docs/guide-es/images/rbac-access-check-1.graphml b/docs/guide-es/images/rbac-access-check-1.graphml new file mode 100644 index 0000000000..44078515cf --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-1.png b/docs/guide-es/images/rbac-access-check-1.png new file mode 100644 index 0000000000..77ad551c26 Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-1.png differ diff --git a/docs/guide-es/images/rbac-access-check-2.graphml b/docs/guide-es/images/rbac-access-check-2.graphml new file mode 100644 index 0000000000..c521d429ea --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-2.png b/docs/guide-es/images/rbac-access-check-2.png new file mode 100644 index 0000000000..254f307a89 Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-2.png differ diff --git a/docs/guide-es/images/rbac-access-check-3.graphml b/docs/guide-es/images/rbac-access-check-3.graphml new file mode 100644 index 0000000000..8747cee0da --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-3.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-3.png b/docs/guide-es/images/rbac-access-check-3.png new file mode 100644 index 0000000000..1fdc0d935a Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-3.png differ diff --git a/docs/guide-es/images/rbac-hierarchy-1.graphml b/docs/guide-es/images/rbac-hierarchy-1.graphml new file mode 100644 index 0000000000..927b416d61 --- /dev/null +++ b/docs/guide-es/images/rbac-hierarchy-1.graphml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-1.png b/docs/guide-es/images/rbac-hierarchy-1.png new file mode 100644 index 0000000000..7443fc7e71 Binary files /dev/null and b/docs/guide-es/images/rbac-hierarchy-1.png differ diff --git a/docs/guide-es/images/rbac-hierarchy-2.graphml b/docs/guide-es/images/rbac-hierarchy-2.graphml new file mode 100644 index 0000000000..b81887b0e0 --- /dev/null +++ b/docs/guide-es/images/rbac-hierarchy-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-2.png b/docs/guide-es/images/rbac-hierarchy-2.png new file mode 100644 index 0000000000..e77c5647c1 Binary files /dev/null and b/docs/guide-es/images/rbac-hierarchy-2.png differ diff --git a/docs/guide-es/images/start-country-list.png b/docs/guide-es/images/start-country-list.png index 6994da2103..918571a9ad 100644 Binary files a/docs/guide-es/images/start-country-list.png and b/docs/guide-es/images/start-country-list.png differ diff --git a/docs/guide-es/images/tutorial-console-help.png b/docs/guide-es/images/tutorial-console-help.png new file mode 100644 index 0000000000..15b8b66a03 Binary files /dev/null and b/docs/guide-es/images/tutorial-console-help.png differ diff --git a/docs/guide-es/input-file-upload.md b/docs/guide-es/input-file-upload.md new file mode 100644 index 0000000000..54728b6589 --- /dev/null +++ b/docs/guide-es/input-file-upload.md @@ -0,0 +1,208 @@ +Subir Archivos +============== + +Subir archivos en Yii es normalmente realizado con la ayuda de [[yii\web\UploadedFile]], que encapsula cada archivo subido +en un objeto `UploadedFile`. Combinado con [[yii\widgets\ActiveForm]] y [modelos](structure-models.md), +puedes fácilmente implementar un mecanismo seguro de subida de archivos. + + +## Crear Modelos + +Al igual que al trabajar con entradas de texto plano, para subir un archivo debes crear una clase de modelo y utilizar un atributo +de dicho modelo para mantener la instancia del archivo subido. Debes también declarar una regla para validar la subida del archivo. +Por ejemplo, + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile + */ + public $imageFile; + + public function rules() + { + return [ + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], + ]; + } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } +} +``` + +En el código anterior, el atributo `imageFile` es utilizado para mantener una instancia del archivo subido. Este está asociado con +una regla de validación `file`, que utiliza [[yii\validators\FileValidator]] para asegurarse que el archivo a subir tenga extensión `png` o `jpg`. +El método `upload()` realizará la validación y guardará el archivo subido en el servidor. + +El validador `file` te permite chequear las extensiones, el tamaño, el tipo MIME, etc. Por favor consulta +la sección [Validadores del Framework](tutorial-core-validators.md#file) para más detalles. + +> Tip: Si estás subiendo una imagen, podrías considerar el utilizar el validador `image`. El validador `image` es + implementado a través de [[yii\validators\ImageValidator]], que verifica que un atributo haya recibido una imagen válida + que pueda ser tanto guardada como procesada utilizando la [Extensión Imagine](https://github.com/yiisoft/yii2-imagine). + + +## Renderizar Campos de Subida de Archivos + +A continuación, crea un campo de subida de archivo en la vista: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFile')->fileInput() ?> + + + + +``` + +Es importante recordad que agregues la opción `enctype` al formulario para que el archivo pueda ser subido apropiadamente. +La llamada a `fileInput()` renderizará un tag `` que le permitirá al usuario seleccionar el archivo a subir. + +> Tip: desde la versión 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] agrega la opción `enctype` al formulario + automáticamente cuando se utiliza una campo de subida de archivo. + +## Uniendo Todo + +Ahora, en una acción del controlador, escribe el código que una el modelo y la vista para implementar la subida de archivos: + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // el archivo se subió exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +En el código anterior, cuando se envía el formulario, el método [[yii\web\UploadedFile::getInstance()]] es llamado +para representar el archivo subido como una instancia de `UploadedFile`. Entonces dependemos de la validación del modelo +para asegurarnos que el archivo subido es válido y entonces subirlo al servidor. + + +## Uploading Multiple Files + +También puedes subir varios archivos a la vez, con algunos ajustes en el código de las subsecciones previas. + +Primero debes ajustar la clase del modelo, agregando la opción `maxFiles` en la regla de validación `file` para limitar +el número máximo de archivos a subir. Definir `maxFiles` como `0` significa que no hay límite en el número de archivos +a subir simultáneamente. El número máximo de archivos permitidos para subir simultáneamente está también limitado +por la directiva PHP [`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads), +cuyo valor por defecto es 20. El método `upload()` debería también ser modificado para guardar los archivos uno a uno. + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile[] + */ + public $imageFiles; + + public function rules() + { + return [ + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], + ]; + } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } +} +``` + +En el archivo de la vista, debes agregar la opción `multiple` en la llamada a `fileInput()` de manera que el campo +pueda recibir varios archivos: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> + + + + +``` + +Y finalmente en la acción del controlador, debes llamar `UploadedFile::getInstances()` en vez de +`UploadedFile::getInstance()` para asignar un array de instancias `UploadedFile` a `UploadForm::imageFiles`. + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // el archivo fue subido exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` diff --git a/docs/guide-es/input-validation.md b/docs/guide-es/input-validation.md new file mode 100644 index 0000000000..c2653aaa5b --- /dev/null +++ b/docs/guide-es/input-validation.md @@ -0,0 +1,714 @@ +Validación de Entrada +===================== + +Como regla básica, nunca debes confiar en los datos recibidos de un usuario final y deberías validarlo siempre +antes de ponerlo en uso. + +Dado un [modelo](structure-models.md) poblado con entradas de usuarios, puedes validar esas entradas llamando al +método [[yii\base\Model::validate()]]. Dicho método devolverá un valor booleano indicando si la validación +tuvo éxito o no. En caso de que no, puedes obtener los mensajes de error de la propiedad [[yii\base\Model::errors]]. Por ejemplo, + +```php +$model = new \app\models\ContactForm(); + +// poblar los atributos del modelo desde la entrada del usuario +$model->load(\Yii::$app->request->post()); +// lo que es equivalente a: +// $model->attributes = \Yii::$app->request->post('ContactForm'); + +if ($model->validate()) { + // toda la entrada es válida +} else { + // la validación falló: $errors es un array que contienen los mensajes de error + $errors = $model->errors; +} +``` + + +## Declarar Reglas + +Para hacer que `validate()` realmente funcione, debes declarar reglas de validación para los atributos que planeas validar. +Esto debería hacerse sobrescribiendo el método [[yii\base\Model::rules()]]. El siguiente ejemplo muestra cómo +son declaradas las reglas de validación para el modelo `ContactForm`: + +```php +public function rules() +{ + return [ + // los atributos name, email, subject y body son obligatorios + [['name', 'email', 'subject', 'body'], 'required'], + + // el atributo email debe ser una dirección de email válida + ['email', 'email'], + ]; +} +``` + +El método [[yii\base\Model::rules()|rules()]] debe devolver un array de reglas, la cual cada una +tiene el siguiente formato: + +```php +[ + // requerido, especifica qué atributos deben ser validados por esta regla. + // Para un sólo atributo, puedes utilizar su nombre directamente + // sin tenerlo dentro de un array + ['attribute1', 'attribute2', ...], + + // requerido, especifica de qué tipo es la regla. + // Puede ser un nombre de clase, un alias de validador, o el nombre de un método de validación + 'validator', + + // opcional, especifica en qué escenario/s esta regla debe aplicarse + // si no se especifica, significa que la regla se aplica en todos los escenarios + // Puedes también configurar la opción "except" en caso de que quieras aplicar la regla + // en todos los escenarios salvo los listados + 'on' => ['scenario1', 'scenario2', ...], + + // opcional, especifica atributos adicionales para el objeto validador + 'property1' => 'value1', 'property2' => 'value2', ... +] +``` + +Por cada regla debes especificar al menos a cuáles atributos aplica la regla y cuál es el tipo de la regla. +Puedes especificar el tipo de regla de las siguientes maneras: + +* el alias de un validador propio del framework, tal como `required`, `in`, `date`, etc. Por favor consulta + [Validadores del núcleo](tutorial-core-validators.md) para la lista completa de todos los validadores incluidos. +* el nombre de un método de validación en la clase del modelo, o una función anónima. Consulta la + subsección [Validadores en Línea](#inline-validators) para más detalles. +* el nombre completo de una clase de validador. Por favor consulta la subsección [Validadores Independientes](#standalone-validators) + para más detalles. + +Una regla puede ser utilizada para validar uno o varios atributos, y un atributo puede ser validado por una o varias reglas. +Una regla puede ser aplicada en ciertos [escenarios](structure-models.md#scenarios) con tan sólo especificando la opción `on`. +Si no especificas una opción `on`, significa que la regla se aplicará en todos los escenarios. + +Cuando el método `validate()` es llamado, este sigue los siguientes pasos para realiza la validación: + +1. Determina cuáles atributos deberían ser validados obteniendo la lista de atributos de [[yii\base\Model::scenarios()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estos atributos son llamados *atributos activos*. +2. Determina cuáles reglas de validación deberían ser validados obteniendo la lista de reglas de [[yii\base\Model::rules()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estas reglas son llamadas *reglas activas*. +3. Utiliza cada regla activa para validar cada atributo activo que esté asociado a la regla. + Las reglas de validación son evaluadas en el orden en que están listadas. + +De acuerdo a los pasos de validación mostrados arriba, un atributo será validado si y sólo si +es un atributo activo declarado en `scenarios()` y está asociado a una o varias reglas activas +declaradas en `rules()`. + +> Note: Es práctico darle nombre a las reglas, por ej: +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> Puedes utilizarlas en una subclase del modelo: +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + + +### Personalizar Mensajes de Error + +La mayoría de los validadores tienen mensajes de error por defecto que serán agregados al modelo siendo validado cuando sus atributos +fallan la validación. Por ejemplo, el validador [[yii\validators\RequiredValidator|required]] agregará +el mensaje "Username no puede estar vacío." a un modelo cuando falla la validación del atributo `username` al utilizar esta regla. + +Puedes especificar el mensaje de error de una regla especificado la propiedad `message` al declarar la regla, +como a continuación, + +```php +public function rules() +{ + return [ + ['username', 'required', 'message' => 'Por favor escoge un nombre de usuario.'], + ]; +} +``` + +Algunos validadores pueden soportar mensajes de error adicionales para describir más precisamente las causas +del fallo de validación. Por ejemplo, el validador [[yii\validators\NumberValidator|number]] soporta +[[yii\validators\NumberValidator::tooBig|tooBig]] y [[yii\validators\NumberValidator::tooSmall|tooSmall]] +para describir si el fallo de validación es porque el valor siendo validado es demasiado grande o demasiado pequeño, respectivamente. +Puedes configurar estos mensajes de error tal como cualquier otroa propiedad del validador en una regla de validación. + + +### Eventos de Validación + +Cuando el método [[yii\base\Model::validate()]] es llamado, este llamará a dos métodos que puedes sobrescribir para personalizar +el proceso de validación: + +* [[yii\base\Model::beforeValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_BEFORE_VALIDATE]]. + Puedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de pre procesamiento + (por ej. normalizar datos de entrada) antes de que ocurra la validación en sí. El método debe devolver un booleano que indique + si la validación debe continuar o no. +* [[yii\base\Model::afterValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_AFTER_VALIDATE]]. + uedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de post procesamiento después + de completada la validación. + + +### Validación Condicional + +Para validar atributos sólo en determinadas condiciones, por ej. la validación de un atributo depende +del valor de otro atributo puedes utilizar la propiedad [[yii\validators\Validator::when|when]] +para definir la condición. Por ejemplo, + +```php + ['state', 'required', 'when' => function($model) { + return $model->country == 'USA'; + }] +``` + +La propiedad [[yii\validators\Validator::when|when]] toma un método invocable PHP con la siguiente firma: + +```php +/** + * @param Model $model el modelo siendo validado + * @param string $attribute al atributo siendo validado + * @return boolean si la regla debe ser aplicada o no + */ +function ($model, $attribute) +``` + +Si también necesitas soportar validación condicional del lado del cliente, debes configurar +la propiedad [[yii\validators\Validator::whenClient|whenClient]], que toma un string que representa una función JavaScript +cuyo valor de retorno determina si debe aplicarse la regla o no. Por ejemplo, + +```php + ['state', 'required', 'when' => function ($model) { + return $model->country == 'USA'; + }, 'whenClient' => "function (attribute, value) { + return $('#country').val() == 'USA'; + }"] +``` + + +### Filtro de Datos + +La entrada del usuario a menudo debe ser filtrada o pre procesada. Por ejemplo, podrías querer eliminar los espacions alrededor +de la entrada `username`. Puedes utilizar reglas de validación para lograrlo. + +Los siguientes ejemplos muestran cómo eliminar esos espacios en la entrada y cómo transformar entradas vacías en null utilizando +los validadores del framework [trim](tutorial-core-validators.md#trim) y [default](tutorial-core-validators.md#default): + +```php +return [ + [['username', 'email'], 'trim'], + [['username', 'email'], 'default'], +]; +``` + +También puedes utilizar el validador más general [filter](tutorial-core-validators.md#filter) para realizar filtros +de datos más complejos. + +Como puedes ver, estas reglas de validación no validan la entrada realmente. En cambio, procesan los valores +y los guardan en el atributo siendo validado. + + +### Manejando Entradas Vacías + +Cuando los datos de entrada son enviados desde formularios HTML, a menudo necesitas asignar algunos valores por defecto a las entradas +si estas están vacías. Puedes hacerlo utilizando el validador [default](tutorial-core-validators.md#default). Por ejemplo, + +```php +return [ + // convierte "username" y "email" en null si estos están vacíos + [['username', 'email'], 'default'], + + // convierte "level" a 1 si está vacío + ['level', 'default', 'value' => 1], +]; +``` + +Por defecto, una entrada se considera vacía si su valor es un string vacío, un array vacío o null. +Puedes personalizar la lógica de detección de valores vacíos configurando la propiedad [[yii\validators\Validator::isEmpty]] +con una función PHP invocable. Por ejemplo, + +```php + ['agree', 'required', 'isEmpty' => function ($value) { + return empty($value); + }] +``` + +> Note: La mayoría de los validadores no manejan entradas vacías si su propiedad [[yii\validators\Validator::skipOnEmpty]] toma + el valor por defecto true. Estas serán simplemente salteadas durante la validación si sus atributos asociados reciben una entrada vacía. + Entre los [validadores del framework](tutorial-core-validators.md), sólo `captcha`, `default`, `filter`, + `required`, y `trim` manejarán entradas vacías. + + +## Validación Ad Hoc + +A veces necesitas realizar *validación ad hoc* para valores que no están ligados a ningún modelo. + +Si sólo necesitas realizar un tipo de validación (por ej: validar direcciones de email), podrías llamar +al método [[yii\validators\Validator::validate()|validate()]] de los validadores deseados, como a continuación: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); + +if ($validator->validate($email, $error)) { + echo 'Email válido.'; +} else { + echo $error; +} +``` + +> Note: No todos los validadores soportan este tipo de validación. Un ejemplo es el validador del framework [unique](tutorial-core-validators.md#unique), + que está diseñado para trabajar sólo con un modelo. + +Si necesitas realizar varias validaciones contro varios valores, puedes utilizar [[yii\base\DynamicModel]], +que soporta declarar tanto los atributos como las reglas sobre la marcha. Su uso es como a continuación: + +```php +public function actionSearch($name, $email) +{ + $model = DynamicModel::validateData(compact('name', 'email'), [ + [['name', 'email'], 'string', 'max' => 128], + ['email', 'email'], + ]); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +El método [[yii\base\DynamicModel::validateData()]] crea una instancia de `DynamicModel`, define los atributos +utilizando los datos provistos (`name` e `email` en este ejemplo), y entonces llama a [[yii\base\Model::validate()]] +con las reglas provistas. + +Alternativamente, puedes utilizar la sintaxis más "clásica" para realizar la validación ad hoc: + +```php +public function actionSearch($name, $email) +{ + $model = new DynamicModel(compact('name', 'email')); + $model->addRule(['name', 'email'], 'string', ['max' => 128]) + ->addRule('email', 'email') + ->validate(); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +Después de la validación, puedes verificar si la validación tuvo éxito o no llamando al +método [[yii\base\DynamicModel::hasErrors()|hasErrors()]], obteniendo así los errores de validación de la +propiedad [[yii\base\DynamicModel::errors|errors]], como haces con un modelo normal. +Puedes también acceder a los atributos dinámicos definidos a través de la instancia del modelo, por ej., +`$model->name` y `$model->email`. + + +## Crear Validadores + +Además de los [validadores del framework](tutorial-core-validators.md) incluidos en los lanzamientos de Yii, puedes también +crear tus propios validadores. Puedes crear validadores en línea o validadores independientes. + + +### Validadores en Línea + +Un validador en línea es uno definido en términos del método de un modelo o una función anónima. La firma +del método/función es: + +```php +/** + * @param string $attribute el atributo siendo validado actualmente + * @param mixed $params el valor de los "parámetros" dados en la regla + */ +function ($attribute, $params) +``` + +Si falla la validación de un atributo, el método/función debería llamar a [[yii\base\Model::addError()]] para guardar +el mensaje de error en el modelo de manera que pueda ser recuperado más tarde y presentado a los usuarios finales. + +Debajo hay algunos ejemplos: + +```php +use yii\base\Model; + +class MyForm extends Model +{ + public $country; + public $token; + + public function rules() + { + return [ + // un validador en línea definido como el método del modelo validateCountry() + ['country', 'validateCountry'], + + // un validador en línea definido como una función anónima + ['token', function ($attribute, $params) { + if (!ctype_alnum($this->$attribute)) { + $this->addError($attribute, 'El token debe contener letras y dígitos.'); + } + }], + ]; + } + + public function validateCountry($attribute, $params) + { + if (!in_array($this->$attribute, ['USA', 'Web'])) { + $this->addError($attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +> Note: Por defecto, los validadores en línea no serán aplicados si sus atributos asociados reciben entradas vacías + o si alguna de sus reglas de validación ya falló. Si quieres asegurarte de que una regla siempre sea aplicada, + puedes configurar las reglas [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] y/o [[yii\validators\Validator::skipOnError|skipOnError]] + como false en las declaraciones de las reglas. Por ejemplo: +> +> ```php +> [ +> ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false], +> ] +> ``` + + +### Validadores Independientes + +Un validador independiente es una clase que extiende de [[yii\validators\Validator]] o sus sub clases. Puedes implementar +su lógica de validación sobrescribiendo el método [[yii\validators\Validator::validateAttribute()]]. Si falla la validación +de un atributo, llama a [[yii\base\Model::addError()]] para guardar el mensaje de error en el modelo, tal como haces +con los [validadores en línea](#inline-validators). + + +Por ejemplo, el validador en línea de arriba podría ser movida a una nueva clase [[components/validators/CountryValidator]]. + +```php +namespace app\components; + +use yii\validators\Validator; + +class CountryValidator extends Validator +{ + public function validateAttribute($model, $attribute) + { + if (!in_array($model->$attribute, ['USA', 'Web'])) { + $this->addError($model, $attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +Si quieres que tu validador soporte la validación de un valor sin modelo, deberías también sobrescribir +el método[[yii\validators\Validator::validate()]]. Puedes también sobrescribir [[yii\validators\Validator::validateValue()]] +en vez de `validateAttribute()` y `validate()` porque por defecto los últimos dos métodos son implementados +llamando a `validateValue()`. + +Debajo hay un ejemplo de cómo podrías utilizar la clase del validador de arriba dentro de tu modelo. + +```php +namespace app\models; + +use Yii; +use yii\base\Model; +use app\components\validators\CountryValidator; + +class EntryForm extends Model +{ + public $name; + public $email; + public $country; + + public function rules() + { + return [ + [['name', 'email'], 'required'], + ['country', CountryValidator::className()], + ['email', 'email'], + ]; + } +} +``` + + +## Validación del Lado del Cliente + +La validación del lado del cliente basada en JavaScript es deseable cuando la entrada del usuario proviene de formularios HTML, dado que +permite a los usuarios encontrar errores más rápido y por lo tanto provee una mejor experiencia. Puedes utilizar o implementar +un validador que soporte validación del lado del cliente *en adición a* validación del lado del servidor. + +> Info: Si bien la validación del lado del cliente es deseable, no es una necesidad. Su principal propósito es proveer al usuario una mejor + experiencia. Al igual que datos de entrada que vienen del los usuarios finales, nunca deberías confiar en la validación del lado del cliente. Por esta razón, + deberías realizar siempre la validación del lado del servidor llamando a [[yii\base\Model::validate()]], como + se describió en las subsecciones previas. + + +### Utilizar Validación del Lado del Cliente + +Varios [validadores del framework](tutorial-core-validators.md) incluyen validación del lado del cliente. Todo lo que necesitas hacer +es solamente utilizar [[yii\widgets\ActiveForm]] para construir tus formularios HTML. Por ejemplo, `LoginForm` mostrado abajo declara dos +reglas: una utiliza el validador del framework [required](tutorial-core-validators.md#required), el cual es soportado tanto en +lado del cliente como del servidor; y el otro usa el validador en línea `validatePassword`, que es sólo soportado de lado +del servidor. + +```php +namespace app\models; + +use yii\base\Model; +use app\models\User; + +class LoginForm extends Model +{ + public $username; + public $password; + + public function rules() + { + return [ + // username y password son ambos requeridos + [['username', 'password'], 'required'], + + // password es validado por validatePassword() + ['password', 'validatePassword'], + ]; + } + + public function validatePassword() + { + $user = User::findByUsername($this->username); + + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Username o password incorrecto.'); + } + } +} +``` + +El formulario HTML creado en el siguiente código contiene dos campos de entrada: `username` y `password`. +Si envias el formulario sin escribir nada, encontrarás que los mensajes de error requiriendo que +escribas algo aparecen sin que haya comunicación alguna con el servidor. + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + + +``` + +Detrás de escena, [[yii\widgets\ActiveForm]] leerá las reglas de validación declaradas en el modelo +y generará el código JavaScript apropiado para los validadores que soportan validación del lado del cliente. Cuando un usuario +cambia el valor de un campo o envia el formulario, se lanzará la validación JavaScript del lado del cliente. + +Si quieres deshabilitar la validación del lado del cliente completamente, puedes configurar +la propiedad [[yii\widgets\ActiveForm::enableClientValidation]] como false. También puedes deshabilitar la validación +del lado del cliente de campos individuales configurando su propiedad [[yii\widgets\ActiveField::enableClientValidation]] +como false. Cuando `enableClientValidation` es configurado tanto a nivel de campo como a nivel de formulario, +tendrá prioridad la primera. + +### Implementar Validación del Lado del Cliente + + +Para crear validadores que soportan validación del lado del cliente, debes implementar +el método [[yii\validators\Validator::clientValidateAttribute()]], que devuelve una pieza de código JavaScript +que realiza dicha validación. Dentro del código JavaScript, puedes utilizar las siguientes +variables predefinidas: + +- `attribute`: el nombre del atributo siendo validado. +- `value`: el valor siendo validado. +- `messages`: un array utilizado para contener los mensajes de error de validación para el atributo. +- `deferred`: un array con objetos diferidos puede ser insertado (explicado en la subsección siguiente). + +En el siguiente ejemplo, creamos un `StatusValidator` que valida si la entrada es un status válido +contra datos de status existentes. El validador soporta tato tanto validación del lado del servidor como del lado del cliente. + +```php +namespace app\components; + +use yii\validators\Validator; +use app\models\Status; + +class StatusValidator extends Validator +{ + public function init() + { + parent::init(); + $this->message = 'Entrada de Status Inválida.'; + } + + public function validateAttribute($model, $attribute) + { + $value = $model->$attribute; + if (!Status::find()->where(['id' => $value])->exists()) { + $model->addError($attribute, $this->message); + } + } + + public function clientValidateAttribute($model, $attribute, $view) + { + $statuses = json_encode(Status::find()->select('id')->asArray()->column()); + $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return << Tip: El código de arriba muestra principalmente cómo soportar validación del lado del cliente. En la práctica, +> puedes utilizar el validador del framework [in](tutorial-core-validators.md#in) para alcanzar el mismo objetivo. Puedes +> escribir la regla de validación como a como a continuación: +> +> ```php +> [ +> ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], +> ] +> ``` + +> Tip: Si necesitas trabajar con validación del lado del cliente manualmente, por ejemplo, agregar campos dinámicamente o realizar alguna lógica de UI, +> consulta [Trabajar con ActiveForm vía JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) +> en el Yii 2.0 Cookbook. + +### Validación Diferida + +Si necesitas realizar validación del lado del cliente asincrónica, puedes crear [Objetos Diferidos](http://api.jquery.com/category/deferred-object/). +Por ejemplo, para realizar validación AJAX personalizada, puedes utilizar el siguiente código: + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + + deferred.push(def); +JS; +} +``` + +> Note: El método `resolve()` debe ser llamado después de que el atributo ha sido validado. De otra manera la validación + principal del formulario no será completada. + +Por simplicidad, el array `deferred` está equipado con un método de atajo, `add()`, que automáticamente crea un +Objeto Diferido y lo agrega al array `deferred`. Utilizando este método, puedes simplificar el ejemplo de arriba de esta manera, + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + }); +JS; +} +``` + + +### Validación AJAX + +Algunas validaciones sólo pueden realizarse del lado del servidor, debido a que sólo el servidor tiene la información necesaria. +Por ejemplo, para validar si un nombre de usuario es único o no, es necesario revisar la tabla de usuarios del lado del servidor. +Puedes utilizar validación basada en AJAX en este caso. Esta lanzará una petición AJAX de fondo para validar +la entrada mientras se mantiene la misma experiencia de usuario como en una validación del lado del cliente regular. + +Para habilitar la validación AJAX individualmente un campo de entrada, configura la propiedad [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] +de ese campo como true y especifica un único `id` de formulario: + +```php +use yii\widgets\ActiveForm; + +$form = ActiveForm::begin([ + 'id' => 'registration-form', +]); + +echo $form->field($model, 'username', ['enableAjaxValidation' => true]); + +// ... + +ActiveForm::end(); +``` + +Para habiliar la validación AJAX en el formulario entero, configura [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] +como true a nivel del formulario: + +```php +$form = ActiveForm::begin([ + 'id' => 'contact-form', + 'enableAjaxValidation' => true, +]); +``` + +> Note: Cuando la propiedad `enableAjaxValidation` es configurada tanto a nivel de campo como a nivel de formulario, + la primera tendrá prioridad. + +Necesitas también preparar el servidor para que pueda manejar las peticiones AJAX. +Esto puede alcanzarse con una porción de código como la siguiente en las acciones del controlador: + +```php +if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { + Yii::$app->response->format = Response::FORMAT_JSON; + return ActiveForm::validate($model); +} +``` + +El código de arriba chequeará si la petición actual es AJAX o no. Si lo es, responderá +esta petición ejecutando la validación y devolviendo los errores en formato JSON. + +> Info: Puedes también utilizar [Validación Diferida](#deferred-validation) para realizar validación AJAX. + De todos modos, la característica de validación AJAX descrita aquí es más sistemática y requiere menos esfuerzo de escritura de código. + +Cuando tanto `enableClientValidation` como `enableAjaxValidation` son definidas como true, la petición de validación AJAX será lanzada +sólo después de una validación del lado del cliente exitosa. diff --git a/docs/guide-es/intro-upgrade-from-v1.md b/docs/guide-es/intro-upgrade-from-v1.md index bcb4df3d47..db0124c7bc 100644 --- a/docs/guide-es/intro-upgrade-from-v1.md +++ b/docs/guide-es/intro-upgrade-from-v1.md @@ -1,5 +1,5 @@ -Actualizando desde Yii 1.1 -========================== +Actualizar desde Yii 1.1 +======================== Existen muchas diferencias entre las versiones 1.1 y 2.0 de Yii ya que el framework fue completamente reescrito en su segunda versión. @@ -18,7 +18,8 @@ Instalación Yii 2.0 adopta íntegramente [Composer](https://getcomposer.org/), el administrador de paquetes de facto de PHP. Tanto la instalación del núcleo del framework como las extensiones se manejan a través de Composer. Por favor consulta la sección [Comenzando con la Aplicación Básica](start-installation.md) para aprender a instalar Yii 2.0. Si quieres crear extensiones -o transformar extensiones de Yii 1.1 para que sean compatibles con Yii 2.0, consulta la sección [Creando Extensiones](structure-extensions.md#creating-extensions) de la guía. +o transformar extensiones de Yii 1.1 para que sean compatibles con Yii 2.0, consulta +la sección [Creando Extensiones](structure-extensions.md#creating-extensions) de la guía. Requerimientos de PHP @@ -107,7 +108,7 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` -Se puede encontrar más detalles acerca del tema en la sección [Configuración de objetos](concept-configurations.md). +Se puede encontrar más detalles acerca del tema en la sección [Configuración](concept-configurations.md). Eventos @@ -142,7 +143,7 @@ están soportados en la mayor parte del núcleo. Por ejemplo, [[yii\caching\File una ruta de directorios normal como un alias. Un alias está estrechamente relacionado con un namespace de la clase. Se recomienda definir un alias -por cada namespace raíz, y así poder utilizar el autolader de Yii sin otra configuración. +por cada namespace raíz, y así poder utilizar el autoloader de Yii sin otra configuración. Por ejemplo, debido a que `@yii` se refiere al directorio de instalación, una clase como `yii\web\Request` puede ser auto-cargada. Si estás utilizando una librería de terceros, como Zend Framework, puedes definir un alias `@Zend` que se refiera al directorio de instalación @@ -155,15 +156,12 @@ Vistas ------ El cambio más significativo con respecto a las vistas en Yii 2 es que la variable especial `$this` dentro de una vista -ya no se refiere al controlador o widget actual. -En vez de eso, `$this` ahora se refiere al objeto de la *vista*, un concepto nuevo introducido en Yii 2.0. -El objeto *vista* es del tipo [[yii\web\View]], que representa la parte de las vistas en el patrón MVC. Si -quieres acceder al controlador o al widget correspondiente desde la propia vista, -puedes utilizar `$this->context`. +ya no se refiere al controlador o widget actual. En vez de eso, `$this` ahora se refiere al objeto de la *vista*, un concepto nuevo +introducido en Yii 2.0. El objeto *vista* es del tipo [[yii\web\View]], que representa la parte de las vistas +en el patrón MVC. Si quieres acceder al controlador o al widget correspondiente desde la propia vista, puedes utilizar `$this->context`. -Para renderizar una vista parcial (partial) dentro de otra vista, se utiliza `$this->render()`, no `$this->renderPartial()`. -La llamada a `render` además tiene que ser mostrada explícitamente a través de `echo`, ya que el método `render()` -devuelve el resultado de la renderización en vez de mostrarlo directamente. Por ejemplo: +Para renderizar una vista parcial (partial) dentro de otra vista, se utiliza `$this->render()`, no `$this->renderPartial()`. La llamada a `render` además tiene que ser mostrada explícitamente a través de `echo`, +ya que el método `render()` devuelve el resultado de la renderización en vez de mostrarlo directamente. Por ejemplo: ```php echo $this->render('_item', ['item' => $item]); @@ -172,7 +170,8 @@ echo $this->render('_item', ['item' => $item]); Además de utilizar PHP como el lenguaje principal de plantillas (templates), Yii 2.0 está también equipado con soporte oficial de otros dos motores de plantillas populares: Smarty y Twig. El motor de plantillas de Prado ya no está soportado. Para utilizar esos motores, necesitas configurar el componente `view` de la aplicación, definiendo la propiedad [[yii\base\View::$renderers|View::$renderers]]. -Por favor consulta la sección [Motores de Plantillas](tutorial-template-engines.md) para más detalles. +Por favor consulta la sección [Motores de Plantillas](tutorial-template-engines.md) +para más detalles. Modelos @@ -431,7 +430,7 @@ class Customer extends \yii\db\ActiveRecord ``` Ahora puedes utilizar `$customer->orders` para acceder a las órdenes de la tabla relacionada. También puedes utilizar el siguiente -código para realizar una consulta relacional 'al-vuelo' con una condición personalizada: +código para realizar una consulta relacional 'sobre la marcha' con una condición personalizada: ```php $orders = $customer->getOrders()->andWhere('status=1')->all(); @@ -504,9 +503,10 @@ User e IdentityInterface La clase `CWebUser` de 1.1 es reemplazada por [[yii\web\User]], y la clase `CUserIdentity` ha dejado de existir. En cambio, ahora debes implementar [[yii\web\IdentityInterface]] el cual es mucho más directo de usar. -La plantilla de Aplicación Avanzada provee un ejemplo así. +El template de proyecto avanzado provee un ejemplo así. + +Consulta las secciones [Autenticación](security-authentication.md), [Autorización](security-authorization.md), y [Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-es/README.md) para más detalles. -Consulta las secciones [Autenticación](security-authentication.md), [Autorización](security-authorization.md), y [Plantilla de Aplicación Avanzada](tutorial-advanced-app.md) para más detalles. Manejo de URLs @@ -526,8 +526,14 @@ En 1.1, tendrías que haber creado dos reglas diferentes para obtener el mismo r Por favor, consulta la sección [Documentación del Manejo de URLs](runtime-routing.md) para más detalles. -Utilizando Yii 1.1 y 2.x juntos -------------------------------- +Un cambio importante en la convención de nombres para rutas es que los nombres en CamelCase de controladores +y acciones ahora son convertidos a minúsculas y cada palabra separada por un guión, por ejemplo el id del controlador +`CamelCaseController` será `camel-case`. +Consulta la sección acerca de [IDs de controladores](structure-controllers.md#controller-ids) y [IDs de acciones](structure-controllers.md#action-ids) para más detalles. + + +Utilizar Yii 1.1 y 2.x juntos +----------------------------- Si tienes código en Yii 1.1 que quisieras utilizar junto con Yii 2.0, por favor consulta la sección [Utilizando Yii 1.1 y 2.0 juntos](tutorial-yii-integration.md). diff --git a/docs/guide-es/intro-yii.md b/docs/guide-es/intro-yii.md index b5bf6405a5..2a68409efd 100644 --- a/docs/guide-es/intro-yii.md +++ b/docs/guide-es/intro-yii.md @@ -25,6 +25,7 @@ Si estás familiarizado con otros framework, puedes apreciar como se compara Yii - Yii es extremadamente extensible. Puedes personalizar o reemplazar prácticamente cualquier pieza de código de base, como se puede también aprovechar su sólida arquitectura de extensiones para utilizar o desarrollar extensiones distribuibles. - El alto rendimiento es siempre la meta principal de Yii. +<<<<<<< HEAD <<<<<<< HEAD Yii no es un proyecto de un sola persona, detrás de Yii hay un [sólido equipo de desarrollo][], así como una gran comunidad en la que numerosos profesionales contribuyen constantemente a su desarrollo. El equipo de desarrollo de Yii se mantiene atento a las últimas tendencias de desarrollo web, así como a las mejores prácticas y características de otros frameworks y proyectos. @@ -38,6 +39,13 @@ Las buenas prácticas y características más relevantes de otros proyectos se i [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +Yii no es un proyecto de un sola persona, detrás de Yii hay un [sólido equipo de desarrollo][about_yii], así como una gran comunidad en la que numerosos profesionales contribuyen constantemente a su desarrollo. +El equipo de desarrollo de Yii se mantiene atento a las últimas tendencias de desarrollo web, así como a las mejores prácticas y características de otros frameworks y proyectos. +Las buenas prácticas y características más relevantes de otros proyectos se incorporan regularmente a la base del framework y se exponen a través de interfaces simples y elegantes. + +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Versiones de Yii ---------------- diff --git a/docs/guide-es/output-client-scripts.md b/docs/guide-es/output-client-scripts.md new file mode 100644 index 0000000000..a1125c79e1 --- /dev/null +++ b/docs/guide-es/output-client-scripts.md @@ -0,0 +1,98 @@ +Trabajar con Scripts del Cliente +================================ + +> Note: Esta sección se encuentra en desarrollo. + +### Registrar scripts + +Con el objeto [[yii\web\View]] puedes registrar scripts. Hay dos métodos dedicados a esto: +[[yii\web\View::registerJs()|registerJs()]] para scripts en línea +[[yii\web\View::registerJsFile()|registerJsFile()]] para scripts externos. +Los scripts en línea son útiles para configuración y código generado dinámicamente. +El método para agregarlos puede ser utilizado así: + +```php +$this->registerJs("var options = ".json_encode($options).";", View::POS_END, 'my-options'); +``` + +El primer argumento es el código JS real que queremos insertar en la página. El segundo argumento +determina en qué parte de la página debería ser insertado el script. Los valores posibles son: + +- [[yii\web\View::POS_HEAD|View::POS_HEAD]] para la sección head. +- [[yii\web\View::POS_BEGIN|View::POS_BEGIN]] justo después de la etiqueta ``. +- [[yii\web\View::POS_END|View::POS_END]] justo antes de cerrar la etiqueta ``. +- [[yii\web\View::POS_READY|View::POS_READY]] para ejecutar código en el evento `ready` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. +- [[yii\web\View::POS_LOAD|View::POS_LOAD]] para ejecutar código en el evento `load` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. + +El último argumento es un ID único del script, utilizado para identificar el bloque de código y reemplazar otro con el mismo ID +en vez de agregar uno nuevo. En caso de no proveerlo, el código JS en sí será utilizado como ID. + +Un script externo puede ser agregado de esta manera: + +```php +$this->registerJsFile('http://example.com/js/main.js', ['depends' => [\yii\web\JqueryAsset::className()]]); +``` + +Los argumentos para [[yii\web\View::registerJsFile()|registerJsFile()]] son similares a los de +[[yii\web\View::registerCssFile()|registerCssFile()]]. En el ejemplo anterior, +registramos el archivo `main.js` con dependencia de `JqueryAsset`. Esto quiere decir que el archivo `main.js` +será agregado DESPUÉS de `jquery.js`. Si esta especificación de dependencia, el orden relativo entre +`main.js` y `jquery.js` sería indefinido. + +Como para [[yii\web\View::registerCssFile()|registerCssFile()]], es altamente recomendable que utilices +[asset bundles](structure-assets.md) para registrar archivos JS externos más que utilizar [[yii\web\View::registerJsFile()|registerJsFile()]]. + + +### Registrar asset bundles + +Como mencionamos anteriormente, es preferible utilizar asset bundles en vez de usar CSS y JavaScript directamente. Puedes obtener detalles +de cómo definir asset bundles en la sección [gestor de assets](structure-assets.md) de esta guía. Utilizar asset bundles +ya definidos es muy sencillo: + +```php +\frontend\assets\AppAsset::register($this); +``` + + + +### Registrar CSS + +Puedes registrar CSS utilizando [[yii\web\View::registerCss()|registerCss()]] o [[yii\web\View::registerCssFile()|registerCssFile()]]. +El primero registra un bloque de código CSS mientras que el segundo registra un archivo CSS externo. Por ejemplo, + +```php +$this->registerCss("body { background: #f00; }"); +``` + +El código anterior dará como resultado que se agregue lo siguiente a la sección head de la página: + +```html + +``` + +Si quieres especificar propiedades adicionales a la etiqueta style, pasa un array de claves-valores como tercer argumento. +Si necesitas asegurarte que haya sólo una etiqueta style utiliza el cuarto argumento como fue mencionado en las descripciones de meta etiquetas. + +```php +$this->registerCssFile("http://example.com/css/themes/black-and-white.css", [ + 'depends' => [BootstrapAsset::className()], + 'media' => 'print', +], 'css-print-theme'); +``` + +El código de arriba agregará un link al archivo CSS en la sección head de la página. + +* El primer argumento especifica el archivo CSS a ser registrado. +* El segundo argumento especifica los atributos HTML de la etiqueta `` resultante. La opción `depends` + es especialmente tratada. Esta especifica de qué asset bundles depende este archivo CSS. En este caso, depende + del asset bundle [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. Esto significa que el archivo CSS será agregado + *después* de los archivos CSS de [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. +* El último argumento especifica un ID que identifica al archivo CSS. Si no es provisto, se utilizará la URL + del archivo. + + +Es altamente recomendable que ustilices [asset bundles](structure-assets.md) para registrar archivos CSS en vez de +utilizar [[yii\web\View::registerCssFile()|registerCssFile()]]. Utilizar asset bundles te permite combinar y comprimir +varios archivos CSS, deseable en sitios web de tráfico alto. diff --git a/docs/guide-es/output-pagination.md b/docs/guide-es/output-pagination.md new file mode 100644 index 0000000000..9f70cb0ad1 --- /dev/null +++ b/docs/guide-es/output-pagination.md @@ -0,0 +1,72 @@ +Paginación +========== + +Cuando hay muchos datos a mostrar en una sola página, una estrategia común es mostrarlos en varias +páginas y en cada una de ellas mostrar sólo una pequeña porción de datos. Esta estrategia es conocida como *paginación*. + +Yii utiliza el objeto [[yii\data\Pagination]] para representar la información acerca del esquema de paginación. En particular, + +* [[yii\data\Pagination::$totalCount|cuenta total]] especifica el número total de ítems de datos. Ten en cuenta que + este es normalmente un número mucho mayor que el número de ítems necesarios a mostrar en una simple página. +* [[yii\data\Pagination::$pageSize|tamaño de página]] especifica cuántos ítems de datos contiene cada página. El valor + por defecto es 20. +* [[yii\data\Pagination::$page|página actual]] da el número de la página actual (comenzando desde 0). El valor + por defecto es 0, lo que sería la primera página. + +Con un objeto [[yii\data\Pagination]] totalmente especificado, puedes obtener y mostrar datos en partes. Por ejemplo, +si estás recuperando datos de una base de datos, puedes especificar las cláusulas `OFFSET` y `LIMIT` de la consulta a la BD +correspondientes a los valores provistos por la paginación. A continuación hay un ejemplo, + +```php +use yii\data\Pagination; + +// construye una consulta a la BD para obtener todos los artículos con status = 1 +$query = Article::find()->where(['status' => 1]); + +// obtiene el número total de artículos (pero no recupera los datos de los artículos todavía) +$count = $query->count(); + +// crea un objeto paginación con dicho total +$pagination = new Pagination(['totalCount' => $count]); + +// limita la consulta utilizando la paginación y recupera los artículos +$articles = $query->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); +``` + +¿Qué página de artículos devolverá el ejemplo de arriba? Depende de si se le es pasado un parámetro llamado `page`. +Por defecto, la paginación intentará definir la [[yii\data\Pagination::$page|página actual]] con +el valor del parámetro `page`. Si el parámetro no es provisto, entonces tomará por defecto el valor 0. + +Para facilitar la construcción de elementos UI que soporten paginación, Yii provee el widget [[yii\widgets\LinkPager]], +que muestra una lista de botones de navegación que el usuario puede presionar para indicar qué página de datos debería mostrarse. +El widget toma un objeto de paginación y tal manera conoce cuál es la página actual y cuántos botones +debe mostrar. Por ejemplo, + +```php +use yii\widgets\LinkPager; + +echo LinkPager::widget([ + 'pagination' => $pagination, +]); +``` + +Si quieres construir los elementos de UI manualmente, puedes utilizar [[yii\data\Pagination::createUrl()]] para generar URLs que +dirigirán a las distintas páginas. El método requiere un parámetro de página y generará una URL apropiadamente formada +contieniendo el parámetro de página. Por ejemplo, + +```php +// especifica la ruta que la URL generada debería utilizar +// Si no lo especificas, se utilizará la ruta de la petición actual +$pagination->route = 'article/index'; + +// muestra: /index.php?r=article%2Findex&page=100 +echo $pagination->createUrl(100); + +// muestra: /index.php?r=article%2Findex&page=101 +echo $pagination->createUrl(101); +``` + +> Tip: puedes personalizar el parámetro `page` de la consulta configurando + la propiedad [[yii\data\Pagination::pageParam|pageParam]] al crear el objeto de la paginación. diff --git a/docs/guide-es/output-theming.md b/docs/guide-es/output-theming.md index 18a7d454cd..eae80c7219 100644 --- a/docs/guide-es/output-theming.md +++ b/docs/guide-es/output-theming.md @@ -1,14 +1,14 @@ Temas ===== -> Nota: Esta sección está en desarrollo. +> Note: Esta sección está en desarrollo. Un tema (theme) es un directorio de archivos y de vistas (views) y layouts. Cada archivo de este directorio sobrescribe el archivo correspondiente de una aplicación cuando se renderiza. Una única aplicación puede usar múltiples temas para que pueden proporcionar experiencias totalmente diferentes. Solo se puede haber un único tema activo. -> Nota: Los temas no están destinados a ser redistribuidos ya que están demasiado ligados a la aplicación. Si se +> Note: Los temas no están destinados a ser redistribuidos ya que están demasiado ligados a la aplicación. Si se quiere redistribuir una apariencia personalizada, se puede considerar la opción de [asset bundles](structure-assets.md) de archivos CSS y Javascript. @@ -96,4 +96,4 @@ En este caso, primero se buscara la vista en `@app/themes/christmas/site/index.p en `@app/themes/basic/site/index.php`. Si la vista no se encuentra en ninguna de rutas especificadas, se usará la vista de aplicación. -Esta capacidad es especialmente útil si se quieren sobrescribir algunas rutas temporal o condicionalmente. \ No newline at end of file +Esta capacidad es especialmente útil si se quieren sobrescribir algunas rutas temporal o condicionalmente. diff --git a/docs/guide-es/rest-authentication.md b/docs/guide-es/rest-authentication.md index d97036e26f..acb8a6073f 100644 --- a/docs/guide-es/rest-authentication.md +++ b/docs/guide-es/rest-authentication.md @@ -1,22 +1,25 @@ Autenticación ============= -A diferencia de las aplicaciones Web, las API RESTful son usualmente sin estado (stateless), lo que permite que las sesiones o las cookies no sean usadas. -Por lo tanto, cada petición debe llevar alguna suerte de credenciales de autenticación, porque la autenticación del usuario no puede ser mantenida por las sesiones o las cookies. -Una práctica común es enviar una pieza (token) secreta de acceso con cada petición para autenticar al usuario. Dado que una pieza de autenticación -puede ser usada para identificar y autenticar solamente a un usuario, **el API de peticiones tiene que ser siempre enviado vía HTTPS para prevenir ataques que intervengan en la transmisión "man-in-the-middle" (MitM) **. +A diferencia de las aplicaciones Web, las API RESTful son usualmente sin estado (stateless), lo que permite que las sesiones o las cookies +no sean usadas. Por lo tanto, cada petición debe llevar alguna suerte de credenciales de autenticación, +porque la autenticación del usuario no puede ser mantenida por las sesiones o las cookies. Una práctica común +es enviar una pieza (token) secreta de acceso con cada petición para autenticar al usuario. Dado que una pieza de autenticación +puede ser usada para identificar y autenticar solamente a un usuario, **la API de peticiones tiene que ser siempre enviado +vía HTTPS para prevenir ataques tipo "man-in-the-middle" (MitM) **. Hay muchas maneras de enviar una token (pieza) de acceso: -* [Autorización Básica HTTP](http://en.wikipedia.org/wiki/Basic_access_authentication): la pieza de acceso +* [Autenticación Básica HTTP](https://es.wikipedia.org/wiki/Autenticaci%C3%B3n_de_acceso_b%C3%A1sica): la pieza de acceso es enviada como nombre de usuario. Esto sólo debe de ser usado cuando la pieza de acceso puede ser guardada de forma segura en la parte del API del consumidor. Por ejemplo, el API del consumidor es un programa ejecutándose en un servidor. * Parámetro de la consulta: la pieza de acceso es enviada como un parámetro de la consulta en la URL de la API, p.e., `https://example.com/users?access-token=xxxxxxxx`. Debido que muchos servidores dejan los parámetros de consulta en los logs del servidor, - esta aproximación suele ser usada principalmente para servir peticiones `JSONP` + esta aproximación suele ser usada principalmente para servir peticiones `JSONP` que no usen las cabeceras HTTP para enviar piezas de acceso. * [OAuth 2](http://oauth.net/2/): la pieza de acceso es obtenida por el consumidor por medio de una autorización del servidor - y enviada al API del servidor según el protocolo OAuth 2 [tokens HTTP del portador] (http://tools.ietf.org/html/rfc6750). + y enviada al API del servidor según el protocolo + OAuth 2 [tokens HTTP del portador](http://tools.ietf.org/html/rfc6750). Yii soporta todos los métodos anteriores de autenticación. Puedes crear nuevos métodos de autenticación de una forma fácil. @@ -33,18 +36,19 @@ El paso 1 no es necesario pero sí recomendable para las APIs RESTful, pues son Cuando [[yii\web\User::enableSession|enableSession]] es false, el estado de autenticación del usuario puede NO persistir entre peticiones usando sesiones. Si embargo, la autenticación será realizada para cada petición, lo que se consigue en los pasos 2 y 3. -> Tip: Puedes configurar [[yii\web\User::enableSession|enableSession]] del componente de la aplicación `user` en la configuración - de las aplicaciones si estás desarrollando APIs RESTful en términos de un aplicación. Si desarrollas un módulo de las APIs RESTful, - puedes poner la siguiente línea en el método del módulo `init()`, tal y como sigue: +> Tip:Puedes configurar [[yii\web\User::enableSession|enableSession]] del componente de la aplicación `user` en la configuración +> de las aplicaciones si estás desarrollando APIs RESTful en términos de un aplicación. Si desarrollas un módulo de las APIs RESTful, +> puedes poner la siguiente línea en el método del módulo `init()`, tal y como sigue: +> > ```php -public function init() -{ - parent::init(); - \Yii::$app->user->enableSession = false; -} -``` +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` -Por ejemplo, para usar HTTP Basic Auth, puedes configurar el comportamiento `authenticator` como sigue, +Por ejemplo, para usar HTTP Basic Auth, puedes configurar el comportamiento (behavior) `authenticator` como sigue, ```php use yii\filters\auth\HttpBasicAuth; @@ -108,14 +112,16 @@ puede intentar autenticar al usuario en su evento `beforeAction()`. Si la autenticación tiene éxito, el controlador realizará otras comprobaciones (como son límite del ratio, autorización) y entonces ejecutar la acción. La identidad del usuario autenticado puede ser recuperada via `Yii::$app->user->identity`. -Si la autenticación falla, una respuesta con estado HTTP 401 será devuelta junto con otras cabeceras apropiadas (tal como la cabecera para autenticación básica HTTP `WWW-Authenticate`). +Si la autenticación falla, una respuesta con estado HTTP 401 será devuelta junto con otras cabeceras apropiadas +(tal como la cabecera para autenticación básica HTTP `WWW-Authenticate`). ## Autorización Después de que un usuario se ha autenticado, probablementer querrás comprobar si él o ella tiene los permisos para realizar -la acción solicitada. Este proceso es llamado *autorización (authorization)* y está cubierto en detalle en la [Sección de Autorización](security-authorization.md). +la acción solicitada. Este proceso es llamado *autorización (authorization)* y está cubierto en detalle +en la [Sección de Autorización](security-authorization.md). Si tus controladores extienden de [[yii\rest\ActiveController]], puedes sobreescribir -el método [[yii\rest\Controller::checkAccess()|checkAccess()]] para realizar la comprobación de la autorización. +el método [[yii\rest\ActiveController::checkAccess()|checkAccess()]] para realizar la comprobación de la autorización. El método será llamado por las acciones contenidas en [[yii\rest\ActiveController]]. diff --git a/docs/guide-es/rest-controllers.md b/docs/guide-es/rest-controllers.md index 1177cdaf2d..7b55fe91fa 100644 --- a/docs/guide-es/rest-controllers.md +++ b/docs/guide-es/rest-controllers.md @@ -53,7 +53,7 @@ En particular, los siguientes filtros se ejecutarán en el orden en que aparecen * [[yii\filters\ContentNegotiator|contentNegotiator]]: soporta la negociación de contenido, que se explica en la sección [Formateo de respuestas](rest-response-formatting.md); * [[yii\filters\VerbFilter|verbFilter]]: soporta métodos de validación HTTP; -* [[yii\filters\AuthMethod|authenticator]]: soporta la autenticación de usuarios, que se explica en +* [[yii\filters\auth\AuthMethod|authenticator]]: soporta la autenticación de usuarios, que se explica en la sección [Autenticación](rest-authentication.md); * [[yii\filters\RateLimiter|rateLimiter]]: soporta la limitación de rango, que se explica en la sección [Límite de Rango](rest-rate-limiting.md). @@ -79,7 +79,7 @@ public function behaviors() ## Extendiendo `ActiveController` Si tu clase controlador extiende de [[yii\rest\ActiveController]], debe establecer -su propiedad [[yii\rest\ActiveController::modelClass||modelClass]] con el nombre de la clase del recurso +su propiedad [[yii\rest\ActiveController::modelClass|modelClass]] con el nombre de la clase del recurso que planeas servir a través de este controlador. La clase debe extender de [[yii\db\ActiveRecord]]. @@ -149,4 +149,4 @@ public function checkAccess($action, $model = null, $params = []) El método `checkAccess()` será llamado por defecto en las acciones predeterminadas de [[yii\rest\ActiveController]]. Si creas nuevas acciones y también deseas llevar a cabo la comprobación de acceso, debe llamar a este método de forma explícita en las nuevas acciones. -> Consejo: Puedes implementar `checkAccess()` mediante el uso del [Componente Role-Based Access Control (RBAC)](security-authorization.md). +> Tip: Puedes implementar `checkAccess()` mediante el uso del [Componente Role-Based Access Control (RBAC)](security-authorization.md). diff --git a/docs/guide-es/rest-error-handling.md b/docs/guide-es/rest-error-handling.md index 092a6ab20f..f5a663ee74 100644 --- a/docs/guide-es/rest-error-handling.md +++ b/docs/guide-es/rest-error-handling.md @@ -6,7 +6,8 @@ ocurre en el servidor, simplemente puedes lanzar una excepción para notificar a Si puedes identificar la causa del error (p.e., el recurso solicitado no existe), debes considerar lanzar una excepción con el código HTTP de estado apropiado (p.e., [[yii\web\NotFoundHttpException]] representa un código de estado 404). Yii enviará la respuesta a continuación con el correspondiente código de estado HTTP y el texto. Yii puede incluir también -la representación serializada de la excepción en el cuerpo de la respuesta. Por ejemplo: +la representación serializada de la excepción en el cuerpo de la respuesta. +Por ejemplo: ``` HTTP/1.1 404 Not Found @@ -17,7 +18,7 @@ Content-Type: application/json; charset=UTF-8 { "name": "Not Found Exception", - "message": "El recurso solicitado no ha sido encontrado.", + "message": "The requested resource was not found.", "code": 0, "status": 404 } @@ -26,21 +27,23 @@ Content-Type: application/json; charset=UTF-8 La siguiente lista sumariza los códigos de estado HTTP que son usados por el framework REST: * `200`: OK. Todo ha funcionado como se esperaba. -* `201`: El recurso ha creado con éxito en respuesta a la petición `POST`. La cabecera de situación `Location` contiene la URL apuntando al nuevo recurso creado. +* `201`: El recurso ha creado con éxito en respuesta a la petición `POST`. La cabecera de situación `Location` + contiene la URL apuntando al nuevo recurso creado. * `204`: La petición ha sido manejada con éxito y el cuerpo de la respuesta no tiene contenido (como una petición `DELETE`). * `304`: El recurso no ha sido modificado. Puede usar la versión en caché. -* `400`: Petición errónea. Esto puede estar causado por varias acciones de el usuario, como proveer un JSON no válido en el cuerpo de la petición, proveyendo parámetros de acción no válidos, etc. +* `400`: Petición errónea. Esto puede estar causado por varias acciones de el usuario, como proveer un JSON no válido + en el cuerpo de la petición, proveyendo parámetros de acción no válidos, etc. * `401`: Autenticación fallida. * `403`: El usuario autenticado no tiene permitido acceder a la API final. * `404`: El recurso pedido no existe. * `405`: Método no permitido. Por favor comprueba la cabecera `Allow` por los métodos HTTP permitidos. * `415`: Tipo de medio no soportado. El tipo de contenido pedido o el número de versión no es válido. -* `422`: La validación de datos ha fallado (en respuesta a una petición `POST` , por ejemplo). Por favor, comprobad en el cuerpo de la respuesta el mensaje detallado. +* `422`: La validación de datos ha fallado (en respuesta a una petición `POST` , por ejemplo). Por favor, comprueba en el cuerpo de la respuesta el mensaje detallado. * `429`: Demasiadas peticiones. La petición ha sido rechazada debido a un limitación de rango. * `500`: Error interno del servidor. Esto puede estar causado por errores internos del programa. -## Personalizando la Respuesta al Error +## Personalizar la Respuesta al Error A veces puedes querer personalizar el formato de la respuesta del error por defecto . Por ejemplo, en lugar de depender del uso de diferentes estados HTTP para indicar los diferentes errores, puedes querer usar siempre el estado HTTP 200 @@ -64,7 +67,7 @@ Content-Type: application/json; charset=UTF-8 } ``` -Para lograr este objetivo, puedes responder al evento `beforeSend` del componente `response` en la configuración de la aplicación: +Para lograrlo, puedes responder al evento `beforeSend` del componente `response` en la configuración de la aplicación: ```php return [ @@ -74,7 +77,7 @@ return [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; - if ($response->data !== null && !empty(Yii::$app->request->get['suppress_response_code'])) { + if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { $response->data = [ 'success' => $response->isSuccessful, 'data' => $response->data, diff --git a/docs/guide-es/rest-quick-start.md b/docs/guide-es/rest-quick-start.md index 836292f9de..05b9844693 100644 --- a/docs/guide-es/rest-quick-start.md +++ b/docs/guide-es/rest-quick-start.md @@ -74,7 +74,7 @@ del componente de aplicación `request` para usar [[yii\web\JsonParser]] para en ] ``` -> Consejo: La configuración anterior es opcional. Sin la configuración anterior, la API sólo reconocería +> Tip: La configuración anterior es opcional. Sin la configuración anterior, la API sólo reconocería `application/x-www-form-urlencoded` y `multipart/form-data` como formatos de entrada. @@ -93,7 +93,7 @@ para acceder a los datos de user. Las APIs que tienes creado incluyen: * `OPTIONS /users`: muestra los verbos compatibles respecto al punto final `/users`; * `OPTIONS /users/123`: muestra los verbos compatibles respecto al punto final `/users/123`. -> Información: Yii automáticamente pluraliza los nombres de los controladores para usarlo en los puntos finales. +> Info: Yii automáticamente pluraliza los nombres de los controladores para usarlo en los puntos finales. > Puedes configurar esto usando la propiedad [[yii\rest\UrlRule::$pluralize]]. Puedes acceder a tus APIs con el comando `curl` de la siguiente manera, @@ -172,7 +172,7 @@ Content-Type: application/json; charset=UTF-8 {"id":1,"username":"example","email":"user@example.com","created_at":1414674789,"updated_at":1414674789} ``` -> Consejo: También puedes acceder a tus APIs a través del navegador web introduciendo la URL `http://localhost/users`. +> Tip: También puedes acceder a tus APIs a través del navegador web introduciendo la URL `http://localhost/users`. Sin embargo, es posible que necesites algunos plugins para el navegador para enviar cabeceras especificas en la petición. Como se puede ver, en las cabeceras de la respuesta, hay información sobre la cuenta total, número de páginas, etc. @@ -183,7 +183,7 @@ Utilizando los parámetros `fields` y `expand`, puedes también especificar que Por ejemplo, la URL `http://localhost/users?fields=id,email` sólo devolverá los campos `id` y `email`. -> Información: Puedes haber notado que el resultado de `http://localhost/users` incluye algunos campos sensibles, +> Info: Puedes haber notado que el resultado de `http://localhost/users` incluye algunos campos sensibles, > tal como `password_hash`, `auth_key`. Seguramente no quieras que éstos aparecieran en el resultado de tu API. > Puedes y deberías filtrar estos campos como se describe en la sección [Response Formatting](rest-response-formatting.md). diff --git a/docs/guide-es/rest-resources.md b/docs/guide-es/rest-resources.md index 3d8979e4d3..b235310350 100644 --- a/docs/guide-es/rest-resources.md +++ b/docs/guide-es/rest-resources.md @@ -73,7 +73,7 @@ public function fields() } ``` -> Atención: Dado que, por defecto, todos los atributos de un modelo pueden ser incluidos en la devolución del API, debes +> Warning: Dado que, por defecto, todos los atributos de un modelo pueden ser incluidos en la devolución del API, debes > examinar tus datos para estar seguro de que no contiene información sensible. Si se da este tipo de información, > debes sobreescribir `fields()` para filtrarlos. En el ejemplo anterior, escogemos > quitar `auth_key`, `password_hash` y `password_reset_token`. diff --git a/docs/guide-es/rest-response-formatting.md b/docs/guide-es/rest-response-formatting.md index 2b8826acdd..7f967a5d6a 100644 --- a/docs/guide-es/rest-response-formatting.md +++ b/docs/guide-es/rest-response-formatting.md @@ -9,8 +9,9 @@ con el formato de la respuesta: 2. La conversión de objetos recurso en arrays, como está descrito en la sección [Recursos (Resources)](rest-resources.md). Esto es realizado por la clase [[yii\rest\Serializer]]. 3. La conversión de arrays en cadenas con el formato determinado por el paso de negociación de contenido. Esto es - realizado por los [[yii\web\ResponseFormatterInterface|response formatters]] registrados con el - componente de la aplicación [[yii\web\Response::formatters|response]]. + realizado por los [[yii\web\ResponseFormatterInterface|formatos de respuesta]] registrados + con la propiedad [[yii\web\Response::formatters|formatters]] del + [componente de la aplicación](structure-application-components.md) `response`. ## Negociación de contenido (Content Negotiation) diff --git a/docs/guide-es/runtime-bootstrapping.md b/docs/guide-es/runtime-bootstrapping.md index 966f4b0252..c9a6a22df8 100644 --- a/docs/guide-es/runtime-bootstrapping.md +++ b/docs/guide-es/runtime-bootstrapping.md @@ -1,4 +1,4 @@ -Bootstrapping +Bootstrapping ============= El Bootstrapping hace referencia al proceso de preparar el entorno antes de que una aplicación se inicie para resolver y procesar una petición entrante. El se ejecuta en dos lugares: el [script de entrada](structure-entry-scripts.md) y la [aplicación](structure-applications.md). diff --git a/docs/guide-es/runtime-handling-errors.md b/docs/guide-es/runtime-handling-errors.md index e7cfb15926..2571fb27c5 100644 --- a/docs/guide-es/runtime-handling-errors.md +++ b/docs/guide-es/runtime-handling-errors.md @@ -1,25 +1,23 @@ -Gestión de Errores +Gestión de Errores ================== Yii incluye un [[yii\web\ErrorHandler|error handler]] que permite una gestión de errores mucho más práctica que anteriormente. En particular, el gestor de errores de Yii hace lo siguiente para mejorar la gestión de errores: -* Todos los errores no fatales (ej. advertencias (warning), avisos (notices)) se convierten en excepciones - capturables. +* Todos los errores no fatales (ej. advertencias (warning), avisos (notices)) se convierten en excepciones capturables. * Las excepciones y los errores fatales de PHP se muestran con una pila de llamadas (call stack) de información detallada y lineas de código fuente. * Soporta el uso de [acciones de controlador](structure-controllers.md#actions) dedicadas para mostrar errores. * Soporta diferentes formatos de respuesta (response) de errores. El [[yii\web\ErrorHandler|error handler]] esta habilitado de forma predeterminada. Se puede deshabilitar definiendo la -constante `YII_ENABLE_ERROR_HANDLER` con valor false en el -[script de entrada (entry script)](structure-entry-scripts.md) de la aplicación. +constante `YII_ENABLE_ERROR_HANDLER` con valor false en el [script de entrada (entry script)](structure-entry-scripts.md) de la aplicación. + ## Uso del Gestor de Errores -El [[yii\web\ErrorHandler|error handler]] se registra como un -[componente de aplicación](structure-application-components.md) llamado `errorHandler`. Se puede configurar en la -configuración de la aplicación como en el siguiente ejemplo: +El [[yii\web\ErrorHandler|error handler]] se registra como un [componente de aplicación](structure-application-components.md) llamado `errorHandler`. +Se puede configurar en la configuración de la aplicación como en el siguiente ejemplo: ```php return [ @@ -31,8 +29,7 @@ return [ ]; ``` -Con la anterior configuración, el numero del lineas de código fuente que se mostrará en las páginas de excepciones -será como máximo de 20. +Con la anterior configuración, el numero del lineas de código fuente que se mostrará en las páginas de excepciones será como máximo de 20. Como se ha mencionado, el gestor de errores convierte todos los errores de PHP no fatales en excepciones capturables. Esto significa que se puede usar el siguiente código para tratar los errores PHP: @@ -61,29 +58,27 @@ use yii\web\NotFoundHttpException; throw new NotFoundHttpException(); ``` + ## Personalizar la Visualización de Errores -El [[yii\web\ErrorHandler|error handler]] ajusta la visualización del error conforme al valor de la constante -`YII_DEBUG`. Cuando `YII_DEBUG` es `true` (es decir, en modo depuración (debug)), el gestor de errores mostrara las -excepciones con una pila detallada de información y con lineas de código fuente para ayudar a depurar. Y cuando la -variable `YII_DEBUG` es `false`, solo se mostrará el mensaje de error para prevenir la revelación de información -sensible de la aplicación. +El [[yii\web\ErrorHandler|error handler]] ajusta la visualización del error conforme al valor de la constante `YII_DEBUG`. +Cuando `YII_DEBUG` es `true` (es decir, en modo depuración (debug)), el gestor de errores mostrara las +excepciones con una pila detallada de información y con lineas de código fuente para ayudar a depurar. Y cuando la variable `YII_DEBUG` es `false`, +solo se mostrará el mensaje de error para prevenir la revelación de información sensible de la aplicación. -> Información: Si una excepción es descendiente de [[yii\base\UserException]], no se mostrará la pila de llamadas - independientemente del valor de `YII_DEBUG`. Esto es debido a que se considera que estas excepciones se deben a - errores cometidos por los usuarios y los desarrolladores no necesitan corregirlas. +> Info: Si una excepción es descendiente de [[yii\base\UserException]], no se mostrará la pila de llamadas +independientemente del valor de `YII_DEBUG`. Esto es debido a que se considera que estas excepciones se deben a +errores cometidos por los usuarios y los desarrolladores no necesitan corregirlas. -De forma predeterminada, el [[yii\web\ErrorHandler|error handler]] muestra los errores usando dos -[vistas](structure-views.md): +De forma predeterminada, el [[yii\web\ErrorHandler|error handler]] muestra los errores usando dos [vistas](structure-views.md): * `@yii/views/errorHandler/error.php`: se usa cuando deben mostrarse los errores SIN la información de la pila de llamadas. Cuando `YII_DEBUG` es falos, este es el único error que se mostrara. -* `@yii/views/errorHandler/exception.php`: se usa cuando los errores deben mostrarse CON la información de la pila de - llamadas. +* `@yii/views/errorHandler/exception.php`: se usa cuando los errores deben mostrarse CON la información de la pila de llamadas. + +Se pueden configurar las propiedades [[yii\web\ErrorHandler::errorView|errorView]] y [[yii\web\ErrorHandler::exceptionView|exceptionView]] +el gestor de errores para usar nuestros propias vistas para personalizar la visualización de los errores. -Se pueden configurar las propiedades [[yii\web\ErrorHandler::errorView|errorView]] y -[[yii\web\ErrorHandler::exceptionView|exceptionView]] el gestor de errores para usar nuestros propias vistas para -personalizar la visualización de los errores. ### Uso de Acciones de Error @@ -129,8 +124,7 @@ class SiteController extends Controller El código anterior define la acción `error` usando la clase [[yii\web\ErrorAction]] que renderiza un error usando la vista llamada `error`. -Además, usando [[yii\web\ErrorAction]], también se puede definir la acción `error` usando un método de acción como en -el siguiente ejemplo, +Además, usando [[yii\web\ErrorAction]], también se puede definir la acción `error` usando un método de acción como en el siguiente ejemplo, ```php public function actionError() @@ -150,8 +144,15 @@ a las siguientes variables si se define el error como un [[yii\web\ErrorAction]] * `exception`: el objeto de excepción a través del cual se puede obtener más información útil, tal como el código de estado HTTP, el código de error, la pila de llamadas del error, etc. -> Información: Tanto la [plantilla de aplicación básica](start-installation.md) como la - [plantilla de aplicación avanzada](tutorial-advanced-app.md), ya incorporan la acción de error y la vista de error. +> Info: Tanto la [plantilla de aplicación básica](start-installation.md) como la [plantilla de aplicación avanzada](tutorial-advanced-app.md), +ya incorporan la acción de error y la vista de error. + +> Note: Si necesitas redireccionar en un gestor de error, hazlo de la siguiente manera: +> ```php +> Yii::$app->getResponse()->redirect($url)->send(); +> return; +> ``` + ### Personalizar el Formato de Respuesta de Error @@ -219,4 +220,4 @@ Content-Type: application/json; charset=UTF-8 "status": 404 } } -``` \ No newline at end of file +``` diff --git a/docs/guide-es/runtime-logging.md b/docs/guide-es/runtime-logging.md index 505caf01aa..a09ec93cbb 100644 --- a/docs/guide-es/runtime-logging.md +++ b/docs/guide-es/runtime-logging.md @@ -1,4 +1,4 @@ -Registro de anotaciones +Registro de anotaciones ======================= Yii proporciona un poderoso framework dedicado al registro de anotaciones (logging) que es altamente personalizable y @@ -35,7 +35,7 @@ ejemplo registra la huella del mensaje para la categoría `application`: Yii::trace('start calculating average revenue'); ``` -> Información: Los mensajes de registro pueden ser tanto cadenas de texto como datos complejos, como arrays u objetos. +> Info: Los mensajes de registro pueden ser tanto cadenas de texto como datos complejos, como arrays u objetos. Es responsabilidad de los [destinos de registros](#log-targets) tratar los mensajes de registro de manera apropiada. De forma predeterminada, si un mensaje de registro no es una cadena de texto, se exporta como si fuera un string llamando a [[yii\helpers\VarDumper::export()]]. @@ -54,7 +54,7 @@ La constante `__METHOD__` equivale al nombre del método (con el prefijo del nom se encuentra la constante. Por ejemplo, es igual a la cadena `'app\controllers\RevenueController::calculate'` si la linea anterior de código se llamara dentro de este método. -> Información: Los métodos de registro de anotaciones descritos anteriormente en realidad son accesos directos al +> Info: Los métodos de registro de anotaciones descritos anteriormente en realidad son accesos directos al método [[yii\log\Logger::log()|log()]] del [[yii\log\Logger|logger object]] que es un singleton accesible a través de la expresión `Yii::getLogger()`. Cuando se hayan registrado suficientes mensajes o cuando la aplicación haya finalizado, el objeto de registro llamará [[yii\log\Dispatcher|message dispatcher]] para enviar los mensajes de @@ -100,7 +100,7 @@ return [ ]; ``` -> Nota: El componente `log` debe cargarse durante el proceso de [bootstrapping](runtime-bootstrapping.md) para que +> Note: El componente `log` debe cargarse durante el proceso de [bootstrapping](runtime-bootstrapping.md) para que pueda enviar los mensajes de registro a los destinos inmediatamente. Este es el motivo por el que se lista en el array `bootstrap` como se muestra más arriba. @@ -171,7 +171,7 @@ de advertencia de las categorías que coincidan con alguno de los siguientes pat ] ``` -> Información: Cuando se captura una excepción de tipo HTTP por el [gestor de errores](runtime-handling-errors.md), se +> Info: Cuando se captura una excepción de tipo HTTP por el [gestor de errores](runtime-handling-errors.md), se registrará un mensaje de error con el nombre de categoría con formato `yii\web\HttpException:ErrorCode`. Por ejemplo, la excepción [[yii\web\NotFoundHttpException]] causará un mensaje de error del tipo `yii\web\HttpException:404`. @@ -250,7 +250,7 @@ La configuración de aplicación anterior establece el [[yii\log\Dispatcher::tra mensaje de registro se le añadirán como mucho 3 niveles de la pila de llamadas del mensaje que se este registrando; y si `YII_DEBUG` está deshabilitado, no se incluirá información de la pila de llamadas. -> Información: Obtener información de la pila de llamadas no es trivial. Por lo tanto, sólo se debe usar esta +> Info: Obtener información de la pila de llamadas no es trivial. Por lo tanto, sólo se debe usar esta característica durante el desarrollo o cuando se depura la aplicación. ### Liberación (Flushing) y Exportación de Mensajes @@ -273,7 +273,7 @@ return [ ]; ``` -> Información: También se produce la liberación de mensajes cuando la aplicación finaliza, esto asegura que los +> Info: También se produce la liberación de mensajes cuando la aplicación finaliza, esto asegura que los destinos de los registros reciban los mensajes de registro. Cuando el [[yii\log\Logger|logger object]] libera los mensajes de registro enviándolos a los @@ -313,7 +313,7 @@ return [ ]; ``` -> Nota: El uso frecuente de liberación y exportación puede degradar el rendimiento de la aplicación. +> Note: El uso frecuente de liberación y exportación puede degradar el rendimiento de la aplicación. ### Conmutación de Destinos de Registros @@ -395,4 +395,4 @@ Si nos dejamos el `\Yii::endProfile('block1')` o lo intercambiamos `\Yii::endPro Se registra un mensaje de registro con el nivel de severidad `profile` para cada bloque de código que se haya perfilado. Se puede configurar el [destino del registro](#log-targets) para reunir todos los mensajes y exportarlos. El [depurador de Yii](tool-debugger.md) incluye un panel de perfilado de rendimiento que muestra los resultados de -perfilado. \ No newline at end of file +perfilado. diff --git a/docs/guide-es/runtime-overview.md b/docs/guide-es/runtime-overview.md index 7e9780bd93..7ac529eda7 100644 --- a/docs/guide-es/runtime-overview.md +++ b/docs/guide-es/runtime-overview.md @@ -1,4 +1,4 @@ -Información General +Información General =============== Cada vez que una aplicación Yii gestiona una petición, se somete a un flujo de trabajo similar. diff --git a/docs/guide-es/runtime-requests.md b/docs/guide-es/runtime-requests.md index 3179577344..53366c36f4 100644 --- a/docs/guide-es/runtime-requests.md +++ b/docs/guide-es/runtime-requests.md @@ -1,4 +1,4 @@ -Peticiones +Peticiones ========== Las peticiones (requests) hechas a una aplicación son representadas como objetos [[yii\web\Request]] que proporcionan @@ -35,7 +35,7 @@ $name = $request->post('name', ''); // equivalente a: $name = isset($_POST['name']) ? $_POST['name'] : ''; ``` -> Información: En lugar de acceder directamente a `$_GET` y `$_POST` para obtener los parámetros de la petición, es +> Info: En lugar de acceder directamente a `$_GET` y `$_POST` para obtener los parámetros de la petición, es recomendable que se obtengan mediante el componente `request` como en el ejemplo anterior. Esto facilitará la creación de tests ya que se puede simular una componente de request con datos de peticiones personalizados. @@ -53,7 +53,7 @@ $params = $request->bodyParams; $param = $request->getBodyParam('id'); ``` -> Información: A diferencia de los parámetros `GET`, los parámetros enviados desde el formulario a través de `POST`, +> Info: A diferencia de los parámetros `GET`, los parámetros enviados desde el formulario a través de `POST`, `PUT`, `PATCH`, etc. se envían en el cuerpo de la petición. El componente `request` convierte los parámetros cuando se acceda a él a través de los métodos descritos anteriormente. Se puede personalizar la manera en como los parámetros se convierten configurando la propiedad [[yii\web\Request::parsers]]. @@ -126,7 +126,7 @@ puede usar el método de negociación de idioma [[yii\web\Request::getPreferredL lista de idiomas soportados por la aplicación, comparados con [[yii\web\Request::acceptableLanguages|acceptableLanguages]], y devuelve el idioma más apropiado. -> Consejo: También se puede usar el filtro [[yii\filters\ContentNegotiator|ContentNegotiator]] para determinar +> Tip: También se puede usar el filtro [[yii\filters\ContentNegotiator|ContentNegotiator]] para determinar diatónicamente el content type y el idioma que debe usarse en la respuesta. El filtro implementa la negociación de contenido en la parte superior de las propiedades y métodos descritos anteriormente. @@ -138,4 +138,4 @@ Se puede obtener el nombre del host y la dirección IP de la máquina cliente a ```php $userHost = Yii::$app->request->userHost; $userIP = Yii::$app->request->userIP; -``` \ No newline at end of file +``` diff --git a/docs/guide-es/runtime-responses.md b/docs/guide-es/runtime-responses.md index 8b16966c72..249d4c3a6d 100644 --- a/docs/guide-es/runtime-responses.md +++ b/docs/guide-es/runtime-responses.md @@ -1,4 +1,4 @@ -Respuestas +Respuestas ========== Cuando una aplicación finaliza la gestión de una [petición (request)](runtime-requests.md), genera un objeto @@ -74,7 +74,7 @@ $headers->set('Pragma', 'no-cache'); $values = $headers->remove('Pragma'); ``` -> Información: Los nombres de las cabeceras case insensitive, es decir, no discriminan entre mayúsculas y minúsculas. +> Info: Los nombres de las cabeceras case insensitive, es decir, no discriminan entre mayúsculas y minúsculas. Además, las nuevas cabeceras registradas no se enviarán al usuario hasta que se llame al método [[yii\web\Response::send()]]. @@ -156,7 +156,7 @@ public function actionInfo() } ``` -> Nota: Si se crea un objeto response propio, no se podrán aprovechar las configuraciones asignadas para el componente +> Note: Si se crea un objeto response propio, no se podrán aprovechar las configuraciones asignadas para el componente `response` en la configuración de la aplicación. Sin embargo, se puede usar la [inyección de dependencias](concept-di-container.md) para aplicar la configuración común al nuevo objeto response. @@ -188,7 +188,7 @@ respuesta. \Yii::$app->response->redirect('http://example.com/new', 301)->send(); ``` -> Información: De forma predeterminada, el método [[yii\web\Response::redirect()]] asigna el estado de respuesta al +> Info: De forma predeterminada, el método [[yii\web\Response::redirect()]] asigna el estado de respuesta al código de estado 302 que indica al navegador que recurso solicitado está *temporalmente* alojado en una URI diferente. Se puede enviar un código de estado 301 para expresar que el recurso se ha movido de forma *permanente*. @@ -197,7 +197,7 @@ redirección del navegador automática. Para resolver este problema, el método una cabecera `X-Redirect` con el valor de la URL de redirección. En el lado del cliente se puede escribir código JavaScript para leer la esta cabecera y redireccionar el navegador como corresponda. -> Información: Yii contiene el archivo JavaScript `yii.js` que proporciona un conjunto de utilidades comunes de +> Info: Yii contiene el archivo JavaScript `yii.js` que proporciona un conjunto de utilidades comunes de JavaScript, incluyendo la redirección de navegador basada en la cabecera `X-Redirect`. Por tanto, si se usa este fichero JavaScript (registrándolo *asset bundle* [[yii\web\YiiAsset]]), no se necesitará escribir nada más para tener soporte en redirecciones AJAX. @@ -262,4 +262,4 @@ Después de llamar a [[yii\web\Response::send()]] por primera vez, cualquier lla significa que una vez se envíe una respuesta, no se le podrá añadir más contenido. Como se puede observar, el método [[yii\web\Response::send()]] lanza varios eventos útiles. Al responder a estos -eventos, es posible ajustar o decorar la respuesta. \ No newline at end of file +eventos, es posible ajustar o decorar la respuesta. diff --git a/docs/guide-es/runtime-routing.md b/docs/guide-es/runtime-routing.md index 45c61ab06f..6c12ebd0fe 100644 --- a/docs/guide-es/runtime-routing.md +++ b/docs/guide-es/runtime-routing.md @@ -1,4 +1,4 @@ -Enrutamiento y Creación de URLS +Enrutamiento y Creación de URLS =============================== Cuando una aplicación Yii empieza a procesar una URL solicitada, lo primero que hace es convertir la URL en una @@ -12,14 +12,14 @@ convertirla en la ruta original con los parámetros asociados. La principal pieza encargada del enrutamiento y de la creación de URLs es [[yii\web\UrlManager|URL manager]], que se registra como el componente de aplicación `urlManager`. El [[yii\web\UrlManager|URL manager]] proporciona el método [[yii\web\UrlManager::parseRequest()|parseRequest()]] para convertir una petición entrante en una ruta y sus -parámetros asociados y el método [yii\web\UrlManager::createUrl()|createUrl()]] para crear una URL a partir de una +parámetros asociados y el método [[yii\web\UrlManager::createUrl()|createUrl()]] para crear una URL a partir de una ruta dada y sus parámetros asociados. Configurando el componente `urlManager` en la configuración de la aplicación, se puede dotar a la aplicación de reconocimiento arbitrario de formatos de URL sin modificar el código de la aplicación existente. Por ejemplo, se puede usar el siguiente código para crear una URL para la acción `post/view`: -``` php +```php use yii\helpers\Url; // Url::to() llama a UrlManager::createUrl() para crear una URL @@ -152,12 +152,12 @@ echo Url::to(['post/index'], 'https'); Hay que tener en cuenta que en el anterior ejemplo, asumimos que se está usando el formato de URL predeterminado. Si habilita el formato de URL amigable, las URLs creadas serán diferentes, de acuerdo con las -[[yii\web\UrlManager::rules|URL rules] que se usen. +[[yii\web\UrlManager::rules|URL rules]] que se usen. La ruta que se pasa al método [[yii\helpers\Url::to()]] es context sensitive. Esto quiere decir que puede ser una ruta *relativa* o una ruta *absoluta* que serán tipificadas de acuerdo con las siguientes reglas: -- Si una ruta es una cadena vacía, se usará la [yii\web\Controller::route|route]] solicitada actualmente. +- Si una ruta es una cadena vacía, se usará la [[yii\web\Controller::route|route]] solicitada actualmente. - Si la ruta no contiene ninguna barra `/`, se considerará que se trata de un ID de acción del controlador actual y se le antepondrá el valor [[\yii\web\Controller::uniqueId|uniqueId]] del controlador actual. - Si la ruta no tiene barra inicial, se considerará que se trata de una ruta relativa al modulo actual y se le @@ -258,7 +258,7 @@ amigable. El resto de propiedades son opcionales. Sin embargo, la anterior confi y crear URLs. Esta es la propiedad principal con la que se debe trabajar para crear URLs que satisfagan el formato de un requerimiento particular de la aplicación. -> Nota: Para ocultar el nombre del script de entrada en las URLs generadas, además de establecer el +> Note: Para ocultar el nombre del script de entrada en las URLs generadas, además de establecer el [[yii\web\UrlManager::showScriptName|showScriptName]] a falso, puede ser necesaria la configuración del servidor Web para que identifique correctamente que script PHP debe ejecutarse cuando se solicita una URL que no lo especifique. Si se usa el servidor Web Apache, se puede utilizar la configuración recomendada descrita en la sección de @@ -324,7 +324,7 @@ el patrón con el formato ``, donde `ParamName` especifica el expresión regular opcional que se usa para encontrar los valores de los parámetros. Si no se especifica `RegExp` significa que el parámetro debe ser una cadena de texto sin ninguna barra. -> Nota: Solo se pueden especificar expresiones regulares para los parámetros. La parte restante del patrón se +> Note: Solo se pueden especificar expresiones regulares para los parámetros. La parte restante del patrón se considera texto plano. Cuando se usa una regla para convertir una URL, esta rellenara los parámetros asociados con los valores que coincidan @@ -390,7 +390,7 @@ como `comment/create`. Del mismo modo, para crear una URL para una ruta `comment/index`, se aplicará la tercera regla, que crea una URL `/index.php/comments`. -> Información: Mediante la parametrización de rutas es posible reducir el numero de reglas de URL e incrementar +> Info: Mediante la parametrización de rutas es posible reducir el numero de reglas de URL e incrementar significativamente el rendimiento del [[yii\web\UrlManager|URL manager]]. De forma predeterminada, todos los parámetros declarados en una regla son requeridos. Si una URL solicitada no @@ -447,7 +447,7 @@ ejemplo, la siguiente regla convertirá la URL `http://en.example.com/posts` en ] ``` -> Nota: Las reglas con nombres de servidor NO deben incluir el subdirectorio del script de entrada (entry script) en +> Note: Las reglas con nombres de servidor NO deben incluir el subdirectorio del script de entrada (entry script) en sus patrones. Por ejemplo, is la aplicación se encuentra en `http://www.example.com/sandbox/blog`, entonces se debe usar el patrón `http://www.example.com/posts` en lugar de `http://www.example.com/sandbox/blog/posts`. Esto permitirá que la aplicación se pueda desarrollar en cualquier directorio sin la necesidad de cambiar el código de la @@ -479,9 +479,9 @@ propiedad [[yii\web\UrlManager::suffix]] como en el siguiente ejemplo de configu La configuración anterior permitirá al [[yii\web\UrlManager|URL manager]] reconocer las URLs solicitadas y a su vez crear URLs con el sufijo `.html`. -> Consejo: Se puede establecer `/` como el prefijo de URL para que las URLs finalicen con una barra. +> Tip: Se puede establecer `/` como el prefijo de URL para que las URLs finalicen con una barra. -> Nota: Cuando se configura un sufijo de URL, si una URL solicitada no tiene el sufijo, se considerará como una URL +> Note: Cuando se configura un sufijo de URL, si una URL solicitada no tiene el sufijo, se considerará como una URL desconocida. Esta es una practica recomendada para SEO (optimización en motores de búsqueda). A veces, se pueden querer usar sufijos diferentes para URLs diferentes. Esto se puede conseguir configurando la @@ -529,10 +529,10 @@ convertirá en `post/view`. ] ``` -> Nota: Si una regla de URL contiene algún método HTTP en su patrón, la regla solo se usará para aplicar conversiones. +> Note: Si una regla de URL contiene algún método HTTP en su patrón, la regla solo se usará para aplicar conversiones. Se omitirá cuando se llame a [[yii\web\UrlManager|URL manager]] para crear URLs. -> Consejo: Para simplificar el enrutamiento en APIs RESTful, Yii proporciona una clase de reglas de URL +> Tip: Para simplificar el enrutamiento en APIs RESTful, Yii proporciona una clase de reglas de URL [[yii\rest\UrlRule]] especial que es bastante eficiente y soporta ciertas características como pluralización de IDs de controladores. Para conocer más detalles, se puede visitar la sección [Enrutamiento](rest-routing.md) acerca de el desarrollo de APIs RESTful. @@ -558,7 +558,7 @@ array completo de configuración para especificar una regla. El siguiente ejempl ] ``` -> Información: De forma predeterminada si no se especifica una opción `class` para una configuración de regla, se +> Info: De forma predeterminada si no se especifica una opción `class` para una configuración de regla, se usará la clase predeterminada [[yii\web\UrlRule]]. diff --git a/docs/guide-es/runtime-sessions-cookies.md b/docs/guide-es/runtime-sessions-cookies.md index 400801b973..0db302ba9b 100644 --- a/docs/guide-es/runtime-sessions-cookies.md +++ b/docs/guide-es/runtime-sessions-cookies.md @@ -64,7 +64,7 @@ foreach ($session as $name => $value) ... foreach ($_SESSION as $name => $value) ... ``` -> Información: Cuando accedas a los datos de sesión a través del componente `session`, una sesión será automáticamente abierta si no lo estaba antes. Esto es diferente accediendo a los datos de sesión a través de `$_SESSION`, el cual requiere llamar explícitamente a `session_start()`. +> Info: Cuando accedas a los datos de sesión a través del componente `session`, una sesión será automáticamente abierta si no lo estaba antes. Esto es diferente accediendo a los datos de sesión a través de `$_SESSION`, el cual requiere llamar explícitamente a `session_start()`. Cuando trabajas con datos de sesiones que son arrays, el componte `session` tiene una limitación que te previene directamente de modificar un elemento del array. Por ejemplo, @@ -125,7 +125,7 @@ Por defecto la clase [[yii\web\Session]] almacena los datos de sesión como fich Todas estas clases de sesión soportan los mismos métodos de la API. Como consecuencia, puedes cambiar el uso de diferentes almacenamientos de sesión sin la necesidad de modificar el código de tu aplicación que usa sesiones. -> Nota: si quieres acceder a los datos de sesión vía `$_SESSION` mientras estás usando un almacenamiento de sesión personalizado, debes asegurar te que la sesión está ya empezada por [[yii\web\Session::open()]]. Esto ocurre porque los manipuladores de almacenamiento de sesión personalizado son registrados sin este método. +> Note: si quieres acceder a los datos de sesión vía `$_SESSION` mientras estás usando un almacenamiento de sesión personalizado, debes asegurar te que la sesión está ya empezada por [[yii\web\Session::open()]]. Esto ocurre porque los manipuladores de almacenamiento de sesión personalizado son registrados sin este método. Para aprender como configurar y usar estas clases de componentes, por favor consulte la documentación de la API. Abajo está un ejemplo que muestra como configurar [[yii\web\DbSession]] en la configuración de la aplicación para usar una tabla en la base de datos como almacenamiento de sesión: @@ -158,7 +158,7 @@ donde 'BLOB' se refiere al BLOB-type de tu DBMS preferida. Abajo está el tipo B - PostgreSQL: BYTEA - MSSQL: BLOB -> Nota: De acuerdo con la configuración de php.ini `session.hash_function`, puedes necesitar ajustar el tamaño de la columna `id`. Por ejemplo, si `session.hash_function=sha256`, deberías usar el tamaño 64 en vez de 40. +> Note: De acuerdo con la configuración de php.ini `session.hash_function`, puedes necesitar ajustar el tamaño de la columna `id`. Por ejemplo, si `session.hash_function=sha256`, deberías usar el tamaño 64 en vez de 40. ### Flash Data @@ -185,7 +185,7 @@ $result = $session->hasFlash('postDeleted'); Al igual que los datos de sesión regulares, puede almacenar datos arbitrarios como flash data. -Cuando llamas a [yii\web\Session::setFlash()]], sobrescribirá cualquier Flash data que tenga el mismo nombre. +Cuando llamas a [[yii\web\Session::setFlash()]], sobrescribirá cualquier Flash data que tenga el mismo nombre. Para añadir un nuevo flash data a el/los existes con el mismo nombre, puedes llamar a [[yii\web\Session::addFlash()]]. Por ejemplo: @@ -203,7 +203,7 @@ $session->addFlash('alerts', 'You are promoted.'); $alerts = $session->getFlash('alerts'); ``` -> Nota: Intenta no usar a la vez [[yii\web\Session::setFlash()]] con [[yii\web\Session::addFlash()]] para flash data +> Note: Intenta no usar a la vez [[yii\web\Session::setFlash()]] con [[yii\web\Session::addFlash()]] para flash data del mismo nombre. Esto ocurre porque el último método elimina el flash data dentro del array así que puedes añadir un nuevo flash data con el mismo nombre. Como resultado, cuando llamas a [[yii\web\Session::getFlash()]], puedes encontrarte algunas veces que te está devolviendo un array mientras que otras veces te está devolviendo un string, esto depende del orden que invoques a estos dos métodos. @@ -262,18 +262,18 @@ unset($cookies['language']); Además de [[yii\web\Cookie::name|name]], [[yii\web\Cookie::value|value]] las propiedades que se muestran en los anteriores ejemplos, la clase [[yii\web\Cookie]] también define otras propiedades para representar toda la información posible de las cookies, tal como [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]]. Puedes configurar estas propiedades según sea necesario para preparar una cookie y luego añadirlo a la colección de cookies de la respuesta. -> Nota: Para mayor seguridad, el valor por defecto de [[yii\web\Cookie::httpOnly]] es true. Esto ayuda a mitigar el riesgo del acceso a la cookie protegida por script desde el lado del cliente (si el navegador lo soporta). Puedes leer el [httpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) para más detalles. +> Note: Para mayor seguridad, el valor por defecto de [[yii\web\Cookie::httpOnly]] es true. Esto ayuda a mitigar el riesgo del acceso a la cookie protegida por script desde el lado del cliente (si el navegador lo soporta). Puedes leer el [httpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) para más detalles. ### Validación de la Cookie Cuando estás leyendo y enviando cookies a través de los componentes `request` y `response` como mostramos en las dos últimas subsecciones, cuentas con el añadido de seguridad de la validación de cookies el cual protege las cookies de ser modificadas en el lado del cliente. Esto se consigue con la firma de cada cookie con una cadena hash, el cual permite a la aplicación saber si una cookie ha sido modificada en el lado del cliente o no. Si es así, la cookie no será accesible a través de [[yii\web\Request::cookies|cookie collection]] del componente `request`. -> Información: Si falla la validación de una cookie, aún puedes acceder a la misma a través de `$_COOKIE`. Esto sucede porque librerías de terceros pueden manipular de forma propia las cookies, lo cual no implica la validación de las mismas. +> Info: Si falla la validación de una cookie, aún puedes acceder a la misma a través de `$_COOKIE`. Esto sucede porque librerías de terceros pueden manipular de forma propia las cookies, lo cual no implica la validación de las mismas. La validación de cookies es habilitada por defecto. Puedes desactivar lo ajustando la propiedad [[yii\web\Request::enableCookieValidation]] a false, aunque se recomienda encarecidamente que no lo haga. -> Nota: Las cookies que son directamente leídas/enviadas vía `$_COOKIE` y `setcookie()` no serán validadas. +> Note: Las cookies que son directamente leídas/enviadas vía `$_COOKIE` y `setcookie()` no serán validadas. Cuando estás usando la validación de cookie, puedes especificar una [[yii\web\Request::cookieValidationKey]] el cual se usará para generar los strings hash mencionados anteriormente. Puedes hacerlo mediante la configuración del componente `request` en la configuración de la aplicación: @@ -287,5 +287,5 @@ return [ ]; ``` -> Información: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] es crítico para la seguridad de tu aplicación. +> Info: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] es crítico para la seguridad de tu aplicación. Sólo debería ser conocido por personas de confianza. No lo guardes en sistemas de control de versiones. diff --git a/docs/guide-es/security-authorization.md b/docs/guide-es/security-authorization.md new file mode 100644 index 0000000000..00ed6100dc --- /dev/null +++ b/docs/guide-es/security-authorization.md @@ -0,0 +1,527 @@ +Autorización +============ + +Autorización esl el proceso de verificación de que un usuario tenga sugifientes permisos para realizar algo. Yii provee +dos métodos de autorización: Filtro de Control de Acceso y Control Basado en Roles (ACF y RBAC por sus siglas en inglés). + + +## Filtro de Control de Acceso + +Filtro de Control de Acceso (ACF) es un único método de autorización implementado como [[yii\filters\AccessControl]], el cual +es mejor utilizado por aplicaciones que sólo requieran un control de acceso simple. Como su nombre lo indica, ACF es +un [filtro](structure-filters.md) de acción que puede ser utilizado en un controlador o en un módulo. Cuando un usuario solicita +la ejecución de una acción, ACF comprobará una lista de [[yii\filters\AccessControl::rules|reglas de acceso]] +para determinar si el usuario tiene permitido acceder a dicha acción. + +El siguiente código muestra cómo utilizar ACF en el controlador `site`: + +```php +use yii\web\Controller; +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['login', 'signup'], + 'roles' => ['?'], + ], + [ + 'allow' => true, + 'actions' => ['logout'], + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +} +``` + +En el código anterior, ACF es adjuntado al controlador `site` en forma de behavior (comportamiento). Esta es la forma típica de utilizar +un filtro de acción. La opción `only` especifica que el ACF debe ser aplicado solamente a las acciones `login`, `logout` y `signup`. +Las acciones restantes en el controlador `site` no están sujetas al control de acceso. La opción `rules` lista +las [[yii\filters\AccessRule|reglas de acceso]], y se lee como a continuación: + +- Permite a todos los usuarios invitados (sin autenticar) acceder a las acciones `login` y `signup`. La opción `roles` + contiene el signo de interrogación `?`, que es un código especial para representar a los "invitados". +- Permite a los usuarios autenticados acceder a la acción `logout`. El signo `@` es otro código especial que representa + a los "usuarios autenticados". + +ACF ejecuta la comprobación de autorización examinando las reglas de acceso una a una desde arriba hacia abajo hasta que encuentra +una regla que aplique al contexto de ejecución actual. El valor `allow` de la regla que coincida será entonces utilizado +para juzgar si el usuario está autorizado o no. Si ninguna de las reglas coincide, significa que el usuario NO está autorizado, +y el ACF detendrá la ejecución de la acción. + +Cuando el ACF determina que un usuario no está autorizado a acceder a la acción actual, toma las siguientes medidas por defecto: + +* Si el usuario es un invitado, llamará a [[yii\web\User::loginRequired()]] para redireccionar el navegador a la pantalla de login. +* Si el usuario está autenticado, lanzará una excepeción [[yii\web\ForbiddenHttpException]]. + +Puedes personalizar este comportamiento configurando la propiedad [[yii\filters\AccessControl::denyCallback]] como a continuación: + +```php +[ + 'class' => AccessControl::className(), + ... + 'denyCallback' => function ($rule, $action) { + throw new \Exception('No tienes los suficientes permisos para acceder a esta página'); + } +] +``` + +Las [[yii\filters\AccessRule|Reglas de Acceso]] soportan varias opciones. Abajo hay un resumen de las mismas. +También puedes extender de [[yii\filters\AccessRule]] para crear tus propias clases de reglas de acceso personalizadas. + + * [[yii\filters\AccessRule::allow|allow]]: especifica si la regla es de tipo "allow" (permitir) o "deny" (denegar). + + * [[yii\filters\AccessRule::actions|actions]]: especifica con qué acciones coinciden con esta regla. Esta debería ser +un array de IDs de acciones. La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las acciones. + + * [[yii\filters\AccessRule::controllers|controllers]]: especifica con qué controladores coincide +esta regla. Esta debería ser un array de IDs de controladores. Cada ID de controlador es prefijado con el ID del módulo (si existe). +La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, significa que la regla se aplica a todos los controladores. + + * [[yii\filters\AccessRule::roles|roles]]: especifica con qué roles de usuarios coincide esta regla. + Son reconocidos dos roles especiales, y son comprobados vía [[yii\web\User::isGuest]]: + + - `?`: coincide con el usuario invitado (sin autenticar) + - `@`: coincide con el usuario autenticado + + El utilizar otro nombre de rol invocará una llamada a [[yii\web\User::can()]], que requiere habilitar RBAC + (a ser descrito en la próxima subsección). Si la opción está vacía o no definida, significa que la regla se aplica a todos los roles. + + * [[yii\filters\AccessRule::ips|ips]]: especifica con qué [[yii\web\Request::userIP|dirección IP del cliente]] coincide esta regla. +Una dirección IP puede contener el caracter especial `*` al final de manera que coincidan todas las IPs que comiencen igual. +Por ejemplo, '192.168.*' coincide con las direcciones IP en el segmento '192.168.'. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las direcciones IP. + + * [[yii\filters\AccessRule::verbs|verbs]]: especifica con qué método de la solicitud (por ej. `GET`, `POST`) coincide esta regla. +La comparación no distingue minúsculas de mayúsculas. + + * [[yii\filters\AccessRule::matchCallback|matchCallback]]: especifica una función PHP invocable que debe ser llamada para determinar +si la regla debe ser aplicada. + + * [[yii\filters\AccessRule::denyCallback|denyCallback]]: especifica una función PHP invocable que debe ser llamada cuando esta regla +deniegue el acceso. + +Debajo hay un ejemplo que muestra cómo utilizar la opción `matchCallback`, que te permite escribir lógica de comprabación de acceso +arbitraria: + +```php +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['special-callback'], + 'rules' => [ + [ + 'actions' => ['special-callback'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], + ], + ], + ]; + } + + // Callback coincidente llamado! Esta página sólo puede ser accedida cada 31 de Octubre + public function actionSpecialCallback() + { + return $this->render('happy-halloween'); + } +} +``` + + +## Control de Acceso Basado en Roles (RBAC) + +El Control de Acceso Basado en Roles (RBAC) provee una simple pero poderosa manera centralizada de control de acceso. Por favos consulta +la [Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control) para más detalles sobre comparar RBAC +con otros mecanismos de control de acceso más tradicionales. + +Yii implementa una Jerarquía General RBAC, siguiendo el [modelo NIST RBAC](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). +Esto provee la funcionalidad RBAC a través de [componente de la aplicación](structure-application-components.md) [[yii\rbac\ManagerInterface|authManager]]. + +Utilizar RBAC envuelve dos cosas. La primera es construir los datos de autorización RBAC, y la segunda +es utilizar esos datos de autorización para comprobar el acceso en los lugares donde se necesite. + +Para facilitar la próxima descripción, necesitamos primero instroducir algunos conceptos RBAC básicos. + + +### Conceptos Básicos + +Un rol representa una colección de *permisos* (por ej. crear posts, actualizar posts). Un rol puede ser asignado +a uno o varios usuarios. Para comprobar que un usuario cuenta con determinado permiso, podemos comprobar si el usuario tiene asignado +un rol que cuente con dicho permiso. + +Asociado a cada rol o permiso, puede puede haber una *regla*. Una regla representa una porción de código que será +ejecutada durante la comprobación de acceso para determinar si el rol o permiso correspondiente aplica al usuario actual. +Por ejemplo, el permiso "actualizar post" puede tener una regla que compruebe que el usuario actual es el autor del post. +Durante la comprobación de acceso, si el usuario NO es el autor del post, se considerará que el/ella no cuenta con el permiso "actualizar post". + +Tanto los roles como los permisos pueden ser organizados en una jerarquía. En particular, un rol puede consistir en otros roles o permisos; +y un permiso puede consistir en otros permisos. Yii implementa una jerarquía de *orden parcial*, que incluye +una jerarquía de *árbol* especial. Mientras que un rol puede contener un permiso, esto no sucede al revés. + + +### Configurar RBAC + +Antes de definir todos los datos de autorización y ejecutar la comprobación de acceso, necesitamos configurar el +componente de la aplicación [[yii\base\Application::authManager|authManager]]. Yii provee dos tipos de administradores de autorización: +[[yii\rbac\PhpManager]] y [[yii\rbac\DbManager]]. El primero utiliza un archivo PHP para almacenar los datos +de autorización, mientras que el segundo almacena dichos datos en una base de datos. Puedes considerar utilizar el primero si tu aplicación +no requiere una administración de permisos y roles muy dinámica. + + +#### Utilizar `PhpManager` + +El siguiente código muestra cómo configurar `authManager` en la configuración de nuestra aplicación utilizando la clase [[yii\rbac\PhpManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + ], + // ... + ], +]; +``` + +El `authManager` ahora puede ser accedido vía `\Yii::$app->authManager`. + +Por defecto, [[yii\rbac\PhpManager]] almacena datos RBAC en archivos bajo el directorio `@app/rbac`. Asegúrate de que el directorio +y todos sus archivos son tienen permiso de escritura para el proceso del servidor Web si la jerarquía de permisos necesita ser modoficada en línea. + + +#### Utilizar `DbManager` + +El sigiente código muestra cómo configurar `authManager` en la configuración de la aplicación utilizando la clase [[yii\rbac\DbManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', + ], + // ... + ], +]; +``` +> Note: si estás utilizando el template yii2-basic-app, existe el archivo de configuración `config/console.php` donde + necesita declararse `authManager` adicionalmente a `config/web.php`. +> En el caso de yii2-advanced-app, `authManager` sólo debe declararse en `common/config/main.php`. + +`DbManager` utiliza cuatro tablas de la BD para almacenar los datos: + +- [[yii\rbac\DbManager::$itemTable|itemTable]]: la tabla para almacenar los ítems de autorización. Por defecto "auth_item". +- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: la tabla para almacentar la jerarquía de los ítems de autorización. Por defecto "auth_item_child". +- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: la tabla para almacenar las asignaciones de los ítems de autorización. Por defecto "auth_assignment". +- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: la tabla para almacenar las reglas. Por defecto "auth_rule". + +Antes de continuar, necesitas crear las tablas respectivas en la base de datos. Para hacerlo, puedes utilizar las migraciones contenidas en `@yii/rbac/migrations`: + +`yii migrate --migrationPath=@yii/rbac/migrations` + +El `authManager` puede ahora ser accedido vía `\Yii::$app->authManager`. + + +### Construir los Datos de Autorización + +Construir los datos de autorización implica las siguientes tareas: + +- definir roles y permisos; +- establecer relaciones entre roles y permisos; +- definir reglas; +- asociar reglas con roles y permisos; +- asignar roles a usuarios. + +Dependiendo de los requerimientos de flexibilidad en la autorización, las tareas se pueden lograr de diferentes maneras. + +Si la jerarquía de permisos no cambia en absoluto y tienes un número fijo de usuarios puede crear un +[comando de consola](tutorial-console.md#create-command) que va a inicializar los datos de autorización una vez a través de las API que ofrece por `authManager`: + +```php +authManager; + + // agrega el permiso "createPost" + $createPost = $auth->createPermission('createPost'); + $createPost->description = 'Create a post'; + $auth->add($createPost); + + // agrega el permiso "updatePost" + $updatePost = $auth->createPermission('updatePost'); + $updatePost->description = 'Update post'; + $auth->add($updatePost); + + // agrega el rol "author" y le asigna el permiso "createPost" + $author = $auth->createRole('author'); + $auth->add($author); + $auth->addChild($author, $createPost); + + // agrega el rol "admin" y le asigna el permiso "updatePost" + // más los permisos del rol "author" + $admin = $auth->createRole('admin'); + $auth->add($admin); + $auth->addChild($admin, $updatePost); + $auth->addChild($admin, $author); + + // asigna roles a usuarios. 1 y 2 son IDs devueltos por IdentityInterface::getId() + // usualmente implementado en tu modelo User. + $auth->assign($author, 2); + $auth->assign($admin, 1); + } +} +``` + +> Note: Si estas utilizando el template avanzado, necesitas poner tu `RbacController` dentro del directorio `console/controllers` + y cambiar el espacio de nombres a `console/controllers`. + +Después de ejecutar el comando `yii rbac/init`, obtendremos la siguiente jerarquía: + +![Simple RBAC hierarchy](../guide/images/rbac-hierarchy-1.png "Simple RBAC hierarchy") + +"Author" puede crear un post, "admin" puede actualizar posts y hacer todo lo que puede hacer "author". + +Si tu aplicación permite el registro de usuarios, necesitas asignar los roles necesarios para cada usuario nuevo. Por ejemplo, para que todos +los usuarios registrados tengan el rol "author", en el template de aplicación avanzada debes modificar `frontend\models\SignupForm::signup()` +como a continuación: + +```php +public function signup() +{ + if ($this->validate()) { + $user = new User(); + $user->username = $this->username; + $user->email = $this->email; + $user->setPassword($this->password); + $user->generateAuthKey(); + $user->save(false); + + // las siguientes tres líneas fueron agregadas + $auth = Yii::$app->authManager; + $authorRole = $auth->getRole('author'); + $auth->assign($authorRole, $user->getId()); + + return $user; + } + + return null; +} +``` + +Para aplicaciones que requieren un control de acceso complejo con una actualización constante en los datos de autorización, puede ser necesario +desarrollar una interfaz especial (por ej. un panel de administración) utilizando las APIs ofrecidas por `authManager`. + + +### Utilizar Reglas + +Como se había mencionado, las reglas agregan restricciones adicionales a los roles y permisos. Una regla es una clase extendida +de [[yii\rbac\Rule]]. Debe implementar al método [[yii\rbac\Rule::execute()|execute()]]. En la jerarquía que creamos +previamente, "author" no puede editar su propio post. Vamos a arreglarlo. Primero necesitamos una regla para comprobar que el usuario actual es el autor del post: + +```php +namespace app\rbac; + +use yii\rbac\Rule; + +/** + * Comprueba si authorID coincide con el usuario pasado como parámetro + */ +class AuthorRule extends Rule +{ + public $name = 'isAuthor'; + + /** + * @param string|integer $user el ID de usuario. + * @param Item $item el rol o permiso asociado a la regla + * @param array $params parámetros pasados a ManagerInterface::checkAccess(). + * @return boolean un valor indicando si la regla permite al rol o permiso con el que está asociado. + */ + public function execute($user, $item, $params) + { + return isset($params['post']) ? $params['post']->createdBy == $user : false; + } +} +``` + +La regla anterior comprueba si el `post` fue creado por `$user`. Crearemos un permiso especial, `updateOwnPost`, en el comando que hemos utilizado +anteriormente: + +```php +$auth = Yii::$app->authManager; + +// agrega la regla +$rule = new \app\rbac\AuthorRule; +$auth->add($rule); + +// agrega el permiso "updateOwnPost" y le asocia la regla. +$updateOwnPost = $auth->createPermission('updateOwnPost'); +$updateOwnPost->description = 'Update own post'; +$updateOwnPost->ruleName = $rule->name; +$auth->add($updateOwnPost); + +// "updateOwnPost" será utilizado desde "updatePost" +$auth->addChild($updateOwnPost, $updatePost); + +// permite a "author" editar sus propios posts +$auth->addChild($author, $updateOwnPost); +``` + +Ahora tenemos la siguiente jerarquía: + +![RBAC hierarchy with a rule](../guide/images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") + + +### Comprobación de Acceso + +Con los datos de autorización listos, la comprobación de acceso se hace con una simple llamada al método [[yii\rbac\ManagerInterface::checkAccess()]]. +Dado que la mayoría de la comprobación de acceso se hace sobre el usuario actual, para mayor comodidad Yii proporciona el atajo +[[yii\web\User::can()]], que puede ser utilizado como a continuación: + +```php +if (\Yii::$app->user->can('createPost')) { + // crear el post +} +``` + +Si el usuario actual es Jane con `ID=1`, comenzamos desde `createPost` y tratamos de alcanzar a `Jane`: + +![Access check](../guide/images/rbac-access-check-1.png "Access check") + +Con el fin de comprobar si un usuario puede actualizar un post, necesitamos pasarle un parámetro adicional requerido por `AuthorRule`, descrito antes: + +```php +if (\Yii::$app->user->can('updatePost', ['post' => $post])) { + // actualizar post +} +``` + +Aquí es lo que sucede si el usuario actual es John: + + +![Access check](../guide/images/rbac-access-check-2.png "Access check") + +Comenzamos desde `updatePost` y pasamos por `updateOwnPost`. Con el fin de pasar la comprobación de acceso, `AuthorRule` +debe devolver `true` desde su método `execute()`. El método recive `$params` desde la llamada al método `can()`, cuyo valor es +`['post' => $post]`. Si todo está bien, vamos a obtener `author`, el cual es asignado a John. + +En caso de Jane es un poco más simple, ya que ella es un "admin": + +![Access check](../guide/images/rbac-access-check-3.png "Access check") + + +### Utilizar Roles por Defecto + +Un rol por defecto es un rol que esta asignado *implícitamente* a *todos* los usuarios. La llamada a [[yii\rbac\ManagerInterface::assign()]] +no es necesaria, y los datos de autorización no contienen su información de asignación. + +Un rol por defecto es usualmente asociado con una regla que determina si el rol aplica al usuario siendo verificado. + +Los roles por defecto se utilizan a menudo en aplicaciones que ya tienen algún tipo de asignación de roles. Por ejemplo, una aplicación +puede tener una columna "grupo" en su tabla de usuario para representar a qué grupo de privilegio pertenece cada usuario. +Si cada grupo privilegio puede ser conectado a un rol de RBAC, se puede utilizar la función de rol por defecto para asignar +cada usuario a un rol RBAC automáticamente. Usemos un ejemplo para mostrar cómo se puede hacer esto. + +Suponga que en la tabla de usuario, usted tiene una columna `group` que utiliza 1 para representar el grupo administrador y 2 al grupo autor. +Planeas tener dos roles RBAC, `admin` y `author`, para representar los permisos de estos dos grupos, respectivamente. +Puede configurar los datos RBAC de la siguiente manera, + + +```php +namespace app\rbac; + +use Yii; +use yii\rbac\Rule; + +/** + * Comprueba si el grupo coincide + */ +class UserGroupRule extends Rule +{ + public $name = 'userGroup'; + + public function execute($user, $item, $params) + { + if (!Yii::$app->user->isGuest) { + $group = Yii::$app->user->identity->group; + if ($item->name === 'admin') { + return $group == 1; + } elseif ($item->name === 'author') { + return $group == 1 || $group == 2; + } + } + return false; + } +} + +$auth = Yii::$app->authManager; + +$rule = new \app\rbac\UserGroupRule; +$auth->add($rule); + +$author = $auth->createRole('author'); +$author->ruleName = $rule->name; +$auth->add($author); +// ... agrega permisos hijos a $author ... + +$admin = $auth->createRole('admin'); +$admin->ruleName = $rule->name; +$auth->add($admin); +$auth->addChild($admin, $author); +// ... agrega permisos hijos a $admin ... +``` + +Tenga en cuenta que en el ejemplo anterior, dado que "author" es agregado como hijo de "admin", cuando implementes el método `execute()` +de la clase de la regla, necesitas respetar esta jerarquía. Esto se debe a que cuando el nombre del rol es "author", +el método `execute()` devolverá true si el grupo de usuario es tanto 1 como 2 (lo que significa que el usuario se encuentra en +cualquiera de los dos grupos, "admin" o "author"). + +Luego, configura `authManager` enumerando los dos roles en [[yii\rbac\BaseManager::$defaultRoles]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + 'defaultRoles' => ['admin', 'author'], + ], + // ... + ], +]; +``` + +Ahora si realizas una comprobación de acceso, tanto el rol `admin` y como el rol `author` serán comprobados evaluando +las reglas asociadas con ellos. Si la regla devuelve true, significa que la regla aplica al usuario actual. +Basado en la implementación de la regla anterior, esto significa que si el valor `group` en un usuario es 1, el rol `admin` +se aplicaría al usuario; y si el valor de `group` es 2, se le aplicaría el rol `author`. diff --git a/docs/guide-es/security-passwords.md b/docs/guide-es/security-passwords.md new file mode 100644 index 0000000000..77241874de --- /dev/null +++ b/docs/guide-es/security-passwords.md @@ -0,0 +1,31 @@ +Trabajar con Passwords +====================== + +La mayoría de los desarrolladores saben que los passwords no deben ser guardados en texto plano, pero muchos desarrolladores aún creen +que es seguro aplicar a los passowrds hash `md5` o `sha1`. Hubo un tiempo cuando utilizar esos algoritmos de hash mencionados era suficiente, +pero el hardware moderno hace posible que ese tipo de hash e incluso más fuertes, puedan revertirse rápidamente utilizando ataques de fuerza bruta. + +Para poder proveer de una seguridad mayor para los passwords de los usuarios, incluso en el peor de los escenarios (tu aplicación sufre una brecha de seguridad), +necesitas utilizar un algoritmo que resista los ataques de fuerza bruta. La mejor elección actualmente es `bcrypt`. +En PHP, puedes generar un hash `bcrypt` utilizando la [función crypt](http://php.net/manual/en/function.crypt.php). Yii provee +dos funciones auxiliares que hacen que `crypt` genere y verifique los hash más fácilmente. + +Cuando un usuario provee un password por primera vez (por ej., en la registración), dicho password necesita ser pasado por un hash: + + +```php +$hash = Yii::$app->getSecurity()->generatePasswordHash($password); +``` + +El hash puede estar asociado con el atributo del model correspondiente, de manera que pueda ser almacenado en la base de datos para uso posterior. + +Cuando un usuario intenta ingresar al sistema, el password enviado debe ser verificado con el password con hash almacenado previamente: + + +```php +if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { + // todo en orden, dejar ingresar al usuario +} else { + // password erróneo +} +``` diff --git a/docs/guide-es/start-databases.md b/docs/guide-es/start-databases.md index fb9f496286..85e1ac6a73 100644 --- a/docs/guide-es/start-databases.md +++ b/docs/guide-es/start-databases.md @@ -1,57 +1,53 @@ -Trabajando con Bases de Datos -============================= +Trabajar con Bases de Datos +=========================== En esta sección, explicaremos cómo crear una nueva página para mostrar datos de países traídos de una tabla de la base de datos llamada `country`. Para lograr este objetivo, configurarás una conexión a la base de datos, crearás una clase [Active Record](db-active-record.md), una [acción](structure-controllers.md) y una [vista](structure-views.md). -A lo largo de este tutorial, aprenderás +A lo largo de este tutorial, aprenderás a -* Cómo configurar una conexión a la base de datos; -* Cómo definir una clase Active Record; -* Cómo realizar consultas a la base de datos utilizando la clase Active Record; -* Cómo mostrar datos en una vista con paginación incluida. +* configurar una conexión a la base de datos; +* definir una clase Active Record; +* realizar consultas a la base de datos utilizando la clase Active Record; +* mostrar datos en una vista con paginación incluida. Ten en cuenta que para finalizar esta sección, deberás tener al menos conocimientos básicos y experiencia con bases de datos. -En particular, deberás ser capaz de crear una base de datos y saber ejecutar consultas SQL usando alguna herramienta de cliente de -base de datos. +En particular, deberás ser capaz de crear una base de datos y saber ejecutar consultas SQL usando alguna herramienta de cliente de base de datos. -Preparando una Base de Datos ----------------------------- +Preparar una Base de Datos +-------------------------- Para empezar, crea una base de datos llamada `yii2basic` de la cual tomarás los datos en la aplicación. -Puedes elegir entre una base de datos SQLite, MySQL, PostgreSQL, MSSQL u Oracle. Por simplicidad, usaremos MySQL -en la siguiente descripción. +Puedes elegir entre una base de datos SQLite, MySQL, PostgreSQL, MSSQL u Oracle, dado que Yii incluye soporte para varios motores. Por simplicidad, usaremos MySQL en la siguiente descripción. -Crea una tabla llamada `country` e inserta algunos datos de ejemplo. Puedes utilizar las siguientes declaraciones SQL. +A continuación, crea una tabla llamada `country` e inserta algunos datos de ejemplo. Puedes utilizar las siguientes declaraciones SQL. ```sql CREATE TABLE `country` ( - `code` char(2) NOT NULL PRIMARY KEY, - `name` char(52) NOT NULL, - `population` int(11) NOT NULL DEFAULT '0' + `code` CHAR(2) NOT NULL PRIMARY KEY, + `name` CHAR(52) NOT NULL, + `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `Country` VALUES ('AU','Australia',18886000); -INSERT INTO `Country` VALUES ('BR','Brazil',170115000); -INSERT INTO `Country` VALUES ('CA','Canada',1147000); -INSERT INTO `Country` VALUES ('CN','China',1277558000); -INSERT INTO `Country` VALUES ('DE','Germany',82164700); -INSERT INTO `Country` VALUES ('FR','France',59225700); -INSERT INTO `Country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `Country` VALUES ('IN','India',1013662000); -INSERT INTO `Country` VALUES ('RU','Russia',146934000); -INSERT INTO `Country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` -Al final, tendrás una base de datos llamada `yii2basic`, y dentro de esta, una tabla llamada `country` con diez -registros en ella. +Al final, tendrás una base de datos llamada `yii2basic`, y dentro de esta, una tabla llamada `country` con diez registros en ella. - -Configurando una conexión a la Base de Datos --------------------------------------------- +Configurar una conexión a la Base de Datos +------------------------------------------ Asegúrate de tener instalado la extensión de PHP [PDO](http://www.php.net/manual/es/book.pdo.php) y el driver de PDO para el motor que estés utilizando (ej. `pdo_mysql` para MySQL). Este es un requisito básico si tu aplicación @@ -72,19 +68,25 @@ return [ ]; ``` -Esta es una típica [configuración](concept-configurations.md) basada en archivos. Especifica los parámetros -necesarios para crear e inicializar una instancia de [[yii\db\Connection]] a través de la cual puedes realizar +El archivo `config/db.php` representa la típica [configuración](concept-configurations.md) basada en archivos. Este archivo de configuración en particular +especifica los parámetros necesarios para crear e inicializar una instancia de [[yii\db\Connection]] a través de la cual puedes realizar consultas SQL contra la base de datos subyacente. La conexión a la base de datos realizada anteriormente puede ser accedida mediante `Yii::$app->db`. -> Información: El archivo `config/db.php` será incluido en el archivo principal de configuración `config/web.php`, +> Info: El archivo `config/db.php` será incluido en el archivo principal de configuración `config/web.php`, el cual especifica cómo la instancia de la [aplicación](structure-applications.md) debe ser inicializada. Para más información, consulta la sección [Configuraciones](concept-configurations.md). +Si necesitas trabajar con bases de datos cuyo soporte no está incluído en Yii, revisa las siguientes extensiones: -Creando un Active Record ------------------------- +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) + + +Crear un Active Record +---------------------- Para representar y extraer datos de la tabla `country`, crea una clase [Active Record](db-active-record.md) llamada `Country` y guárdala en el archivo `models/Country.php`. @@ -101,12 +103,13 @@ class Country extends ActiveRecord } ``` -La clase `Country` extiende de [[yii\db\ActiveRecord]]. No necesitas escribir ningún código dentro de ella. -Yii adivinará la tabla correspondiente a la clase desde su nombre. En caso de que esto no funcione, puedes -sobrescribir el método [[yii\db\ActiveRecord::tableName()]] para especificar la tabla asociada a la clase. +La clase `Country` extiende de [[yii\db\ActiveRecord]]. No necesitas escribir ningún código dentro de ella! Con tan sólo el código de arriba, +Yii adivinará la tabla correspondiente a la clase desde su nombre. -Utilizando la clase `Country`, puedes manipular los datos de la tabla `country` fácilmente. Debajo hay sencillos -ejemplos de código que muestran cómo utilizar la clase `Country`. +> Info: Si no se puede realizar un emparejamiento entre el nombre de la clase y la tabla, puedes +sobrescribir el método [[yii\db\ActiveRecord::tableName()]] para especificar explícitamente el nombre de la tabla asiciada. + +Utilizando la clase `Country`, puedes manipular los datos de la tabla `country` fácilmente, como se muestra en los siguiente ejemplos: ```php use app\models\Country; @@ -125,14 +128,12 @@ $country->name = 'U.S.A.'; $country->save(); ``` -> Información: Active Record es una potente forma de acceder y manipular datos de una base de datos de una manera -orientada a objetos. -Puedes encontrar información más detallada acerca de [Active Record](db-active-record.md). Además de Active Record, -puedes utilizar un método de acceso de bajo nivel llamado [Data Access Objects](db-dao.md). +> Info: Active Record es una potente forma de acceder y manipular datos de una base de datos de una manera orientada a objetos. +Puedes encontrar información más detallada acerca de [Active Record](db-active-record.md). Además de Active Record, puedes utilizar un método de acceso de bajo nivel llamado [Data Access Objects](db-dao.md). -Creando una Acción ------------------- +Crear una Acción +---------------- Para mostrar el país a los usuarios, necesitas crear una acción. En vez de hacerlo en el controlador `site` como lo hiciste en las secciones previas, tiene más sentido crear un nuevo controlador que englobe todas las @@ -174,8 +175,7 @@ class CountryController extends Controller Guarda el código anterior en el archivo `controllers/CountryController.php`. -La acción `index` llama a `Country::find()` para generar una consulta a la base de datos y traer todos los datos -de la tabla `country`. +La acción `index` llama a `Country::find()` para generar una consulta a la base de datos y traer todos los datos de la tabla `country`. Para limitar la cantidad de registros traídos en cada petición, la consulta es paginada con la ayuda de un objeto [[yii\data\Pagination]]. El objeto `Pagination` sirve para dos propósitos: @@ -188,8 +188,8 @@ Al final, la acción `index` renderiza una vista llamada `index` y le pasa los d de paginación relacionada. -Creando una Vista ------------------ +Crear una Vista +--------------- Bajo el directorio `views`, crea primero un sub-directorio llamado `country`. Este será usado para contener todas las vistas renderizadas por el controlador `country`. @@ -214,8 +214,7 @@ use yii\widgets\LinkPager; ``` La vista consiste en dos partes. En la primera, los datos de países son recorridos y renderizados como una lista HTML. -En la segunda parte, un widget [[yii\widgets\LinkPager]] es renderizado usando la información de paginación -pasada desde la acción. +En la segunda parte, un widget [[yii\widgets\LinkPager]] es renderizado usando la información de paginación pasada desde la acción. El widget `LinkPager` muestra una lista de botones que representan las páginas disponibles. Haciendo click en cualquiera de ellas mostrará los datos de países de la página correspondiente. @@ -226,7 +225,7 @@ Probándolo Para ver cómo funciona, utiliza a la siguiente URL en tu navegador: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` ![Lista de Países](images/start-country-list.png) @@ -236,7 +235,7 @@ Si haces click en el botón "2", verás que la página muestra otros cinco país Observa más cuidadosamente y verás que la URL en el navegador cambia a ``` -http://hostname/index.php?r=country/index&page=2 +http://hostname/index.php?r=country%2Findex&page=2 ``` Entre bastidores, [[yii\data\Pagination|Pagination]] está realizando su magia. diff --git a/docs/guide-es/start-forms.md b/docs/guide-es/start-forms.md index 2516677d3f..64e58771b4 100644 --- a/docs/guide-es/start-forms.md +++ b/docs/guide-es/start-forms.md @@ -106,7 +106,7 @@ Si todo está bien, la acción mostrará una vista llamada `entry-confirm` para con el usuario que acepta los datos que ha ingresado. De otra manera, la vista `entry` será mostrada, y mostrará el formulario HTML junto con los mensajes de error de validación (si es que hay alguno). -> Información: La expresión `Yii::$app` representa la instancia de la [aplicación](structure-applications.md) +> Info: La expresión `Yii::$app` representa la instancia de la [aplicación](structure-applications.md) que es un singleton globalmente accesible. También es un [service locator](concept-service-locator.md) (localizador de servicio) que provee los componentes, tales como `request`, `response`, `db`, etc. para soportar funcionalidades específicas. En el código de arriba, el componente `request` es utilizado para acceder los datos `$_POST`. diff --git a/docs/guide-es/start-gii.md b/docs/guide-es/start-gii.md index 709acc079c..281ffaac17 100644 --- a/docs/guide-es/start-gii.md +++ b/docs/guide-es/start-gii.md @@ -24,7 +24,9 @@ $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` @@ -117,7 +119,7 @@ o por si desearas personalizarlos: * Modelos: `models/Country.php` y `models/CountrySearch.php` * Vistas: `views/country/*.php` -> Información: Gii está diseñado para ser una herramienta altamente configurable. Utilizándola con sabiduría +> Info: Gii está diseñado para ser una herramienta altamente configurable. Utilizándola con sabiduría puede acelerar enormemente la velocidad de desarrollo de tu aplicación. Para más detalles, consulta la sección [Gii](tool-gii.md). diff --git a/docs/guide-es/start-hello.md b/docs/guide-es/start-hello.md index 76ff1da77e..a75f400539 100644 --- a/docs/guide-es/start-hello.md +++ b/docs/guide-es/start-hello.md @@ -22,7 +22,7 @@ Para la tarea "Hola", crearás una [acción](structure-controllers.md#creating-a un parámetro `message` de la petición y muestra este mensaje de vuelta al usuario. Si la petición no provee un parámetro `message`, la acción mostrará el mensaje por defecto "Hola". -> Información: Las [acciones](structure-controllers.md#creating-actions) son objetos que los usuarios finales pueden utilizar directamente para +> Info: Las [acciones](structure-controllers.md#creating-actions) son objetos que los usuarios finales pueden utilizar directamente para su ejecución. Las acciones están agrupadas por [controladores](structure-controllers.md) (controllers). El resultado de la ejecución de una acción es la respuesta que el usuario final recibirá. @@ -101,7 +101,7 @@ Probándolo Después de crear la acción y la vista, puedes acceder a la nueva página abriendo el siguiente URL: ``` -http://hostname/index.php?r=site/say&message=Hello+World +http://hostname/index.php?r=site%2Fsay&message=Hello+World ``` ![Hello World](images/start-hello-world.png) @@ -111,7 +111,7 @@ Esta URL resultará en una página mostrando "Hello World". La página comparte Si omites el parámetro `message` en el URL, verás que la página muestra sólo "Hola". Esto es porque `message` es pasado como un parámetro al método `actionSay()`, y cuando es omitido, el valor por defecto `"Hola"` será utilizado. -> Información: La nueva página comparte el mismo encabezado y pie de página que otras páginas porque el método [[yii\web\Controller::render()|render()]] +> Info: La nueva página comparte el mismo encabezado y pie de página que otras páginas porque el método [[yii\web\Controller::render()|render()]] automáticamente inyectará el resultado de la vista `say` en el [layout](structure-views.md#layouts), que en este caso está localizada en `views/layouts/main.php`. @@ -122,7 +122,7 @@ la parte `ActionID` para determinar cual acción debe ser inizializada para hace En este ejemplo, la ruta `site/say` será respondida por la clase controlador `SiteController` y la acción `say`. Como resultado, el método `SiteController::actionSay()` será llamado para manejar el requerimiento. -> Información: Al igual que las acciones, los controladores tambien tienen ID únicos que los identifican en una aplicación. +> Info: Al igual que las acciones, los controladores tambien tienen ID únicos que los identifican en una aplicación. Los ID de los Controladores utilizan las mismas reglas de nombrado que los ID de las acciones. Los nombres de las clases de los controladores son derivados de los ID de los controladores removiendo los guiones de los ID, colocando la primera letra en mayúscula en cada palabra, y colocando el sufijo `Controller` al resultado. Por ejemplo, el ID del controlador `post-comentario` corresponde al nombre de clase del controlador `PostComentarioController`. diff --git a/docs/guide-es/start-installation.md b/docs/guide-es/start-installation.md index 2fd798cc0a..d9c4dc5d96 100644 --- a/docs/guide-es/start-installation.md +++ b/docs/guide-es/start-installation.md @@ -1,70 +1,116 @@ -Instalando Yii -============== +Instalar Yii +============ +<<<<<<< HEAD <<<<<<< HEAD Yii puede ser instalado de dos maneras, usando [Composer](http://getcomposer.org/) o descargando un archivo comprimido. ======= Yii puede ser instalado de dos maneras, usando [Composer](https://getcomposer.org/) o descargando un archivo comprimido. >>>>>>> yiichina/master Es preferible usar la primera forma, ya que te permite instalar [extensiones](structure-extensions.md) o actualizar Yii ejecutando un simple comando. +======= +Puedes instalar Yii de dos maneras, utilizando el administrador de paquetes [Composer](https://getcomposer.org/) o descargando un archivo comprimido. +La forma recomendada es la primera, ya que te permite instalar nuevas [extensions](structure-extensions.md) o actualizar Yii con sólo ejecutar un comando. +>>>>>>> master -> Nota: A diferencia de Yii 1, la instalación estándar de Yii 2 resulta en la descarga e instalación tanto del framework como del esqueleto de la aplicación. +La instalación estándar de Yii cuenta tanto con el framework como un template de proyecto instalados. +Un template de proyecto es un proyecto Yii funcional que implementa algunas características básicas como: login, formulario de contacto, etc. +El código está organizado de una forma recomendada. Por lo tanto, puede servir como un buen punto de partida para tus proyectos. + +En esta y en las próximas secciones, describiremos cómo instalar Yii con el llamado *Template de Proyecto Básico* +y cómo implementar nuevas características por encima del template. Yii también provee otro template llamado +[Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) qué es mejor para desarrollar aplicaciones con varios niveles +en el entorno de un equipo de desarrollo. + +> Info: El Template de Proyecto Básico es adecuado para desarrollar el 90 porciento de las aplicaciones Web. Difiere del + Template de Proyecto Avanzado principalmente en cómo está organizado el código. Si eres nuevo en Yii, te recomendamos + utilizar el Template de Proyecto Básico por su simplicidad pero funcionalidad suficiente. -Instalando a través de Composer +Installing via Composer ------------------------------- Si aún no tienes Composer instalado, puedes hacerlo siguiendo las instrucciones que se encuentran en [getcomposer.org](https://getcomposer.org/download/). En Linux y Mac OS X, se ejecutan los siguientes comandos: +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master mv composer.phar /usr/local/bin/composer +======= +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` +>>>>>>> master En Windows, tendrás que descargar y ejecutar [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). Por favor, consulta la [Documentación de Composer](https://getcomposer.org/doc/) si encuentras algún problema o deseas obtener un conocimiento más profundo sobre su utilización. -Si ya tienes composer instalado asegurate que esté actualizado ejecutando `composer self-update` +Si ya tienes composer instalado, asegúrate de tener una versión actualizada. Puedes actualizar Composer +ejecutando el comando `composer self-update` Teniendo Composer instalado, puedes instalar Yii ejecutando los siguientes comandos en un directorio accesible vía Web: -Nota: es posible que en al ejecutar el primer comando te pida tu username +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master composer create-project --prefer-dist yiisoft/yii2-app-basic basic +======= +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` -El comando anterior instala Yii dentro del directorio `basic`. +El primer comando instala [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), +que permite administrar dependencias de paquetes bower y npm a través de Composer. Sólo necesitas ejecutar este comando +una vez. El segundo comando instala Yii en un directorio llamado `basic`. Puedes elegir un nombre de directorio diferente si así lo deseas. +>>>>>>> master -> Tip: Si quieres instalar la última versión de desarrollo de Yii, puedes utilizar el siguiente comando, -> que añade una [opción de estabilidad mínima](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> Note: Durante la instalación, Composer puede preguntar por tus credenciales de acceso de Github. Esto es normal ya que Composer +> necesita obtener suficiente límite de acceso de la API para traer la información de dependencias de Github. Para más detalles, +> consulta la [documentación de Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +> Tip: Si quieres instalar la última versión de desarrollo de Yii, puedes utilizar uno de los siguientes comandos, +> que agregan una [opción de estabilidad](https://getcomposer.org/doc/04-schema.md#minimum-stability): > -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` > -> Ten en cuenta que la versión de desarrollo de Yii no debería ser usada para producción ya que podría romper el funcionamiento actual de la aplicación. +> Ten en cuenta que la versión de desarrollo de Yii no debería ser utilizada en producción ya que podría romper tu código actual. -Instalando desde un Archivo Comprimido --------------------------------------- +Instalar desde un Archivo Comprimido +------------------------------------ -Instalar Yii desde un archivo comprimido involucra dos pasos: +Instalar Yii desde un archivo comprimido involucra tres pasos: 1. Descargar el archivo desde [yiiframework.com](http://www.yiiframework.com/download/yii2-basic). 2. Descomprimirlo en un directorio accesible vía Web. +3. Modificar el archivo `config/web.php` introduciendo una clave secreta para el ítem de configuración `cookieValidationKey` + (esto se realiza automáticamente si estás instalando Yii a través de Composer): + + ```php + // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` Otras Opciones de Instalación ----------------------------- Las instrucciones anteriores muestran cómo instalar Yii, lo que también crea una aplicación Web lista para ser usada. -Este es un buen punto de partida para pequeñas aplicaciones, o cuando apenas estás aprendiendo a utilizar Yii. +Este es un buen punto de partida para la mayoría de proyectos, tanto grandes como pequeños. Es especialmente adecuado si recién +estás aprendiendo a utilizar Yii. Pero también hay otras opciones de instalación disponibles: @@ -77,52 +123,68 @@ Pero también hay otras opciones de instalación disponibles: Verificando las Instalación --------------------------- -Después de la instalación, puedes acceder a la aplicación instalada a través de la siguiente URL: - -``` -http://localhost/basic/web/index.php +Una vez finalizada la instalación, o bien configura tu servidor web (mira la sección siguiente) o utiliza +el [servidor web incluido en PHP](https://secure.php.net/manual/en/features.commandline.webserver.php) ejecutando el siguiente +comando de consola estando parado en el directorio `web` de la aplicación: + +```bash +php yii serve ``` -Esta URL da por hecho que Yii se instaló en un directorio llamado `basic`, directamente bajo el directorio del Servidor Web, -y que el Servidor Web está corriendo en tu máquina local (`localhost`). Sino, podrías necesitar ajustarlo de acuerdo a tu entorno de instalación. +> Note: Por defecto el servidor HTTP escuchará en el puerto 8080. De cualquier modo, si el puerto está en uso o deseas +servir varias aplicaciones de esta manera, podrías querer especificar qué puerto utilizar. Sólo agrega el argumento --port: + +```bash +php yii serve --port=8888 +``` + +Puedes utilizar tu navegador para acceder a la aplicación instalada de Yii en la siguiente URL: + +``` +http://localhost:8080/. + ![Instalación Correcta de Yii](images/start-app-installed.png) Deberías ver la página mostrando "Congratulations!" en tu navegador. Si no ocurriera, por favor chequea que la instalación -de PHP satisface los requerimientos de Yii. Esto puedes hacerlo usando cualquiera de los siguientes procedimientos: +de PHP satisfaga los requerimientos de Yii. Esto puedes hacerlo usando cualquiera de los siguientes procedimientos: -* Visitando la URL `http://localhost/basic/requirements.php` en tu navegador +* Copiando `/requirements.php` a `/web/requirements.php` y visitando la URL `http://localhost/basic/requirements.php` en tu navegador * Corriendo los siguientes comandos: - ``` + ```bash cd basic php requirements.php ``` - -Deberías configurar tu instalación de PHP para que satisfaga los requisitos mínimos de Yii. Lo que es más importante, debes tener PHP 5.4 o mayor. -También deberías instalar la [Extensión de PHP PDO](http://www.php.net/manual/es/pdo.installation.php) y el correspondiente driver de base de datos -(como `pdo_mysql` para bases de datos MySQL), si tu aplicación lo necesitara. + +Deberías configurar tu instalación de PHP para que satisfaga los requisitos mínimos de Yii. Lo que es más importante, +debes tener PHP 5.4 o mayor. También deberías instalar la [Extensión de PHP PDO](http://www.php.net/manual/es/pdo.installation.php) +y el correspondiente driver de base de datos (como `pdo_mysql` para bases de datos MySQL), si tu aplicación lo necesitara. -Configurando Servidores Web ---------------------------- +Configurar Servidores Web +------------------------- -> Información: Puedes saltear esta sección por ahora si sólo estás probando Yii sin intención de poner la aplicación en un servidor de producción. +> Info: Puedes saltear esta sección por ahora si sólo estás probando Yii sin intención + de poner la aplicación en un servidor de producción. -La aplicación instalada debería estar lista para usar tanto con un [servidor HTTP Apache](http://httpd.apache.org/) como con un [servidor HTTP Nginx](http://nginx.org/), -en Windows, Mac OS X, o Linux. +La aplicación instalada siguiendo las instrucciones mencionadas debería estar lista para usar tanto +con un [servidor HTTP Apache](http://httpd.apache.org/) como con un [servidor HTTP Nginx](http://nginx.org/), +en Windows, Mac OS X, o Linux utilizando PHP 5.4 o mayor. Yii 2.0 también es compatible con [HHVM](http://hhvm.com/) +de Facebook. De todos modos, hay algunos casos donde HHVM se comporta diferente del +PHP oficial, por lo que tendrás que tener cuidados extra al utilizarlo. -En un servidor de producción, podrías querer configurar el servidor Web para que la aplicación sea accedida a través de la -URL `http://www.example.com/index.php` en vez de `http://www.example.com/basic/web/index.php`. Tal configuración -require apuntar el document root de tu servidor Web al directorio `basic/web`. También podrías querer ocultar `index.php` -de la URL, como se describe en la sección [Parseo y Generación de URLs](runtime-url-handling.md). +En un servidor de producción, podrías querer configurar el servidor Web para que la aplicación sea accedida +a través de la URL `http://www.example.com/index.php` en vez de `http://www.example.com/basic/web/index.php`. Tal configuración +require apuntar el document root de tu servidor Web a la carpeta `basic/web`. También podrías +querer ocultar `index.php` de la URL, como se describe en la sección [Parseo y Generación de URLs](runtime-url-handling.md). En esta sub-sección, aprenderás a configurar tu servidor Apache o Nginx para alcanzar estos objetivos. -> Información: Al definir `basic/web` como document root, también previenes que los usuarios finales accedan +> Info: Al definir `basic/web` como document root, también previenes que los usuarios finales accedan al código privado o archivos con información sensible de tu aplicación que están incluidos en los directorios del mismo nivel que `basic/web`. Denegando el acceso es una importante mejora en la seguridad. -> Información: En caso de que tu aplicación corra en un entorno de hosting compartido donde no tienes permisos para modificar +> Info: En caso de que tu aplicación corra en un entorno de hosting compartido donde no tienes permisos para modificar la configuración del servidor Web, aún puedes ajustar la estructura de la aplicación para mayor seguridad. Por favor consulta la sección [Entorno de Hosting Compartido](tutorial-shared-hosting.md) para más detalles. @@ -132,29 +194,29 @@ la sección [Entorno de Hosting Compartido](tutorial-shared-hosting.md) para má Utiliza la siguiente configuración del archivo `httpd.conf` de Apache dentro de la configuración del virtual host. Ten en cuenta que deberás reemplazar `path/to/basic/web` con la ruta real a `basic/web`. -``` -# Definir el document root de "basic/web" +```apache +# Definir el document root como "basic/web" DocumentRoot "path/to/basic/web" + # utiliza mod_rewrite para soporte de URLs amigables RewriteEngine on - - # Si el directorio o archivo existe, utiliza el request directamente + # Si el directorio o archivo existe, utiliza la petición directamente RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d - # Sino envía el request a index.php + # Sino, redirige la petición a index.php RewriteRule . index.php - # ...más configuraciones... + # ...otras configuraciones... ``` ### Configuración Recomendada de Nginx -Deberías haber instalado PHP como un [FPM SAPI](http://php.net/install.fpm) para utilizar [Nginx](http://wiki.nginx.org/). -Utiliza la siguiente configuración de Nginx, reemplazando `path/to/basic/web` con la ruta real a `basic/web` y `mysite.local` con el -hostname real del servidor. +Para utilizar [Nginx](http://wiki.nginx.org/), debes instalar PHP como un [FPM SAPI](http://php.net/install.fpm). +Utiliza la siguiente configuración de Nginx, reemplazando `path/to/basic/web` con la ruta real a +`basic/web` y `mysite.local` con el hostname real a servir. ``` server { @@ -168,12 +230,12 @@ server { root /path/to/basic/web; index index.php; - access_log /path/to/basic/log/access.log main; + access_log /path/to/basic/log/access.log; error_log /path/to/basic/log/error.log; location / { # Redireccionar a index.php todo lo que no sea un archivo real - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # descomentar para evitar el procesamiento de llamadas de Yii a archivos estáticos no existente @@ -183,9 +245,11 @@ server { #error_page 404 /404.html; location ~ \.php$ { - include fastcgi.conf; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; } location ~ /\.(ht|svn|git) { diff --git a/docs/guide-es/start-looking-ahead.md b/docs/guide-es/start-looking-ahead.md index 477e4952c1..39129b378d 100644 --- a/docs/guide-es/start-looking-ahead.md +++ b/docs/guide-es/start-looking-ahead.md @@ -1,32 +1,35 @@ Mirando Hacia Adelante ====================== -Hasta ahora, has creado una aplicación completa en Yii, y has aprendido cómo implementar algunas de las características más típicas y necesarias, como la de obtener datos de los usuarios a través de un formulario HTML, y traer datos de la base de datos -para mostrarlos en forma paginada. También has aprendido cómo utilizar la herramienta [Gii](tool-gii.md) para generar -código automáticamente, lo que transforma el hecho de programar en una tarea tan simple como la de completar algunos formularios. -En esta sección, resumiremos los recursos acerca de Yii que ayudan a ser más productivos al utilizar la librería. +Si has leído el capítulo "Comenzando con Yii" completo, has creado una aplicación completa en Yii. En el proceso, has aprendido cómo implementar algunas +características comúnmente necesitadas, tales como obtener datos del usuario a través de formularios HTML, traer datos desde la base de datos, +y mostrar datos utilizando paginación. También has aprendido a utilizar [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) para generar +código automáticamente. Utilizar Gii para la generación de código transforma la carga en el proceso de tu desarrollo Web en una tarea tan simple como solamente completar unos formularios. + +Esta sección resumirá los recursos disponibles de Yii que te ayudarán a ser más productivo al utilizar el framework. * Documentación - - La Guía Definitiva: - Como el nombre indica, la guía precisamente define cómo Yii debería trabajar y te da una guía general - acerca de cómo usar la librería. Este es el tutorial simple más importante de Yii que deberías leer - antes de empezar a escribir código. - - La Referencia de Clases: - Este especifica el uso de cada clase provista por Yii. Debería ser utilizado mayormente cuando estés escribiendo - código y quieras entender el funcionamiento de alguna clase, método o propiedad en particular. - - Artículos de la Wiki: - Estos artículos son escritos por usuarios de Yii basado en experiencias propias. En su mayoría son escritos - como recetas que muestran cómo resolver problemas particulares en Yii. Aunque la calidad de estos artículos - puede ser tan buena como la Guía Definitiva, son particularmente útiles al cubrir aspectos más amplios - y puede a menudo ofrecer soluciones listas para usar. - - Libros + - [La Guía Definitiva](http://www.yiiframework.com/doc-2.0/guide-README.html): + Como su nombre lo indica, la guía define precisamente cómo debería trabajar Yii y provee guías generales + acerca de su utilización. Es el tutorial más importante de Yii, y el que deberías leer + antes de escribir cualquier código en Yii. + - [La Referencia de Clases](http://www.yiiframework.com/doc-2.0/index.html): + Esta especifica el uso de cada clase provista por Yii. Debería ser utilizada principalmente cuando estás escribiendo + código y deseas entender el uso de una clase, método o propiedad en particular. El uso de la referencia de clases es mejor luego de un entendimiento contextual del framework. + - [Los Artículos de la Wiki](http://www.yiiframework.com/wiki/?tag=yii2): + Los artículos de la wiki son escritos por usuarios de Yii basados en sus propias experiencias. La mayoría de ellos están escritos + como recetas de cocina, y muestran cómo resolver problemas particulares utilizando Yii. Si bien la calidad de estos + puede no ser tan buena como la de la Guía Definitiva, son útiles ya que cubren un espectro muy amplio + de temas y puede proveer a menudo soluciones listas para usar. + - [Libros](http://www.yiiframework.com/doc/) * [Extensiones](http://www.yiiframework.com/extensions/): - Yii cuenta con una librería de cientos de extensiones que han sido provistas por la comunidad de usuarios que pueden ser fácilmente integradas - en tu aplicación y lograr que sea más simple y rápido desarrollarla. + Yii puede hacer alarde de una librería de miles de extensiones contribuidas por usuarios, que pueden fácilmente conectadas a tu aplicación, haciendo que el desarrollo de la misma sea todavía más fácil y rápido. * Comunidad - - [Foro](http://www.yiiframework.com/forum/) - - [GitHub](https://github.com/yiisoft/yii2) - - [Facebook](https://www.facebook.com/groups/yiitalk/) - - [Twitter](https://twitter.com/yiiframework) - - [LinkedIn](https://www.linkedin.com/groups/yii-framework-1483367) - + - Foro: + - Chat IRC: El canal #yii en la red freenode () + - Chat Gitter: + - GitHub: + - Facebook: + - Twitter: + - LinkedIn: + - Stackoverflow: diff --git a/docs/guide-es/start-workflow.md b/docs/guide-es/start-workflow.md index d2c952cad6..2695c3e889 100644 --- a/docs/guide-es/start-workflow.md +++ b/docs/guide-es/start-workflow.md @@ -6,10 +6,13 @@ la URL `http://hostname/basic/web/index.php` o `http://hostname/index.php`, depe Esta sección será una introducción a la funcionalidad incluida de la aplicación, cómo se organiza el código, y cómo la aplicación maneja los requests en general. -> Información: Por simplicidad, en el transcurso de este tutorial "Para Empezar", se asume que has definido `basic/web` +> Info: Por simplicidad, en el transcurso de este tutorial "Para Empezar", se asume que has definido `basic/web` como el document root de tu servidor Web, y configurado la URL de acceso a tu aplicación para que sea `http://hostname/index.php` o similar. Dependiendo de tus necesidades, por favor ajusta dichas URLs. + +Ten en cuenta que a diferencia del framework en sí, después de que el template de proyecto es instalado, este es todo tuyo. Eres libre de agregar o eliminar +código modificar todo según tu necesidad. Funcionalidad @@ -17,10 +20,9 @@ Funcionalidad La aplicación básica contiene 4 páginas: -* Página principal, mostrada cuando se accede a la URL `http://hostname/index.php`, +* página principal, mostrada cuando se accede a la URL `http://hostname/index.php`, * página "Acerca de (About)", -* la página "Contacto (Contact)", que muestra un formulario de contacto que permite a los usuarios - finales contactarse vía email, +* la página "Contacto (Contact)", que muestra un formulario de contacto que permite a los usuarios finales contactarse vía email, * y la página "Login", que muestra un formulario para loguearse que puede usarse para autenticar usuarios. Intenta loguearte con "admin/admin", y verás que el elemento "Login" del menú principal cambiará a "Logout". @@ -28,10 +30,13 @@ Estas páginas comparten un encabezado y un pie. El encabezado contiene una barr la navegación entre las diferentes páginas. También deberías ver una barra en la parte inferior de la ventana del navegador. -Esta es la útil [herramienta de depuración](tool-debugger.md) provista por Yii para registrar y mostrar mucha información de depuración, -tal como los mensajes de log, response status, las consultas ejecutadas a la base de datos, y más. +Esta es la útil [herramienta de depuración](tool-debugger.md) provista por Yii para registrar y mostrar mucha información de depuración, tal como los mensajes de log, response status, las consultas ejecutadas a la base de datos, y más. +Adicionalmente a la aplicación web, hay un script de consola llamado `yii`, localizado en el directorio base de la aplicación. +El script puede ser utilizado para ejecutar tareas de fondo y tareas de mantenimiento de la aplicación, las cuales son descritas +en la [Sección de Aplicación de Consola](tutorial-console.md). + Estructura de la aplicación --------------------------- @@ -71,7 +76,7 @@ Cada aplicación tiene un script de entrada `web/index.php` que es el único scr El script de entrada toma una petición (request) entrante y crea una instancia de una [aplicación](structure-applications.md) para manejarlo. La [aplicación](structure-applications.md) resuelve la petición (request) con la ayuda de sus [componentes](concept-components.md), y la envía al resto de los elementos MVC. Los [widgets](structure-widgets.md) son usados en las [vistas](structure-views.md) -para ayudar a construir elementos de interfáz complejos y dinámicos. +para ayudar a construir elementos de interfaz complejos y dinámicos. Ciclo de Vida de una Petición (Request) @@ -94,3 +99,4 @@ El siguiente diagrama muestra cómo una aplicación maneja una petición. 9. La acción renderiza una vista, pasándole los datos del modelo cargado. 10. El resultado de la renderización es pasado al componente [response](runtime-responses.md) de la aplicación. 11. El componente response envía el resultado de la renderización al navegador del usuario. + diff --git a/docs/guide-es/structure-application-components.md b/docs/guide-es/structure-application-components.md index c2645873b1..e4bdb928ad 100644 --- a/docs/guide-es/structure-application-components.md +++ b/docs/guide-es/structure-application-components.md @@ -42,7 +42,7 @@ Por ejemplo: ] ``` -> Información: A pesar de que puedes registrar tantos componentes como desees, deberías hacerlo con criterio. +> Info: A pesar de que puedes registrar tantos componentes como desees, deberías hacerlo con criterio. Los componente de la aplicación son como variables globales. Abusando demasiado de ellos puede resultar en un código más difícil de mantener y testear. En muchos casos, puedes simplemente crear un componente local y utilizarlo únicamente cuando sea necesario. diff --git a/docs/guide-es/structure-applications.md b/docs/guide-es/structure-applications.md index f1b472873c..f5f7d18ba4 100644 --- a/docs/guide-es/structure-applications.md +++ b/docs/guide-es/structure-applications.md @@ -6,7 +6,7 @@ hechas en Yii. Cada aplicación Yii contiene un objeto `Application` que es creado en el [script de entrada](structure-entry-scripts.md) y es globalmente accesible a través de la expresión `\Yii::$app`. -> Información: Dependiendo del contexto, cuando decimos "una aplicación", puede significar tanto un objeto Application +> Info: Dependiendo del contexto, cuando decimos "una aplicación", puede significar tanto un objeto Application o un sistema desarrollado en Yii. Hay dos tipos de aplicaciones: [[yii\web\Application|aplicaciones Web]] y @@ -150,7 +150,7 @@ if (YII_ENV_DEV) { } ``` -> Nota: Agregar demasiados componentes `bootstrap` degradará la performance de tu aplicación debido a que +> Note: Agregar demasiados componentes `bootstrap` degradará la performance de tu aplicación debido a que por cada request, se necesita correr el mismo grupo de componentes. Por lo tanto, utiliza componentes `bootstrap` con criterio. @@ -372,11 +372,11 @@ no especifica una. La ruta puede consistir el ID de un sub-módulo, el ID de un Por ejemplo, `help`, `post/create`, `admin/post/create`. Si el ID de la acción no se especifica, tomará el valor por defecto especificado en [[yii\base\Controller::defaultAction]]. -Para [yii\web\Application|aplicaciones Web], el valor por defecto de esta propiedad es `'site'`, lo que significa que el +Para [[yii\web\Application|aplicaciones Web]], el valor por defecto de esta propiedad es `'site'`, lo que significa que el controlador `SiteController` y su acción por defecto serán usados. Como resultado, si accedes a la aplicación sin especificar una ruta, mostrará el resultado de `app\controllers\SiteController::actionIndex()`. -Para [yii\console\Application|aplicaciones de consola], el valor por defecto es `'help'`, lo que significa que el comando +Para [[yii\console\Application|aplicaciones de consola]], el valor por defecto es `'help'`, lo que significa que el comando [[yii\console\controllers\HelpController::actionIndex()]] debería ser utilizado. Como resultado, si corres el comando `yii` sin proveer ningún argumento, mostrará la información de ayuda. @@ -387,10 +387,14 @@ Esta propiedad especifica la lista de [extensiones](structure-extensions.md) que por la aplicación. Por defecto, tomará el array devuelto por el archivo `@vendor/yiisoft/extensions.php`. El archivo `extensions.php` <<<<<<< HEAD +<<<<<<< HEAD es generado y mantenido automáticamente cuando utilizas [Composer](http://getcomposer.org) para instalar extensiones. ======= es generado y mantenido automáticamente cuando utilizas [Composer](https://getcomposer.org) para instalar extensiones. >>>>>>> yiichina/master +======= +es generado y mantenido automáticamente cuando utilizas [Composer](https://getcomposer.org) para instalar extensiones. +>>>>>>> master Por lo tanto, en la mayoría de los casos no necesitas configurarla. En el caso especial de que quieras mantener las extensiones a mano, puedes configurar la propiedad como se muestra a continuación: @@ -460,11 +464,15 @@ representado por el alias `@app/views`. Puedes configurarlo como un directorio o #### [[yii\base\Application::vendorPath|vendorPath]] +<<<<<<< HEAD <<<<<<< HEAD Esta propiedad especifica el directorio `vendor` que maneja [Composer](http://getcomposer.org). Contiene ======= Esta propiedad especifica el directorio `vendor` que maneja [Composer](https://getcomposer.org). Contiene >>>>>>> yiichina/master +======= +Esta propiedad especifica el directorio `vendor` que maneja [Composer](https://getcomposer.org). Contiene +>>>>>>> master todas las librerías de terceros utilizadas por tu aplicación, incluyendo el núcleo de Yii. Su valor por defecto está representado por el alias `@app/vendor`. diff --git a/docs/guide-es/structure-assets.md b/docs/guide-es/structure-assets.md index 8f3ea954b9..438cf2baa0 100644 --- a/docs/guide-es/structure-assets.md +++ b/docs/guide-es/structure-assets.md @@ -1,4 +1,4 @@ -Assets +Assets ====== Un asset en Yii es un archivo al que se puede hacer referencia en una página Web. Puede ser un archivo CSS, un archivo @@ -64,7 +64,7 @@ A continuación se explicarán más detalladamente las propiedades del [[yii\web propiedad en consecuencia. Se debe establecer esta propiedad si los archivos asset ya se encuentran en un directorio Web público y no necesitan ser publicados. Se pueden usar [alias de ruta](concept-aliases.md). * [[yii\web\AssetBundle::baseUrl|baseUrl]]: especifica la URL correspondiente al directorio - [[yii\web\AssetBundle::basePath|basePath]]. Como en [yii\web\AssetBundle::basePath|basePath]], si se especifica la + [[yii\web\AssetBundle::basePath|basePath]]. Como en [[yii\web\AssetBundle::basePath|basePath]], si se especifica la propiedad [[yii\web\AssetBundle::sourcePath|sourcePath]], el [gestor de assets](#asset-manager) publicara los assets y sobrescribirá esta propiedad en consecuencia. Se pueden usar [alias de ruta](concept-aliases.md). * [[yii\web\AssetBundle::js|js]]: un array lista los archivos JavaScript que contiene este bundle. Tenga en cuenta que @@ -114,7 +114,7 @@ Para las [extensiones](structure-extensions.md), por el hecho de que sus assets fuente, en directorios que no son accesibles para la Web, se tiene que especificar la propiedad [[yii\web\AssetBundle::sourcePath|sourcePath]] cuando se definan clases asset bundle para ellas. -> Nota: No se debe usar `@webroot/assets` como [yii\web\AssetBundle::sourcePath|source path]]. Este directorio se usa +> Note: No se debe usar `@webroot/assets` como [[yii\web\AssetBundle::sourcePath|source path]]. Este directorio se usa por defecto por el [[yii\web\AssetManager|asset manager]] para guardar los archivos asset publicados temporalmente y pueden ser eliminados. @@ -141,7 +141,7 @@ incluidos en una página. Los valores de estas propiedades serán enviadas a los [[yii\web\View::registerCssFile()]] y [[yii\web\View::registerJsFile()]], respectivamente cuando las [vistas](structure-views.md) los llamen para incluir los archivos CSS y JavaScript. -> Nota: Las opciones que se especifican en una clase bundle se aplican a *todos* los archivos CSS/JavaScript de un +> Note: Las opciones que se especifican en una clase bundle se aplican a *todos* los archivos CSS/JavaScript de un bundle. Si se quiere usar diferentes opciones para diferentes archivos, se deben crear assets bundles separados y usar un conjunto de opciones para cada bundle. @@ -219,7 +219,7 @@ Si tu aplicación o extensión usa estos paquetes, se recomienda seguir los sigu `@npm\PackageName`. Esto se debe a que Composer instalará el paquete Bower o NPM en el correspondiente directorio de este alias. -> Nota: Algunos paquetes pueden distribuir sus archivos en subdirectorios. Si es el caso, se debe especificar el +> Note: Algunos paquetes pueden distribuir sus archivos en subdirectorios. Si es el caso, se debe especificar el subdirectorio como valor del [[yii\web\AssetBundle::sourcePath|sourcePath]]. Por ejemplo, [[yii\web\JqueryAsset]] usa `@bower/jquery/dist` en vez de `@bower/jquery`. @@ -234,7 +234,7 @@ use app\assets\AppAsset; AppAsset::register($this); // $this representa el objeto vista ``` -> Información: El método [[yii\web\AssetBundle::register()]] devuelve un objeto asset bundle que contiene la +> Info: El método [[yii\web\AssetBundle::register()]] devuelve un objeto asset bundle que contiene la información acerca de los assets publicados, tales como [[yii\web\AssetBundle::basePath|basePath]] o [[yii\web\AssetBundle::baseUrl|baseUrl]]. @@ -280,7 +280,7 @@ Del mismo modo, se pueden configurar múltiples asset bundles a través de [[yii del array deben ser los nombres de clase (sin la primera barra invertida) de los asset bundles, y los valores del array deben ser las correspondientes [configuraciones de arrays](concept-configurations.md). -> Consejo: Se puede elegir condicionalmente que assets se van a usar en un asset bundle. El siguiente ejemplo +> Tip: Se puede elegir condicionalmente que assets se van a usar en un asset bundle. El siguiente ejemplo muestra como usar `jquery.js` en el entorno de desarrollo y `jquery.min.js` en los otros casos: > > ```php @@ -340,7 +340,7 @@ Si se detecta que alguna de estas claves es la última parte de un archivo asset registrado con la vista. Por ejemplo, un archivo asset `mi/ruta/a/jquery.js` concuerda con la clave `jquery.js`. -> Nota: Sólo los assets especificados usando rutas relativas están sujetos al mapeo de assets. Y las rutas de los +> Note: Sólo los assets especificados usando rutas relativas están sujetos al mapeo de assets. Y las rutas de los assets destino deben ser tanto URLs absolutas o rutas relativas a [[yii\web\AssetManager::basePath]]. ### Publicación de Asset @@ -459,7 +459,7 @@ los valores del array las extensiones de archivo resultantes y los comandos para Los tokens `{from}` y `{to}` en los comandos se reemplazarán por las rutas de origen de los archivos asset y las rutas de destino de los archivos asset. -> Información: Hay otras maneras de trabajar con las assets de sintaxis extendidas, además de la descrita +> Info: Hay otras maneras de trabajar con las assets de sintaxis extendidas, además de la descrita anteriormente. Por ejemplo, se pueden usar herramientas generadoras tales como [grunt](http://gruntjs.com/) para monitorear y convertir automáticamente los assets de sintaxis extendidas. En este caso, se deben listar los archivos CSS/JavaScript resultantes en lugar de los archivos de originales. @@ -504,7 +504,7 @@ efectiva. Por otra parte, por el hecho de que un único grupo contenga todos los más grandes y por tanto incrementan el tiempo de transmisión del archivo inicial. En este ejemplo, se usará la primera opción, ej., usar un único grupo que contenga todos los bundles. -> Información: Dividiendo los asset bundles en grupos no es una tarea trivial. Normalmente requiere un análisis de los +> Info: Dividiendo los asset bundles en grupos no es una tarea trivial. Normalmente requiere un análisis de los datos del tráfico real de varios assets en diferentes páginas. Al principio, se puede empezar con un único grupo para simplificar. @@ -618,7 +618,7 @@ Se debe modificar este archivo para especificar que bundles plantea combinar en `targets` se debe especificar como se deben dividir entre los grupos. Se puede especificar uno o más grupos, como se ha comentado. -> Nota: Debido a que los alias `@webroot` y `@web` no están disponibles en la aplicación de consola, se deben definir +> Note: Debido a que los alias `@webroot` y `@web` no están disponibles en la aplicación de consola, se deben definir explícitamente en la configuración. Los archivos JavaScript se combinan, comprimen y guardan en `js/all-{hash}.js` donde {hash} se reemplaza con el hash @@ -640,6 +640,6 @@ yii asset assets.php config/assets-prod.php El archivo de configuración generado se puede incluir en la configuración de la aplicación, como se ha descrito en la anterior subsección. -> Información: Usar el comando `asset` no es la única opción de automatizar el proceso de combinación y compresión. +> Info: Usar el comando `asset` no es la única opción de automatizar el proceso de combinación y compresión. Se puede usar la excelente herramienta de ejecución de tareas [grunt](http://gruntjs.com/) para lograr el mismo - objetivo. \ No newline at end of file + objetivo. diff --git a/docs/guide-es/structure-controllers.md b/docs/guide-es/structure-controllers.md index b9d1aed746..c9fb8b0b67 100644 --- a/docs/guide-es/structure-controllers.md +++ b/docs/guide-es/structure-controllers.md @@ -112,7 +112,7 @@ Por esta razón, los IDs de controladores son a menudo sustantivos de los tipos Por ejemplo, podrías utilizar `article` como el ID de un controlador que maneja datos de artículos. Por defecto, los IDs de controladores deberían contener sólo estos caracteres: letras del Inglés en minúscula, dígitos, -guiones bajos y medios, y barras. Por ejemplo, `article`, `post-comment`, `admin/post2-comment` son todos +guiones bajos y medios, y barras. Por ejemplo, `article`, `post-comment`, `admin/post-comment` son todos IDs de controladores válidos, mientras que `article?`, `PostComment`, `admin\post` no lo son. Los guiones en un ID de controlador son utilizados para separar palabras, mientras que las barras diagonales lo son para @@ -134,14 +134,14 @@ toma el valor por defecto: `app\controllers`: * `article` deriva en `app\controllers\ArticleController`; * `post-comment` deriva en `app\controllers\PostCommentController`; -* `admin/post2-comment` deriva en `app\controllers\admin\Post2CommentController`. +* `admin/post-comment` deriva en `app\controllers\admin\PostCommentController`. Las clases de controladores deben ser [autocargables](concept-autoloading.md). Por esta razón, en los ejemplos anteriores, la clase del controlador `article` debe ser guardada en un archivo cuyo alias [alias](concept-aliases.md) -es `@app/controllers/ArticleController.php`; mientras que el controlador `admin/post2-comment` debería estar -en `@app/controllers/admin/Post2CommentController.php`. +es `@app/controllers/ArticleController.php`; mientras que el controlador `admin/post-comment` debería estar +en `@app/controllers/admin/PostCommentController.php`. -> Información: En el último ejemplo, `admin/post2-comment`, demuestra cómo puedes poner un controlador bajo un sub-directorio +> Info: En el último ejemplo, `admin/post-comment`, demuestra cómo puedes poner un controlador bajo un sub-directorio del [[yii\base\Application::controllerNamespace|controller namespace]]. Esto es útil cuando quieres organizar tus controladores en varias categorías pero sin utilizar [módulos](structure-modules.md). @@ -243,7 +243,7 @@ Los nombre de métodos de acciones derivan de los IDs de acuerdo al siguiente cr Por ejemplo, `index` se vuelve `actionIndex`, y `hello-world` se vuelve `actionHelloWorld`. -> Nota: Los nombres de los métodos de acción son *case-sensitive* (distinguen entre minúsculas y mayúsculas). Si tienes un +> Note: Los nombres de los métodos de acción son *case-sensitive* (distinguen entre minúsculas y mayúsculas). Si tienes un método llamado `ActionIndex`, no será considerado como un método de acción, y como resultado, solicitar la acción `index` resultará en una excepción. También ten en cuenta que los métodos de acción deben ser `public`. Un método `private` o `protected` NO define un método de acción. @@ -312,7 +312,7 @@ los usuarios. * Para [[yii\web\Application|aplicaciones Web]], el valor de retorno pueden ser también datos arbitrarios que serán asignados a [[yii\web\Response::data]] y más adelante convertidos a una cadena representando el cuerpo de la respuesta. -* Para [[yii\console\Application|aplicaciones de consola], el valor de retorno puede ser también un entero representando +* Para [[yii\console\Application|aplicaciones de consola]], el valor de retorno puede ser también un entero representando el [[yii\console\Response::exitStatus|status de salida]] de la ejecución del comando. En los ejemplos mostrados arriba, los resultados de las acciones son todas cadenas que serán tratadas como el cuerpo de la respuesta diff --git a/docs/guide-es/structure-entry-scripts.md b/docs/guide-es/structure-entry-scripts.md index e1cabfebe9..76483c5050 100644 --- a/docs/guide-es/structure-entry-scripts.md +++ b/docs/guide-es/structure-entry-scripts.md @@ -1,4 +1,4 @@ -Scripts de Entrada +Scripts de Entrada ================== Los scripts de entrada son el primer eslabón en el proceso de arranque de la aplicación. Una aplicación (ya sea una @@ -18,10 +18,14 @@ El script de entrada principalmente hace los siguientes trabajos: * Definir las constantes globales; <<<<<<< HEAD +<<<<<<< HEAD * Registrar el [cargador automático de Composer](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * Registrar el [cargador automático de Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* Registrar el [cargador automático de Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * Incluir el archivo de clase [[Yii]]; * Cargar la configuración de la aplicación; * Crear y configurar una instancia de [aplicación](structure-applications.md); @@ -67,10 +71,6 @@ De la misma manera, el siguiente código es el script de entrada para la [aplica defined('YII_DEBUG') or define('YII_DEBUG', true); -// el fcgi no tiene STDIN y STDOUT definidos por defecto -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - // registrar el cargador automático de Composer require(__DIR__ . '/vendor/autoload.php'); diff --git a/docs/guide-es/structure-extensions.md b/docs/guide-es/structure-extensions.md index 8ce45dbe09..d573c4b9d5 100644 --- a/docs/guide-es/structure-extensions.md +++ b/docs/guide-es/structure-extensions.md @@ -1,4 +1,4 @@ -Extensiones +Extensiones =========== Las extensiones son paquetes de software redistribuibles diseñados especialmente para ser usados en aplicaciones Yii y @@ -7,7 +7,7 @@ añade una practica barra de herramientas de depuración (debug toolbar) al fina ayudar a comprender más fácilmente como se han generado las páginas. Se pueden usar extensiones para acelerar el proceso de desarrollo. También se puede empaquetar código propio para compartir nuestro trabajo con otra gente. -> Información: Usamos el termino "extensión" para referirnos a los paquetes específicos de software Yii. Para +> Info: Usamos el termino "extensión" para referirnos a los paquetes específicos de software Yii. Para propósitos generales los paquetes de software pueden usarse sin Yii, nos referiremos a ellos usando los términos "paquetes" (package) o "librerías" (library). @@ -52,7 +52,7 @@ Después de la instalación, debemos encontrar el directorio `yiisoft/yii2-imagi `BasePath/vendor`. También debemos encontrar el directorio `imagine/imagine` que contiene sus paquetes dependientes instalados. -> Información: La extensión `yiisoft/yii2-imagine` es una extensión del núcleo (core) desarrollada y mantenida por el +> Info: La extensión `yiisoft/yii2-imagine` es una extensión del núcleo (core) desarrollada y mantenida por el equipo de desarrollo de Yii. Todas las extensiones del núcleo se hospedan en [Packagist](https://packagist.org/) y son nombradas como `yiisoft/yii2-xyz`, donde `zyz` varia según la extensión. @@ -68,7 +68,7 @@ Image::thumbnail('@webroot/img/test-image.jpg', 120, 120) ->save(Yii::getAlias('@runtime/thumb-test-image.jpg'), ['quality' => 50]); ``` -> Información: Las clases de extensiones se cargan automáticamente gracias a +> Info: Las clases de extensiones se cargan automáticamente gracias a [autocarga de clases de Yii](concept-autoloading.md). ### Instalación Manual de Extensiones @@ -387,10 +387,10 @@ mencionados a continuación para facilitar a otra gente el uso de nuestra extens fácilmente. Más información acerca de documentación de código en [archivo de Objetos de clase](https://github.com/yiisoft/yii2/blob/master/framework/base/Object.php) -> Información: Los comentarios de código pueden ser escritos en formato Markdown. La extensión `yiisoft/yii2-apidoc` +> Info: Los comentarios de código pueden ser escritos en formato Markdown. La extensión `yiisoft/yii2-apidoc` proporciona una herramienta para generar buena documentación de API basándose en los comentarios del código. -> Información: Aunque no es un requerimiento, se recomienda que la extensión se adhiera a ciertos estilos de +> Info: Aunque no es un requerimiento, se recomienda que la extensión se adhiera a ciertos estilos de codificación. Se puede hacer referencia a [estilo de código del núcleo del framework](https://github.com/yiisoft/yii2/wiki/Core-framework-code-style) para obtener más detalles. @@ -437,4 +437,4 @@ se describe en la subsección [Uso de Extensiones](#using-extensions) - [yiisoft/yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer): proporciona características de envío de correos electrónicos basadas en [swiftmailer](http://swiftmailer.org/). - [yiisoft/yii2-twig](https://github.com/yiisoft/yii2-twig): proporciona un motor de plantillas basado en - [Twig](http://twig.sensiolabs.org/). \ No newline at end of file + [Twig](http://twig.sensiolabs.org/). diff --git a/docs/guide-es/structure-filters.md b/docs/guide-es/structure-filters.md index 1cc4bb6ff0..39b9158b80 100644 --- a/docs/guide-es/structure-filters.md +++ b/docs/guide-es/structure-filters.md @@ -1,4 +1,4 @@ -Filtros +Filtros ======= Los Filtros (filters) son objetos que se ejecutan antes y/o después de las @@ -45,7 +45,7 @@ Una vez hecho, los filtros serán aplicados a *todas* las acciones de controlado aplicación, a menos que las propiedades [[yii\base\ActionFilter::only|only]] y [[yii\base\ActionFilter::except|except]] sean configuradas como se ha descrito anteriormente. -> Nota: Cuando se declaran filtros en módulos o aplicaciones, deben usarse [rutas](structure-controllers.md#routes) en +> Note: Cuando se declaran filtros en módulos o aplicaciones, deben usarse [rutas](structure-controllers.md#routes) en lugar de IDs de acciones en las propiedades [[yii\base\ActionFilter::only|only]] y [[yii\base\ActionFilter::except|except]]. Esto es debido a que los IDs de acciones no pueden especificar acciones dentro del ámbito de un modulo o una aplicación por si mismos. @@ -226,7 +226,7 @@ use yii\web\Response; ]; ``` -> Información: En el caso que el tipo preferido de contenido y el idioma no puedan ser determinados por una petición, +> Info: En el caso que el tipo preferido de contenido y el idioma no puedan ser determinados por una petición, será utilizando el primer elemento de formato e idioma de la lista [[formats]] y [[lenguages]]. @@ -290,8 +290,8 @@ Por favor refiérase a [Caché de Páginas](caching-page.md) para obtener más d ### [[yii\filters\RateLimiter|RateLimiter]] RateLimiter implementa un algoritmo de para limitar la tasa de descarga basándose en -(leaky bucket algorithm)[http://en.wikipedia.org/wiki/Leaky_bucket]. Este se utiliza sobre todo en la implementación -de APIs RESTful. Por favor, refiérase a la sección (limite de tasa)[rest-rate-limiting.md] para obtener más detalles +[leaky bucket algorithm](http://en.wikipedia.org/wiki/Leaky_bucket). Este se utiliza sobre todo en la implementación +de APIs RESTful. Por favor, refiérase a la sección [limite de tasa](rest-rate-limiting.md) para obtener más detalles acerca de el uso de este filtro. ### [[yii\filters\VerbFilter|VerbFilter]] @@ -322,7 +322,7 @@ public function behaviors() ### [[yii\filters\Cors|Cors]] -(CORS)[https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS] es un mecanismo que permite a diferentes +[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) es un mecanismo que permite a diferentes recursos (por ejemplo: fuentes, JavaScript, etc) de una pagina Web ser solicitados por otro dominio diferente al dominio que esta haciendo la petición. En particular las llamadas AJAX de JavaScript pueden utilizar el mecanismo XMLHttpRequest. De otro modo esta petición de dominio cruzado seria prohibida por los navegadores Web, por la misma diff --git a/docs/guide-es/structure-models.md b/docs/guide-es/structure-models.md index 0cd0c4c7bb..b0cc3b08e5 100644 --- a/docs/guide-es/structure-models.md +++ b/docs/guide-es/structure-models.md @@ -1,4 +1,4 @@ -Modelos +Modelos ======= Los modelos forman parte de la arquitectura @@ -18,7 +18,7 @@ soporta muchas características útiles: La clase 'modelo' también es una base para modelos más avanzados, tales como [Active Records](db-active-record.md). -> Información: No es obligatorio basar las clases modelo en [[yii\base\Model]]. Sin embargo, debido a que hay muchos +> Info: No es obligatorio basar las clases modelo en [[yii\base\Model]]. Sin embargo, debido a que hay muchos componentes de Yii construidos para dar soporte a [[yii\base\Model]], por lo general, es la clase base preferible para un modelo. @@ -146,7 +146,7 @@ public function attributeLabels() Incluso se puede definir etiquetas de atributo condicionales. Por ejemplo, basándose en el [escenario](#scenarios) en que se esta usando el modelo, se pueden devolver diferentes etiquetas para un mismo atributo. -> Información: Estrictamente hablando, los atributos son parte de las [vistas](structure-views.md). Pero declarar las +> Info: Estrictamente hablando, los atributos son parte de las [vistas](structure-views.md). Pero declarar las etiquetas en los modelos, a menudo, es muy conveniente y puede generar a un código muy limpio y reutilizable. ## Escenarios @@ -190,7 +190,7 @@ class User extends ActiveRecord } ``` -> Información: En el anterior y en los siguientes ejemplos, las clases modelo extienden a [[yii\db\ActiveRecord]] +> Info: En el anterior y en los siguientes ejemplos, las clases modelo extienden a [[yii\db\ActiveRecord]] porque el uso de múltiples escenarios normalmente sucede con clases de [Active Records](db-active-record.md). El método 'scenarios()' devuelve un array cuyas claves son el nombre de escenario y los valores correspondientes a los @@ -333,7 +333,7 @@ public function scenarios() } ``` -> Información: La razón de que la asignación masiva sólo se aplique a los atributos seguros es debida a que se quiere +> Info: La razón de que la asignación masiva sólo se aplique a los atributos seguros es debida a que se quiere controlar qué atributos pueden ser modificados por los datos del usuario final. Por ejemplo, si el modelo 'User' tiene un atributo 'permission' que determina los permisos asignados al usuario, se quiere que estos atributos sólo sean modificados por administradores desde la interfaz backend. @@ -459,7 +459,7 @@ public function fields() } ``` -> Atención: debido a que por defecto todos los atributos de un modelo serán incluidos en el array exportado, se debe +> Warning: debido a que por defecto todos los atributos de un modelo serán incluidos en el array exportado, se debe examinar los datos para asegurar que no contienen información sensible. Si existe dicha información, se debe sobrescribir 'fields()' para filtrarla. En el anterior ejemplo, se filtra 'aut_key', 'password_hash' y 'password_reset_token'. @@ -497,4 +497,4 @@ Por ejemplo, en la [Plantilla de Aplicación Avanzada](tutorial-advanced-app.md) 'frontend\models\Post' que extienda a 'common\models\Post'. Y de forma similar en la aplicación back end, definiendo 'backend\models\Post'. Con esta estrategia, nos aseguramos que el código de 'frontend\models\Post' es específico para la aplicación front end, y si se efectúa algún cambio en el, no nos tenemos que preocupar de si el cambio afectará a -la aplicación back end. \ No newline at end of file +la aplicación back end. diff --git a/docs/guide-es/structure-modules.md b/docs/guide-es/structure-modules.md index 9d191d6676..3907f0998a 100644 --- a/docs/guide-es/structure-modules.md +++ b/docs/guide-es/structure-modules.md @@ -1,4 +1,4 @@ -Módulos +Módulos ======= Los módulos son unidades de software independientes que consisten en [modelos](structure-models.md), [vistas](structure-views.md), [controladores](structure-controllers.md), y otros componentes de apoyo. Los usuarios @@ -160,7 +160,7 @@ Dónde ‘MyModuleClass’ hace referencia al nombre de la clase módulo en la q método devolverá nulo. Hay que tener en cuenta que si se crea una nueva instancia del módulo, esta será diferente a la creada por Yii en respuesta a la solicitud. -> Información: Cuando se desarrolla un módulo, no se debe dar por sentado que el módulo usará un ID fijo. Esto se debe +> Info: Cuando se desarrolla un módulo, no se debe dar por sentado que el módulo usará un ID fijo. Esto se debe a que un módulo puede asociarse a un ID arbitrario cuando se usa en una aplicación o dentro de otro módulo. Para obtener el ID del módulo, primero se debe usar el código del anterior ejemplo para obtener la instancia y luego el ID mediante ‘$modeule->id’. @@ -234,7 +234,7 @@ En un controlador dentro de un módulo anidado, la ruta debe incluir el ID de to ejemplo, la ruta ‘forum/admin/dashboard/index’ representa la acción ‘index’ del controlador ‘dashboard’ en el módulo ‘admin’ que es el módulo hijo del módulo ‘forum’. -> Información: El método [[yii\base\Module::getModule()|getModule()]] sólo devuelve el módulo hijo que pertenece +> Info: El método [[yii\base\Module::getModule()|getModule()]] sólo devuelve el módulo hijo que pertenece directamente a su padre. La propiedad [[yii\base\Application::loadedModules]] contiene una lista de los módulos cargados, incluyendo los hijos directos y los anidados, indexados por sus nombres de clase. @@ -246,4 +246,4 @@ desarrollar como un módulo que puede ser desarrollado y mantenido por un progra Los módulos también son una buena manera de reutilizar código a nivel de grupo de funcionalidades. Algunas funcionalidades de uso común, tales como la gestión de usuarios o la gestión de comentarios, pueden ser desarrollados -como módulos para que puedan ser fácilmente reutilizados en futuros proyectos. \ No newline at end of file +como módulos para que puedan ser fácilmente reutilizados en futuros proyectos. diff --git a/docs/guide-es/structure-views.md b/docs/guide-es/structure-views.md index 6d2ee00598..b32a3f3a82 100644 --- a/docs/guide-es/structure-views.md +++ b/docs/guide-es/structure-views.md @@ -1,14 +1,19 @@ -Vistas +Vistas ====== Las Vistas (views) son una parte de la arquitectura [MVC](http://es.wikipedia.org/wiki/Modelo%E2%80%93vista%E2%80%93controlador). -Estas son el código responsable de presentar los datos al usuario final. En una aplicación Web, las vistas son usualmente creadas en términos de *templates* que son archivos PHP que contienen principalmente HTML y PHP. -Son manejadas por el componente de la aplicación [[yii\web\View|view]], el cual provee métodos comúnmente utilizados para facilitar la composición y el renderizado de las mismas. Por simplicidad, a menudo las llamamos *templates* o *archivos de templates*. +Estas son el código responsable de presentar los datos al usuario final. En una aplicación Web, las vistas son usualmente creadas +en términos de *templates* que son archivos PHP que contienen principalmente HTML y PHP. +Estas son manejadas por el [componente de la aplicación](structure-application-components.md) [[yii\web\View|view]], el cual provee los métodos comúnmente utilizados +para facilitar la composición y renderizado. Por simplicidad, a menudo nos referimos a los templates de vistas o archivos de templates +como vistas. -## Creando Vistas +## Crear Vistas -Como fue mencionado, una vista es simplemente un archivo PHP que mezcla código PHP y HTML. La siguiente es una vista que muestra un formulario de login. Como puedes ver, el código PHP utilizado es para generar contenido dinámico, como el título de la página y el formulario mismo, mientras que el código HTML organiza estos elementos en una página HTML mostrable. +Como fue mencionado, una vista es simplemente un archivo PHP que mezcla código PHP y HTML. La siguiente es una vista +que muestra un formulario de login. Como puedes ver, el código PHP utilizado es para generar contenido dinámico, como el +título de la página y el formulario mismo, mientras que el código HTML organiza estos elementos en una página HTML mostrable. ```php title = 'Login'; ``` -Dentro de una vista, puedes acceder a la variable `$this` referida al [[yii\web\View|componente view]] que maneja y renderiza la vista actual. +Dentro de una vista, puedes acceder a la variable `$this` referida al [[yii\web\View|componente view]] +que maneja y renderiza la vista actual. -Además de `$this`, puede haber otras variables predefinidas en una vista, como `$form` y `$model` en el ejemplo anterior. Estas variables representan los datos que son *inyectados* a la vista desde el [controlador](structure-controllers.md) o algún otro objeto que dispara la [renderización de la vista](#rendering-views). +Además de `$this`, puede haber otras variables predefinidas en una vista, como `$form` y `$model` en el +ejemplo anterior. Estas variables representan los datos que son *inyectados* a la vista desde el [controlador](structure-controllers.md) +o algún otro objeto que dispara la [renderización de la vista](#rendering-views). -> Tip: La lista de variables predefinidas están listadas en un bloque de comentario al principio de la vista así pueden ser reconocidas por las IDEs. Esto es también una buena manera de documentar tus propias vistas. +> Tip: La lista de variables predefinidas están listadas en un bloque de comentario al principio de la vista así + pueden ser reconocidas por las IDEs. Esto es también una buena manera de documentar tus propias vistas. ### Seguridad -Al crear vistas que generan páginas HTML, es importante que codifiques (encode) y/o filtres los datos provenientes de los usuarios antes de mostrarlos. De otro modo, tu aplicación puede estar expuesta a ataques tipo [cross-site scripting](http://es.wikipedia.org/wiki/Cross-site_scripting). +Al crear vistas que generan páginas HTML, es importante que codifiques (encode) y/o filtres los datos +provenientes de los usuarios antes de mostrarlos. De otro modo, tu aplicación puede estar expuesta +a ataques tipo [cross-site scripting](http://es.wikipedia.org/wiki/Cross-site_scripting). -Para mostrar un texto plano, codifícalos previamente utilizando [[yii\helpers\Html::encode()]]. Por ejemplo, el siguiente código aplica una codificación del nombre de usuario antes de mostrarlo: +Para mostrar un texto plano, codifícalos previamente utilizando [[yii\helpers\Html::encode()]]. Por ejemplo, el siguiente código aplica +una codificación del nombre de usuario antes de mostrarlo: ```php ``` -Para mostrar contenido HTML, utiliza [[yii\helpers\HtmlPurifier]] para filtrarlo antes. Por ejemplo, el siguiente código filtra el contenido del post antes de mostrarlo en pantalla: +Para mostrar contenido HTML, utiliza [[yii\helpers\HtmlPurifier]] para filtrarlo antes. Por ejemplo, el siguiente código +filtra el contenido del post antes de mostrarlo en pantalla: ```php ``` -> Tip: Aunque HTMLPurifier hace un excelente trabajo al hacer la salida más segura, no es rápido. Deberías considerar utilizar [caching](caching-overview.md) al resultado de aplicar el filtro si tu aplicación requiere un gran desempeño (performance). +> Tip: Aunque HTMLPurifier hace un excelente trabajo al hacer la salida más segura, no es rápido. Deberías considerar +el aplicar un [caching](caching-overview.md) al resultado de aplicar el filtro si tu aplicación requiere un gran desempeño (performance). -### Organizando Vistas +### Organizar las Vistas Así como en [controladores](structure-controllers.md) y [modelos](structure-models.md), existen convenciones para organizar las vistas. -* Para vistas renderizadas por controladores, deberían colocarse en un directorio tipo `@app/views/ControllerID` por defecto, donde `ControllerID` se refiere al [ID del controlador](structure-controllers.md#routes). Por ejemplo, si la clase del controlador es `PostController`, el directorio sería `@app/views/post`; Si fuera `PostCommentController`, el directorio sería `@app/views/post-comment`. En caso de que el controlador pertenezca a un módulo, el directorio sería `views/ControllerID` bajo el [[yii\base\Module::basePath|directorio del módulo]]. -* Para vistas renderizadas por un [widget](structure-widgets.md), deberían ser puestas en un directorio tipo `WidgetPath/views` por defecto, donde `WidgetPath` se refiere al directorio que contiene a la clase del widget. +* Para vistas renderizadas por controladores, deberían colocarse en un directorio tipo `@app/views/ControllerID` por defecto, + donde `ControllerID` se refiere al [ID del controlador](structure-controllers.md#routes). Por ejemplo, + si la clase del controlador es `PostController`, el directorio sería `@app/views/post`; Si fuera `PostCommentController`, + el directorio sería `@app/views/post-comment`. En caso de que el controlador pertenezca a un módulo, + el directorio sería `views/ControllerID` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +* Para vistas renderizadas por un [widget](structure-widgets.md), deberían ser puestas en un directorio + tipo `WidgetPath/views` por defecto, donde `WidgetPath` se refiere al directorio que contiene a la clase del widget. * Para vistas renderizadas por otros objetos, se recomienda seguir una convención similar a la utilizada con los widgets. -Puedes personalizar estos directorios por defecto sobrescribiendo el método [[yii\base\ViewContextInterface::getViewPath()]] en el controlador o widget necesario. +Puedes personalizar estos directorios por defecto sobrescribiendo el método [[yii\base\ViewContextInterface::getViewPath()]] +en el controlador o widget necesario. ## Renderizando Vistas -Puedes renderizar vistas desde [controllers](structure-controllers.md), [widgets](structure-widgets.md), o cualquier otro lugar llamando a los métodos de renderización de vistas. Estos métodos comparten una firma similar, como se muestra a continuación: +Puedes renderizar vistas desde [controllers](structure-controllers.md), [widgets](structure-widgets.md), o cualquier otro lugar +llamando a los métodos de renderización de vistas. Estos métodos comparten una firma similar, como se muestra a continuación: ``` /** @@ -99,10 +120,15 @@ methodName($view, $params = []) Dentro de los [controladores](structure-controllers.md), puedes llamar al siguiente método del controlador para renderizar una vista: -* [[yii\base\Controller::render()|render()]]: renderiza la [vista nombrada](#named-views) y aplica un [layout](#layouts) al resultado de la renderización. +* [[yii\base\Controller::render()|render()]]: renderiza la [vista nombrada](#named-views) y aplica un [layout](#layouts) + al resultado de la renderización. * [[yii\base\Controller::renderPartial()|renderPartial()]]: renderiza la [vista nombrada](#named-views) sin ningún layout aplicado. -* [[yii\web\Controller::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) sin layout, e inyecta todos los scripts y archivos JS/CSS registrados. Esto sucede usualmente en respuestas a llamadas a AJAX `requests`. -* [[yii\base\Controller::renderFile()|renderFile()]]: renderiza la vista especificada en términos de la ruta al archivo o [alias](concept-aliases.md). +* [[yii\web\Controller::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) sin layout, + e inyecta todos los scripts y archivos JS/CSS registrados. Esto sucede usualmente en respuestas a peticiones AJAX. +* [[yii\base\Controller::renderFile()|renderFile()]]: renderiza la vista especificada en términos de la ruta al archivo o + [alias](concept-aliases.md). +* [[yii\base\Controller::renderContent()|renderContent()]]: renderiza un string fijo, inscrustándolo en + el [layout](#layouts) actualmente aplicable. Este método está disponible desde la versión 2.0.1. Por ejemplo: @@ -137,7 +163,8 @@ class PostController extends Controller Dentro de [widgets](structure-widgets.md), puedes llamar a cualquier de los siguientes métodos de widget para renderizar una vista. * [[yii\base\Widget::render()|render()]]: renderiza la [vista nombrada](#named-views). -* [[yii\base\Widget::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo o [alias](concept-aliases.md). +* [[yii\base\Widget::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo + o [alias](concept-aliases.md). Por ejemplo: @@ -162,22 +189,25 @@ class ListWidget extends Widget ``` -### Renderizando en Vistas +### Renderizar en Vistas Puedes renderizar una vista dentro de otra vista llamando a algunos de los siguientes métodos provistos por el [[yii\base\View|componente view]]: * [[yii\base\View::render()|render()]]: renderiza la [vista nombrada](#named-views). -* [[yii\web\View::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) e inyecta todos los archivos y scripts JS/CSS. Esto sucede usualmente en respuestas a llamadas a AJAX `requests`. -* [[yii\base\View::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo o [alias](concept-aliases.md). +* [[yii\web\View::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) e inyecta + todos los archivos y scripts JS/CSS. Esto sucede usualmente en respuestas a las peticiones AJAX. +* [[yii\base\View::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo + o [alias](concept-aliases.md). -Por ejemplo, el siguiente código en una vista renderiza el template `_overview.php` encontrado en el mismo directorio de la vista renderizada actualmente. Recuerda que la variable `$this` en una vista se refiere al componente [[yii\base\View|view]]: +Por ejemplo, el siguiente código en una vista renderiza el template `_overview.php` encontrado en el mismo directorio +de la vista renderizada actualmente. Recuerda que la variable `$this` en una vista se refiere al componente [[yii\base\View|view]]: ```php render('_overview') ?> ``` -### Renderizando en Otros Lugares +### Renderizar en Otros Lugares En cualquier lugar, puedes tener acceso al componente [[yii\base\View|view]] utilizando la expresión `Yii::$app->view` y entonces llamar a los métodos previamente mencionados para renderizar una vista. Por ejemplo: @@ -190,25 +220,43 @@ echo \Yii::$app->view->renderFile('@app/views/site/license.php'); ### Vistas Nombradas -Cuando renderizas una vista, puedes especificar el template utilizando tanto el nombre de la vista o la ruta/alias al archivo. En la mayoría de los casos, utilizarías la primera porque es más concisa y flexible. *Vistas nombradas* son vistas especificadas mediante un nombre en vez de una ruta al archivo o alias. +Cuando renderizas una vista, puedes especificar el template utilizando tanto el nombre de la vista o la ruta/alias al archivo. En la mayoría de los casos, +utilizarías la primera porque es más concisa y flexible. Las *vistas nombradas* son vistas especificadas mediante un nombre en vez de una ruta al archivo o alias. Un nombre de vista es resuelto a su correspondiente ruta de archivo siguiendo las siguientes reglas: -* Un nombre de vista puede omitir la extensión del archivo. En estos casos se utilizará `.php` como extensión del archivo. Por ejemplo, el nombre de vista `about` corresponde al archivo `about.php`. +* Un nombre de vista puede omitir la extensión del archivo. En estos casos se utilizará `.php` como extensión del archivo. Por ejemplo, + el nombre de vista `about` corresponde al archivo `about.php`. * Si el nombre de la vista comienza con doble barra (`//`), la ruta al archivo correspondiente será `@app/views/ViewName`. -Esto quiere decir que la vista es buscada bajo el [[yii\base\Application::viewPath|view path de la aplicación]]. -Por ejemplo, `//site/about` será resuelto como `@app/views/site/about.php`. -* Si el nombre de la vista comienza con una barra simple `/`, la ruta al archivo de la vista utilizará como prefijo el nombre de la vista con el [[yii\base\Module::viewPath|view path]] del [módulo](structure-modules.md) utilizado actualmente. Si no hubiera módulo activo se utilizará `@app/views/ViewName`. Por ejemplo, `/user/create` será resuelto a `@app/modules/user/views/user/create.php` si el módulo activo es `user`. Si no hubiera módulo activo, la ruta al archivo será `@app/views/user/create.php`. -* Si la vista es renderizada con un [[yii\base\View::context|context]] y dicho contexto implementa [[yii\base\ViewContextInterface]], la ruta al archivo se forma utilizando como prefijo el [[yii\base\ViewContextInterface::getViewPath()|view path]] del contexto de la vista. Esto principalmente aplica a vistas renderizadas en controladores y widgets. Por ejemplo, `site/about` será resuelto a `@app/views/site/about.php` si el contexto es el controlador `SiteController`. -* Si la vista es renderizada dentro de otra vista, el directorio que contiene la otra vista será prefijado al nuevo nombre de la vista para formar la ruta a la vista. Por ejemplo, `item` sera resuelto a `@app/views/post/item` si está siendo renderizado desde la vista `@app/views/post/index.php`. + Esto quiere decir que la vista es buscada bajo el [[yii\base\Application::viewPath|ruta de vistas de la aplicación]]. + Por ejemplo, `//site/about` será resuelto como `@app/views/site/about.php`. +* Si el nombre de la vista comienza con una barra simple `/`, la ruta al archivo de la vista utilizará como prefijo el nombre de la vista + con el [[yii\base\Module::viewPath|view path]] del [módulo](structure-modules.md) utilizado actualmente. + Si no hubiera módulo activo se utilizará `@app/views/ViewName`. Por ejemplo, `/user/create` será resuelto como + `@app/modules/user/views/user/create.php` si el módulo activo es `user`. Si no hubiera módulo activo, + la ruta al archivo será `@app/views/user/create.php`. +* Si la vista es renderizada con un [[yii\base\View::context|context]] y dicho contexto implementa [[yii\base\ViewContextInterface]], + la ruta al archivo se forma utilizando como prefijo la [[yii\base\ViewContextInterface::getViewPath()|ruta de vistas]] del contexto + de la vista. Esto principalmente aplica a vistas renderizadas en controladores y widgets. Por ejemplo, + `about` será resuelto como `@app/views/site/about.php` si el contexto es el controlador `SiteController`. +* Si la vista es renderizada dentro de otra vista, el directorio que contiene la otra vista será prefijado + al nuevo nombre de la vista para formar la ruta a la vista. Por ejemplo, `item` sera resuelto como `@app/views/post/item` + si está siendo renderizado desde la vista `@app/views/post/index.php`. -De acuerdo a las reglas mencionadas, al llamar a `$this->render('view')` en el controlador `app\controllers\PostController` se renderizará el template `@app/views/post/view.php`, mientras que llamando a `$this->render('_overview')` en la vista renderizará el template `@app/views/post/_overview.php`. +De acuerdo a las reglas mencionadas, al llamar a `$this->render('view')` en el controlador `app\controllers\PostController` +se renderizará el template `@app/views/post/view.php`, mientras que llamando a `$this->render('_overview')` en la vista +renderizará el template `@app/views/post/_overview.php`. -### Accediendo a Datos en la Vista + +### Acceder a Datos en la Vista Hay dos modos posibles de acceder a los datos en la vista: push (inyectar) y pull (traer). -Al pasar los datos como segundo parámetro en algún método de renderización, estás utilizando el modo push. Los datos deberían ser representados como un array de pares clave-valor. Cuando la vista está siendo renderizada, la función PHP `extract()` será llamada sobre este array así se extraen las variables que contiene a la vista actual. Por ejemplo, el siguiente código de renderización en un controlador inyectará dos variables a la vista `report`: `$foo = 1` and `$bar = 2`. +Al pasar los datos como segundo parámetro en algún método de renderización, estás utilizando el modo push. +Los datos deberían ser representados como un array de pares clave-valor. Cuando la vista está siendo renderizada, la función PHP `extract()` +será llamada sobre este array así se extraen las variables que contiene a la vista actual. +Por ejemplo, el siguiente código de renderización en un controlador inyectará dos variables a la vista `report`: +`$foo = 1` y `$bar = 2`. ```php echo $this->render('report', [ @@ -217,26 +265,34 @@ echo $this->render('report', [ ]); ``` -El modo pull obtiene los datos del [[yii\base\View|componente view]] u otros objetos accesibles en las vistas (ej. `Yii::$app`). Utilizando el código anterior como ejemplo, dentro de una vista puedes acceder al objeto del controlador a través de la expresión `$this->context`. Como resultado, te es posible acceder a cualquier propiedad o método del controlador en la vista `report`, tal como el ID del controlador como se muestra a continuación: +El modo pull obtiene los datos del [[yii\base\View|componente view]] u otros objetos accesibles +en las vistas (ej. `Yii::$app`). Utilizando el código anterior como ejemplo, dentro de una vista puedes acceder al objeto del controlador +a través de la expresión `$this->context`. Como resultado, te es posible acceder a cualquier propiedad o método +del controlador en la vista `report`, tal como el ID del controlador como se muestra a continuación: ```php El ID del controlador es: context->id ?> -?> ``` -Para acceder a datos en la vista, normalmente se prefiere el modo push, ya que hace a la vista menos dependiente de los objetos del contexto. La contra es que tienes que construir el array manualmente cada vez, lo que podría volverse tedioso y propenso al error si la misma vista es compartida y renderizada desde diferentes lugares. +Para acceder a datos en la vista, normalmente se prefiere el modo push, ya que hace a la vista menos dependiente +de los objetos del contexto. La contra es que tienes que construir el array manualmente cada vez, lo que podría +volverse tedioso y propenso al error si la misma vista es compartida y renderizada desde diferentes lugares. -### Compartiendo Datos Entre las Vistas -El [[yii\base\View|componente view]] provee la propiedad [[yii\base\View::params|params]] para que puedas compartir datos entre diferentes vistas. +### Compartir Datos Entre las Vistas -Por ejemplo, en una vista `about`, podrías tener el siguiente código que especifica el segmento actual del breadcrumbs (migas de pan). +El [[yii\base\View|componente view]] provee la propiedad [[yii\base\View::params|params]] para que puedas compartir datos +entre diferentes vistas. + +Por ejemplo, en una vista `about`, podrías tener el siguiente código que especifica el segmento actual +del breadcrumbs (migas de pan). ```php $this->params['breadcrumbs'][] = 'Acerca de Nosotros'; ``` -Entonces, en el archivo del [layout](#layouts), que es también una vista, puedes mostrar el breadcrumbs utilizando los datos pasados a través de [[yii\base\View::params|params]]: +Entonces, en el archivo del [layout](#layouts), que es también una vista, puedes mostrar el breadcrumbs utilizando los datos +pasados a través de [[yii\base\View::params|params]]: ```php -Los layouts son un tipo especial de vista que representan partes comunes de otras múltiples vistas. Por ejemplo, las páginas de la mayoría de las aplicaciones Web comparten el mismo encabezado y pie de página. Aunque puedes repetirlos en todas y cada una de las vistas, una mejor forma es hacerlo sólo en el layout e incrustar el resultado de la renderización de la vista en un lugar apropiado del mismo. +Los layouts son un tipo especial de vista que representan partes comunes de otras múltiples vistas. Por ejemplo, las páginas +de la mayoría de las aplicaciones Web comparten el mismo encabezado y pie de página. Aunque puedes repetirlos en todas y cada una de las vistas, +una mejor forma es hacerlo sólo en el layout e incrustar el resultado de la renderización de la vista +en un lugar apropiado del mismo. -### Creando Layouts +### Crear Layouts -Dado que los layouts son también vistas, pueden ser creados de manera similar a las vistas comunes. Por defecto, los layouts son guardados en el directorio `@app/views/layouts`. Para layouts utilizados dentro de un [módulo](structure-modules.md), deberían ser guardados en el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. Puedes personalizar el directorio de layouts por defecto configurando la propiedad [[yii\base\Module::layoutPath]] de la aplicación o módulos. +Dado que los layouts son también vistas, pueden ser creados de manera similar a las vistas comunes. Por defecto, los layouts +son guardados en el directorio `@app/views/layouts`. Para layouts utilizados dentro de un [módulo](structure-modules.md), deberían ser guardados +en el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +Puedes personalizar el directorio de layouts por defecto configurando la propiedad [[yii\base\Module::layoutPath]] +de la aplicación o módulos. -El siguiente ejemplo muestra cómo debe verse un layout. Ten en cuenta que por motivos ilustrativos, hemos simplificado bastante el código del layout. En la práctica, probablemente le agregues más contenido, como tags en el `head`, un menú principal, etc. +El siguiente ejemplo muestra cómo debe verse un layout. Ten en cuenta que por motivos ilustrativos, hemos simplificado +bastante el código del layout. En la práctica, probablemente le agregues más contenido, como tags en el `head`, un menú principal, etc. ```php endPage() ?> ``` -Como puedes ver, el layout genera los tags HTML comunes a todas las páginas. Dentro de la sección ``,el layout imprime la variable `$content`, que representa el resultado de la renderización del contenido de cada vista y es incrustado dentro del layout cuando se llama al método [[yii\base\Controller::render()]]. +Como puedes ver, el layout genera los tags HTML comunes a todas las páginas. Dentro de la sección ``, +el layout imprime la variable `$content`, que representa el resultado de la renderización del contenido de cada vista +y es incrustado dentro del layout cuando se llama al método [[yii\base\Controller::render()]]. -La mayoría de layouts deberían llamar a los siguientes métodos (como fue mostrado recién). Estos métodos principalmente disparan eventos acerca del proceso de renderizado así los scripts y tags registrados en otros lugares pueden ser propiamente inyectados en los lugares donde los métodos son llamados. +La mayoría de layouts deberían llamar a los siguientes métodos (como fue mostrado recién). Estos métodos principalmente disparan eventos +acerca del proceso de renderizado así los scripts y tags registrados en otros lugares pueden ser propiamente inyectados +en los lugares donde los métodos son llamados. -- [[yii\base\View::beginPage()|beginPage()]]: Este método debería ser llamado bien al principio del layout. Esto dispara el evento [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], el cual indica el comienzo de la página. -- [[yii\base\View::endPage()|endPage()]]: Este método debería ser llamado al final del layout. Esto dispara el evento [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]], indicando el final de la página. -- [[yii\web\View::head()|head()]]: Este método debería llamarse dentro de la sección `` de una página HTML. Esto genera un espacio vacío que será reemplazado con el código del head HTML registrado (ej. link tags, meta tags) cuando una página finaliza el renderizado. -- [[yii\base\View::beginBody()|beginBody()]]: Este método debería llamarse al principio de la sección ``. Esto dispara el evento [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] y genera un espacio vacío que será reemplazado con el código HTML registrado (ej. JavaScript) que apunta al principio del body. -- [[yii\base\View::endBody()|endBody()]]: Este método debería llamarse al final de la sección ``. Esto dispara el evento [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]], que genera un espacio vacío a ser reemplazado por el código HTML registrado (ej. JavaScript) que apunta al final del body. +- [[yii\base\View::beginPage()|beginPage()]]: Este método debería ser llamado bien al principio del layout. + Esto dispara el evento [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], el cual indica el comienzo de la página. +- [[yii\base\View::endPage()|endPage()]]: Este método debería ser llamado al final del layout. + Esto dispara el evento [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]], indicando el final de la página. +- [[yii\web\View::head()|head()]]: Este método debería llamarse dentro de la sección `` de una página HTML. + Esto genera un espacio vacío que será reemplazado con el código del head HTML registrado (ej. link tags, meta tags) + cuando una página finaliza el renderizado. +- [[yii\base\View::beginBody()|beginBody()]]: Este método debería llamarse al principio de la sección ``. + Esto dispara el evento [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] y genera un espacio vacío que será reemplazado + con el código HTML registrado (ej. JavaScript) que apunta al principio del body. +- [[yii\base\View::endBody()|endBody()]]: Este método debería llamarse al final de la sección ``. + Esto dispara el evento [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]], que genera un espacio vacío a ser reemplazado + por el código HTML registrado (ej. JavaScript) que apunta al final del body. -### Accediendo a Datos en Layouts +### Acceder a Datos en Layouts -Dentro de un layout, tienes acceso a dos variables predefinidas: `$this` y `$content`. La primera se refiere al componente [[yii\base\View|view]], como en cualquier vista, mientras que la última contiene el resultado de la renderización del contenido de la vista que está siendo renderizada all llamar al método [[yii\base\Controller::render()|render()]] en los controladores. +Dentro de un layout, tienes acceso a dos variables predefinidas: `$this` y `$content`. La primera se refiere al componente [[yii\base\View|view]], +como en cualquier vista, mientras que la última contiene el resultado de la renderización del contenido de la vista que está siendo renderizada +al llamar al método [[yii\base\Controller::render()|render()]] en los controladores. -Si quieres acceder a otros datos en los layouts, debes utilizar el modo pull que fue descrito en la sub-sección [Accediendo a Datos en la Vista](#accessing-data-in-views). Si quieres pasar datos desde al contenido de la vista a un layout, puedes utilizar el método descrito en la sub-sección [Compartiendo Datos Entre las Vistas](#sharing-data-among-views). +Si quieres acceder a otros datos en los layouts, debes utilizar el modo pull que fue descrito en la sub-sección [Accediendo a Datos en la Vista](#accessing-data-in-views). +Si quieres pasar datos desde al contenido de la vista a un layout, puedes utilizar el método descrito en la +sub-sección [Compartiendo Datos Entre las Vistas](#sharing-data-among-views). -### Utilizando Layouts +### Utilizar Layouts -Como se describe en la sub-sección [Renderizando en Controllers](#rendering-in-controllers), cuando renderizas una vista llamando al método [[yii\base\Controller::render()|render()]] en un controlador, al resultado de dicha renderización le será aplicado un layout. Por defecto, el layout `@app/views/layouts/main.php` será el utilizado. +Como se describe en la sub-sección [Renderizando en Controllers](#rendering-in-controllers), cuando renderizas una vista +llamando al método [[yii\base\Controller::render()|render()]] en un controlador, al resultado de dicha renderización le será aplicado un layout. +Por defecto, el layout `@app/views/layouts/main.php` será el utilizado. -Puedes utilizar un layout diferente configurando la propiedad [[yii\base\Application::layout]] o [[yii\base\Controller::layout]]. El primero se refiere al layout utilizado por todos los controladores, mientras que el último sobrescribe el layout en controladores individuales. Por ejemplo, el siguiente código hace que el controlador `post` utilice `@app/views/layouts/post.php` como layout al renderizar sus vistas. Otros controladores, asumiendo que su propiedad `layout` no fue modificada, utilizarán `@app/views/layouts/main.php` como layout. +Puedes utilizar un layout diferente configurando la propiedad [[yii\base\Application::layout]] o [[yii\base\Controller::layout]]. El primero +se refiere al layout utilizado por todos los controladores, mientras que el último sobrescribe el layout en controladores individuales. +Por ejemplo, el siguiente código hace que el controlador `post` utilice `@app/views/layouts/post.php` como layout al renderizar sus vistas. +Otros controladores, asumiendo que su propiedad `layout` no fue modificada, +utilizarán `@app/views/layouts/main.php` como layout. ```php namespace app\controllers; @@ -320,20 +406,31 @@ class PostController extends Controller } ``` -Para controladores que pertencen a un módulo, puedes también configurar la propiedad [[yii\base\Module::layout|layout]] y así utilizar un layout en particular para esos controladores. +Para controladores que pertencen a un módulo, puedes también configurar la propiedad [[yii\base\Module::layout|layout]] y así utilizar un layout +en particular para esos controladores. -Dado que la propiedad `layout` puede ser configurada en diferentes niveles (controladores, módulos, aplicación), detrás de escena Yii realiza dos pasos para determinar cuál es el archivo de layout siendo utilizado para un controlador en particular. +Dado que la propiedad `layout` puede ser configurada en diferentes niveles (controladores, módulos, aplicación), detrás de escena +Yii realiza dos pasos para determinar cuál es el archivo de layout siendo utilizado para un controlador en particular. En el primer paso, determina el valor del layout y el módulo de contexto: -- Si la propiedad [[yii\base\Controller::layout]] no es `null`, la utiliza como valor del layout y el [[yii\base\Controller::module|módulo]] del controlador como el módulo de contexto. -- Si [[yii\base\Controller::layout|layout]] es `null`, busca a través de todos los módulos ancestros del controlador y encuentra el primer módulo cuya propiedad [[yii\base\Module::layout|layout]] no es `null`. Utiliza ese módulo y su valor de [[yii\base\Module::layout|layout]] como módulo de contexto y como layout seleccionado. Si tal módulo no puede ser encontrado, significa que no se aplicará ningún layout. +- Si la propiedad [[yii\base\Controller::layout]] no es `null`, la utiliza como valor del layout y el [[yii\base\Controller::module|módulo]] + del controlador como el módulo de contexto. +- Si [[yii\base\Controller::layout|layout]] es `null`, busca a través de todos los módulos ancestros del controlador + y encuentra el primer módulo cuya propiedad [[yii\base\Module::layout|layout]] no es `null`. + Utiliza ese módulo y su valor de [[yii\base\Module::layout|layout]] como módulo de contexto y como layout seleccionado. + Si tal módulo no puede ser encontrado, significa que no se aplicará ningún layout. -En el segundo paso, se determina el archivo de layout actual de acuerdo al valor de layout y el módulo de contexto determinado en el primer paso. El valor de layout puede ser: +En el segundo paso, se determina el archivo de layout actual de acuerdo al valor de layout y el módulo de contexto determinado en el primer paso. +El valor de layout puede ser: - un alias de ruta (ej. `@app/views/layouts/main`). -- una ruta absoluta (ej. `/main`): el valor del layout comienza con una barra. El archivo de layout actual será buscado bajo el [[yii\base\Application::layoutPath|layout path]] de la aplicación, que es por defecto `@app/views/layouts`. -- una ruta relativa (ej. `main`): El archivo de layout actual será buscado bajo el [[yii\base\Module::layoutPath|layout path]] del módulo de contexto, que es por defecto el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +- una ruta absoluta (ej. `/main`): el valor del layout comienza con una barra. El archivo de layout actual será buscado + bajo el [[yii\base\Application::layoutPath|layout path]] de la aplicación, + que es por defecto `@app/views/layouts`. +- una ruta relativa (ej. `main`): El archivo de layout actual será buscado bajo el [[yii\base\Module::layoutPath|layout path]] + del módulo de contexto, que es por defecto el directorio `views/layouts` + bajo el [[yii\base\Module::basePath|directorio del módulo]]. - el valor booleano `false`: no se aplicará ningún layout. Si el valor de layout no contiene una extensión de tipo de archivo, utilizará por defecto `.php`. @@ -341,7 +438,10 @@ Si el valor de layout no contiene una extensión de tipo de archivo, utilizará ### Layouts Anidados -A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferentes secciones de un sitio Web, podrías querer utilizar layouts diferentes, mientras que todos esos layouts comparten el mismo layout básico que genera la estructura general de la página en HTML5. Esto es posible llamando a los métodos [[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]] en los layouts hijos como se muestra a continuación: +A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferentes secciones de un sitio Web, +podrías querer utilizar layouts diferentes, mientras que todos esos layouts comparten el mismo layout básico que genera +la estructura general de la página en HTML5. Esto es posible llamando a los métodos +[[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]] en los layouts hijos como se muestra a continuación: ```php beginContent('@app/views/layouts/base.php'); ?> @@ -351,14 +451,80 @@ A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferen endContent(); ?> ``` -Como se acaba de mostrar, el contenido del layout hijo debe ser encerrado dentro de [[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]]. El parámetro pasado a [[yii\base\View::beginContent()|beginContent()]] especifica cuál es el módulo padre. Este puede ser tanto un archivo layout como un alias. +Como se acaba de mostrar, el contenido del layout hijo debe ser encerrado dentro de [[yii\base\View::beginContent()|beginContent()]] +y [[yii\base\View::endContent()|endContent()]]. El parámetro pasado a [[yii\base\View::beginContent()|beginContent()]] +especifica cuál es el módulo padre. Este puede ser tanto un archivo layout como un alias. Utilizando la forma recién mencionada, puedes anidar layouts en más de un nivel. -## Utilizando Componentes de Vista +### Utilizar Blocks -Los [[yii\base\View|componentes de vista]] proveen características relacionadas a las vistas. Aunque puedes obtener componentes de vista creando instancias individuales de [[yii\base\View]] o sus clases hijas, en la mayoría de los casos utilizarías el componente `view` del a aplicación. Puedes configurar este componente en la [configuración de la aplicación](structure-applications.md#application-configurations) como a continuación: +Los bloques te permiten especificar el contenido de la vista en un lugar y mostrarlo en otro. Estos son a menudo utilizados junto a +los layouts. Por ejemplo, puedes definir un bloque un una vista de contenido y mostrarla en el layout. + +Para definir un bloque, llamas a [[yii\base\View::beginBlock()|beginBlock()]] y [[yii\base\View::endBlock()|endBlock()]]. +El bloque puede ser accedido vía `$view->blocks[$blockID]`, donde `$blockID` se refiere al ID único que le asignas +al bloque cuando lo defines. + +El siguiente ejemplo muestra cómo utilizar bloques para personalizar partes especificas del layout in una vista. + +Primero, en una vista, define uno o varios bloques: + +```php +... + +beginBlock('block1'); ?> + +...contenido de block1... + +endBlock(); ?> + +... + +beginBlock('block3'); ?> + +...contenido de block3... + +endBlock(); ?> +``` + +Entonces, en la vista del layout, renderiza los bloques si están disponibles, o muestra un contenido por defecto si el bloque +no está definido. + +```php +... +blocks['block1'])): ?> + blocks['block1'] ?> + + ... contenido por defecto de block1 ... + + +... + +blocks['block2'])): ?> + blocks['block2'] ?> + + ... contenido por defecto de block2 ... + + +... + +blocks['block3'])): ?> + blocks['block3'] ?> + + ... contenido por defecto de block3 ... + +... +``` + + +## Utilizar Componentes de Vista + +Los [[yii\base\View|componentes de vista]] proveen características relacionadas a las vistas. Aunque puedes obtener componentes de vista +creando instancias individuales de [[yii\base\View]] o sus clases hijas, en la mayoría de los casos utilizarías el componente `view` del a aplicación. +Puedes configurar este componente en la [configuración de la aplicación](structure-applications.md#application-configurations) +como a continuación: ```php [ @@ -386,13 +552,15 @@ Puedes también utilizar frecuentemente el siguiente menor pero útil grupo de c ### Definiendo Títulos de Página -Toda página Web debería tener un título. Normalmente el tag de título es generado en [layout](#layouts). De todos modos, en la práctica el título es determinado en el contenido de las vistas más que en layouts. Para resolver este problema, [[yii\web\View]] provee la propiedad [[yii\web\View::title|title]] para que puedas pasar información del título desde el contenido de la vista a los layouts. +Toda página Web debería tener un título. Normalmente el tag de título es generado en [layout](#layouts). De todos modos, en la práctica +el título es determinado en el contenido de las vistas más que en layouts. Para resolver este problema, [[yii\web\View]] provee +la propiedad [[yii\web\View::title|title]] para que puedas pasar información del título desde el contenido de la vista a los layouts. Para utilizar esta característica, en cada contenido de la vista, puedes definir el título de la siguiente manera: ```php title = 'Mi título de página'; +$this->title = 'Título de mi página'; ?> ``` @@ -403,11 +571,13 @@ Entonces en el layout, asegúrate de tener el siguiente código en la sección ` ``` -### Registrando Meta Tags +### Registrar Meta Tags -Las páginas Web usualmente necesitan generar varios meta tags necesarios por diferentes grupos (ej. Facebook, motores de búsqueda, etc). Cómo los títulos de página, los meta tags aparecen en la sección `` y son usualmente generado en los layouts. +Las páginas Web usualmente necesitan generar varios meta tags necesarios para diferentes grupos. Cómo los títulos de página, los meta tags +aparecen en la sección `` y son usualmente generado en los layouts. -Si quieres especificar cuáles meta tags generar en las vistas, puedes llamar a [[yii\web\View::registerMetaTag()]] dentro de una de ellas, como se muestra a continuación: +Si quieres especificar cuáles meta tags generar en las vistas, puedes llamar a [[yii\web\View::registerMetaTag()]] +dentro de una de ellas, como se muestra a continuación: ```php registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php' ?> ``` -El código anterior registrará el meta tag "keywords" a través del componente view. El meta tag registrado no se renderiza hasta que finaliza el renderizado del layout. Para entonces, el siguiente código HTML será insertado en el lugar donde llamas a [[yii\web\View::head()]] en el layout, generando el siguiente HTML: +El código anterior registrará el meta tag "keywords" a través del componente view. El meta tag registrado +no se renderiza hasta que finaliza el renderizado del layout. Para entonces, el siguiente código HTML será insertado +en el lugar donde llamas a [[yii\web\View::head()]] en el layout, generando el siguiente HTML: ```php ``` -Ten en cuenta que si llamas a [[yii\web\View::registerMetaTag()]] varias veces, esto registrará varios meta tags, sin tener en cuenta si los meta tags son los mismo o no. +Ten en cuenta que si llamas a [[yii\web\View::registerMetaTag()]] varias veces, esto registrará varios meta tags, +sin tener en cuenta si los meta tags son los mismo o no. -Para asegurarte de que sólo haya una instancia de cierto tipo de meta tag, puedes especificar una clave al llamar al método. Por ejemplo, el siguiente código registra dos meta tags "description", aunque sólo el segundo será renderizado. +Para asegurarte de que sólo haya una instancia de cierto tipo de meta tag, puedes especificar una clave al llamar al método. +Por ejemplo, el siguiente código registra dos meta tags "description", aunque sólo el segundo será renderizado. -```html +```php $this->registerMetaTag(['name' => 'description', 'content' => 'Este es mi sitio Web cool hecho con Yii!'], 'description'); $this->registerMetaTag(['name' => 'description', 'content' => 'Este sitio Web es sobre mapaches graciosos.'], 'description'); ``` -### Registrando Link Tags +### Registrar Link Tags -Tal como los [meta tags](#adding-meta-tags), los link tags son útiles en muchos casos, como personalizar el ícono (favicon) del sitio, apuntar a una fuente de RSS o delegar OpenID a otro servidor. Puedes trabajar con link tags, al igual que con meta tags, utilizando [[yii\web\View::registerLinkTag()]]. Por ejemplo, en el contenido de una vista, puedes registrar un link tag como se muestra a continuación: +Tal como los [meta tags](#adding-meta-tags), los link tags son útiles en muchos casos, como personalizar el ícono (favicon) del sitio, +apuntar a una fuente de RSS o delegar OpenID a otro servidor. Puedes trabajar con link tags, al igual que con meta tags, +utilizando [[yii\web\View::registerLinkTag()]]. Por ejemplo, en el contenido de una vista, puedes registrar un link tag como se muestra a continuación: ```php $this->registerLinkTag([ @@ -450,16 +626,20 @@ El resultado del código es el siguiente: ``` -Al igual que con [[yii\web\View::registerMetaTag()|registerMetaTags()]], puedes especificar una clave al llamar a [[yii\web\View::registerLinkTag()|registerLinkTag()]] para evitar registrar link tags repetidos. +Al igual que con [[yii\web\View::registerMetaTag()|registerMetaTags()]], puedes especificar una clave al llamar +a [[yii\web\View::registerLinkTag()|registerLinkTag()]] para evitar registrar link tags repetidos. ## Eventos de Vistas -Los [[yii\base\View|componentes de vistas]] disparan varios eventos durante el proceso de renderizado de la vista. Puedes responder a estos eventos para inyectar contenido a la vista o procesar el resultado de la renderización antes de que sea enviada al usuario final. +Los [[yii\base\View|componentes de vistas]] disparan varios eventos durante el proceso de renderizado de la vista. Puedes responder +a estos eventos para inyectar contenido a la vista o procesar el resultado de la renderización antes de que sea enviada al usuario final. -- [[yii\base\View::EVENT_BEFORE_RENDER|EVENT_BEFORE_RENDER]]: disparado al principio del renderizado de un archivo en un controlador. Los manejadores de este evento pueden definir [[yii\base\ViewEvent::isValid]] como `false` para cancelar el proceso de renderizado. -- [[yii\base\View::EVENT_AFTER_RENDER|EVENT_AFTER_RENDER]]: disparado por la llamada a [[yii\base\View::beginPage()]] en layouts. -Los manejadores de este evento pueden obtener el resultado de la renderización a través de [[yii\base\ViewEvent::output]] y entonces modificar esta propiedad para así cambiar el mismo. +- [[yii\base\View::EVENT_BEFORE_RENDER|EVENT_BEFORE_RENDER]]: disparado al principio del renderizado de un archivo + en un controlador. Los manejadores de este evento pueden definir [[yii\base\ViewEvent::isValid]] como `false` para cancelar el proceso de renderizado. +- [[yii\base\View::EVENT_AFTER_RENDER|EVENT_AFTER_RENDER]]: disparado luego de renderizar un archivo con la llamada de [[yii\base\View::afterRender()]]. + Los manejadores de este evento pueden obtener el resultado del renderizado a través de [[yii\base\ViewEvent::output]] y modificar + esta propiedad para cambiar dicho resultado. - [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]]: disparado por la llamada a [[yii\base\View::beginPage()]] en layouts. - [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]]: disparado por la llamada a [[yii\base\View::endPage()]] en layouts. - [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]]: disparado por la llamada a [[yii\web\View::beginBody()]] en layouts. @@ -474,9 +654,10 @@ Por ejemplo, el siguiente código inyecta la fecha actual al final del body de l ``` -## Renderizando Páginas Estáticas +## Renderizar Páginas Estáticas -Con páginas estáticas nos referimos a esas páginas cuyo contenido es mayormente estático y sin necesidad de acceso a datos dinámicos enviados desde los controladores. +Con páginas estáticas nos referimos a esas páginas cuyo contenido es mayormente estático y sin necesidad de acceso +a datos dinámicos enviados desde los controladores. Puedes generar páginas estáticas utilizando un código como el que sigue dentro de un controlador: @@ -487,7 +668,9 @@ public function actionAbout() } ``` -Si un sitio Web contiene muchas páginas estáticas, resultaría tedioso repetir el mismo código en muchos lados. Para resolver este problema, puedes introducir una [acción independiente](structure-controllers.md#standalone-actions) llamada [[yii\web\ViewAction]] en el controlador. Por ejemplo, +Si un sitio Web contiene muchas páginas estáticas, resultaría tedioso repetir el mismo código en muchos lados. +Para resolver este problema, puedes introducir una [acción independiente](structure-controllers.md#standalone-actions) +llamada [[yii\web\ViewAction]] en el controlador. Por ejemplo, ```php namespace app\controllers; @@ -507,13 +690,16 @@ class SiteController extends Controller } ``` -Ahora, si creamos una vista llamada `about` bajo el directorio `@app/views/site/pages`, serás capáz de mostrarla en la siguiente URL: +Ahora, si creamos una vista llamada `about` bajo el directorio `@app/views/site/pages`, serás capáz de mostrarla +en la siguiente URL: ``` -http://localhost/index.php?r=site/page&view=about +http://localhost/index.php?r=site%2Fpage&view=about ``` -El parámetro `GET` `view` le comunica a [[yii\web\ViewAction]] cuál es la vista solicitada. La acción entonces buscará esta vista dentro de `@app/views/site/pages`. Puedes configurar la propiedad [[yii\web\ViewAction::viewPrefix]] para cambiar el directorio en el que se buscarán dichas páginas. +El parámetro `GET` `view` le comunica a [[yii\web\ViewAction]] cuál es la vista solicitada. La acción entonces buscará +esta vista dentro de `@app/views/site/pages`. Puedes configurar la propiedad [[yii\web\ViewAction::viewPrefix]] +para cambiar el directorio en el que se buscarán dichas páginas. ## Buenas Prácticas @@ -523,12 +709,15 @@ Las vistas son responsables de la presentación de modelos en el formato que el * deberían contener principalmente sólo código de presentación, como HTML, y PHP simple para recorrer, dar formato y renderizar datos. * no deberían contener código que realiza consultas a la base de datos. Ese tipo de código debe ir en los modelos. * deberían evitar el acceso directo a datos del `request`, como `$_GET` y/o `$_POST`. Esto es una responsabilidad de los controladores. -Si se necesitan datos del `request`, deben ser inyectados a la vista desde el controlador. + Si se necesitan datos del `request`, deben ser inyectados a la vista desde el controlador. * pueden leer propiedades del modelo, pero no debería modificarlas. -Para hacer las vistas más manejables, evita crear vistas que son demasiado complejas o que contengan código redundante. Puedes utilizar estas técnicas para alcanzar dicha meta: +Para hacer las vistas más manejables, evita crear vistas que son demasiado complejas o que contengan código redundante. +Puedes utilizar estas técnicas para alcanzar dicha meta: * utiliza [layouts](#layouts) para representar secciones comunes (ej. encabezado y footer de la página). -* divide una vista compleja en varias más simples. Las vistas pequeñas pueden ser renderizadas y unidas una mayor utilizando los métodos de renderización antes descritos. +* divide una vista compleja en varias más simples. Las vistas pequeñas pueden ser renderizadas y unidas una mayor + utilizando los métodos de renderización antes descritos. * crea y utiliza [widgets](structure-widgets.md) como bloques de construcción de la vista. * crea y utilizar helpers para transformar y dar formato a los datos en la vista. + diff --git a/docs/guide-es/structure-widgets.md b/docs/guide-es/structure-widgets.md index fdf78303a8..f5a408730f 100644 --- a/docs/guide-es/structure-widgets.md +++ b/docs/guide-es/structure-widgets.md @@ -1,4 +1,4 @@ -Widgets +Widgets ======= Los Widgets son bloques de código reutilizables utilizados en las [vistas](structure-views.md) para crear elementos de @@ -190,4 +190,4 @@ JavaScript, imágenes, etc. Afortunadamente Yii proporciona soporte para Cuando un widget sólo contiene código de vista, este es muy similar a una [vista](structure-views.md). De hecho, en este caso, su única diferencia es que un widget es una clase redistribuible, mientras que una vista es sólo un script -PHP llano que prefiere mantenerse dentro de su aplicación. \ No newline at end of file +PHP llano que prefiere mantenerse dentro de su aplicación. diff --git a/docs/guide-es/test-environment-setup.md b/docs/guide-es/test-environment-setup.md new file mode 100644 index 0000000000..631f96a9ee --- /dev/null +++ b/docs/guide-es/test-environment-setup.md @@ -0,0 +1,49 @@ +Preparación del entorno de test +=============================== + +> Note: Esta sección se encuentra en desarrollo. + +Yii 2 ha mantenido integración oficial con el framework de testing [`Codeception`](https://github.com/Codeception/Codeception), +que te permite crear los siguientes tipos de tests: + +- [Test de unidad](test-unit.md) - verifica que una unidad simple de código funciona como se espera; +- [Test funcional](test-functional.md) - verifica escenarios desde la perspectiva de un usuario a través de la emulación de un navegador; +- [Test de aceptación](test-acceptance.md) - verifica escenarios desde la perspectiva de un usuario en un navegador. + +Yii provee grupos de pruebas listos para utilizar en ambos +[`yii2-basic`](https://github.com/yiisoft/yii2-app-basic) y +[`yii2-advanced`](https://github.com/yiisoft/yii2-app-advanced) templates de proyectos. + +Para poder ejecutar estos tests es necesario instalar [Codeception](https://github.com/Codeception/Codeception). +Puedes instalarlo tanto localmente - únicamente para un proyecto en particular, o globalmente - para tu máquina de desarrollo. + +Para la instalación local utiliza los siguientes comandos: + +``` +composer require "codeception/codeception=2.1.*" +composer require "codeception/specify=*" +composer require "codeception/verify=*" +``` + +Para la instalación global necesitarás la directiva `global`: + +``` +composer global require "codeception/codeception=2.1.*" +composer global require "codeception/specify=*" +composer global require "codeception/verify=*" +``` + +En caso de que nunca hayas utilizado Composer para paquetes globales, ejecuta `composer global status`. Esto debería mostrar la salida: + +``` +Changed current directory to +``` + +Entonces agrega `/vendor/bin` a tu variable de entorno `PATH`. Ahora podrás utilizar el `codecept` en la línea +de comandos a nivel global. + +> Note: la instalación global te permite usar Codeception para todos los proyectos en los que trabajes en tu máquina de desarrollo y + te permite ejecutar el comando `codecept` globalmente sin especificar su ruta. De todos modos, ese acercamiento podría ser inapropiado, + por ejemplo, si 2 proyectos diferentes requieren diferentes versiones de Codeception instaladas. + Por simplicidad, todos los comandos relacionados a tests en esta guía están escritos asumiendo que Codeception + ha sido instalado en forma global. diff --git a/docs/guide-es/test-fixtures.md b/docs/guide-es/test-fixtures.md new file mode 100644 index 0000000000..445617ddc8 --- /dev/null +++ b/docs/guide-es/test-fixtures.md @@ -0,0 +1,379 @@ +Fixtures +======== + +Los fixtures son una parte importante de los tests. Su propósito principal es el de preparar el entorno en una estado fijado/conocido +de manera que los tests sean repetibles y corran de la manera esperada. Yii provee un framework de fixtures que te permite +dichos fixtures de manera precisa y usarlo de forma simple. + +Un concepto clave en el framework de fixtures de Yii es el llamado *objeto fixture*. Un objeto fixture representa +un aspecto particular de un entorno de pruebas y es una instancia de [[yii\test\Fixture]] o heredada de esta. Por ejemplo, +puedes utilizar `UserFixture` para asegurarte de que la tabla de usuarios de la BD contiene un grupo de datos fijos. Entonces cargas uno o varios +objetos fixture antes de correr un test y lo descargas cuando el test ha concluido. + +Un fixture puede depender de otros fixtures, especificándolo en su propiedad [[yii\test\Fixture::depends]]. +Cuando un fixture está siendo cargado, los fixtures de los que depende serán cargados automáticamente ANTES que él; +y cuando el fixture está siendo descargado, los fixtures dependientes serán descargados DESPUÉS de él. + + +Definir un Fixture +------------------ + +Para definir un fixture, crea una nueva clase que extienda de [[yii\test\Fixture]] o [[yii\test\ActiveFixture]]. +El primero es más adecuado para fixtures de propósito general, mientras que el último tiene características mejoradas específicamente +diseñadas para trabajar con base de datos y ActiveRecord. + +El siguiente código define un fixture acerca del ActiveRecord `User` y su correspondiente tabla user. + +```php + Tip: Cada `ActiveFixture` se encarga de preparar la tabla de la DB para los tests. Puedes especificar la tabla +> definiendo tanto la propiedad [[yii\test\ActiveFixture::tableName]] o la propiedad [[yii\test\ActiveFixture::modelClass]]. +> Haciéndolo como el último, el nombre de la tabla será tomado de la clase `ActiveRecord` especificada en `modelClass`. + +> Note: [[yii\test\ActiveFixture]] es sólo adecualdo para bases de datos SQL. Para bases de datos NoSQL, Yii provee +> las siguientes clases `ActiveFixture`: +> +> - Mongo DB: [[yii\mongodb\ActiveFixture]] +> - Elasticsearch: [[yii\elasticsearch\ActiveFixture]] (desde la versión 2.0.2) + + +Los datos para un fixture `ActiveFixture` son usualmente provistos en un archivo ubicado en `FixturePath/data/TableName.php`, +donde `FixturePath` corresponde al directorio conteniendo el archivo de clase del fixture, y `TableName` +es el nombre de la tabla asociada al fixture. En el ejemplo anterior, el archivo debería ser +`@app/tests/fixtures/data/user.php`. El archivo de datos debe devolver un array de registros +a ser insertados en la tabla user. Por ejemplo, + +```php + [ + 'username' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + 'user2' => [ + 'username' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` + +Puedes dar un alias al registro tal que más tarde en tu test, puedas referirte a ese registra a través de dicho alias. En el ejemplo anterior, +los dos registros tienen como alias `user1` y `user2`, respectivamente. + +Además, no necesitas especificar los datos de columnas auto-incrementales. Yii automáticamente llenará esos valores +dentro de los registros cuando el fixture está siendo cargado. + +> Tip: Puedes personalizar la ubicación del archivo de datos definiendo la propiedad [[yii\test\ActiveFixture::dataFile]]. +> Puedes también sobrescribir [[yii\test\ActiveFixture::getData()]] para obtener los datos. + +Como se describió anteriormente, un fixture puede depender de otros fixtures. Por ejemplo, un `UserProfileFixture` puede necesitar depender de `UserFixture` +porque la table de perfiles de usuarios contiene una clave foránea a la tabla user. +La dependencia es especificada vía la propiedad [[yii\test\Fixture::depends]], como a continuación, + +```php +namespace app\tests\fixtures; + +use yii\test\ActiveFixture; + +class UserProfileFixture extends ActiveFixture +{ + public $modelClass = 'app\models\UserProfile'; + public $depends = ['app\tests\fixtures\UserFixture']; +} +``` + +La dependencia también asegura que los fixtures son cargados y descargados en un orden bien definido. En el ejemplo `UserFixture` +será siempre cargado antes de `UserProfileFixture` para asegurar que todas las referencias de las claves foráneas existan y será siempre descargado después de `UserProfileFixture` +por la misma razón. + +Arriba te mostramos cómo definir un fixture de BD. Para definir un fixture no relacionado a BD +(por ej. un fixture acerca de archivos y directorios), puedes extender de la clase base más general +[[yii\test\Fixture]] y sobrescribir los métodos [[yii\test\Fixture::load()|load()]] y [[yii\test\Fixture::unload()|unload()]]. + + +Utilizar Fixtures +----------------- + +Si estás utilizando [Codeception](http://codeception.com/) para hacer tests de tu código, deberías considerar el utilizar +la extensión `yii2-codeception`, que tiene soporte incorporado para la carga y acceso a fixtures. +En caso de que utilices otros frameworks de testing, puedes usar [[yii\test\FixtureTrait]] en tus casos de tests +para alcanzar el mismo objetivo. + +A continuación describiremos cómo escribir una clase de test de unidad `UserProfile` utilizando `yii2-codeception`. + +En tu clase de test de unidad que extiende de [[yii\codeception\DbTestCase]] o [[yii\codeception\TestCase]], +indica cuáles fixtures quieres utilizar en el método [[yii\test\FixtureTrait::fixtures()|fixtures()]]. Por ejemplo, + +```php +namespace app\tests\unit\models; + +use yii\codeception\DbTestCase; +use app\tests\fixtures\UserProfileFixture; + +class UserProfileTest extends DbTestCase +{ + public function fixtures() + { + return [ + 'profiles' => UserProfileFixture::className(), + ]; + } + + // ...métodos de test... +} +``` + +Los fixtures listados en el método `fixtures()` serán automáticamente cargados antes de correr cada método de test +en el caso de test y descargado al finalizar cada uno. También, como describimos antes, cuando un fixture está +siendo cargado, todos sus fixtures dependientes serán cargados primero. En el ejemplo de arriba, debido a que +`UserProfileFixture` depende de `UserFixture`, cuando ejecutas cualquier método de test en la clase, +dos fixtures serán cargados secuencialmente: `UserFixture` y `UserProfileFixture`. + +Al especificar fixtures en `fixtures()`, puedes utilizar tanto un nombre de clase o un array de configuración para referirte a +un fixture. El array de configuración te permitirá personalizar las propiedades del fixture cuando este es cargado. + +Puedes también asignarles alias a los fixtures. En el ejemplo anterior, el `UserProfileFixture` tiene como alias `profiles`. +En los métodos de test, puedes acceder a un objeto fixture utilizando su alias. Por ejemplo, `$this->profiles` +devolverá el objeto `UserProfileFixture`. + +Dado que `UserProfileFixture` extiende de `ActiveFixture`, puedes por lo tanto usar la siguiente sintáxis para acceder +a los datos provistos por el fixture: + +```php +// devuelve el registro del fixture cuyo alias es 'user1' +$row = $this->profiles['user1']; +// devuelve el modelo UserProfile correspondiente al registro cuyo alias es 'user1' +$profile = $this->profiles('user1'); +// recorre cada registro en el fixture +foreach ($this->profiles as $row) ... +``` + +> Info: `$this->profiles` es todavía del tipo `UserProfileFixture`. Las características de acceso mostradas arriba son implementadas +> a través de métodos mágicos de PHP. + + +Definir y Utilizar Fixtures Globales +------------------------------------ + +Los fixtures descritos arriba son principalmente utilizados para casos de tests individuales. En la mayoría de los casos, puedes necesitar algunos +fixtures globales que sean aplicados a TODOS o muchos casos de test. Un ejemplo sería [[yii\test\InitDbFixture]], que hace +dos cosas: + +* Realiza alguna tarea de inicialización común al ejectutar un script ubicado en `@app/tests/fixtures/initdb.php`; +* Deshabilita la comprobación de integridad antes de cargar otros fixtures de BD, y la rehabilita después de que todos los fixtures son descargados. + +Utilizar fixtures globales es similar a utilizar los no-globales. La única diferencia es que declaras estos fixtures +en [[yii\codeception\TestCase::globalFixtures()]] en vez de en `fixtures()`. Cuando un caso de test carga fixtures, +primero carga los globales y luego los no-globales. + +Por defecto, [[yii\codeception\DbTestCase]] ya declara `InitDbFixture` en su método `globalFixtures()`. +Esto significa que sólo necesitas trabajar con `@app/tests/fixtures/initdb.php` si quieres realizar algún trabajo de inicialización +antes de cada test. Sino puedes simplemente enfocarte en desarrollar cada caso de test individual y sus fixtures correspondientes. + + +Organizar Clases de Fixtures y Archivos de Datos +------------------------------------------------ + +Por defecto, las clases de fixtures busca los archivos de datos correspondientes dentro de la carpeta `data`, que es una subcarpeta +de la carpeta conteniendo los archivos de clases de fixtures. Puedes seguir esta convención al trabajar en proyectos simples. +Para proyectos más grandes, es probable que a menudo necesites intercambiar entre diferentes archivos de datos para la misma clase de fixture +en diferentes tests. Recomendamos que organices los archivos de datos en forma jerárquica similar +a tus espacios de nombre de clases. Por ejemplo, + +``` +# bajo la carpeta tests\unit\fixtures + +data\ + components\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php + models\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php +# y así sucesivamente +``` + +De esta manera evitarás la colisión de archivos de datos de fixtures entre tests y podrás utlilizarlos como necesites. + +> Note: En el ejemplo de arriba los archivos de fixtures son nombrados así sólo como ejemplo. En la vida real deberías nombrarlos +> de acuerdo a qué clase de fixture extienden tus clases de fixtures. Por ejemplo, si estás extendiendo +> de [[yii\test\ActiveFixture]] para fixtures de BD, deberías utilizar nombres de tabla de la BD como nombres de los archivos de fixtures; +> Si estás extendiendo de [[yii\mongodb\ActiveFixture]] para fixtures de MongoDB, deberías utilizar nombres de colecciones para los nombres de archivo. + +Se puede utilizar una jerarquía similar para organizar archivos de clases de fixtures. En vez de utilizar `data` como directorio raíz, podrías +querer utilizar `fixtures` como directorio raíz para evitar conflictos con los archivos de datos. + + +Resumen +------- + +> Note: Esta sección se encuentra en desarrollo. + +Arriba, definimos cómo definir y utilizar fixtures. Abajo resumiremos el típico flujo de trabajo +de correr tests de unidad relacionados a BD: + +1. Usa la herramienta `yii migrate` para actualizar tu base de datos de prueba a la última versión; +2. Corre el caso de test: + - Carga los fixtures: limpia las tablas de la BD relevantes y cargala con los datos de los fixtures; + - Realiza el test en sí; + - Descarga los fixtures. +3. Repite el Paso 2 hasta que todos los tests terminen. + + +**Lo siguiente, a ser limpiado** + +Administrar Fixtures +==================== + +> Note: Esta sección está en desarrollo. +> +> todo: este tutorial podría ser unificado con la parte de arriba en test-fixtures.md + +Los fixtures son una parte importante del testing. Su principal propósito es el de poblarte con datos necesarios para el test +de diferentes casos. Con estos datos. utilizar tests se vuelve más eficiente y útil. + +Yii soporta fixtures a través de la herramienta de línea de comandos `yii fixture`. Esta herramienta soporta: + +* Cargar fixtures a diferentes almacenamientos: RDBMS, NoSQL, etc; +* Descargar fixtures de diferentes maneras (usualmente limpiando el almacenamiento); +* Auto-generar fixtures y poblarlos con datos al azar. + +Formato de Fixtures +------------------- + +Los fixtures son objetos con diferentes métodos y configuraciones, inspecciónalos en la [documentación oficial](https://github.com/yiisoft/yii2/blob/master/docs/guide-es/test-fixtures.md). +Asumamos que tenemos datos de fixtures a cargar: + +``` +#archivo users.php bajo la ruta de los fixtures, por defecto @tests\unit\fixtures\data + +return [ + [ + 'name' => 'Chase', + 'login' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + [ + 'name' => 'Celestine', + 'login' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` +Si estamos utilizando un fixture que carga datos en la base de datos, entonces esos registros serán insertados en la tabla `users`. Si estamos utilizando fixtures no sql, por ejemplo de `mongodb`, +entonces estos datos serán aplicados a la colección mongodb `users`. Para aprender cómo implementar varias estrategias de carga y más, visita la [documentación oficial](https://github.com/yiisoft/yii2/blob/master/docs/guide-es/test-fixtures.md). +El fixture de ejemplo de arriba fue autogenerado por la extensión `yii2-faker`, lee más acerca de esto en su [sección](#auto-generating-fixtures). +Los nombres de clase de fixtures no deberían ser en plural. + +Cargar fixtures +---------------- + +Las clases de fixture deberían tener el prefijo `Fixture`. Por defecto los fixtures serán buscados bajo el espacio de nombre `tests\unit\fixtures`, puedes +modificar este comportamiento con opciones de comando o configuración. Puedes excluir algunos fixtures para carga o descarga especificando `-` antes de su nombre, por ejemplo `-User`. + +Para cargar un fixture, ejecuta el siguiente comando: + +``` +yii fixture/load +``` + +El parámetro requerido `fixture_name` especifica un nombre de fixture cuyos datos serán cargados. Puedes cargar varios fixtures de una sola vez. +Abajo se muestran formatos correctos de este comando: + +``` +// carga el fixture `User` +yii fixture/load User + +// lo mismo que arriba, dado que la acción por defecto del comando "fixture" es "load" +yii fixture User + +// carga varios fixtures +yii fixture User UserProfile + +// carga todos los fixtures +yii fixture/load "*" + +// lo mismo que arriba +yii fixture "*" + +// carga todos los fixtures excepto uno +yii fixture "*" -DoNotLoadThisOne + +// carga fixtures, pero los busca en diferente espacio de nombre. El espacio de nombre por defecto es: tests\unit\fixtures. +yii fixture User --namespace='alias\my\custom\namespace' + +// carga el fixture global `some\name\space\CustomFixture` antes de que otros fixtures sean cargados. +// Por defecto está opción se define como `InitDbFixture` para habilitar/deshabilitar la comprobación de integridad. Puedes especificar varios +// fixtures globales separados por coma. +yii fixture User --globalFixtures='some\name\space\Custom' +``` + +Descargar fixtures +------------------ + +Para descargar un fixture, ejecuta el siguiente comando: + +``` +// descarga el fixture Users, por defecto limpiará el almacenamiento del fixture (por ejemplo la tabla "users", o la colección "users" si es un fixture mongodb). +yii fixture/unload User + +// descarga varios fixtures +yii fixture/unload User,UserProfile + +// descarga todos los fixtures +yii fixture/unload "*" + +// descarga todos los fixtures excepto uno +yii fixture/unload "*" -DoNotUnloadThisOne + +``` + +Opciones de comando similares como: `namespace`, `globalFixtures` también pueden ser aplicadas a este comando. + +Configurar el Comando Globalmente +--------------------------------- +Mientras que las opciones de línea de comandos nos permiten configurar el comando de migración +en el momento, a veces queremos configurar el comando de una vez y para siempre. Por ejemplo puedes configurar +diferentes rutas de migración como a continuación: + +``` +'controllerMap' => [ + 'fixture' => [ + 'class' => 'yii\console\controllers\FixtureController', + 'namespace' => 'myalias\some\custom\namespace', + 'globalFixtures' => [ + 'some\name\space\Foo', + 'other\name\space\Bar' + ], + ], +] +``` + +Autogenerando fixtures +---------------------- + +Yii puede también autogenerar fixtures por tí basándose en algún template. Puedes generar tus fixtures con distintos datos en diferentes lenguajes y formatos. +Esta característica es realizada por la librería [Faker](https://github.com/fzaninotto/Faker) y la extensión `yii2-faker`. +Visita la [guía de la extensión](https://github.com/yiisoft/yii2-faker) para mayor documentación. diff --git a/docs/guide-es/test-functional.md b/docs/guide-es/test-functional.md new file mode 100644 index 0000000000..ee53ebc9d1 --- /dev/null +++ b/docs/guide-es/test-functional.md @@ -0,0 +1,11 @@ +Tests Funcionales +================= + +> Note: Esta sección se encuentra en desarrollo. + +- [Tests Funcionales de Codeception](http://codeception.com/docs/04-FunctionalTests) + +Ejecutar test funcionales de templates básicos y avanzados +---------------------------------------------------------- + +Por favor consulta las instrucciones provistas en `apps/advanced/tests/README.md` y `apps/basic/tests/README.md`. diff --git a/docs/guide-es/test-unit.md b/docs/guide-es/test-unit.md new file mode 100644 index 0000000000..5cb43fef74 --- /dev/null +++ b/docs/guide-es/test-unit.md @@ -0,0 +1,25 @@ +Tests de Unidad +=============== + +> Note: Esta sección se encuentra en desarrollo. + +Un test de unidad se encarga de verificar que una unidad simple de código funcione como se espera. En la programación orientada a objetos, +la unidad de código más básica es una clase. Por lo tanto, un test de unidad necesita verificar que cada método de la interfaz de la clase funciona apropiadamente. +Esto quiere decir que, dando diferentes parámetros de entrada, el test verifica que el método devuelve el resultado esperado. +Los tests de unidad son normalmente desarrollados por la persona que escribe las clases siendo testeadas. + +Los tests de unidad en Yii están construidos en base a PHPUnit y opcionalmente, Codeception, por lo que se recomineda consultar su respectiva documentación: + +- [Documentación de PHPUnit comienza en el capítulo 2](http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html). +- [Tests de Unidad con Codeception](http://codeception.com/docs/05-UnitTests). + +Ejecutar test de unidad de templates básicos y avanzados +-------------------------------------------------------- + +Por favor consulta las instrucciones provistas en `apps/advanced/tests/README.md` y `apps/basic/tests/README.md`. + +Test de unidad del Framework +---------------------------- + +Si quieres ejecutar tests de unidad para Yii en sí, consulta +"[Comenzando a desarrollar con Yii 2](https://github.com/yiisoft/yii2/blob/master/docs/internals/getting-started.md)". diff --git a/docs/guide-es/tutorial-core-validators.md b/docs/guide-es/tutorial-core-validators.md index 740a7f7062..d56325b6f2 100644 --- a/docs/guide-es/tutorial-core-validators.md +++ b/docs/guide-es/tutorial-core-validators.md @@ -1,7 +1,7 @@ -Validadores del núcleo -====================== +Validadores del framework +========================= -Yii provee en el núcleo un conjunto de validadores de uso común, que se pueden encontrar principalmente bajo el espacio de nombres (namespace) `yii\validators`. +Yii provee en su núcleo un conjunto de validadores de uso común, que se pueden encontrar principalmente bajo el espacio de nombres (namespace) `yii\validators`. En vez de utilizar interminables nombres de clases para los validadores, puedes usar *alias* para especificar el uso de esos validadores del núcleo. Por ejemplo, puedes usar el alias `required` para referirte a la clase [[yii\validators\RequiredValidator]] : ```php @@ -37,7 +37,7 @@ Este validador comprueba si el valor de la entrada (input) es booleano. - `strict`: Si el tipo del valor de la entrada (input) debe corresponder con `trueValue` y `falseValue`. Valor por defecto a `false`. -> Nota: Ya que los datos enviados con la entrada, vía formularios HTML,son todos cadenas (strings), usted debe normalmente dejar la propiedad [[yii\validators\BooleanValidator::strict|strict]] a false. +> Note: Ya que los datos enviados con la entrada, vía formularios HTML,son todos cadenas (strings), usted debe normalmente dejar la propiedad [[yii\validators\BooleanValidator::strict|strict]] a false. ## [[yii\captcha\CaptchaValidator|captcha]] diff --git a/docs/guide-es/tutorial-mailing.md b/docs/guide-es/tutorial-mailing.md new file mode 100644 index 0000000000..a0cae155ff --- /dev/null +++ b/docs/guide-es/tutorial-mailing.md @@ -0,0 +1,231 @@ +Envío de Emails +=============== + +> Note: Esta sección se encuentra en desarrollo. + +Yii soporta composición y envío de emails. De cualquier modo, el núcleo del framework provee +sólo la funcionalidad de composición y una interfaz básica. En mecanismo de envío en sí debería +ser provisto por la extensión, dado que diferentes proyectos pueden requerir diferente implementación y esto +usualmente depende de servicios y librerías externas. + +Para la mayoría de los casos, puedes utilizar la extensión oficial [yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer). + + +Configuración +------------- + +La configuración del componente Mail depende de la extensión que hayas elegido. +En general, la configuración de tu aplicación debería verse así: + +```php +return [ + //.... + 'components' => [ + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + + +Uso Básico +---------- + +Una vez configurado el componente 'mailer', puedes utilizar el siguiente código para enviar un correo electrónico: + +```php +Yii::$app->mailer->compose() + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Asunto del mensaje') + ->setTextBody('Contenido en texto plano') + ->setHtmlBody('Contenido HTML') + ->send(); +``` + +En el ejemplo anterior, el método `compose()` crea una instancia del mensaje de correo, el cual puede ser llenado y enviado. +En caso de ser necesario, puedes agregar una lógica más compleja en el proceso: + +```php +$message = Yii::$app->mailer->compose(); +if (Yii::$app->user->isGuest) { + $message->setFrom('from@domain.com') +} else { + $message->setFrom(Yii::$app->user->identity->email) +} +$message->setTo(Yii::$app->params['adminEmail']) + ->setSubject('Asunto del mensaje') + ->setTextBody('Contenido en texto plano') + ->send(); +``` + +> Note: cada extensión 'mailer' viene en dos grandes clases: 'Mailer' y 'Message'. 'Mailer' siempre conoce + el nombre de clase especifico de 'Message'. No intentes instanciar el objeto 'Message' directamente - + siempre utiliza el método `compose()` para ello. + +Puedes también enviar varios mensajes al mismo tiempo: + +```php +$messages = []; +foreach ($users as $user) { + $messages[] = Yii::$app->mailer->compose() + // ... + ->setTo($user->email); +} +Yii::$app->mailer->sendMultiple($messages); +``` + +Algunas extensiones en particular pueden beneficiarse de este enfoque, utilizando mensaje simple de red, etc. + + +Componer el contenido del mensaje +--------------------------------- + +Yii permite componer el contenido de los mensajes de correo a través de archivos de vista especiales. +Por defecto, estos archivos deben estar ubicados en la ruta '@app/mail'. + +Ejemplo de archivo de contenido de correo: + +```php + +

Este mensaje te permite visitar nuestro sitio con un sólo click

+ +``` + +Para componer el contenido del mensaje utilizando un archivo, simplemente pasa el nombre de la vista al método `compose()`: + +```php +Yii::$app->mailer->compose('home-link') // el resultado del renderizado de la vista se transforma en el cuerpo del mensaje aquí + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Asunto del mensaje') + ->send(); +``` + +Puedes pasarle parámetros adicionales a la vista en el método `compose()`, los cuales estarán disponibles dentro de las vistas: + +```php +Yii::$app->mailer->compose('greetings', [ + 'user' => Yii::$app->user->identity, + 'advertisement' => $adContent, +]); +``` + +Puedes especificar diferentes archivos de vista para el contenido del mensaje en HTML y texto plano: + +```php +Yii::$app->mailer->compose([ + 'html' => 'contact-html', + 'text' => 'contact-text', +]); +``` + +Si especificas el nombre de la vista como un string, el resultado de su renderización será utilizado como cuerpo HTML, mientras +que el cuerpo en texto plano será compuesto removiendo todas las entidades HTML del anterior. + +El resultado de la renderización de la vista puede ser envuelta en el layout, que puede ser definido utiliazando [[yii\mail\BaseMailer::htmlLayout]] +y [[yii\mail\BaseMailer::textLayout]]. Esto funciona igual a como funcionan los layouts en una aplicación web normal. +El layout puede utilizar estilos CSS u otros contenidos compartidos: + +```php + +beginPage() ?> + + + + + + head() ?> + + + beginBody() ?> + + + endBody() ?> + + +endPage() ?> +``` + + +Adjuntar archivos +----------------- + +Puedes adjuntar archivos al mensaje utilizando los métodos `attach()` y `attachContent()`: + +```php +$message = Yii::$app->mailer->compose(); + +// Adjunta un archivo del sistema local de archivos: +$message->attach('/path/to/file.pdf'); + +// Crear adjuntos sobre la marcha +$message->attachContent('Contenido adjunto', ['fileName' => 'attach.txt', 'contentType' => 'text/plain']); +``` + + +Incrustar imágenes +------------------ + +Puedes incrustar imágenes en el mensaje utilizando el método `embed()`. Este método devuelve el id del adjunto, +que debería ser utilizado como tag 'img'. +Este método es fácil de utilizar al componer mensajes a través de un archivo de vista: + +```php +Yii::$app->mailer->compose('embed-email', ['imageFileName' => '/path/to/image.jpg']) + // ... + ->send(); +``` + +Entonces, dentro de tu archivo de vista, puedes utilizar el siguiente código: + +```php + +``` + + +Testear y depurar +----------------- + +Un desarrollador a menudo necesita comprobar qué emails están siendo enviados por la aplicación, cuál es su contenido y otras cosas. +Yii concede dicha habilidad vía `yii\mail\BaseMailer::useFileTransport`. Si se habilita, esta opción hace que +los datos del mensaje sean guardados en archivos locales en vez de enviados. Esos archivos serán guardados bajo +`yii\mail\BaseMailer::fileTransportPath`, que por defecto es '@runtime/mail'. + +> Note: puedes o bien guardar los mensajes en archivos, o enviarlos a sus receptores correspondientes, pero no puedes hacer las dos cosas al mismo tiempo. + +Un archivo de mensaje puede ser abierto por un editor de texto común, de modo que puedas ver sus cabeceras, su contenido y demás. +Este mecanismo en sí puede comprobarse al depurar la aplicación o al ejecutar un test de unidad. + +> Note: el archivo de contenido de mensaje es compuesto vía `\yii\mail\MessageInterface::toString()`, por lo que depende de la extensión + actual de correo utilizada en tu aplicación. + + +Crear tu solución personalizada de correo +----------------------------------------- + +Para crear tu propia solución de correo, necesitas crear 2 clases: una para 'Mailer' y +otra para 'Message'. +Puedes utilizar `yii\mail\BaseMailer` y `yii\mail\BaseMessage` como clases base de tu solución. Estas clases +ya contienen un lógica básica, la cual se describe en esta guía. De cualquier modo, su utilización no es obligatoria, es suficiente +con implementar las interfaces `yii\mail\MailerInterface` y `yii\mail\MessageInterface`. +Luego necesitas implementar todos los métodos abstractos para construir tu solución. diff --git a/docs/guide-es/tutorial-start-from-scratch.md b/docs/guide-es/tutorial-start-from-scratch.md new file mode 100644 index 0000000000..cd810fff00 --- /dev/null +++ b/docs/guide-es/tutorial-start-from-scratch.md @@ -0,0 +1,55 @@ +Crear tu propia estructura de Aplicación +======================================== + +> Note: Esta sección se encuentra en desarrollo. + +Mientras que los templates de proyectos [basic](https://github.com/yiisoft/yii2-app-basic) y [advanced](https://github.com/yiisoft/yii2-app-advanced) +son grandiosos para la mayoría de tus necesidades, podrías querer crear tu propio template de proyecto del cual +partir todos tus proyectos. + +Los templates de proyectos en Yii son simplemente repositorios conteniendo un archivo `composer.json`, y registrado como un paquete de Composer. +Cualquier repositorio puede ser identificado como paquete Composer, haciéndolo instalable a través del comando de Composer `create-project`. + +Dado que es un poco demasiado comenzar tu template de proyecto desde cero, es mejor utilizar uno de los +templates incorporados como una base. Utilicemos el template básico aquí. + +Clonar el Template Básico +------------------------- + +El primer paso es clonar el template básico de Yii desde su repositorio Git: + +```bash +git clone git@github.com:yiisoft/yii2-app-basic.git +``` + +Entonces espera que el repositorio sea descargado a tu computadora. Dado que los cambios realizados al template no serán enviados al repositorio, puedes eliminar el directorio `.git` +y todo su contenido de la descarga. + +Modificar los Archivos +---------------------- + +A continuación, querrás modificar el archivo `composer.json` para que refleje tu template. Cambia los valores de `name`, `description`, `keywords`, `homepage`, `license`, y `support` +de forma que describa tu nuevo template. También ajusta las opciones `require`, `require-dev`, `suggest`, y demás para que encajen con los requerimientos de tu template. + +> Note: En el archivo `composer.json`, utiliza el parámetro `writable` (bajo `extra`) para especificar +> permisos-por-archivo a ser definidos después de que la aplicación es creada a partir del template. + +Luego, pasa a modificar la estructura y contenido de la aplicación como te gustaría que sea por defecto. Finalmente, actualiza el archivo README para que sea aplicable a tu template. + +Hacer un Paquete +---------------- + +Con el template definido, crea un repositorio Git a partir de él, y sube tus archivos ahí. Si tu template va a ser de código abierto, [Github](http://github.com) es el mejor lugar para alojarlo. Si tu intención es que el template no sea colaborativo, cualquier sitio de repositorios Git servirá. + +Ahora, necesitas registrar tu paquete para Composer. Para templates públicos, el paquete debe ser registrado en [Packagist](https://packagist.org/). +Para templates privados, es un poco más complicado registrarlo. Puedes ver instrucciones para hacerlo en la [documentación de Composer](https://getcomposer.org/doc/05-repositories.md#hosting-your-own). + +Utilizar el Template +-------------------- + +Eso es todo lo que se necesita para crear un nuevo template de proyecto Yii. Ahora puedes crear tus propios proyectos a partir de este template: + +``` +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project +``` diff --git a/docs/guide-es/tutorial-template-engines.md b/docs/guide-es/tutorial-template-engines.md new file mode 100644 index 0000000000..addcf8ff50 --- /dev/null +++ b/docs/guide-es/tutorial-template-engines.md @@ -0,0 +1,49 @@ +Usar motores de plantillas +========================== + +Por defecto, Yii utiliza PHP como su lenguaje de plantilla, pero puedes configurar Yii para que soporte otros motores de renderizado, tal como +[Twig](http://twig.sensiolabs.org/) o [Smarty](http://www.smarty.net/), disponibles como extensiones. + +El componente `view` es el responsable de renderizar las vistas. Puedes agregar un motor de plantillas personalizado reconfigurando +el comportamiento (behavior) de este componente: + +```php +[ + 'components' => [ + 'view' => [ + 'class' => 'yii\web\View', + 'renderers' => [ + 'tpl' => [ + 'class' => 'yii\smarty\ViewRenderer', + //'cachePath' => '@runtime/Smarty/cache', + ], + 'twig' => [ + 'class' => 'yii\twig\ViewRenderer', + 'cachePath' => '@runtime/Twig/cache', + // Array de opciones de Twig: + 'options' => [ + 'auto_reload' => true, + ], + 'globals' => ['html' => '\yii\helpers\Html'], + 'uses' => ['yii\bootstrap'], + ], + // ... + ], + ], + ], +] +``` + +En el código de arriba, tanto Smarty como Twig son configurados para ser utilizables por los archivos de vista. Pero para tener ambas extensiones en tu proyecto, también necesitas modificar +tu archivo `composer.json` para incluirlos: + +``` +"yiisoft/yii2-smarty": "*", +"yiisoft/yii2-twig": "*", +``` +Ese código será agregado a la sección `require` de `composer.json`. Después de realizar ese cambio y guardar el archivo, puedes instalar estas extensiones ejecutando `composer update --prefer-dist` en la línea de comandos. + +Para más detalles acerca del uso concreto de cada motor de plantillas, visita su documentación: + +- [Guía de Twig](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) +- [Guía de Smarty](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) diff --git a/docs/guide-es/tutorial-yii-integration.md b/docs/guide-es/tutorial-yii-integration.md index a5864854a3..20739f1918 100644 --- a/docs/guide-es/tutorial-yii-integration.md +++ b/docs/guide-es/tutorial-yii-integration.md @@ -1,23 +1,27 @@ -Trabajando con código de terceros -================================= +Trabajar con código de terceros +=============================== -De tiempo en tiempo, puede necesitar usar algún código de terceros en sus aplicaciones Yii. O puedes querer usar Yii como una librería en otros sistemas de terceros. En esta sección, te enseñaremos cómo conseguir estos objetivos. +De tiempo en tiempo, puede necesitar usar algún código de terceros en sus aplicaciones Yii. O puedes querer +utilizar Yii como una librería en otros sistemas de terceros. En esta sección, te enseñaremos cómo conseguir estos objetivos. -## Usando librerías de terceros en Yii - -Para usar una librería en una aplicación Yii, primeramente debes de asegurarte que las clases een la librería son incluidas adecuadamente o pueden ser cargadas de forma automática. +Utilizar librerías de terceros en Yii +------------------------------------- +Para usar una librería en una aplicación Yii, primeramente debes de asegurarte que las clases en la librería +son incluidas adecuadamente o pueden ser cargadas de forma automática. ### Usando Paquetes de Composer Muchas librerías de terceros son liberadas en términos de paquetes [Composer](https://getcomposer.org/). -Puedes instalar este tipo de librerias siguiendo dos sencillos pasos: +Puedes instalar este tipo de librerías siguiendo dos sencillos pasos: 1. modificar el fichero `composer.json` de tu aplicación y especificar que paquetes Composer quieres instalar. -2. ejecuta `composer install` para instalar los paquetes específicados. +2. ejecuta `composer install` para instalar los paquetes especificados. -Las clases en los paquetes Composer instalados pueden ser autocargados usando el cargador automatizado de Composer autoloader. Asegúrate que el fichero [script de entrada](structure-entry-scripts.md) de tu aplicación contiene las siguientes líneas para instalar el cargador automático de Composer: +Las clases en los paquetes Composer instalados pueden ser autocargados usando el cargador automatizado de Composer autoloader. +Asegúrate que el fichero [script de entrada](structure-entry-scripts.md) de tu aplicación contiene las siguientes líneas +para instalar el cargador automático de Composer: ```php // instalar el cargador automático de Composer @@ -27,15 +31,21 @@ require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); ``` - ### Usando librerías Descargadas Si la librería no es liberada como un paquete de Composer, debes de seguir sus instrucciones de instalación para instalarla. -En muchos casos, puedes necesitar descargar manualmente el fichero de la versión y desempaquetarlo en el directorio `BasePath/vendor` , donde `BasePath` representa el [camino base (base path)](structure-applications.md#basePath) de tu aplicación. +En muchos casos, puedes necesitar descargar manualmente el fichero de la versión y desempaquetarlo en el directorio `BasePath/vendor`, +donde `BasePath` representa el [camino base (base path)](structure-applications.md#basePath) de tu aplicación. -Si la librería lleva su propio cargador automático (autoloader), puedes instalarlo en [script de entrada](structure-entry-scripts.md) de tu aplicación. Es recomendable que la instalación se termine antes de incluir el fichero `Yii.php` de forma que el cargador automático tenga precedencia al cargar de forma automática las clases. +Si la librería lleva su propio cargador automático (autoloader), puedes instalarlo en [script de entrada](structure-entry-scripts.md) de tu aplicación. +Es recomendable que la instalación se termine antes de incluir el fichero `Yii.php` de forma que el cargador automático tenga precedencia al cargar +de forma automática las clases. -Si la librería no provee un cargador automático de clases, pero la denominación de sus clases sigue el [PSR-4](http://www.php-fig.org/psr/psr-4/), puedes usar el cargador automático de Yii para cargar de forma automática las clases. Todo lo que necesitas es declarar un [alias raiz](concept-aliases.md#defining-aliases) para cada espacio de nombres (namespace) raiz usado en sus clases. Por ejemplo, asume que has instalado una librería en el directorio `vendor/foo/bar`, y que las clases de la librería están bajo el espacio de nombres raiz `xyz`. Puedes incluir el siguiente código en la configuración de tu aplicación: +Si la librería no provee un cargador automático de clases, pero la denominación de sus clases sigue el [PSR-4](http://www.php-fig.org/psr/psr-4/), +puedes usar el cargador automático de Yii para cargar de forma automática las clases. Todo lo que necesitas +es declarar un [alias raíz](concept-aliases.md#defining-aliases) para cada espacio de nombres (namespace) raiz usado en sus clases. Por ejemplo, +asume que has instalado una librería en el directorio `vendor/foo/bar`, y que las clases de la librería están bajo el espacio de nombres raiz `xyz`. +Puedes incluir el siguiente código en la configuración de tu aplicación: ```php [ @@ -45,30 +55,61 @@ Si la librería no provee un cargador automático de clases, pero la denominaci ] ``` -Si ninguno de lo anterior es el caso, estaría bien que la librería dependa del camino de inclusión (include path) de configuración de PHP para localizar correctamente e incluir los ficheros de las clases. Simplemente siguiendo estas instrucciones de cómo configurar el camino de inclusión de PHP. +Si ninguno de lo anterior es el caso, estaría bien que la librería dependa del camino de inclusión (include path) de configuración de PHP +para localizar correctamente e incluir los ficheros de las clases. Simplemente siguiendo estas instrucciones de cómo configurar el camino de inclusión de PHP. -En el caso más grave en el que la librería necesite incluir cada uno de sus ficheros de clases, puedes usar el siguiente método para incluir las clases según se pidan: +En el caso más grave en el que la librería necesite incluir cada uno de sus ficheros de clases, puedes usar el siguiente método +para incluir las clases según se pidan: * Identificar que clases contiene la librería. -* Listar las clases y el camino a los ficheros correspondientes en `Yii::$classMap` en el script de entrada [script de entrada](structure-entry-scripts.md) de la aplicación. Por ejemplo, +* Listar las clases y el camino a los archivos correspondientes en `Yii::$classMap` en el script de entrada [script de entrada](structure-entry-scripts.md) + de la aplicación. Por ejemplo, ```php Yii::$classMap['Class1'] = 'path/to/Class1.php'; Yii::$classMap['Class2'] = 'path/to/Class2.php'; ``` -## Usando Yii en Sistemas de Terceros +Utilizar Yii en Sistemas de Terceros +------------------------------------ -Debido a que Yii provee muchas posibilidades excelentes, a veces puedes querer usar alguna de sus características para permitir el desarrollo o mejora de sistemas de terceros, como es WordPress, Joomla, o aplicaciones desarrolladas usando otros frameworks de PHP. Por ejemplo, puedes queres usar la clase [[yii\helpers\ArrayHelper]] o usar la característica [Active Record](db-active-record.md) en un sistema de terceros. Para lograr este objetivo, principalmente necesitas realizar dos pasos: instalar Yii , e iniciar Yii. +Debido a que Yii provee muchas posibilidades excelentes, a veces puedes querer usar alguna de sus características para permitir +el desarrollo o mejora de sistemas de terceros, como es WordPress, Joomla, o aplicaciones desarrolladas usando otros frameworks de PHP. +Por ejemplo, puedes querer utilizar la clase [[yii\helpers\ArrayHelper]] o usar la característica [Active Record](db-active-record.md) +en un sistema de terceros. Para lograr este objetivo, principalmente necesitas realizar dos pasos: +instalar Yii , e iniciar Yii. -Si el sistema de terceros usa Composer para manejar sus dependencias, simplemente ejecuta estos comandos para instalar Yii: +Si el sistema de terceros usa Composer para manejar sus dependencias, simplemente ejecuta estos comandos +para instalar Yii: -``` -composer require "yiisoft/yii2:*" -composer install + composer global require "fxp/composer-asset-plugin:~1.1.1" + composer require yiisoft/yii2 + composer install + +El primer comando instala el [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), +que permite administrar paquetes bower y npm a través de Composer. Incluso si sólo quieres utilizar la capa de base de datos +u otra característica de Yii no relacionada a assets, requiere que instales el paquete composer de Yii. + +Si quieres utilizar la [publicación de Assets de Yii](structure-assets.md) deberías agregar también la siguiente configuración +a la sección `extra` de tu `composer.json`: + +```json +{ + ... + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } +} ``` -En otro caso, puedes [descargar](http://www.yiiframework.com/download/) el fichero de la edición de Yii y desempaquetarla en el directorio `BasePath/vendor`. +Visita también la [sección de cómo instalar Yii](start-installation.md#installing-via-composer) para más información +sobre Composer y sobre cómo solucionar posibles problemas que surjan durante la instalación. + +En otro caso, puedes [descargar](http://www.yiiframework.com/download/) el archivo de la edición de Yii +y desempaquetarla en el directorio `BasePath/vendor`. Después, debes de modificar el script de entrada de sistema de terceros para incluir el siguiente código al principio: @@ -79,24 +120,32 @@ $yiiConfig = require(__DIR__ . '/../config/yii/web.php'); new yii\web\Application($yiiConfig); // No ejecutes run() aquí ``` -Como puedes ver, el código anterior es muy similar al que puedes ver en [script de entrada](structure-entry-scripts.md) de una aplicación típica. La única diferencia es que después de que se crea la instancia de la aplicación, el método `run()` no es llamado. Esto es así porque llamando a `run()`, Yii se haría cargo del control del flujo de trabajo del manejo de las peticiones, lo cual no es necesario en este caso por estar ya es manejado por la aplicación existente. +Como puedes ver, el código anterior es muy similar al que puedes ver en [script de entrada](structure-entry-scripts.md) +de una aplicación típica. La única diferencia es que después de que se crea la instancia de la aplicación, el método `run()` no es llamado. +Esto es así porque llamando a `run()`, Yii se haría cargo del control del flujo de trabajo del manejo de las peticiones, +lo cual no es necesario en este caso por estar ya es manejado por la aplicación existente. -Como en una aplicación Yii, debes configurar la instancia de la aplicación basándose en el entorno que se está ejecutando del sistema de terceros. Por ejemplo, para usar la característica [Active Record](db-active-record.md) , necesitas configurar `db` [componente de la aplicación](structure-application-components.md) con los parámetros de la conexión de base de datos usados por el sistema de terceros. +Como en una aplicación Yii, debes configurar la instancia de la aplicación basándose en el entorno que se está +ejecutando del sistema de terceros. Por ejemplo, para usar la característica [Active Record](db-active-record.md), necesitas configurar +el [componente de la aplicación](structure-application-components.md) `db` con los parámetros de la conexión a la BD del sistema de terceros. -Ahora puedes usar muchas características provistas por Yii. Por ejemplo, puedes crear clases Active Record y usarlas para trabajar con bases de datos. +Ahora puedes usar muchas características provistas por Yii. Por ejemplo, puedes crear clases Active Record y usarlas +para trabajar con bases de datos. -## Usando Yii 2 con Yii 1 - -Si estaba usando Yii 1 previamente, es como si tuvieras una aplicación Yii 1 funcionando. En vez de reescribir toda la aplicación en Yii 2, puedes solamente mejorarla usando alguna de las características sólo disponibles en Yii 2. +Utilizar Yii 2 con Yii 1 +------------------------ +Si estaba usando Yii 1 previamente, es como si tuvieras una aplicación Yii 1 funcionando. En vez de reescribir +toda la aplicación en Yii 2, puedes solamente mejorarla usando alguna de las características sólo disponibles en Yii 2. Esto se puede lograr tal y como se describe abajo. -> Nota: Yii 2 requiere PHP 5.4 o superior. Debes de estar seguro que tanto tu servidor como la aplicación existente lo soportan. +> Note: Yii 2 requiere PHP 5.4 o superior. Debes de estar seguro que tanto tu servidor como la aplicación +> existente lo soportan. Primero, instala Yii 2 en tu aplicación siguiendo las instrucciones descritas en la [última subsección](#using-yii-in-others). -Segundo,modifica el script de entrada de la aplicación como sigue, +Segundo, modifica el script de entrada de la aplicación como sigue, ```php // incluir la clase Yii personalizada descrita debajo @@ -112,7 +161,6 @@ Yii::createWebApplication($yii1Config)->run(); ``` Debido a que ambos Yii 1 y Yii 2 tiene la clase `Yii` , debes crear una versión personalizada para combinarlas. - El código anterior incluye el fichero con la clase `Yii` personalizada, que tiene que ser creada como sigue. ```php @@ -128,15 +176,17 @@ class Yii extends \yii\BaseYii } Yii::$classMap = include($yii2path . '/classes.php'); -// registrar el autoloader de Yii2 autoloader via Yii1 +// registrar el autoloader de Yii 2 vía Yii 1 Yii::registerAutoloader(['Yii', 'autoload']); // crear el contenedor de inyección de dependencia Yii::$container = new yii\di\Container; ``` -¡Esto es todo!. Ahora, en cualquier parte de tu código, puedes usar `Yii::$app` para acceder a la instancia de la aplicación de Yii 2, mientras `Yii::app()` proporciona la instancia de la aplicación de Yii 1 : +¡Esto es todo!. Ahora, en cualquier parte de tu código, puedes usar `Yii::$app` para acceder a la instancia de la aplicación de Yii 2, +mientras `Yii::app()` proporciona la instancia de la aplicación de Yii 1 : ```php echo get_class(Yii::app()); // genera 'CWebApplication' echo get_class(Yii::$app); // genera 'yii\web\Application' ``` + diff --git a/docs/guide-es/widget-bootstrap.md b/docs/guide-es/widget-bootstrap.md deleted file mode 100644 index 5672a199dc..0000000000 --- a/docs/guide-es/widget-bootstrap.md +++ /dev/null @@ -1,63 +0,0 @@ -Widgets de Bootstrap -==================== - -> Nota: Esta sección está bajo desarrollo. - -Yii incluye soporta las marcas y componentes del framework [Bootstrap 3](http://getbootstrap.com/) (también conocido como "Twitter Bootstrap"). Bootstrap es un excelente, adaptable framework que puede aumentar la velocidad de desarrollo de los procesos del lado del cliente. - -El núcleo de Bootstrap está representado en dos partes: - -- Elementos básicos de CSS, como son un sistema de diseño en formato cuadrícula , tipografía, clases de ayuda (helpers), y utilidades adaptables(responsive). - -- Componentes preparados para su uso, tales como formularios, menús, paginación, cajas modales, pestañas, etc - -Elementos básicos ------------------ - -Yii no hace uso de elementos básicos de boostrap en el código PHP ya que HTML es muy simple por sí mismo, en este caso. Puedes encontrar detalle del uso de estos elementos básicos en [sitio web de la documentación de bootstrap](http://getbootstrap.com/css/). Aún así Yii provee una manera conveniente de incluir los elementos básicos de los recursos de bootstrap en tus páginas con una simple línea añadida a `AppAsset.php` localizada en tu directorio `@app/assets` : - -```php -public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', // Esta línea -]; -``` - -Usar bootstrap a través de el gestor de recursos Yii te permite minimizar estos recursos y combinar con tus propios recursos cuando sea necesario.. - -Widgets de Yii --------------- - -Componentes más complejos de bootstrap components están envueltos dentro de widgets de Yii para permitir una sintaxis más robusta e integrar con las posibilidades y características del framework. Todos los widgets pertenecen al espacio de nombres `\yii\bootstrap` : - -- [[yii\bootstrap\ActiveForm|ActiveForm]] -- [[yii\bootstrap\Alert|Alert]] -- [[yii\bootstrap\Button|Button]] -- [[yii\bootstrap\ButtonDropdown|ButtonDropdown]] -- [[yii\bootstrap\ButtonGroup|ButtonGroup]] -- [[yii\bootstrap\Carousel|Carousel]] -- [[yii\bootstrap\Collapse|Collapse]] -- [[yii\bootstrap\Dropdown|Dropdown]] -- [[yii\bootstrap\Modal|Modal]] -- [[yii\bootstrap\Nav|Nav]] -- [[yii\bootstrap\NavBar|NavBar]] -- [[yii\bootstrap\Progress|Progress]] -- [[yii\bootstrap\Tabs|Tabs]] - - -Usando los ficheros .less de Bootstrap directamente ---------------------------------------------------- - -Si quieres incluir el [CSS Bootstrap directamente en tus ficheros less](http://getbootstrap.com/getting-started/#customizing) puedes necesitar desactivar la carga los ficheros css originales de bootstrap. -Esto lo puedes hacer poniendo la propiedad css de [[yii\bootstrap\BootstrapAsset|BootstrapAsset]] vacía. -Para esto necesitas configurar el `assetManager` [componente de la aplicación](structure-application-components.md) como sigue: - -```php - 'assetManager' => [ - 'bundles' => [ - 'yii\bootstrap\BootstrapAsset' => [ - 'css' => [], - ] - ] - ] -``` diff --git a/docs/guide-es/widget-jui.md b/docs/guide-es/widget-jui.md deleted file mode 100644 index d42bf1f322..0000000000 --- a/docs/guide-es/widget-jui.md +++ /dev/null @@ -1,27 +0,0 @@ -Widgets de Jquery UI -==================== - -> Nota: Esta sección está en desarrollo. - -Además de lo anterior, Yii incluye soporte para la librería jquery [jQuery UI](http://api.jqueryui.com/). jQuery UI es un probado conjunto de interacciones con el interface de usuario, efectos, widgets, y temas sobre la librería JavaScript de jquery. - -widgets de Yii --------------- - -Los componentes más complejos de jQuery UI están envueltos dentro de los widgets de Yii para permitir una sintaxis más robusta e integralas con las características del framework. Todos los widgets pertenecen al espacio de nombre `\yii\jui` : - -- [[yii\jui\Accordion|Accordion]] -- [[yii\jui\AutoComplete|AutoComplete]] -- [[yii\jui\DatePicker|DatePicker]] -- [[yii\jui\Dialog|Dialog]] -- [[yii\jui\Draggable|Draggable]] -- [[yii\jui\Droppable|Droppable]] -- [[yii\jui\Menu|Menu]] -- [[yii\jui\ProgressBar|ProgressBar]] -- [[yii\jui\Resizable|Resizable]] -- [[yii\jui\Selectable|Selectable]] -- [[yii\jui\Slider|Slider]] -- [[yii\jui\SliderInput|SliderInput]] -- [[yii\jui\Sortable|Sortable]] -- [[yii\jui\Spinner|Spinner]] -- [[yii\jui\Tabs|Tabs]] \ No newline at end of file diff --git a/docs/guide-fr/concept-aliases.md b/docs/guide-fr/concept-aliases.md new file mode 100644 index 0000000000..fc20fdd59f --- /dev/null +++ b/docs/guide-fr/concept-aliases.md @@ -0,0 +1,106 @@ +Les Alias +========= +Les alias sont utilisés pour représenter des chemins de fichier ou des URLs de sorte que vous n'ayez pas à spécifier des chemins ou des URLs explicitement dans votre projet. Un alias doit commencer par le caractère `@` de façon à le différencier des chemins de fichiers habituels et des URLs. Yii dispose déjà d'un nombre important d'alias prédéfinis. Par exemple, l'alias `@yii` représéente le chemin d'installation du framework Yii; `@web` représente l'URL de base pour l'application web courante. + + + +Définir des alias +----------------- + +Vous pouvez définir un alias soit pour un chemin de fichier ou pour une URL en appelant [[Yii::setAlias()]]: + +```php +// un alias pour un chemin de fichier +Yii::setAlias('@foo', '/path/to/foo'); + +// un alias pour une URL +Yii::setAlias('@bar', 'http://www.example.com'); +``` +> Note: le chemin de fichier ou l'URL cible de l'alias *ne* doit *pas* nécessairement référencer un fichier ou une ressource existante. + +Etant donné un alias défini, il est possible de faire dériver un nouvel alias (sans appeler la commande [[Yii::setAlias()]]) en ajoutant une barre oblique `/` suivi d'un ou de plusieurs segments de chemin de fichier. Les alias définis via la commande [[Yii::setAlias()]] sont des *alias racines*, les alias qui en dérivent sont des *alias dérivés*. Par example, `@foo` est un alias racine, tandis que `@foo/bar/file.php` est un alias dérivé. + +Il est possible de définir une alias en utilisant un autre alias (qu'il soit racine ou dérivé): + +```php +Yii::setAlias('@foobar', '@foo/bar'); +``` + +Les alias racines sont habituellement définit pendant l'étape d'[amorçage](runtime-bootstrapping.md). Vous pouvez par exemple appeler la commande [[Yii::setAlias()]] dans le [script d'entrée](structure-entry-scripts.md). Pour plus de commodité, [Application](structure-applications.md) propose une propriété modifiable appelée `aliases` que vous pouvez définir dans la [configuration](concept-configurations.md) de l'application: + +```php +return [ + // ... + 'aliases' => [ + '@foo' => '/chemin/vers/foo', + '@bar' => 'http://www.example.com', + ], +]; +``` + +Résolution des alias +-------------------- + +Vous pouvez appeler la méthode [[Yii::getAlias()]] pour obtenir le chemin de fichier ou l'URL qu'un alias représente. La même méthode peut aussi convertir des alias dérivés dans leur chemin de fichier ou URL correspondants: + +```php +echo Yii::getAlias('@foo'); // displays: /path/to/foo +echo Yii::getAlias('@bar'); // displays: http://www.example.com +echo Yii::getAlias('@foo/bar/file.php'); // displays: /path/to/foo/bar/file.php +``` + +Le chemin/URL représenté par un alias dérivé est déterminé en renplaçant la partie alias racine avec son chemain/URL correspondant dans l'alias dérivé. +> Note: La méthode [[Yii::getAlias()]] ne vérifie pas si le chemin/URL obtenu représente un fichier ou une ressource existante. + +Un alias racine peut également conctenir des barres obliques `/`. La méthode [[Yii::getAlias()]] est suffisement intelligeante pour déterminer quelle part de l'alias est un alias racine et donc détermine correctement le chemin de fichier ou l'url correspondant: + +```php +Yii::setAlias('@foo', '/chemin/vers/foo'); +Yii::setAlias('@foo/bar', '/chemin2/bar'); +Yii::getAlias('@foo/test/file.php'); // affiche /chemin/vers/foo/test/file.php +Yii::getAlias('@foo/bar/file.php'); // affiche /chemin2/bar/file.php +``` + +Si `@foo/bar` n'est pas défini comme un alias racine, le dernier exemple affichierait `/chemin/vers/foo/bar/file.php`. + + +Utilisation des alias +---------------------- + +Les alias sont reconnus en de nombreux endroits de Yii sans avoir besoin d'appeler la méthode [[Yii::getAlias()]] pour les convertir en chemin ou URLs. A titre d'exemple, la méthode [[yii\caching\FileCache::cachePath]] accepte aussi bien un chemin de fichier et un alias représentant un chemin de fichier, grâce au préfixe `@` qui permet de différencier le chemin de fichier d'un alias. + +```php +use yii\caching\FileCache; + +$cache = new FileCache([ + 'cachePath' => '@runtime/cache', +]); +``` +Merci de porter attention à la documentation de l'API pour vérifier si une propriété ou un paramètre d'une méthode supporte les alias. + + +Alias prédéfinis +---------------- +Yii définit une série d'alias pour faciliter le référencement des chemins de fichier et URLs souvent utilisés: + +- `@yii`, le répertoire où se situe le fichier `BaseYii.php` (aussi appelé le répertoire framework). +- `@app`, le [[yii\base\Application::basePath|chemin de base]] de l'application courante. +- `@runtime`, le [[yii\base\Application::runtimePath|le chemin d'exécution]] de l'application courante. Valeur par défaut: `@app/runtime`. +- `@webroot`, La répertoire web racine de l'application web courante. It is determined based on the directory + containing the [entry script](structure-entry-scripts.md). +- `@web`, l'url de base de l'application courante. Cet alias a la même valeur que la propriété [[yii\web\Request::baseUrl]]. +- `@vendor`, le [[yii\base\Application::vendorPath|Le répertoire vendor de Composer]]. Valeur par défaut: `@app/vendor`. +- `@bower`, le répertoire racine qui contient [les paquets bower](http://bower.io/). Valeur par défaut: `@vendor/bower`. +- `@npm`, le répertoire racine qui contient [les paquets npm](https://www.npmjs.org/). Valeur par défaut: `@vendor/npm`. + +L'alias `@yii` est défini quand le fichier `Yii.php`est inclu dans votre [script d'entrée](structure-entry-scripts.md). Le reste des alias sont définit dans le constructeur de l'application au moment ou la [configuration](concept-configurations.md) de cette dernière est appliquée + +Alias d'extension +----------------- + +Un alias est automatiquement définit pour chaque [extension](structure-extensions.md) installée via Composer. +Chacun de ces alias est nommé par l'espace de nom (namespace) racine de l'extension tel que déclaré dans son fichier `composer.json`, et chacun pointe sur le répertoire racine du paquet. Par exemple, si vous installez l'extension `yiisoft/yii2-jui`, vous obtiendrez automatiquement un alias `@yii/jui` défini pendant la [phase d'amorçage](runtime-bootstrapping.md), équivalent à + +```php +Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); +``` \ No newline at end of file diff --git a/docs/guide-fr/images/start-country-list.png b/docs/guide-fr/images/start-country-list.png index 6994da2103..375419414d 100644 Binary files a/docs/guide-fr/images/start-country-list.png and b/docs/guide-fr/images/start-country-list.png differ diff --git a/docs/guide-fr/intro-yii.md b/docs/guide-fr/intro-yii.md index f62ed46f6b..69f7b4818a 100644 --- a/docs/guide-fr/intro-yii.md +++ b/docs/guide-fr/intro-yii.md @@ -29,21 +29,29 @@ support cache multi-niveaux; et plus. profiter de son architecture extensible solide, afin d'utiliser ou développer des extensions redistribuables. - La haute performance est toujours un des principaux objectifs de Yii. +<<<<<<< HEAD <<<<<<< HEAD Yii n'est pas un one-man show, il est soutenu par une [solide équipe de développement du noyau][] ainsi que d'une grande communauté ======= Yii n'est pas un one-man show, il est soutenu par une [solide équipe de développement du noyau][about_yii] ainsi que d'une grande communauté >>>>>>> yiichina/master +======= +Yii n'est pas un one-man show, il est soutenu par une [solide équipe de développement du noyau][about_yii] ainsi que d'une grande communauté +>>>>>>> master avec de nombreux professionnels qui contribuent constamment au développement de Yii. L'équipe de développeurs de Yii garde un œil attentif sur les dernières tendances en développement Web, et sur ​​les meilleures pratiques et caractéristiques trouvées dans d'autres frameworks ou projets. Les meilleures pratiques et caractéristiques les plus pertinentes trouvées ailleurs sont régulièrement intégrées dans le code du noyau et utilisables via des interfaces simples et élégantes. +<<<<<<< HEAD <<<<<<< HEAD [solide équipe de développement du noyau]: http://www.yiiframework.com/about/ ======= [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Versions de Yii --------------- diff --git a/docs/guide-fr/start-databases.md b/docs/guide-fr/start-databases.md index 6be96986bf..d8f181c454 100644 --- a/docs/guide-fr/start-databases.md +++ b/docs/guide-fr/start-databases.md @@ -36,16 +36,16 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `country` VALUES ('AU','Australia',18886000); -INSERT INTO `country` VALUES ('BR','Brazil',170115000); -INSERT INTO `country` VALUES ('CA','Canada',1147000); -INSERT INTO `country` VALUES ('CN','China',1277558000); -INSERT INTO `country` VALUES ('DE','Germany',82164700); -INSERT INTO `country` VALUES ('FR','France',59225700); -INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `country` VALUES ('IN','India',1013662000); -INSERT INTO `country` VALUES ('RU','Russia',146934000); -INSERT INTO `country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` A ce niveau, vous avez une base de données appelée `yii2basic`, et dedans, une table `country` comportant trois colonnes, contenant dix lignes de données. diff --git a/docs/guide-fr/start-gii.md b/docs/guide-fr/start-gii.md index 7989174040..30dc24fffb 100644 --- a/docs/guide-fr/start-gii.md +++ b/docs/guide-fr/start-gii.md @@ -26,7 +26,9 @@ $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` diff --git a/docs/guide-fr/start-installation.md b/docs/guide-fr/start-installation.md index 4d9bf19bc0..d1c20231a0 100644 --- a/docs/guide-fr/start-installation.md +++ b/docs/guide-fr/start-installation.md @@ -1,11 +1,15 @@ Installer Yii ============= +<<<<<<< HEAD <<<<<<< HEAD Vous pouvez installer Yii de deux façons, en utilisant [Composer](http://getcomposer.org/) ou en téléchargeant une archive. ======= Vous pouvez installer Yii de deux façons, en utilisant [Composer](https://getcomposer.org/) ou en téléchargeant une archive. >>>>>>> yiichina/master +======= +Vous pouvez installer Yii de deux façons, en utilisant [Composer](https://getcomposer.org/) ou en téléchargeant une archive. +>>>>>>> master La première méthode est conseillée, étant donné qu'elle permet d'installer de nouvelles [extensions](extend-creating-extensions.md) ou de mettre à jour Yii en éxécutant simplement une commande. > Remarque : Contrairement à Yii 1, les installations standards de Yii 2 auront pour résultat le téléchargement et l'installation du framework, ainsi que d'un squelette d'application. @@ -17,11 +21,15 @@ Installer via Composer Si vous n'avez pas déjà installé Composer, vous pouvez le faire en suivant les instructions sur le site [getcomposer.org](https://getcomposer.org/download/). Sous Linux et Mac OS X, vous pouvez éxécuter les commandes : +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master +======= + curl -sS https://getcomposer.org/installer | php +>>>>>>> master mv composer.phar /usr/local/bin/composer Sous Windows, téléchargez et éxécutez [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). diff --git a/docs/guide-fr/structure-entry-scripts.md b/docs/guide-fr/structure-entry-scripts.md index 9f678252bb..63036ac056 100644 --- a/docs/guide-fr/structure-entry-scripts.md +++ b/docs/guide-fr/structure-entry-scripts.md @@ -17,10 +17,14 @@ Les scipts de démarrage effectuent principalement les tâches suivantes : * Définir des constantes globales; <<<<<<< HEAD +<<<<<<< HEAD * Enregistrer l'[autoloader Composer](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * Enregistrer l'[autoloader Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* Enregistrer l'[autoloader Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * Inclure le fichier de classe de [[Yii]]; * Charger la configuration de l'application; * Créer et configurer une instance d'[application](structure-applications.md); @@ -68,10 +72,6 @@ De même, le code qui suit est le code du script de démarrage d'une application defined('YII_DEBUG') or define('YII_DEBUG', true); -// fcgi doesn't have STDIN and STDOUT defined by default -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - // register Composer autoloader require(__DIR__ . '/vendor/autoload.php'); diff --git a/docs/guide-id/README.md b/docs/guide-id/README.md new file mode 100644 index 0000000000..57256a8568 --- /dev/null +++ b/docs/guide-id/README.md @@ -0,0 +1,198 @@ +Panduan Definitif Untuk Yii 2.0 +=============================== + +Tutorial ini dirilis di bawah [Persyaratan Dokumentasi Yii] (http://www.yiiframework.com/doc/terms/). + +Seluruh hak cipta dilindungi. + +2014 (c) Yii Software LLC. + + +Pengantar +------------ + +* [Tentang Yii] (intro-yii.md) +* [Upgrade dari Versi 1.1] (intro-upgrade-from-v1.md) + + +Mulai +--------------- + +* [Instalasi Yii] (start-installation.md) +* [Menjalankan Aplikasi] (start-workflow.md) +* [Mengatakan Hello] (start-hello.md) +* [Bekerja dengan Form] (start-forms.md) +* [Bekerja dengan Database] (start-databases.md) +* [Membuat Kode Otomatis dengan Gii] (start-gii.md) +* [Menatap ke Depan] (start-looking-ahead.md) + + +Struktur Aplikasi +--------------------- + +* [Tinjauan] (structure-overview.md) +* [Script Masuk] (structure-entry-scripts.md) +* [Aplikasi] (structure-applications.md) +* [Komponen Aplikasi] (structure-application-components.md) +* [Controller] (structure-controllers.md) +* [Model] (structure-models.md) +* [Views] (structure-views.md) +* [Modul] (structure-modules.md) +* [Filter] (structure-filters.md) +* [Widgets] (structure-widgets.md) +* [Aset] (structure-assets.md) +* [Ekstensi] (structure-extensions.md) + + +Penanganan Permintaan +----------------- + +* [Tinjauan] (runtime-overview.md) +* [Bootstrap] (runtime-bootstrapping.md) +* [Routing dan Pembuatan URL] (runtime-routing.md) +* [Permintaan] (runtime-requests.md) +* [Tanggapan] (runtime-responses.md) +* [Sesi dan Cookies] (runtime-sessions-cookies.md) +* [Penanganan Kesalahan] (runtime-handling-errors.md) +* [Logging] (runtime-logging.md) + + +Konsep Pokok +------------ + +* [Komponen] (concept-components.md) +* [Properti] (concept-properties.md) +* [Event] (concept-events.md) +* [Perilaku] (concept-behaviors.md) +* [Konfigurasi] (concept-configurations.md) +* [Alias] (concept-aliases.md) +* [Class Autoloading] (concept-autoloading.md) +* [Layanan Locator] (concept-service-locator.md) +* [Dependency Injection] (concept-di-container.md) + + +Bekerja dengan Database +---------------------- + +* [Data Access Objects] (db-dao.md): Menghubungkan ke database, query dasar, transaksi, dan manipulasi skema +* [Query Builder] (db-query-builder.md): Query database menggunakan lapisan abstraksi sederhana +* [Active Record] (db-active-record.md): ORM Active Record, mengambil dan memanipulasi catatan, dan mendefinisikan hubungan +* [Migrasi] (db-migrations.md): Terapkan kontrol versi untuk database Anda dalam lingkungan pengembangan tim +* [Sphinx] (https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide/README.md) +* [Redis] (https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/README.md) +* [MongoDB] (https://github.com/yiisoft/yii2-mongodb/blob/master/docs/guide/README.md) +* [ElasticSearch] (https://github.com/yiisoft/yii2-elasticsearch/blob/master/docs/guide/README.md) + + +Mendapatkan Data dari Pengguna +----------------------- + +* [Membuat Formulir] (input-forms.md) +* [Memvalidasi Masukan] (input-validation.md) +* [Mengunggah File] (input-file-upload.md) +* [Mengumpulkan Masukan Tabel] (input-tabular-input.md) +* [Mendapatkan Data untuk Beberapa Model] (input-multiple-models.md) + + +Menampilkan Data +--------------- + +* [Pemformatan Data] (output-formatting.md) +* [Pagination] (output-pagination.md) +* [Pengurutan] (output-sorting.md) +* [Penyedia Data] (output-data-providers.md) +* [Data Widget] (output-data-widgets.md) +* [Bekerja dengan Script Client] (output-client-scripts.md) +* [Tema] (output-theming.md) + + +Keamanan +-------- + +* [Otentikasi] (security-authentication.md) +* [Otorisasi] (security-authorization.md) +* [Bekerja dengan Kata Sandi] (security-passwords.md) +* [Otentikasi Klien] (https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) +* [Praktik Terbaik] (security-best-practices.md) + + +Caching +------- + +* [Tinjauan] (caching-overview.md) +* [Caching Data] (caching-data.md) +* [Caching Fragmen] (caching-fragment.md) +* [Caching Halaman] (caching-page.md) +* [Caching HTTP] (caching-http.md) + + +Layanan Web RESTful +-------------------- + +* [Quick Start] (rest-quick-start.md) +* [Sumber Daya] (rest-resources.md) +* [Controller] (rest-controllers.md) +* [Routing] (rest-routing.md) +* [Penformatan Respon] (rest-response-formatting.md) +* [Otentikasi] (rest-authentication.md) +* [Pembatasan Laju] (rest-rate-limiting.md) +* [Versi] (rest-versioning.md) +* [Penanganan Kesalahan] (rest-error-handling.md) + + +Alat Pengembangan +----------------- + +* [Debug Toolbar dan Debugger] (https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) +* [Membuat Kode Otomatis dengan Gii] (https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) +* ** TBD ** [Membuat API Documentation] (https://github.com/yiisoft/yii2-apidoc) + + +Pengujian +------- + +* [Tinjauan] (test-overview.md) +* [Persiapan Lingkungan Pengujian] (test-environment-setup.md) +* [Tes Satuan] (test-unit.md) +* [Tes Fungsional] (test-functional.md) +* [Tes Penerimaan] (test-acceptance.md) +* [Jadwal] (test-fixtures.md) + + +Topik Khusus +-------------- + +* [Cetakan Proyek Lanjutan] (https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) +* [Membangun Aplikasi dari Awal] (tutorial-start-from-scratch.md) +* [Console Commands] (tutorial-console.md) +* [Validator Inti] (tutorial-core-validators.md) +* [Internasionalisasi] (tutorial-i18n.md) +* [Mailing] (tutorial-mailing.md) +* [Penyetelan Performa] (tutorial-performance-tuning.md) +* [Lingkungan Shared Hosting] (tutorial-shared-hosting.md) +* [Template Engine] (tutorial-template-engines.md) +* [Bekerja dengan Kode Pihak Ketiga] (tutorial-yii-integration.md) + + +Widget +------- + +* GridView: ** TBD ** Link ke demo halaman +* ListView: ** TBD ** Link ke halaman demo +* DetailView: ** TBD ** Link ke halaman demo +* ActiveForm: ** TBD ** Link ke halaman demo +* Pjax: ** TBD ** Link ke demo halaman +* Menu: ** TBD ** Link ke halaman demo +* LinkPager: ** TBD ** Link ke halaman demo +* LinkSorter: ** TBD ** Link ke halaman demo +* [Bootstrap Widgets] (https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/README.md) +* [JQuery UI Widgets] (https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md) + + +Alat Bantu +--------- + +* [Tinjauan] (helper-overview.md) +* [ArrayHelper] (helper-array.md) +* [Html] (helper-html.md) +* [Url] (helper-url.md) diff --git a/docs/guide-id/images/application-lifecycle.graphml b/docs/guide-id/images/application-lifecycle.graphml new file mode 100644 index 0000000000..850863ab26 --- /dev/null +++ b/docs/guide-id/images/application-lifecycle.graphml @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Entry script (index.php or yii) + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Load application config + + + + + + + + + + + + + + + + + + + + + Create application instance + + + + + + + + + + Folder 5 + + + + + + + + + + + + + + + + preInit() + + + + + + + + + + + + + + + + + Register error handler + + + + + + + + + + + + + + + + + Configure application properties + + + + + + + + + + + + + + + + + init() + + + + + + + + + + + + + + + + + bootstrap() + + + + + + + + + + + + + + + + + + + + + + + Run application + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + EVENT_BEFORE_REQUEST + + + + + + + + + + + + + + + + + + + + + Handle request + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Resolve request into route and parameters + + + + + + + + + + + + + + + + + Create module, controller and action + + + + + + + + + + + + + + + + + Run action + + + + + + + + + + + + + + + + + + + EVENT_AFTER_REQUEST + + + + + + + + + + + + + + + + + Send response to end user + + + + + + + + + + + + + + + + + + + Complete request processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration array + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exit status + + + + + + + + + + + + + + + + diff --git a/docs/guide-id/images/application-lifecycle.png b/docs/guide-id/images/application-lifecycle.png new file mode 100644 index 0000000000..6a505ccefb Binary files /dev/null and b/docs/guide-id/images/application-lifecycle.png differ diff --git a/docs/guide-id/images/application-structure.graphml b/docs/guide-id/images/application-structure.graphml new file mode 100644 index 0000000000..f6fce488be --- /dev/null +++ b/docs/guide-id/images/application-structure.graphml @@ -0,0 +1,430 @@ + + + + + + + + + + + + + + + + + + + + + + + application +component + + + + + + + + + + + + + + + + + entry script + + + + + + + + + + + + + + + + + application + + + + + + + + + + + + + + + + + controller + + + + + + + + + + + + + + + + + filter + + + + + + + + + + + + + + + + + module + + + + + + + + + + + + + + + + + view + + + + + + + + + + + + + + + + + model + + + + + + + + + + + + + + + + + widget + + + + + + + + + + + + + + + + + asset bundle + + + + + + + + + + + + + + + + + 1:1 + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 1..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + diff --git a/docs/guide-id/images/application-structure.png b/docs/guide-id/images/application-structure.png new file mode 100644 index 0000000000..d3a5549888 Binary files /dev/null and b/docs/guide-id/images/application-structure.png differ diff --git a/docs/guide-id/images/rbac-access-check-1.graphml b/docs/guide-id/images/rbac-access-check-1.graphml new file mode 100644 index 0000000000..44078515cf --- /dev/null +++ b/docs/guide-id/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-id/images/rbac-access-check-1.png b/docs/guide-id/images/rbac-access-check-1.png new file mode 100644 index 0000000000..77ad551c26 Binary files /dev/null and b/docs/guide-id/images/rbac-access-check-1.png differ diff --git a/docs/guide-id/images/rbac-access-check-2.graphml b/docs/guide-id/images/rbac-access-check-2.graphml new file mode 100644 index 0000000000..c521d429ea --- /dev/null +++ b/docs/guide-id/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-id/images/rbac-access-check-2.png b/docs/guide-id/images/rbac-access-check-2.png new file mode 100644 index 0000000000..254f307a89 Binary files /dev/null and b/docs/guide-id/images/rbac-access-check-2.png differ diff --git a/docs/guide-id/images/rbac-access-check-3.graphml b/docs/guide-id/images/rbac-access-check-3.graphml new file mode 100644 index 0000000000..8747cee0da --- /dev/null +++ b/docs/guide-id/images/rbac-access-check-3.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-id/images/rbac-access-check-3.png b/docs/guide-id/images/rbac-access-check-3.png new file mode 100644 index 0000000000..1fdc0d935a Binary files /dev/null and b/docs/guide-id/images/rbac-access-check-3.png differ diff --git a/docs/guide-id/images/rbac-hierarchy-1.graphml b/docs/guide-id/images/rbac-hierarchy-1.graphml new file mode 100644 index 0000000000..927b416d61 --- /dev/null +++ b/docs/guide-id/images/rbac-hierarchy-1.graphml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-id/images/rbac-hierarchy-1.png b/docs/guide-id/images/rbac-hierarchy-1.png new file mode 100644 index 0000000000..7443fc7e71 Binary files /dev/null and b/docs/guide-id/images/rbac-hierarchy-1.png differ diff --git a/docs/guide-id/images/rbac-hierarchy-2.graphml b/docs/guide-id/images/rbac-hierarchy-2.graphml new file mode 100644 index 0000000000..b81887b0e0 --- /dev/null +++ b/docs/guide-id/images/rbac-hierarchy-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-id/images/rbac-hierarchy-2.png b/docs/guide-id/images/rbac-hierarchy-2.png new file mode 100644 index 0000000000..e77c5647c1 Binary files /dev/null and b/docs/guide-id/images/rbac-hierarchy-2.png differ diff --git a/docs/guide-id/images/request-lifecycle.graphml b/docs/guide-id/images/request-lifecycle.graphml new file mode 100644 index 0000000000..aed5293b17 --- /dev/null +++ b/docs/guide-id/images/request-lifecycle.graphml @@ -0,0 +1,834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + user + + + + + + + + + + + + + + + + + + + + model + + + + + + + + + + + + + + + + + database + + + + + + + + + + + + + + + + + + + + + + + + + + + + view + + + + + + + + + + + + + + + + + + + + controller + + + + + + + + + + Folder 1 + + + + + + + + + + + + + + + + create action + + + + + + + + + + + + + + + + + perform filters + + + + + + + + + + + + + + + + + + + + action + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + load model + + + + + + + + + + + + + + + + + render view + + + + + + + + + + + + + + + + + + + + + response component + + + + + + + + + + + + + + + + + request component + + + + + + + + + + + + + + + + + + + + application + + + + + + + + + + Folder 2 + + + + + + + + + + + + + + + + resolve route + + + + + + + + + + + + + + + + + create controller + + + + + + + + + + + + + + + + + + + + + + entry script + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + load app config + + + + + + + + + + + + + + + + + run application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11 + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + 4 + + + + + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + 8 + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="66px" viewBox="0 0 57 66" enable-background="new 0 0 57 66" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3799" y1="-2276.8809" x2="27.6209" y2="-2306.6792" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_13_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + <path fill="#2068A3" stroke="#2068A3" d="M28.106,33.487c-8.112,0-12.688,4.312-12.688,10.437c0,7.422,12.688,10.438,12.688,10.438 + s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.487,28.106,33.487z M26.288,53.051c0,0-7.135-2.093-8.805-7.201 + c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_2_" cx="14.2417" cy="9.1006" r="53.247" gradientTransform="matrix(1 0 0 -1 0.04 65.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#74AEEE"/> + <stop offset="1" style="stop-color:#2068A3"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#2068A3" stroke-miterlimit="10" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.022,7.807-14.022,7.807s-10.472-2.484-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path fill="#5491CF" stroke="#2068A3" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397c-0.514,1.027-1.669,4.084-1.669,5.148 + c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.335-2.36c-3.601-1.419-4.071-3.063-5.89-4.854 + C12.523,47.135,12.878,45,13.404,44.173z"/> + <path fill="#5491CF" stroke="#2068A3" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617c0.516,1.025,3.617,3.693,3.617,6.617 + c0,5.186-10.27,8.576-16.698,9.145c1.429,4.938,11.372,1.293,13.804-0.313c3.563-2.354,4.563-5.133,7.854-3.705 + C47.754,49.045,48.006,46.574,45.777,43.924z"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.623" cy="-2278.646" r="23.425" fx="23.0534" fy="-2281.1357" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="5761.7578" y1="11330.6484" x2="5785.3872" y2="11424.0977" gradientTransform="matrix(0.275 0 0 0.2733 -1558.9874 -3088.4209)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M27.958,6.333c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.083,13.952,36.271,6.268,27.958,6.333z"/> + <path id="Hair_Young_Brown_1_" fill="#CC9869" stroke="#99724F" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> + <path fill="#4B4B4B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M28.105,2 + C22.464,2,20.2,4.246,18.13,5.533C29.753,2.865,41.152,10.375,44.46,20.5C44.459,16.875,44.459,2,28.105,2z"/> + <path fill="#9B9B9B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M11.151,17.751 + C12.878,8.25,18.686,6.309,25.273,7.127C31.295,7.875,36.93,10.491,44.459,20.5C37.777,7.125,20.278-3.375,9.903,3.921 + C5.569,6.97,4.903,13.375,11.151,17.751z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + x="0px" y="0px" width="41px" height="48px" viewBox="-0.875 -0.887 41 48" enable-background="new -0.875 -0.887 41 48" + xml:space="preserve"> +<defs> +</defs> +<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-979.1445" x2="682.0508" y2="-979.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_1_)" d="M19.625,36.763C8.787,36.763,0,34.888,0,32.575v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,34.888,30.464,36.763,19.625,36.763z"/> +<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-973.1445" x2="682.0508" y2="-973.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_2_)" d="M19.625,36.763c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.927-18.396,3.927 + c-9.481,0-17.396-1.959-18.396-3.927l-1.229,2C0,34.888,8.787,36.763,19.625,36.763z"/> +<path fill="#3C89C9" d="M19.625,26.468c10.16,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.554,5.438 + c-12.125,0-18.467-2.484-19.541-4.918C-0.127,29.125,9.465,26.468,19.625,26.468z"/> +<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-965.6948" x2="682.0508" y2="-965.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_3_)" d="M19.625,23.313C8.787,23.313,0,21.438,0,19.125v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,21.438,30.464,23.313,19.625,23.313z"/> +<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-959.6948" x2="682.0508" y2="-959.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_4_)" d="M19.625,23.313c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926l-1.229,2C0,21.438,8.787,23.313,19.625,23.313z"/> +<path fill="#3C89C9" d="M19.476,13.019c10.161,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.555,5.438 + c-12.125,0-18.467-2.485-19.541-4.918C-0.277,15.674,9.316,13.019,19.476,13.019z"/> +<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-952.4946" x2="682.0508" y2="-952.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_5_)" d="M19.625,10.113C8.787,10.113,0,8.238,0,5.925v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,8.238,30.464,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-946.4946" x2="682.0508" y2="-946.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_6_)" d="M19.625,10.113c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926L0,5.925C0,8.238,8.787,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.0293" y1="-943.4014" x2="680.8223" y2="-943.4014" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<ellipse fill="url(#SVGID_7_)" cx="19.625" cy="3.926" rx="18.396" ry="3.926"/> +<path opacity="0.24" fill="#FFFFFF" enable-background="new " d="M31.04,45.982c0,0-4.354,0.664-7.29,0.781 + c-3.125,0.125-8.952,0-8.952,0l-2.384-10.292l0.044-2.108l-1.251-1.154L9.789,23.024l-0.082-0.119L9.5,20.529l-1.65-1.254 + L5.329,8.793c0,0,4.213,0.903,7.234,1.07s8.375,0.25,8.375,0.25l3,9.875l-0.25,1.313l1.063,2.168l2.312,9.645l-0.521,1.416 + l1.46,1.834L31.04,45.982z"/> +</svg> + + + + diff --git a/docs/guide-id/images/request-lifecycle.png b/docs/guide-id/images/request-lifecycle.png new file mode 100644 index 0000000000..f9ed032cec Binary files /dev/null and b/docs/guide-id/images/request-lifecycle.png differ diff --git a/docs/guide-id/images/start-app-installed.png b/docs/guide-id/images/start-app-installed.png new file mode 100644 index 0000000000..a70f3e687d Binary files /dev/null and b/docs/guide-id/images/start-app-installed.png differ diff --git a/docs/guide-id/images/start-country-list.png b/docs/guide-id/images/start-country-list.png new file mode 100644 index 0000000000..375419414d Binary files /dev/null and b/docs/guide-id/images/start-country-list.png differ diff --git a/docs/guide-id/images/start-entry-confirmation.png b/docs/guide-id/images/start-entry-confirmation.png new file mode 100644 index 0000000000..ed442a4c6e Binary files /dev/null and b/docs/guide-id/images/start-entry-confirmation.png differ diff --git a/docs/guide-id/images/start-form-validation.png b/docs/guide-id/images/start-form-validation.png new file mode 100644 index 0000000000..f38652871d Binary files /dev/null and b/docs/guide-id/images/start-form-validation.png differ diff --git a/docs/guide-id/images/start-gii-country-grid.png b/docs/guide-id/images/start-gii-country-grid.png new file mode 100644 index 0000000000..746663c8f9 Binary files /dev/null and b/docs/guide-id/images/start-gii-country-grid.png differ diff --git a/docs/guide-id/images/start-gii-country-update.png b/docs/guide-id/images/start-gii-country-update.png new file mode 100644 index 0000000000..51f2d38a32 Binary files /dev/null and b/docs/guide-id/images/start-gii-country-update.png differ diff --git a/docs/guide-id/images/start-gii-crud-preview.png b/docs/guide-id/images/start-gii-crud-preview.png new file mode 100644 index 0000000000..85c2355f2e Binary files /dev/null and b/docs/guide-id/images/start-gii-crud-preview.png differ diff --git a/docs/guide-id/images/start-gii-crud.png b/docs/guide-id/images/start-gii-crud.png new file mode 100644 index 0000000000..77c1ada18a Binary files /dev/null and b/docs/guide-id/images/start-gii-crud.png differ diff --git a/docs/guide-id/images/start-gii-model-preview.png b/docs/guide-id/images/start-gii-model-preview.png new file mode 100644 index 0000000000..080be64b1f Binary files /dev/null and b/docs/guide-id/images/start-gii-model-preview.png differ diff --git a/docs/guide-id/images/start-gii-model.png b/docs/guide-id/images/start-gii-model.png new file mode 100644 index 0000000000..59c16a477e Binary files /dev/null and b/docs/guide-id/images/start-gii-model.png differ diff --git a/docs/guide-id/images/start-gii.png b/docs/guide-id/images/start-gii.png new file mode 100644 index 0000000000..28c75b6b79 Binary files /dev/null and b/docs/guide-id/images/start-gii.png differ diff --git a/docs/guide-id/images/start-hello-world.png b/docs/guide-id/images/start-hello-world.png new file mode 100644 index 0000000000..30d1acad38 Binary files /dev/null and b/docs/guide-id/images/start-hello-world.png differ diff --git a/docs/guide-id/images/tutorial-console-help.png b/docs/guide-id/images/tutorial-console-help.png new file mode 100644 index 0000000000..6813a96bd4 Binary files /dev/null and b/docs/guide-id/images/tutorial-console-help.png differ diff --git a/docs/guide-id/intro-upgrade-from-v1.md b/docs/guide-id/intro-upgrade-from-v1.md new file mode 100644 index 0000000000..df26874d4c --- /dev/null +++ b/docs/guide-id/intro-upgrade-from-v1.md @@ -0,0 +1,540 @@ +Upgrade dari Versi 1.1 +========================== + +Ada banyak perbedaan antara versi 1.1 dan 2.0 karena Yii Framework benar-benar ditulis ulang di versi 2.0. +Akibatnya, upgrade dari versi 1.1 tidak mudah seperti upgrade untuk versi minor. Dalam panduan ini Anda akan +menemukan perbedaan utama antara dua versi. + +Jika Anda belum pernah menggunakan Yii 1.1 sebelumnya, Anda dapat dengan aman melewati bagian ini dan menuju ke "[Persiapan](start-installation.md)". + +Harap dicatat bahwa Yii 2.0 memperkenalkan lebih banyak fitur baru dari yang tercakup dalam ringkasan ini. Sangat dianjurkan +Anda membaca keseluruhan panduan definitif untuk mempelajari hal tersebut. Kemungkinannya adalah bahwa +beberapa fitur yang sebelumnya harus anda kembangkan sendiri kini menjadi bagian dari kode inti. + + +Instalasi +------------ + +Yii 2.0 sepenuhnya menggunakan [composer](https://getcomposer.org/), yaitu dependency manager yang sudah diakui oleh PHP. Instalasi +dari kerangka inti serta ekstensi, ditangani melalui Composer. Silakan merujuk ke +bagian [Instalasi Yii](start-installation.md) untuk belajar cara menginstal Yii 2.0. Jika Anda menghendaki +membuat ekstensi baru, atau mengubah ekstensi 1.1 yang sudah ke ekstensi 2.0 yang kompatibel, silakan +merujuk panduan [Membuat Ekstensi](structure-extensions.md#menciptakan-ekstensi). + + +Persyaratan PHP +---------------- + +Yii 2.0 membutuhkan PHP 5.4 atau versi lebih tinggi, yang merupakan perbaikan besar atas PHP versi 5.2 yang dibutuhkan oleh Yii 1.1. +Akibatnya, ada banyak perbedaan pada tingkat bahasa yang harus Anda perhatikan. +Di bawah ini adalah ringkasan perubahan utama mengenai PHP: + +- [Namespaces](http://php.net/manual/en/language.namespaces.php). +- [Anonymous fungsi](http://php.net/manual/en/functions.anonymous.php). +- Sintaks array pendek `[... elemen ...]` digunakan sebagai pengganti `array (... elemen ...)`. +- Tags echo pendek `<=` digunakan dalam tampilan file. Ini aman digunakan mulai dari PHP 5.4. +- [Class SPL dan interface](http://php.net/manual/en/book.spl.php). +- [Late Static Bindings](http://php.net/manual/en/language.oop5.late-static-bindings.php). +- [Tanggal dan Waktu](http://php.net/manual/en/book.datetime.php). +- [Traits](http://php.net/manual/en/language.oop5.traits.php). +- [Intl](http://php.net/manual/en/book.intl.php). Yii 2.0 menggunakan `ekstensi PHP intl` +  untuk mendukung fitur internasionalisasi. + + +Namespace +--------- + +Perubahan yang paling jelas dalam Yii 2.0 adalah penggunaan namespace. Hampir setiap kelas inti +menggunakan namespace, misalnya, `yii\web\Request`. Awalan "C" tidak lagi digunakan dalam nama kelas. +Skema penamaan sekarang mengikuti struktur direktori. Misalnya, `yii\web\Request` +menunjukkan bahwa file kelas yang sesuai adalah `web/Request.php` bawah folder framework Yii. + +(Anda dapat menggunakan setiap kelas inti tanpa menyertakannya secara eksplisit berkat Yiiclass loader.) + + +Komponen dan Object +-------------------- + +Yii 2.0 membagi kelas `CComponent` di 1.1 menjadi dua kelas: [[yii\base\Object]] dan [[yii\base\Component]]. +Class [[yii\base\Object|Object]] adalah class dasar ringan yang memungkinkan mendefinisikan [objek properti](concept-properties.md) +melalui getter dan setter. Class [[yii\base\Component|Component]] adalah perluasan dari [[yii\base\Object|Object]] dengan dukungan +[Event](concept-events.md) dan [behavior](concept-behaviors.md). + +Jika class Anda tidak perlu fitur event atau behavior, Anda harus mempertimbangkan menggunakan +[[yii\base\Object|Object]] sebagai class dasar. Hal ini biasanya terjadi untuk class yang mewakili +struktur data dasar. + + +Konfigurasi objek +-------------------- + +Class [[yii\base\Object|Object]] memperkenalkan cara seragam untuk mengkonfigurasi objek. Setiap class turunan +dari [[yii\base\Object|Object]] harus menyatakan konstruktor (jika diperlukan) dengan cara berikut agar +dapat dikonfigurasi dengan benar: + +```php +class MyClass extends \yii\base\Object +{ + public function __construct($param1, $param2, $config = []) + { + // ... inisialisasi sebelum konfigurasi diterapkan + + parent::__construct($config); + } + + public function init() + { + parent::init(); + + // ... inisialisasi setelah konfigurasi diterapkan + } +} +``` + +Dalam contoh di atas, parameter terakhir dari konstruktor harus mengambil array konfigurasi +yang berisi pasangan nama-nilai untuk menginisialisasi properti pada akhir konstruktor. +Anda dapat menimpa method [[yii\base\Object::init()|init()]] untuk melakukan pekerjaan inisialisasi yang harus dilakukan setelah +konfigurasi telah diterapkan. + +Dengan mengikuti konvensi ini, Anda akan dapat membuat dan mengkonfigurasi objek baru menggunakan array konfigurasi: + +```php +$object = Yii::createObject([ + 'class' => 'MyClass', + 'property1' => 'abc', + 'property2' => 'cde', +], [$param1, $param2]); +``` + +Rincian lebih lanjut tentang konfigurasi dapat ditemukan di bagian [Konfigurasi](concept-configurations.md). + + +Event +------ + +Di Yii 1, event dibuat dengan mendefinisikan method `on` (misalnya,`onBeforeSave`). Di Yii 2, Anda sekarang dapat menggunakan semua nama sebagai event. +Anda memicu suatu event dengan memanggil method [[yii\base\Component::trigger()|trigger()]]: + +```php +$event = new \yii\base\Event; +$component->trigger($eventName, $event); +``` + +Untuk melampirkan penanganan event, mengunakan method [[yii\base\Component::on()|on()]]: + +```php +$component->on($eventName, $handler); +// To detach the handler, use: +// $component->off($eventName, $handler); +``` + +Ada banyak pengembangan dari fitur event. Untuk lebih jelasnya, silakan lihat bagian [Event](concept-events.md). + + +Path Alias +------------ + +Yii 2.0 memperluas penggunaan alias path baik untuk file/direktori maupun URL. Yii 2.0 juga sekarang mensyaratkan +nama alias dimulai dengan karakter `@`. +Misalnya, alias `@yii` mengacu pada direktori instalasi Yii. Alias path +didukung di sebagian besar tempat di kode inti Yii. Misalnya, [[yii\caching\FileCache::cachePath]] dapat mengambil +baik alias path maupun direktori normal. + +Sebuah alias juga terkait erat dengan namespace kelas. Disarankan alias didefinisikan untuk setiap akar namespace, +sehingga memungkinkan Anda untuk menggunakan autoloader class Yii tanpa konfigurasi lebih lanjut. +Misalnya, karena `@yii` mengacu pada direktori instalasi Yii, class seperti `yii\web\Request` dapat otomatis diambil. +Jika Anda menggunakan librari pihak ketiga seperti Zend Framework. Anda dapat menentukan alias path `@Zend` yang mengacu pada +direktori instalasi framework direktori. Setelah Anda selesai melakukannya, Yii akan dapat menload setiap class dalam librari Zend Framework. + +Lebih jauh tentang alias path dapat ditemukan di bagian [Alias](concept-aliases.md). + + +View +----- + +Perubahan yang paling signifikan tentang view di Yii 2 adalah bahwa variabel khusus `$this` dalam sebuah view tidak lagi mengacu +controller saat ini atau widget. Sebaliknya, `$this` sekarang mengacu pada objek *view*, konsep baru +yang diperkenalkan di 2.0. Objek *view* adalah [[yii\web\View]], yang merupakan bagian view +dari pola MVC. Jika Anda ingin mengakses controller atau widget di tampilan, Anda dapat menggunakan `$this->context`. + +Untuk membuat tampilan parsial dalam view lain, Anda menggunakan `$this->render()`, tidak lagi `$this->renderPartial()`. +Panggilan untuk `render` juga sekarang harus secara eksplisit *di-echo)*, mengingat method `render()` sekarang mengembalikan nilai +yang dirender, bukan langsung menampilkannya. Sebagai contoh: + +```php +echo $this->render('_item', ['item' => $item]); +``` + +Selain menggunakan PHP sebagai bahasa template utama, Yii 2.0 juga dilengkapi dengan dukungan resmi +dua mesin template populer: Smarty dan Twig. Mesin template Prado tidak lagi didukung. +Untuk menggunakan mesin template ini, Anda perlu mengkonfigurasi komponen aplikasi `view` dengan menetapkan +properti [[yii\base\View::$renderers|View::$renderers]]. Silakan merujuk ke bagian [Template Engine](tutorial-template-engines.md) +untuk lebih jelasnya. + + +Model +------ + +Yii 2.0 menggunakan [[yii\base\Model]] sebagai model dasar, mirip dengan `CModel` di 1.1. +class `CFormModel` telah dibuang seluruhnya. Sebaliknya, di Yii 2 Anda harus memperluas [[yii\base\Model]] untuk membuat class model formulir. + +Yii 2.0 memperkenalkan metode baru yang disebut [[yii\base\Model::scenario()|scenario()]] untuk menyatakan +skenario yang didukung, dan untuk menunjukkan di mana skenario atribut perlu divalidasi serta atribut yang dapat dianggap sebagai aman atau tidak +dll Sebagai contoh: + +```php +public function scenarios() +{ + return [ + 'backend' => ['email', 'role'], + 'frontend' => ['email', '!role'], + ]; +} +``` + +Dalam contoh di atas, dua skenario dinyatakan: `backend` dan` frontend`. Untuk `skenario backend`, baik +atribut `email` maupun` role` aman dan dapat diassign secara masal. Untuk `skenario frontend`, +`email` dapat diassign secara masal sementara` role` tidak bisa. Kedua `email` dan` role` harus divalidasi sesuai aturan. + +Method [[yii\base\Model::rules()|rules()]] ini masih digunakan untuk menyatakan aturan validasi. Perhatikan bahwa dengan dikenalkannya +[[yii\base\Model::scenario()|scenario()]] sekarang tidak ada lagi validator `unsafe`. + +Dalam kebanyakan kasus, Anda tidak perlu menimpa [[yii\base\Model::scenario()|scenario()]] +jika method [[yii\base\Model::rules()|rules()]] sepenuhnya telah menentukan skenario yang akan ada dan jika tidak ada kebutuhan untuk menyatakan +atribut `unsafe`. + +Untuk mempelajari lebih lanjut tentang model, silakan merujuk ke bagian [Model](structure-models.md). + + +Controller +----------- + +Yii 2.0 menggunakan [[yii\web\Controller]] sebagai kelas dasar controller, yang mirip dengan `CController` di Yii 1.1. +[[Yii\base\Action]] adalah kelas dasar untuk kelas action. + +Dampak paling nyata dari perubahan ini pada kode Anda adalah bahwa aksi kontroler harus mengembalikan nilai konten +alih-alih menampilkannya: + +```php +public function actionView($id) +{ + $model = \app\models\Post::findOne($id); + if ($model) { + return $this->render('view', ['model' => $model]); + } else { + throw new \yii\web\NotFoundHttpException; + } +} +``` + +Silakan merujuk ke bagian [Controller](structure-controllers.md) untuk rincian lebih lanjut tentang controller. + + +widget +------- + +Yii 2.0 menggunakan [[yii\base\Widget]] sebagai kelas dasar widget, mirip dengan `CWidget` di Yii 1.1. + +Untuk mendapatkan dukungan yang lebih baik untuk kerangka di IDE, Yii 2.0 memperkenalkan sintaks baru untuk menggunakan widget. +Metode statis [[yii\base\Widget::begin()|begin()]], [[yii\base\Widget::end()|end()]], dan [[yii\base\Widget::widget()|widget()]] +mulai diperkenalkan, yang akan digunakan seperti: + +```php +use yii\widgets\Menu; +use yii\widgets\ActiveForm; + +// Note that you have to "echo" the result to display it +echo Menu::widget(['items' => $items]); + +// Passing an array to initialize the object properties +$form = ActiveForm::begin([ + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']], +]); +... form input fields here ... +ActiveForm::end(); +``` + +Silakan merujuk ke bagian [Widget](structure-widgets.md) untuk lebih jelasnya. + + +Tema +------ + +Tema bekerja benar-benar berbeda di 2.0. Mereka sekarang berdasarkan mekanisme pemetaan path yang memetakan +file sumber ke file tema. Misalnya, jika peta path untuk tema adalah +`['/web/views' => '/web/themes/basic']`, maka versi tema dari file view +`/web/views/site/index.php` akan menjadi `/web/themes/basic/site/index.php`. Untuk alasan ini, tema sekarang bisa +diterapkan untuk setiap file view, bahkan view diberikan di luar controller atau widget. + +Juga, tidak ada lagi komponen `CThemeManager`. Sebaliknya, `theme` adalah properti dikonfigurasi dari komponen `view` +aplikasi. + +Silakan merujuk ke bagian [Theming](output-theming.md) untuk lebih jelasnya. + + +Aplikasi konsol +-------------------- + +Aplikasi konsol sekarang diatur sebagai controller seperti pada aplikasi Web. kontroler konsol +harus diperluas dari [[yii\console\Controller]], mirip dengan `CConsoleCommand` di 1.1. + +Untuk menjalankan perintah konsol, menggunakan `yii `, di mana `` adalah rute kontroler +(Misalnya `sitemap/index`). Argumen anonim tambahan dilewatkan sebagai parameter ke +action controller yang sesuai, sedangkan argumen bernama diurai menurut +deklarasi pada [[yii\console\Controller::options()]]. + +Yii 2.0 mendukung pembuatan informasi bantuan command secara otomatis berdasarkan blok komentar. + +Silakan lihat bagian [Console Commands](tutorial-console.md) untuk lebih jelasnya. + + +I18N +---- + +Yii 2,0 menghilangkan formater tanggal dan angka terpasang bagian dari [PECL modul intl PHP](http://pecl.php.net/package/intl). + +Penterjemahan pesan sekarang dilakukan melalui komponen aplikasi `i18n`. +Komponen ini mengelola satu set sumber pesan, yang memungkinkan Anda untuk menggunakan pesan yang berbeda +sumber berdasarkan kategori pesan. + +Silakan merujuk ke bagian [Internasionalisasi](tutorial-i18n.md) untuk rincian lebih lanjut. + + +Action Filter +-------------- + +Action Filter sekarang diimplementasikan melalui behavior. Untuk membuat baru, filter diperluas dari [[yii\base\ActionFilter]]. +Untuk menggunakan filter, pasang Kelas filter untuk controller sebagai behavior. Misalnya, untuk menggunakan filter [[yii\filters\AccessControl]], +Anda harus mengikuti kode berikut di kontroler: + +```php +public function behaviors() +{ + return [ + 'access' => [ + 'class' => 'yii\filters\AccessControl', + 'rules' => [ + ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], + ], + ], + ]; +} +``` + +Silakan merujuk ke bagian [Filtering](structure-filters.md) untuk lebih jelasnya. + + +Aset +------ + +Yii 2.0 memperkenalkan konsep baru yang disebut *bundel aset* yang menggantikan konsep paket script di Yii 1.1. + +Bundel aset adalah kumpulan file asset (misalnya file JavaScript, file CSS, file gambar, dll) +dalam direktori. Setiap bundel aset direpresentasikan sebagai kelas turunan dari [[yii\web\AssetBundle]]. +Dengan mendaftarkan bundel aset melalui [[yii\web\AssetBundle::register()]], Anda membuat +aset dalam bundel diakses melalui Web. Tidak seperti di Yii 1, halaman yang mendaftarkan bundel akan secara otomatis +berisi referensi ke JavaScript dan file CSS yang ditentukan dalam bundel itu. + +Silakan merujuk ke bagian [Managing Aset](structure-assets.md) untuk lebih jelasnya. + + +Helper +------- + +Yii 2.0 memperkenalkan banyak helper umum untuk digunakan, termasuk. + +* [[yii\helpers\Html]] +* [[yii\helpers\ArrayHelper]] +* [[yii\helpers\StringHelper]] +* [[yii\helpers\FileHelper]] +* [[yii\helpers\Json]] + +Silakan lihat bagian [Tinjauan Helper](helper-overview.md) untuk lebih jelasnya. + +Formulir +-------- + +Yii 2.0 memperkenalkan konsep *field* untuk membangun formulir menggunakan [[yii\widgets\ActiveForm]]. Field +adalah wadah yang terdiri dari label, masukan, pesan kesalahan, dan atau teks petunjuk. +Field diwakili sebagai objek [[yii\widgets\ActiveField|ActiveField]]. +Menggunakan field, Anda dapat membangun formulir yang lebih bersih dari sebelumnya: + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> +
+ +
+ +``` + +Silakan merujuk ke bagian [Membuat Formulir](input-forms.md) untuk lebih jelasnya. + + +Query Builder +------------- + +Dalam 1.1, query builder itu tersebar di antara beberapa kelas, termasuk `CDbCommand`, +`CDbCriteria`, dan` CDbCommandBuilder`. Yii 2.0 merepresentasikan sebuah query DB sebagai objek [[yii\db\Query|Query]] +yang dapat berubah menjadi sebuah pernyataan SQL dengan bantuan [[yii\db\QueryBuilder|QueryBuilder]]. +Sebagai contoh: + +```php +$query = new \yii\db\Query(); +$query->select('id, name') + ->from('user') + ->limit(10); + +$command = $query->createCommand(); +$sql = $command->sql; +$rows = $command->queryAll(); +``` + +Yang terbaik dari semua itu adalah, query builder juga dapat digunakan ketika bekerja dengan [Active Record](db-active-record.md). + +Silakan lihat bagian [Query Builder](db-query-builder.md) untuk lebih jelasnya. + + +Active Record +------------- + +Yii 2.0 memperkenalkan banyak perubahan [Active Record](db-active-record.md). Dua yang paling jelas melibatkan +query builder dan penanganan permintaan relasional. + +Kelas `CDbCriteria` di 1.1 digantikan oleh [[yii\db\ActiveQuery]] di Yii 2. Karena kelas tersebut adalah perluasan dari [[yii\db\Query]], dengan demikian +mewarisi semua metode query builder. Anda bisa memanggil [[yii\db\ActiveRecord::find()]] untuk mulai membangun query: + +```php +// Untuk mengambil semua customer yang *active* diurutkan sesuai ID: +$customers = Customer::find() + ->where(['status' => $active]) + ->orderBy('id') + ->all(); +``` + +Untuk menyatakan suatu relasi, hanya dengan menentukan metod getter yang mengembalikan sebuah objek [[yii\db\ActiveQuery|ActiveQuery]]. +Nama properti yang didefinisikan oleh getter akan menjadi nama relasi. Misalnya, kode berikut mendeklarasikan +sebuah relasi `orders` (di 1.1, Anda akan harus menyatakan relasi di tempat `relations()`): + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getOrders() + { + return $this->hasMany('Order', ['customer_id' => 'id']); + } +} +``` + +Sekarang Anda dapat menggunakan `$customer->orders` untuk mengakses pesanan pelanggan dari tabel terkait. Anda juga dapat menggunakan kode berikut +untuk melakukan permintaan relasi secara cepat dengan kondisi permintaan yang disesuaikan: + +```php +$orders = $customer->getOrders()->andWhere('status=1')->all(); +``` + +Ketika ingin memuat relasi, Yii 2.0 melakukannya secara berbeda dari 1.1. Secara khusus, di 1.1 query JOIN +akan dibuat untuk memilih data utama dan data relasi. Di Yii 2.0, dua pernyataan SQL dijalankan +tanpa menggunakan JOIN: pernyataan pertama membawa kembali data utama dan yang kedua membawa kembali data relasi +dengan menyaring sesuai kunci primer dari data utama. + +Alih-alih mengembalikan objek [[yii\db\ActiveRecord|ActiveRecord]], Anda mungkin ingin menyambung dengan [[yii\db\ActiveQuery::asArray()|asArray()]] +ketika membangun query untuk mendapatkan sejumlah besar data. Hal ini akan menyebabkan hasil query dikembalikan +sebagai array, yang dapat secara signifikan mengurangi waktu CPU yang dibutuhkan dan memori jika terdapat sejumlah besar data. Sebagai contoh: + +```php +$customers = Customer::find()->asArray()->all(); +``` + +Perubahan lain adalah bahwa Anda tidak dapat menentukan nilai default atribut melalui properti publik lagi. +Jika Anda membutuhkan mereka, Anda harus mengatur mereka dalam metode init kelas record Anda. + +```php +public function init() +{ + parent::init(); + $this->status = self::STATUS_NEW; +} +``` + +Ada beberapa masalah dengan override konstruktor dari kelas ActiveRecord di 1.1. Ini tidak lagi hadir di +versi 2.0. Perhatikan bahwa ketika menambahkan parameter ke constructor Anda mungkin harus mengganti [[yii\db\ActiveRecord::instantiate()]]. + +Ada banyak perubahan lain dan perangkat tambahan untuk Rekaman Aktif. Silakan merujuk ke +bagian [Rekaman Aktif](db-active-record.md) untuk rincian lebih lanjut. + + +Active Record Behaviors +----------------------- + +Dalam 2.0, kami telah membuang kelas behavior dasar `CActiveRecordBehavior`. Jika Anda ingin membuat behavior Active Record, +Anda akan harus memperluasnya langsung dari `yii\base\Behavior`. Jika kelas behavior perlu menanggapi beberapa event +dari pemilik, Anda harus mengganti method `events()` seperti berikut ini, + +```php +namespace app\components; + +use yii\db\ActiveRecord; +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + // ... + + public function events() + { + return [ + ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + public function beforeValidate($event) + { + // ... + } +} +``` + + +Pengguna dan IdentityInterface +-------------------------- + +Kelas `CWebUser` di 1.1 kini digantikan oleh [[yii\web\User]], dan sekarang tidak ada lagi +Kelas `CUserIdentity`. Sebaliknya, Anda harus menerapkan [[yii\web\IdentityInterface]] yang +jauh lebih mudah untuk digunakan. Template proyek lanjutan memberikan contoh seperti itu. + +Silakan merujuk ke bagian [Otentikasi](security-authentication.md), [Otorisasi](security-authorization.md), +dan [Template Proyek Lanjutan](https://github.com/yiisoft/yii2-app-advanced/blob /master/docs/guide/README.md) untuk lebih jelasnya. + + +Manajemen URL +-------------- + +Manajemen URL di Yii 2 mirip dengan yang di 1.1. Tambahan utamanya adalah, sekarang manajemen URL mendukung opsional +parameter. Misalnya, jika Anda memiliki aturan dinyatakan sebagai berikut, maka akan cocok +baik dengan `post/popular` maupun `post/1/popular`. Dalam 1.1, Anda akan harus menggunakan dua aturan untuk mencapai +tujuan yang sama. + +```php +[ + 'pattern' => 'post//', + 'route' => 'post/index', + 'defaults' => ['page' => 1], +] +``` + +Silakan merujuk ke bagian [docs manajer Url](runtime-routing.md) untuk lebih jelasnya. + +Perubahan penting dalam konvensi penamaan untuk rute adalah bahwa nama-nama camelcase dari controller +dan action sekarang dikonversi menjadi huruf kecil di mana setiap kata dipisahkan oleh hypen, misal controller +id untuk `CamelCaseController` akan menjadi `camel-case`. +Lihat bagian tentang [Kontroler ID](structure-controllers.md#controller-ids) dan [Action ID](structure-controllers.md#action-ids) untuk lebih jelasnya. + + +Menggunakan Yii 1.1 dan 2.x bersama-sama +------------------------------ + +Jika Anda memiliki warisan kode Yii 1.1 yang ingin Anda gunakan bersama-sama dengan Yii 2.0, silakan lihat +bagian [Menggunakan Yii 1.1 dan 2.0 Bersama](tutorial-yii-integration.md). diff --git a/docs/guide-id/intro-yii.md b/docs/guide-id/intro-yii.md new file mode 100644 index 0000000000..f1dd73e35b --- /dev/null +++ b/docs/guide-id/intro-yii.md @@ -0,0 +1,52 @@ +Apa Itu Yii +=========== + +Yii adalah kerangka kerja PHP berkinerja tinggi, berbasis komponen yang digunakan untuk mengembangkan aplikasi web modern dengan cepat. +Nama Yii (diucapkan `Yee` atau `[ji:]`) yang berarti "sederhana dan berevolusi" dalam bahasa Cina. Hal ini dapat juga +dianggap sebagai singkatan **Yes It Is (Ya, Itu Dia)**! + + +Yii Terbaik untuk Apa? +--------------------- + +Yii adalah kerangka kerja pemrograman web umum, yang berarti bahwa hal itu dapat digunakan untuk mengembangkan semua jenis +aplikasi Web yang menggunakan PHP. Karena arsitektur berbasis komponen dan dukungan caching yang canggih, Yii sangat cocok untuk mengembangkan aplikasi skala besar seperti portal, forum, sistem manajemen konten (CMS), proyek e-commerce, layanan web REST, dan sebagainya. + + +Bagaimana jika Yii Dibandingkan dengan Frameworks lain? +------------------------------------------- + +Jika Anda sudah akrab dengan framework lain, Anda mungkin menghargai pengetahuan bagaimana Yii dibandingkan: + +- Seperti kebanyakan PHP framework, Yii mengimplementasikan pola arsitektur MVC (Model-View-Controller) dan mempromosikan kode organisasi berdasarkan pola itu. +- Yii mengambil filosofi bahwa kode harus ditulis dengan cara sederhana namun elegan. Yii tidak akan pernah mencoba untuk mendesain berlebihan terutama untuk mengikuti beberapa pola desain secara ketat. +- Yii adalah framework penuh yang menyediakan banyak fitur teruji dan siap pakai seperti: query builder dan ActiveRecord baik untuk relasional maupun NoSQL database; dukungan pengembangan API REST; dukungan caching banyak lapis dan masih banyak lagi. +- Yii sangat extensible. Anda dapat menyesuaikan atau mengganti hampir setiap bagian dari kode inti Yii. Anda juga bisa mengambil keuntungan dari arsitektur ekstensi Yii yang solid untuk menggunakan atau mengembangkan ekstensi untuk disebarkan kembali. +- Kinerja tinggi selalu menjadi tujuan utama dari Yii. + +Yii tidak dikerjakan oleh satu orang, Yii didukung oleh [tim pengembang inti yang kuat][about_yii], serta komunitas besar +profesional yang terus memberikan kontribusi bagi pengembangan Yii. Tim pengembang Yii +terus mengamati perkembangan tren terbaru Web, pada penerapan terbaik serta fitur yang +ditemukan dalam framework dan proyek lain. Penerapan terbaik yang paling relevan dan fitur yang ditemukan di tempat lain secara teratur +dimasukkan ke dalam kerangka inti dan menampakkannya melalui antarmuka yang sederhana dan elegan. + +[about_yii]: http://www.yiiframework.com/about/ + +Versi Yii +---------- + +Yii saat ini memiliki dua versi utama yang tersedia: 1.1 dan 2.0. Versi 1.1 adalah generasi lama dan sekarang dalam mode pemeliharaan. +Versi 2.0 adalah penulisan ulang lengkap dari Yii, mengadopsi teknologi dan protokol terbaru, termasuk composer, PSR, namespace, trait, dan sebagainya. +Versi 2.0 merupakan generasi framework yang sekarang dan terus menerima upaya pengembangan selama beberapa tahun ke depan. +Panduan ini terutama tentang versi 2.0. + + +Persyaratan dan Prasyarat +-------------------------- + +Yii 2.0 memerlukan PHP 5.4.0 atau versi lebih tinggi. Anda dapat menemukan persyaratan yang lebih rinci untuk setiap fitur +dengan menjalankan pengecek persyaratan yang diikutsertakan dalam setiap rilis Yii. + +Menggunakan Yii memerlukan pengetahuan dasar tentang pemrograman berorientasi objek (OOP), mengingat Yii adalah framework berbasis OOP murni. +Yii 2.0 juga memanfaatkan fitur terbaru dari PHP, seperti [namespace](http://www.php.net/manual/en/language.namespaces.php) dan [traits](http://www.php.net/manual/en/language.oop5.traits.php). +Memahami konsep-konsep ini akan membantu Anda lebih mudah memahami Yii 2.0. diff --git a/docs/guide-id/start-hello.md b/docs/guide-id/start-hello.md new file mode 100644 index 0000000000..f20a0439eb --- /dev/null +++ b/docs/guide-id/start-hello.md @@ -0,0 +1,143 @@ +Katakan Hello +============ + +Bagian ini menjelaskan cara membuat halaman "Hello" baru dalam aplikasi Anda. +Untuk mencapai tujuan ini, Anda akan membuat [action](structure-controllers.md#creating-actions) dan +sebuah [view](structure-views.md): + +* Aplikasi ini akan mengirimkan permintaan halaman ke `action`. +* Dan `action` pada gilirannya akan membuat tampilan yang menunjukkan kata "Hello" kepada pengguna akhir. + +Melalui tutorial ini, Anda akan belajar tiga hal: + +1. Cara membuat [action](structure-controllers.md#creating-actions) untuk menanggapi permintaan, +2. Cara membuat [view](structure-views.md) untuk menyusun konten respon, dan +3. bagaimana aplikasi mengirimkan permintaan ke [action](structure-controllers.md#creating-actions). + + +Membuat Action +--------------- + +Untuk tugas "Hello", Anda akan membuat [action](structure-controllers.md#creating-actions) `say` yang membaca +parameter `message` dari request dan menampilkan pesan bahwa kembali ke pengguna. Jika request +tidak memberikan parameter `message`, aksi akan menampilkan pesan "Hello". + +> Info: [Action](structure-controllers.md#creating-actions) adalah objek yang pengguna akhir dapat langsung merujuk ke +  eksekusi. Action dikelompokkan berdasarkan [controllers](structure-controllers.md). Hasil eksekusi +  action adalah respon yang pengguna akhir akan terima. + +Action harus dinyatakan di [controllers](structure-controllers.md). Untuk mempermudah, Anda mungkin +mendeklarasikan action `say` di` SiteController` yang ada. kontroler ini didefinisikan +dalam file kelas `controllers/SiteController.php`. Berikut adalah awal dari action baru: + +```php +render('say', ['message' => $message]); + } +} +``` + +Pada kode di atas, action `say` didefinisikan sebagai metode bernama` actionSay` di kelas `SiteController`. +Yii menggunakan awalan `action` untuk membedakan metode action dari metode non-action dalam kelas controller. +Nama setelah awalan `action` peta untuk ID tindakan ini. + +Untuk sampai pada penamaan action, Anda harus memahami bagaimana Yii memperlakukan ID action. ID action selalu +direferensikan dalam huruf kecil. Jika ID tindakan membutuhkan beberapa kata, mereka akan digabungkan dengan `tanda hubung` +(Mis, `create-comment`). nama metode aksi yang dipetakan ke ID tindakan diperoleh dengan menghapus tanda hubung apapun dari ID, +mengkapitalkan huruf pertama di setiap kata, dan awalan string yang dihasilkan dengan `action`. Sebagai contoh, +ID action `create-comment` sesuai dengan nama method action `actionCreateComment`. + +Metode action dalam contoh kita mengambil parameter `$message`, yang nilai defaultnya adalah `"Hello"` (persis +dengan cara yang sama Anda menetapkan nilai default untuk fungsi atau metode apapun argumen di PHP). Ketika aplikasi +menerima permintaan dan menentukan bahwa action `say` bertanggung jawab untuk penanganan request, aplikasi akan +mengisi parameter ini dengan parameter bernama sama yang ditemukan dalam request. Dengan kata lain, jika permintaan mencakup +a parameter `message` dengan nilai` "Goodbye" `, maka variabel `$message` dalam aksi akan ditugaskan nilai itu. + +Dalam metode action, [[yii\web\Controller::render()|render()]] dipanggil untuk membuat +sebuah [view](structure-views.md) dari file bernama `say`. Parameter `message` juga diteruskan ke view +sehingga dapat digunakan di sana. Hasil render dikembalikan dengan metode tindakan. Hasil yang akan diterima +oleh aplikasi dan ditampilkan kepada pengguna akhir di browser (sebagai bagian dari halaman HTML yang lengkap). + + +Membuat View +--------------- + +[View](structure-views.md) adalah skrip yang Anda tulis untuk menghasilkan konten respon. +Untuk "Hello" tugas, Anda akan membuat view `say` yang mencetak parameter `message` yang diterima dari metode aksi: + +```php + + +``` + +View `say` harus disimpan dalam file `views/site/say.php`. Ketika metode [[yii\web\Controller::render()|render()]] +disebut dalam tindakan, itu akan mencari file PHP bernama `views/ControllerID/ViewName.php`. + +Perhatikan bahwa dalam kode di atas, parameter `message` adalah di-[[yii\helpers\Html::encode()|HTML-encoded]] +sebelum dicetak. Hal ini diperlukan karena sebagai parameter yang berasal dari pengguna akhir, sangat rentan terhadap +[serangan Cross-site scripting (XSS)](http://en.wikipedia.org/wiki/Cross-site_scripting) dengan melekatkan +kode JavaScript berbahaya dalam parameter. + +Tentu, Anda dapat menempatkan lebih banyak konten di view `say`. konten dapat terdiri dari tag HTML, teks biasa, dan bahkan pernyataan PHP. +Nyatanya, view `say` hanyalah sebuah script PHP yang dijalankan oleh metode [[yii\web\Controller::render()|render()]]. +Isi dicetak oleh skrip view akan dikembalikan ke aplikasi sebagai hasil respon ini. Aplikasi ini pada gilirannya akan mengeluarkan hasil ini kepada pengguna akhir. + + +Trying it Out +------------- + +Setelah membuat action dan view, Anda dapat mengakses halaman baru dengan mengakses URL berikut: + +``` +http://hostname/index.php?r=site%2Fsay&message=Hello+World +``` + +![Hello World](images/start-hello-world.png) + +URL ini akan menghasilkan halaman yang menampilkan "Hello World". Halaman yang berbagi header dan footer yang sama dengan halaman aplikasi lainnya. + +Jika Anda menghilangkan parameter `message` dalam URL, Anda akan melihat tampilan halaman "Hello". Hal ini karena `message` dilewatkan sebagai parameter untuk metode `actionSay()`, dan ketika itu dihilangkan, +nilai default `"Hello"` akan digunakan sebagai gantinya. + +> Info: Halaman baru berbagi header dan footer yang sama dengan halaman lain karena metode [[yii\web\Controller::render()|render()]] +  otomatis akan menanamkan hasil view `say` kedalam apa yang disebut [layout](structure-views.md#layouts) yang dalam hal ini +  Kasus terletak di `views/layouts/main.php`. + +Parameter `r` di URL di atas memerlukan penjelasan lebih lanjut. Ini adalah singkatan dari [route](runtime-routing.md), sebuah ID unik aplikasi +yang mengacu pada action. format rute ini adalah `ControllerID/ActionID`. Ketika aplikasi menerima +permintaan, itu akan memeriksa parameter ini, menggunakan bagian `ControllerID` untuk menentukan kontroler +kelas harus dipakai untuk menangani permintaan. Kemudian, controller akan menggunakan bagian `ActionID` +untuk menentukan action yang harus dipakai untuk melakukan pekerjaan yang sebenarnya. Dalam contoh kasus ini, rute `site/say` +akan diselesaikan dengan kontroler kelas `SiteController` dan action `say`. Sebagai hasilnya, +metode `SiteController::actionSay()` akan dipanggil untuk menangani permintaan. + +> Info: Seperti action, kontroler juga memiliki ID yang unik mengidentifikasi mereka dalam sebuah aplikasi. +  ID kontroler menggunakan aturan penamaan yang sama seperti ID tindakan. nama kelas controller yang berasal dari +  kontroler ID dengan menghapus tanda hubung dari ID, memanfaatkan huruf pertama di setiap kata, +  dan suffixing string yang dihasilkan dengan kata `Controller`. Misalnya, controller ID `post-comment` berkorespondensi +  dengan nama kelas controller `PostCommentController`. + + +Ringkasan +------- + +Pada bagian ini, Anda telah menyentuh controller dan melihat bagian dari pola arsitektur MVC. +Anda menciptakan sebuah action sebagai bagian dari controller untuk menangani permintaan khusus. Dan Anda juga menciptakan view +untuk menulis konten respon ini. Dalam contoh sederhana ini, tidak ada model yang terlibat. Satu-satunya data yang digunakan adalah parameter `message`. + +Anda juga telah belajar tentang rute di Yii, yang bertindak sebagai jembatan antara permintaan pengguna dan tindakan controller. + +Pada bagian berikutnya, Anda akan belajar cara membuat model, dan menambahkan halaman baru yang berisi bentuk HTML. diff --git a/docs/guide-id/start-installation.md b/docs/guide-id/start-installation.md new file mode 100644 index 0000000000..d79556af96 --- /dev/null +++ b/docs/guide-id/start-installation.md @@ -0,0 +1,238 @@ +Instalasi Yii +============== + +Anda dapat menginstal Yii dalam dua cara, menggunakan [Composer](https://getcomposer.org/) paket manager atau dengan mengunduh file arsip. +Yang pertama adalah cara yang lebih disukai, karena memungkinkan Anda untuk menginstal [ekstensi](structure-extensions.md) baru atau memperbarui Yii dengan hanya menjalankan *command line*. + +Hasil instalasi standar Yii baik framework maupun template proyek keduanya akan terunduh dan terpasang. +Sebuah template proyek adalah proyek Yii yang menerapkan beberapa fitur dasar, seperti login, formulir kontak, dll. +Kode diatur dalam cara yang direkomendasikan. Oleh karena itu, dapat berfungsi sebagai titik awal yang baik untuk proyek-proyek Anda. +     +Dalam hal ini dan beberapa bagian berikutnya, kita akan menjelaskan cara menginstal Yii dengan apa yang disebut *Template Proyek Dasar* dan +bagaimana menerapkan fitur baru di atas template ini. Yii juga menyediakan template lain yang disebut +yang [Template Proyek Lanjutan](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) yang lebih baik digunakan dalam lingkungan pengembangan tim +untuk mengembangkan aplikasi dengan beberapa tingkatan. + +> Info: Template Proyek Dasar ini cocok untuk mengembangkan 90 persen dari aplikasi Web. Ini berbeda +  dari Template Proyek Lanjutan terutama dalam bagaimana kode mereka diatur. Jika Anda baru untuk Yii, kami sangat +  merekomendasikan Anda tetap pada Template Proyek Dasar untuk kesederhanaan dan fungsi yang cukup. + + +Menginstal melalui Komposer +----------------------- + +Jika Anda belum memiliki Composer terinstal, Anda dapat melakukannya dengan mengikuti petunjuk di +[getcomposer.org] (https://getcomposer.org/download/). Pada Linux dan Mac OS X, Anda akan menjalankan perintah berikut: + +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` + +Pada Windows, Anda akan mengunduh dan menjalankan [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). + +Silakan merujuk ke [Dokumentasi Composer](https://getcomposer.org/doc/) jika Anda menemukan +masalah atau ingin mempelajari lebih lanjut tentang penggunaan Composer. + +Jika Composer sudah terinstal sebelumnya, pastikan Anda menggunakan versi terbaru. Anda dapat memperbarui Komposer +dengan menjalankan `composer self-update`. + +Dengan Komposer diinstal, Anda dapat menginstal Yii dengan menjalankan perintah berikut di bawah folder yang terakses web: + +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` + +Perintah pertama menginstal [komposer aset Plugin](https://github.com/francoispluchino/composer-asset-plugin/) +yang memungkinkan mengelola bower dan paket npm melalui Composer. Anda hanya perlu menjalankan perintah ini +sekali untuk semua. Perintah kedua menginstal Yii dalam sebuah direktori bernama `basic`. Anda dapat memilih nama direktori yang berbeda jika Anda ingin. + +> Catatan: Selama instalasi, Composer dapat meminta login Github Anda. Ini normal karena Komposer +> Perlu mendapatkan cukup API rate-limit untuk mengambil informasi paket dari Github. Untuk lebih jelasnya, +> Silahkan lihat [Documentation Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +> Tip: Jika Anda ingin menginstal versi pengembangan terbaru dari Yii, Anda dapat menggunakan perintah berikut sebagai gantinya, +> Yang menambahkan [opsi stabilitas](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` +> +> Perhatikan bahwa versi pengembangan dari Yii tidak boleh digunakan untuk produksi karena kemungkinan dapat *merusak* kode Anda yang sedang berjalan. + + +Instalasi dari file Arsip +------------------------------- + +Instalasi Yii dari file arsip melibatkan tiga langkah: + +1. Download file arsip dari [yiiframework.com](http://www.yiiframework.com/download/). +2. Uraikan file yang didownload ke folder yang bisa diakses web. +3. Memodifikasi `config/web.php` dengan memasukkan kunci rahasia untuk `cookieValidationKey`. +   (Ini dilakukan secara otomatis jika Anda menginstal Yii menggunakan Composer): + + ```php + // !!! Isikan nilai key jika kosong - ini diperlukan oleh cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` + + +Pilihan Instalasi lainnya +-------------------------- + +Petunjuk instalasi di atas menunjukkan cara menginstal Yii, yang juga menciptakan aplikasi Web dasar yang bekerja di luar kotak. +Pendekatan ini adalah titik awal yang baik untuk sebagian besar proyek, baik kecil atau besar. Hal ini terutama cocok jika Anda hanya +mulai belajar Yii. + +Tetapi ada pilihan instalasi lain yang tersedia: + +* Jika Anda hanya ingin menginstal kerangka inti dan ingin membangun seluruh aplikasi dari awal, +  Anda dapat mengikuti petunjuk seperti yang dijelaskan dalam [Membangun Aplikasi dari Scratch](tutorial-start-from-scratch.md). +* Jika Anda ingin memulai dengan aplikasi yang lebih canggih, lebih cocok untuk tim lingkungan pengembangan, +  Anda dapat mempertimbangkan memasang [Template Lanjutan Proyek] (https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). + + +Memverifikasi Instalasi +-------------------------- + +Setelah instalasi selesai, baik mengkonfigurasi web server Anda (lihat bagian berikutnya) atau menggunakan +[Built-in web server PHP] (https://secure.php.net/manual/en/features.commandline.webserver.php) dengan menjalankan berikut +konsol perintah sementara dalam proyek `web` direktori: + +```bash +php yii serve +``` + +> Catatan: Secara default HTTP-server akan mendengarkan port 8080. Namun jika port yang sudah digunakan atau Anda ingin +melayani beberapa aplikasi dengan cara ini, Anda mungkin ingin menentukan port apa yang harus digunakan. Cukup tambahkan argumen --port: + +```bash +php yii serve --port = 8888 +``` + +Anda dapat menggunakan browser untuk mengakses aplikasi Yii yang diinstal dengan URL berikut: + +``` +http://localhost:8080/ +``` + +![Instalasi Sukses dari Yii](images/start-app-installed.png) + +Anda seharusnya melihat halaman "Congratulations!" di browser Anda. Jika tidak, periksa apakah instalasi PHP Anda memenuhi +persyaratan Yii. Anda dapat memeriksa apakah persyaratan minimumnya cocok dengan menggunakan salah satu pendekatan berikut: + +* Copy `/requirements.php` ke `/web/requirements.php` kemudian gunakan browser untuk mengakses melalui `http://localhost/requirements.php` +* Jalankan perintah berikut: + +  ```bash +  cd basic +  php requirements.php +  ``` + +Anda harus mengkonfigurasi instalasi PHP Anda sehingga memenuhi persyaratan minimal Yii. Yang paling penting, Anda +harus memiliki PHP versi 5.4 atau lebih. Anda juga harus menginstal [PDO PHP Ekstensi](http://www.php.net/manual/en/pdo.installation.php) +dan driver database yang sesuai (seperti `pdo_mysql` untuk database MySQL), jika aplikasi Anda membutuhkan database. + + +Konfigurasi Web Server +----------------------- + +> Info: Anda dapat melewati seksi ini untuk saat ini jika Anda hanya menguji sebuah Yii dengan niat +  penggelaran itu untuk server produksi. + +Aplikasi yang diinstal sesuai dengan petunjuk di atas seharusnya bekerja dengan baik +pada [Apache HTTP server](http://httpd.apache.org/) atau [Nginx HTTP server](http://nginx.org/), pada +Windows, Mac OS X, atau Linux yang menjalankan PHP 5.4 atau lebih tinggi. Yii 2.0 juga kompatibel dengan facebook +[HHVM](http://hhvm.com/). Namun, ada beberapa kasus di mana HHVM berperilaku berbeda dari PHP asli, +sehingga Anda harus mengambil beberapa perlakuan ekstra ketika menggunakan HHVM. + +Pada server produksi, Anda mungkin ingin mengkonfigurasi server Web Anda sehingga aplikasi dapat diakses +melalui URL `http://www.example.com/index.php` bukannya `http://www.example.com/dasar/web/index.php`. konfigurasi seperti itu +membutuhkan root dokumen server Web Anda menunjuk ke folder `basic/web`. Anda mungkin juga +ingin menyembunyikan `index.php` dari URL, seperti yang dijelaskan pada bagian [Routing dan Penciptaan URL](runtime-routing.md). +Dalam bagian ini, Anda akan belajar bagaimana untuk mengkonfigurasi Apache atau Nginx server Anda untuk mencapai tujuan tersebut. + +> Info: Dengan menetapkan `basic/web` sebagai akar dokumen, Anda juga mencegah pengguna akhir mengakses +kode private aplikasi Anda dan file data sensitif yang disimpan dalam direktori sejajar +dari `basic/web`. Mencegah akses ke folder lainnya adalah sebuah peningkatan keamanan. + +> Info: Jika aplikasi Anda akan berjalan di lingkungan shared hosting di mana Anda tidak memiliki izin +untuk memodifikasi konfigurasi server Web-nya, Anda mungkin masih menyesuaikan struktur aplikasi Anda untuk keamanan yang lebih baik. Silakan merujuk ke +yang lebih baik. Lihat bagian [Shared Hosting Lingkungan](tutorial-shared-hosting.md) untuk rincian lebih lanjut. + + +### Konfigurasi Apache yang Direkomendasikan + +Gunakan konfigurasi berikut di file `httpd.conf` Apache atau dalam konfigurasi virtual host. Perhatikan bahwa Anda +harus mengganti `path/to/basic/web` dengan path ` dasar/web` yang sebenarnya. + +```apache +# Set document root to be "basic/web" +DocumentRoot "path/to/basic/web" + + + # use mod_rewrite for pretty URL support + RewriteEngine on + # If a directory or a file exists, use the request directly + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Otherwise forward the request to index.php + RewriteRule . index.php + + # ...other settings... + +``` + + +### Konfigurasi Nginx yang Direkomendasikan + +Untuk menggunakan [Nginx](http://wiki.nginx.org/), Anda harus menginstal PHP sebagai [FPM SAPI](http://php.net/install.fpm). +Anda dapat menggunakan konfigurasi Nginx berikut, menggantikan `path/to/basic/web` dengan path yang sebenarnya untuk +`basic/web` dan `mysite.local` dengan hostname yang sebenarnya untuk server. + +```nginx +server { + charset utf-8; + client_max_body_size 128M; + + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 + + server_name mysite.local; + root /path/to/basic/web; + index index.php; + + access_log /path/to/basic/log/access.log; + error_log /path/to/basic/log/error.log; + + location / { + # Redirect everything that isn't a real file to index.php + try_files $uri $uri/ /index.php$is_args$args; + } + + # uncomment to avoid processing of calls to non-existing static files by Yii + #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + # try_files $uri =404; + #} + #error_page 404 /404.html; + + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; + #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; + } + + location ~ /\.(ht|svn|git) { + deny all; + } +} +``` + +Bila menggunakan konfigurasi ini, Anda juga harus menetapkan `cgi.fix_pathinfo=0` di file` php.ini` +untuk menghindari banyak panggilan `stat()` sistem yang tidak perlu. + +Sekalian catat bahwa ketika menjalankan server HTTPS, Anda perlu menambahkan `fastcgi_param HTTPS on;` sehingga Yii +benar dapat mendeteksi jika sambungan aman. \ No newline at end of file diff --git a/docs/guide-id/start-workflow.md b/docs/guide-id/start-workflow.md new file mode 100644 index 0000000000..eaa19b566b --- /dev/null +++ b/docs/guide-id/start-workflow.md @@ -0,0 +1,102 @@ +Menjalankan Aplikasi +==================== + +Setelah menginstal Yii, Anda memiliki aplikasi Yii yang dapat diakses melalui +URL `http://hostname/basic/web/index.php` atau `http://hostname/index.php`, tergantung +pada konfigurasi Anda. Bagian ini akan memperkenalkan fungsi built-in aplikasi, +bagaimana kode ini disusun, dan bagaimana aplikasi menangani permintaan secara umum. + +> Info: Untuk mempermudah, selama tutorial "Mulai", itu diasumsikan bahwa Anda telah menetapkan `basic/web` +  sebagai root dokumen server Web Anda, dan URL dikonfigurasi untuk mengakses +  aplikasi Anda untuk menjadi `http://hostname/index.php` atau sesuatu yang serupa. +  Untuk kebutuhan Anda, silakan menyesuaikan URL sesuai deskripsi kami. +   +Perhatikan bahwa tidak seperti framework itu sendiri, setelah template proyek diinstal, itu semua milikmu. Anda bebas untuk menambah atau menghapus +kode dan memodifikasi keseluruhannya sesuai yang Anda butuhkan. + + +Fungsi +------------- + +Aplikasi dasar diinstal berisi empat halaman: + +* Homepage, ditampilkan saat Anda mengakses URL `http://hostname/index.php`, +* Halaman "About", +* Halaman "Contact", yang menampilkan formulir kontak yang memungkinkan pengguna akhir untuk menghubungi Anda melalui email, +* Dan halaman "Login", yang menampilkan form login yang dapat digunakan untuk otentikasi pengguna akhir. Cobalah masuk +  dengan "admin/admin", dan Anda akan menemukan item "Login" di menu utama akan berubah menjadi "Logout". + +Halaman ini berbagi header umum dan footer. header berisi menu bar utama untuk memungkinkan navigasi +antara halaman yang berbeda. + +Anda juga harus melihat toolbar di bagian bawah jendela browser. +Ini adalah [debugger tool](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) yang disediakan oleh Yii +untuk merekam dan menampilkan banyak informasi debug, seperti log pesan, status respon, query database berjalan, dan sebagainya. + +Selain itu untuk aplikasi web, ada script konsol yang disebut `yii`, yang terletak di direktori aplikasi dasar. +Script ini dapat digunakan untuk menjalankan aplikasi background dan tugas pemeliharaan untuk aplikasi, yang diuraikan +di bagian [Console Application](tutorial-console.md). + + +Struktur aplikasi +--------------------- + +Direktori yang paling penting dan file dalam aplikasi Anda (dengan asumsi direktori root aplikasi adalah `basic`): + +``` +basic/ path aplikasi dasar + composer.json digunakan oleh Composer, package information + config/ berisi konfigurasi aplikasi dan yang lain + console.php konfigurasi aplikasi konsole + web.php konfigurasi aplikasi web + commands/ contains console command classes + controllers/ contains controller classes + models/ contains model classes + runtime/ contains files generated by Yii during runtime, such as logs and cache files + vendor/ contains the installed Composer packages, including the Yii framework itself + views/ contains view files + web/ application Web root, contains Web accessible files + assets/ contains published asset files (javascript and css) by Yii + index.php the entry (or bootstrap) script for the application + yii the Yii console command execution script +``` + +Secara umum, file dalam aplikasi dapat dibagi menjadi dua jenis: mereka yang di bawah `basic/web` dan mereka yang +di bawah direktori lain. Yang pertama dapat langsung diakses melalui HTTP (yaitu, di browser), sedangkan yang kedua tidak dapat dan tidak seharusnya boleh. + +Yii mengimplementasikan pola arsitektur [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller), +yang tercermin dalam organisasi direktori di atas. Direktori `models` berisi semua [Model kelas](structure-models.md), +direktori `views` berisi semua [view script] structure-views.md), dan direktori `controllers` mengandung +semua [kelas kontroler](structure-controllers.md). + +Diagram berikut memperlihatkan struktur statis dari sebuah aplikasi. + +![Struktur statis aplikasi](images/application-structure.png) + +Setiap aplikasi memiliki naskah entri `web/index.php` yang merupakan satu-satunya PHP skrip dalam aplikasi yang dapat diakses web. +Naskah entri mengambil permintaan masuk dan menciptakan [aplikasi](structure-applications.md) untuk menanganinya. +[Aplikasi](structure-applications.md) menyelesaikan permintaan dengan bantuan [komponen](concept-components.md)nya, +dan mengirimkan permintaan ke elemen MVC. [Widget](structure-widgets.md) digunakan dalam [view](structure-views.md) +untuk membantu membangun elemen antarmuka pengguna yang kompleks dan dinamis. + + +Daur Hidup Request +----------------- + +Diagram berikut menunjukkan bagaimana aplikasi menangani permintaan. + +![Request Lifecycle](images/request-lifecycle.png) + +1. Pengguna membuat permintaan ke [skrip entri](structure-entry-scripts.md) `web/index.php`. +2. Naskah entri memuat [konfigurasi](concept-configurations.md) aplikasi dan menciptakan +   [aplikasi](structure-applications.md) untuk menangani permintaan. +3. Aplikasi menyelesaikan [route](runtime-routing.md) yang diminta dengan bantuan +   komponen [request](runtime-requests.md) aplikasi. +4. Aplikasi ini menciptakan [kontroler](structure-controllers.md) untuk menangani permintaan. +5. Controller menciptakan [action](structure-controllers.md) dan melakukan filter untuk action. +6. Jika filter gagal, aksi dibatalkan. +7. Jika semua filter lulus, aksi dieksekusi. +8. Action memuat model data, mungkin dari database. +9. Aksi meyiapkan view, menyediakannya dengan model data. +10. Hasilnya diberikan dikembalikan ke komponen aplikasi [respon](runtime-responses.md). +11. Komponen respon mengirimkan hasil yang diberikan ke browser pengguna. \ No newline at end of file diff --git a/docs/guide-it/README.md b/docs/guide-it/README.md index 2b7773e53c..d936ba34a5 100644 --- a/docs/guide-it/README.md +++ b/docs/guide-it/README.md @@ -99,11 +99,15 @@ Ricezione dati dagli utenti Visualizzazione dei dati ------------------------ +<<<<<<< HEAD <<<<<<< HEAD * **TBD** [Formattazione](output-formatter.md) ======= * **TBD** [Formattazione](output-formatting.md) >>>>>>> yiichina/master +======= +* **TBD** [Formattazione](output-formatting.md) +>>>>>>> master * **TBD** [Paginazione](output-pagination.md) * **TBD** [Ordinamento](output-sorting.md) * [Data Provider](output-data-providers.md) diff --git a/docs/guide-it/intro-yii.md b/docs/guide-it/intro-yii.md index b766d08fe4..887a037562 100644 --- a/docs/guide-it/intro-yii.md +++ b/docs/guide-it/intro-yii.md @@ -29,21 +29,29 @@ Se hai già familiarità con altri framework potrai apprezzare questi punti in c sfuttare la solida architettura delle estensioni di Yii per usare o sviluppare estensioni ridistribuibili. - Le prestazioni elevate sono sempre il focus primario di Yii. +<<<<<<< HEAD <<<<<<< HEAD Yii non è frutto di un uomo solo, ma è supportato da un [folto gruppo di sviluppatori][], così come da una numerosa ======= Yii non è frutto di un uomo solo, ma è supportato da un [folto gruppo di sviluppatori][about_yii], così come da una numerosa >>>>>>> yiichina/master +======= +Yii non è frutto di un uomo solo, ma è supportato da un [folto gruppo di sviluppatori][about_yii], così come da una numerosa +>>>>>>> master comunità di professionisti che contribuiscono costantemente allo sviluppo. Il gruppo di sviluppatori tiene sempre sott'occhio le ultime tendenze e tecnologie di sviluppo web, sulle pratiche ottimali e funzionalità degli altri framework e progetti. Le peculiarità più rilevanti che si trovano altrove sono regolarmente incorporate nel codice principale del framework, e rese disponibili tramite semplici ed eleganti interfacce. +<<<<<<< HEAD <<<<<<< HEAD [folto gruppo di sviluppatori]: http://www.yiiframework.com/about/ ======= [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Versioni di Yii --------------- diff --git a/docs/guide-it/start-installation.md b/docs/guide-it/start-installation.md index 61228ed1bf..9508f8a0b8 100644 --- a/docs/guide-it/start-installation.md +++ b/docs/guide-it/start-installation.md @@ -1,11 +1,15 @@ Installare Yii ============== +<<<<<<< HEAD <<<<<<< HEAD Puoi installare Yii in due modi, usando [Composer](http://getcomposer.org/) o scaricando un archivio. ======= Puoi installare Yii in due modi, usando [Composer](https://getcomposer.org/) o scaricando un archivio. >>>>>>> yiichina/master +======= +Puoi installare Yii in due modi, usando [Composer](https://getcomposer.org/) o scaricando un archivio. +>>>>>>> master Il metodo preferito è il primo, perché ti consente di installare [estensioni](structure-extensions.md) o aggiornare il core di Yii semplicemente eseguendo un comando. @@ -18,11 +22,15 @@ Installazione via Composer Se non hai già installato Composer puoi farlo seguendo le istruzioni al sito [getcomposer.org](https://getcomposer.org/download/). Su Linux e Mac OS X puoi installare Composer con questo comando: +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master +======= + curl -sS https://getcomposer.org/installer | php +>>>>>>> master mv composer.phar /usr/local/bin/composer Su Windows devi scaricare ed eseguire [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). @@ -35,11 +43,15 @@ Se hai già Composer installato assicurati di avere una versione aggiornata. Puo Una volta installato Composer, puoi installare Yii eseguendo questo comando in una directory accessbile via web: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= + composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer create-project --prefer-dist yiisoft/yii2-app-basic basic Il primo comando installa il [plugin composer asset](https://github.com/francoispluchino/composer-asset-plugin/) diff --git a/docs/guide-ja/README.md b/docs/guide-ja/README.md index c06a086ebb..12ecfbca67 100644 --- a/docs/guide-ja/README.md +++ b/docs/guide-ja/README.md @@ -79,16 +79,22 @@ All Rights Reserved. * [アクティブレコード](db-active-record.md): アクティブレコード ORM、レコードの読み出しと操作、リレーションの定義 * [マイグレーション](db-migrations.md): チーム開発環境においてデータベースにバージョンコントロールを適用 <<<<<<< HEAD +<<<<<<< HEAD * **未定** [Sphinx](db-sphinx.md) * **未定** [Redis](db-redis.md) * **未定** [MongoDB](db-mongodb.md) * **未定** [ElasticSearch](db-elasticsearch.md) ======= +======= +>>>>>>> master * [Sphinx](https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide-ja/README.md) * [Redis](https://github.com/yiisoft/yii2-redis/blob/master/docs/guide-ja/README.md) * [MongoDB](https://github.com/yiisoft/yii2-mongodb/blob/master/docs/guide-ja/README.md) * [ElasticSearch](https://github.com/yiisoft/yii2-elasticsearch/blob/master/docs/guide-ja/README.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ユーザからのデータ取得 @@ -98,17 +104,21 @@ All Rights Reserved. * [入力を検証する](input-validation.md) * [ファイルをアップロードする](input-file-upload.md) * [表形式インプットのデータ収集](input-tabular-input.md) -* [複数モデルのデータ取得](input-multiple-models.md) +* [複数のモデルのデータを取得する](input-multiple-models.md) データの表示 ------------ +<<<<<<< HEAD <<<<<<< HEAD * [データのフォーマット](output-formatter.md) ======= * [データのフォーマット](output-formatting.md) >>>>>>> yiichina/master +======= +* [データのフォーマット](output-formatting.md) +>>>>>>> master * [ページネーション](output-pagination.md) * [並べ替え](output-sorting.md) * [データプロバイダ](output-data-providers.md) @@ -120,14 +130,21 @@ All Rights Reserved. セキュリティ ------------ +* [概要](security-overview.md) * [認証](security-authentication.md) * [権限付与](security-authorization.md) * [パスワードを扱う](security-passwords.md) <<<<<<< HEAD +<<<<<<< HEAD * [認証クライアント](security-auth-clients.md) ======= * [認証クライアント](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide-ja/README.md) >>>>>>> yiichina/master +======= +* [暗号化](security-cryptography.md) +* [ビューのセキュリティ](structure-views.md#security) +* [認証クライアント](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide-ja/README.md) +>>>>>>> master * [ベストプラクティス](security-best-practices.md) @@ -158,6 +175,7 @@ RESTful ウェブサービス 開発ツール ---------- +<<<<<<< HEAD <<<<<<< HEAD * [デバッグツールバーとデバッガ](tool-debugger.md) * [Gii を使ってコードを生成する](tool-gii.md) @@ -166,6 +184,10 @@ RESTful ウェブサービス * [デバッグツールバーとデバッガ](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-ja/README.md) * [Gii を使ってコードを生成する](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) >>>>>>> yiichina/master +======= +* [デバッグツールバーとデバッガ](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-ja/README.md) +* [Gii を使ってコードを生成する](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) +>>>>>>> master テスト @@ -182,11 +204,15 @@ RESTful ウェブサービス スペシャルトピック ------------------ +<<<<<<< HEAD <<<<<<< HEAD * [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) ======= * [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) >>>>>>> yiichina/master +======= +* [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) +>>>>>>> master * [アプリケーションを一から構築する](tutorial-start-from-scratch.md) * [コンソールコマンド](tutorial-console.md) * [コアバリデータ](tutorial-core-validators.md) @@ -210,18 +236,23 @@ RESTful ウェブサービス * LinkPager: **未定** デモページへリンク * LinkSorter: **未定** デモページへリンク <<<<<<< HEAD +<<<<<<< HEAD * [Bootstrap ウィジェット](widget-bootstrap.md) * [jQuery UI ウィジェット](widget-jui.md) ======= * [Bootstrap ウィジェット](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-ja/README.md) * [jQuery UI ウィジェット](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-ja/README.md) >>>>>>> yiichina/master +======= +* [Bootstrap ウィジェット](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-ja/README.md) +* [jQuery UI ウィジェット](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-ja/README.md) +>>>>>>> master ヘルパ ------ * [概要](helper-overview.md) -* [ArrayHelper](helper-array.md) -* [Html](helper-html.md) -* [Url](helper-url.md) +* [配列ヘルパ](helper-array.md) +* [Html ヘルパ](helper-html.md) +* [Url ヘルパ](helper-url.md) diff --git a/docs/guide-ja/blocktypes.json b/docs/guide-ja/blocktypes.json new file mode 100644 index 0000000000..f6c0789612 --- /dev/null +++ b/docs/guide-ja/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Note:": "補足:", + "Info:": "情報:", + "Tip:": "ヒント:", + "Warning:": "警告:" +} \ No newline at end of file diff --git a/docs/guide-ja/caching-data.md b/docs/guide-ja/caching-data.md index 44f7af8fc3..7e21c4930a 100644 --- a/docs/guide-ja/caching-data.md +++ b/docs/guide-ja/caching-data.md @@ -2,7 +2,7 @@ ============ データキャッシュは PHP の変数をキャッシュに格納し、あとでキャッシュからそれらを読み込みます。 -[クエリキャッシュ](#query-caching) や [ページキャッシュ](caching-page.md) などのより高度なキャッシュ機能の基礎でもあります。 +これは、[クエリキャッシュ](#query-caching) や [ページキャッシュ](caching-page.md) などの、より高度なキャッシュ機能の基礎でもあります。 以下はデータキャッシュの典型的な利用パターンを示したコードです。`$cache` は [キャッシュコンポーネント](#cache-components) を指します: @@ -63,7 +63,7 @@ if ($data === false) { ], ``` -> ヒント: キャッシュコンポーネントは複数登録することができます。`cache` という名前のコンポーネントはキャッシュに依存したクラスによってデフォルトで使用されています (例えば [[yii\web\UrlManager]] など) 。 +> Tip: キャッシュコンポーネントは複数登録することができます。`cache` という名前のコンポーネントはキャッシュに依存したクラスによってデフォルトで使用されています (例えば [[yii\web\UrlManager]] など) 。 ### サポートされているキャッシュストレージ @@ -87,7 +87,7 @@ Yii はさまざまなキャッシュストレージをサポートしていま * [[yii\caching\XCache]]: PHP の [XCache](http://xcache.lighttpd.net/) 拡張モジュールを使用します。 * [[yii\caching\ZendDataCache]]: キャッシュメディアして [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) を使用します。 -> ヒント: 同じアプリケーション内で異なるキャッシュを使用することもできます。一般的なやり方として、小さくとも常に使用されるデータ (例えば、統計データ) を格納する場合はメモリベースのキャッシュストレージを使用し、大きくて使用頻度の低いデータ (例えば、ページコンテント) を格納する場合はファイルベース、またはデータベースのキャッシュストレージを使用します 。 +> Tip: 同じアプリケーション内で異なるキャッシュを使用することもできます。一般的なやり方として、小さくとも常に使用されるデータ (例えば、統計データ) を格納する場合はメモリベースのキャッシュストレージを使用し、大きくて使用頻度の低いデータ (例えば、ページコンテント) を格納する場合はファイルベース、またはデータベースのキャッシュストレージを使用します 。 ## キャッシュ API @@ -97,18 +97,18 @@ Yii はさまざまなキャッシュストレージをサポートしていま * [[yii\caching\Cache::get()|get()]]: 指定されたキーを用いてキャッシュからデータを取得します。データが見つからないか、もしくは有効期限が切れたり無効になったりしている場合は false を返します。 * [[yii\caching\Cache::set()|set()]]: キーによって識別されるデータをキャッシュに格納します。 * [[yii\caching\Cache::add()|add()]]: キーがキャッシュ内で見つからない場合に、キーによって識別されるデータをキャッシュに格納します。 -* [[yii\caching\Cache::mget()|mget()]]: 指定されたキーを用いてキャッシュから複数のデータを取得します。 -* [[yii\caching\Cache::mset()|mset()]]: キャッシュに複数のデータを格納します。各データはキーによって識別されます。 -* [[yii\caching\Cache::madd()|madd()]]: キャッシュに複数のデータを格納します。各データはキーによって識別されます。もしキャッシュ内にキーがすでに存在する場合はスキップされます。 +* [[yii\caching\Cache::multiGet()|multiGet()]]: 指定されたキーを用いてキャッシュから複数のデータを取得します。 +* [[yii\caching\Cache::multiSet()|multiSet()]]: キャッシュに複数のデータを格納します。各データはキーによって識別されます。 +* [[yii\caching\Cache::multiAdd()|multiAdd()]]: キャッシュに複数のデータを格納します。各データはキーによって識別されます。もしキャッシュ内にキーがすでに存在する場合はスキップされます。 * [[yii\caching\Cache::exists()|exists()]]: 指定されたキーがキャッシュ内で見つかったかどうかを示す値を返します。 * [[yii\caching\Cache::delete()|delete()]]: キャッシュからキーによって識別されるデータを削除します。 * [[yii\caching\Cache::flush()|flush()]]: キャッシュからすべてのデータを削除します。 -> 注意: [[yii\caching\Cache::get()|get()]] メソッドは、データがキャッシュ内に見つからないことを示すために戻り値として false を使用しているので、直接 boolean 型の `false` をキャッシュしないでください。 +> Note: [[yii\caching\Cache::get()|get()]] メソッドは、データがキャッシュ内に見つからないことを示すために戻り値として false を使用しているので、直接 boolean 型の `false` をキャッシュしないでください。 代りに配列内に `false` を置いてキャッシュすることによって、この問題を回避できます。 キャッシュされたデータを取得する際に発生するオーバーヘッドを減らすために、MemCache, APC などのいくつかのキャッシュストレージは、バッチモードで複数のキャッシュされた値を取得することをサポートしています。 -[[yii\caching\Cache::mget()|mget()]] や [[yii\caching\Cache::madd()|madd()]] などの API はこの機能を十分に引き出すために提供されています。 +[[yii\caching\Cache::multiGet()|multiGet()]] や [[yii\caching\Cache::multiAdd()|multiAdd()]] などの API はこの機能を十分に引き出すために提供されています。 基礎となるキャッシュストレージがこの機能をサポートしていない場合には、シミュレートされます。 [[yii\caching\Cache]] は `ArrayAccess` インターフェイスを継承しているので、キャッシュコンポーネントは配列のように扱うことができます。以下はいくつかの例です: @@ -208,16 +208,24 @@ $data = $cache->get($key); - [[yii\caching\TagDependency]]: キャッシュされるデータアイテムに一つまたは複数のタグを関連付けます。 [[yii\caching\TagDependency::invalidate()]] を呼び出すことによって、指定されたタグ (複数可) を持つキャッシュされたデータアイテムを無効にすることができます。 +> Note: 依存を有するキャッシュについて [[yii\caching\Cache::exists()|exists()]] メソッドを使用することは避けてください。 + このメソッドは、キャッシュされたデータに関連づけられた依存がある場合でも、依存が変化したかどうかをチェックしません。 + つまり、[[yii\caching\Cache::exists()|exists()]] が `true` を返しているのに、 [[yii\caching\Cache::get()|get()]] が `false` を返すという場合があり得ます。 + ## クエリキャッシュ クエリキャッシュは、データキャッシュ上に構築された特別なキャッシュ機能で、データベースのクエリ結果をキャッシュするために提供されています。 +<<<<<<< HEAD <<<<<<< HEAD クエリキャッシュは [[yii\db\Connection|データベース接続]] と有効な `cache` アプリケーションコンポーネントを必要とします。 ======= クエリキャッシュは [[yii\db\Connection|データベース接続]] と有効な `cache` [アプリケーションコンポーネント](#cache-components) を必要とします。 >>>>>>> yiichina/master +======= +クエリキャッシュは [[yii\db\Connection|データベース接続]] と有効な `cache` [アプリケーションコンポーネント](#cache-components) を必要とします。 +>>>>>>> master `$db` を [[yii\db\Connection]] のインスタンスと仮定した場合、クエリキャッシュの基本的な使い方は以下のようになります: ```php @@ -237,10 +245,24 @@ $result = Customer::getDb()->cache(function ($db) { }); ``` -> 情報: いくつかの DBMS (例えば [MySQL](http://dev.mysql.com/doc/refman/5.1/ja/query-cache.html)) でもデータベースのサーバサイドのクエリキャッシュをサポートしています。 +> Info: いくつかの DBMS (例えば [MySQL](http://dev.mysql.com/doc/refman/5.1/ja/query-cache.html)) でもデータベースのサーバサイドのクエリキャッシュをサポートしています。 どちらのクエリキャッシュメカニズムも選べますが、前述した Yii のクエリキャッシュにはキャッシュの依存を柔軟に指定できるという利点があり、潜在的にはより効率的でしょう。 +### キャッシュのフラッシュ + +保存されている全てのキャッシュデータを無効化する必要がある場合は、[[yii\caching\Cache::flush()]] を呼ぶことが出来ます。 + +コンソールから `yii cache/flush` を呼ぶことによっても、キャッシュをフラッシュすることが出来ます。 + - `yii cache`: アプリケーションで利用可能なキャッシュのリストを表示します。 + - `yii cache/flush cache1 cache2`: キャッシュコンポーネント `cache1` と `cache2` をフラッシュします +(複数のコンポーネント名をスペースで区切って渡すことが出来ます) + - `yii cache/flush-all`: アプリケーションの全てのキャッシュコンポーネントをフラッシュします。 + +> Info: デフォルトでは、コンソールアプリケーションは独立した構成情報ファイルを使用します。 +正しい結果を得るためには、ウェブとコンソールのアプリケーション構成で同じキャッシュコンポーネントを使用していることを確認してください。 + + ### 構成 クエリキャッシュには [[yii\db\Connection]] を通して設定可能な三つのグローバルなオプションがあります: diff --git a/docs/guide-ja/caching-http.md b/docs/guide-ja/caching-http.md index e130ccfe8b..bba0d1472d 100644 --- a/docs/guide-ja/caching-http.md +++ b/docs/guide-ja/caching-http.md @@ -100,7 +100,7 @@ ETag は `Last-Modified` ヘッダよりも複雑 かつ/または より正確 ETag はリクエスト毎に再評価する必要があるため、負荷の高い生成方法を使うと `HttpCache` の本来の目的を損なって不必要なオーバーヘッドが生じる場合があります。 ページのコンテントが変更されたときにキャッシュを無効化するための式は単純なものを指定するようにして下さい。 -> 注意: [RFC 7232](http://tools.ietf.org/html/rfc7232#section-2.4) に準拠して `Etag` と `Last-Modified` ヘッダの両方を設定した場合、`HttpCache` はその両方とも送信します。また、もし `If-None-Match` ヘッダと `If-Modified-Since` ヘッダの両方を送信した場合は前者のみが尊重されます。 +> Note: [RFC 7232](http://tools.ietf.org/html/rfc7232#section-2.4) に準拠して `Etag` と `Last-Modified` ヘッダの両方を設定した場合、`HttpCache` はその両方とも送信します。また、もし `If-None-Match` ヘッダと `If-Modified-Since` ヘッダの両方を送信した場合は前者のみが尊重されます。 ## `Cache-Control` ヘッダ diff --git a/docs/guide-ja/concept-aliases.md b/docs/guide-ja/concept-aliases.md index 6ded1760b7..3126736e2e 100644 --- a/docs/guide-ja/concept-aliases.md +++ b/docs/guide-ja/concept-aliases.md @@ -1,7 +1,10 @@ エイリアス ======= -ファイルパスや URL を表すのにエイリアスを使用すると、あなたはプロジェクト内で絶対パスや URL をハードコードする必要がなくなります。エイリアスは、通常のファイルパスや URL と区別するために、 `@` 文字で始まる必要があります。Yii はすでに利用可能な多くの事前定義エイリアスを持っています。 +ファイルパスや URL を表すのにエイリアスを使用すると、あなたはプロジェクト内で絶対パスや URL をハードコードする必要がなくなります。エイリアスは、通常のファイルパスや URL と区別するために、 `@` 文字で始まる必要があります。 +先頭に `@` を付けずに定義されたエイリアスは、`@` 文字が先頭に追加されます。 + +Yii はすでに利用可能な多くの事前定義エイリアスを持っています。 たとえば、 `@yii` というエイリアスは Yii フレームワークのインストールパスを表し、 `@web` は現在実行中の Web アプリケーションのベース URL を表します。 @@ -18,7 +21,7 @@ Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@bar', 'http://www.example.com'); ``` -> 補足: エイリアスされているファイルパスや URL は、必ずしも実在するファイルまたはリソースを参照しない場合があります。 +> Note: エイリアスされているファイルパスや URL は、必ずしも実在するファイルまたはリソースを参照しない場合があります。 定義済みのエイリアスがあれば、スラッシュ `/` に続けて 1 つ以上のパスセグメントを追加することで([[Yii::setAlias()]] の呼び出しを必要とせずに) 新しいエイリアスを導出することができます。 [[Yii::setAlias()]] を通じて定義されたエイリアスは @@ -62,7 +65,7 @@ echo Yii::getAlias('@foo/bar/file.php'); // /path/to/foo/bar/file.php を表示 派生エイリアスによって表されるパスや URL は、派生エイリアス内のルートエイリアス部分を、対応するパス/URL で置換して決定されます。 -> 補足: [[Yii::getAlias()]] メソッドは、 結果のパスや URL が実在するファイルやリソースを参照しているかをチェックしません。 +> Note: [[Yii::getAlias()]] メソッドは、 結果のパスや URL が実在するファイルやリソースを参照しているかをチェックしません。 ルートエイリアス名にはスラッシュ `/` 文字を含むことができます。 [[Yii::getAlias()]] メソッドは、 エイリアスのどの部分がルートエイリアスであるかを賢く判別し、正確に対応するファイルパスや URL を決定します: diff --git a/docs/guide-ja/concept-autoloading.md b/docs/guide-ja/concept-autoloading.md index 61c93be5b8..526667c438 100644 --- a/docs/guide-ja/concept-autoloading.md +++ b/docs/guide-ja/concept-autoloading.md @@ -1,11 +1,11 @@ クラスのオートローディング ================= -Yiiは、必要となるすべてのクラスファイルを、特定してインクルードするにあたり、 [クラスのオートローディングメカニズム](http://www.php.net/manual/ja/language.oop5.autoload.php) -を頼りにします。[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供します。 +Yiiは、必要となるすべてのクラスファイルを特定してインクルードするにあたり、 [クラスのオートローディングメカニズム](http://www.php.net/manual/ja/language.oop5.autoload.php) +を頼りにします。Yii は、[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供しています。 このオートローダーは、あなたが `Yii.php` ファイルをインクルードするときにインストールされます。 -> 補足: 説明を簡単にするため、このセクションではクラスのオートローディングについてのみお話しします。しかし、 +> Note: 説明を簡単にするため、このセクションではクラスのオートローディングについてのみお話しします。しかし、 ここに記述されている内容は、同様に、インタフェースとトレイトのオートロードにも適用されることに注意してください。 @@ -14,32 +14,41 @@ Yii オートローダーの使用 Yii のクラスオートローダーを使用するには、自分のクラスを作成して名前を付けるとき、次の2つの単純なルールに従わなければなりません: -* 各クラスは名前空間の下になければなりません (例 `foo\bar\MyClass`) +* 各クラスは [名前空間](http://php.net/manual/ja/language.namespaces.php) の下になければなりません (例 `foo\bar\MyClass`) * 各クラスは次のアルゴリズムで決定される個別のファイルに保存されなければなりません: ```php -// $className は先頭にバックスラッシュを持つ完全修飾名 +// $className は先頭にバックスラッシュを持たない完全修飾クラス名 $classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); ``` たとえば、クラス名と名前空間が `foo\bar\MyClass` であれば、対応するクラスファイルのパスの [エイリアス](concept-aliases.md) は、 -`@foo/bar/MyClass.php` になります。このエイリアスがファイルパスになるようにするには、`@foo` または `@foo/bar` +`@foo/bar/MyClass.php` になります。このエイリアスがファイルパスとして解決できるようにするためには、`@foo` または `@foo/bar` のどちらかが、 [ルートエイリアス](concept-aliases.md#defining-aliases) でなければなりません。 +<<<<<<< HEAD <<<<<<< HEAD [Basic Application Template](start-basic.md) を使用している場合、最上位の名前空間 `app` の下にクラスを置くことができ、 ======= [ベーシックプロジェクトテンプレート](start-basic.md) を使用している場合、最上位の名前空間 `app` の下にクラスを置くことができ、 >>>>>>> yiichina/master +======= +[ベーシックプロジェクトテンプレート](start-installation.md) を使用している場合、最上位の名前空間 `app` の下にクラスを置くことができ、 +>>>>>>> master そうすると、新しいエイリアスを定義しなくても、Yii によってそれらをオートロードできるようになります。これは `@app` が [事前定義されたエイリアス](concept-aliases.md#predefined-aliases) であるためで、`app\components\MyClass` のようなクラス名を -今説明したアルゴリズムに従って、クラスファイル `AppBasePath/components/MyClass.php` だと解決できるのです。 +今説明したアルゴリズムに従って、クラスファイル `AppBasePath/components/MyClass.php` であると解決することが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD [Advanced Application Template](tutorial-advanced-app.md) では、各階層にそれ自身のルートエイリアスを持っています。たとえば、 ======= [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) では、各階層にそれ自身のルートエイリアスを持っています。たとえば、 >>>>>>> yiichina/master フロントエンド層はルートエイリアス `@frontend` を持ち、バックエンド層は `@backend` です。その結果、名前空間 `frontend` の下に +======= +[アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) では、各層がそれ自身のルートエイリアスを持っています。たとえば、 +フロントエンド層はルートエイリアス `@frontend` を持ち、バックエンド層のルートエイリアスは `@backend` です。その結果、名前空間 `frontend` の下に +>>>>>>> master フロントエンドクラスを置き、バックエンドクラスを `backend` の下に置けます。これで、これらのクラスは Yii のオートローダーによって オートロードできるようになります。 @@ -70,7 +79,7 @@ Yii はパッケージ依存関係マネージャとして Composer を包含し Yii オートローダーを他のオートローダーと一緒に使うときは、他のすべてのオートローダーがインストールされた *後で* 、 `Yii.php` ファイルをインクルードする必要があります。これで Yii のオートローダーが、任意クラスのオートローディング要求に応答する最初のものになります。 -たとえば、次のコードは [Basic Application Template](start-basic.md) の [エントリスクリプト](structure-entry-scripts.md) から抜粋したものです。 +たとえば、次のコードは [ベーシックプロジェクトテンプレート](start-installation.md) の [エントリスクリプト](structure-entry-scripts.md) から抜粋したものです。 最初の行は、Composer のオートローダーをインストールしており、二行目は Yii のオートローダーをインストールしています。 ```php @@ -81,7 +90,7 @@ require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); あなたは Yii のオートローダーを使わず、Composer のオートローダーだけを単独で使用することもできます。しかし、そうすることによって、 あなたのクラスのオートローディングのパフォーマンスは低下し、クラスをオートロード可能にするために Composer が設定したルールに従わなければならなくなります。 -> Info: Yiiのオートローダーを使用したくない場合は、 `Yii.php` ファイルの独自のバージョンを作成し、 +> Info: Yiiのオートローダーを使用したくない場合は、 `Yii.php` ファイルのあなた独自のバージョンを作成し、 それを [エントリスクリプト](structure-entry-scripts.md) でインクルードする必要があります。 @@ -92,5 +101,4 @@ Yii のオートローダーは、 [エクステンション](structure-extensio エクステンションがその `composer.json` ファイルに正しく `autoload` セクションを指定していることです。 `autoload` 指定方法の詳細については [Composer のドキュメント](https://getcomposer.org/doc/04-schema.md#autoload) 参照してください。 -Yii のオートローダーを使用しない場合でも、まだ Composer のオートローダーはエクステンションクラスをオートロード可能です。 - +Yii のオートローダーを使用しない場合でも、まだ Composer のオートローダーがエクステンションクラスをオートロードすることが可能です。 diff --git a/docs/guide-ja/concept-behaviors.md b/docs/guide-ja/concept-behaviors.md index e2368a31dc..47febbdb3e 100644 --- a/docs/guide-ja/concept-behaviors.md +++ b/docs/guide-ja/concept-behaviors.md @@ -51,6 +51,9 @@ class MyBehavior extends Behavior > Tip: ビヘイビア内から、[[yii\base\Behavior::owner]] プロパティを介して、ビヘイビアをアタッチしたコンポーネントにアクセスすることができます。 +> Note: ビヘイビアの [[yii\base\Behavior::__get()]] および/または [[yii\base\Behavior::__set()]] メソッドをオーバーライドする場合は、同時に [[yii\base\Behavior::canGetProperty()]] および/または [[yii\base\Behavior::canSetProperty()]] もオーバーライドする必要があります。 + + コンポーネントイベントのハンドリング ------------------ @@ -248,8 +251,8 @@ $component->detachBehaviors(); ------------------------- しめくくりに、[[yii\behaviors\TimestampBehavior]] を見てみましょう。このビヘイビアは、 -保存時 (つまり挿入や更新) に、[[yii\db\ActiveRecord|アクティブレコード]] モデルの -タイムスタンプ属性の自動更新をサポートします。 +`insert()`、`update()` または `save()` のメソッドを通じて [[yii\db\ActiveRecord|アクティブレコード]] モデルが保存されるときに、 +タイムスタンプ属性の自動的な更新をサポートします。 まず、使用しようと考えている [[yii\db\ActiveRecord|アクティブレコード]] クラスに、このビヘイビアをアタッチします: @@ -272,6 +275,8 @@ class User extends ActiveRecord ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], ], + // UNIX タイムスタンプではなく datetime を使う場合は + // 'value' => new Expression('NOW()'), ], ]; } @@ -280,11 +285,13 @@ class User extends ActiveRecord 上のビヘイビア構成は、レコードが: -* 挿入されるとき、ビヘイビアは現在のタイムスタンプを `created_at` と `updated_at` 属性に割り当てます -* 更新されるとき、ビヘイビアは現在のタイムスタンプを `updated_at` 属性に割り当てます +* 挿入されるとき、ビヘイビアは現在の UNIX タイムスタンプを `created_at` と `updated_at` 属性に割り当てます +* 更新されるとき、ビヘイビアは現在の UNIX タイムスタンプを `updated_at` 属性に割り当てます -所定の位置にそのコードを使用すると、もし `User` オブジェクトを設け、それを保存しようとしたら、そこで、 -`created_at` と `updated_at` が自動的に現在のタイムスタンプで埋められます。 +> Note: 上記の実装が MySQL データベースで動作するようにするためには、`created_at` と `updated_at` のカラムを UNIX タイムスタンプ になるように int(11) として宣言してください。 + +このコードが所定の位置にあれば、例えば `User` オブジェクトがあって、それを保存しようとしたら、そこで、 +`created_at` と `updated_at` が自動的に現在の UNIX タイムスタンプで埋められます。 ```php $user = new User; @@ -293,13 +300,25 @@ $user->save(); echo $user->created_at; // 現在のタイムスタンプが表示される ``` -[[yii\behaviors\TimestampBehavior|TimestampBehavior]] は、指定された属性に現在のタイムスタンプを割り当てて -それをデータベースに保存する、便利なメソッド [[yii\behaviors\TimestampBehavior::touch()|touch()]] を提供します。 +[[yii\behaviors\TimestampBehavior|TimestampBehavior]] は、また、指定された属性に現在のタイムスタンプを割り当てて +それをデータベースに保存する、便利なメソッド [[yii\behaviors\TimestampBehavior::touch()|touch()]] を提供しています。 ```php $user->touch('login_time'); ``` +その他のビヘイビア +------------------ + +その他にも、内蔵または外部ライブラリによって利用できるビヘイビアがいくつかあります。 + +- [[yii\behaviors\BlameableBehavior]] - 指定された属性に現在のユーザ ID を自動的に設定します。 +- [[yii\behaviors\SluggableBehavior]] - 指定された属性に、URL のスラグとして使用できる値を自動的に設定します。 +- [[yii\behaviors\AttributeBehavior]] - 特定のイベントが発生したときに、ActiveRecord オブジェクトの一つまたは複数の属性に、指定された値を自動的に設定します。 +- [yii2tech\ar\softdelete\SoftDeleteBehavior](https://github.com/yii2tech/ar-softdelete) - ActiveRecord をソフト・デリートおよびソフト・リストアする、すなわち、レコードの削除を示すフラグまたはステータスを設定するメソッドを提供します。 +- [yii2tech\ar\position\PositionBehavior](https://github.com/yii2tech/ar-position) - レコードの順序を整数のフィールドによって管理することが出来るように、順序変更メソッドを提供します。 + + ビヘイビアとトレイトの比較 ---------------------- diff --git a/docs/guide-ja/concept-components.md b/docs/guide-ja/concept-components.md index cb21f1547d..b8656c2dee 100644 --- a/docs/guide-ja/concept-components.md +++ b/docs/guide-ja/concept-components.md @@ -77,7 +77,7 @@ $component = \Yii::createObject([ ], [1, 2]); ``` -> 補足: [[Yii::createObject()]] を呼び出すアプローチは複雑に見えますが、より強力です。というのも、それが [依存性注入コンテナ](concept-di-container.md) 上に実装されているからです。 +> Note: [[Yii::createObject()]] を呼び出すアプローチは複雑に見えますが、より強力です。というのも、それが [依存性注入コンテナ](concept-di-container.md) 上に実装されているからです。 [[yii\base\Object]] クラスには、次のオブジェクトライフサイクルが適用されます: diff --git a/docs/guide-ja/concept-configurations.md b/docs/guide-ja/concept-configurations.md index fa2f013f22..a3934fcc06 100644 --- a/docs/guide-ja/concept-configurations.md +++ b/docs/guide-ja/concept-configurations.md @@ -87,10 +87,14 @@ Yii::configure($object, $config); それは [[yii\web\Application|アプリケーション]] クラスが、設定可能なプロパティとイベントを数多く持つためです。 さらに重要なことは、その [[yii\web\Application::components|components]] プロパティが、アプリケーションに登録されている <<<<<<< HEAD +<<<<<<< HEAD コンポーネント生成用の構成情報配列を受け取ることができることです。以下は、 [basic application template](start-basic.md) ======= コンポーネント生成用の構成情報配列を受け取ることができることです。以下は、 [ベーシックプロジェクトテンプレート](start-basic.md) >>>>>>> yiichina/master +======= +コンポーネント生成用の構成情報配列を受け取ることができることです。以下は、 [ベーシックプロジェクトテンプレート](start-basic.md) +>>>>>>> master のアプリケーション構成ファイルの概要です。 ```php diff --git a/docs/guide-ja/concept-di-container.md b/docs/guide-ja/concept-di-container.md index c9bd96b441..73840a8624 100644 --- a/docs/guide-ja/concept-di-container.md +++ b/docs/guide-ja/concept-di-container.md @@ -2,7 +2,7 @@ ================ 依存注入 (DI) コンテナは、オブジェクトとそれが依存するすべてのブジェクトを、インスタンス化し、設定する方法を知っているオブジェクトです。 -なぜ DI コンテナが便利なのかは、[Martin の記事](http://martinfowler.com/articles/injection.html) の説明がわかりやすいでしょう。 +なぜ DI コンテナが便利なのかは、[Martin Fowler の記事](http://martinfowler.com/articles/injection.html) の説明がわかりやすいでしょう。 ここでは、主に Yii の提供する DI コンテナの使用方法を説明します。 @@ -70,7 +70,7 @@ $container->get('Foo', [], [ ]); ``` -> Info|情報: [[yii\di\Container::get()]] メソッドは三番目のパラメータを、生成されるオブジェクトに適用されるべき構成情報配列として受け取ります。 +> Info: [[yii\di\Container::get()]] メソッドは三番目のパラメータを、生成されるオブジェクトに適用されるべき構成情報配列として受け取ります。 クラスが [[yii\base\Configurable]] インタフェイスを実装している場合 (例えば、クラスが [[yii\base\Object]] である場合) には、この構成情報配列がクラスのコンストラクタの最後のパラメータとして渡されます。 そうでない場合は、構成情報はオブジェクトが生成された *後で* 適用されることになります。 @@ -91,7 +91,7 @@ $container->set('Foo', function () { $foo = $container->get('Foo'); ``` -新しいオブジェクトを構築するための複雑なロジックを隠蔽するために、PHP コーラブルを返すスタティックなクラスメソッドを使うことが出来ます。 +新しいオブジェクトを構築するための複雑なロジックを隠蔽するために、スタティックなクラスメソッドをコーラブルとして使うことが出来ます。 例えば、 ```php @@ -99,20 +99,17 @@ class FooBuilder { public static function build() { - return function () { - $foo = new Foo(new Bar); - // ... その他の初期化 ... - return $foo; - }; + $foo = new Foo(new Bar); + // ... その他の初期化 ... + return $foo; } } -$container->set('Foo', FooBuilder::build()); +$container->set('Foo', ['app\helper\FooBuilder', 'build']); $foo = $container->get('Foo'); ``` -ご覧のように、PHP コーラブルが `FooBuilder::build()` メソッドによって返されています。 このようにすれば、`Foo` クラスを構成しようとする人は、`Foo` がどのように構築されるかを気にする必要はもうなくなります。 @@ -169,7 +166,7 @@ $container->set('db', function ($container, $params, $config) { $container->set('pageCache', new FileCache); ``` -> 補足: 依存の名前が対応する依存の定義と同じである場合は、それを DI コンテナに登録する必要はありません。 +> Note: 依存の名前が対応する依存の定義と同じである場合は、それを DI コンテナに登録する必要はありません。 `set()` を介して登録された依存は、依存が必要とされるたびにインスタンスを生成します。 [[yii\di\Container::setSingleton()]] を使うと、単一のインスタンスしか生成しない依存を登録することができます: @@ -194,17 +191,17 @@ $container->setSingleton('yii\db\Connection', [ [[yii\di\Container::get()]] を使って、新しいオブジェクトを作成することができます。 このメソッドは、クラス名、インタフェース名、エイリアス名で指定できる依存の名前を受け取ります。 -依存の名前は、 `set()` や `setSingleton()` を介して登録されていたりされていなかったりする -可能性があります。オプションで、クラスのコンストラクタのパラメータのリストや、新しく作成された -オブジェクトを設定するための [設定情報](concept-configurations.md) を渡すことができます。 +依存の名前は、 `set()` や `setSingleton()` を介して登録されている場合もあれば、登録されていない場合もあります。 +オプションで、クラスのコンストラクタのパラメータのリストや、新しく作成されたオブジェクトを設定するための +[設定情報](concept-configurations.md) を渡すことができます。 たとえば ```php // "db" は事前に登録されたエイリアス名 $db = $container->get('db'); -// これと同じ意味: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); -$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); +// これと同じ意味: $engine = new \app\components\SearchEngine($apiKey, $apiSecret, ['type' => 1]); +$engine = $container->get('app\components\SearchEngine', [$apiKey, $apiSecret], ['type' => 1]); ``` 見えないところで、DIコンテナは、単に新しいオブジェクトを作成するよりもはるかに多くの作業を行います。 @@ -290,7 +287,7 @@ Yii は、新しいオブジェクトを作成するさい、そのコアコー \Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); ``` -次のコードでビューでウィジェットを使用すれば、 `maxButtonCount` プロパティは、 +そして、次のコードでビューでウィジェットを使用すれば、`maxButtonCount` プロパティは、 クラスで定義されているデフォルト値 10 の代わりに 5 で初期化されます。 ```php @@ -303,6 +300,8 @@ echo \yii\widgets\LinkPager::widget(); echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); ``` +> Tip: どのような型の値であろうとも上書きされますので、オプションの配列の指定には気を付けてください。オプションの配列はマージされません。 + DI コンテナの自動コンストラクタ・インジェクションの利点を活かす別の例です。 あなたのコントローラクラスが、ホテル予約サービスのような、いくつかの他のオブジェクトに依存するとします。 あなたは、コンストラクタパラメータを通して依存関係を宣言して、DI コンテナにあなたの課題を解決させることができます。 @@ -325,8 +324,8 @@ class HotelController extends Controller } ``` -あなたがブラウザからこのコントローラにアクセスすると、 `BookingInterface` をインスタンス化できませんという -不具合報告エラーが表示されるでしょう。これは、この依存関係に対処する方法を DI コンテナに教える必要があるからです: +あなたがブラウザからこのコントローラにアクセスすると、 `BookingInterface` をインスタンス化できない、という不平を言う +エラーが表示されるでしょう。これは、この依存関係に対処する方法を DI コンテナに教える必要があるからです: ```php \Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); diff --git a/docs/guide-ja/concept-events.md b/docs/guide-ja/concept-events.md index e067c27087..15d0f4d136 100644 --- a/docs/guide-ja/concept-events.md +++ b/docs/guide-ja/concept-events.md @@ -4,22 +4,22 @@ イベントを使うと、既存のコードの特定の実行ポイントに、カスタムコードを挿入することができます。イベントにカスタムコードを添付すると、 イベントがトリガされたときにコードが自動的に実行されます。たとえば、メーラーオブジェクトがメッセージを正しく送信できたとき、 `messageSent` イベントをトリガするとします。もしメッセージの送信がうまく行ったことを知りたければ、単に `messageSent` -イベントにトラッキングコードを付与すするだけで、それが可能になります。 +イベントにトラッキングコードを付与するだけで、それが可能になります。 Yiiはイベントをサポートするために、 [[yii\base\Component]] と呼ばれる基底クラスを導入してします。クラスがイベントをトリガする必要がある場合は、 [[yii\base\Component]] もしくはその子クラスを継承する必要があります。 イベントハンドラ --------------- +---------------- イベントハンドラとは、関連するイベントがトリガされたときに実行される、 [PHP コールバック](http://www.php.net/manual/ja/language.types.callable.php) です。次のコールバックのいずれも使用可能です: -- 文字列で指定されたグローバル PHP 関数 (括弧を除く) `'trim'` など -- オブジェクトとメソッド名文字列の配列で指定された、オブジェクトのメソッド (括弧を除く) `[$object, 'methodName']` など -- クラス名文字列とメソッド名文字列の配列で指定された、静的なクラスメソッド (括弧を除く) `['ClassName', 'methodName']` など -- 無名関数 `function ($event) { ... }` など +- 文字列で指定されたグローバル PHP 関数 (括弧を除く)、例えば `'trim'`。 +- オブジェクトとメソッド名文字列の配列で指定された、オブジェクトのメソッド (括弧を除く)、例えば `[$object, 'methodName']`。 +- クラス名文字列とメソッド名文字列の配列で指定された、静的なクラスメソッド (括弧を除く)、例えば `['ClassName', 'methodName']`。 +- 無名関数、例えば `function ($event) { ... }`。 イベントハンドラのシグネチャはこのようになります: @@ -37,7 +37,7 @@ function ($event) { イベントハンドラのアタッチ ------------------------- +-------------------------- イベントハンドラは [[yii\base\Component::on()]] を呼び出すことでアタッチできます。たとえば: @@ -76,7 +76,7 @@ function function_name($event) { ``` イベントハンドラの順序 -------------------- +---------------------- ひとつのイベントには、ひとつだけでなく複数のハンドラをアタッチすることができます。イベントがトリガされると、アタッチされたハンドラは、 それらがイベントにアタッチされた順序どおりに呼び出されます。あるハンドラがその後に続くハンドラの呼び出しを停止する必要がある場合は、 @@ -98,8 +98,8 @@ $foo->on(Foo::EVENT_HELLO, function ($event) { }, $data, false); ``` -イベントのトリガー ------------------ +イベントのトリガ +---------------- イベントは、 [[yii\base\Component::trigger()]] メソッドを呼び出すことでトリガされます。このメソッドには **イベント名** が必須で、 オプションで、イベントハンドラに渡されるパラメータを記述したイベントオブジェクトを渡すこともできます。たとえば: @@ -163,7 +163,7 @@ class Mailer extends Component イベントハンドラのデタッチ ------------------------- +-------------------------- イベントからハンドラを取り外すには、 [[yii\base\Component::off()]] メソッドを呼び出します。たとえば: @@ -192,7 +192,7 @@ $foo->off(Foo::EVENT_HELLO); クラスレベル・イベントハンドラ --------------------------- +------------------------------ ここまでの項では、 *インスタンスレベル* でのイベントにハンドラをアタッチする方法を説明してきました。 場合によっては、特定のインスタンスだけではなく、クラスのすべてのインスタンスがトリガした @@ -214,7 +214,7 @@ Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ``` [[yii\db\ActiveRecord|ActiveRecord]] またはその子クラスのいずれかが、 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] -をトリガーするといつでも、このイベントハンドラが呼び出されます。ハンドラの中では、 `$event->sender` を通して、 +をトリガするといつでも、このイベントハンドラが呼び出されます。ハンドラの中では、 `$event->sender` を通して、 イベントをトリガしたオブジェクトを取得することができます。 オブジェクトがイベントをトリガするときは、最初にインスタンスレベルのハンドラを呼び出し、続いてクラスレベルのハンドラとなります。 @@ -233,9 +233,9 @@ Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { Event::trigger(Foo::className(), Foo::EVENT_HELLO); ``` -この場合、`$event->sender` は、オブジェクトインスタンスではなく、イベントをトリガーするクラスの名前を指すことに注意してください。 +この場合、`$event->sender` は、オブジェクトインスタンスではなく、イベントをトリガするクラスの名前を指すことに注意してください。 -> 注: クラスレベルのハンドラは、そのクラスのあらゆるインスタンス、またはあらゆる子クラスのインスタンスがトリガしたイベントに応答 +> Note: クラスレベルのハンドラは、そのクラスのあらゆるインスタンス、またはあらゆる子クラスのインスタンスがトリガしたイベントに応答 してしまうため、よく注意して使わなければなりません。 [[yii\base\Object]] のように、クラスが低レベルの基底クラスの場合は特にそうです。 クラスレベルのイベントハンドラを取り外すときは、 [[yii\base\Event::off()]] を呼び出します。たとえば: @@ -249,8 +249,78 @@ Event::off(Foo::className(), Foo::EVENT_HELLO); ``` +インターフェイスを使うイベント +------------------------------ + +イベントを扱うためには、もっと抽象的な方法もあります。 +特定のイベントのために専用のインターフェイスを作っておき、必要な場合にいろいろなクラスでそれを実装するのです。 + +例えば、次のようなインタフェイスを作ります。 + +```php +interface DanceEventInterface +{ + const EVENT_DANCE = 'dance'; +} +``` + +そして、それを実装する二つのクラスを作ります。 + +```php +class Dog extends Component implements DanceEventInterface +{ + public function meetBuddy() + { + echo "ワン!"; + $this->trigger(DanceEventInterface::EVENT_DANCE); + } +} + +class Developer extends Component implements DanceEventInterface +{ + public function testsPassed() + { + echo "よっしゃ!"; + $this->trigger(DanceEventInterface::EVENT_DANCE); + } +} +``` + +これらのクラスのどれかによってトリガされた `EVENT_DANCE` を扱うためには、インターフェイスの名前を最初の引数にして [[yii\base\Event::on()|Event::on()]] を呼びます。 + +```php +Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) { + Yii::trace($event->sender->className . ' が躍り上がって喜んだ。'); // 犬または開発者が躍り上がって喜んだことをログに記録。 +}) +``` + +これらのクラスのイベントをトリガすることも出来ます。 + +```php +Event::trigger(DanceEventInterface::className(), DanceEventInterface::EVENT_DANCE); +``` + +ただし、このインタフェイスを実装する全クラスのイベントをトリガすることは出来ない、ということに注意して下さい。 + +```php +// これは動かない +Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // エラー +``` + +イベントハンドラをデタッチするためには、[[yii\base\Event::off()|Event::off()]] を呼びます。 +例えば、 + +```php +// $handler をデタッチ +Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler); + +// DanceEventInterface::EVENT_DANCE の全てのハンドラをデタッチ +Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE); +``` + + グローバル・イベント -------------- +-------------------- Yiiは、実際に上記のイベントメカニズムに基づいたトリックである、いわゆる *グローバル・イベント* をサポートしています。 グローバル・イベントは、 [アプリケーション](structure-applications.md) インスタンス自身などの、グローバルにアクセス可能なシングルトンを必要とします。 diff --git a/docs/guide-ja/concept-properties.md b/docs/guide-ja/concept-properties.md index 31ee4b5f35..9177cc6889 100644 --- a/docs/guide-ja/concept-properties.md +++ b/docs/guide-ja/concept-properties.md @@ -2,8 +2,8 @@ ========== PHPでは、クラスのメンバ変数は *プロパティ* とも呼ばれます。これらの変数は、クラス定義の一部で、クラスのインスタンスの状態を表すために -(すなわち、クラスのあるインスタンスを別のものと区別するために) 使用されます。現実によく、特別な方法でこのプロパティの読み書きを扱いたい -場合があります。たとえば、`label` プロパティに割り当てられる文字列が常にトリミングされるようにしたい、など。その仕事を成し遂げるために、 +(すなわち、クラスのあるインスタンスを別のものと区別するために) 使用されます。現実には、特別な方法でこのプロパティの読み書きを扱いたい +場合がよくあります。たとえば、`label` プロパティに割り当てられる文字列が常にトリミングされるようにしたい、など。その仕事を成し遂げるために、 あなたは次のようなコードを使ってきたのではありませんか: ```php @@ -17,7 +17,7 @@ $object->label = trim($label); この問題を解決するために、Yii は *getter* メソッドと *setter* メソッドをベースにしたプロパティ定義をサポートする、 [[yii\base\Object]] 基底クラスを提供します。 クラスがその機能を必要とするなら、 [[yii\base\Object]] またはその子クラスを継承しましょう。 -> 補足: Yiiのフレームワークのほぼすべてのコアクラスは、 [[yii\base\Object]] またはその子クラスを継承しています。 +> Note: Yiiのフレームワークのほぼすべてのコアクラスは、 [[yii\base\Object]] またはその子クラスを継承しています。 これは、コアクラスに getter または setter があれば、それをプロパティのように使用できることを意味します。 getter メソッドは、名前が `get` で始まるメソッドで、setter メソッドは、`set` で始まるメソッドです。 @@ -44,7 +44,7 @@ class Foo extends Object } } ``` -(詳しく言うと、getter および setter メソッドは、この場合には、内部的に `_label` と名付けられた private 属性を参照する `label` プロパティを作っています。) +詳しく言うと、getter および setter メソッドは、この場合には、内部的に `_label` と名付けられた private 属性を参照する `label` プロパティを作っています。 getter と setter によって定義されたプロパティは、クラスのメンバ変数のように使用することができます。主な違いは、 それらのプロパティが読み取りアクセスされるときは、対応する getter ソッドが呼び出されることであり、プロパティに値が割り当てられるときには、 @@ -65,14 +65,15 @@ setter なしの getter で定義されたプロパティは、 *読み取り専 getter と setter で定義されたプロパティには、いくつかの特別なルールと制限があります: * この種のプロパティでは、名前の *大文字と小文字を区別しません* 。たとえば、 `$object->label` と `$object->Label` は同じです。 - これは、PHPのメソッド名が大文字と小文字を区別しないためです。 + これは、PHP のメソッド名が大文字と小文字を区別しないためです。 * この種のプロパティの名前と、クラスのメンバ変数の名前とが同じである場合、後者が優先されます。 たとえば、上記の `Foo` クラスがもしメンバ変数 `label` を持っているとすると、`$object->label = 'abc'` という代入は *メンバ変数の* `label` に作用することになり、その行で `setLabel()` setter メソッドは呼び出されなくなります。 -* これらのプロパティは可視性をサポートしていません。プロパティが public、protected、private であるかどうかで、 - getter または setter メソッドの定義に違いは生じません。 -* プロパティは、 *静的でない* getter および setter でしか定義できません。静的メソッドは同じようには扱われません。 +* これらのプロパティは可視性をサポートしていません。プロパティが public、protected、private であるかどうかを、 + getter または setter メソッドの定義によって決めることは出来ません。 +* プロパティは、 *静的でない* getter および setter によってのみ定義することが出来ます。静的なメソッドは同様には扱われません。 +* 通常の `property_exists()` の呼び出しでは、マジック・プロパティが存在するかどうかを知ることは出来ません。 + それぞれ、[[yii\base\Object::canGetProperty()|canGetProperty()]] または [[yii\base\Object::canSetProperty()|canSetProperty()]] を呼び出さなければなりません。 このガイドの冒頭で説明した問題に戻ると、 `label` に値が代入されているあらゆる箇所で `trim()` を呼ぶのではなく、もう `setLabel()` という setter の内部だけで `trim()` を呼べば済むのです。 さらに、新しい要求でラベルの先頭を大文字にする必要が発生しても、他のいっさいのコードに触れることなく、すぐに `setLabel()` メソッドを変更することができます。一箇所の変更は、すべての `label` への代入に普遍的に作用します。 - diff --git a/docs/guide-ja/concept-service-locator.md b/docs/guide-ja/concept-service-locator.md index 9bfdc18f28..5e9e99f14e 100644 --- a/docs/guide-ja/concept-service-locator.md +++ b/docs/guide-ja/concept-service-locator.md @@ -1,15 +1,14 @@ サービスロケータ -=============== +================ サービスロケータは、アプリケーションが必要とする可能性のある各種のサービス (またはコンポーネント) を提供する方法を知っているオブジェクトです。 -サービスロケータ内では、各コンポーネントは単一のインスタンスとして存在し、IDによって一意に識別されます。 -あなたは、このIDを使用してサービスロケータからコンポーネントを取得できます。 +サービスロケータ内では、各コンポーネントは単一のインスタンスとして存在し、ID によって一意に識別されます。 +あなたは、この ID を使用してサービスロケータからコンポーネントを取得できます。 Yii では、サービスロケータは単純に [[yii\di\ServiceLocator]] のインスタンス、または子クラスのインスタンスです。 -Yii の中で最も一般的に使用されるサービスロケータは、 *アプリケーション* オブジェクトで、 `\Yii::$app` -を通じてアクセスできます。それが提供するサービスは、 *アプリケーションコンポーネント* と呼ばれ、それは `request` 、 -`response`、 `urlManager` のようなコンポーネントです。あなたはサービスロケータによって提供される機能を通じて、 +Yii の中で最も一般的に使用されるサービスロケータは、`\Yii::$app` を通じてアクセスできる *アプリケーション* オブジェクトです。これが提供するサービスは、 *アプリケーションコンポーネント* と呼ばれる `request` 、 +`response`、 `urlManager` などのコンポーネントです。あなたはサービスロケータによって提供される機能を通じて、 簡単に、これらのコンポーネントを構成、あるいは独自の実装に置き換え、といったことができます。 アプリケーションオブジェクトの他に、各モジュールオブジェクトもまたサービスロケータです。 @@ -56,7 +55,7 @@ $cache = $locator->cache; それを返します。後でそのコンポーネントが再度アクセスされた場合、サービスロケータは同じインスタンスを返します。 [[yii\di\ServiceLocator::has()]] を使って、コンポーネント ID がすでに登録されているかをチェックできます。 -無効なIDで [[yii\di\ServiceLocator::get()]] を呼び出した場合、例外がスローされます。 +無効な ID で [[yii\di\ServiceLocator::get()]] を呼び出した場合、例外がスローされます。 サービスロケータは多くの場合、 [構成情報](concept-configurations.md) で作成されるため、 [[yii\di\ServiceLocator::setComponents()|components]] という名前の書き込み可能プロパティが提供されています。 diff --git a/docs/guide-ja/db-active-record.md b/docs/guide-ja/db-active-record.md index 4fcda60555..2d7ac63f68 100644 --- a/docs/guide-ja/db-active-record.md +++ b/docs/guide-ja/db-active-record.md @@ -1,6 +1,7 @@ アクティブレコード ================== +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: この節はまだ執筆中です。 @@ -14,6 +15,12 @@ アクティブレコードのインスタンスはそのテーブルの行に対応し、アクティブレコードのインスタンスの *属性* がその行にある特定のカラムの値を表現します。 生の SQL 文を書く代りに、アクティブレコードの属性にアクセスしたり、アクティブレコードのメソッドを呼んだりして、データベーステーブルに保存さているデータにアクセスしたり、データを操作したりします。 >>>>>>> yiichina/master +======= +[アクティブレコード](http://ja.wikipedia.org/wiki/Active_Record) は、データベースに保存されているデータにアクセスするために、オブジェクト指向のインタフェイスを提供するものです。 +アクティブレコードクラスはデータベーステーブルと関連付けられます。 +アクティブレコードのインスタンスはそのテーブルの行に対応し、アクティブレコードのインスタンスの *属性* がその行にある特定のカラムの値を表現します。 +生の SQL 文を書く代りに、アクティブレコードの属性にアクセスしたり、アクティブレコードのメソッドを呼んだりして、データベーステーブルに保存さているデータにアクセスしたり、データを操作したりします。 +>>>>>>> master 例えば、`Customer` が `customer` テーブルに関連付けられたアクティブレコードクラスであり、`name` が `customer` テーブルのカラムであると仮定しましょう。 `customer` テーブルに新しい行を挿入するために次のコードを書くことが出来ます。 @@ -24,11 +31,15 @@ $customer->name = 'Qiang'; $customer->save(); ``` +<<<<<<< HEAD <<<<<<< HEAD 上記のコードは、MySQL では、次のように生の SQL 文を使うのと等価なものです。 ======= 上記のコードは、MySQL では、次のような生の SQL 文を使うのと等価なものです。 >>>>>>> yiichina/master +======= +上記のコードは、MySQL では、次のような生の SQL 文を使うのと等価なものです。 +>>>>>>> master しかし、生の SQL 文の方は、直感的でなく、間違いも生じやすく、また、別の種類のデータベースを使う場合には、互換性の問題も生じ得ます。 ```php @@ -58,6 +69,7 @@ Yii は次のリレーショナルデータベースに対して、アクティ しかし、ここで説明するほとんどの内容は NoSQL データベースのためのアクティブレコードにも適用することが出来るものです。 +<<<<<<< HEAD <<<<<<< HEAD アクティブレコードクラスを宣言する ---------------------------------- @@ -70,6 +82,12 @@ Yii は次のリレーショナルデータベースに対して、アクティ まずは、[[yii\db\ActiveRecord]] を拡張してアクティブレコードクラスを宣言するところから始めましょう。 すべてのアクティブレコードクラスはデータベーステーブルと関連付けられますので、このクラスの中で [[yii\db\ActiveRecord::tableName()|tableName()]] メソッドをオーバーライドして、どのテーブルにこのクラスが関連付けられるかを指定しなければなりません。 >>>>>>> yiichina/master +======= +## アクティブレコードクラスを宣言する + +まずは、[[yii\db\ActiveRecord]] を拡張してアクティブレコードクラスを宣言するところから始めましょう。 +すべてのアクティブレコードクラスはデータベーステーブルと関連付けられますので、このクラスの中で [[yii\db\ActiveRecord::tableName()|tableName()]] メソッドをオーバーライドして、どのテーブルにこのクラスが関連付けられるかを指定しなければなりません。 +>>>>>>> master 次の例では、`customer` というデータベーステーブルのための `Customer` という名前のアクティブレコードクラスを宣言しています。 @@ -96,11 +114,15 @@ class Customer extends ActiveRecord アクティブレコードのインスタンスは [モデル](structure-models.md) であると見なされます。 この理由により、私たちは通常 `app\models` 名前空間 (あるいはモデルクラスを保管するための他の名前空間) の下にアクティブレコードクラスを置きます。 +<<<<<<< HEAD <<<<<<< HEAD [[yii\db\ActiveRecord]] は [[yii\base\Model]] から拡張していますので、属性、検証規則、データのシリアライゼーションなど、[モデル](structure-models.md) が持つ *全ての* 機能を継承しています。 ======= [[yii\db\ActiveRecord]] は [[yii\base\Model]] から拡張していますので、属性、検証規則、データのシリアル化など、[モデル](structure-models.md) が持つ *全ての* 機能を継承しています。 >>>>>>> yiichina/master +======= +[[yii\db\ActiveRecord]] は [[yii\base\Model]] から拡張していますので、属性、検証規則、データのシリアル化など、[モデル](structure-models.md) が持つ *全ての* 機能を継承しています。 +>>>>>>> master ## データベースに接続する @@ -121,11 +143,15 @@ return [ ]; ``` +<<<<<<< HEAD <<<<<<< HEAD アクティブレコードクラスのために `db` とは異なるデータベース接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドしなければなりません。 ======= `db` コンポーネントとは異なるデータベース接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドしなければなりません。 >>>>>>> yiichina/master +======= +`db` コンポーネントとは異なるデータベース接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドしなければなりません。 +>>>>>>> master ```php class Customer extends ActiveRecord @@ -151,10 +177,14 @@ class Customer extends ActiveRecord ご覧のように、このプロセスは [クエリビルダ](db-query-builder.md) による手続きと非常によく似ています。 <<<<<<< HEAD +<<<<<<< HEAD 唯一の違いは、`new` 演算子を使ってクエリオブジェクトを生成する代りに、[[yii\db\ActiveRecord::find()]] を呼んで [[yii\db\ActiveQuery]] クラスであるクエリオブジェクトを返すという点です。 ======= 唯一の違いは、`new` 演算子を使ってクエリオブジェクトを生成する代りに、[[yii\db\ActiveQuery]] クラスであるクエリオブジェクトを返す [[yii\db\ActiveRecord::find()]] を呼ぶ、という点です。 >>>>>>> yiichina/master +======= +唯一の違いは、`new` 演算子を使ってクエリオブジェクトを生成する代りに、[[yii\db\ActiveQuery]] クラスであるクエリオブジェクトを返す [[yii\db\ActiveRecord::find()]] を呼ぶ、という点です。 +>>>>>>> master 以下の例は、アクティブクエリを使ってデータをクエリする方法を示すものです。 @@ -178,11 +208,15 @@ $count = Customer::find() ->where(['status' => Customer::STATUS_ACTIVE]) ->count(); +<<<<<<< HEAD <<<<<<< HEAD // アクティブな全ての顧客を顧客IDによってインデックスされた配列として返す ======= // 全ての顧客を顧客IDによってインデックスされた配列として返す >>>>>>> yiichina/master +======= +// 全ての顧客を顧客IDによってインデックスされた配列として返す +>>>>>>> master // SELECT * FROM `customer` $customers = Customer::find() ->indexBy('id') @@ -191,12 +225,16 @@ $customers = Customer::find() 上記において、`$customer` は `Customer` オブジェクトであり、`$customers` は `Customer` オブジェクトの配列です。 <<<<<<< HEAD +<<<<<<< HEAD これらは全て `customer` テーブルから取得されたデータを投入されます。 ======= 全てこれらには `customer` テーブルから取得されたデータが投入されます。 >>>>>>> yiichina/master +======= +全てこれらには `customer` テーブルから取得されたデータが投入されます。 +>>>>>>> master -> Info|情報: [[yii\db\ActiveQuery]] は [[yii\db\Query]] から拡張しているため、[クエリビルダ](db-query-builder.md) の節で説明されたクエリ構築メソッドとクエリメソッドの *全て* を使うことが出来ます。 +> Info: [[yii\db\ActiveQuery]] は [[yii\db\Query]] から拡張しているため、[クエリビルダ](db-query-builder.md) の節で説明されたクエリ構築メソッドとクエリメソッドの *全て* を使うことが出来ます。 プライマリキーの値や一群のカラムの値でクエリをすることはよく行われる仕事ですので、Yii はこの目的のために、二つのショートカットメソッドを提供しています。 @@ -231,11 +269,12 @@ $customer = Customer::findOne([ // アクティブでない全ての顧客を返す // SELECT * FROM `customer` WHERE `status` = 0 -$customer = Customer::findAll([ +$customers = Customer::findAll([ 'status' => Customer::STATUS_INACTIVE, ]); ``` +<<<<<<< HEAD > Note|注意: [[yii\db\ActiveRecord::findOne()]] も [[yii\db\ActiveQuery::one()]] も、生成される SQL 文に `LIMIT 1` を追加しません。 <<<<<<< HEAD あなたのクエリが多数のデータ行を返すかもしれない場合は、パフォーマンスを向上させるために、例えば `Customer::find()->limit(1)->one()` のように、`limit(1)` を明示的に呼ぶべきです。 @@ -250,6 +289,14 @@ $customer = Customer::findAll([ クエリ構築メソッドを使う以外に、生の SQL を書いてデータをクエリして結果をアクティブレコードオブジェクトに投入することも出来ます。 そうするためには [[yii\db\ActiveRecord::findBySql()]] メソッドを呼ぶことが出来ます。 >>>>>>> yiichina/master +======= +> Note: [[yii\db\ActiveRecord::findOne()]] も [[yii\db\ActiveQuery::one()]] も、生成される SQL 文に `LIMIT 1` を追加しません。 + あなたのクエリが多数のデータ行を返すかもしれない場合は、パフォーマンスを向上させるために、`limit(1)` を明示的に呼ぶべきです。 + 例えば `Customer::find()->limit(1)->one()` のように。 + +クエリ構築メソッドを使う以外に、生の SQL を書いてデータをクエリして結果をアクティブレコードオブジェクトに投入することも出来ます。 +そうするためには [[yii\db\ActiveRecord::findBySql()]] メソッドを呼ぶことが出来ます。 +>>>>>>> master ```php // アクティブでない全ての顧客を返す @@ -257,10 +304,14 @@ $sql = 'SELECT * FROM customer WHERE status=:status'; $customers = Customer::findBySql($sql, [':status' => Customer::STATUS_INACTIVE])->all(); ``` <<<<<<< HEAD +<<<<<<< HEAD [[yii\db\ActiveRecord::queryBySql()|queryBySql()]] を呼んだ後では、無視されますので、クエリ構築メソッドを追加で呼び出してはいけません。 ======= [[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼んだ後は、追加でクエリ構築メソッドを呼び出してはいけません。呼んでも無視されます。 >>>>>>> yiichina/master +======= +[[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼んだ後は、追加でクエリ構築メソッドを呼び出してはいけません。呼んでも無視されます。 +>>>>>>> master ## データにアクセスする @@ -277,7 +328,7 @@ $id = $customer->id; $email = $customer->email; ``` -> Note|注意: アクティブレコードの属性の名前は、関連付けられたテーブルのカラムの名前に従って、大文字と小文字を区別して名付けられます。 +> Note: アクティブレコードの属性の名前は、関連付けられたテーブルのカラムの名前に従って、大文字と小文字を区別して名付けられます。 Yii は、関連付けられたテーブルの全てのカラムに対して、アクティブレコードの属性を自動的に定義します。 これらの属性は、すべて、再宣言してはいけません。 @@ -290,10 +341,14 @@ $email = $customer->email; 入力または表示されるデータの形式が、データベースにデータを保存するときに使われるものと異なる場合がよくあります。 例えば、データベースでは顧客の誕生日を UNIX タイムスタンプで保存している (まあ、あまり良い設計ではありませんが) けれども、ほとんどの場合において誕生日を `'YYYY/MM/DD'` という形式の文字列として操作したい、というような場合です。 <<<<<<< HEAD +<<<<<<< HEAD この目的を達するために、次のように、`Customer` アクティブレコードクラスにおいてデータ変換メソッドを定義することが出来ます。 ======= この目的を達するために、次のように、`Customer` アクティブレコードクラスにおいて *データ変換* メソッドを定義することが出来ます。 >>>>>>> yiichina/master +======= +この目的を達するために、次のように、`Customer` アクティブレコードクラスにおいて *データ変換* メソッドを定義することが出来ます。 +>>>>>>> master ```php class Customer extends ActiveRecord @@ -314,6 +369,7 @@ class Customer extends ActiveRecord このようにすれば、PHP コードにおいて、`$customer->birthday` にアクセスする代りに、`$customer->birthdayText` にアクセスすれば、顧客の誕生日を `'YYYY/MM/DD'` の形式で入力および表示することが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD ======= > Tip|ヒント: 上記は、一般にデータの変換を達成するための簡単な方法を示すためのものです。 @@ -321,6 +377,12 @@ class Customer extends ActiveRecord > DatePicker については、[JUI ウィジェットの節](widget-jui#datepicker-date-input) で説明されています。 >>>>>>> yiichina/master +======= +> Tip: 上記は、一般にデータの変換を達成するための簡単な方法を示すためのものです。 +> 日付の値については、Yii は、[DateValidator](tutorial-core-validators.md#date) と DatePicker ウィジェットを使用するという、より良い方法を提供しています。 +> DatePicker については、[JUI ウィジェットの節](widget-jui#datepicker-date-input) で説明されています。 + +>>>>>>> master ### データを配列に取得する @@ -335,6 +397,7 @@ $customers = Customer::find() ->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: このメソッドはメモリを節約してパフォーマンスを向上させますが、低レベルの DB 抽象レイヤに近いものであり、あなたはアクティブレコードの機能をほとんど失うことになります。 非常に重要な違いがカラムの値のデータタイプにあります。 @@ -342,6 +405,10 @@ $customers = Customer::find() > Note|注意: このメソッドはメモリを節約してパフォーマンスを向上させますが、低レベルの DB 抽象レイヤに近いものであり、あなたはアクティブレコードの機能のほとんどを失うことになります。 非常に重要な違いが、カラムの値のデータタイプに現れます。 >>>>>>> yiichina/master +======= +> Note: このメソッドはメモリを節約してパフォーマンスを向上させますが、低レベルの DB 抽象レイヤに近いものであり、あなたはアクティブレコードの機能のほとんどを失うことになります。 + 非常に重要な違いが、カラムの値のデータタイプに現れます。 +>>>>>>> master アクティブレコードインスタンスとしてデータを返す場合、カラムの値は実際のカラムの型に従って自動的に型キャストされます。 一方、配列としてデータを返す場合は、実際のカラムの型に関係なく、カラムの値は文字列になります。 なぜなら、何も処理をしない場合の PDO の結果は文字列だからです。 @@ -365,9 +432,13 @@ foreach (Customer::find()->each(10) as $customer) { // イーガーローディングをするバッチクエリ foreach (Customer::find()->with('orders')->each() as $customer) { <<<<<<< HEAD +<<<<<<< HEAD ======= // $customer は Customer オブジェクト >>>>>>> yiichina/master +======= + // $customer は 'orders' リレーションを投入された Customer オブジェクト +>>>>>>> master } ``` @@ -412,17 +483,22 @@ public function save($runValidation = true, $attributeNames = null) } ``` +<<<<<<< HEAD <<<<<<< HEAD > Tip|ヒント: [[yii\db\ActiveRecord::insert()|insert()]] または [[yii\db\ActiveRecord::update()|update()]] を直接に呼んでも、行を挿入または更新することが出来ます。 ======= > Tip|ヒント: [[yii\db\ActiveRecord::insert()|insert()]] または [[yii\db\ActiveRecord::update()|update()]] を直接に呼んで、行を挿入または更新することも出来ます。 >>>>>>> yiichina/master +======= +> Tip: [[yii\db\ActiveRecord::insert()|insert()]] または [[yii\db\ActiveRecord::update()|update()]] を直接に呼んで、行を挿入または更新することも出来ます。 +>>>>>>> master ### データの検証 [[yii\db\ActiveRecord]] は [[yii\base\Model]] を拡張したものですので、同じ [データ検証](input-validation.md) 機能を共有しています。 <<<<<<< HEAD +<<<<<<< HEAD 例えば、[[yii\base\Model::rules()|rules()]] メソッドをオーバーライドして検証規則を宣言することが出来ます。 [[yii\db\ActiveRecord::rules()|rules()]] メソッドをオーバーライドすることによって検証規則を宣言し、m[[yii\db\ActiveRecord::validate()|validate()]] メソッドを呼不ことによってテータの検証を実行することが出来ます。 @@ -432,10 +508,15 @@ public function save($runValidation = true, $attributeNames = null) [[yii\db\ActiveRecord::save()|save()]] を呼ぶと、デフォルトでは [[yii\db\ActiveRecord::validate()|validate()]] が自動的に呼ばれます。 >>>>>>> yiichina/master +======= +[[yii\db\ActiveRecord::rules()|rules()]] メソッドをオーバーライドすることによって検証規則を宣言し、[[yii\db\ActiveRecord::validate()|validate()]] メソッドを呼ぶことによってテータの検証を実行することが出来ます。 + +[[yii\db\ActiveRecord::save()|save()]] を呼ぶと、デフォルトでは [[yii\db\ActiveRecord::validate()|validate()]] が自動的に呼ばれます。 +>>>>>>> master 検証が通った時だけ、実際にデータが保存されます。 検証が通らなかった時は単に false が返され、[[yii\db\ActiveRecord::errors|errors]] プロパティをチェックして検証エラーメッセージを取得することが出来ます。 -> Tip|情報: データが検証を必要としないことが確実である場合 (例えば、データが信頼できるソースに由来するものである場合) は、検証をスキップするために `save(false)` を呼ぶことが出来ます。 +> Tip: データが検証を必要としないことが確実である場合 (例えば、データが信頼できるソースに由来するものである場合) は、検証をスキップするために `save(false)` を呼ぶことが出来ます。 ### 一括代入 @@ -471,12 +552,17 @@ $post = Post::findOne(100); $post->updateCounters(['view_count' => 1]); ``` +<<<<<<< HEAD > Note|注意: カウンタカラムを更新するのに [[yii\db\ActiveRecord::save()]] を使うと、不正確な結果になってしまう場合があります。 <<<<<<< HEAD というのは、同じカウンタの値を読み書きする複数のリクエストによって、同一のカウンタが保存される可能性があるからです。 ======= というのは、同じカウンタの値を読み書きする複数のリクエストによって、同一のカウンタが保存される可能性があるからです。 >>>>>>> yiichina/master +======= +> Note: カウンタカラムを更新するのに [[yii\db\ActiveRecord::save()]] を使うと、不正確な結果になってしまう場合があります。 + というのは、同じカウンタの値を読み書きする複数のリクエストによって、同一のカウンタが保存される可能性があるからです。 +>>>>>>> master ### ダーティな属性 @@ -492,14 +578,24 @@ $post->updateCounters(['view_count' => 1]); 最新の修正を受ける前の属性値を知りたい場合は、[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] または [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]] を呼ぶことが出来ます。 +> Note: 新旧の値は `===` 演算子を使って比較されるため、同じ値を持っていても型が違うとダーティであると見なされます。 +> このことは、モデルが HTML フォームからユーザの入力を受け取るときにしばしば生じます。 +> HTML フォームでは全ての値が文字列として表現されるからです。 +> 入力値が正しい型、例えば整数値となることを保証するために、`['attributeName', 'filter', 'filter' => 'intval']` のように [検証フィルタ](input-validation.md#data-filtering) を適用することが出来ます。 +> このフィルタは、[intval()](http://php.net/manual/ja/function.intval.php), [floatval()](http://php.net/manual/ja/function.floatval.php), +> [boolval](http://php.net/manual/ja/function.boolval.php) など、PHP の全てのタイプキャスト関数で動作します。 ### デフォルト属性値 +<<<<<<< HEAD <<<<<<< HEAD あなたのテーブルのカラムの中には、データベースでデフォルト値が定義されているものもあるかも知れません。 ======= あなたのテーブルのカラムの中には、データベースでデフォルト値が定義されているものがあるかも知れません。 >>>>>>> yiichina/master +======= +あなたのテーブルのカラムの中には、データベースでデフォルト値が定義されているものがあるかも知れません。 +>>>>>>> master そして、場合によっては、アクティブレコードインスタンスのウェブフォームに、そういうデフォルト値をあらかじめ投入したいことがあるでしょう。 同じデフォルト値を繰り返して書くことを避けるために、[[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] を呼んで、DB で定義されたデフォルト値を対応するアクティブレコードの属性に投入することが出来ます。 @@ -543,7 +639,7 @@ $customer->delete(); Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); ``` -> Note|注意: [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] を呼ぶときは、十分に注意深くしてください。 +> Note: [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] を呼ぶときは、十分に注意深くしてください。 なぜなら、条件の指定を間違うと、あなたのテーブルからすべてのデータを完全に消し去ってしまうことになるからです。 @@ -583,14 +679,18 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); 1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントをトリガ。 このメソッドが false を返すか、[[yii\base\ModelEvent::isValid]] が false であった場合、残りのステップはスキップされる。 <<<<<<< HEAD +<<<<<<< HEAD 2. データ検証を実行。データ検証が失敗した場合、3 以降のステップはスキップされる。 ======= 2. データ検証を実行。データ検証が失敗した場合、3 より後のステップはスキップされる。 >>>>>>> yiichina/master +======= +2. データ検証を実行。データ検証が失敗した場合、3 より後のステップはスキップされる。 +>>>>>>> master 3. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] イベントをトリガ。 4. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] または [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] イベントをトリガ。 このメソッドが false を返すか、[[yii\base\ModelEvent::isValid]] が false であった場合、残りのステップはスキップされる。 -5. 実際のデータの挿入または更新を実行する。 +5. 実際のデータの挿入または更新を実行。 6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] または [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] イベントをトリガ。 @@ -600,18 +700,34 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); 1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] イベントをトリガ。 このメソッドが false を返すか、[[yii\base\ModelEvent::isValid]] が false であった場合は、残りのステップはスキップされる。 -2. 実際のデータの削除を実行する。 +2. 実際のデータの削除を実行。 3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] イベントをトリガ。 -> Note|注意: 次のメソッドは、どれを呼んでも、上記のライフサイクルを開始させません。 +> Note: 次のメソッドを呼んだ場合は、いずれの場合も、上記のライフサイクルのどれかを開始させることはありません。 +> これらのメソッドは、レコード単位ではなく、データベース上で直接に動作するためです。 > > - [[yii\db\ActiveRecord::updateAll()]] > - [[yii\db\ActiveRecord::deleteAll()]] > - [[yii\db\ActiveRecord::updateCounters()]] > - [[yii\db\ActiveRecord::updateAllCounters()]] +### データをリフレッシュする際のライフサイクル +[[yii\db\ActiveRecord::refresh()|refresh()]] を呼んでアクティブレコードインスタンスをリフレッシュする際は、リフレッシュが成功してメソッドが `true` を返すと +[[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] イベントがトリガされます。 + + +## トランザクションを扱う + +アクティブレコードを扱う際には、二つの方法で [トランザクション](db-dao.md#performing-transactions) を処理することができます。 + +最初の方法は、次に示すように、アクティブレコードのメソッドの呼び出しを明示的にトランザクションのブロックで囲む方法です。 + +```php +$customer = Customer::findOne(123); + +<<<<<<< HEAD <<<<<<< HEAD ## トランザクション操作 @@ -650,6 +766,29 @@ try { 第二の方法は、トランザクションのサポートが必要な DB 操作を [[yii\db\ActiveRecord::transactions()]] メソッドに列挙するという方法です。 >>>>>>> yiichina/master +======= +Customer::getDb()->transaction(function($db) use ($customer) { + $customer->id = 200; + $customer->save(); + // ... 他の DB 操作 ... +}); + +// あるいは、別の方法 + +$transaction = Customer::getDb()->beginTransaction(); +try { + $customer->id = 200; + $customer->save(); + // ... 他の DB 操作 ... + $transaction->commit(); +} catch(\Exception $e) { + $transaction->rollBack(); + throw $e; +} +``` + +第二の方法は、トランザクションのサポートが必要な DB 操作を [[yii\db\ActiveRecord::transactions()]] メソッドに列挙するという方法です。 +>>>>>>> master ```php class Post extends \yii\db\ActiveRecord @@ -666,6 +805,7 @@ class Post extends \yii\db\ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD 上記において、`admin` と `api` はモデルのシナリオであり、`OP_` で始まる定数は、これらのシナリオについてトランザクションで囲まれるべき操作を示しています。 サポートされている操作は、`OP_INSERT`、`OP_UPDATE`、そして、`OP_DELETE` です。 @@ -683,6 +823,20 @@ class Post extends \yii\db\ActiveRecord 複数の操作を示すためには、`|` を使って上記の定数を連結してください。 ショートカット定数 [[yii\db\ActiveRecord::OP_ALL|OP_ALL]] を使って、上記の三つの操作すべてを示すことも出来ます。 >>>>>>> yiichina/master +======= +[[yii\db\ActiveRecord::transactions()]] メソッドが返す配列では、キーは [シナリオ](structure-models.md#scenarios) の名前であり、値はトランザクションで囲まれるべき操作でなくてはなりません。 +いろいろな DB 操作を参照するのには、次の定数を使わなければなりません。 + +* [[yii\db\ActiveRecord::OP_INSERT|OP_INSERT]]: [[yii\db\ActiveRecord::insert()|insert()]] によって実行される挿入の操作。 +* [[yii\db\ActiveRecord::OP_UPDATE|OP_UPDATE]]: [[yii\db\ActiveRecord::update()|update()]] によって実行される更新の操作。 +* [[yii\db\ActiveRecord::OP_DELETE|OP_DELETE]]: [[yii\db\ActiveRecord::delete()|delete()]] によって実行される削除の操作。 + +複数の操作を示すためには、`|` を使って上記の定数を連結してください。 +ショートカット定数 [[yii\db\ActiveRecord::OP_ALL|OP_ALL]] を使って、上記の三つの操作すべてを示すことも出来ます。 + +このメソッドを使って生成されたトランザクションは、[[yii\db\ActiveRecord::beforeSave()|beforeSave()]] を呼ぶ前に開始され、 +[[yii\db\ActiveRecord::afterSave()|afterSave()]] を実行した後にコミットされます。 +>>>>>>> master ## 楽観的ロック @@ -690,6 +844,7 @@ class Post extends \yii\db\ActiveRecord 楽観的ロックは、一つのデータ行が複数のユーザによって更新されるときに発生しうる衝突を回避するための方法です。 例えば、ユーザ A と ユーザ B が 同時に同じ wiki 記事を編集しており、ユーザ A が自分の編集結果を保存した後に、ユーザ B も自分の編集結果を保存しようとして「保存」ボタンをクリックする場合を考えてください。 <<<<<<< HEAD +<<<<<<< HEAD ユーザ B は、実際には古くなったバージョンの記事に対する操作をしようとしていますので、彼が記事を保存するのを防止して、彼に何らかのヒントメッセージを表示する方策を取ることが望まれます。 楽観的ロックは、あるカラムを使って各行のバージョン番号を記録するという方法によって、上記の問題を解決します。 @@ -700,33 +855,86 @@ class Post extends \yii\db\ActiveRecord 楽観的ロックは、あるカラムを使って各行のバージョン番号を記録するという方法によって、上記の問題を解決します。 古くなったバージョン番号とともに行を保存しようとすると、[[yii\db\StaleObjectException]] 例外が投げられて、行が保存されるのが防止されます。 >>>>>>> yiichina/master +======= +ユーザ B は、実際には古くなったバージョンの記事に対する操作をしようとしていますので、彼が記事を保存するのを防止し、彼に何らかのヒントメッセージを表示する方法があることが望まれます。 + +楽観的ロックは、あるカラムを使って各行のバージョン番号を記録するという方法によって、上記の問題を解決します。 +古くなったバージョン番号とともに行を保存しようとすると、[[yii\db\StaleObjectException]] 例外が投げられて、行が保存されるのが防止されます。 +>>>>>>> master 楽観的ロックは、 [[yii\db\ActiveRecord::update()]] または [[yii\db\ActiveRecord::delete()]] メソッドを使って既存の行を更新または削除しようとする場合にだけサポートされます。 楽観的ロックを使用するためには、次のようにします。 +<<<<<<< HEAD <<<<<<< HEAD 1. 各行のバージョン番号を保存するカラムを作成します。カラムのタイプは `BIGINT DEFAULT 0` でなければなりません。 `optimisticLock()` メソッドをオーバーライドして、このカラムの名前を返すようにします。 2. ユーザ入力を収集するウェブフォームに、更新されるレコードのロックバージョンを保持する隠しフィールドを追加します。 3. データ更新を行うコントローラアクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、衝突を解決するために必要なビジネスロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。 +======= +1. アクティブレコードクラスと関連付けられている DB テーブルに、各行のバージョン番号を保存するカラムを作成します。 + カラムは長倍精度整数 (big integer) タイプでなければなりません (MySQL では `BIGINT DEFAULT 0` です)。 +2. [[yii\db\ActiveRecord::optimisticLock()]] メソッドをオーバーライドして、このカラムの名前を返すようにします。 +3. ユーザ入力を収集するウェブフォームに、更新されるレコードの現在のバージョン番号を保持する隠しフィールドを追加します。 + バージョン属性が入力の検証規則を持っており、検証が成功することを確かめてください。 +4. アクティブレコードを使って行の更新を行うコントローラアクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、衝突を解決するために必要なビジネスロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。 +>>>>>>> master - -## リレーショナルデータを扱う - -テーブルのリレーショナルデータもアクティブレコードを使ってクエリすることが出来ます -(すなわち、テーブル A のデータを選択すると、テーブル B の関連付けられたデータも一緒に取り込むことが出来ます)。 -アクティブレコードのおかげで、返されるリレーショナルデータは、プライマリテーブルと関連付けられたアクティブレコードオブジェクトのプロパティのようにアクセスすることが出来ます。 - -例えば、適切なリレーションが宣言されていれば、`$customer->orders` にアクセスすることによって、指定された顧客が発行した注文を表す `Order` オブジェクトの配列を取得することが出来ます。 - -リレーションを宣言するためには、[[yii\db\ActiveQuery]] オブジェクトを返すゲッターメソッドを定義します。そして、その [[yii\db\ActiveQuery]] オブジェクトに、リレーションのコンテキストに関する情報を持たせ、従って関連するレコードだけをクエリさせます。 -例えば、 +例えば、バージョン番号のカラムが `version` と名付けられているとすると、次のようなコードによって楽観的ロックを実装することが出来ます。 ```php -class Customer extends \yii\db\ActiveRecord +// ------ ビューのコード ------- + +use yii\helpers\Html; + +// ... 他の入力フィールド +echo Html::activeHiddenInput($model, 'version'); + + +// ------ コントローラのコード ------- + +use yii\db\StaleObjectException; + +public function actionUpdate($id) { + $model = $this->findModel($id); + + try { + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } catch (StaleObjectException $e) { + // 衝突を解決するロジック + } +} +``` + + +## リレーショナルデータを扱う + +個々のデータベーステーブルを扱うだけでなく、アクティブレコードは関連したテーブルのデータも一緒に読み出して、主たるデータを通して簡単にアクセス出来るようにすることが出来ます。 +例えば、一人の顧客は一つまたは複数の注文を発することがあり得ますので、顧客のデータは注文のデータと関連を持っていることになります。 +このリレーションが適切に宣言されていれば、`$customer->orders` という式を使って顧客の注文情報にアクセスすることが出来ます。 +`$customer->orders` は、顧客の注文情報を `Order` アクティブレコードインスタンスの配列として返してくれます。 + + +### リレーションを宣言する + +アクティブレコードを使ってリレーショナルデータを扱うためには、最初に、アクティブレコードクラスの中でリレーションを宣言する必要があります。 +これは、以下のように、関心のあるそれぞれのリレーションについて *リレーションメソッド* を宣言するだけの簡単な作業です。 + +```php +class Customer extends ActiveRecord +{ + // ... + public function getOrders() { +<<<<<<< HEAD // Customer は Order.customer_id -> id によって、複数の Order を持つ ======= 1. アクティブレコードクラスと関連付けられている DB テーブルに、各行のバージョン番号を保存するカラムを作成します。 @@ -789,15 +997,24 @@ class Customer extends ActiveRecord public function getOrders() { >>>>>>> yiichina/master +======= +>>>>>>> master return $this->hasMany(Order::className(), ['customer_id' => 'id']); } } +<<<<<<< HEAD <<<<<<< HEAD class Order extends \yii\db\ActiveRecord +======= +class Order extends ActiveRecord +>>>>>>> master { + // ... + public function getCustomer() { +<<<<<<< HEAD // Order は Customer.id -> customer_id によって、一つの Customer を持つ ======= class Order extends ActiveRecord @@ -805,46 +1022,96 @@ class Order extends ActiveRecord public function getCustomer() { >>>>>>> yiichina/master +======= +>>>>>>> master return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } ``` +<<<<<<< HEAD <<<<<<< HEAD 上記の例で使用されている [[yii\db\ActiveRecord::hasMany()]] と [[yii\db\ActiveRecord::hasOne()]] のメソッドは、リレーショナルデータベースにおける多対一と一対一の関係を表現するために使われます。 例えば、顧客 (customer) は複数の注文 (order) を持ち、注文 (order) は一つの顧客 (customer)を持つ、という関係です。 これらのメソッドはともに二つのパラメータを取り、[[yii\db\ActiveQuery]] オブジェクトを返します。 +======= +上記のコードでは、`Customer` クラスのために `orders` リレーションを宣言し、`Order` クラスのために `customer` リレーションを宣言しています。 +>>>>>>> master - - `$class`: 関連するモデルのクラス名。これは完全修飾のクラス名でなければなりません。 - - `$link`: 二つのテーブルに属するカラム間の関係。これは配列として与えられなければなりません。 - 配列のキーは、`$class` と関連付けられるテーブルにあるカラムの名前であり、配列の値はリレーションを宣言しているクラスのテーブルにあるカラムの名前です。 - リレーションをテーブルの外部キーに基づいて定義するのが望ましいプラクティスです。 +各リレーションメソッドは `getXyz` という名前にしなければなりません。 +ここで `xyz` (最初の文字は小文字です) が *リレーション名* と呼ばれます。 +リレーション名は *大文字と小文字を区別する* ことに注意してください。 -リレーションを宣言した後は、リレーショナルデータを取得することは、対応するゲッターメソッドで定義されているコンポーネントのプロパティを取得するのと同じように、とても簡単なことになります。 +リレーションを宣言する際には、次の情報を指定しなければなりません。 + +- リレーションの多重性: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]] のどちらかを呼ぶことによって指定されます。 + 上記の例では、リレーションの宣言において、顧客は複数の注文を持ち得るが、一方、注文は一人の顧客しか持たない、ということが容易に読み取れます。 +- 関連するアクティブレコードクラスの名前: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]] の最初のパラメータとして指定されます。 + クラス名を取得するのに `Xyz::className()` を呼ぶのが推奨されるプラクティスです。 + そうすれば、IDE の自動補完のサポートを得ることことが出来るだけでなく、コンパイル段階でエラーを検出することが出来ます。 +- 二つのデータタイプ間のリンク: 二つのデータタイプの関連付けに用いられるカラムを指定します。 + 配列の値は主たるデータ (リレーションを宣言しているアクティブレコードクラスによって表されるデータ) のカラムであり、配列のキーは関連するデータのカラムです。 + + +### リレーショナルデータにアクセスする + +リレーションを宣言した後は、リレーション名を通じてリレーショナルデータにアクセスすることが出来ます。 +これは、リレーションメソッドによって定義されるオブジェクト [プロパティ](concept-properties.md) にアクセスするのと同様です。 +このため、これを *リレーションプロパティ* と呼びます。 +例えば、 ```php -// 顧客の注文を取得する -$customer = Customer::findOne(1); -$orders = $customer->orders; // $orders は Order オブジェクトの配列 +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +// $orders is an array of Order objects +$orders = $customer->orders; ``` -舞台裏では、上記のコードは、各行について一つずつ、次の二つの SQL クエリを実行します。 +> Info: `xyz` という名前のリレーションを getter メソッド `getXyz()` によって宣言すると、`xyz` を [オブジェクトプロパティ](concept-properties.md) のようにアクセスすることが出来るようになります。 + 名前は大文字と小文字を区別することに注意してください。 -```sql -SELECT * FROM customer WHERE id=1; -SELECT * FROM order WHERE customer_id=1; -``` +リレーションが [[yii\db\ActiveRecord::hasMany()|hasMany()]] によって宣言されている場合は、このリレーションプロパティにアクセスすると、関連付けられたアクティブレコードインスタンスの配列が返されます。 +リレーションが [[yii\db\ActiveRecord::hasOne()|hasOne()]] によって宣言されている場合は、このリレーションプロパティにアクセスすると、関連付けられたアクティブレコードインスタンスか、関連付けられたデータが見つからないときは null が返されます。 -> Tip|情報: `$customer->orders` という式に再びアクセスした場合は、第二の SQL クエリはもう実行されません。 - 第二の SQL クエリは、この式が最初にアクセスされた時だけ実行されます。 - 二度目以降のアクセスでは、内部的にキャッシュされている以前に読み出した結果が返されるだけです。 - リレーショナルデータを再クエリしたい場合は、単純に、まず既存の式を未設定状態に戻して (`unset($customer->orders);`) から、再度、`$customer->orders` にアクセスします。 +リレーションプロパティに最初にアクセスしたときは、上記の例で示されているように、SQL 文が実行されます。 +その同じプロパティに再びアクセスしたときは、SQL 文を再実行することなく、以前の結果が返されます。 +SQL 文の再実行を強制するためには、まず、リレーションプロパティの割り当てを解除 (unset) しなければなりません : `unset($customer->orders)`。 -場合によっては、リレーショナルクエリにパラメータを渡したいことがあります。 -例えば、顧客の注文を全て返す代りに、小計が指定した金額を超える大きな注文だけを返したいことがあるでしょう。 -そうするためには、次のようなゲッターメソッドで `bigOrders` リレーションを宣言します。 +> Note: リレーションプロパティの概念は [オブジェクトプロパティ](concept-properties.md) の機能と同一であるように見えますが、一つ、重要な相違点があります。 +> 通常のオブジェクトプロパティでは、プロパティの値はそれを定義する getter メソッドと同じ型を持ちます。 +> しかし、リレーションプロパティにアクセスすると [[yii\db\ActiveRecord]] のインスタンスまたはその配列が返されるのに対して、リレーションメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。 +> +> ```php +> $customer->orders; // `Order` オブジェクトの配列 +> $customer->getOrders(); // ActiveQuery のインスタンス +> ``` +> +> このことは、次の項で説明するように、カスタマイズしたクエリを作成するのに役に立ちます。 + +### 動的なリレーショナルクエリ + +リレーションメソッドは [[yii\db\ActiveQuery]] のインスタンスを返すため、DB クエリを実行する前に、クエリ構築メソッドを使ってこのクエリを更に修正することが出来ます。 +例えば、 ```php +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id` +$orders = $customer->getOrders() + ->where(['>', 'subtotal', 200]) + ->orderBy('id') + ->all(); +``` + +リレーションプロパティにアクセスする場合と違って、リレーションメソッドによって動的なリレーショナルクエリを実行する場合は、同じ動的なリレーショナルクエリが以前に実行されたことがあっても、毎回、SQL 文が実行されます。 + +さらに進んで、もっと簡単に動的なリレーショナルクエリを実行できるように、リレーションの宣言をパラメータ化したい場合もあるでしょう。 +例えば、`bigOrders` リレーションを下記のように宣言することが出来ます。 + +```php +<<<<<<< HEAD class Customer extends \yii\db\ActiveRecord ======= 上記のコードでは、`Customer` クラスのために `orders` リレーションを宣言し、`Order` クラスのために `customer` リレーションを宣言しています。 @@ -924,6 +1191,9 @@ $orders = $customer->getOrders() ```php class Customer extends ActiveRecord >>>>>>> yiichina/master +======= +class Customer extends ActiveRecord +>>>>>>> master { public function getBigOrders($threshold = 100) { @@ -934,30 +1204,37 @@ class Customer extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD `hasMany()` が 返す [[yii\db\ActiveQuery]] は、[[yii\db\ActiveQuery]] のメソッドを呼ぶことでクエリをカスタマイズ出来るものであることを覚えておいてください。 上記の宣言によって、`$customer->bigOrders` にアクセスした場合は、小計が 100 以上である注文だけが返されることになります。 異なる閾値を指定するためには、次のコードを使用します。 +======= +これによって、次のようなリレーショナルクエリを実行することが出来るようになります。 +>>>>>>> master ```php +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id` $orders = $customer->getBigOrders(200)->all(); + +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 100 ORDER BY `id` +$orders = $customer->bigOrders; ``` -> Note|注意: リレーションメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。 -リレーションを属性 (すなわち、クラスのプロパティ) としてアクセスした場合は、返り値はリレーションのクエリ結果となります。 -クエリ結果は、リレーションが複数のレコードを返すものか否かに応じて、[[yii\db\ActiveRecord]] の一つのインスタンス、またはその配列、または null となります。 -例えば、`$customer->getOrders()` は `ActiveQuery` のインスタンスを返し、`$customer->orders` は `Order` オブジェクトの配列 (またはクエリ結果が無い場合は空の配列) を返します。 +### 中間テーブルによるリレーション -### 中間テーブルを使うリレーション +データベースの設計において、二つの関連するテーブル間の多重性が多対多である場合は、通常、[中間テーブル](https://en.wikipedia.org/wiki/Junction_table) が導入されます。 +例えば、`order` テーブルと `item` テーブルは、`order_item` と言う名前の中間テーブルによって関連付けることが出来ます。 +このようにすれば、一つの注文を複数の商品に対応させ、また、一つの商品を複数の注文に対応させることが出来ます。 -場合によっては、二つのテーブルが [中間テーブル][] と呼ばれる中間的なテーブルによって関連付けられていることがあります。 -そのようなリレーションを宣言するために、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] メソッドを呼んで、[[yii\db\ActiveQuery]] オブジェクトをカスタマイズすることが出来ます。 - -例えば、テーブル `order` とテーブル `item` が中間テーブル `order_item` によって関連付けられている場合、`Order` クラスにおいて `items` リレーションを次のように宣言することが出来ます。 +このようなリレーションを宣言するときは、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] のどちらかを呼んで中間テーブルを指定します。 +[[yii\db\ActiveQuery::via()|via()]] と [[yii\db\ActiveQuery::viaTable()|viaTable()]] の違いは、前者が既存のリレーション名の形式で中間テーブルを指定するのに対して、後者は中間テーブルを直接に指定する、という点です。 +例えば、 ```php +<<<<<<< HEAD class Order extends \yii\db\ActiveRecord ======= これによって、次のようなリレーショナルクエリを実行することが出来るようになります。 @@ -984,6 +1261,9 @@ $orders = $customer->bigOrders; ```php class Order extends ActiveRecord >>>>>>> yiichina/master +======= +class Order extends ActiveRecord +>>>>>>> master { public function getItems() { @@ -993,6 +1273,7 @@ class Order extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD [[yii\db\ActiveQuery::via()|via()]] メソッドは、最初のパラメータとして、結合テーブルの名前ではなく、アクティブレコードクラスで宣言されているリレーションの名前を取ること以外は、[[yii\db\ActiveQuery::viaTable()|viaTable()]] と同じです。 例えば、上記の `items` リレーションは次のように宣言しても等価です。 @@ -1005,6 +1286,12 @@ class Order extends \yii\db\ActiveRecord ```php class Order extends ActiveRecord >>>>>>> yiichina/master +======= +あるいは、また、 + +```php +class Order extends ActiveRecord +>>>>>>> master { public function getOrderItems() { @@ -1019,22 +1306,39 @@ class Order extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD [中間テーブル]: https://en.wikipedia.org/wiki/Junction_table "Junction table on Wikipedia" +======= +中間テーブルを使って宣言されたリレーションの使い方は、通常のリレーションと同じです。 +例えば、 +>>>>>>> master + +```php +// SELECT * FROM `order` WHERE `id` = 100 +$order = Order::findOne(100); + +// SELECT * FROM `order_item` WHERE `order_id` = 100 +// SELECT * FROM `item` WHERE `item_id` IN (...) +// 商品オブジェクトの配列を返す +$items = $order->items; +``` -### レイジーローディングとイーガーローディング +### レイジーローディングとイーガーローディング -前に述べたように、関連オブジェクトに最初にアクセスしたときに、アクティブレコードは DB クエリを実行して関連データを読み出し、それを関連オブジェクトに投入します。 -同じ関連オブジェクトに再度アクセスしても、クエリは実行されません。 -これを *レイジーローディング* と呼びます。 +[リレーショナルデータにアクセスする](#accessing-relational-data) において、通常のオブジェクトプロパティにアクセスするのと同じようにして、アクティブレコードインスタンスのリレーションプロパティにアクセスすることが出来ることを説明しました。 +SQL 文は、リレーションプロパティに最初にアクセスするときにだけ実行されます。 +このようなリレーショナルデータのアクセス方法を *レイジーローディング* と呼びます。 例えば、 ```php -// 実行される SQL: SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// 実行される SQL: SELECT * FROM order WHERE customer_id=1 +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 $orders = $customer->orders; +<<<<<<< HEAD ======= 中間テーブルを使って宣言されたリレーションの使い方は、通常のリレーションと同じです。 例えば、 @@ -1065,31 +1369,39 @@ $customer = Customer::findOne(123); $orders = $customer->orders; >>>>>>> yiichina/master +======= + +>>>>>>> master // SQL は実行されない $orders2 = $customer->orders; ``` +<<<<<<< HEAD <<<<<<< HEAD レイジーローディングは非常に使い勝手が良いものです。しかし、次のシナリオでは、パフォーマンスの問題を生じ得ます。 +======= +レイジーローディングは非常に使い勝手が良いものです。 +しかし、複数のアクティブレコードインスタンスの同じリレーションプロパティにアクセスする必要がある場合は、パフォーマンスの問題を生じ得ます。 +次のコードサンプルを考えて見てください。実行される SQL 文の数はいくらになるでしょう? +>>>>>>> master ```php -// 実行される SQL: SELECT * FROM customer WHERE id=1 +// SELECT * FROM `customer` LIMIT 100 $customers = Customer::find()->limit(100)->all(); foreach ($customers as $customer) { - // 実行される SQL: SELECT * FROM order WHERE customer_id=... + // SELECT * FROM `order` WHERE `customer_id` = ... $orders = $customer->orders; - // ... $orders を処理 ... } ``` -データベースに 100 人以上の顧客が登録されていると仮定した場合、上記のコードで何個の SQL クエリが実行されるでしようか? -101 です。最初の SQL クエリが 100 人の顧客を返します。 -次に、100 人の顧客全てについて、それぞれ、顧客の注文を返すための SQL クエリが実行されます。 +上のコードのコメントから判るように、実行される SQL 文は 101 にもなります。 +これは、for ループの中で、異なる `Customer` オブジェクトの `orders` リレーションにアクセスするたびに、SQL 文が一つ実行されることになるからです。 -上記のパフォーマンスの問題を解決するためには、[[yii\db\ActiveQuery::with()]] を呼んでいわゆる *イーガーローディング* を使うことが出来ます。 +このパフォーマンスの問題を解決するために、次に示すように、いわゆる *イーガーローディング* の手法を使うことが出来ます。 ```php +<<<<<<< HEAD // 実行される SQL: SELECT * FROM customer LIMIT 100; // SELECT * FROM orders WHERE customer_id IN (1,2,...) $customers = Customer::find()->limit(100) @@ -1115,40 +1427,71 @@ foreach ($customers as $customer) { このパフォーマンスの問題を解決するために、次に示すように、いわゆる *イーガーローディング* の手法を使うことが出来ます。 ```php +======= +>>>>>>> master // SELECT * FROM `customer` LIMIT 100; // SELECT * FROM `orders` WHERE `customer_id` IN (...) $customers = Customer::find() ->with('orders') ->limit(100) ->all(); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master foreach ($customers as $customer) { // SQL は実行されない $orders = $customer->orders; +<<<<<<< HEAD <<<<<<< HEAD // ... $orders を処理 ... +======= +>>>>>>> master } ``` -ご覧のように、同じ仕事をするのに必要な SQL クエリがたった二つになります。 +[[yii\db\ActiveQuery::with()]] を呼ぶことによって、最初の 100 人の顧客の注文をたった一つの SQL 文で返すように、アクティブレコードに指示をしています。 +結果として、実行される SQL 文の数は 101 から 2 に減ります。 -> Info|情報: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが `via()` または `viaTable()` によって定義されている場合、この `N` 個のリレーションをイーガーロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。 -> 主たるテーブルの行を返すために一つ、`via()` または `viaTable()` の呼び出しに対応する `M` 個の中間テーブルのそれぞれに対して一つずつ、そして、`N` 個の関連テーブルのそれぞれに対して一つずつ、という訳です。 +イーガーローディングは、一つだけでなく、複数のリレーションに対しても使うことが出来ます。 +さらには、*ネストされたリレーション* でさえ、イーガーロードすることが出来ます。 +ネストされたリレーションというのは、関連するアクティブレコードの中で宣言されているリレーションです。 +例えば、`Cutomer` が `orders` リレーションによって `Order` と関連しており、`Order` が `items` リレーションによって `Item` と関連している場合です。 +`Customer` に対するクエリを実行するときに、ネストされたリレーションの記法である `orders.items` を使って、`items` をイーガーロードすることが出来ます。 -> Note|注意: イーガーローディングで `select()` をカスタマイズしようとする場合は、関連モデルにリンクするカラムを必ず含めてください。 -> そうしないと、関連モデルは読み出されません。例えば、 +次のコードは、[[yii\db\ActiveQuery::with()|with()]] のさまざまな使い方を示すものです。 +ここでは、`Customer` クラスは `orders` と `country` という二つのリレーションを持っており、また、`Order` クラスは `items` という一つのリレーションを持っていると仮定しています。 ```php -$orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); -// $orders[0]->customer は常に null になる。この問題を解決するためには、次のようにしなければならない。 -$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +// "orders" と "country" の両方をイーガーロードする +$customers = Customer::find()->with('orders', 'country')->all(); +// これは下の配列記法と等価 +$customers = Customer::find()->with(['orders', 'country'])->all(); +// SQL は実行されない +$orders= $customers[0]->orders; +// SQL は実行されない +$country = $customers[0]->country; + +// "orders" リレーションと、ネストされた "orders.items" をイーガーロード +$customers = Customer::find()->with('orders.items')->all(); +// 最初の顧客の、最初の注文の品目にアクセスする +// SQL は実行されない +$items = $customers[0]->orders[0]->items; ``` -場合によっては、リレーショナルクエリをその場でカスタマイズしたいことがあるでしょう。 -これは、レイジーローディングでもイーガーローディングでも、可能です。例えば、 +深くネストされたリレーション、たとえば `a.b.c.c` をイーガーロードすることも出来ます。 +このとき、すべての親リレーションもイーガーロードされます。 +つまり、`a.b.c.d` を使って [[yii\db\ActiveQuery::with()|with()]] を呼ぶと、`a`、`a.b`、`a.b.c` そして `a.b.c.d` をイーガーロードすることになります。 + +> Info: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが [中間テーブル](#junction-table) によって定義されている場合、この `N` 個のリレーションをイーガーロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。 + ネストされたリレーション `a.b.c.d` は 4 個のリレーションとして数えられることに注意してください。 + +リレーションをイーガーロードするときに、対応するリレーショナルクエリを無名関数を使ってカスタマイズすることが出来ます。 +例えば、 ```php +<<<<<<< HEAD $customer = Customer::findOne(1); // レイジーローディング: SELECT * FROM order WHERE customer_id=1 AND subtotal>100 $orders = $customer->getOrders()->where('subtotal>100')->all(); @@ -1202,6 +1545,8 @@ $items = $customers[0]->orders[0]->items; 例えば、 ```php +======= +>>>>>>> master // 顧客を検索し、その国とアクティブな注文を同時に返す // SELECT * FROM `customer` // SELECT * FROM `country` WHERE `id` IN (...) @@ -1210,54 +1555,57 @@ $customers = Customer::find()->with([ 'country', 'orders' => function ($query) { $query->andWhere(['status' => Order::STATUS_ACTIVE]); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master }, ])->all(); ``` <<<<<<< HEAD +<<<<<<< HEAD +======= +リレーションのためのリレーショナルクエリをカスタマイズするときは、リレーション名を配列のキーとし、対応する値に無名関数を使わなければなりません。 +無名関数が受け取る `$query` パラメータは、リレーションのためのリレーショナルクエリを実行するのに使用される [[yii\db\ActiveQuery]] オブジェクトを表します。 +上のコード例では、注文の状態に関する条件を追加して、リレーショナルクエリを修正しています。 +>>>>>>> master -### 逆リレーション +> Note: リレーションをイーガーロードするときに [[yii\db\Query::select()|select()]] を呼ぶ場合は、リレーションの宣言で参照されているカラムが選択されるように注意しなければなりません。 +> そうしないと、リレーションのモデルが正しくロードされないことがあります。 +> 例えば、 +> +> ```php +> $orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); +> // この場合、$orders[0]->customer は常に null になります。 +> // 問題を修正するためには、次のようにしなければなりません。 +> $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +> ``` -リレーションは、たいていの場合、ペアで定義することが出来ます。 -例えば、`Customer` が `orders` という名前のリレーションを持ち、`Order` が `customer` という名前のリレーションを持つ、ということがあります。 +### リレーションを使ってテーブルを結合する + +> Note: この項で説明されていることは、MySQL、PostgreSQL など、リレーショナルデータベースに対してのみ適用されます。 + +ここまで説明してきたリレーショナルクエリは、主たるデータを検索する際に主テーブルのカラムだけを参照するものでした。 +現実には、関連するテーブルのカラムを参照しなければならない場合がよくあります。 +例えば、少なくとも一つのアクティブな注文を持つ顧客を取得したい、というような場合です。 +この問題を解決するためには、以下のようにして、テーブルを結合するクエリを構築することが出来ます。 ```php -class Customer extends ActiveRecord -{ - .... - public function getOrders() - { - return $this->hasMany(Order::className(), ['customer_id' => 'id']); - } -} - -class Order extends ActiveRecord -{ - .... - public function getCustomer() - { - return $this->hasOne(Customer::className(), ['id' => 'customer_id']); - } -} -``` - -次に例示するクエリを実行すると、注文 (order) のリレーションとして取得した顧客 (customer) が、最初にその注文をリレーションとして取得した顧客とは別の Customer オブジェクトになってしまうことに気付くでしょう。 -また、`customer->orders` にアクセスすると一個の SQL が実行され、`order->customer` にアクセスするともう一つ別の SQL が実行されるということにも気付くでしょう。 - -```php -// SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// "等しくない" がエコーされる -// SELECT * FROM order WHERE customer_id=1 -// SELECT * FROM customer WHERE id=1 -if ($customer->orders[0]->customer === $customer) { - echo '等しい'; -} else { - echo '等しくない'; -} +// SELECT `customer`.* FROM `customer` +// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id` +// WHERE `order`.`status` = 1 +// +// SELECT * FROM `order` WHERE `customer_id` IN (...) +$customers = Customer::find() + ->select('customer.*') + ->leftJoin('order', '`order`.`customer_id` = `customer`.`id`') + ->where(['order.status' => Order::STATUS_ACTIVE]) + ->with('orders') + ->all(); ``` +<<<<<<< HEAD 冗長な最後の SQL 文の実行を避けるためには、次のように、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] メソッドを呼んで、`customer` と `oerders` のリレーションに対して逆リレーションを宣言することが出来ます。 ======= リレーションのためのリレーショナルクエリをカスタマイズするときは、リレーション名を配列のキーとし、対応する値に無名関数を使わなければなりません。 @@ -1370,163 +1718,223 @@ class Customer extends ActiveRecord return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer'); } } -``` +======= +> Note: JOIN SQL 文を含むリレーショナルクエリを構築する場合は、カラム名の曖昧さを解消することが重要です。 + カラム名に対応するテーブル名をプレフィクスするのが慣例です。 -こうすると、上記と同じクエリを実行したときに、次の結果を得ることが出来ます。 +しかしながら、もっと良いのは、[[yii\db\ActiveQuery::joinWith()]] を呼んで、既にあるリレーションの宣言を利用するという手法です。 ```php -// SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// "等しい" がエコーされる -// SELECT * FROM order WHERE customer_id=1 -if ($customer->orders[0]->customer === $customer) { - echo '等しい'; -} else { - echo '等しくない'; -} +$customers = Customer::find() + ->joinWith('orders') + ->where(['order.status' => Order::STATUS_ACTIVE]) + ->all(); +>>>>>>> master ``` -上記では、レイジーローディングにおいて逆リレーションを使う方法を示しました。 -逆リレーションはイーガーローディングにも適用されます。 +どちらの方法でも、実行される SQL 文のセットは同じです。 +けれども、後者の方がはるかに明快で簡潔です。 -```php -// SELECT * FROM customer -// SELECT * FROM order WHERE customer_id IN (1, 2, ...) -$customers = Customer::find()->with('orders')->all(); -// "等しい" がエコーされる -if ($customers[0]->orders[0]->customer === $customers[0]) { - echo '等しい'; -} else { - echo '等しくない'; -} -``` +デフォルトでは、[[yii\db\ActiveQuery::joinWith()|joinWith()]] は `LEFT JOIN` を使って、関連するテーブルを主テーブルに結合します。 +第三のパラメータ `$joinType` によって異なる結合タイプ (例えば `RIGHT JOIN`) を指定することが出来ます。 +指定したい結合タイプが `INNER JOIN` である場合は、代りに、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] を呼ぶだけで済ませることが出来ます。 -> Note|注意: 逆リレーションはピボットテーブルを含むリレーションに対しては定義することが出来ません。 -> つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()]] を追加で呼ぶことは出来ません。 +デフォルトでは、[[yii\db\ActiveQuery::joinWith()|joinWith()]] を呼ぶと、リレーションのデータが [イーガーロード](#lazy-eager-loading) されます。 +リレーションのデータを読み取りたくない場合は、第二のパラメータ `$eagerLoading` を false に指定することが出来ます。 - -### リレーションを使ってテーブルを結合する - -リレーショナルデータベースを扱う場合、複数のテーブルを結合して、JOIN SQL 文にさまざまなクエリ条件とパラメータを指定することは、ごく当り前の仕事です。 -その目的を達するために、[[yii\db\ActiveQuery::join()]] を明示的に呼んで JOIN クエリを構築する代りに、既存のリレーション定義を再利用して [[yii\db\ActiveQuery::joinWith()]] を呼ぶことが出来ます。 +[[yii\db\ActiveQuery::with()|with()]] と同じように、一つまたは複数のリレーションを結合したり、リレーションクエリをその場でカスタマイズしたり、ネストされたリレーションを結合したりすることが出来ます。 +また、[[yii\db\ActiveQuery::with()|with()]] と [[yii\db\ActiveQuery::joinWith()|joinWith()]] を混ぜて使用することも出来ます。 例えば、 ```php -// 全ての注文を検索して、注文を顧客 ID と注文 ID でソートする。同時に "customer" をイーガーロードする。 -$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all(); -// 書籍を含む全ての注文を検索し、"books" をイーガーロードする。 -$orders = Order::find()->innerJoinWith('books')->all(); +$customers = Customer::find()->joinWith([ + 'orders' => function ($query) { + $query->andWhere(['>', 'subtotal', 100]); + }, +])->with('country') + ->all(); ``` -上記において、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] メソッドは、結合タイプを `INNER JOIN` とする [[yii\db\ActiveQuery::joinWith()|joinWith()]] へのショートカットです。 - -一個または複数のリレーションを結合することが出来ます。リレーションにクエリ条件をその場で適用することも出来ます。 -また、サブリレーションを結合することも出来ます。例えば、 +二つのテーブルを結合するときに、結合クエリの `ON` の部分に追加の条件を指定する必要がある場合があるでしょう。 +これは、次のように、[[yii\db\ActiveQuery::onCondition()]] メソッドを呼ぶことによって実現できます。 ```php -// 複数のリレーションを結合 -// 書籍を含む注文で、過去 24 時間以内に登録した顧客によって発行された注文を検索する -$orders = Order::find()->innerJoinWith([ - 'books', - 'customer' => function ($query) { - $query->where('customer.created_at > ' . (time() - 24 * 3600)); - } +// SELECT `customer`.* FROM `customer` +// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id` AND `order`.`status` = 1 +// +// SELECT * FROM `order` WHERE `customer_id` IN (...) +$customers = Customer::find()->joinWith([ + 'orders' => function ($query) { + $query->onCondition(['order.status' => Order::STATUS_ACTIVE]); + }, ])->all(); -// サブリレーションとの結合: 書籍および書籍の著者を結合 -$orders = Order::find()->joinWith('books.author')->all(); ``` -舞台裏では、Yii は最初に JOIN SQL 文を実行して、その JOIN SQL に適用された条件を満たす主たるモデルを取得します。 -そして、次にリレーションごとのクエリを実行して、対応する関連レコードを投入します。 +上記のクエリは *全ての* 顧客を返し、各顧客について全てのアクティブな注文を返します。 +これは、少なくとも一つのアクティブな注文を持つ顧客を全て返す、という以前の例とは異なっていることに注意してください。 -[[yii\db\ActiveQuery::joinWith()|joinWith()]] と [[yii\db\ActiveQuery::with()|with()]] の違いは、前者が主たるモデルクラスのテーブルと関連モデルクラスのテーブルを結合して主たるモデルを読み出すのに対して、後者は主たるモデルクラスのテーブルに対してだけクエリを実行して主たるモデルを読み出す、という点にあります。 +> Info: [[yii\db\ActiveQuery]] が [[yii\db\ActiveQuery::onCondition()|onCondition()]] によって条件を指定された場合、クエリが JOIN 句を含む場合は、条件は `ON` の部分に置かれます。 + クエリが JOIN 句を含まない場合は、条件は自動的に `WHERE` の部分に追加されます。 + このようにして、リレーションのテーブルのカラムを含む条件だけが `ON` の部分に置かれます。 -この違いによって、[[yii\db\ActiveQuery::joinWith()|joinWith()]] では、JOIN SQL 文だけに指定できるクエリ条件を適用することが出来ます。 -例えば、上記の例のように、関連モデルに対する条件によって主たるモデルをフィルタすることが出来ます。 -主たるモデルを関連テーブルのカラムを使って並び替えることも出来ます。 +#### リレーションのテーブルのエイリアス -[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使うときは、カラム名の曖昧さを解決することについて、あなたが責任を負わなければなりません。 -上記の例では、order テーブルと item テーブルがともに `id` という名前のカラムを持っているため、`item.id` と `order.id` を使って、`id` カラムの参照の曖昧さを解決しています。 - -デフォルトでは、リレーションを結合すると、リレーションがイーガーロードされることにもなります。 -このデフォルトの動作は、指定されたリレーションをイーガーロードするかどうかを規定する `$eagerLoading` パラメータを渡して、変更することが出来ます。 - -また、デフォルトでは、[[yii\db\ActiveQuery::joinWith()|joinWith()]] は関連テーブルを結合するのに `LEFT JOIN` を使います。 -結合タイプをカスタマイズするために `$joinType` パラメータを渡すことが出来ます。 -`INNER JOIN` タイプのためのショートカットとして、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] を使うことが出来ます。 - -下記に、いくつかの例を追加します。 +前に注意したように、クエリに JOIN を使うときは、カラム名の曖昧さを解消する必要があります。 +そのために、テーブルにエイリアスを定義することがよくあります。 +リレーションのテーブルのためにエイリアスを設定することは、リレーショナルクエリを次のようにカスタマイズすることによっても可能です。 ```php -// 書籍を含む注文を全て検索するが、"books" はイーガーロードしない。 -$orders = Order::find()->innerJoinWith('books', false)->all(); -// これも上と等価 -$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all(); +$query->joinWith([ + 'orders' => function ($q) { + $q->from(['o' => Order::tableName()]); + }, +]) ``` -二つのテーブルを結合するとき、場合によっては、JOIN クエリの ON の部分で何らかの追加条件を指定する必要があります。 -これは、次のように、[[yii\db\ActiveQuery::onCondition()]] メソッドを呼んで実現することが出来ます。 +しかし、これでは非常に複雑ですし、リレーションオブジェクトのテーブル名をハードコーディングしたり、`Order::tableName()` を呼んだりしなければなりません。 +バージョン 2.0.7 以降、Yii はこれに対するショートカットを提供しています。 +今では、次のようにしてリレーションのテーブルのエイリアスを定義して使うことが出来ます。 ```php -class User extends ActiveRecord +// orders リレーションを JOIN し、結果を orders.id でソートする +$query->joinWith(['orders o'])->orderBy('o.id'); +``` + +### 逆リレーション + +リレーションの宣言は、たいていの場合、二つのアクティブレコードクラスの間で相互的なものになります。 +例えば、`Customer` は `orders` リレーションによって `Order` に関連付けられ、逆に、`Order` は`customer` リレーションによって `Customer` に関連付けられる、という具合です。 + +```php +class Customer extends ActiveRecord { - public function getBooks() + public function getOrders() { - return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]); + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} + +class Order extends ActiveRecord +{ + public function getCustomer() + { + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } ``` -上記においては、[[yii\db\ActiveRecord::hasMany()|hasMany()]] メソッドが [[yii\db\ActiveQuery]] のインスタンスを返しています。 -そして、それに対して [[yii\db\ActiveQuery::onCondition()|onCondition()]] が呼ばれて、`category_id` が 1 である品目だけが返されるべきことを指定しています。 - -[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使ってクエリを実行すると、指定された ON 条件が対応する JOIN クエリの ON の部分に挿入されます。 -例えば、 +ここで、次のコード断片について考えて見てください。 ```php -// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 -// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 -$users = User::find()->joinWith('books')->all(); +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$order = $customer->orders[0]; + +// SELECT * FROM `customer` WHERE `id` = 123 +$customer2 = $order->customer; + +// "異なる" が表示される +echo $customer2 === $customer ? '同じ' : '異なる'; ``` -[[yii\db\ActiveQuery::with()]] を使ってイーガーロードする場合や、レイジーロードする場合には、JOIN クエリは使われないため、ON 条件が対応する SQL 文の WHERE の部分に挿入されることに注意してください。 -例えば、 +私たちは `$customer` と `$customer2` が同じであると期待しますが、そうではありません。 +実際、二つは同じ顧客データを含んでいますが、オブジェクトとしては異なります。 +`$order->customer` にアクセスするときに追加の SQL 文が実行されて、新しいオブジェクトである `$customer2` にデータが投入されます。 + +上記の例において、冗長な最後の SQL 文の実行を避けるためには、下に示すように、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] メソッドを呼ぶことによって、`customer` が `orders` の *逆リレーション* であることを Yii に教えておかなければなりません。 + +このようにリレーションの宣言を修正すると、次の結果を得ることが出来ます。 ```php -// SELECT * FROM user WHERE id=10 -$user = User::findOne(10); -// SELECT * FROM item WHERE owner_id=10 AND category_id=1 -$books = $user->books; +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$order = $customer->orders[0]; + +// No SQL will be executed +$customer2 = $order->customer; + +// "同じ" が表示される +echo $customer2 === $customer ? '同じ' : '異なる'; ``` +> Note: 逆リレーションは [中間テーブル](#junction-table) を含むリレーションについては宣言することが出来ません。 + つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] を追加で呼んではいけません。 -関連付けを扱う --------------- -アクティブレコードは、二つのアクティブレコードオブジェクト間の関連付けを確立および破棄するために、次の二つのメソッドを提供しています。 +## リレーションを保存する -- [[yii\db\ActiveRecord::link()|link()]] -- [[yii\db\ActiveRecord::unlink()|unlink()]] - -例えば、顧客と新しい注文があると仮定したとき、次のコードを使って、その注文をその顧客のものとすることが出来ます。 +リレーショナルデータを扱う時には、たいてい、さまざまなデータ間にリレーションを確立したり、既存のリレーションを破棄したりする必要があります。 +そのためには、リレーションを定義するカラムの値を適切に設定することが必要です。 +アクティブレコードを使う場合は、結局の所、次のようなコードを書くことになるでしょう。 ```php -$customer = Customer::findOne(1); +$customer = Customer::findOne(123); $order = new Order(); $order->subtotal = 100; -$customer->link('orders', $order); +// ... + +// Order において "customer" リレーションを定義する属性の値を設定する +$order->customer_id = $customer->id; +$order->save(); ``` -上記の [[yii\db\ActiveRecord::link()|link()]] の呼び出しは、注文の `customer_id` に `$customer` のプライマリキーの値を設定し、[[yii\db\ActiveRecord::save()|save()]] を呼んで注文をデータベースに保存します。 - - -DBMS 間のリレーション ---------------------- - -アクティブレコードは、異なる DBMS に属するエンティティ間、例えば、リレーショナルデータベースのテーブルと MongoDB のコレクションの間に、リレーションを確立することを可能にしています。 -そのようなリレーションでも、何も特別なコードは必要ありません。 +アクティブレコードは、この仕事をもっと楽に達成することが出来るように、[[yii\db\ActiveRecord::link()|link()]] メソッドを提供しています。 ```php +$customer = Customer::findOne(123); +$order = new Order(); +$order->subtotal = 100; +// ... + +$order->link('customer', $customer); +``` + +[[yii\db\ActiveRecord::link()|link()]] メソッドは、リレーション名と、リレーションを確立する対象のアクティブレコードインスタンスを指定することを要求します。 +このメソッドは、二つのアクティブレコードインスタンスをリンクする属性の値を修正して、それをデータベースに書き込みます。 +上記の例では、`Order` インスタンスの `customer_id` 属性を `Customer` インスタンスの `id` 属性の値になるようにセットして、それをデータベースに保存します。 + +> Note: 二つの新規作成されたアクティブレコードインスタンスをリンクすることは出来ません。 + +[[yii\db\ActiveRecord::link()|link()]] を使用することの利点は、リレーションが [中間テーブル](#junction-table) によって定義されている場合に、さらに明白になります。 +例えば、一つの `Order` インスタンスと一つの`Item` インスタンスをリンクするのに、次のコードを使うことが出来ます。 + +```php +$order->link('items', $item); +``` + +上記のコードによって、`order_item` 中間テーブルに、注文と商品を関連付けるための行が自動的に挿入されます。 + +> Info: [[yii\db\ActiveRecord::link()|link()]] メソッドは、影響を受けるアクティブレコードインスタンスを保存する際に、データ検証を実行しません。 + このメソッドを呼ぶ前にすべての入力値を検証することはあなたの責任です。 + +[[yii\db\ActiveRecord::link()|link()]] の逆の操作が [[yii\db\ActiveRecord::unlink()|unlink()]] です。 +これは、既存の二つのアクティブレコードインスタンスのリレーションを破棄します。 +例えば、 + +```php +$customer = Customer::find()->with('orders')->where(['id' => 123])->one(); +$customer->unlink('orders', $customer->orders[0]); +``` + +デフォルトでは、[[yii\db\ActiveRecord::unlink()|unlink()]] メソッドは、既存のリレーションを指定している外部キーの値を null に設定します。 +ただし、`$delete` パラメータを true にしてメソッドに渡して、その外部キーを含むテーブル行を削除するという方法を選ぶことも出来ます。 + +リレーションに中間テーブルが含まれている場合は、[[yii\db\ActiveRecord::unlink()|unlink()]] を呼ぶと、中間テーブルにある外部キーがクリアされるか、または、`$delete` が true であるときは、中間テーブルにある対応する行が削除されるかします。 + + +## DBMS 間のリレーション + +アクティブレコードは、異なるデータベースをバックエンドに持つアクティブレコードの間でリレーションを宣言することを可能にしています。 +データベースは異なるタイプ (例えば、MySQL と PostgreSQL、または、MS SQL と MongoDB) であってもよく、別のサーバで動作していても構いません。 +同じ構文を使ってリレーショナルクエリを実行することが出来ます。 +例えば、 + +```php +<<<<<<< HEAD // リレーショナルデータベースのアクティブレコード ======= public function getOrders() @@ -1657,6 +2065,9 @@ $customer->unlink('orders', $customer->orders[0]); ```php // Customer はリレーショナルデータベース (例えば MySQL) の "customer" テーブルと関連付けられている >>>>>>> yiichina/master +======= +// Customer はリレーショナルデータベース (例えば MySQL) の "customer" テーブルと関連付けられている +>>>>>>> master class Customer extends \yii\db\ActiveRecord { public static function tableName() @@ -1666,20 +2077,28 @@ class Customer extends \yii\db\ActiveRecord public function getComments() { +<<<<<<< HEAD <<<<<<< HEAD // リレーショナルデータベースに保存されている Customer は、MongoDB コレクションに保存されている複数の Comment を持つ ======= // Customer は多くの Comment を持つ >>>>>>> yiichina/master +======= + // Customer は多くの Comment を持つ +>>>>>>> master return $this->hasMany(Comment::className(), ['customer_id' => 'id']); } } +<<<<<<< HEAD <<<<<<< HEAD // MongoDb のアクティブレコード ======= // Comment は MongoDb データベースの "comment" コレクションと関連付けられている >>>>>>> yiichina/master +======= +// Comment は MongoDb データベースの "comment" コレクションと関連付けられている +>>>>>>> master class Comment extends \yii\mongodb\ActiveRecord { public static function collectionName() @@ -1689,72 +2108,66 @@ class Comment extends \yii\mongodb\ActiveRecord public function getCustomer() { +<<<<<<< HEAD <<<<<<< HEAD // MongoDB コレクションに保存されている Comment は、リレーショナルデータベースに保存されている一つの Customer を持つ +======= + // Comment は 一つの Customer を持つ +>>>>>>> master return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } + +$customers = Customer::find()->with('comments')->all(); ``` -アクティブレコードの全ての機能、例えば、イーガーローディングやレイジーローディング、関連付けの確立や破棄などが、DBMS 間のリレーションでも利用可能です。 +この節で説明されたリレーショナルクエリ機能のほとんどを使用することが出来ます。 -> Note|注意: DBMS ごとのアクティブレコードの実装には、DBMS 固有のメソッドや機能が含まれる場合があり、そういうものは DBMS 間のリレーションには適用できないということを忘れないでください。 - 例えば、[[yii\db\ActiveQuery::joinWith()]] の使用が MongoDB コレクションに対するリレーションでは動作しないことは明白です。 +> Note: [[yii\db\ActiveQuery::joinWith()]] の使用は、データベース間の JOIN クエリをサポートしているデータベースに限定されます。 + この理由により、上記の例では `joinWith` メソッドは使用することが出来ません。 + MongoDB は JOIN をサポートしていないからです。 -スコープ --------- - -[[yii\db\ActiveRecord::find()|find()]] または [[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼ぶと、[[yii\db\ActiveQuery|ActiveQuery]] のインスタンスが返されます。 -そして、追加のクエリメソッド、例えば、[[yii\db\ActiveQuery::where()|where()]] や [[yii\db\ActiveQuery::orderBy()|orderBy()]] を呼んで、クエリ条件をさらに指定することが出来ます。 - -別々の場所で同じ一連のクエリメソッドを呼びたいということがあり得ます。 -そのような場合には、いわゆる *スコープ* を定義することを検討すべきです。 -スコープは、本質的には、カスタムクエリクラスの中で定義されたメソッドであり、クエリオブジェクトを修正する一連のメソッドを呼ぶものです。 -スコープを定義しておくと、通常のクエリメソッドを呼ぶ代りに、スコープを使うことが出来るようになります。 - -スコープを定義するためには二つのステップが必要です。 -最初に、モデルのためのカスタムクエリクラスを作成して、このクラスの中に必要なスコープメソッドを定義します。 -例えば、`Comment` モデルのために `CommentQuery` クラスを作成して、次のように、`active()` というスコープメソッドを定義します。 - -```php -namespace app\models; - -use yii\db\ActiveQuery; - -class CommentQuery extends ActiveQuery -{ - public function active($state = true) - { - $this->andWhere(['active' => $state]); - return $this; - } -} -``` - -重要な点は、以下の通りです。 - -1. クラスは `yii\db\ActiveQuery` (または、`yii\mongodb\ActiveQuery` などの、その他の `ActiveQuery`) を拡張したものにしなければなりません。 -2. メソッドは `public` で、メソッドチェーンが出来るように `$this` を返さなければなりません。メソッドはパラメータを取ることが出来ます。 -3. クエリ条件を修正する方法については、[[yii\db\ActiveQuery]] のメソッド群を参照するのが非常に役に立ちます。 - -次に、[[yii\db\ActiveRecord::find()]] をオーバーライドして、通常の [[yii\db\ActiveQuery|ActiveQuery]] の代りに、カスタムクエリクラスを使うようにします。 -上記の例のためには、次のコードを書く必要があります。 +## クエリクラスをカスタマイズする +デフォルトでは、全てのアクティブレコードのクエリは [[yii\db\ActiveQuery]] によってサポートされます。 +カスタマイズされたクエリクラスをアクティブレコードで使用するためには、[[yii\db\ActiveRecord::find()]] メソッドをオーバーライドして、カスタマイズされたクエリクラスのインスタンスを返すようにしなければなりません。 +例えば、 + ```php namespace app\models; use yii\db\ActiveRecord; +use yii\db\ActiveQuery; class Comment extends ActiveRecord { - /** - * @inheritdoc - * @return CommentQuery - */ public static function find() { return new CommentQuery(get_called_class()); + } +} + +class CommentQuery extends ActiveQuery +{ + // ... +} +``` + +このようにすると、`Comment` のクエリを実行したり (例えば `find()` や `findOne()` を呼んだり) リレーションを定義したり (例えば `hasOne()` を定義したり) する際には、いつでも、`AcctiveQuery` の代りに `CommentQuery` のインスタンスを使用することになります。 + +> Tip: 大きなプロジェクトでは、アクティブレコードクラスをクリーンに保つことが出来るように、クエリ関連のコードのほとんどをカスタマイズされたクエリクラスに保持することが推奨されます。 + +クエリクラスは、さまざまのクリエイティブな方法によってカスタマイズして、あなたのクエリ構築の体験を向上させることが出来ます。 +例えば、カスタマイズされたクエリクラスにおいて、新しいクエリ構築メソッドを定義することが出来ます。 + +```php +class CommentQuery extends ActiveQuery +{ + public function active($state = true) + { +<<<<<<< HEAD + return new CommentQuery(get_called_class()); ======= // Comment は 一つの Customer を持つ return $this->hasOne(Customer::className(), ['id' => 'customer_id']); @@ -1811,10 +2224,14 @@ class CommentQuery extends ActiveQuery { return $this->andWhere(['active' => $state]); >>>>>>> yiichina/master +======= + return $this->andWhere(['active' => $state]); +>>>>>>> master } } ``` +<<<<<<< HEAD <<<<<<< HEAD 以上です。これで、カスタムスコープメソッドを使用することが出来ます。 @@ -1824,27 +2241,36 @@ class CommentQuery extends ActiveQuery このようにすると、次のようにクエリ構築のコードを書くことが出来るようになります。 >>>>>>> yiichina/master +======= +> Note: 新しいクエリ構築メソッドを定義する場合は、通常は、既存の条件が上書きされないように、[[yii\db\ActiveQuery::where()|where()]] ではなく、[[yii\db\ActiveQuery::andWhere()|andWhere()]] または [[yii\db\ActiveQuery::orWhere()|orWhere()]] を呼んで条件を追加しなければなりません。 + +このようにすると、次のようにクエリ構築のコードを書くことが出来るようになります。 + +>>>>>>> master ```php $comments = Comment::find()->active()->all(); $inactiveComments = Comment::find()->active(false)->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD リレーションを定義するときにもスコープを使用することが出来ます。例えば、 +======= +この新しいクエリ構築メソッドは、`Comment` に関するリレーションを定義するときや、リレーショナルクエリを実行するときにも使用することが出来ます。 +>>>>>>> master ```php -class Post extends \yii\db\ActiveRecord +class Customer extends \yii\db\ActiveRecord { public function getActiveComments() { - return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active(); - + return $this->hasMany(Comment::className(), ['customer_id' => 'id'])->active(); } } -``` -または、リレーショナルクエリを実行するときに、その場でスコープを使うことも出来ます。 +$customers = Customer::find()->with('activeComments')->all(); +<<<<<<< HEAD ```php $posts = Post::find()->with([ ======= @@ -1865,33 +2291,25 @@ $customers = Customer::find()->with('activeComments')->all(); $customers = Customer::find()->with([ >>>>>>> yiichina/master +======= +// あるいは、また + +$customers = Customer::find()->with([ +>>>>>>> master 'comments' => function($q) { $q->active(); } ])->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD ### デフォルトスコープ - -あなたが Yii 1.1 を前に使ったことがあれば、*デフォルトスコープ* と呼ばれる概念を知っているかも知れません。 -デフォルトスコープは、全てのクエリに適用されるスコープです。 -デフォルトスコープは、[[yii\db\ActiveRecord::find()]] をオーバライドすることによって、簡単に定義することが出来ます。 -例えば、 - -```php -public static function find() -{ - return parent::find()->where(['deleted' => false]); -} -``` - -ただし、すべてのクエリにおいて、デフォルトの条件を上書きしないために、[[yii\db\ActiveQuery::where()|where()]] を使わず、[[yii\db\ActiveQuery::andWhere()|andWhere()]] または [[yii\db\ActiveQuery::orWhere()|orWhere()]] を使うべきであることに注意してください。 - ======= -> Info|情報: Yii 1.1 には、*スコープ* と呼ばれる概念がありました。 +> Info: Yii 1.1 には、*スコープ* と呼ばれる概念がありました。 Yii 2.0 では、スコープはもはや直接にはサポートされません。 同じ目的を達するためには、カスタマイズされたクエリクラスとクエリメソッドを使わなければなりません。 +>>>>>>> master ## 追加のフィールドを選択する @@ -1951,6 +2369,149 @@ class Customer extends \yii\db\ActiveRecord そして、order を結合して注文数を計算するクエリを構築することが出来ます。 +<<<<<<< HEAD +======= +> Info|情報: Yii 1.1 には、*スコープ* と呼ばれる概念がありました。 + Yii 2.0 では、スコープはもはや直接にはサポートされません。 + 同じ目的を達するためには、カスタマイズされたクエリクラスとクエリメソッドを使わなければなりません。 + + +## 追加のフィールドを選択する + +アクティブレコードのインスタンスにクエリ結果からデータが投入されるときは、受け取ったデータセットのカラムの値が対応する属性に入れられます。 + +クエリ結果から追加のカラムや値を取得して、アクティブレコードの内部に格納することが出来ます。 +例えば、ホテルの客室の情報を含む 'room' という名前のテーブルがあるとしましょう。 +そして、全ての客室のデータは 'length' (長さ)、'width' (幅)、'height' (高さ) というフィールドを使って、部屋の幾何学的なサイズに関する情報を格納しているとします。 +空いている全ての部屋の一覧を容積の降順で取得する必要がある場合を考えて見てください。 +レコードをその値で並べ替える必要があるので、PHP を使って容積を計算することは出来ません。 +しかし、同時に、一覧には 'volume' (容積) も表示したいでしょう。 +目的を達するためには、'Room' アクティブレコードクラスにおいて追加のフィールドを宣言し、'volume' の値を格納する必要があります。 +======= +```php +$customers = Customer::find() + ->select([ + '{{customer}}.*', // 顧客の全てのフィールドを選択 + 'COUNT({{order}}.id) AS ordersCount' // 注文数を計算 + ]) + ->joinWith('orders') // テーブルの結合を保証する + ->groupBy('{{customer}}.id') // 結果をグループ化して、集計関数の動作を保証する + ->all(); +``` + +この方法を使うことの短所の一つは、情報が SQL クエリでロードされていない場合には、それを別途計算しなければならない、ということです。 +このことは、また、新しく保存したレコードも追加のフィールドについては情報を持っていないことになることを意味します。 + +```php +$room = new Room(); +$room->length = 100; +$room->width = 50; +$room->height = 2; + +$room->volume; // まだ指定されていないため、この値は null になります。 +``` + +[[yii\db\BaseActiveRecord::__get()|__get()]] と [[yii\db\BaseActiveRecord::__set()|__set()]] のマジックメソッドを使用すれば、プロパティの動作をエミュレートすることが出来ます。 +>>>>>>> master + +```php +class Room extends \yii\db\ActiveRecord +{ +<<<<<<< HEAD + public $volume; +======= + private $_volume; + + public function setVolume($volume) + { + $this->_volume = (float) $volume; + } + + public function getVolume() + { + if (empty($this->length) || empty($this->width) || empty($this->height)) { + return null; + } + + if ($this->_volume === null) { + $this->setVolume( + $this->length * $this->width * $this->height + ); + } + + return $this->_volume; + } +>>>>>>> master + + // ... +} +``` + +<<<<<<< HEAD +そして、部屋の容積を計算して並べ替えを実行するクエリを構築しなければなりません。 + +```php +$rooms = Room::find() + ->select([ + '{{room}}.*', // 全てのカラムを選択 + '([[length]] * [[width]].* [[height]]) AS volume', // 容積を計算 + ]) + ->orderBy('volume DESC') // 並べ替えを適用 + ->all(); + +foreach ($rooms as $room) { + echo $room->volume; // SQL によって計算された値を含んでいる +} +``` + +追加のフィールドが選択できることは、集計クエリに対して特に有効に機能します。 +注文の数とともに顧客の一覧を表示する必要がある場合を想定してください。 +まず初めに、`Customer` クラスの中で、'orders' リレーションと、注文数を格納するための追加のフィールドを宣言しなければなりません。 +======= +このようにすると、SELECT クエリによって容積が提供されていない場合に、モデルの他の属性を使って容積を自動的に計算することが出来ます。 + +この手法は、リレーショナルデータに依存する追加のフィールドに対しても、同じように使用する事が出来ます。 +>>>>>>> master + +```php +class Customer extends \yii\db\ActiveRecord +{ +<<<<<<< HEAD + public $ordersCount; +======= + private $_ordersCount; + + public function setOrdersCount($count) + { + $this->_ordersCount = (int) $count; + } + + public function getOrdersCount() + { + if ($this->isNewRecord) { + return null; // プライマリキーが null の場合のリレーショナルクエリを防止 + } + + if ($this->_ordersCount === null) { + $this->setOrdersCount(count($this->orders)); + } + + return $this->_ordersCount; + } +>>>>>>> master + + // ... + + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} +``` +<<<<<<< HEAD + +そして、order を結合して注文数を計算するクエリを構築することが出来ます。 + ```php $customers = Customer::find() ->select([ @@ -1962,3 +2523,5 @@ $customers = Customer::find() ->all(); ``` >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide-ja/db-dao.md b/docs/guide-ja/db-dao.md index 7fff720124..d46d8271e8 100644 --- a/docs/guide-ja/db-dao.md +++ b/docs/guide-ja/db-dao.md @@ -53,7 +53,7 @@ return [ こうすると `Yii::$app->db` という式で DB 接続にアクセスすることが出来るようになります。 -> Tip|ヒント: あなたのアプリケーションが複数のデータベースにアクセスする必要がある場合は、複数の DB アプリケーションコンポーネントを構成することが出来ます。 +> Tip: あなたのアプリケーションが複数のデータベースにアクセスする必要がある場合は、複数の DB アプリケーションコンポーネントを構成することが出来ます。 DB 接続を構成するときは、つねに [[yii\db\Connection::dsn|dsn]] プロパティによってデータソース名 (DSN) を指定しなければなりません。 DSN の形式はデータベースによってさまざまに異なります。 @@ -85,7 +85,21 @@ ODBC 経由でデータベースに接続しようとする場合は、[[yii\db\ [[yii\db\Connection::dsn|dsn]] プロパティに加えて、たいていは [[yii\db\Connection::username|username]] と [[yii\db\Connection::password|password]] も構成しなければなりません。 構成可能なプロパティの全てのリストは [[yii\db\Connection]] を参照して下さい。 -> Info|情報: DB 接続のインスタンスを作成するとき、実際のデータベース接続は、最初の SQL を実行するか、[[yii\db\Connection::open()|open()]] メソッドを明示的に呼ぶかするまでは確立されません。 +> Info: DB 接続のインスタンスを作成するとき、実際のデータベース接続は、最初の SQL を実行するか、[[yii\db\Connection::open()|open()]] メソッドを明示的に呼ぶかするまでは確立されません。 + +> Tip: 時として、何らかの環境変数を初期化するために、データベース接続を確立した直後に何かクエリを実行したい場合があるでしょう (例えば、タイムゾーンや文字セットを設定するなどです)。 +> そうするために、データベース接続の [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] イベントに対するイベントハンドラを登録することが出来ます。 +> 以下のように、アプリケーションの構成情報に直接にハンドラを登録してください。 +> +> ```php +> 'db' => [ +> // ... +> 'on afterOpen' => function($event) { +> // $event->sender は DB 接続を指す +> $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); +> } +> ] +> ``` <<<<<<< HEAD ======= @@ -107,58 +121,35 @@ ODBC 経由でデータベースに接続しようとする場合は、[[yii\db\ いったんデータベース接続のインスタンスを得てしまえば、次の手順に従って SQL クエリを実行することが出来ます。 -1. 素の SQL で [[yii\db\Command]] を作成する。 +1. 素の SQL クエリで [[yii\db\Command]] を作成する。 2. パラメータをバインドする (オプション)。 3. [[yii\db\Command]] の SQL 実行メソッドの一つを呼ぶ。 次に、データベースからデータを読み出すさまざまな方法を例示します。 ```php -$db = new yii\db\Connection(...); - // 行のセットを返す。各行は、カラム名と値の連想配列。 -// 結果が無い場合は空の配列が返される。 -$posts = $db->createCommand('SELECT * FROM post') +// クエリが結果を返さなかった場合は空の配列が返される。 +$posts = Yii::$app->db->createCommand('SELECT * FROM post') ->queryAll(); // 一つの行 (最初の行) を返す。 -// 結果が無い場合は false が返される。 -$post = $db->createCommand('SELECT * FROM post WHERE id=1') +// クエリの結果が無かった場合は false が返される。 +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1') ->queryOne(); // 一つのカラム (最初のカラム) を返す。 -// 結果が無い場合は空の配列が返される。 -$titles = $db->createCommand('SELECT title FROM post') +// クエリが結果を返さなかった場合は空の配列が返される。 +$titles = Yii::$app->db->createCommand('SELECT title FROM post') ->queryColumn(); // スカラ値を返す。 -// 結果が無い場合は false が返される。 -$count = $db->createCommand('SELECT COUNT(*) FROM post') +// クエリの結果が無かった場合は false が返される。 +$count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post') ->queryScalar(); ``` -> Note|注意: 精度を保つために、対応するデータベースカラムの型が数値である場合でも、データベースから取得されたデータは、全て文字列として表現されます。 - -> Tip|ヒント: 接続を確立した直後に SQL を実行する必要がある場合 (例えば、タイムゾーンや文字セットを設定したい場合) は、[[yii\db\Connection::EVENT_AFTER_OPEN]] ハンドラの中でそれをすることが出来ます。 -> 例えば、 -> -```php -return [ - // ... - 'components' => [ - // ... - 'db' => [ - 'class' => 'yii\db\Connection', - // ... - 'on afterOpen' => function($event) { - // $event->sender は DB 接続を指す - $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); - } - ], - ], - // ... -]; -``` +> Note: 精度を保つために、対応するデータベースカラムの型が数値である場合でも、データベースから取得されたデータは、全て文字列として表現されます。 ### パラメータをバインドする @@ -167,7 +158,7 @@ return [ 例えば、 ```php -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') ->bindValue(':id', $_GET['id']) ->bindValue(':status', 1) ->queryOne(); @@ -186,11 +177,11 @@ SQL 文において、一つまたは複数のパラメータプレースホル ```php $params = [':id' => $_GET['id'], ':status' => 1]; -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') ->bindValues($params) ->queryOne(); -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) ->queryOne(); ``` @@ -199,16 +190,17 @@ $post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', 例えば、 ```php -$command = $db->createCommand('SELECT * FROM post WHERE id=:id'); +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id'); $post1 = $command->bindValue(':id', 1)->queryOne(); $post2 = $command->bindValue(':id', 2)->queryOne(); +// ... ``` [[yii\db\Command::bindParam()|bindParam()]] はパラメータを参照渡しでバインドすることをサポートしていますので、上記のコードは次のように書くことも出来ます。 ```php -$command = $db->createCommand('SELECT * FROM post WHERE id=:id') +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id') ->bindParam(':id', $id); $id = 1; @@ -229,7 +221,7 @@ $post2 = $command->queryOne(); 例えば、 ```php -$db->createCommand('UPDATE post SET status=1 WHERE id=1') +Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') ->execute(); ``` @@ -241,16 +233,16 @@ INSERT、UPDATE および DELETE クエリのためには、素の SQL を書く ```php // INSERT (テーブル名, カラムの値) -$db->createCommand()->insert('user', [ +Yii::$app->db->createCommand()->insert('user', [ 'name' => 'Sam', 'age' => 30, ])->execute(); // UPDATE (テーブル名, カラムの値, 条件) -$db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); +Yii::$app->db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); // DELETE (テーブル名, 条件) -$db->createCommand()->delete('user', 'status = 0')->execute(); +Yii::$app->db->createCommand()->delete('user', 'status = 0')->execute(); ``` [[yii\db\Command::batchInsert()|batchInsert()]] を呼んで複数の行を一気に挿入することも出来ます。 @@ -258,13 +250,17 @@ $db->createCommand()->delete('user', 'status = 0')->execute(); ```php // テーブル名, カラム名, カラムの値 -$db->createCommand()->batchInsert('user', ['name', 'age'], [ +Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ ['Tom', 30], ['Jane', 20], ['Linda', 25], ])->execute(); ``` +上述のメソッド群はクエリを生成するだけであり、実際にそれを実行するためには、常に [[yii\db\Command::execute()|execute()]] +を呼び出す必要があることに注意してください。 + + ## テーブルとカラムの名前を引用符で囲む 特定のデータベースに依存しないコードを書くときには、テーブルとカラムの名前を適切に引用符で囲むことが、たいてい、頭痛の種になります。 @@ -274,20 +270,20 @@ $db->createCommand()->batchInsert('user', ['name', 'age'], [ * `[[カラム名]]`: 引用符で囲まれるカラム名を二重角括弧で包む。 * `{{テーブル名}}`: 引用符で囲まれるテーブル名を二重波括弧で包む。 -Yii DAO は、SQL に含まれるこのような構文を、対応する適切な引用符で囲まれたカラム名とテーブル名に自動的に変換します。 +Yii DAO は、このような構文を、DBMS 固有の文法に従って、適切な引用符で囲まれたカラム名とテーブル名に自動的に変換します。 例えば、 ```php // MySQL では SELECT COUNT(`id`) FROM `employee` という SQL が実行される -$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") ->queryScalar(); ``` ### テーブル接頭辞を使う -あなたの DB テーブルのほとんどが何か共通の接頭辞を持っている場合は、Yii DAO によってサポートされているテーブル接頭辞の機能を使うことが出来ます。 +あなたの DB テーブル名のほとんどが何か共通の接頭辞を持っている場合は、Yii DAO によってサポートされているテーブル接頭辞の機能を使うことが出来ます。 -最初に、[[yii\db\Connection::tablePrefix]] プロパティによって、テーブル接頭辞を指定します。 +最初に、アプリケーションの構成情報で、[[yii\db\Connection::tablePrefix]] プロパティによって、テーブル接頭辞を指定します。 ```php return [ @@ -308,7 +304,7 @@ return [ ```php // MySQL では SELECT COUNT(`id`) FROM `tbl_employee` という SQL が実行される -$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") ->queryScalar(); ``` @@ -320,16 +316,17 @@ $count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") 次のコードはトランザクションの典型的な使用方法を示すものです。 ```php -$db->transaction(function($db) { +Yii::$app->db->transaction(function($db) { $db->createCommand($sql1)->execute(); $db->createCommand($sql2)->execute(); // ... その他の SQL 文を実行 ... }); ``` -上記のコードは、次のものと等価です。 +上記のコードは、次のものと等価です。こちらの方が、エラー処理のコードをより細かく制御することが出来ます。 ```php +$db = Yii::$app->db; $transaction = $db->beginTransaction(); try { @@ -347,12 +344,13 @@ try { } ``` -[[yii\db\Connection::beginTransaction()|beginTransaction()]] メソッドを呼んで、新しいトランザクションを開始します。 -トランザクションは、変数 `$transaction` に保存された [[yii\db\Transaction]] オブジェクトとして表現されています。 -そして、実行されるクエリを `try...catch...` ブロックで囲みます。 -全てのクエリの実行が成功した場合には [[yii\db\Transaction::commit()|commit()]] を呼んでトランザクションをコミットします。 -そうでなければ、例外がトリガされてキャッチされ、[[yii\db\Transaction::rollBack()|rollBack()]] が呼ばれて、失敗したクエリに先行するクエリがトランザクションの中で行った変更がロールバックされます。 - +[[yii\db\Connection::beginTransaction()|beginTransaction()]] メソッドを呼ぶことによって、新しいトランザクションが開始されます。 +トランザクションは、変数 `$transaction` に保存された [[yii\db\Transaction]] オブジェクトとして表現されます。 +そして、実行されるクエリが `try...catch...` ブロックで囲まれます。 +全てのクエリの実行が成功した場合には、トランザクションをコミットするために [[yii\db\Transaction::commit()|commit()]] が呼ばれます。 +そうでなく、例外がトリガされてキャッチされた場合は、[[yii\db\Transaction::rollBack()|rollBack()]] +が呼ばれて、トランザクションの中で失敗したクエリに先行するクエリによって行なわれた変更が、ロールバックされます。 +そして、`throw $e` が、まるでそれをキャッチしなかったかのように、例外を再スローしますので、通常のエラー処理プロセスがその例外の面倒を見ることになります。 ### 分離レベルを指定する @@ -363,13 +361,13 @@ Yii は、トランザクションの [分離レベル] の設定もサポート ```php $isolationLevel = \yii\db\Transaction::REPEATABLE_READ; -$db->transaction(function ($db) { +Yii::$app->db->transaction(function ($db) { .... }, $isolationLevel); // あるいは -$transaction = $db->beginTransaction($isolationLevel); +$transaction = Yii::$app->db->beginTransaction($isolationLevel); ``` Yii は、最もよく使われる分離レベルのために、四つの定数を提供しています。 @@ -385,12 +383,12 @@ Yii は、最もよく使われる分離レベルのために、四つの定数 DBMS によっては、接続全体に対してのみ分離レベルの設定を許容しているものがあることに注意してください。 その場合、すべての後続のトランザクションは、指定しなくても、それと同じ分離レベルで実行されます。 従って、この機能を使用するときは、矛盾する設定を避けるために、全てのトランザクションについて分離レベルを明示的に指定しなければなりません。 -このチュートリアルを書いている時点では、これに該当する DBMS は MSSQL と SQLite だけです。 +このチュートリアルを書いている時点では、この制約の影響を受ける DBMS は MSSQL と SQLite だけです。 -> Note|注意: SQLite は、二つの分離レベルしかサポートしていません。すなわち、`READ UNCOMMITTED` と `SERIALIZABLE` しか使えません。 +> Note: SQLite は、二つの分離レベルしかサポートしていません。すなわち、`READ UNCOMMITTED` と `SERIALIZABLE` しか使えません。 他のレベルを使おうとすると、例外が投げられます。 -> Note|注意: PostgreSQL は、トランザクションを開始する前に分離レベルを指定することを許容していません。 +> Note: PostgreSQL は、トランザクションを開始する前に分離レベルを指定することを許容していません。 すなわち、トランザクションを開始するときに、分離レベルを直接に指定することは出来ません。 この場合、トランザクションを開始した後に [[yii\db\Transaction::setIsolationLevel()]] を呼び出す必要があります。 @@ -401,7 +399,7 @@ DBMS によっては、接続全体に対してのみ分離レベルの設定を あなたの DBMS が Savepoint をサポートしている場合は、次のように、複数のトランザクションを入れ子にすることが出来ます。 ```php -$db->transaction(function ($db) { +Yii::$app->db->transaction(function ($db) { // 外側のトランザクション $db->transaction(function ($db) { @@ -413,6 +411,7 @@ $db->transaction(function ($db) { あるいは、 ```php +$db = Yii::$app->db; $outerTransaction = $db->beginTransaction(); try { $db->createCommand($sql1)->execute(); @@ -421,13 +420,15 @@ try { try { $db->createCommand($sql2)->execute(); $innerTransaction->commit(); - } catch (Exception $e) { + } catch (\Exception $e) { $innerTransaction->rollBack(); + throw $e; } $outerTransaction->commit(); -} catch (Exception $e) { +} catch (\Exception $e) { $outerTransaction->rollBack(); + throw $e; } ``` @@ -478,14 +479,14 @@ try { $db = Yii::createObject($config); // スレーブの一つに対してクエリを実行する -$rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); +$rows = Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); // マスタに対してクエリを実行する -$db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); +Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); ``` -> Info|情報: [[yii\db\Command::execute()]] を呼ぶことで実行されるクエリは、書き込みのクエリと見なされ、[[yii\db\Command]] の "query" メソッドのうちの一つによって実行されるその他すべてのクエリは、読み出しクエリと見なされます。 - 現在アクティブなスレーブ接続は `$db->slave` によって取得することが出来ます。 +> Info: [[yii\db\Command::execute()]] を呼ぶことで実行されるクエリは、書き込みのクエリと見なされ、[[yii\db\Command]] の "query" メソッドのうちの一つによって実行されるその他すべてのクエリは、読み出しクエリと見なされます。 + 現在アクティブなスレーブ接続は `Yii::$app->db->slave` によって取得することが出来ます。 `Connection` コンポーネントは、スレーブ間のロードバランス調整とフェイルオーバーをサポートしています。 読み出しクエリを最初に実行するときに、`Connection` コンポーネントはランダムにスレーブを選んで接続を試みます。 @@ -493,7 +494,7 @@ $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); スレーブが一つも使用できないときは、マスタに接続します。 [[yii\db\Connection::serverStatusCache|サーバステータスキャッシュ]] を構成することによって、「死んでいる」サーバを記憶し、[[yii\db\Connection::serverRetryInterval|一定期間]] はそのサーバへの接続を再試行しないようにすることが出来ます。 -> Info|情報: 上記の構成では、すべてのスレーブに対して 10 秒の接続タイムアウトが指定されています。 +> Info: 上記の構成では、すべてのスレーブに対して 10 秒の接続タイムアウトが指定されています。 これは、10 秒以内に接続できなければ、そのスレーブは「死んでいる」と見なされることを意味します。 このパラメータは、実際の環境に基づいて調整することが出来ます。 @@ -544,12 +545,13 @@ $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); `Connection` コンポーネントは、スレーブ間での場合と同じように、マスタ間でのロードバランス調整とフェイルオーバーをサポートしています。 一つ違うのは、マスタが一つも利用できないときは例外が投げられる、という点です。 -> Note|注意: [[yii\db\Connection::masters|masters]] プロパティを使って一つまたは複数のマスタを構成する場合は、データベース接続を定義する `Connection` オブジェクト自体のその他のプロパティ (例えば、`dsn`、`username`、`password`) は全て無視されます。 +> Note: [[yii\db\Connection::masters|masters]] プロパティを使って一つまたは複数のマスタを構成する場合は、データベース接続を定義する `Connection` オブジェクト自体のその他のプロパティ (例えば、`dsn`、`username`、`password`) は全て無視されます。 デフォルトでは、トランザクションはマスタ接続を使用します。そして、トランザクション内では、全ての DB 操作はマスタ接続を使用します。 例えば、 ```php +$db = Yii::$app->db; // トランザクションはマスタ接続で開始される $transaction = $db->beginTransaction(); @@ -568,19 +570,19 @@ try { スレーブ接続を使ってトランザクションを開始したいときは、次のように、明示的にそうする必要があります。 ```php -$transaction = $db->slave->beginTransaction(); +$transaction = Yii::$app->db->slave->beginTransaction(); ``` 時として、読み出しクエリの実行にマスタ接続を使うことを強制したい場合があります。 これは、`useMaster()` メソッドを使うによって達成できます。 ```php -$rows = $db->useMaster(function ($db) { - return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); +$rows = Yii::$app->db->useMaster(function ($db) { + return Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); }); ``` -直接に `$db->enableSlaves` を false に設定して、全てのクエリをマスタ接続に向けることも出来ます。 +直接に `Yii::$app->db->enableSlaves` を false に設定して、全てのクエリをマスタ接続に向けることも出来ます。 ## データベーススキーマを扱う @@ -607,18 +609,23 @@ Yii DAO は、新しいテーブルを作ったり、テーブルからカラム ```php // CREATE TABLE -$db->createCommand()->createTable('post', [ +Yii::$app->db->createCommand()->createTable('post', [ 'id' => 'pk', 'title' => 'string', 'text' => 'text', ]); ``` -テーブルに関する定義情報を DB 接続の [[yii\db\Connection::getTableSchema()|getTableSchema()]] メソッドによって取得することも出来ます。 +上記の配列は、生成されるカラムの名前と型を記述しています。 +Yii はカラムの型のために一連の抽象データ型を提供しているため、データベースの違いを意識せずにスキーマを定義することが可能です。 +これらの抽象データ型は、テーブルが作成されるデータベースに依存する DBMS 固有の型定義に変換されます。 +詳しい情報は [[yii\db\Command::createTable()|createTable()]] メソッドの API ドキュメントを参照してください。 + +データベースのスキーマを変更するだけでなく、テーブルに関する定義情報を DB 接続の [[yii\db\Connection::getTableSchema()|getTableSchema()]] メソッドによって取得することも出来ます。 例えば、 ```php -$table = $db->getTableSchema('post'); +$table = Yii::$app->db->getTableSchema('post'); ``` このメソッドは、テーブルのカラム、プライマリキー、外部キーなどの情報を含む [[yii\db\TableSchema]] オブジェクトを返します。 diff --git a/docs/guide-ja/db-elasticsearch.md b/docs/guide-ja/db-elasticsearch.md deleted file mode 100644 index 411d83d1ab..0000000000 --- a/docs/guide-ja/db-elasticsearch.md +++ /dev/null @@ -1,6 +0,0 @@ -Elasticsearch -============= - -> Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md index eb6fcaebc5..64b70c3968 100644 --- a/docs/guide-ja/db-migrations.md +++ b/docs/guide-ja/db-migrations.md @@ -1,6 +1,7 @@ データベースマイグレーション ============================ +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: この節はまだ執筆中です。 @@ -18,21 +19,46 @@ 下記の一連のステップは、開発中にチームによってデータベースマイグレーションがどのように使用されるかを示す例です。 >>>>>>> yiichina/master +======= +データベース駆動型のアプリケーションを開発し保守する途上で、ソースコードが進化するのと同じように、使用されるデータベースの構造も進化していきます。 +例えば、アプリケーションの開発中に、新しいテーブルが必要であることが分ったり、アプリケーションを配備した後に、クエリのパフォーマンスを向上させるためにインデックスを作成すべきことが発見されたりします。 +データベースの構造の変更が何らかのソースコードの変更を要求する場合はよくありますから、Yii はいわゆる *データベースマイグレーション* 機能を提供して、ソースコードとともにバージョン管理される *データベースマイグレーション* の形式でデータベースの変更を追跡できるようにしています。 + +下記の一連のステップは、開発中にチームによってデータベースマイグレーションがどのように使用されるかを示す例です。 +>>>>>>> master 1. Tim が新しいマイグレーション (例えば、新しいテーブルを作成したり、カラムの定義を変更したりなど) を作る。 2. Tim が新しいマイグレーションをソースコントロールシステム (例えば Git や Mercurial) にコミットする。 3. Doug がソースコントロールシステムから自分のレポジトリを更新して新しいマイグレーションを受け取る。 <<<<<<< HEAD +<<<<<<< HEAD 4. Doug がマイグレーションを彼のローカルの開発用データベースに適用し、Tim が行った変更を反映して、自分のデータベースを同期する。 +======= +4. Doug がマイグレーションを彼のローカルの開発用データベースに適用して、自分のデータベースの同期を取り、Tim が行った変更を反映する。 -Yii はデータベースマイグレーションを `yii migrate` コマンドラインツールによってサポートします。 -このツールは、以下の機能をサポートしています。 +そして、次の一連のステップは、本番環境でデータベースマイグレーションとともに新しいリリースを配備する方法を示すものです。 +>>>>>>> master + +1. Scott は新しいデータベースマイグレーションをいくつか含むプロジェクトのレポジトリにリリースタグを作成する。 +2. Scott は本番サーバでソースコードをリリースタグまで更新する。 +3. Scott は本番のデータベースに対して累積したデータベースマイグレーションを全て適用する。 + +Yii は一連のマイグレーションコマンドラインツールを提供して、以下の機能をサポートします。 * 新しいマイグレーションの作成 -* マイグレーションの適用、取消、再適用 -* マイグレーションの履歴と新規マイグレーションの閲覧 +* マイグレーションの適用 +* マイグレーションの取消 +* マイグレーションの再適用 +* マイグレーションの履歴と状態の表示 +これらのツールは、全て、`yii migrate` コマンドからアクセスすることが出来ます。 +この節では、これらのツールを使用して、さまざまなタスクをどうやって達成するかを詳細に説明します。 +各ツールの使用方法は、ヘルプコマンド `yii help migrate` によっても知ることが出来ます。 +> Tip: マイグレーションはデータベーススキーマに影響を及ぼすだけでなく、既存のデータを新しいスキーマに合うように修正したり、RBAC 階層を作成したり、 +キャッシュをクリーンアップしたりするために使うことも出来ます。 + +<<<<<<< HEAD マイグレーションを作成する -------------------------- ======= @@ -61,6 +87,10 @@ Yii は一連のマイグレーションコマンドラインツールを提供 ## マイグレーションを作成する >>>>>>> yiichina/master +======= + +## マイグレーションを作成する +>>>>>>> master 新しいマイグレーションを作成するためには、次のコマンドを実行します。 @@ -70,24 +100,32 @@ yii migrate/create 要求される `name` パラメータには、マイグレーションの非常に短い説明を指定します。 <<<<<<< HEAD +<<<<<<< HEAD 例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、コマンドを次のようにして使います。 ======= 例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、`create_news_table` という名前を使って、次のようにコマンドを実行すれば良いでしょう。 >>>>>>> yiichina/master +======= +例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、`create_news_table` という名前を使って、次のようにコマンドを実行すれば良いでしょう。 +>>>>>>> master ``` yii migrate/create create_news_table ``` +<<<<<<< HEAD <<<<<<< HEAD すぐ後で説明するように、マイグレーションでは、この `name` パラメータは PHP のクラス名の一部として使用されます。 したがって、アルファベット、数字、および/または、アンダースコアだけを含まなければなりません。 +======= +> Note: この `name` 引数は、生成されるマイグレーションクラス名の一部として使用されますので、アルファベット、数字、および/または、アンダースコアだけを含むものでなければなりません。 +>>>>>>> master -上記のコマンドは、`m101129_185401_create_news_table.php` という名前の新しいファイルを作成します。 -このファイルは `@app/migrations` ディレクトリに作成されます。 -初期状態では、このマイグレーションファイルは以下のコードを含んでいます。 +上記のコマンドは、`m150101_185401_create_news_table.php` という名前の新しい PHP クラスファイルを `@app/migrations` ディレクトリに作成します。 +このファイルは次のようなコードを含み、主として、スケルトンコードを持った `m150101_185401_create_news_table` というマイグレーションクラスを宣言するためのものす。 ```php +<<<<<<< HEAD class m101129_185401_create_news_table extends \yii\db\Migration ======= > Note|注意: この `name` 引数は、生成されるマイグレーションクラス名の一部として使用されますので、アルファベット、数字、および/または、アンダースコアだけを含むものでなければなりません。 @@ -103,34 +141,52 @@ use yii\db\Migration; class m150101_185401_create_news_table extends Migration >>>>>>> yiichina/master +======= +>>>>>> master { public function up() { + } public function down() { echo "m101129_185401_create_news_table cannot be reverted.\n"; + return false; } + +<<<<<<< HEAD +<<<<<<< HEAD +クラス名はファイル名と同じであり、`m_` というパターンに従います。ここで、 +======= + /* + // Use safeUp/safeDown to run migration code within a transaction + public function safeUp() + { + } +>>>>>>> master + + public function safeDown() + { + } + */ } ``` +各データベースマイグレーションは [[yii\db\Migration]] から拡張した PHP クラスとして定義されます。 +マイグレーションクラスの名前は、`m_` という形式で自動的に生成されます。 +ここで、 + +* `` は、マイグレーション作成コマンドが実行された UTC 日時を表し、 +* `` は、あなたがコマンドに与えた `name` 引数と同じ値になります。 + <<<<<<< HEAD -クラス名はファイル名と同じであり、`m_` というパターンに従います。ここで、 - -* `` は、マイグレーションが作成された (`yymmdd_hhmmss` という書式の) UTC タイムスタンプであり、 -* `` は、コマンドの `name` パラメータから取られた文字列です。 - -クラスの中では、`up()` メソッドが、実際のデータベースマイグレーションを実装するコードを含むべきメソッドです。 -言い換えると、`up()` メソッドが実際にデータベースを変更するコードを実行します。 -`down()` メソッドは、`up()` によって加えられた変更を取り消すコードを含むことが出来ます。 - -場合によっては、`down()` がデータベースマイグレーションを取り消すことが出来ないことがあります。 -例えば、マイグレーションがテーブルの行やテーブル全体を削除した場合は、そのデータを `down()` メソッドで復旧することは出来ません。 -そのような場合には、マイグレーションは不可逆であると呼ばれ、データベースを以前の状態にロールバックすることは出来ません。 -マイグレーションが不可逆である場合は、生成された上記のコードのように、`down()` メソッドは `false` を返して、マイグレーションが取り消せないものであることを示します。 - 例として、新しいテーブルを作成するマイグレーションを示しましょう。 ======= 各データベースマイグレーションは [[yii\db\Migration]] から拡張した PHP クラスとして定義されます。 @@ -140,16 +196,23 @@ class m150101_185401_create_news_table extends Migration * `` は、マイグレーション作成コマンドが実行された UTC 日時を表し、 * `` は、あなたがコマンドに与えた `name` 引数と同じ値になります。 +======= +>>>>>>> master マイグレーションクラスにおいて、あなたがなすべき事は、データベースの構造に変更を加える `up()` メソッドにコードを書くことです。 また、`up()` によって加えられた変更を取り消すための `down()` メソッドにも、コードを書きたいと思うかもしれません。 `up()` メソッドは、このマイグレーションによってデータベースをアップグレードする際に呼び出され、`down()` メソッドはデータベースをダウングレードする際に呼び出されます。 下記のコードは、新しい `news` テーブルを作成するマイグレーションクラスをどのようにして実装するかを示すものです。 +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ```php +>>>>>> yiichina/master +======= +use yii\db\Migration; + +class m150101_185401_create_news_table extends Migration +>>>>>>> master { public function up() { $this->createTable('news', [ +<<<<<<< HEAD <<<<<<< HEAD 'id' => 'pk', ======= 'id' => Schema::TYPE_PK, >>>>>>> yiichina/master +======= + 'id' => Schema::TYPE_PK, +>>>>>>> master 'title' => Schema::TYPE_STRING . ' NOT NULL', 'content' => Schema::TYPE_TEXT, ]); @@ -179,23 +251,33 @@ class m150101_185401_create_news_table extends \yii\db\Migration } ``` +<<<<<<< HEAD <<<<<<< HEAD 基底クラスである [[\yii\db\Migration]] が、データベース接続を `db` プロパティによって提供しています。 これを使って、データベースのデータとスキーマを操作することが出来ます。 +======= +> Info: 全てのマイグレーションが取り消し可能な訳ではありません。 + 例えば、`up()` メソッドがテーブルからある行を削除するものである場合、`down()` メソッドでその行を回復することは出来ません。 + また、データベースマイグレーションを取り消すことはあまり一般的ではありませんので、場合によっては、面倒くさいというだけの理由で `down()` を実装しないこともあるでしょう。 + そういう場合は、マイグレーションが取り消し不可能であることを示すために、`down()` メソッドで false を返さなければなりません。 +>>>>>>> master -この例で使われているカラムのタイプは抽象的なタイプであり、Yii によって、あなたが使用するデータベース管理システムに応じて、対応するタイプに置き換えられます。 -抽象的なタイプを使うと、データベースに依存しないマイグレーションを書くことが出来ます。 -例えば、`pk` は、MySQL では `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY` に置き換えられ、sqlite では `integer PRIMARY KEY AUTOINCREMENT NOT NULL` に置き換えられます。 -さらに詳細な説明と、利用可能なタイプについては、[[yii\db\QueryBuilder::getColumnType()]] のドキュメントを参照してください。 -また、カラムのタイプを定義するのに、[[yii\db\Schema]] で定義されている定数を使うことも出来ます。 +基底のマイグレーションクラス [[yii\db\Migration]] は、[[yii\db\Migration::db|db]] プロパティによって、データベース接続にアクセスすることを可能にしています。 +このデータベース接続によって、[データベーススキーマを扱う](db-dao.md#database-schema) で説明されているメソッドを使い、データベーススキーマを操作することが出来ます。 -> Note|注意: テーブル定義の最後に、単純な文字列として指定された、制約やその他の特別なテーブルオプションを追加することが出来ます。 -> 例えば、上記のマイグレーションでは、`content` 属性の定義の後に、`'CONSTRAINT ...'` やその他の特別なオプションを書くことが出来ます。 +テーブルやカラムを作成するときは、物理的な型を使うのでなく、*抽象型* を使って、あなたのマイグレーションが特定の DBMS に依存しないようにします。 +[[yii\db\Schema]] クラスが、サポートされている抽象型を表す一連の定数を定義しています。 +これらの定数は `TYPE_` という形式の名前を持っています。 +例えば、`TYPE_PK` は、オートインクリメントのプライマリキー型であり、`TYPE_STRING` は文字列型です。 +これらの抽象型は、マイグレーションが特定のデータベースに適用されるときに、対応する物理型に翻訳されます。 +MySQL の場合は、`TYPE_PK` は `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY` に変換され、`TYPE_STRING` は `varchar(255)` となります。 +抽象型を使用するときに、付随的な制約を追加することが出来ます。 +上記の例では、`Schema::TYPE_STRING` に ` NOT NULL` を追加して、このカラムが null を許容しないことを指定しています。 -トランザクションを使うマイグレーション --------------------------------------- +> Info: 抽象型と物理型の対応関係は、それぞれの `QueryBuilder` の具象クラスの [[yii\db\QueryBuilder::$typeMap|$typeMap]] プロパティによって定義されています。 +<<<<<<< HEAD 複雑な DB マイグレーションを実行するときは、通常、データベースの一貫性と整合性を保つために、個々のマイグレーションが全体として成功または失敗することを保証する必要があります。 この目的を達成するために、DB トランザクションを利用することが出来ます。 この目的のためには、`safeUp` と `safeDown` という特別なメソッドを使います。 @@ -233,9 +315,15 @@ MySQL の場合は、`TYPE_PK` は `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY` 次の例では、`news` テーブルを作成するだけでなく、このテーブルに初期値となる行を挿入しています。 >>>>>>> yiichina/master +======= +バージョン 2.0.6 以降は、カラムのスキーマを定義するための更に便利な方法を提供するスキーマビルダが新たに導入されています。 +したがって、上記のマイグレーションは次のように書くことが出来ます。 +>>>>>>> master ```php +>>>>>> yiichina/master +======= +use yii\db\Migration; + +class m150101_185401_create_news_table extends Migration +>>>>>>> master { - public function safeUp() + public function up() { $this->createTable('news', [ +<<<<<<< HEAD 'id' => 'pk', 'title' => Schema::TYPE_STRING . ' NOT NULL', 'content' => Schema::TYPE_TEXT, @@ -265,30 +359,43 @@ class m150101_185401_create_news_table extends Migration 'title' => 'test 1', 'content' => 'content 1', >>>>>>> yiichina/master +======= + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), +>>>>>>> master ]); } - public function safeDown() + public function down() { <<<<<<< HEAD $this->dropTable('news'); - $this->dropTable('user'); } - } ``` -使用するクエリが一つだけでない場合は、`safeUp` と `safeDown` を使うことを推奨します。 - -> Note|注意: 全ての DBMS がトランザクションをサポートしている訳ではありません。 -> また、トランザクションに入れることが出来ない DB クエリもあります。 -> その場合には、代りに、`up()` と `down()` を実装しなければなりません。 -> また、MySQL の場合は、[暗黙のコミット](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) を引き起こす SQL 文があります。 +カラムの型を定義するために利用できる全てのメソッドのリストは、[[yii\db\SchemaBuilderTrait]] の API ドキュメントで参照することが出来ます。 -マイグレーションを適用する --------------------------- +## マイグレーションを生成する +バージョン 2.0.7 以降では、マイグレーション・コンソールがマイグレーションを生成する便利な方法を提供しています。 + +マイグレーションの名前が特定の形式である場合は、生成されるマイグレーション・ファイルに追加のコードが書き込まれます。 +例えば、`create_xxx` や `drop_xxx` であれば、テーブルの作成や削除をするコードが追加されます。 +以下で、この機能の全ての変種を説明します。 + + +### テーブルの作成 + +```php +yii migrate/create create_post +``` + +上記のコマンドは、次のコードを生成します。 + +<<<<<<< HEAD 利用できる全ての新しいマイグレーションを適用する (すなわち、ローカルのデータベースを最新の状態にする) ためには、次のコマンドを実行します。 ======= $this->delete('news', ['id' => 1]); @@ -344,56 +451,315 @@ class m150101_185401_create_news_table extends Migration データベースを最新の構造にアップグレードするためには、利用できる全ての新しいマイグレーションを適用するために、次のコマンドを使わなければなりません。 >>>>>>> yiichina/master +======= +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() + ]); + } +>>>>>>> master + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} ``` -yii migrate + +テーブルのフィールドも直接に生成したい場合は、`--fields` オプションでフィールドを指定します。 + +```php +yii migrate/create create_post --fields="title:string,body:text" +``` + +これは、次のコードを生成します。 + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} + ``` +<<<<<<< HEAD <<<<<<< HEAD コマンドを実行すると、すべての新しいマイグレーションが一覧表示されます。 マイグレーションを適用することを確認すると、クラス名のタイムスタンプの値の順に、一つずつ、すべての新しいマイグレーションクラスの `up()` メソッドが実行されます。 +======= +さらに多くのフィールド・パラメータを指定することも出来ます。 +>>>>>>> master -マイグレーションを適用した後に、マイグレーションツールは `migration` という名前のデータベーステーブルに記録を残します。 -これによって、ツールは、どのマイグレーションが適用済みで、どのマイグレーションが適用されていないかを特定することが出来ます。 -`migration` テーブルが存在しない場合は、ツールは `db` [アプリケーションコンポーネント](structure-application-components.md)によって指定されたデータベースの中にテーブルを自動的に作成します。 +```php +yii migrate/create create_post --fields="title:string(12):notNull:unique,body:text" +``` -時として、新しいマイグレーションを一個また数個だけ適用したい場合があります。その場合は、次のコマンドを使うことが出来ます。 +これは、次のコードを生成します。 +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } -``` -yii migrate/up 3 + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} ``` -このコマンドは、次の新しいマイグレーションを 3 個適用します。3 という値を変更して、適用されるマイグレーションの数を変更することが出来ます。 +> Note: プライマリ・キーが自動的に追加されて、デフォルトでは `id` と名付けられます。 +> 別の名前を使いたい場合は、`--fields="name:primaryKey"` のように、明示的に指定してください。 -また、下記のコマンドを使って、特定のバージョンまでデータベースをマイグレートすることも可能です。 -``` -yii migrate/to 101129_185401 +#### 外部キー + +バージョン 2.0.8 からは、`foreignKey` キーワードを使って外部キーを生成することができます。 + +```php +yii migrate/create create_post --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" ``` -すなわち、マイグレーション名のタイムスタンプ部分を使って、データベースをマイグレートして到達したいバージョンを指定します。 -最後に適用されたマイグレーションと指定されたマイグレーションの間に複数のマイグレーションがある場合は、それらのマイグレーションがすべて適用されます。 -指定されたマイグレーションが適用済みである場合は、その後に適用されたすべてのマイグレーションが取り消されます (次の節で説明します)。 +これは、次のコードを生成します。 +```php +/** + * Handles the creation for table `post`. + * Has foreign keys to the tables: + * + * - `user` + * - `category` + */ +class m160328_040430_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'author_id' => $this->integer()->notNull(), + 'category_id' => $this->integer()->defaultValue(1), + 'title' => $this->string(), + 'body' => $this->text(), + ]); -マイグレーションを取り消す --------------------------- + // creates index for column `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); -適用された最後のマイグレーション (一個または複数個) を取り消したい場合は、下記のコマンドを使うことが出来ます。 + // add foreign key for table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + // creates index for column `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); -``` -yii migrate/down [step] + // add foreign key for table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // drops foreign key for table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // drops index for column `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} ``` -ここで、オプションの `step` パラメータは何個のマイグレーションを取り消すかを指定するものです。 -デフォルト値は 1 で、適用された最後のマイグレーションだけが取り消されることを意味します。 +カラムの記述における `foreignKey` キーワードの位置によって、生成されるコードが変ることはありません。 +つまり、 -前に説明したように、全てのマイグレーションが取り消せるとは限りません。 -取り消せないマイグレーションを取り消そうとすると例外が投げられて、取り消しのプロセス全体が終了させられます。 +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +これらはすべて同じコードを生成します。 + +`foreignKey` キーワードは括弧の中にパラメータを取ることが出来て、これが生成される外部キーの関連テーブルの名前になります。 +パラメータが渡されなかった場合は、テーブル名はカラム名から推測されます。 + +上記の例で `author_id:integer:notNull:foreignKey(user)` は、`user` テーブルへの外部キーを持つ `author_id` という名前のカラムを生成します。 +一方、`category_id:integer:defaultValue(1):foreignKey` は、`category` テーブルへの外部キーを持つ `category_id` というカラムを生成します。 +### テーブルを削除する + +```php +yii migrate/create drop_post --fields="title:string(12):notNull:unique,body:text" +``` + +これは、次のコードを生成します。 + +```php +class m150811_220037_drop_post extends Migration +{ + public function up() + { + $this->dropTable('post'); + } + + public function down() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } +} +``` + +### カラムを追加する + +マイグレーションの名前が `add_xxx_to_yyy` の形式である場合、ファイルの内容は、必要となる `addColumn` と `dropColumn` を含むことになります。 + +カラムを追加するためには、次のようにします。 + +```php +yii migrate/create add_position_to_post --fields="position:integer" +``` + +これが次のコードを生成します。 + +```php +class m150811_220037_add_position_to_post extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### カラムを削除する + +マイグレーションの名前が `drop_xxx_from_yyy` の形式である場合、ファイルの内容は、必要となる `addColumn` と `dropColumn` を含むことになります。 + +```php +yii migrate/create drop_position_from_post --fields="position:integer" +``` + +これは、次のコードを生成します。 + +```php +class m150811_220037_drop_position_from_post extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +<<<<<<< HEAD マイグレーションを再適用する ---------------------------- ======= @@ -446,114 +812,388 @@ yii migrate/down 3 # 最近に適用された三個のマイグレーション ## マイグレーションを再適用する >>>>>>> yiichina/master +======= +### 中間テーブルを追加する +>>>>>>> master + +マイグレーションの名前が `create_junction_xxx_and_yyy` の形式である場合は、中間テーブルを作成するのに必要となるコードが生成されます。 + +```php +yii migrate/create create_junction_post_and_tag --fields="created_at:dateTime" +``` +<<<<<<< HEAD +<<<<<<< HEAD +yii migrate/redo [step] +======= + +これは、次のコードを生成します。 + +```php +/** + * Handles the creation for table `post_tag`. + * Has foreign keys to the tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_post_and_tag extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post_tag', [ + 'post_id' => $this->integer(), + 'tag_id' => $this->integer(), + 'created_at' => $this->dateTime(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +>>>>>>> master +``` + + +### トランザクションを使うマイグレーション + +複雑な一連の DB マイグレーションを実行するときは、通常、データベースの一貫性と整合性を保つために、各マイグレーションが全体として成功または失敗することを保証する必要があります。 +この目的を達成するために、各マイグレーションの DB 操作を [トランザクション](db-dao.md#performing-transactions) で囲むことが推奨されます。 + +トランザクションを使うマイグレーションを実装するためのもっと簡単な方法は、マイグレーションのコードを `safeUp()` と `safeDown()` のメソッドに入れることです。 +この二つのメソッドが `up()` および `down()` と違う点は、これらが暗黙のうちにトランザクションに囲まれていることです。 +結果として、これらのメソッドの中で何か操作が失敗した場合は、先行する全ての操作が自動的にロールバックされます。 + +次の例では、`news` テーブルを作成するだけでなく、このテーブルに初期値となる行を挿入しています。 + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + + $this->insert('news', [ + 'title' => 'test 1', + 'content' => 'content 1', + ]); + } + + public function safeDown() + { + $this->delete('news', ['id' => 1]); + $this->dropTable('news'); + } +} +``` + +通常、`safeUp()` で複数の DB 操作を実行する場合は、`safeDown()` では実行の順序を逆にしなければならないことに注意してください。 +上記の例では、`safeUp()` では、最初にテーブルを作って、次に行を挿入し、`safeDown()` では、先に行を削除して、次にテーブルを削除しています。 + +> Note: 全ての DBMS がトランザクションをサポートしている訳ではありません。 + また、トランザクションに入れることが出来ない DB クエリもあります。 + いくつかの例を [暗黙のコミット](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html) で見ることが出来ます。 + その場合には、代りに、`up()` と `down()` を実装しなければなりません。 + +### データベースアクセスメソッド + +基底のマイグレーションクラス [[yii\db\Migration]] は、データベースにアクセスして操作するための一連のメソッドを提供しています。 +あなたは、これらのメソッドが、[[yii\db\Command]] クラスによって提供される [DAO メソッド](db-dao.md) と同じような名前を付けられていることに気付くでしょう。 +例えば、[[yii\db\Migration::createTable()]] メソッドは、[[yii\db\Command::createTable()]] と全く同じように、新しいテーブルを作成します。 + +[[yii\db\Migration]] によって提供されているメソッドを使うことの利点は、[[yii\db\Command]] インスタンスを明示的に作成する必要がないこと、そして、各メソッドを実行すると、どのようなデータベース操作がどれだけの時間をかけて実行されたかを教えてくれる有益なメッセージが自動的に表示されることです。 + +以下がそういうデータベースアクセスメソッドの一覧です。 + +* [[yii\db\Migration::execute()|execute()]]: SQL 文を実行 +* [[yii\db\Migration::insert()|insert()]]: 一行を挿入 +* [[yii\db\Migration::batchInsert()|batchInsert()]]: 複数行を挿入 +* [[yii\db\Migration::update()|update()]]: 行を更新 +* [[yii\db\Migration::delete()|delete()]]: 行を削除 +* [[yii\db\Migration::createTable()|createTable()]]: テーブルを作成 +* [[yii\db\Migration::renameTable()|renameTable()]]: テーブルの名前を変更 +* [[yii\db\Migration::dropTable()|dropTable()]]: テーブルを削除 +* [[yii\db\Migration::truncateTable()|truncateTable()]]: テーブル中の全ての行を削除 +* [[yii\db\Migration::addColumn()|addColumn()]]: カラムを追加 +* [[yii\db\Migration::renameColumn()|renameColumn()]]: カラムの名前を変更 +* [[yii\db\Migration::dropColumn()|dropColumn()]]: カラムを削除 +* [[yii\db\Migration::alterColumn()|alterColumn()]]: カラムの定義を変更 +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: プライマリキーを追加 +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: プライマリキーを削除 +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: 外部キーを追加 +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: 外部キーを削除 +* [[yii\db\Migration::createIndex()|createIndex()]]: インデックスを作成 +* [[yii\db\Migration::dropIndex()|dropIndex()]]: インデックスを削除 +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: カラムにコメントを追加 +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: カラムからコメントを削除 +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: テーブルにコメントを追加 +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: テーブルからコメントを削除 + +> Info: [[yii\db\Migration]] は、データベースクエリメソッドを提供しません。 + これは、通常、データベースからのデータ取得については、メッセージを追加して表示する必要がないからです。 + 更にまた、複雑なクエリを構築して実行するためには、強力な [クエリビルダ](db-query-builder.md) を使うことが出来るからです。 + +> Note: マイグレーションを使ってデータを操作する場合に、あなたは、あなたの [アクティブレコード](db-active-record.md) クラスをデータ操作に使えば便利じゃないか、と気付くかもしれません。 +> なぜなら、いくつかのロジックは既にアクティブレコードで実装済みだから、と。 +> しかしながら、マイグレーションの中で書かれるコードが永久に不変であることを本質とするのと対照的に、アプリケーションのロジックは変化にさらされるものであるということを心に留めなければなりません。 +> 従って、マイグレーションのコードでアクティブレコードを使用していると、アクティブレコードのレイヤにおけるロジックの変更が思いがけず既存のマイグレーションを破壊することがあり得ます。 +> このような理由のため、マイグレーションのコードはアクティブレコードのようなアプリケーションの他のロジックから独立を保つべきです。 + + +## マイグレーションを適用する + +データベースを最新の構造にアップグレードするためには、利用できる全ての新しいマイグレーションを適用するために、次のコマンドを使わなければなりません。 + +``` +yii migrate +``` + +コマンドを実行すると、まだ適用されていない全てのマイグレーションが一覧表示されます。 +リストされたマイグレーションを適用することをあなたが確認すると、タイムスタンプの値の順に、一つずつ、すべての新しいマイグレーションクラスの `up()` または `safeUp()` メソッドが実行されます。 +マイグレーションのどれかが失敗した場合は、コマンドは残りのマイグレーションを適用せずに終了します。 + +> Tip: あなたのサーバでコマンドラインを使用できない場合は +> [web shell](https://github.com/samdark/yii2-webshell) エクステンションを使ってみてください。 + +適用が成功したマイグレーションの一つ一つについて、`migration` という名前のデータベーステーブルに行が挿入されて、マイグレーションの成功が記録されます。 +この記録によって、マイグレーションツールは、どのマイグレーションが適用され、どのマイグレーションが適用されていないかを特定することが出来ます。 + +> Info: マイグレーションツールは、コマンドの [[yii\console\controllers\MigrateController::db|db]] オプションで指定されたデータベースに `migration` テーブルを自動的に作成します。 + デフォルトでは、このデータベースは `db` [アプリケーションコンポーネント](structure-application-components.md) によって指定されます。 + +時として、利用できる全てのマイグレーションではなく、一つまたは数個の新しいマイグレーションだけを適用したい場合があります。 +コマンドを実行するときに、適用したいマイグレーションの数を指定することによって、そうすることが出来ます。 +例えば、次のコマンドは、次の三個の利用できるマイグレーションを適用しようとするものです。 + +``` +yii migrate 3 +``` + +また、そのマイグレーションまでをデータベースに適用するという、特定のマイグレーションを明示的に指定することも出来ます。 +そのためには、`migrate/to` コマンドを、次のどれかの形式で使います。 + +``` +yii migrate/to 150101_185401 # タイムスタンプを使ってマイグレーションを指定 +yii migrate/to "2015-01-01 18:54:01" # strtotime() によって解釈できる文字列を使用 +yii migrate/to m150101_185401_create_news_table # フルネームを使用 +yii migrate/to 1392853618 # UNIX タイムスタンプを使用 +``` + +指定されたマイグレーションよりも古いものが適用されずに残っている場合は、指定されたものが適用される前に、すべて適用されます。 + +指定されたマイグレーションが既に適用済みである場合、それより新しいものが適用されていれば、すべて取り消されます。 + + +## マイグレーションを取り消す + +適用済みのマイグレーションを一個または複数個取り消したい場合は、下記のコマンドを使うことが出来ます。 + + +``` +yii migrate/down # 最近に適用されたマイグレーション一個を取り消す +yii migrate/down 3 # 最近に適用されたマイグレーション三個を取り消す +``` + +> Note: 全てのマイグレーションが取り消せるとは限りません。 + そのようなマイグレーションを取り消そうとするとエラーとなり、取り消しのプロセス全体が終了させられます。 + + +## マイグレーションを再適用する マイグレーションの再適用とは、指定されたマイグレーションを最初に取り消してから、再度適用することを意味します。 これは次のコマンドによって実行することが出来ます。 ``` -<<<<<<< HEAD -yii migrate/redo [step] +yii migrate/redo # 最後に適用された一個のマイグレーションを再適用する +yii migrate/redo 3 # 最後に適用された三個のマイグレーションを再適用する ``` -ここで、オプションの `step` パラメータは何個のマイグレーションを再適用するかを指定するものです。 -デフォルト値は 1 で、最後のマイグレーションだけが再適用されることを意味します。 +> Note: マイグレーションが取り消し不可能な場合は、それを再適用することは出来ません。 -マイグレーション情報を表示する ------------------------------- - -マイグレーションを適用したり取り消したりする他に、マイグレーションツールはマイグレーションの履歴、および、まだ適用されていない新しいマイグレーションを表示することも出来ます。 +## マイグレーションをリスト表示する +どのマイグレーションが適用済みであり、どのマイグレーションが未適用であるかをリスト表示するために、次のコマンドを使うことが出来ます。 ``` -yii migrate/history [limit] -yii migrate/new [limit] +yii migrate/history # 最後に適用された 10 個のマイグレーションを表示 +yii migrate/history 5 # 最後に適用された 5 個のマイグレーションを表示 +yii migrate/history all # 適用された全てのマイグレーションを表示 + +yii migrate/new # 適用可能な最初の 10 個のマイグレーションを表示 +yii migrate/new 5 # 適用可能な最初の 5 個のマイグレーションを表示 +yii migrate/new all # 適用可能な全てのマイグレーションを表示 ``` -ここで、オプションの `limit` パラメータは、何個のマイグレーションを表示するかを指定するものです。 -`limit` が指定されない場合は、利用可能な全てのマイグレーションが表示されます。 -最初のコマンドは適用済みのマイグレーションを表示し、第二のコマンドはまだ適用されていないマイグレーションを表示します。 +## マイグレーション履歴を修正する - -マイグレーション履歴を修正する ------------------------------- - -時として、実際には関係のあるマイグレーションを適用または取り消すことなく、マイグレーション履歴を特定のマイグレーションバージョンに修正したい場合があります。 -このことは新しいマイグレーションを開発するときにしばしば起ります。 -次のコマンドを使ってこの目的を達することが出来ます。 +時として、実際にマイグレーションを適用したり取り消したりするのではなく、データベースが特定のマイグレーションまでアップグレードされたとマークしたいだけ、という場合があります。 +このようなことがよく起るのは、データベースを手作業で特定の状態に変更した後に、その変更のための一つまたは複数のマイグレーションを記録はするが再度適用はしたくない、という場合です。 +次のコマンドでこの目的を達することが出来ます。 ``` -yii migrate/mark 101129_185401 +yii migrate/mark 150101_185401 # タイムスタンプを使ってマイグレーションを指定 +yii migrate/mark "2015-01-01 18:54:01" # strtotime() によって解釈できる文字列を使用 +yii migrate/mark m150101_185401_create_news_table # フルネームを使用 +yii migrate/mark 1392853618 # UNIX タイムスタンプを使用 ``` -このコマンドは `yii migrate/to` コマンドと非常によく似ていますが、マイグレーションを適用または取り消すことなく、マイグレーション履歴テーブルを指定されたバージョンに修正することだけを行うという点で違っています。 +このコマンドは、一定の行を追加または削除して、`migration` テーブルを修正し、データベースが指定されたものまでマイグレーションが適用済みであることを示します。 +このコマンドによってマイグレーションが適用されたり取り消されたりはしません。 -マイグレーションコマンドをカスタマイズする ------------------------------------------- +## マイグレーションをカスタマイズする マイグレーションコマンドをカスタマイズする方法がいくつかあります。 -### コマンドラインオプションを使う +### コマンドラインオプションを使う -マイグレーションコマンドには、コマンドラインで指定できるいくつかのオプションがあります。 +マイグレーションコマンドには、その動作をカスタマイズするために使うことが出来るコマンドラインオプションがいくつかあります。 -* `interactive`: 真偽値。マイグレーションを対話モードで実行するかどうかを指定します。 - デフォルト値は true で、指定されたマイグレーションを実行するときに、ユーザは何らかの入力を促されます。 - このオプションを false にセットして、マイグレーションがバックグラウンドプロセスとして実行されるようにすることが出来ます。 +* `interactive`: 真偽値 (デフォルト値は true)。 + マイグレーションを対話モードで実行するかどうかを指定します。 + true である場合は、コマンドが何らかの操作を実行する前に、ユーザは確認を求められます。 + コマンドがバックグラウンドのプロセスで使用される場合は、このオプションを false にセットします。 -* `migrationPath`: 文字列。全てのマイグレーションクラスファイルを保存しているディレクトリを指定します。 - このパスは、パスエイリアスの形式で指定されなければならず、また、対応するディレクトリが存在する必要があります。 - このオプションが指定されない場合は、アプリケーションのベースパスの下の `migrations` サブディレクトリが使われます。 +* `migrationPath`: 文字列 (デフォルト値は `@app/migrations`)。 + 全てのマイグレーションクラスファイルを保存しているディレクトリを指定します。 + この値は、ディレクトリパスか、パス [エイリアス](concept-aliases.md) として指定することが出来ます。 + ディレクトリが存在する必要があり、そうでなければコマンドがエラーを発生させることに注意してください。 -* `migrationTable`: 文字列。マイグレーション履歴の情報を保存するためのデータベーステーブル名を指定します。 - デフォルト値は `migration` です。このテーブルは、`version varchar(255) primary key, apply_time integer` という構造を持ちます。 +* `migrationTable`: 文字列 (デフォルト値は `migration`)。 + マイグレーション履歴の情報を保存するためのデータベーステーブル名を指定します。 + テーブルが存在しない場合は、コマンドによって自動的に作成されます。 + `version varchar(255) primary key, apply_time integer` という構造のテーブルを手作業で作成しても構いません。 -* `db`: 文字列。データベース [アプリケーションコンポーネント](structure-application-components.md) の ID を指定します。 - デフォルト値は 'db' です。 +* `db`: 文字列 (デフォルト値は `db`)。 + データベース [アプリケーションコンポーネント](structure-application-components.md) の ID を指定します。 + このコマンドによってマイグレーションを適用されるデータベースを表します。 -* `templateFile`: 文字列。マイグレーションクラスを生成するためのコードテンプレートとして使われるファイルのパスを指定します。 - このパスは、パスエイリアスの形式で指定しなければなりません (例えば、`application.migrations.template`)。 - 指定されない場合は、内部テンプレートが使用されます。 - テンプレートの中の `{ClassName}` というトークンが実際のマイグレーションクラス名によって置き換えられます。 +* `templateFile`: 文字列 (デフォルト値は `@yii/views/migration.php`)。 + スケルトンのマイグレーションクラスファイルを生成するために使用されるテンプレートファイルのパスを指定します。 + この値は、ファイルパスか、パス [エイリアス](concept-aliases.md) として指定することが出来ます。 + テンプレートファイルは PHP スクリプトであり、その中で、マイグレーションクラスの名前を取得するための `$className` という事前定義された変数を使うことが出来ます。 -これらのオプションを指定するためには、下記の書式を使って migrate コマンドを実行します。 +* `generatorTemplateFiles`: 配列 (デフォルト値は `[ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createJunctionMigration.php' + ]`)。 + マイグレーション・コードを生成するためのテンプレート・ファイルを指定します。 + 詳細は "[マイグレーションを生成する](#generating-migrations)" を参照してください。 + +* `fields`: マイグレーション・コードを生成するためのカラム定義文字列の配列。 + デフォルト値は `[]`。個々の定義の書式は `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR` です。 + 例えば、`--fields=name:string(12):notNull` は、サイズが 12 の null でない文字列カラムを作成します。 + +次の例は、これらのオプションの使い方を示すものです。 + +例えば、`forum` モジュールにマイグレーションを適用しようとしており、そのマイグレーションファイルがモジュールの `migrations` ディレクトリに配置されている場合、次のコマンドを使うことが出来ます。 ``` -yii migrate/up --option1=value1 --option2=value2 ... +# forum モジュールのマイグレーションを非対話的に適用する +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 ``` -例えば、`forum` モジュールのためのマイグレーションを実行するときに、マイグレーションファイルがモジュールの `migrations` ディレクトリに置かれている場合には、下記のコマンドを使うことが出来ます。 - - -``` -yii migrate/up --migrationPath=@app/modules/forum/migrations -``` - - -### コマンドをグローバルに構成する - -コマンドラインオプションを使ってマイグレーションコマンドをその場その場で構成することも出来ますが、場合によっては、一度にまとめてコマンドを構成しておきたいこともあります。 -例えば、マイグレーションの履歴を保存するのに別のテーブルを使用したいとか、カスタマイズしたマイグレーションテンプレートを使用したいとかです。 -そうするためには、コンソールアプリケーションの構成情報ファイルを以下のように修正します。 +### コマンドをグローバルに構成する +マイグレーションコマンドを実行するたびに同じオプションの値を入力する代りに、次のように、アプリケーションの構成情報でコマンドを一度だけ構成して済ませることが出来ます。 ```php -'controllerMap' => [ - 'migrate' => [ - 'class' => 'yii\console\controllers\MigrateController', - 'migrationTable' => 'my_custom_migrate_table', +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], ], -] +]; ``` -これで、`migrate` コマンドを実行すると、上記の構成情報が効果を発揮して、毎回コマンドラインオプションを入力する必要がなくなります。 -その他のコマンドラインオプションも、このようにして構成することが出来ます。 +上記のように構成しておくと、`migrate` コマンドを実行するたびに、`backend_migration` テーブルがマイグレーション履歴を記録するために使われるようになります。 +もう、`migrationTable` のコマンドラインオプションを使ってテーブルを指定する必要はなくなります。 -### 複数のデータベースのマイグレーション +## 複数のデータベースにマイグレーションを適用する +<<<<<<< HEAD デフォルトでは、マイグレーションは `db` [アプリケーションコンポーネント](structure-application-components.md) によって指定されるデータベースに対して適用されます。 これは、`--db` オプションを指定することによって変更することが出来ます。例えば、 ======= @@ -662,33 +1302,54 @@ return [ デフォルトでは、マイグレーションは `db` [アプリケーションコンポーネント](structure-application-components.md) によって指定された同じデータベースに対して適用されます。 マイグレーションを別のデータベースに適用したい場合は、次のように、`db` コマンドラインオプションを指定することが出来ます。 >>>>>>> yiichina/master +======= +デフォルトでは、マイグレーションは `db` [アプリケーションコンポーネント](structure-application-components.md) によって指定された同じデータベースに対して適用されます。 +マイグレーションを別のデータベースに適用したい場合は、次のように、`db` コマンドラインオプションを指定することが出来ます。 +>>>>>>> master ``` yii migrate --db=db2 ``` +<<<<<<< HEAD <<<<<<< HEAD 上記のコマンドは、デフォルトのマイグレーションパスに置かれている *全ての* マイグレーションを `db2` データベースに適用するものです。 +======= +上記のコマンドはマイグレーションを `db2` データベースに適用します。 +>>>>>>> master -アプリケーションが複数のデータベースを扱っている場合は、いくつかのマイグレーションはあるデータベースに適用されなければならず、他のマイグレーションは別のデータベースに適用されなければならない、ということがあり得ます。 -そのような場合には、異なるデータベースごとに基底マイグレーションクラスを作成して、下記のように [[yii\db\Migration::init()]] メソッドをオーバーライドすることを推奨します。 +場合によっては、*いくつかの* マイグレーションはあるデータベースに適用し、*別のいくつかの* マイグレーションはもう一つのデータベースに適用したい、ということがあります。 +この目的を達するためには、マイグレーションクラスを実装する時に、そのマイグレーションが使用する DB コンポーネントの ID を明示的に指定しなければなりません。 +例えば、次のようにします。 ```php -public function init() +db = 'db2'; - parent::init(); + public function init() + { + $this->db = 'db2'; + parent::init(); + } } ``` +上記のマイグレーションは、`db` コマンドラインオプションで別のデータベースを指定した場合でも、`db2` に対して適用されます。 +ただし、マイグレーション履歴は、`db` コマンドラインオプションで指定されたデータベースに記録されることに注意してください。 + +同じデータベースを使う複数のマイグレーションがある場合は、上記の `init()` コードを持つ基底のマイグレーションクラスを作成することを推奨します。 +そうすれば、個々のマイグレーションクラスは、その基底クラスから拡張することが出来ます。 + こうすると、特定のデータベースに適用されるべきマイグレーションを作成するためには、対応する基底マイグレーションクラスから拡張するだけで済みます。 これで、`yii migrate` コマンドを実行すると、全てのマイグレーションはそれぞれ対応するデータベースに対して適用されるようになります。 -> Info|情報: それぞれのマイグレーションはハードコードされた DB 接続を使用しますので、`migrate` コマンドの `--db` オプションは効果を持ちません。 - また、マイグレーション履歴はデフォルトの `db` データベースに保存されることに注意してください。 - -`--db` オプションによる DB 接続の変更をサポートしたい場合は、次のような別の方法で複数のデータベースを扱うことが出来ます。 +> Tip: 異なるデータベースを操作するためには、[[yii\db\Migration::db|db]] プロパティを設定する以外にも、マイグレーションクラスの中で新しいデータベース接続を作成するという方法があります。 + そうすれば、そのデータベース接続で [DAO メソッド](db-dao.md) を使って、違うデータベースを操作することが出来ます。 +<<<<<<< HEAD それぞれのデータベースについて、マイグレーションパスを作成し、対応する全てのマイグレーションクラスをそこに保存します。 マイグレーションを適用するためには、次のようなコマンドを実行します。 ======= @@ -727,6 +1388,10 @@ class m150101_185401_create_news_table extends Migration 複数のデータベースに対してマイグレーションを適用するために採用できるもう一つの戦略としては、異なるデータベースに対するマイグレーションは異なるマイグレーションパスに保持する、というものがあります。 そうすれば、次のように、異なるデータベースのマイグレーションを別々のコマンドで適用することが出来ます。 >>>>>>> yiichina/master +======= +複数のデータベースに対してマイグレーションを適用するために採用できるもう一つの戦略としては、異なるデータベースに対するマイグレーションは異なるマイグレーションパスに保持する、というものがあります。 +そうすれば、次のように、異なるデータベースのマイグレーションを別々のコマンドで適用することが出来ます。 +>>>>>>> master ``` yii migrate --migrationPath=@app/migrations/db1 --db=db1 @@ -734,8 +1399,12 @@ yii migrate --migrationPath=@app/migrations/db2 --db=db2 ... ``` +<<<<<<< HEAD <<<<<<< HEAD > Info|情報: 上記の方法では、マイグレーション履歴は `--db` オプションによって指定された別々のデータベースに保存されます。 ======= 最初のコマンドは `@app/migrations/db1` にあるマイグレーションを `db1` データベースに適用し、第二のコマンドは `@app/migrations/db2` にあるマイグレーションを `db2` データベースに適用する、という具合です。 >>>>>>> yiichina/master +======= +最初のコマンドは `@app/migrations/db1` にあるマイグレーションを `db1` データベースに適用し、第二のコマンドは `@app/migrations/db2` にあるマイグレーションを `db2` データベースに適用する、という具合です。 +>>>>>>> master diff --git a/docs/guide-ja/db-mongodb.md b/docs/guide-ja/db-mongodb.md deleted file mode 100644 index 14302535aa..0000000000 --- a/docs/guide-ja/db-mongodb.md +++ /dev/null @@ -1,6 +0,0 @@ -Mongo DB -======== - -> Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 diff --git a/docs/guide-ja/db-query-builder.md b/docs/guide-ja/db-query-builder.md index 4b7a940130..812c2c281b 100644 --- a/docs/guide-ja/db-query-builder.md +++ b/docs/guide-ja/db-query-builder.md @@ -1,8 +1,8 @@ クエリビルダ ============ -[データベースアクセスオブジェクト](db-dao.md) の上に構築されているクエリビルダは、SQL 文をプログラム的に、かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。 -クエリビルダを使うと、生の SQL を書くことに比べて、より読みやすい SQL 関連のコードを書き、より安全な SQL 文を生成することが容易になります。 +[データベースアクセスオブジェクト](db-dao.md) の上に構築されているクエリビルダは、SQL クエリをプログラム的に、かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。 +クエリビルダを使うと、生の SQL 文を書くことに比べて、より読みやすい SQL 関連のコードを書き、より安全な SQL 文を生成することが容易になります。 通常、クエリビルダの使用は、二つのステップから成ります。 @@ -20,7 +20,7 @@ $rows = (new \yii\db\Query()) ->all(); ``` -上記のコードは、次の SQL 文を生成して実行します。 +上記のコードは、次の SQL クエリを生成して実行します。 ここでは、`:last_name` パラメータは `'Smith'` という文字列にバインドされています。 ```sql @@ -30,15 +30,15 @@ WHERE `last_name` = :last_name LIMIT 10 ``` -> Info|情報: 通常は、[[yii\db\QueryBuilder]] ではなく、主として [[yii\db\Query]] を使用します。 +> Info: 通常は、[[yii\db\QueryBuilder]] ではなく、主として [[yii\db\Query]] を使用します。 前者は、クエリメソッドの一つを呼ぶときに、後者によって黙示的に起動されます。 [[yii\db\QueryBuilder]] は、DBMS に依存しない [[yii\db\Query]] オブジェクトから、DBMS に依存する SQL 文を生成する (例えば、テーブルやカラムの名前を DBMS ごとに違う方法で引用符で囲む) 役割を負っているクラスです。 ## クエリを構築する -[[yii\db\Query]] オブジェクトを構築するために、さまざまなクエリ構築メソッドを呼んで、SQL 文のさまざまな部分を定義します。 +[[yii\db\Query]] オブジェクトを構築するために、さまざまなクエリ構築メソッドを呼んで、SQL クエリのさまざまな部分を定義します。 これらのメソッドの名前は、SQL 文の対応する部分に使われる SQL キーワードに似たものになっています。 -例えば、SQL 文の `FROM` の部分を定義するためには、`from()` メソッドを呼び出します。 +例えば、SQL クエリの `FROM` の部分を定義するためには、`from()` メソッドを呼び出します。 クエリ構築メソッドは、すべて、クエリオブジェクトそのものを返しますので、複数の呼び出しをチェーンしてまとめることが出来ます。 以下で、それぞれのクエリ構築メソッドの使用方法を説明しましょう。 @@ -58,7 +58,7 @@ $query->select(['id', 'email']); $query->select('id, email'); ``` -選択されるカラム名は、生の SQL を書くときにするように、テーブル接頭辞 および/または カラムのエイリアスを含むことが出来ます。 +選択されるカラム名は、生の SQL クエリを書くときにするように、テーブル接頭辞 および/または カラムのエイリアスを含むことが出来ます。 例えば、 ```php @@ -85,12 +85,19 @@ $query->select(['user_id' => 'user.id', 'email']); ```php <<<<<<< HEAD +<<<<<<< HEAD $query->select(["CONCAT(first_name, ' ', last_name]) AS full_name", 'email']); ======= $query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); >>>>>>> yiichina/master +======= +$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); +>>>>>>> master ``` +生の SQL が使われる場所ではどこでもそうですが、セレクトに DB 式を書く場合には、テーブルやカラムの名前を表すために +[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。 + バージョン 2.0.1 以降では、サブクエリもセレクトすることが出来ます。 各サブクエリは、[[yii\db\Query]] オブジェクトの形で指定しなければなりません。 例えば、 @@ -120,7 +127,7 @@ $query->from('user'); ``` セレクトの対象になる (一つまたは複数の) テーブルは、文字列または配列として指定することが出来ます。 -テーブル名は、生の SQL を書くときにするように、スキーマ接頭辞 および/または テーブルエイリアスを含むことが出来ます。例えば、 +テーブル名は、生の SQL 文を書くときにするように、スキーマ接頭辞 および/または テーブルエイリアスを含むことが出来ます。例えば、 ```php $query->from(['public.user u', 'public.post p']); @@ -149,7 +156,7 @@ $query->from(['u' => $subQuery]); ### [[yii\db\Query::where()|where()]] -[[yii\db\Query::where()|where()]] メソッドは、SQL 文の `WHERE` 句を定義します。 +[[yii\db\Query::where()|where()]] メソッドは、SQL クエリの `WHERE` 句を定義します。 `WHERE` の条件を指定するために、次の三つの形式から一つを選んで使うことが出来ます。 - 文字列形式、例えば、`'status=1'` @@ -159,7 +166,7 @@ $query->from(['u' => $subQuery]); #### 文字列形式 -文字列形式は、非常に単純な条件を定義する場合に最適です。 +文字列形式は、非常に単純な条件を定義する場合や、DBMS の組み込み関数を使う必要がある場合に最適です。 これは、生の SQL を書いている場合と同じように動作します。 例えば、 @@ -168,6 +175,9 @@ $query->where('status=1'); // あるいは、パラメータバインディングを使って、動的にパラメータをバインドする $query->where('status=:status', [':status' => $status]); + +// date フィールドに対して MySQL の YEAR() 関数を使う生の SQL +$query->where('YEAR(somedate) = 2015'); ``` 次のように、条件式に変数を直接に埋め込んではいけません。 @@ -185,6 +195,9 @@ $query->where('status=:status') ->addParams([':status' => $status]); ``` +生の SQL が使われる場所ではどこでもそうですが、文字列形式で条件を書く場合には、テーブルやカラムの名前を表すために +[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。 + #### ハッシュ形式 @@ -212,6 +225,10 @@ $userQuery = (new Query())->select('id')->from('user'); $query->where(['id' => $userQuery]); ``` +ハッシュ形式を使う場合、Yii は内部的にパラメータバインディングを使用します。 +従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。 + + #### 演算子形式 演算子形式を使うと、任意の条件をプログラム的な方法で指定することが出来ます。 @@ -257,7 +274,7 @@ $query->where(['id' => $userQuery]); `false` または空の配列を使って、値が既にエスケープ済みであり、それ以上エスケープを適用すべきでないことを示すことが出来ます。 エスケープマッピングを使用する場合 (または第三のオペランドが与えられない場合) は、値が自動的に一組のパーセント記号によって囲まれることに注意してください。 - > Note|注意: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。 + > Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。 - `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、`like` 演算子と同じです。 @@ -273,6 +290,9 @@ $query->where(['id' => $userQuery]); - `>`、`<=`、その他、二つのオペランドを取る有効な DB 演算子全て: 最初のオペランドはカラム名、第二のオペランドは値でなければなりません。 例えば、`['>', 'age', 10]` は `age>10` を生成します。 +演算子形式を使う場合、Yii は内部的にパラメータバインディングを使用します。 +従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。 + #### 条件を追加する @@ -293,7 +313,7 @@ if (!empty($search)) { } ``` -`$search` が空でない場合は次の SQL が生成されます。 +`$search` が空でない場合は次の WHERE 条件 が生成されます。 ```sql WHERE (`status` = 10) AND (`title` LIKE '%yii%') @@ -314,15 +334,29 @@ $query->filterWhere([ ``` [[yii\db\Query::filterWhere()|filterWhere()]] と [[yii\db\Query::where()|where()]] の唯一の違いは、前者は [ハッシュ形式](#hash-format) の条件において提供された空の値を無視する、という点です。 -従って、`$email` が空で `$sername` がそうではない場合は、上記のコードは、結果として `...WHERE username=:username` という SQL になります。 +従って、`$email` が空で `$sername` がそうではない場合は、上記のコードは、結果として `WHERE username=:username` という SQL 条件になります。 -> Info|情報: 値が空であると見なされるのは、null、空の配列、空の文字列、または空白のみを含む文字列である場合です。 +> Info: 値が空であると見なされるのは、null、空の配列、空の文字列、または空白のみを含む文字列である場合です。 [[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、既存の条件に別のフィルタ条件を追加することも出来ます。 +さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる [[yii\db\Query::andFilterCompare()]] があります。 + +```php +$query->andFilterCompare('name', 'John Doe'); +$query->andFilterCompare('rating', '>9'); +$query->andFilterCompare('value', '<=100'); +``` + +比較演算子を明示的に指定することも可能です。 + +```php +$query->andFilterCompare('name', 'Doe', 'like'); +``` + ### [[yii\db\Query::orderBy()|orderBy()]] -[[yii\db\Query::orderBy()|orderBy()]] メソッドは SQL 文の `ORDER BY` 句を指定します。例えば、 +[[yii\db\Query::orderBy()|orderBy()]] メソッドは SQL クエリの `ORDER BY` 句を指定します。例えば、 ```php @@ -336,14 +370,14 @@ $query->orderBy([ 上記のコードにおいて、配列のキーはカラム名であり、配列の値は並べ替えの方向です。 PHP の定数 `SORT_ASC` は昇順、`SORT_DESC` は降順を指定するものです。 -`ORDER BY` が単純なカラム名だけを含む場合は、生の SQL を書くときにするように、文字列を使って指定することが出来ます。 +`ORDER BY` が単純なカラム名だけを含む場合は、生の SQL 文を書くときにするように、文字列を使って指定することが出来ます。 例えば、 ```php $query->orderBy('id ASC, name DESC'); ``` -> Note|注意: `ORDER BY` が何らかの DB 式を含む場合は、配列形式を使わなければなりません。 +> Note: `ORDER BY` が何らかの DB 式を含む場合は、配列形式を使わなければなりません。 [[yii\db\Query::addOrderBy()|addOrderBy()]] を呼んで、`ORDER BY' 句にカラムを追加することが出来ます。 例えば、 @@ -355,7 +389,7 @@ $query->orderBy('id ASC') ### [[yii\db\Query::groupBy()|groupBy()]] -[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL 文の `GROUP BY` 句を指定します。 +[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL クエリの `GROUP BY` 句を指定します。 例えば、 ```php @@ -363,18 +397,22 @@ $query->orderBy('id ASC') $query->groupBy(['id', 'status']); ``` -`GROUP BY` が単純なカラム名だけを含む場合は、生の SQL を書くときにするように、文字列を使って指定することが出来ます。 +`GROUP BY` が単純なカラム名だけを含む場合は、生の SQL 文を書くときにするように、文字列を使って指定することが出来ます。 例えば、 ```php <<<<<<< HEAD +<<<<<<< HEAD $query->groupBy('id, status']); ======= $query->groupBy('id, status'); >>>>>>> yiichina/master +======= +$query->groupBy('id, status'); +>>>>>>> master ``` -> Note|注意: `GROUP BY` が何らかの DB 式を含む場合は、配列形式を使わなければなりません。 +> Note: `GROUP BY` が何らかの DB 式を含む場合は、配列形式を使わなければなりません。 [[yii\db\Query::addGroupBy()|addGroupBy()]] を呼んで、`GROUP BY` 句にカラムを追加することが出来ます。 例えば、 @@ -387,7 +425,7 @@ $query->groupBy(['id', 'status']) ### [[yii\db\Query::having()|having()]] -[[yii\db\Query::having()|having()]] メソッドは SQL 文の `HAVING` 句を指定します。 +[[yii\db\Query::having()|having()]] メソッドは SQL クエリの `HAVING` 句を指定します。 このメソッドが取る条件は、[where()](#where) と同じ方法で指定することが出来ます。 例えば、 @@ -410,7 +448,7 @@ $query->having(['status' => 1]) ### [[yii\db\Query::limit()|limit()]] と [[yii\db\Query::offset()|offset()]] -[[yii\db\Query::limit()|limit()]] と [[yii\db\Query::offset()|offset()]] のメソッドは、SQL 文の `LIMIT` と `OFFSET` 句を指定します。 +[[yii\db\Query::limit()|limit()]] と [[yii\db\Query::offset()|offset()]] のメソッドは、SQL クエリの `LIMIT` 句と `OFFSET` 句を指定します。 例えば、 ```php @@ -420,12 +458,12 @@ $query->limit(10)->offset(20); 無効な上限やオフセット (例えば、負の数) を指定した場合は、無視されます。 -> Info|情報: `LIMIT` と `OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、クエリビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。 +> Info: `LIMIT` と `OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、クエリビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。 ### [[yii\db\Query::join()|join()]] -[[yii\db\Query::join()|join()]] メソッドは SQL 文の `JOIN` 句を指定します。例えば、 +[[yii\db\Query::join()|join()]] メソッドは SQL クエリの `JOIN` 句を指定します。例えば、 ```php // ... LEFT JOIN `post` ON `post`.`user_id` = `user`.`id` @@ -438,6 +476,9 @@ $query->join('LEFT JOIN', 'post', 'post.user_id = user.id'); - `$table`: 結合されるテーブルの名前。 - `$on`: オプション。結合条件、すなわち、`ON` 句。 条件の指定方法の詳細については、[where()](#where) を参照してください。 + カラムに基づく条件を指定する場合は、配列記法は**使えない**ことに注意してください。 + 例えば、`['user.id' => 'comment.userId']` は、user の id が `'comment.userId'` という文字列でなければならない、という条件に帰結します。 + 配列記法ではなく文字列記法を使って、`'user.id = comment.userId'` という条件を指定しなければなりません。 - `$params`: オプション。結合条件にバインドされるパラメータ。 `INNER JOIN`、`LEFT JOIN` および `RIGHT JOIN` を指定するためには、それぞれ、次のショートカットメソッドを使うことが出来ます。 @@ -468,7 +509,7 @@ $query->leftJoin(['u' => $subQuery], 'u.id = author_id'); ### [[yii\db\Query::union()|union()]] -[[yii\db\Query::union()|union()]] メソッドは SQL 文の `UNION` 句を指定します。例えば、 +[[yii\db\Query::union()|union()]] メソッドは SQL クエリの `UNION` 句を指定します。例えば、 ```php $query1 = (new \yii\db\Query()) @@ -503,11 +544,16 @@ $query1->union($query2); 上記のメソッドの全ては、オプションで、DB クエリの実行に使用されるべき [[yii\db\Connection|DB 接続]] を表す `$db` パラメータを取ることが出来ます。 <<<<<<< HEAD +<<<<<<< HEAD このパラメータを省略した場合は、DB 接続として `db` アプリケーションコンポーネントが使用されます。 ======= このパラメータを省略した場合は、DB 接続として `db` [アプリケーションコンポーネント](structure-application-components.md) が使用されます。 >>>>>>> yiichina/master 次に `count()` クエリメソッドを使う例をもう一つ挙げます。 +======= +このパラメータを省略した場合は、DB 接続として `db` [アプリケーションコンポーネント](structure-application-components.md) が使用されます。 +次に [[yii\db\Query::count()|count()]] クエリメソッドを使う例をもう一つ挙げます。 +>>>>>>> master ```php // 実行される SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name @@ -521,7 +567,7 @@ $count = (new \yii\db\Query()) * [[yii\db\QueryBuilder]] を呼んで、[[yii\db\Query]] の現在の構成に基づいた SQL 文を生成する。 * 生成された SQL 文で [[yii\db\Command]] オブジェクトを作成する。 -* [[yii\db\Command]] のクエリメソッド (例えば `queryAll()`) を呼んで、SQL 文を実行し、データを取得する。 +* [[yii\db\Command]] のクエリメソッド (例えば [[yii\db\Command::queryAll()|queryAll()]]) を呼んで、SQL 文を実行し、データを取得する。 場合によっては、[[yii\db\Query]] オブジェクトから構築された SQL 文を調べたり使ったりしたいことがあるでしょう。 次のコードを使って、その目的を達することが出来ます。 @@ -573,6 +619,13 @@ $query = (new \yii\db\Query()) この無名関数は、現在の行データを含む `$row` というパラメータを取り、現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。 +> Note: [[yii\db\Query::groupBy()|groupBy()]] や [[yii\db\Query::orderBy()|orderBy()]] +> のようなクエリメソッドが SQL に変換されてクエリの一部となるのとは対照的に、 +> このメソッドはデータベースからデータが取得された後で動作します。 +> このことは、クエリの SELECT に含まれるカラム名だけを使うことが出来る、ということを意味します。 +> また、テーブルプレフィックスを付けてカラムを選択した場合、例えば `customer.id` を選択した場合は、 +> リザルトセットのカラム名は `id` しか含みませんので、テーブルプレフィックス無しで `->indexBy('id')` と呼ぶ必要があります。 + ## バッチクエリ @@ -622,5 +675,6 @@ foreach ($query->batch() as $users) { } foreach ($query->each() as $username => $user) { + // ... } ``` diff --git a/docs/guide-ja/db-redis.md b/docs/guide-ja/db-redis.md deleted file mode 100644 index ad8fbe938f..0000000000 --- a/docs/guide-ja/db-redis.md +++ /dev/null @@ -1,6 +0,0 @@ -Redis -===== - -> Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 diff --git a/docs/guide-ja/db-sphinx.md b/docs/guide-ja/db-sphinx.md deleted file mode 100644 index 9a8f486bf6..0000000000 --- a/docs/guide-ja/db-sphinx.md +++ /dev/null @@ -1,6 +0,0 @@ -Sphinx Search -============= - -> Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 diff --git a/docs/guide-ja/helper-array.md b/docs/guide-ja/helper-array.md index fb490cd9bd..0edcb00447 100644 --- a/docs/guide-ja/helper-array.md +++ b/docs/guide-ja/helper-array.md @@ -1,12 +1,12 @@ -ArrayHelper -=========== +配列ヘルパ +========== [PHP の充実した配列関数](http://php.net/manual/ja/book.array.php) への追加として、Yii の配列ヘルパは、配列をさらに効率的に扱うことを可能にするスタティックなメソッドを提供しています。 ## 値を取得する -配列、オブジェクト、またはその両方から成る複雑な構造から標準的な PHP を使って値を取得することは、何度も繰り返さねばならない面倒くさい仕事です。 +配列、オブジェクト、またはその両方から成る複雑な構造から標準的な PHP を使って値を取得することは、非常に面倒くさい仕事です。 最初に `isset` でキーの存在をチェックしなければならず、次に、キーが存在していれば値を取得し、存在していなければ、デフォルト値を提供しなければなりません。 ```php @@ -109,30 +109,91 @@ $result = ArrayHelper::getColumn($array, function ($element) { ## 配列を再インデックスする 指定されたキーに従って配列にインデックスを付けるために、`index` メソッドを使うことが出来ます。 -入力値の配列は、多次元配列であるか、オブジェクトの配列でなければなりません。 -キーは、サブ配列のキーの名前、オブジェクトのプロパティの名前、または、配列要素を与えられてキーの値を返す無名関数とすることが出来ます。 +入力値は、多次元配列であるか、オブジェクトの配列でなければなりません。 +`$key` は、サブ配列のキーの名前、オブジェクトのプロパティの名前、または、キーとして使用される値を返す無名関数とすることが出来ます。 + +`$groups` 属性はキーの配列であり、入力値の配列を一つまたは複数のサブ配列にグループ化するために使用されます。 + +特定の要素の `$key` 属性またはその値が null であるとき、`$groups` が定義されていない場合は、その要素は破棄されて、結果には入りません。 +そうではなく、`$groups` が指定されている場合は、配列の要素はキー無しで結果の配列に追加されます。 -キーの値が null である場合、対応する配列要素は破棄されて、結果には入りません。 例えば、 ```php $array = [ - ['id' => '123', 'data' => 'abc'], - ['id' => '345', 'data' => 'def'], + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], ]; $result = ArrayHelper::index($array, 'id'); -// 結果は次のようになります -// [ -// '123' => ['id' => '123', 'data' => 'abc'], -// '345' => ['id' => '345', 'data' => 'def'], -// ] +``` -// 無名関数を使う +結果は、`id` 属性の値をキーとする連想配列になります。 + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // 元の配列の2番目の要素は、同じ id であるため、最後の要素によって上書きされます +] +``` + +`$key` として無名関数を渡しても同じ結果になります。 + +```php $result = ArrayHelper::index($array, function ($element) { return $element['id']; }); ``` +`id` を3番目の引数として渡すと、`$array` を `id` によってグループ化することが出来ます。 + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +結果は、最初のレベルが `id` でグループ化され、第2のレベルはインデックスされていない連想配列になります。 + +```php +[ + '123' => [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ], + '345' => [ // このインデックスを持つ全ての要素が結果の配列に入る + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ] +] +``` + +無名関数を配列のグループ化に使うことも出来ます。 + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +結果は、最初のレベルが `id` でグループ化され、第2のレベルが `device` でグループ化され、第3のレベルが `data` でインデックスされた連想配列になります。 + +```php +[ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ] + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + ] + ] +] +``` + ## マップを作成する @@ -264,7 +325,7 @@ $decoded = ArrayHelper::htmlDecode($data); ```php $posts = Post::find()->limit(10)->all(); -$data = ArrayHelper::toArray($post, [ +$data = ArrayHelper::toArray($posts, [ 'app\models\Post' => [ 'id', 'title', @@ -301,3 +362,22 @@ $data = ArrayHelper::toArray($post, [ ``` 特定のクラスについて、配列に変換するデフォルトの方法を提供するためには、そのクラスの [[yii\base\Arrayable|Arrayable]] インタフェイスを実装することが出来ます。 + +## 配列の中にあるかどうか調べる + +ある要素が配列の中に存在するかどうか、また、一連の要素が配列のサブセットであるかどうか、ということを調べる必要がある場合がよくあります。 +PHP は `in_array()` を提供していますが、これはサブセットや `\Traversable` なオブジェクトをサポートしていません。 + +この種のチェックを助けるために、[[yii\base\ArrayHelper]] は [[yii\base\ArrayHelper::isIn()|isIn()]] +および [[yii\base\ArrayHelper::isSubset()|isSubset()]] を [[in_array()]] と同じシグニチャで提供しています。 + +```php +// true +ArrayHelper::isIn('a', ['a']); +// true +ArrayHelper::isIn('a', new(ArrayObject['a'])); + +// true +ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) + +``` diff --git a/docs/guide-ja/helper-html.md b/docs/guide-ja/helper-html.md index a1f6eac544..529411fe50 100644 --- a/docs/guide-ja/helper-html.md +++ b/docs/guide-ja/helper-html.md @@ -6,7 +6,7 @@ Html ヘルパ Yii はそのような手助けを Html ヘルパの形式で提供します。 これは、よく使われる HTML タグとそのオプションやコンテントを処理するための一連のスタティックメソッドを提供するものです。 -> Note|注意: あなたのマークアップがおおむね静的なものである場合は、HTML を直接に使用する方が適切です。 +> Note: あなたのマークアップがおおむね静的なものである場合は、HTML を直接に使用する方が適切です。 > 何でもかんでも Html ヘルパの呼び出しでラップする必要はありません。 @@ -74,7 +74,57 @@ echo Html::tag('div', 'Pwede na', $options); //
Pwede na
``` -同じことを `style` 属性のスタイルについて行うためには、次のようにします。 +配列形式を使って複数の CSS クラスを指定することも出来ます。 + +```php +$options = ['class' => ['btn', 'btn-default']]; + +echo Html::tag('div', 'Save', $options); +// '
Save
' をレンダリングする +``` + +クラスを追加・削除する際にも配列形式を使うことが出来ます。 + +```php +$options = ['class' => 'btn']; + +if ($type === 'success') { + Html::addCssClass($options, ['btn-success', 'btn-lg']); +} + +echo Html::tag('div', 'Save', $options); +// '
Save
' をレンダリングする +``` + +`Html::addCssClass()` はクラスの重複を防止しますので、同じクラスが二度出現するかも知れないと心配する必要はありません。 + +```php +$options = ['class' => 'btn btn-default']; + +Html::addCssClass($options, 'btn-default'); // クラス 'btn-default' は既に存在する + +echo Html::tag('div', 'Save', $options); +// '
Save
' をレンダリングする +``` + +CSS のクラスオプションを配列形式で指定する場合には、名前付きのキーを使ってクラスの論理的な目的を示すことが出来ます。 +この場合、`Html::addCssClass()` で同じキーを持つクラスを指定しても無視されます。 + +```php +$options = [ + 'class' => [ + 'btn', + 'theme' => 'btn-default', + ] +]; + +Html::addCssClass($options, ['theme' => 'btn-success']); // 'theme' キーは既に使用されている + +echo Html::tag('div', 'Save', $options); +// '
Save
' をレンダリングする +``` + +CSS のスタイルも `style` 属性を使って、同じように設定することが出来ます。 ```php $options = ['style' => ['width' => '100px', 'height' => '100px']]; @@ -112,7 +162,7 @@ $decodedUserName = Html::decode($userName); フォームのマークアップを扱う仕事は、極めて面倒くさく、エラーを生じがちなものです。 このため、フォームのマークアップの仕事を助けるための一群のメソッドがあります。 -> Note|注意: モデルを扱っており、バリデーションが必要である場合は、[[yii\widgets\ActiveForm|ActiveForm]] を使うことを検討してください。 +> Note: モデルを扱っており、バリデーションが必要である場合は、[[yii\widgets\ActiveForm|ActiveForm]] を使うことを検討してください。 ### フォームを作成する diff --git a/docs/guide-ja/helper-overview.md b/docs/guide-ja/helper-overview.md index 8f1722eb86..4364f860db 100644 --- a/docs/guide-ja/helper-overview.md +++ b/docs/guide-ja/helper-overview.md @@ -1,7 +1,7 @@ ヘルパ ====== -> Note|注意: この節はまだ執筆中です。 +> Note: この節はまだ執筆中です。 Yii は、一般的なコーディングのタスク、例えば、文字列や配列の操作、HTML コードの生成などを手助けする多くのクラスを提供しています。 これらのヘルパクラスは `yii\helpers` 名前空間の下に編成されており、すべてスタティックなクラス (すなわち、スタティックなプロパティとメソッドのみを含み、インスタンス化すべきでないクラス) です。 @@ -15,7 +15,7 @@ use yii\helpers\Html; echo Html::encode('Test > test'); ``` -> Note|注意: [ヘルパクラスをカスタマイズする](#customizing-helper-classes) ことをサポートするために、Yii はコアヘルパクラスのすべてを二つのクラスに分割しています。 +> Note: [ヘルパクラスをカスタマイズする](#customizing-helper-classes) ことをサポートするために、Yii はコアヘルパクラスのすべてを二つのクラスに分割しています。 > すなわち、基底クラス (例えば `BaseArrayHelper`) と具象クラス (例えば `ArrayHelper`) です。 > ヘルパを使うときは、具象クラスのみを使うべきであり、基底クラスは決して使ってはいけません。 @@ -25,18 +25,18 @@ echo Html::encode('Test > test'); 以下のコアヘルパクラスが Yii のリリースにおいて提供されています。 -- [ArrayHelper](helper-array.md) +- [配列ヘルパ](helper-array.md) - Console - FileHelper -- [Html](helper-html.md) +- FormatConverter +- [Html ヘルパ](helper-html.md) - HtmlPurifier -- Image +- Imagine (yii2-imagine エクステンションによって提供) - Inflector - Json - Markdown -- Security - StringHelper -- [Url](helper-url.md) +- [Url ヘルパ](helper-url.md) - VarDumper diff --git a/docs/guide-ja/helper-url.md b/docs/guide-ja/helper-url.md index e754b4c3c1..d25070b95b 100644 --- a/docs/guide-ja/helper-url.md +++ b/docs/guide-ja/helper-url.md @@ -16,9 +16,9 @@ $absoluteHomeUrl = Url::home(true); $httpsAbsoluteHomeUrl = Url::home('https'); ``` -パラメータが渡されない場合は、相対 URL が生成されます。 -`true` を渡すと、現在のスキーマの絶対 URL を取得することが出来ます。 -または、スキーマを明示的に指定して (`http`, `https`) 絶対 URL を取得することも出来ます。 +パラメータが渡されない場合は、生成される URL は相対 URL になります。 +パラメータとして `true` を渡せば、現在のスキーマの絶対 URL を取得することが出来ます。 +または、スキーマ (`http`, `https`) を明示的に指定しても構いません。 現在のリクエストのベース URL を取得するためには、次のようにします。 @@ -45,14 +45,14 @@ $url = Url::toRoute(['product/view', 'id' => 42]); 配列の形式は、以下のようにしなければなりません。 ```php -// /index.php?r=site/index¶m1=value1¶m2=value2 を生成 +// /index.php?r=site%2Findex¶m1=value1¶m2=value2 を生成 ['site/index', 'param1' => 'value1', 'param2' => 'value2'] ``` アンカーの付いた URL を生成したい場合は、`#` パラメータを持つ配列を使うことが出来ます。例えば、 ```php -// /index.php?r=site/index¶m1=value1#name を生成 +// /index.php?r=site%2Findex¶m1=value1#name を生成 ['site/index', 'param1' => 'value1', '#' => 'name'] ``` @@ -70,19 +70,19 @@ $url = Url::toRoute(['product/view', 'id' => 42]); 以下に、このメソッドの使用例をいくつか挙げます。 ```php -// /index.php?r=site/index +// /index.php?r=site%2Findex echo Url::toRoute('site/index'); -// /index.php?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); -// /index.php?r=post/edit&id=100 エイリアス "@postEdit" は "post/edit" と定義されていると仮定 +// /index.php?r=post%2Fedit&id=100 エイリアス "@postEdit" は "post/edit" と定義されていると仮定 echo Url::toRoute(['@postEdit', 'id' => 100]); -// http://www.example.com/index.php?r=site/index +// http://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', true); -// https://www.example.com/index.php?r=site/index +// https://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', 'https'); ``` @@ -104,13 +104,13 @@ echo Url::toRoute('site/index', 'https'); 下記にいくつかの用例を挙げます。 ```php -// /index.php?r=site/index +// /index.php?r=site%2Findex echo Url::to(['site/index']); -// /index.php?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); -// /index.php?r=post/edit&id=100 エイリアス "@postEdit" が "post/edit" と定義されていると仮定 +// /index.php?r=post%2Fedit&id=100 エイリアス "@postEdit" が "post/edit" と定義されていると仮定 echo Url::to(['@postEdit', 'id' => 100]); // 現在リクエストされている URL @@ -136,12 +136,12 @@ echo Url::to('@web/images/logo.gif', 'https'); ```php // $_GET が ['id' => 123, 'src' => 'google'] であり、現在のルートが "post/view" であると仮定 -// /index.php?r=post/view&id=123&src=google +// /index.php?r=post%2Fview&id=123&src=google echo Url::current(); -// /index.php?r=post/view&id=123 +// /index.php?r=post%2Fview&id=123 echo Url::current(['src' => null]); -// /index.php?r=post/view&id=100&src=google +// /index.php?r=post%2Fview&id=100&src=google echo Url::current(['id' => 100]); ``` diff --git a/docs/guide-ja/images/start-country-list.png b/docs/guide-ja/images/start-country-list.png index 6994da2103..375419414d 100644 Binary files a/docs/guide-ja/images/start-country-list.png and b/docs/guide-ja/images/start-country-list.png differ diff --git a/docs/guide-ja/images/tutorial-console-help.png b/docs/guide-ja/images/tutorial-console-help.png index 34812a6d90..15b8b66a03 100644 Binary files a/docs/guide-ja/images/tutorial-console-help.png and b/docs/guide-ja/images/tutorial-console-help.png differ diff --git a/docs/guide-ja/input-file-upload.md b/docs/guide-ja/input-file-upload.md index 8e1025c875..227d2f3b37 100644 --- a/docs/guide-ja/input-file-upload.md +++ b/docs/guide-ja/input-file-upload.md @@ -1,15 +1,16 @@ ファイルをアップロードする ========================== -Yii におけるファイルのアップロードは、フォームモデル、その検証規則、そして、いくらかのコントローラコードによって行われます。 -アップロードを適切に処理するために何が必要とされるのか、見ていきましよう。 +Yii におけるファイルのアップロードは、通常、アップロードされる個々のファイルを `UploadedFile` としてカプセル化する [[yii\web\UploadedFile]] の助けを借りて実行されます。 +これを [[yii\widgets\ActiveForm]] および [モデル](structure-models.md) と組み合わせることで、安全なファイルアップロードメカニズムを簡単に実装することが出来ます。 -一つのファイルをアップロードする --------------------------------- +## モデルを作成する + +プレーンなテキストインプットを扱うのと同じように、一つのファイルをアップロードするためには、モデルクラスを作成して、そのモデルの一つの属性を使ってアップロードされるファイルのインスタンスを保持します。 +また、ファイルのアップロードを検証するために、検証規則も宣言しなければなりません。 +例えば、 -まず最初に、ファイルのアップロードを処理するモデルを作成する必要があります。 -次の内容を持つ `models/UploadForm.php` を作成してください。 ```php namespace app\models; @@ -17,34 +18,49 @@ namespace app\models; use yii\base\Model; use yii\web\UploadedFile; -/** - * UploadForm : アップロードのフォームの背後にあるモデル - */ class UploadForm extends Model { /** - * @var UploadedFile file 属性 + * @var UploadedFile */ - public $file; + public $imageFile; - /** - * @return array 検証規則 - */ public function rules() { return [ - [['file'], 'file'], + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], ]; } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } } ``` +上記のコードにおいては、`imageFile` 属性がアップロードされたファイルのインスタンスを保持するのに使われます。 +この属性が関連付けられている `file` 検証規則は、[[yii\validators\FileValidator]] を使って、`png` または `jpg` の拡張子を持つファイルがアップロードされることを保証しています。 +`upload()` メソッドは検証を実行して、アップロードされたファイルをサーバに保存します。 + +`file` バリデータによって、ファイル拡張子、サイズ、MIME タイプなどをチェックすることが出来ます。 +詳細については、[コアバリデータ](tutorial-core-validators.md#file) の節を参照してください。 + +> Tip: 画像をアップロードしようとする場合は、`image` バリデータを代りに使うことを考慮しても構いません。 +`image` バリデータは [[yii\validators\ImageValidator]] によって実装されており、属性が有効な画像、すなわち、保存したり [Imagine エクステンション](https://github.com/yiisoft/yii2-imagine) を使って処理したりすることが可能な有効な画像を、受け取ったかどうかを検証します。 + + 上記のコードにおいて作成した `UploadForm` というモデルは、HTML フォームで `` となる `$file` という属性を持ちます。 この属性は [[yii\validators\FileValidator|FileValidator]] を使用する `file` という検証規則を持ちます。 -### フォームのビュー +## ファイルインプットをレンダリングする -次に、フォームを表示するビューを作成します。 +次に、ビューでファイルインプットを作成します。 ```php ['enctype' => 'multipart/form-data']]) ?> -field($model, 'file')->fileInput() ?> + field($model, 'imageFile')->fileInput() ?> - + ``` -ファイルのアップロードを可能にする `'enctype' => 'multipart/form-data'` は不可欠です。 -`fileInput()` がフォームの入力フィールドを表します。 +ファイルが正しくアップロードされるように、フォームに `enctype` オプションを追加することを憶えておくのは重要なことです。 +`fileInput()` を呼ぶと `` のタグがレンダリングされて、ユーザがアップロードするファイルを選ぶことが出来るようになります。 -### コントローラ +> Tip: バージョン 2.0.8 以降では、ファイルインプットのフィールドが使われているときは、[[yii\web\widgets\ActiveField::fileInput|fileInput]] がフォームに `enctype` オプションを自動的に追加します。 -そして、フォームとモデルを結び付けるコントローラを作成します。 + +## 繋ぎ合せる + +そして、コントローラアクションの中で、モデルとビューを繋ぎ合せるコードを書いて、ファイルのアップロードを実装します。 ```php namespace app\controllers; @@ -82,10 +101,10 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { - $model->file = UploadedFile::getInstance($model, 'file'); - - if ($model->file && $model->validate()) { - $model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // ファイルのアップロードが成功 + return; } } @@ -94,6 +113,7 @@ class SiteController extends Controller } ``` +<<<<<<< HEAD `model->load(...)` の代りに `UploadedFile::getInstance(...)` を使っています。 [[\yii\web\UploadedFile|UploadedFile]] はモデルの検証を実行せず、アップロードされたファイルに関する情報を提供するだけです。 そのため、`$model->validate()` を手作業で実行して、[[yii\validators\FileValidator|FileValidator]] を起動する必要があります。 @@ -139,27 +159,23 @@ public function rules() ``` ### MIME タイプ +======= +上記のコードでは、フォームが送信されると [[yii\web\UploadedFile::getInstance()]] メソッドが呼ばれて、アップロードされたファイルが `UploadedFile` のインスタンスとして表現されます。 +そして、次に、モデルの検証によってアップロードされたファイルが有効なものであることを確かめ、サーバにファイルを保存します。 +>>>>>>> master -アップロードされるファイルのタイプを検証することは賢明なことです。 -`FileValidator` はこの目的のための `extensions` プロパティを持っています。 - -```php -public function rules() -{ - return [ - [['file'], 'file', 'extensions' => 'gif, jpg'], - ]; -} -``` - -デフォルトでは、ファイルのコンテントの MIME タイプが指定された拡張子に対応するものであるかどうかが検証されます。 -例えば、`gif` に対しては `image/gif`、`jpg` に対しては `image/jpeg` であるかどうかが検証されます。 - -MIME タイプの中には、`file` バリデータによって使われている PHP fileinfo 拡張では適切に検知することが出来ないものがあることに注意してください。 -例えば、`csv` ファイルは `text/csv` ではなく `text/plain` として検知されます。 -このような振る舞いを避けるために、`checkExtensionByMimeType` を `false` に設定して、MIME タイプを手動で指定することが出来ます。 + +## 複数のファイルをアップロードする + +ここまでの項で示したコードに若干の修正を加えれば、複数のファイルを一度にアップロードすることも出来ます。 + +最初に、モデルクラスを修正して、`file` 検証規則に `maxFiles` オプションを追加して、アップロードを許可されるファイルの最大数を制限しなければなりません。 +`maxFiles` を `0` に設定することは、同時にアップロード出来るファイル数に制限がないことを意味します。 +同時にアップロードすることを許されるファイルの数は、また、PHP のディレクティブ [`max_file_uploads`](http://php.net/manual/ja/ini.core.php#ini.max-file-uploads) (デフォルト値は 20) によっても制限されます。 +`upload()` メソッドも、アップロードされた複数のファイルを一つずつ保存するように修正しなければなりません。 ```php +<<<<<<< HEAD public function rules() { return [ @@ -182,54 +198,58 @@ public function rules() 複数のファイルをアップロードする -------------------------------- +======= +namespace app\models; +>>>>>>> master -複数のファイルを一度にアップロードする必要がある場合は、少し修正が必要になります。 - -モデル: +use yii\base\Model; +use yii\web\UploadedFile; -```php class UploadForm extends Model { /** - * @var UploadedFile|Null ファイル属性 + * @var UploadedFile[] */ - public $file; + public $imageFiles; - /** - * @return array 検証規則 - */ public function rules() { return [ - [['file'], 'file', 'maxFiles' => 10], // <--- ここ ! + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], ]; } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } } ``` -ビュー: +ビューファイルでは、`fileInput()` の呼び出しに `multiple` オプションを追加して、ファイルアップロードのフィールドが複数のファイルを受け取ることが出来るようにしなければなりません。 ```php ['enctype' => 'multipart/form-data']]); ?> -field($model, 'file[]')->fileInput(['multiple' => true]) ?> + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> - + ``` -違いがあるのは、次の行です。 - -```php -field($model, 'file[]')->fileInput(['multiple' => true]) ?> -``` - -コントローラ: +そして、最後に、コントローラアクションの中では、`UploadedFile::getInstance()` の代りに `UploadedFile::getInstances()` を呼んで、`UploadedFile` インスタンスの配列を `UploadForm::imageFiles` に代入しなければなりません。 ```php namespace app\controllers; @@ -246,12 +266,10 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { - $model->file = UploadedFile::getInstances($model, 'file'); - - if ($model->file && $model->validate()) { - foreach ($model->file as $file) { - $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); - } + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // ファイルのアップロードが成功 + return; } } @@ -259,8 +277,3 @@ class SiteController extends Controller } } ``` - -単一のファイルのアップロードとは、二つの点で異なります。 -最初の違いは、`UploadedFile::getInstance($model, 'file');` の代りに `UploadedFile::getInstances($model, 'file');` が使用されることです。 -前者が一つのインスタンスを返すだけなのに対して、後者はアップロードされた **全ての** ファイルのインスタンスを返します。 -第二の違いは、`foreach` によって、全てのファイルをそれぞれ保存している点です。 diff --git a/docs/guide-ja/input-forms.md b/docs/guide-ja/input-forms.md index 68e148da0d..d4ab03cfad 100644 --- a/docs/guide-ja/input-forms.md +++ b/docs/guide-ja/input-forms.md @@ -50,14 +50,14 @@ $form = ActiveForm::begin([ ``` -上記のコードでは、[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] がフォームのインスタンスを作成するだけでなく、フォームの開始をマークしています。 +上記のコードでは、[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] がフォームのインスタンスを作成するとともに、フォームの開始をマークしています。 [[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] と [[yii\widgets\ActiveForm::end()|ActiveForm::end()]] の間に置かれた全てのコンテントが HTML の `
` タグによって囲まれます。 どのウィジェットでも同じですが、ウィジェットをどのように構成すべきかに関するオプションを指定するために、`begin` メソッドに配列を渡すことが出来ます。 この例では、追加の CSS クラスと要素を特定するための ID が渡されて、`` の開始タグに適用されています。 -利用できるオプションはすべて [[yii\widgets\ActiveForm]] の API ドキュメントに記されていますので参照してください。 +利用できるオプションの全ては [[yii\widgets\ActiveForm]] の API ドキュメントに記されていますので参照してください。 フォームの中では、フォームの要素を作成するために、ActiveForm ウィジェットの [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドが呼ばれています。 -これは、フォームの要素だけでなく、そのラベルも作成し、適用できる JavaScript の検証メソッドがあれば、それも追加します。 +このメソッドは、フォームの要素だけでなく、そのラベルも作成し、適用できる JavaScript の検証メソッドがあれば、それも追加します。 [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドは、[[yii\widgets\ActiveField]] のインスタンスを返します。 このメソッドの呼び出し結果を直接にエコーすると、結果は通常の (text の) インプットになります。 このメソッドの呼び出しに追加の [[yii\widgets\ActiveField|ActiveField]] のメソッドをチェーンして、出力結果をカスタマイズすることが出来ます。 @@ -76,6 +76,10 @@ $form = ActiveForm::begin([ 例えば、上記の例における `username` 属性のインプットフィールドの名前は `LoginForm[username]` となります。 この命名規則の結果として、ログインフォームの全ての属性が配列として、サーバ側においては `$_POST['LoginForm']` に格納されて利用できることになります。 +> Tip: 一つのフォームに一つのモデルだけがある場合、インプットの名前を単純化したいときは、 +> モデルの [[yii\base\Model::formName()|formName()]] メソッドをオーバーライドして空文字列を返すようにして、配列の部分をスキップすることが出来ます。 +> この方法を使えば、[GridView](output-data-widgets.md#grid-view) で使われるフィルターモデルで、もっと見栄えの良い URL を生成させることが出来ます。 + モデルの属性を指定するために、もっと洗練された方法を使うことも出来ます。 例えば、複数のファイルをアップロードしたり、複数の項目を選択したりする場合に、属性の名前に `[]` を付けて、属性が配列の値を取り得ることを指定することが出来ます。 @@ -87,28 +91,107 @@ echo $form->field($model, 'uploadFile[]')->fileInput(['multiple'=>'multiple']); echo $form->field($model, 'items[]')->checkboxList(['a' => 'Item A', 'b' => 'Item B', 'c' => 'Item C']); ``` +送信ボタンなどのフォーム要素に名前をつけるときには注意が必要です。 +[jQuery ドキュメント](https://api.jquery.com/submit/) によれば、衝突を生じさせ得る予約された名前がいくつかあります。 + +> フォームおよびフォームの子要素は、フォームのプロパティと衝突するインプット名や id、たとえば `submit`、`length`、`method` などを使ってはなりません。 +> 名前の衝突は訳の分らない失敗を生じさせることがあります。 +> 命名規則の完全なリストを知り、この問題についてあなたのマークアップをチェックするためには、[DOMLint](http://kangax.github.io/domlint/) を参照してください。 + フォームに HTML タグを追加するためには、素の HTML を使うか、または、上記の例の [[yii\helpers\Html::submitButton()|Html::submitButton()]] のように、[[yii\helpers\Html|Html]] ヘルパクラスのメソッドを使うことが出来ます。 -> Tip|ヒント: あなたのアプリケーションで Twitter Bootstrap CSS を使っている場合は、[[yii\widgets\ActiveForm]] の代りに [[yii\bootstrap\ActiveForm]] を使うのが良いでしょう。 -> これは ActiveForm クラスの拡張であり、bootstrap CSS フレームワークで使用するための追加のスタイルをサポートしています。 +> Tip: あなたのアプリケーションで Twitter Bootstrap CSS を使っている場合は、[[yii\widgets\ActiveForm]] の代りに [[yii\bootstrap\ActiveForm]] を使うのが良いでしょう。 +> 後者は前者の拡張であり、bootstrap CSS フレームワークで使用するための追加のスタイルをサポートしています。 -> tip|ヒント: 必須フィールドをアスタリスク付きのスタイルにするために、次の CSS を使うことが出来ます。 +> Tip: 必須フィールドをアスタリスク付きのスタイルにするために、次の CSS を使うことが出来ます。 > ->```css ->div.required label:after { -> content: " *"; -> color: red; ->} ->``` +> ```css +> div.required label.control-label:after { +> content: " *"; +> color: red; +> } +> ``` + +ドロップダウンリストを作る +-------------------------- + +ActiveForm の [dropDownList()](http://www.yiiframework.com/doc-2.0/yii-widgets-activefield.html#dropDownList()-detail) +メソッドを使ってドロップダウンリストを作ることが出来ます。 + +```php +use app\models\ProductCategory; + +/* @var $this yii\web\View */ +/* @var $form yii\widgets\ActiveForm */ +/* @var $model app\models\Product */ + +echo $form->field($model, 'product_category')->dropdownList( + ProductCategory::find()->select(['category_name', 'id'])->indexBy('id')->column(), + ['prompt'=>'カテゴリを選択してください'] +); +``` + +モデルのフィールドの値は、前もって自動的に選択されます。 + + +Pjax を使う +----------- + +[[yii\widgets\Pjax|Pjax]] ウィジェットを使うと、ページ全体をリロードせずに、ページの一部分だけを更新することが出来ます。 +これを使うと、送信後にフォームだけを更新して、その中身を入れ替えることが出来ます。 + +[[yii\widgets\Pjax::$formSelector|$formSelector]] を構成すると、どのフォームの送信が pjax を起動するかを指定することが出来ます。 +それが指定されていない場合は、Pjax に囲まれたコンテントの中にあって `data-pjax` 属性を持つすべてのフォームが pjax リクエストを起動することになります。 + +```php +use yii\widgets\Pjax; +use yii\widgets\ActiveForm; + +Pjax::begin([ + // Pjax のオプション +]); + $form = ActiveForm::begin([ + 'options' => ['data' => ['pjax' => true]], + // ActiveForm の追加のオプション + ]); + + // ActiveForm のコンテント + + ActiveForm::end(); +Pjax::end(); +``` +> Tip: [[yii\widgets\Pjax|Pjax]] ウィジェット内部のリンクに注意してください。 +> と言うのは、リンクに対するレスポンスもウィジェット内部でレンダリングされるからです。 +> これを防ぐためには、`data-pjax="0"` という HTML 属性を使用します。 + +#### 送信ボタンの値とファイルのアップロード + +`jQuery.serializeArray()` については、 +[[https://github.com/jquery/jquery/issues/2321|ファイル]] および +[[https://github.com/jquery/jquery/issues/2321|送信ボタンの値]] +を扱うときに問題があることが知られています。 +これは解決される見込みがなく、HTML5 で導入された `FormData` クラスの使用に乗り換えるべく、廃止予定となっています。 + +このことは、すなわち、ajax または [[yii\widgets\Pjax|Pjax]] ウィジェットを使う場合、ファイルと送信ボタンの値に対する公式なサポートは、ひとえに +`FormData` クラスに対する [[https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility|ブラウザのサポート]] に依存するということを意味します。 + + +さらに読むべき文書 +------------------ 次の節 [入力を検証する](input-validation.md) は、送信されたフォームデータのサーバ側でのバリデーションと、ajax バリデーションおよびクライアント側バリデーションを扱います。 フォームのもっと複雑な使用方法については、以下の節を読んで下さい。 - [表形式インプットのデータ収集](input-tabular-input.md) - 同じ種類の複数のモデルのデータを収集する。 +<<<<<<< HEAD - [複数のモデルを持つ複雑なフォーム](input-multiple-models.md) - 同じフォームの中で複数の異なるモデルを扱う。 <<<<<<< HEAD - [ファイルをアップロードする](input-file-upload) - フォームを使ってファイルをアップロードする方法。 ======= - [ファイルをアップロードする](input-file-upload.md) - フォームを使ってファイルをアップロードする方法。 >>>>>>> yiichina/master +======= +- [複数のモデルのデータを取得する](input-multiple-models.md) - 同じフォームの中で複数の異なるモデルを扱う。 +- [ファイルをアップロードする](input-file-upload.md) - フォームを使ってファイルをアップロードする方法。 +>>>>>>> master diff --git a/docs/guide-ja/input-multiple-models.md b/docs/guide-ja/input-multiple-models.md index 992a9faadc..68ef5ee583 100644 --- a/docs/guide-ja/input-multiple-models.md +++ b/docs/guide-ja/input-multiple-models.md @@ -1,33 +1,78 @@ -複数のモデルを扱う複雑なフォーム -================================ +複数のモデルのデータを取得する +============================== -複雑なユーザインタフェイスにおいては、一つのフォームにユーザが入力したデータをデータベースの異なる複数のテーブルに保存しなければならないということが生じ得ます。 -Yii のフォームの概念に従うと、単一モデルのフォームと比べても、ほとんど複雑さを加えることなく、そういうフォームを構築することが出来ます。 +複雑なデータを扱う場合には、複数の異なるモデルを使用してユーザの入力を収集する必要があることがあり得ます。 +例えば、ユーザのログイン情報は `user` テーブルに保存されているけれども、ユーザのプロファイル情報は `profile` テーブルに保存されているという場合を考えて見ると、ユーザに関して入力されたデータを `User` モデルと `Profile` モデルによって収集しなければならないでしょう。 +Yii のモデルとフォームのサポートを使えば、単一のモデルを扱うのとそれほど違いのない方法によってこの問題を解決することが出来ます。 -単一モデルの場合と同じように、サーバ側では次のような検証のスキーマに従います。 +下記において、`User` と `Profile` の二つのモデルのデータを収集することが出来るフォームをどのようにして作成することが出来るかを示します。 -1. モデルのクラスをインスタンス化する。 -2. モデルの属性に入力されたデータを投入する。 -3. 全てのモデルを検証する。 -4. 全てのモデルに対して検証が通った場合は、それらを保存する。 -5. 検証が失敗した場合、または、データが送信されなかった場合は、全てのモデルのインスタンスをビューに渡してフォームを表示する。 +最初に、ユーザとプロファイルのデータを収集するためのコントローラアクションは、次のように書くことが出来ます。 -次に、一つのフォームで複数のモデルを使用する例を示します ... (未定) +```php +namespace app\controllers; -複数のモデルの例 ----------------- +use Yii; +use yii\base\Model; +use yii\web\Controller; +use yii\web\NotFoundHttpException; +use app\models\User; +use app\models\Profile; -> Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 +class UserController extends Controller +{ + public function actionUpdate($id) + { + $user = User::findOne($id); + $profile = Profile::findOne($id); + + if (!isset($user, $profile)) { + throw new NotFoundHttpException("ユーザが見つかりませんでした。"); + } + + $user->scenario = 'update'; + $profile->scenario = 'update'; + + if ($user->load(Yii::$app->request->post()) && $profile->load(Yii::$app->request->post())) { + $isValid = $user->validate(); + $isValid = $profile->validate() && $isValid; + if ($isValid) { + $user->save(false); + $profile->save(false); + return $this->redirect(['user/view', 'id' => $id]); + } + } + + return $this->render('update', [ + 'user' => $user, + 'profile' => $profile, + ]); + } +} +``` -(未定) +この `update` アクションでは、最初に、更新の対象になる `$user` と `$profile` のモデルをデータベースからロードします。 +次に [[yii\base\Model::load()]] を呼んで、これら二つのモデルにユーザ入力を代入します。 +代入が成功すれば、二つのモデルを検証して保存します。 +そうでない場合は、次の内容を持つ `update` ビューをレンダリングします。 -依存するモデル --------------- +```php + Note|注意: この節はまだ執筆中です。 -> -> まだ内容がありません。 +$form = ActiveForm::begin([ + 'id' => 'user-update-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($user, 'username') ?> -(未定) + ...other input fields... + + field($profile, 'website') ?> + + 'btn btn-primary']) ?> + +``` + +ご覧のように、`update` ビューでは、二つのモデル、すなわち `$user` と `$profile` を使ってインプットフィールドをレンダリングすることになります。 diff --git a/docs/guide-ja/input-tabular-input.md b/docs/guide-ja/input-tabular-input.md index 20e5506972..8835c882bf 100644 --- a/docs/guide-ja/input-tabular-input.md +++ b/docs/guide-ja/input-tabular-input.md @@ -103,7 +103,7 @@ public function actionCreate() ### 更新、作成、削除を一つのページに組み合わせる -> Note|注意: この節はまだ執筆中です。 +> Note: この節はまだ執筆中です。 > > まだ内容がありません。 diff --git a/docs/guide-ja/input-validation.md b/docs/guide-ja/input-validation.md index 3edd311e76..85331a06aa 100644 --- a/docs/guide-ja/input-validation.md +++ b/docs/guide-ja/input-validation.md @@ -10,6 +10,7 @@ ```php <<<<<<< HEAD +<<<<<<< HEAD $model = new \app\models\ContactForm; // モデルの属性にユーザ入力を投入する @@ -22,6 +23,14 @@ $model->load(\Yii::$app->request->post()); // これは次と等価 // $model->attributes = \Yii::$app->request->post('ContactForm'); >>>>>>> yiichina/master +======= +$model = new \app\models\ContactForm(); + +// モデルの属性にユーザ入力を投入する +$model->load(\Yii::$app->request->post()); +// これは次と等価 +// $model->attributes = \Yii::$app->request->post('ContactForm'); +>>>>>>> master if ($model->validate()) { // 全ての入力が有効 @@ -97,6 +106,27 @@ public function rules() 属性は、上記の検証のステップに従って、`scenarios()` でアクティブな属性であると宣言されており、かつ、`rules()` で宣言された一つまたは複数のアクティブな規則と関連付けられている場合に、また、その場合に限って、検証されます。 +> Note: 規則に名前を付けると便利です。すなわち、 +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> これを子のモデルで使うことが出来ます。 +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + ### エラーメッセージをカスタマイズする @@ -136,11 +166,9 @@ public function rules() 例えば、 ```php -[ ['state', 'required', 'when' => function($model) { return $model->country == 'USA'; }], -] ``` [[yii\validators\Validator::when|when]] プロパティは、次のシグニチャを持つ PHP コーラブルを値として取ります。 @@ -159,13 +187,11 @@ function ($model, $attribute) 例えば、 ```php -[ ['state', 'required', 'when' => function ($model) { return $model->country == 'USA'; }, 'whenClient' => "function (attribute, value) { return $('#country').val() == 'USA'; - }"], -] + }"] ``` @@ -178,10 +204,10 @@ function ($model, $attribute) 次の例では、入力値の前後にある空白を除去して、空の入力値を null に変換することを、[trim](tutorial-core-validators.md#trim) および [default](tutorial-core-validators.md#default) のコアバリデータで行っています。 ```php -[ +return [ [['username', 'email'], 'trim'], [['username', 'email'], 'default'], -] +]; ``` もっと汎用的な [filter](tutorial-core-validators.md#filter) バリデータを使って、もっと複雑なデータフィルタリングをすることも出来ます。 @@ -196,13 +222,13 @@ HTML フォームから入力データが送信されたとき、入力値が空 例えば、 ```php -[ +return [ // 空の時は "username" と "email" を null にする [['username', 'email'], 'default'], // 空の時は "level" を 1 にする ['level', 'default', 'value' => 1], -] +]; ``` デフォルトでは、入力値が空であると見なされるのは、それが、空文字列であるか、空配列であるか、null であるときです。 @@ -210,14 +236,14 @@ HTML フォームから入力データが送信されたとき、入力値が空 例えば、 ```php -[ +return [ ['agree', 'required', 'isEmpty' => function ($value) { return empty($value); }], -] +]; ``` -> Note|注意: たいていのバリデータは、[[yii\base\Validator::skipOnEmpty]] プロパティがデフォルト値 `true` を取っている場合は、空の入力値を処理しません。 +> Note: たいていのバリデータは、[[yii\validators\Validator::skipOnEmpty]] プロパティがデフォルト値 `true` を取っている場合は、空の入力値を処理しません。 そのようなバリデータは、関連付けられた属性が空の入力値を受け取ったときは、検証の過程ではスキップされるだけになります。 [コアバリデータ](tutorial-core-validators.md) の中では、`captcha`、`default`、`filter`、`required`、そして `trim` だけが空の入力値を処理します。 @@ -239,7 +265,7 @@ if ($validator->validate($email, $error)) { } ``` -> Note|注意: 全てのバリデータがこの種の検証をサポートしている訳ではありません。 +> Note: 全てのバリデータがこの種の検証をサポートしている訳ではありません。 その一例が [unique](tutorial-core-validators.md#unique) コアバリデータであり、これはモデルとともに使用されることだけを前提にして設計されています。 いくつかの値に対して複数の検証を実行する必要がある場合は、属性と規則の両方をその場で宣言することが出来る [[yii\base\DynamicModel]] を使うことが出来ます。 @@ -340,7 +366,7 @@ class MyForm extends Model } ``` -> Note|注意: デフォルトでは、インラインバリデータは、関連付けられている属性が空の入力値を受け取ったり、既に何らかの検証規則に失敗したりしている場合には、適用されません。 +> Note: デフォルトでは、インラインバリデータは、関連付けられている属性が空の入力値を受け取ったり、既に何らかの検証規則に失敗したりしている場合には、適用されません。 > 規則が常に適用されることを保証したい場合は、規則の宣言において [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] および/または [[yii\validators\Validator::skipOnError|skipOnError]] のプロパティを false に設定することが出来ます。 > 例えば、 > @@ -356,7 +382,8 @@ class MyForm extends Model スタンドアロンバリデータは、[[yii\validators\Validator]] またはその子クラスを拡張するクラスです。 [[yii\validators\Validator::validateAttribute()]] メソッドをオーバーライドすることによって、その検証ロジックを実装することが出来ます。 [インラインバリデータ](#inline-validators) でするのと同じように、属性が検証に失敗した場合は、[[yii\base\Model::addError()]] を呼んでエラーメッセージをモデルに保存します。 -例えば、 + +例えば、上記のインラインバリデータは、新しい [[components/validators/CountryValidator]] クラスに作りかえることが出来ます。 ```php namespace app\components; @@ -380,13 +407,39 @@ class CountryValidator extends Validator と言うのは、前の二つは、デフォルトでは、`validateValue()` を呼び出すことによって実装されているからです。 +次の例は、上記のバリデータクラスをあなたのモデルの中でどのように使用することが出来るかを示すものです。 + +```php +namespace app\models; + +use Yii; +use yii\base\Model; +use app\components\validators\CountryValidator; + +class EntryForm extends Model +{ + public $name; + public $email; + public $country; + + public function rules() + { + return [ + [['name', 'email'], 'required'], + ['country', CountryValidator::className()], + ['email', 'email'], + ]; + } +} +``` + ## クライアント側での検証 エンドユーザが HTML フォームで値を入力する際には、JavaScript に基づくクライアント側での検証を提供することが望まれます。 というのは、クライアント側での検証は、ユーザが入力のエラーを早く見つけることが出来るようにすることによって、より良いユーザ体験を提供するものだからです。 あなたも、サーバ側での検証 *に加えて* クライアント側での検証をサポートするバリデータを使用したり実装したりすることが出来ます。 -> Info|情報: クライアント側での検証は望ましいものですが、不可欠なものではありません。 +> Info: クライアント側での検証は望ましいものですが、不可欠なものではありません。 その主たる目的は、ユーザにより良い体験を提供することにあります。 エンドユーザから来る入力値と同じように、クライアント側での検証を決して信用してはいけません。 この理由により、これまでの項で説明したように、常に [[yii\base\Model::validate()]] を呼び出してサーバ側での検証を実行しなければなりません。 @@ -450,9 +503,13 @@ class LoginForm extends Model クライアント側の検証を完全に無効にしたい場合は、[[yii\widgets\ActiveForm::enableClientValidation]] プロパティを false に設定することが出来ます。 また、個々の入力フィールドごとにクライアント側の検証を無効にしたい場合には、入力フィールドの [[yii\widgets\ActiveField::enableClientValidation]] プロパティを false に設定することが出来ます。 <<<<<<< HEAD +<<<<<<< HEAD ======= `eanbleClientValidation` が入力フィールドのレベルとフォームのレベルの両方で構成されている場合は前者が優先されます。 >>>>>>> yiichina/master +======= +`eanbleClientValidation` が入力フィールドのレベルとフォームのレベルの両方で構成されている場合は前者が優先されます。 +>>>>>>> master ### クライアント側の検証を実装する @@ -495,7 +552,7 @@ class StatusValidator extends Validator $statuses = json_encode(Status::find()->select('id')->asArray()->column()); $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return << Tip|ヒント: 上記のコード例の主たる目的は、クライアント側の検証をサポートする方法を説明することにあります。 +> Tip: 上記のコード例の主たる目的は、クライアント側の検証をサポートする方法を説明することにあります。 > 実際の仕事では、[in](tutorial-core-validators.md#in) コアバリデータを使って、同じ目的を達することが出来ます。 > 次のように検証規則を書けばよいのです。 > @@ -513,6 +570,9 @@ JS; > ] > ``` +> Tip: クライアント側の検証を手動で操作する必要がある場合、すなわち、動的にフィールドを追加したり、何か特殊な UI ロジックを実装する場合は、 +> Yii 2.0 Cookbook の [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) を参照してください。 + ### Deferred 検証 非同期のクライアント側の検証をサポートする必要がある場合は、[Defered オブジェクト](http://api.jquery.com/category/deferred-object/) を作成することが出来ます。 @@ -560,7 +620,7 @@ JS; } ``` -> Note|注意: 属性が検証された後に、`resolve()` メソッドを呼び出さなければなりません。 +> Note: 属性が検証された後に、`resolve()` メソッドを呼び出さなければなりません。 そうしないと、主たるフォームの検証が完了しません。 簡潔に記述できるように、`deferred` 配列はショートカットメソッド `add()` を装備しており、このメソッドを使うと、自動的に Deferred オブジェクトを作成して `deferred` 配列に追加することが出来ます。 @@ -596,16 +656,37 @@ JS; このような場合には、AJAX ベースの検証を使うことが出来ます。 AJAX 検証は、通常のクライアント側での検証と同じユーザ体験を保ちながら、入力値を検証するためにバックグラウンドで AJAX リクエストを発行します。 +<<<<<<< HEAD <<<<<<< HEAD AJAX 検証をフォーム全体に対して有効にするためには、[[yii\widgets\ActiveForm::enableAjaxValidation]] プロパティを `true` に設定して、`id` にフォームを特定するユニークな ID を設定しなければなりません。 +======= +単一のインプットフィールドに対して AJAX 検証を有効にするためには、そのフィールドの [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] プロパティを true に設定し、フォームに一意の `id` を指定します。 +>>>>>>> master ```php - 'contact-form', - 'enableAjaxValidation' => true, -]); ?> +use yii\widgets\ActiveForm; + +$form = ActiveForm::begin([ + 'id' => 'registration-form', +]); + +echo $form->field($model, 'username', ['enableAjaxValidation' => true]); + +// ... + +ActiveForm::end(); ``` +フォーム全体に対して AJAX 検証を有効にするためには、フォームのレベルで [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] を true に設定します。 + +```php +$form = ActiveForm::begin([ + 'id' => 'contact-form', + 'enableAjaxValidation' => true, +]); +``` + +<<<<<<< HEAD 個別の入力フィールドについても、[[yii\widgets\ActiveField::enableAjaxValidation]] プロパティを設定して、AJAX 検証を有効にしたり無効にしたりすることが出来ます。 ======= 単一のインプットフィールドに対して AJAX 検証を有効にするためには、そのフィールドの [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] プロパティを true に設定し、フォームに一意の `id` を指定します。 @@ -635,6 +716,9 @@ $form = ActiveForm::begin([ > Note|注意: `enableAjaxValidation` プロパティがインプットフィールドのレベルとフォームのレベルの両方で構成された場合は、前者が優先されます。 >>>>>>> yiichina/master +======= +> Note: `enableAjaxValidation` プロパティがインプットフィールドのレベルとフォームのレベルの両方で構成された場合は、前者が優先されます。 +>>>>>>> master また、サーバ側では、AJAX 検証のリクエストを処理できるように準備しておく必要があります。 これは、コントローラのアクションにおいて、次のようなコード断片を使用することで達成できます。 @@ -649,5 +733,7 @@ if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { 上記のコードは、現在のリクエストが AJAX であるかどうかをチェックします。 もし AJAX であるなら、リクエストに応えて検証を実行し、エラーを JSON 形式で返します。 -> Info|情報: AJAX 検証を実行するためには、[Deferred 検証](#deferred-validation) を使うことも出来ます。 +> Info: AJAX 検証を実行するためには、[Deferred 検証](#deferred-validation) を使うことも出来ます。 しかし、ここで説明された AJAX 検証の機能の方がより体系化されており、コーディングの労力も少なくて済みます。 + +`enableClientValidation` と `enableAjaxValidation` が両方とも真に設定されているときは、クライアント検証が成功した後でだけ AJAX 検証が起動されます。 diff --git a/docs/guide-ja/intro-upgrade-from-v1.md b/docs/guide-ja/intro-upgrade-from-v1.md index 38d4c96266..4478e7b1a0 100644 --- a/docs/guide-ja/intro-upgrade-from-v1.md +++ b/docs/guide-ja/intro-upgrade-from-v1.md @@ -102,11 +102,15 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` +<<<<<<< HEAD <<<<<<< HEAD 構成情報に関する詳細は、[オブジェクトの構成情報](concept-configurations.md) の節で見ることが出来ます。 ======= 構成情報に関する詳細は、[構成情報](concept-configurations.md) の節で見ることが出来ます。 >>>>>>> yiichina/master +======= +構成情報に関する詳細は、[構成情報](concept-configurations.md) の節で見ることが出来ます。 +>>>>>>> master イベント @@ -179,7 +183,7 @@ Prado テンプレートエンジンはもはやサポートされていませ Yii 2.0 は [[yii\base\Model]] を 1.1 における `CModel` と同様な基底モデルとして使います。 `CFormModel` というクラスは完全に廃止されました。 -Yii 2 では、それの代りに [yii\base\Model]] を拡張して、フォームのモデルクラスを作成すべきです。 +Yii 2 では、それの代りに [[yii\base\Model]] を拡張して、フォームのモデルクラスを作成すべきです。 Yii 2.0 は サポートされるシナリオを宣言するための [[yii\base\Model::scenarios()|scenarios()]] という新しいメソッドを導入しました。 このメソッドを使って、どのシナリオの下で、ある属性が検証される必要があるか、また、安全とみなされるか否か、などを宣言することが出来ます。 @@ -508,6 +512,7 @@ User と IdentityInterface 1.1 の `CWebUser` クラスは [[yii\web\User]] に取って換られました。 そして `CUserIdentity` クラスはもうありません。代りに、使い方がもっと単純な [[yii\web\IdentityInterface]] を実装すべきです。 <<<<<<< HEAD +<<<<<<< HEAD アドバンストアプリケーションテンプレートがそういう例を提供しています。 詳細は [認証](security-authentication.md)、[権限付与](security-authorization.md)、そして [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) の節を参照してください。 @@ -516,6 +521,11 @@ User と IdentityInterface 詳細は [認証](security-authentication.md)、[権限付与](security-authorization.md)、そして [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) の節を参照してください。 >>>>>>> yiichina/master +======= +アドバンストプロジェクトテンプレートがそういう例を提供しています。 + +詳細は [認証](security-authentication.md)、[権限付与](security-authorization.md)、そして [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) の節を参照してください。 +>>>>>>> master URL 管理 @@ -536,6 +546,10 @@ Yii 2 の URL 管理は 1.1 のそれと似たようなものです。 詳細については [ルーティングと URL 生成](runtime-routing.md) の節を参照してください。 +ルートの命名規約における重要な変更は、コントローラとアクションのキャメルケースの名前が各単語をハイフンで分けた小文字の名前になるようになった、という点です。 +例えば、`CamelCaseController` のコントローラ ID は `camel-case` となります。 +詳細については、[コントローラ ID](structure-controllers.md#controller-ids) と [アクション ID](structure-controllers.md#action-ids) の節を参照してください。 + Yii 1.1 と 2.x を一緒に使う --------------------------- diff --git a/docs/guide-ja/intro-yii.md b/docs/guide-ja/intro-yii.md index d3b36da2bf..cdcedddeb7 100644 --- a/docs/guide-ja/intro-yii.md +++ b/docs/guide-ja/intro-yii.md @@ -19,7 +19,7 @@ Yii を他のフレームワークと比べると あなたが既に他のフレームワークに親しんでいる場合は、Yii を比較するとどうなのかを知りたいでしょう。 -- ほとんどの PHP フレームワーク同様、Yii は MVC (Model-View-Controller) デザインパターンを実装し、このパターンに基づいたコードの編成を推進しています。 +- ほとんどの PHP フレームワーク同様、Yii は MVC (Model-View-Controller) アーキテクチャパターンを実装し、このパターンに基づいたコードの編成を推進しています。 - Yii は、コードはシンプルかつエレガントに書かれるべきである、という哲学を採用しています。 何らかのデザインパターンの厳密な遵守を主な目的とする凝りすぎた設計を、Yii がしようと試みることは決してありません。 - Yii はフル装備のフレームワークです。 @@ -28,6 +28,7 @@ Yii を他のフレームワークと比べると また、Yii の堅固なエクステンションアーキテクチャを利用して、再配布可能なエクステンションを使用したり開発したりすることも出来ます。 - 高性能であることは常に Yii の主たる目標です。 +<<<<<<< HEAD <<<<<<< HEAD Yii はワンマンショーではありません。Yii は [強力なコア開発チーム][] および Yii 開発に間断なく貢献してくれるプロフェッショナルの大きなコミュニティーに支えられたプロジェクトです。 Yii 開発チームは、最新のウェブ開発の潮流と、他のフレームワークやプロジェクトに見出される最善のプラクティスと機能を、注意深く見守り続けています。 @@ -41,6 +42,13 @@ Yii 開発チームは、最新のウェブ開発の潮流と、他のフレー [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +Yii はワンマンショーではありません。Yii は [強力なコア開発チーム][about_yii] および Yii 開発に間断なく貢献してくれるプロフェッショナルの大きなコミュニティーに支えられたプロジェクトです。 +Yii 開発チームは、最新のウェブ開発の潮流と、他のフレームワークやプロジェクトに見出される最善のプラクティスと機能を、注意深く見守り続けています。 +他のところで見出された最善のプラクティスと機能で最も適切なものは、定期的にコアフレームワークに組み込まれ、シンプルかつエレガントなインターフェイスを通じて公開されます。 + +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Yii のバージョン ---------------- diff --git a/docs/guide-ja/output-client-scripts.md b/docs/guide-ja/output-client-scripts.md index 80ab27e9cf..f0c71dcb3a 100644 --- a/docs/guide-ja/output-client-scripts.md +++ b/docs/guide-ja/output-client-scripts.md @@ -1,7 +1,7 @@ クライアントスクリプトを扱う ============================ -> Note|注意: この節はまだ執筆中です。 +> Note: この節はまだ執筆中です。 ### スクリプトを登録する diff --git a/docs/guide-ja/output-data-providers.md b/docs/guide-ja/output-data-providers.md index bc2374f38f..6941fbc7b5 100644 --- a/docs/guide-ja/output-data-providers.md +++ b/docs/guide-ja/output-data-providers.md @@ -1,139 +1,232 @@ データプロバイダ ================ +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: この節はまだ執筆中です。 +======= +[ページネーション](output-pagination.md) と [並べ替え](output-sorting.md) の節において、エンドユーザが特定のページのデータを選んで表示し、いずれかのカラムによってデータを並べ替えることが出来るようにする方法を説明しました。 +データのページネーションと並べ替えは非常によくあるタスクですから、Yii はこれをカプセル化した一連の *データプロバイダ* を提供しています。 +>>>>>>> master -データプロバイダは、 [[yii\data\DataProviderInterface]] によってデータセットを抽象化し、ページネーションと並べ替えを処理します。 -[グリッドやリストなどのデータウィジェット](output-data-widgets.md) で使用することが出来ます。 +データプロバイダは [[yii\data\DataProviderInterface]] を実装するクラスであり、主として、ページ分割され並べ替えられたデータの取得をサポートするものです。 +通常は、[データウィジェット](output-data-widgets.md) と共に使用して、エンドユーザが対話的にデータのページネーションと並べ替えをすることが出来るようにします。 -Yii は三つのデータプロバイダを内蔵しています。すなわち、[[yii\data\ActiveDataProvider]]、[[yii\data\ArrayDataProvider]] そして [[yii\data\SqlDataProvider]] です。 +Yii のリリースには次のデータプロバイダのクラスが含まれています。 -アクティブデータプロバイダ --------------------------- +* [[yii\data\ActiveDataProvider]]: [[yii\db\Query]] または [[yii\db\ActiveQuery]] を使ってデータベースからデータを取得して、配列または [アクティブレコード](db-active-record.md) インスタンスの形式でデータを返します。 +* [[yii\data\SqlDataProvider]]: SQL 文を実行して、データベースのデータを配列として返します。 +* [[yii\data\ArrayDataProvider]]: 大きな配列を受け取り、ページネーションと並べ替えの指定に基づいて、一部分を切り出して返します。 -`ActiveDataProvider` は [[yii\db\Query]] および [[yii\db\ActiveQuery]] を使って DB クエリを実行して、データを提供します。 - -次のコードは、これを使って、ActiveRecord のインスタンスを提供する例です。 +これら全てのデータプロバイダの使用方法は、次の共通のパターンを持っています。 ```php -$provider = new ActiveDataProvider([ - 'query' => Post::find(), - 'pagination' => [ - 'pageSize' => 20, - ], +// ページネーションと並べ替えのプロパティを構成してデータプロバイダを作成する +$provider = new XyzDataProvider([ + 'pagination' => [...], + 'sort' => [...], ]); -// 現在のページの投稿を取得する -$posts = $provider->getModels(); +// ページ分割されて並べ替えられたデータを取得する +$models = $provider->getModels(); + +// 現在のページにあるデータアイテムの数を取得する +$count = $provider->getCount(); + +// 全ページ分のデータアイテムの総数を取得する +$totalCount = $provider->getTotalCount(); ``` -そして次の例は、ActiveRecord なしで ActiveDataProvider を使う方法を示すものです。 +データプロバイダのページネーションと並べ替えの振る舞いを指定するためには、その [[yii\data\BaseDataProvider::pagination|pagination]] と [[yii\data\BaseDataProvider::sort|sort]] のプロパティを構成します。 +二つのプロパティは、それぞれ、[[yii\data\Pagination]] と [[yii\data\Sort]] の構成情報に対応します。 +これらを false に設定して、ページネーションや並べ替えの機能を無効にすることも出来ます。 + +[データウィジェット](output-data-widgets.md)、例えば [[yii\grid\GridView]] は、`dataProvider` という名前のプロパティを持っており、これにデータプロバイダのインスタンスを受け取らせて、それが提供するデータを表示させることが出来ます。 +例えば、 ```php -$query = new Query(); -$provider = new ActiveDataProvider([ - 'query' => $query->from('post'), - 'sort' => [ - // デフォルトのソートを name ASC, created_at DESC とする - 'defaultOrder' => [ - 'name' => SORT_ASC, - 'created_at' => SORT_DESC - ] - ], - 'pagination' => [ - 'pageSize' => 20, - ], +echo yii\grid\GridView::widget([ + 'dataProvider' => $dataProvider, ]); - -// 現在のページの投稿を取得する -$posts = $provider->getModels(); ``` -配列データプロバイダ --------------------- +これらのデータプロバイダの主たる相異点は、データソースがどのように指定されるかという点にあります。 +次に続く項において、各データプロバイダの詳細な使用方法を説明します。 -ArrayDataProvider はデータの配列に基づいたデータプロバイダを実装するものです。 -[[yii\data\ArrayDataProvider::$allModels]] プロパティが、並べ替えやページネーションの対象となるデータの全てのモデルを含みます。 -ArrayDataProvider は、並べ替えとページネーションを実行した後に、データを提供します。 -[[yii\data\ArrayDataProvider::$sort]] および [[yii\data\ArrayDataProvider::$pagination]] のプロパティを構成して、並べ替えとページネーションの動作をカスタマイズすることが出来ます。 +## アクティブデータプロバイダ -[[yii\data\ArrayDataProvider::$allModels]] 配列の要素は、オブジェクト (例えば、モデルのオブジェクト) であるか、連想配列 (例えば、DAO のクエリ結果) であるかの、どちらかです。 -[[yii\data\ArrayDataProvider::$key]] プロパティには、必ず、データレコードを一意的に特定出来るフィールドの名前をセットするか、そのようなフィールドがない場合は `false` をセットするかしなければなりません。 - -`ActiveDataProvider` と比較すると、`ArrayDataProvider` は、[[yii\data\ArrayDataProvider::$allModels]] を準備して持たなければならないため、効率が良くありません。 - -`ArrayDataProvider` は次のようにして使用することが出来ます。 +[[yii\data\ActiveDataProvider]] を使用するためには、その [[yii\data\ActiveDataProvider::query|query]] プロパティを構成しなければなりません。 +これは、[[yii\db\Query]] または [[yii\db\ActiveQuery]] のオブジェクトを取ることが出来ます。 +前者であれば、返されるデータは配列になります。 +後者であれば、返されるデータは配列または [アクティブレコード](db-active-record.md) インスタンスとすることが出来ます。 +例えば、 ```php -$query = new Query(); -$provider = new ArrayDataProvider([ - 'allModels' => $query->from('post')->all(), - 'sort' => [ - 'attributes' => ['id', 'username', 'email'], - ], +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['status' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, 'pagination' => [ 'pageSize' => 10, ], + 'sort' => [ + 'defaultOrder' => [ + 'created_at' => SORT_DESC, + 'title' => SORT_ASC, + ] + ], ]); -// 現在のページの投稿を取得する + +// Post オブジェクトの配列を返す $posts = $provider->getModels(); ``` -> Note|注意: 並べ替えの機能を使いたいときは、どのカラムがソート出来るかをプロバイダが知ることが出来るように、[[sort]] プロパティを構成しなければなりません。 - -SQL データプロバイダ --------------------- - -SqlDataProvider は、素の SQL 文に基づいたデータプロバイダを実装するものです。 -これは、各要素がクエリ結果の行を表す配列の形式でデータを提供します。 - -他のプロバイダ同様に、SqlDataProvider も、並べ替えとページネーションをサポートしています。 -並べ替えとページネーションは、与えられた [[yii\data\SqlDataProvider::$sql]] 文を "ORDER BY" 句および "LIMIT" 句で修正することによって実行されます。 -[[yii\data\SqlDataProvider::$sort]] および [[yii\data\SqlDataProvider::$pagination]] のプロパティを構成して、並べ替えとページネーションの動作をカスタマイズすることが出来ます。 - -`SqlDataProvider` は次のようにして使用することが出来ます。 +上記の例における `$query` が次のコードによって作成される場合は、提供されるデータは生の配列になります。 ```php +use yii\db\Query; + +$query = (new Query())->from('post')->where(['status' => 1]); +``` + +> Note: クエリが既に `orderBy` 句を指定しているものである場合、(`sort` の構成を通して) エンドユーザによって与えられる並べ替えの指定は、既存の `orderBy` 句に追加されます。 +一方、`limit` と `offset` の句が存在している場合は、(`pagenation` の構成を通して) エンドユーザによって指定されるページネーションのリクエストによって上書きされます。 + +デフォルトでは、[[yii\data\ActiveDataProvider]] はデータベース接続として `db` アプリケーションコンポーネントを使用します。 +[[yii\data\ActiveDataProvider::db]] プロパティを構成すれば、別のデータベース接続を使用することが出来ます。 + + +## SQL データプロバイダ + +[[yii\data\SqlDataProvider]] は、生の SQL 文を使用して、必要なデータを取得します。 +このデータプロバイダは、[[yii\data\SqlDataProvider::sort|sort]] と [[yii\data\SqlDataProvider::pagination|pagination]] の指定に基づいて、SQL 文の `ORDER BY` と `OFFSET/LIMIT` の句を修正し、指定された順序に並べ替えられたデータを要求されたページの分だけ取得します。 + +[[yii\data\SqlDataProvider]] を使用するためには、[[yii\data\SqlDataProvider::sql|sql]] プロパティだけでなく、[[yii\data\SqlDataProvider::totalCount|totalCount]] プロパティを指定しなければなりません。 +例えば、 + +```php +use yii\data\SqlDataProvider; + $count = Yii::$app->db->createCommand(' - SELECT COUNT(*) FROM user WHERE status=:status + SELECT COUNT(*) FROM post WHERE status=:status ', [':status' => 1])->queryScalar(); -$dataProvider = new SqlDataProvider([ - 'sql' => 'SELECT * FROM user WHERE status=:status', +$provider = new SqlDataProvider([ + 'sql' => 'SELECT * FROM post WHERE status=:status', 'params' => [':status' => 1], 'totalCount' => $count, + 'pagination' => [ + 'pageSize' => 10, + ], 'sort' => [ 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - 'default' => SORT_DESC, - 'label' => 'Name', - ], + 'title', + 'view_count', + 'created_at', ], ], - 'pagination' => [ - 'pageSize' => 20, - ], ]); -// 現在のページの user のレコードを取得する -$models = $dataProvider->getModels(); +// データ行の配列を返す +$models = $provider->getModels(); ``` -> Note|注意: ページネーションの機能を使いたい場合は、[[yii\data\SqlDataProvider::$totalCount]] プロパティに (ページネーション無しの) 総行数を設定しなければなりません。 -そして、並べ替えの機能を使いたい場合は、どのカラムがソート出来るかをプロバイダが知ることが出来るように、[[yii\data\SqlDataProvider::$sort]] プロパティを構成しなければなりません。 +> Info: [[yii\data\SqlDataProvider::totalCount|totalCount]] プロパティは、データにページネーションを適用しなければならない場合にだけ要求されます。 + これは、[[yii\data\SqlDataProvider::sql|sql]] によって指定される SQL 文は、現在要求されているページのデータだけを返すように、データプロバイダによって修正されてしまうからです。 + データプロバイダは、総ページ数を正しく計算するためには、データアイテムの総数を知る必要があります。 -あなた自身のカスタムデータプロバイダを実装する ----------------------------------------------- +## 配列データプロバイダ -Yii はあなた自身のカスタムデータプロバイダを導入することを許容しています。 -そうするためには、下記の `protected` メソッドを実装する必要があります。 +[[yii\data\ArrayDataProvider]] は、一つの大きな配列を扱う場合に最も適しています。 +このデータプロバイダによって、一つまたは複数のカラムで並べ替えた配列データの 1 ページ分を返すことが出来ます。 +[[yii\data\ArrayDataProvider]] を使用するためには、全体の大きな配列として [[yii\data\ArrayDataProvider::allModels|allModels]] プロパティを指定しなければなりません。 +この大きな配列の要素は、連想配列 (例えば [DAO](db-dao.md) のクエリ結果) またはオブジェクト (例えば [アクティブレコード](db-active-record.md) インスタンス) とすることが出来ます。 +例えば、 + +```php +use yii\data\ArrayDataProvider; + +$data = [ + ['id' => 1, 'name' => 'name 1', ...], + ['id' => 2, 'name' => 'name 2', ...], + ... + ['id' => 100, 'name' => 'name 100', ...], +]; + +$provider = new ArrayDataProvider([ + 'allModels' => $data, + 'pagination' => [ + 'pageSize' => 10, + ], + 'sort' => [ + 'attributes' => ['id', 'name'], + ], +]); + +// 現在リクエストされているページの行を返す +$rows = $provider->getModels(); +``` + +> Note: [アクティブデータプロバイダ](#active-data-provider) および [SQL データプロバイダ](#sql-data-provider) と比較すると、配列データプロバイダは効率の面では劣ります。 + 何故なら、*全ての* データをメモリにロードしなければならないからです。 + + +## データのキーを扱う + +データプロバイダによって返されたデータアイテムを使用する場合、各データアイテムを一意のキーで特定しなければならないことがよくあります。 +例えば、データアイテムが顧客情報を表す場合、顧客 ID を各顧客データのキーとして使用したいでしょう。 +データプロバイダは、[[yii\data\DataProviderInterface::getModels()]] によって返されたデータアイテムに対応するそのようなキーのリストを返すことが出来ます。 +例えば、 + +```php +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['status' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, +]); + +// Post オブジェクトの配列を返す +$posts = $provider->getModels(); + +// $post に対応するプライマリキーの値を返す +$ids = $provider->getKeys(); +``` + +上記の例では、[[yii\data\ActiveDataProvider]] に対して [[yii\db\ActiveQuery]] オブジェクトを供給していますから、キーとしてプライマリキーの値を返すのが理にかなっています。 +キーの値の計算方法を明示的に指定するために、[[yii\data\ActiveDataProvider::key]] にカラム名を設定したり、キーの値を計算するコーラブルを設定したりすることも出来ます。 +例えば、 + +```php +// "slug" カラムをキーの値として使用する +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => 'slug', +]); + +// md5(id) の結果をキーの値として使用する +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => function ($model) { + return md5($model->id); + } +]); +``` + + +## カスタムデータプロバイダを作成する + +あなた自身のカスタムデータプロバイダクラスを作成するためには、[[yii\data\DataProviderInterface]] を実装しなければなりません。 +[[yii\data\BaseDataProvider]] を拡張するのが比較的簡単な方法です。 +そうすれば、データプロバイダのコアのロジックに集中することが出来ます。 +具体的に言えば、実装する必要があるのは、主として次のメソッドです。 +<<<<<<< HEAD - `prepareModels` - 現在のページで利用できるデータモデルを準備して、それを配列として返します。 - `prepareKeys` - 現在利用できるデータモデルの配列を受け取って、それと関連付けられるキーの配列を返します。 - `prepareTotalCount` - データプロバイダにあるデータモデルの総数を示す値を返します。 @@ -362,12 +455,18 @@ $provider = new ActiveDataProvider([ - [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: 現在利用できるデータモデルの配列を受け取って、それと関連付けられるキーの配列を返します。 - [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: データプロバイダにあるデータモデルの総数を示す値を返します。 >>>>>>> yiichina/master +======= +- [[yii\data\BaseDataProvider::prepareModels()|prepareModels()]]: 現在のページで利用できるデータモデルを準備して、それを配列として返します。 +- [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: 現在利用できるデータモデルの配列を受け取って、それと関連付けられるキーの配列を返します。 +- [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: データプロバイダにあるデータモデルの総数を示す値を返します。 +>>>>>>> master 下記は、CSV ファイルを効率的に読み出すデータプロバイダのサンプルです。 ```php >>>>>> yiichina/master +======= +use yii\data\BaseDataProvider; + +class CsvDataProvider extends BaseDataProvider +{ + /** + * @var string 読み出す CSV ファイルの名前 +>>>>>>> master */ public $filename; diff --git a/docs/guide-ja/output-data-widgets.md b/docs/guide-ja/output-data-widgets.md index 2de725fbee..08183caf23 100644 --- a/docs/guide-ja/output-data-widgets.md +++ b/docs/guide-ja/output-data-widgets.md @@ -16,13 +16,17 @@ DetailView は単一のデータ [[yii\widgets\DetailView::$model|モデル]] DetailView は [[yii\widgets\DetailView::$attributes]] プロパティを使って、モデルのどの属性が表示されるべきか、また、どういうフォーマットで表示されるべきかを決定します。 <<<<<<< HEAD +<<<<<<< HEAD 利用できるフォーマットのオプションについては、[フォーマッタの節](output-formatter.md) を参照してください。 ======= 利用できるフォーマットのオプションについては、[フォーマッタの節](output-formatting.md) を参照してください。 >>>>>>> yiichina/master +======= +利用できるフォーマットのオプションについては、[フォーマッタの節](output-formatting.md) を参照してください。 +>>>>>>> master 次に DetailView の典型的な用例を示します。 - + ```php echo DetailView::widget([ 'model' => $model, @@ -74,7 +78,7 @@ use yii\helpers\HtmlPurifier; ?>

title) ?>

- + text) ?>
``` @@ -106,7 +110,7 @@ echo ListView::widget([ GridView
-------- -データグリッドすなわち GridView は Yii の最も強力なウィジェットの一つです。 +データグリッドすなわち [[yii\grid\GridView|GridView]] は Yii の最も強力なウィジェットの一つです。 これは、システムの管理セクションを素速く作らねばならない時に、この上なく便利なものです。 このウィジェットは [データプロバイダ](output-data-providers.md) からデータを受けて、テーブルの形式で、行ごとに一組の [[yii\grid\GridView::columns|カラム]] を使ってデータを表示します。 @@ -219,18 +223,22 @@ echo GridView::widget([ 'format' => ['date', 'php:Y-m-d'] ], ], -]); +]); ``` 上記において、`text` は [[\yii\i18n\Formatter::asText()]] に対応し、カラムの値が最初の引数として渡されます。 二番目のカラムの定義では、`date` が [[\yii\i18n\Formatter::asDate()]] に対応します。 カラムの値が、ここでも、最初の引数として渡され、'php:Y-m-d' が二番目の引数の値として渡されます。 +<<<<<<< HEAD <<<<<<< HEAD 利用できるフォーマッタの一覧については、[データのフォーマット](output-formatter.md) の節を参照してください。 ======= 利用できるフォーマッタの一覧については、[データのフォーマット](output-formatting.md) の節を参照してください。 >>>>>>> yiichina/master +======= +利用できるフォーマッタの一覧については、[データのフォーマット](output-formatting.md) の節を参照してください。 +>>>>>>> master データカラムを構成するためには、ショートカット形式を使うことも出来ます。 それについては、[[yii\grid\GridView::columns|columns]] の API ドキュメントで説明されています。 @@ -271,9 +279,28 @@ echo GridView::widget([ 上記のコードで、`$url` はカラムがボタンのために生成する URL、`$model` は現在の行に表示されるモデルオブジェクト、そして `$key` はデータプロバイダの配列の中にあるモデルのキーです。 -- [yii\grid\ActionColumn::urlCreator|urlCreator]] は、指定されたモデルの情報を使って、ボタンの URL を生成するコールバックです。 +- [[yii\grid\ActionColumn::urlCreator|urlCreator]] は、指定されたモデルの情報を使って、ボタンの URL を生成するコールバックです。 コールバックのシグニチャは [[yii\grid\ActionColumn::createUrl()]] のそれと同じでなければなりません。 このプロパティが設定されていないときは、ボタンの URL は [[yii\grid\ActionColumn::createUrl()]] を使って生成されます。 +- [[yii\grid\ActionColumn::visibleButtons|visibleButtons]] は、各ボタンの可視性の条件を定義する配列です。 + 配列のキーはボタンの名前 (波括弧を除く) であり、値は真偽値 true/false または無名関数です。 + ボタンの名前がこの配列の中で指定されていない場合は、デフォルトで、ボタンが表示されます。 + コールバックは次のシグニチャを使わなければなりません。 + + ```php + function ($model, $key, $index) { + return $model->status === 'editable'; + } + ``` + + または、真偽値を渡すことも出来ます。 + + ```php + [ + 'update' => \Yii::$app->user->can('update') + ] + ``` + #### チェックボックスカラム @@ -303,7 +330,7 @@ var keys = $('#grid').yiiGridView('getSelectedRows'); #### シリアルカラム -シリアルカラムは、`1` から始まる行番号を表示します。 +[[yii\grid\SerialColumn|シリアルカラム]] は、`1` から始まる行番号を表示します。 使い方は、次のように、とても簡単です。 @@ -318,15 +345,16 @@ echo GridView::widget([ ### データを並べ替える -> Note|注意: このセクションはまだ執筆中です。 +> Note: このセクションはまだ執筆中です。 > > - https://github.com/yiisoft/yii2/issues/1576 ### データをフィルタリングする -データをフィルタリングするためには、GridView は、フィルタリングのフォームから入力を受け取り、検索基準に合わせてデータプロバイダのクエリを修正するための [モデル](structure-models.md) を必要とします。 -[アクティブレコード](db-active-record.md) を使用している場合は、必要な機能を提供する検索用のモデルクラスを作成するのが一般的なプラクティスです (あなたに代って Gii が生成してくれます)。 -このクラスは、検索のためのバリデーション規則を定義し、データプロバイダを返す `search()` メソッドを提供するものです。 +データをフィルタリングするためには、GridView は検索基準を表す [モデル](structure-models.md) を必要とします。 +検索基準は、通常は、グリッドビューのテーブルのフィルタのフィールドから取得されます。 +[アクティブレコード](db-active-record.md) を使用している場合は、必要な機能を提供する検索用のモデルクラスを作成するのが一般的なプラクティスです (あなたに代って [Gii](start-gii.md) が生成してくれます)。 +このクラスは、検索のためのバリデーション規則を定義し、検索基準に従って修正されたクエリを持つデータプロバイダを返す `search()` メソッドを提供するものです。 `Post` モデルに対して検索機能を追加するために、次の例のようにして、`PostSearch` モデルを作成することが出来ます。 @@ -380,6 +408,9 @@ class PostSearch extends Post ``` +> Tip: フィルタのクエリを構築する方法を学ぶためには、[クエリビルダ](db-query-builder.md)、 +> 中でも特に [フィルタ条件](db-query-builder.md#filter-conditions) を参照してください。 + この `search()` メソッドをコントローラで使用して、GridView のためのデータプロバイダを取得することが出来ます。 ```php @@ -405,6 +436,87 @@ echo GridView::widget([ ``` +### 独立したフィルタ・フォーム + +たいていの場合はグリッドビューのヘッダのフィルタで十分でしょう。 +しかし、独立したフィルタのフォームが必要な場合でも、簡単に追加することができます。 +まず、以下の内容を持つパーシャル・ビュー `_search.php` を作成します。 + +```php + + +
+ ['index'], + 'method' => 'get', + ]); ?> + + field($model, 'title') ?> + + field($model, 'creation_date') ?> + +
+ 'btn btn-primary']) ?> + 'btn btn-default']) ?> +
+ + +
+``` + +そして、これを以下のように `index.php` ビューにインクルードします。 + +```php +render('_search', ['model' => $searchModel]) ?> +``` + +> Note: Gii を使って CRUD コードを生成する場合、デフォルトで、独立したフィルタ・フォーム (`_search.php`) が生成されます。 + ただし、`index.php` ビューの中ではコメントアウトされています。 + コメントを外せば、すぐに使うことが出来ます。 + +独立したフィルタ・フォームは、グリッドビューに表示されないフィールドによってフィルタをかけたり、 +または日付の範囲のような特殊なフィルタ条件を使う必要があったりする場合に便利です。 +日付の範囲によってフィルタする場合は、DB には存在しない `createdFrom` と `createdTo` という属性を検索用のモデルに追加すること良いでしょう。 + +```php +class PostSearch extends Post +{ + /** + * @var string + */ + public $createdFrom; + + /** + * @var string + */ + public $createdTo; +} +``` + +そして、`search()` メソッドでクエリの条件を次のように拡張します。 + +```php +$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom]) + ->andFilterWhere(['<=', 'creation_date', $this->createdTo]); +``` + +そして、フィルタ・フォームに、日付の範囲を示すフィールドを追加します。 + +```php +field($model, 'creationFrom') ?> + +field($model, 'creationTo') ?> +``` + + ### モデルのリレーションを扱う GridView でアクティブレコードを表示するときに、リレーションのカラムの値、例えば、単に投稿者の `id` というのではなく、投稿者の名前を表示するという場合に遭遇するかも知れません。 @@ -424,6 +536,7 @@ $dataProvider = new ActiveDataProvider([ // リレーション `author` を結合します。これはテーブル `users` に対するリレーションであり、 // テーブルエイリアスを `author` とします。 $query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]); +// バージョン 2.0.7 以降では、上の行は $query->joinWith('author AS author'); として単純化することが出来ます。 // リレーションのカラムによる並べ替えを有効にします。 $dataProvider->sort->attributes['author.name'] = [ 'asc' => ['author.name' => SORT_ASC], @@ -457,15 +570,16 @@ public function rules() $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name')]); ``` -> Info|情報: 上の例では、リレーション名とテーブルエイリアスに同じ文字列を使用しています。 +> Info: 上の例では、リレーション名とテーブルエイリアスに同じ文字列を使用しています。 > しかし、エイリアスとリレーション名が異なる場合は、どこでエイリアスを使い、どこでリレーション名を使うかに注意を払わなければなりません。 > これに関する簡単な規則は、データベースクエリを構築するために使われる全ての場所でエイリアスを使い、`attributes()` や `rules()` など、その他の全ての定義においてリレーション名を使う、というものです。 > > 例えば、投稿者のリレーションテーブルに `au` というエイリアスを使う場合は、joinWith の文は以下のようになります。 > > ```php -> $query->joinWith(['author' => function($query) { $query->from(['au' => 'users']); }]); +> $query->joinWith(['author au']); > ``` +> > リレーションの定義においてエイリアスが定義されている場合は、単に `$query->joinWith(['author']);` として呼び出すことも可能です。 > > フィルタ条件においてはエイリアスが使われなければなりませんが、属性の名前はリレーション名のままで変りません。 @@ -489,7 +603,7 @@ $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name' > $dataProvider->sort->defaultOrder = ['author.name' => SORT_ASC]; > ``` -> Info|情報: `joinWith` およびバックグラウンドで実行されるクエリの詳細については、[アクティブレコード - リレーションを使ってテーブルを結合する](db-active-record.md#joining-with-relations) を参照してください。 +> Info: `joinWith` およびバックグラウンドで実行されるクエリの詳細については、[アクティブレコード - リレーションを使ってテーブルを結合する](db-active-record.md#joining-with-relations) を参照してください。 #### SQL ビューを使って、データのフィルタリング・並べ替え・表示をする @@ -592,7 +706,42 @@ echo GridView::widget([ ### GridView を Pjax とともに使う -> Note|注意: このセクションはまだ執筆中です。 -> +[[yii\widgets\Pjax|Pjax]] ウィジェットを使うと、ページ全体をリロードせずに、ページの一部分だけを更新することが出来ます。 +これを使うと、フィルタを使うときに、[[yii\grid\GridView|GridView]] の中身だけを更新することが出来ます。 -(内容未定) +```php +use yii\widgets\Pjax; +use yii\grid\GridView; + +Pjax::begin([ + // PJax のオプション +]); + Gridview::widget([ + // GridView のオプション + ]); +Pjax::end(); +``` + +[[yii\widgets\Pjax|Pjax]] は、[[yii\widgets\Pjax::$linkSelector|Pjax::$linkSelector]] の指定に従って、リンクに対しても動作します。 +これは [[yii\grid\ActionColumn|ActionColumn]] を使う場合には問題となり得ます。 +この問題を防止するためには、[[yii\grid\ActionColumn::$buttons|ActionColumn::$buttons]] +プロパティを編集して `data-pjax="0"` という HTML 属性を追加します。 + +#### Gii における Pjax を伴う GridView + +バージョン 2.0.5 以降、[Gii](start-gii.md) では `$enablePjax` というオプションがウェブインターフェイスまたはコマンドラインで使用可能になっています。 + +```php +yii gii/crud --controllerClass="backend\\controllers\PostController" \ + --modelClass="common\\models\\Post" \ + --enablePjax=1 +``` + +これによって、[[yii\grid\GridView|GridView]] または [[yii\widgets\ListView|ListView]] +を囲む [[yii\widgets\Pjax|Pjax]] ウィジェットが生成されます。 + + +さらに読むべき文書 +------------------ + +- Arno Slatius による [Rendering Data in Yii 2 with GridView and ListView](http://www.sitepoint.com/rendering-data-in-yii-2-with-gridview-and-listview/)。 diff --git a/docs/guide-ja/output-formatter.md b/docs/guide-ja/output-formatter.md deleted file mode 100644 index 577a12be5c..0000000000 --- a/docs/guide-ja/output-formatter.md +++ /dev/null @@ -1,196 +0,0 @@ -データフォーマッタ -================== - -出力をフォーマットするために、Yii はフォーマッタクラスを提供して、データをユーザにとってより読みやすいものにします。 -デフォルトでは、[[yii\i18n\Formatter]] というヘルパクラスが、`formatter` という名前の [アプリケーションコンポーネント](structure-application-components.md) として登録されます。 - -このヘルパが、日付/時刻、数字、その他のよく使われる形式について、データをローカライズしてフォーマットするための一連のメソッドを提供します。 -フォーマッタは、二つの異なる方法で使うことが出来ます。 - -1. フォーマットするメソッド (全て `as` という接頭辞を持つフォーマッタのメソッドです) を直接に使用する。 - - ```php - echo Yii::$app->formatter->asDate('2014-01-01', 'long'); // 出力: January 1, 2014 - echo Yii::$app->formatter->asPercent(0.125, 2); // 出力: 12.50% - echo Yii::$app->formatter->asEmail('cebe@example.com'); // 出力: cebe@example.com - echo Yii::$app->formatter->asBoolean(true); // 出力: Yes - // null 値の表示も処理します。 - echo Yii::$app->formatter->asDate(null); // 出力: (Not set) - ``` - -2. [[yii\i18n\Formatter::format()|format()]] メソッドとフォーマット名を使う。 - [[yii\grid\GridView]] や [[yii\widgets\DetailView]] のようなウィジェットでは、構成情報でカラムのデータの書式を指定することが出来ますが、これらウィジェットでもこのメソッドが使われています。 - - ```php - echo Yii::$app->formatter->format('2014-01-01', 'date'); // 出力: January 1, 2014 - // 配列を使って、フォーマットメソッドのパラメータを指定することも出来ます。 - // `2` は asPercent() メソッドの $decimals パラメータの値です。 - echo Yii::$app->formatter->format(0.125, ['percent', 2]); // 出力: 12.50% - ``` - - -[PHP intl 拡張](http://php.net/manual/ja/book.intl.php) がインストールされているときは、フォーマッタの全ての出力がローカライズされます。 -これのために [[yii\i18n\Formatter::locale|locale]] プロパティを構成することが出来ます。 -[[yii\i18n\Formatter::locale|locale]] が構成されていないときは、アプリケーションの [[yii\base\Application::language|language]] がロケールとして用いられます。 -詳細は [国際化](tutorial-i18n.md) の節を参照してください。 -フォーマッタはロケールに従って、正しい日付や数字の形式を選択し、月や曜日の名称もカレントの言語に翻訳します。 -日付の形式は [[yii\i18n\Formatter::timeZone|timeZone]] によっても左右されます。 -[[yii\i18n\Formatter::timeZone|timeZone]] も、明示的に構成されていない場合は、アプリケーションの [[yii\base\Application::timeZone|timeZone]] から取られます。 - -例えば、日付のフォーマットを呼ぶと、ロケールによってさまざまな結果を出力します。 - -```php -Yii::$app->formatter->locale = 'en-US'; -echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: January 1, 2014 -Yii::$app->formatter->locale = 'de-DE'; -echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 1. Januar 2014 -Yii::$app->formatter->locale = 'ru-RU'; -echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 1 января 2014 г. -Yii::$app->formatter->locale = 'ja-JP'; -echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 2014/01/01 -``` - -> Note|注意: フォーマットの仕方は、PHP とともにコンパイルされた ICU ライブラリのバージョンの違いによって異なる可能性がありますし、[PHP intl 拡張](http://php.net/manual/ja/book.intl.php) がインストールされているか否かという事実によっても異なってきます。 -> 従って、あなたのウェブサイトが全ての環境で同じ出力を表示することを保証するために、全ての環境に PHP intl 拡張をインストールして、ICU ライブラリのバージョンが同じであることを確認する事を推奨します。 -> [PHP 環境を国際化のために設定する](tutorial-i18n.md#setup-environment) も参照してください。 -> -> もう一つ注意してほしいのは、たとえ intl 拡張がインストールされていても、32-bit システムでは、2038 年以降および 1901 年以前の日付・時刻の書式は、ローカライズされた月と日の名前を提供しない PHP の実装にフォールバックする、ということです。 -> これは 32-bit システムでは intl が 32-bit の UNIX タイムスタンプを内部的に使用するからです。 -> 64-bit のシステムでは、インストールされていれば、全ての場合に intl フォーマッタが使用されます。 - - -フォーマッタを構成する ----------------------- - -フォーマットメソッドによって使われるデフォルトの書式は、[[yii\i18n\Formatter|フォーマッタクラス]] のプロパティを使って調整することが出来ます。 -プロパティの値をアプリケーション全体にわたって調整するために、[アプリケーションの構成情報](concept-configurations.md#application-configurations) において、`formatter` コンポーネントを構成することが出来ます。 -構成の例を下記に示します。 -利用できるプロパティの詳細については、[[yii\i18n\Formatter|Formatter クラスの API ドキュメント]] と、後続の項を参照してください。 - -```php -'components' => [ - 'formatter' => [ - 'dateFormat' => 'dd.MM.yyyy', - 'decimalSeparator' => ',', - 'thousandSeparator' => ' ', - 'currencyCode' => 'EUR', - ], -], -``` - -日時の値をフォーマットする --------------------------- - -フォーマッタクラスは日時の値をフォーマットするさまざまなメソッドを提供しています。すなわち、 - -- [[yii\i18n\Formatter::asDate()|date]] - 値は日付としてフォーマットされます。例えば `2014/01/01`。 -- [[yii\i18n\Formatter::asTime()|time]] - 値は時刻としてフォーマットされます。例えば `14:23`。 -- [[yii\i18n\Formatter::asDatetime()|datetime]] - 値は日付および時刻としてフォーマットされます。例えば `2014/01/01 14:23`。 -- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - 値は [unix タイムスタンプ](http://en.wikipedia.org/wiki/Unix_time) としてフォーマットされます。例えば `1412609982`。 -- [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - 値は、その日時と現在との間隔として、人間に分かりやすい言葉でフォーマットされます。例えば `1 時間前`。 - -[[yii\i18n\Formatter::asDate()|date]]、[[yii\i18n\Formatter::asTime()|time]]、[[yii\i18n\Formatter::asDatetime()|datetime]] メソッドの日時の書式は、フォーマッタのプロパティ [[yii\i18n\Formatter::$dateFormat|$dateFormat]]、[[yii\i18n\Formatter::$timeFormat|$timeFormat]]、[[yii\i18n\Formatter::$datetimeFormat|$datetimeFormat]] を構成することで、グローバルに指定することが出来ます。 - -デフォルトでは、フォーマッタが使う書式は、ショートカット形式で指定します。 -これは、日付と時刻をユーザの国と言語にとって一般的な形式でフォーマット出来るように、現在アクティブなロケールに従ってさまざまに解釈されるものです。 -四つの異なるショートカット形式が利用できます。 - -- `short` は、`en_GB` ロケールでは、例えば、日付を `06/10/2014`、時刻を `15:58` と表示します。 -- `medium` は、 `6 Oct 2014` および `15:58:42`、 -- `long` は、`6 October 2014` および `15:58:42 GMT`、 -- そして `full` は `Monday, 6 October 2014` および `15:58:42 GMT` を表示します。 - -> Info:情報| `ja_JP` ロケールでは、次のようになります。 -> -> - `short` ... `2014/10/06` および `15:58` -> - `medium` ... `2014/10/06` および `15:58:42` -> - `long` ... `2014年10月6日` および `15:58:42 JST` -> - `full` ... `2014年10月6日月曜日` および `15時58分42秒 日本標準時` - -これに加えて、[ICU プロジェクト](http://site.icu-project.org/) によって定義された構文を使うカスタム書式を指定することが出来ます。 -この構文を説明する ICU マニュアルが下記の URL にあります: 。 -別の選択肢として、`php:` という接頭辞を付けた文字列を使って、PHP の [date()](http://php.net/manual/ja/function.date.php) 関数が認識する構文を使うことも出来ます。 - -```php -// ICU 形式 -echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06 -// PHP date() 形式 -echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06 -``` - -### タイムゾーン - -日時の値をフォーマットするときに、Yii はその値を [[yii\i18n\Formatter::timeZone|設定されたタイムゾーン]] に変換します。 -従って、入力値は、タイムゾーンが明示的に指定されていなければ、UTC であると見なされます。 -この理由により、全ての日時の値を UTC、それも、なるべくなら、定義によって UTC であることが保証されている UNIX タイムスタンプで保存することが推奨されます。 -入力値が UTC とは異なるタイムゾーンに属する場合は、次の例のように、タイムゾーンを明示的に記述しなければなりません。 - -```php -// Yii::$app->timeZone は 'Asia/Tokyo' であるとします。 -echo Yii::$app->formatter->asTime(1412599260); // 21:41:00 -echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 21:41:00 -echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 -``` - -バージョン 2.0.1 からは、上記のコードの二番目の例のようにタイムゾーン識別子を含まないタイムスタンプに対して適用されるタイムゾーンを設定することも可能になりました。 -[[yii\i18n\Formatter::defaultTimeZone]] を設定して、データストレージに使用しているタイムゾーンに合わせることが出来ます。 - -> Note|注意: タイムゾーンは世界中のさまざまな政府によって作られる規則に従うものであり、頻繁に変更されるものであるため、あなたのシステムにインストールされたタイムゾーンのデータベースが最新の情報を持っていない可能性が大いにあります。 -> タイムゾーンデータベースの更新についての詳細は、[ICU マニュアル](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) で参照することが出来ます。 -> [PHP 環境を国際化のために設定する](tutorial-i18n.md#setup-environment) も参照してください。 - - -数値をフォーマットする ----------------------- - -数値をフォーマットするために、フォーマッタクラスは次のメソッドを提供しています。 - -- [[yii\i18n\Formatter::asInteger()|integer]] - 値は整数としてフォーマットされます。例えば `42`。 -- [[yii\i18n\Formatter::asDecimal()|decimal]] - 値は小数点と三桁ごとの区切りを使って十進数としてフォーマットされます。例えば `2,542.123` または `2.542,123`。 -- [[yii\i18n\Formatter::asPercent()|percent]] - 値は百分率としてフォーマットされます。例えば `42%`。 -- [[yii\i18n\Formatter::asScientific()|scientific]] - 値は科学記法による数値としてフォーマットされます。例えば `4.2E4`。 -- [[yii\i18n\Formatter::asCurrency()|currency]] - 値は通貨の値としてフォーマットされます。例えば `£420.00`。 - この関数が正しく働くためには、`en_GB` や `en_US` のように、ロケールが国コードを含んでいる必要があります。 - なぜなら、この場合は言語だけでは曖昧になるからです。 -- [[yii\i18n\Formatter::asSize()|size]] - バイト数である値が人間にとって読みやすいサイズとしてフォーマットされます。例えば `410 キビバイト`。 -- [[yii\i18n\Formatter::asShortSize()|shortSize]] - [[yii\i18n\Formatter::asSize()|size]] の短いバージョンです。例えば `410 KiB`。 - -数値のフォーマットに使われる書式は、デフォルトではロケールに従って設定される [[yii\i18n\Formatter::decimalSeparator|decimalSeparator]] と [[yii\i18n\Formatter::thousandSeparator|thousandSeparator]] を使って調整することが出来ます。 - -更に高度な設定のためには、[[yii\i18n\Formatter::numberFormatterOptions]] と [[yii\i18n\Formatter::numberFormatterTextOptions]] を使って、内部的に使用される [NumberFormatter クラス](http://php.net/manual/ja/class.numberformatter.php) を構成することが出来ます。 - -例えば、小数部の最大桁数と最小桁数を調整するためには、次のように [[yii\i18n\Formatter::numberFormatterOptions]] プロパティを構成します。 - -```php -'numberFormatterOptions' => [ - NumberFormatter::MIN_FRACTION_DIGITS => 0, - NumberFormatter::MAX_FRACTION_DIGITS => 2, -] -``` - -その他のフォーマッタ --------------------- - -日付、時刻、そして、数値の他にも、Yii はさまざまな状況で使える一連のフォーマッタを提供しています。 - -- [[yii\i18n\Formatter::asRaw()|raw]] - 値はそのまま出力されます。`null` 値が [[nullDisplay]] を使ってフォーマットされる以外は、何の効果もない擬似フォーマッタです。 -- [[yii\i18n\Formatter::asText()|text]] - 値は HTML エンコードされます。 - これは [GridView DataColumn](output-data-widgets.md#data-column) で使われるデフォルトの形式です。 -- [[yii\i18n\Formatter::asNtext()|ntext]] - 値は HTML エンコードされ、改行文字が強制改行に変換された平文テキストとしてフォーマットされます。 -- [[yii\i18n\Formatter::asParagraphs()|paragraphs]] - 値は HTML エンコードされ、`

` タグに囲まれた段落としてフォーマットされます。 -- [[yii\i18n\Formatter::asHtml()|html]] - 値は XSS 攻撃を避けるために [[HtmlPurifier]] を使って浄化されます。 - `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]` のような追加のオプションを渡すことが出来ます。 -- [[yii\i18n\Formatter::asEmail()|email]] - 値は `mailto` リンクとしてフォーマットされます。 -- [[yii\i18n\Formatter::asImage()|image]] - 値は `image` タグとしてフォーマットされます。 -- [[yii\i18n\Formatter::asUrl()|url]] - 値はハイパーリンクとしてフォーマットされます。 -- [[yii\i18n\Formatter::asBoolean()|boolean]] - 値は真偽値としてフォーマットされます。 - デフォルトでは、`true` は `Yes`、`false` は `No` とレンダリングされ、現在のアプリケーションの言語に翻訳されます。 - この振る舞いは [[yii\i18n\Formatter::booleanFormat]] プロパティを構成して調整できます。 - -`null` 値 ---------- - -PHP において `null` である値に対して、フォーマッタクラスは空文字ではなくプレースホルダを表示します。 -`null` のプレースホルダは、デフォルトでは `(not set)` であり、それが現在のアプリケーションの言語に翻訳されます。 -[[yii\i18n\Formatter::nullDisplay|nullDisplay]] プロパティを構成して、カスタムのプレースホルダを設定することが出来ます。 -`null` 値の特別な扱いをしたくない場合は、[[yii\i18n\Formatter::nullDisplay|nullDisplay]] を `null` に設定することが出来ます。 diff --git a/docs/guide-ja/output-formatting.md b/docs/guide-ja/output-formatting.md index 577a12be5c..1aa9faaec3 100644 --- a/docs/guide-ja/output-formatting.md +++ b/docs/guide-ja/output-formatting.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD データフォーマッタ ================== @@ -110,6 +111,95 @@ echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 2014/01/01 これに加えて、[ICU プロジェクト](http://site.icu-project.org/) によって定義された構文を使うカスタム書式を指定することが出来ます。 この構文を説明する ICU マニュアルが下記の URL にあります: 。 別の選択肢として、`php:` という接頭辞を付けた文字列を使って、PHP の [date()](http://php.net/manual/ja/function.date.php) 関数が認識する構文を使うことも出来ます。 +======= +データのフォーマット +==================== + +ユーザにとってより読みやすい形式でデータを表示するために、`formatter` [アプリケーションコンポーネント](structure-application-components.md) を使ってデータをフォーマットすることが出来ます。 +デフォルトでは、フォーマッタは [[yii\i18n\Formatter]] によって実装されており、これが、日付/時刻、数字、通貨、その他のよく使われる形式にデータをフォーマットする一連のメソッドを提供します。 +このフォーマッタは次のようにして使うことが出来ます。 + +```php +$formatter = \Yii::$app->formatter; + +// 出力: January 1, 2014 +echo $formatter->asDate('2014-01-01', 'long'); + +// 出力: 12.50% +echo $formatter->asPercent(0.125, 2); + +// 出力: cebe@example.com +echo $formatter->asEmail('cebe@example.com'); + +// 出力: Yes +echo $formatter->asBoolean(true); +// it also handles display of null values: + +// 出力: (Not set) +echo $formatter->asDate(null); +``` + +ご覧のように、これらのメソッドは全て `asXyz()` という名前を付けられており、`Xyz` がサポートされている形式を表しています。 +別の方法として、汎用メソッド [[yii\i18n\Formatter::format()|format()]] を使ってデータをフォーマットすることも出来ます。 +この方法を使うと望む形式をプログラム的に制御することが可能になりますので、[[yii\grid\GridView]] や [[yii\widgets\DetailView]] などのウィジェットでは、こちらがよく使われています。 +例えば、 + +```php +// 出力: January 1, 2014 +echo Yii::$app->formatter->format('2014-01-01', 'date'); + +// 配列を使ってフォーマットメソッドのパラメータを指定することも出来ます。 +// `2` は asPercent() メソッドの $decimals パラメータの値です。 +// 出力: 12.50% +echo Yii::$app->formatter->format(0.125, ['percent', 2]); +``` + +> Note: フォーマッタコンポーネントは、エンドユーザへの表示用に値をフォーマットすることを目的に設計されています。 +> ユーザの入力を機械が読み取れる形式にフォーマットしたい場合、また、日付を機械が読み取れる形式にフォーマットしたいだけ、という場合には、 +> フォーマッタは適切なツールではありません。 +> 日付と数値についてユーザ入力を変換するためには、それぞれ、[[yii\validators\DateValidator]] と [[yii\validators\NumberValidator]] +> を使うことが出来ます。機械が読み取れる日付と時刻のフォーマットの単純な相互変換には、PHP の [date()](http://php.net/manual/ja/function.date.php) 関数で十分です。 + +## フォーマッタを構成する + +[アプリケーションの構成情報](concept-configurations.md#application-configurations) の中で `formatter` コンポーネントを構成して、フォーマットの規則をカスタマイズすることが出来ます。 +例えば、 + + +```php +return [ + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'dd.MM.yyyy', + 'decimalSeparator' => ',', + 'thousandSeparator' => ' ', + 'currencyCode' => 'EUR', + ], + ], +]; +``` + +構成可能なプロパティについては、[[yii\i18n\Formatter]] を参照してください。 + + +## 日付と時刻の値をフォーマットする + +フォーマッタは日付と時刻に関連した下記の出力形式をサポートしています。 + +- [[yii\i18n\Formatter::asDate()|date]] - 値は日付としてフォーマットされます。例えば `January 01, 2014`。 +- [[yii\i18n\Formatter::asTime()|time]] - 値は時刻としてフォーマットされます。例えば `14:23`。 +- [[yii\i18n\Formatter::asDatetime()|datetime]] - 値は日付および時刻としてフォーマットされます。例えば `January 01, 2014 14:23`。 +- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - 値は [unix タイムスタンプ](http://en.wikipedia.org/wiki/Unix_time) としてフォーマットされます。例えば `1412609982`。 +- [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - 値は、その日時と現在との間隔として、人間に分かりやすい言葉でフォーマットされます。例えば `1 hour ago`。 +- [[yii\i18n\Formatter::asDuration()|duration]] - 値は継続時間として、人間に分かりやすい言葉でフォーマットされます。例えば `1 day, 2 minutes`。 + + +[[yii\i18n\Formatter::asDate()|date]]、[[yii\i18n\Formatter::asTime()|time]]、[[yii\i18n\Formatter::asDatetime()|datetime]] メソッドに使われるデフォルトの日時書式は、フォーマッタの [[yii\i18n\Formatter::$dateFormat|$dateFormat]]、[[yii\i18n\Formatter::$timeFormat|$timeFormat]]、[[yii\i18n\Formatter::$datetimeFormat|$datetimeFormat]] を構成することで、グローバルにカスタマイズすることが出来ます。 + +日付と時刻のフォーマットは、[ICU 構文](http://userguide.icu-project.org/formatparse/datetime) によって指定することが出来ます。 +また、ICU 構文と区別するために `php:` という接頭辞を付けて、[PHP の date() 構文](http://php.net/manual/ja/function.date.php) を使うことも出来ます。 +例えば、 +>>>>>>> master ```php // ICU 形式 @@ -118,6 +208,7 @@ echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06 echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06 ``` +<<<<<<< HEAD ### タイムゾーン 日時の値をフォーマットするときに、Yii はその値を [[yii\i18n\Formatter::timeZone|設定されたタイムゾーン]] に変換します。 @@ -136,10 +227,64 @@ echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 [[yii\i18n\Formatter::defaultTimeZone]] を設定して、データストレージに使用しているタイムゾーンに合わせることが出来ます。 > Note|注意: タイムゾーンは世界中のさまざまな政府によって作られる規則に従うものであり、頻繁に変更されるものであるため、あなたのシステムにインストールされたタイムゾーンのデータベースが最新の情報を持っていない可能性が大いにあります。 +======= +複数の言語をサポートする必要があるアプリケーションを扱う場合には、ロケールごとに異なる日付と時刻のフォーマットを指定しなければならないことがよくあります。 +この仕事を単純化するためには、(`long`、`short` などの) フォーマットのショートカットを代りに使うことが出来ます。 +フォーマッタは、現在アクティブな [[yii\i18n\Formatter::locale|locale]] に従って、フォーマットのショートカットを適切なフォーマットに変換します。 +フォーマットのショートカットとして、次のものがサポートされています +(例は `en_GB` がアクティブなロケールであると仮定したものです)。 + +- `short`: 日付は `06/10/2014`、時刻は `15:58` を出力 +- `medium`: `6 Oct 2014` と `15:58:42` を出力 +- `long`: `6 October 2014` と `15:58:42 GMT` を出力 +- `full`: `Monday, 6 October 2014` と `15:58:42 GMT` を出力 + +> Info: ja_JP ロケールでは、次のようになります。 +> +> short: 2014/10/06 と 15:58 +> medium: 2014/10/06 と 15:58:42 +> long: 2014年10月6日 と 15:58:42 JST +> full: 2014年10月6日月曜日 と 15時58分42秒 日本標準時 + +バージョン 2.0.7 以降では、さまざまな暦法に従って日付をフォーマットすることが可能です。 +通常のグレゴリオ暦とは異なる暦法を使用する方法については、フォーマッタの [[yii\i18n\Formatter::$calendar|$calendar]] +プロパティの API ドキュメントを参照して下さい。 + + +### タイムゾーン + +日時の値をフォーマットするときに、Yii はその値をターゲット [[yii\i18n\Formatter::timeZone|タイムゾーン]] に変換します。 +フォーマットされる値は、タイムゾーンが明示的に指定されるか、[[yii\i18n\Formatter::defaultTimeZone]] が構成されるかしていない限り、UTC であると見なされます。 + +次の例では、ターゲット [[yii\i18n\Formatter::timeZone|タイムゾーン]] が `Europe/Berlin` に設定されているものとします。 + +```php +// UNIX タイムスタンプを時刻としてフォーマット +echo Yii::$app->formatter->asTime(1412599260); // 14:41:00 + +// UTC の日付時刻文字列を時刻としてフォーマット +echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00 + +// CEST の日付時刻文字列を時刻としてフォーマット +echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 +``` + +> Info: +> ターゲット [[yii\i18n\Formatter::timeZone|タイムゾーン]] が `Asia/Tokyo` である場合は、次のようになります。 +> +> ```php +> echo Yii::$app->formatter->asTime(1412599260); // 21:41:00 +> echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 21:41:00 +> echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 +> ``` + +> Note: タイムゾーンは世界中のさまざまな政府によって作られる規則に従うものであり、頻繁に変更されるものであるため、あなたのシステムにインストールされたタイムゾーンのデータベースが最新の情報を持っていない可能性が大いにあります。 +>>>>>>> master > タイムゾーンデータベースの更新についての詳細は、[ICU マニュアル](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) で参照することが出来ます。 > [PHP 環境を国際化のために設定する](tutorial-i18n.md#setup-environment) も参照してください。 +<<<<<<< HEAD 数値をフォーマットする ---------------------- @@ -147,6 +292,14 @@ echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 - [[yii\i18n\Formatter::asInteger()|integer]] - 値は整数としてフォーマットされます。例えば `42`。 - [[yii\i18n\Formatter::asDecimal()|decimal]] - 値は小数点と三桁ごとの区切りを使って十進数としてフォーマットされます。例えば `2,542.123` または `2.542,123`。 +======= +## 数値をフォーマットする + +フォーマッタは、数値に関連した下記の出力フォーマットをサポートしています。 + +- [[yii\i18n\Formatter::asInteger()|integer]] - 値は整数としてフォーマットされます。例えば `42`。 +- [[yii\i18n\Formatter::asDecimal()|decimal]] - 値は小数点と三桁ごとの区切りを考慮して十進数としてフォーマットされます。例えば `2,542.123` または `2.542,123`。 +>>>>>>> master - [[yii\i18n\Formatter::asPercent()|percent]] - 値は百分率としてフォーマットされます。例えば `42%`。 - [[yii\i18n\Formatter::asScientific()|scientific]] - 値は科学記法による数値としてフォーマットされます。例えば `4.2E4`。 - [[yii\i18n\Formatter::asCurrency()|currency]] - 値は通貨の値としてフォーマットされます。例えば `£420.00`。 @@ -168,6 +321,7 @@ echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 ] ``` +<<<<<<< HEAD その他のフォーマッタ -------------------- @@ -176,6 +330,16 @@ echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 - [[yii\i18n\Formatter::asRaw()|raw]] - 値はそのまま出力されます。`null` 値が [[nullDisplay]] を使ってフォーマットされる以外は、何の効果もない擬似フォーマッタです。 - [[yii\i18n\Formatter::asText()|text]] - 値は HTML エンコードされます。 これは [GridView DataColumn](output-data-widgets.md#data-column) で使われるデフォルトの形式です。 +======= +## その他のフォーマット + +日付/時刻と数値のフォーマット以外にも、Yii はよく使われるフォーマットをサポートしています。 +その中には、次のものが含まれます。 + +- [[yii\i18n\Formatter::asRaw()|raw]] - 値はそのまま出力されます。`null` 値が [[nullDisplay]] を使ってフォーマットされる以外は、何の効果もない擬似フォーマッタです。 +- [[yii\i18n\Formatter::asText()|text]] - 値は HTML エンコードされます。 + これは [GridView DataColumn](output-data-widgets.md#data-column) で使われるデフォルトのフォーマットです。 +>>>>>>> master - [[yii\i18n\Formatter::asNtext()|ntext]] - 値は HTML エンコードされ、改行文字が強制改行に変換された平文テキストとしてフォーマットされます。 - [[yii\i18n\Formatter::asParagraphs()|paragraphs]] - 値は HTML エンコードされ、`

` タグに囲まれた段落としてフォーマットされます。 - [[yii\i18n\Formatter::asHtml()|html]] - 値は XSS 攻撃を避けるために [[HtmlPurifier]] を使って浄化されます。 @@ -185,6 +349,7 @@ echo Yii::$app->formatter->asTime('2014-10-06 21:41:00 JST'); // 21:41:00 - [[yii\i18n\Formatter::asUrl()|url]] - 値はハイパーリンクとしてフォーマットされます。 - [[yii\i18n\Formatter::asBoolean()|boolean]] - 値は真偽値としてフォーマットされます。 デフォルトでは、`true` は `Yes`、`false` は `No` とレンダリングされ、現在のアプリケーションの言語に翻訳されます。 +<<<<<<< HEAD この振る舞いは [[yii\i18n\Formatter::booleanFormat]] プロパティを構成して調整できます。 `null` 値 @@ -194,3 +359,44 @@ PHP において `null` である値に対して、フォーマッタクラス `null` のプレースホルダは、デフォルトでは `(not set)` であり、それが現在のアプリケーションの言語に翻訳されます。 [[yii\i18n\Formatter::nullDisplay|nullDisplay]] プロパティを構成して、カスタムのプレースホルダを設定することが出来ます。 `null` 値の特別な扱いをしたくない場合は、[[yii\i18n\Formatter::nullDisplay|nullDisplay]] を `null` に設定することが出来ます。 +======= + この動作は [[yii\i18n\Formatter::booleanFormat]] プロパティを構成して調整できます。 + + +## `null` 値 + +Null 値は特殊な方法でフォーマットされます。 +空文字列を表示する代りに、フォーマッタは null 値を事前定義された文字列 (そのデフォルト値は `(not set)` です) に変換し、それを現在のアプリケーションの言語に翻訳します。 +この文字列は [[yii\i18n\Formatter::nullDisplay|nullDisplay]] プロパティを構成してカスタマイズすることが出来ます。 + +## データのフォーマットをローカライズする + +既に述べたように、フォーマッタは現在のアクティブな [[yii\i18n\Formatter::locale|locale]] を使って、ターゲットの国/地域にふさわしい値のフォーマットを決定することが出来ます。 +例えば、同じ日時の値でも、ロケールによって異なる書式にフォーマットされます。 + +```php +Yii::$app->formatter->locale = 'en-US'; +echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: January 1, 2014 + +Yii::$app->formatter->locale = 'de-DE'; +echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 1. Januar 2014 + +Yii::$app->formatter->locale = 'ru-RU'; +echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 1 января 2014 г. + +Yii::$app->formatter->locale = 'ja-JP'; +echo Yii::$app->formatter->asDate('2014-01-01'); // 出力: 2014/01/01 +``` + +デフォルトでは、現在のアクティブな [[yii\i18n\Formatter::locale|locale]] は [[yii\base\Application::language]] の値によって決定されます。 +これは [[yii\i18n\Formatter::locale]] プロパティを明示的に指定することによってオーバーライドすることが出来ます。 + +> Note: Yii のフォーマッタは、[PHP intl extension](http://php.net/manual/ja/book.intl.php) に依存してデータのフォーマットのローカライズをサポートしています。 +> PHP にコンパイルされた ICU ライブラリのバージョンによってフォーマットの結果が異なる場合がありますので、あなたの全ての環境で、同じ ICU バージョンを使うことが推奨されます。 +> 詳細については、[PHP 環境を国際化のために設定する](tutorial-i18n.md#setup-environment) を参照してください。 +> +> intl 拡張がインストールされていない場合は、データはローカライズされません。 +> +> 1901年より前、または、2038年より後の日時の値は、たとえ intl 拡張がインストールされていても、32-bit システムではローカライズされないことに注意してください。 +> これは、この場合、ICU ライブラリが日時の値に対して 32-bit の UNIX タイムスタンプを使用しているのが原因です。 +>>>>>>> master diff --git a/docs/guide-ja/output-pagination.md b/docs/guide-ja/output-pagination.md index a4c6fb1436..91743b5348 100644 --- a/docs/guide-ja/output-pagination.md +++ b/docs/guide-ja/output-pagination.md @@ -1,44 +1,65 @@ ページネーション ================ +<<<<<<< HEAD <<<<<<< HEAD 一つのページに一度に表示するにはデータ数が多すぎる場合に、それぞれ一定数のデータアイテムを含む部分にデータを分割して、一度に一つの部分だけを表示することがよく行われます。 このような部分はページと呼ばれますが、それがページネーションという名前の由来です。 +======= +一つのページに表示するにはデータの数が多すぎるという場合に、データを複数のページに分割して、それぞれのページでは一部分だけを表示する、という戦略がよく使われます。 +この戦略が *ページネーション* として知られるものです。 +>>>>>>> master -あなたが [データウィジェット](output-data-widgets.md) の一つとともに [データプロバイダ](output-data-providers.md) を使っている場合は、ページネーションは既に自動的に設定されて、うまく動作するようになっています。 -そうでない場合は、あなたが [[\yii\data\Pagination]] オブジェクトを作成し、[[\yii\data\Pagination::$totalCount|総アイテム数]]、[[\yii\data\Pagination::$pageSize|ページサイズ]]、[[\yii\data\Pagination::$page|現在のページ]] などのデータを代入して、クエリに適用し、そして [[\yii\widgets\LinkPager|リンクページャ]] に与えなければなりません。 +Yii は [[yii\data\Pagination]] オブジェクトを使って、ページネーションのスキームに関する情報を表します。 +具体的に言えば、 -まず最初に、コントローラアクションの中でページネーションオブジェクトを作成し、データを代入します。 +* [[yii\data\Pagination::$totalCount|totalCount]] データアイテムの総数を指定します。 + 通常、データアイテムの総数は、一つのページを表示するのに必要なデータアイテムの数より、ずっと大きなものになることに注意してください。 +* [[yii\data\Pagination::$pageSize|pageSize]] 各ページが含むアイテムの数を指定します。 + デフォルト値は 20 です。 +* [[yii\data\Pagination::$page|page]] 現在のページ番号 (0 から始まる) を示します。 + デフォルト値は 0 であり、最初のページを意味します。 + +これらの情報を全て定義した [[yii\data\Pagination]] オブジェクトを使って、データの一部分を取得して表示することが出来ます。 +例えば、データプロバイダからデータを取得する場合であれば、ページネーションによって提供される値によって、それに対応する `OFFSET` と `LIMIT` の句を DB クエリに指定することが出来ます。 +下記に例を挙げます。 ```php -function actionIndex() -{ - $query = Article::find()->where(['status' => 1]); - $countQuery = clone $query; - $pages = new Pagination(['totalCount' => $countQuery->count()]); - $models = $query->offset($pages->offset) - ->limit($pages->limit) - ->all(); +use yii\data\Pagination; - return $this->render('index', [ - 'models' => $models, - 'pages' => $pages, - ]); -} +// status = 1 である全ての記事を取得する DB クエリを構築する +$query = Article::find()->where(['status' => 1]); + +// 記事の総数を取得する (ただし、記事のデータはまだ取得しない) +$count = $query->count(); + +// 記事の総数を使ってページネーションオブジェクトを作成する +$pagination = new Pagination(['totalCount' => $count]); + +// ページネーションを使ってクエリの OFFSET と LIMIT を修正して記事を取得する +$articles = $query->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); ``` -次に、ビューにおいて、現在のページのモデルを出力し、リンクページャにページネーションオブジェクトを渡します。 +上記の例で返される記事のページ番号はどうなるでしょう? +それは `page` という名前のクエリパラメータがリクエストに含まれるかどうかによって決ります。 +デフォルトでは、ページネーションオブジェクトは [[yii\data\Pagination::$page|page]] に `page` パラメータの値をセットしようと試みます。 +そして、このパラメータが提供されていない場合には、デフォルト値である 0 が使用されます。 + +ページネーションをサポートする UI 要素の構築を容易にするために、Yii はページボタンのリストを表示する [[yii\widgets\LinkPager]] ウィジェットを提供しています。 +これは、ユーザがページボタンをクリックして、どのページを表示すべきかを指示することが出来るものです。 +このウィジェットは、ページネーションオブジェクトを受け取って、現在のページ番号が何であるかを知り、何個のページボタンを表示すべきかを知ります。 +例えば、 ```php -foreach ($models as $model) { - // ここで $model を表示 -} +use yii\widgets\LinkPager; -// ページネーションを表示 echo LinkPager::widget([ - 'pagination' => $pages, + 'pagination' => $pagination, ]); ``` +<<<<<<< HEAD ======= 一つのページに表示するにはデータの数が多すぎるという場合に、データを複数のページに分割して、それぞれのページでは一部分だけを表示する、という戦略がよく使われます。 この戦略が *ページネーション* として知られるものです。 @@ -92,6 +113,8 @@ echo LinkPager::widget([ 'pagination' => $pagination, ]); ``` +======= +>>>>>>> master UI 要素を手動で構築したい場合は、[[yii\data\Pagination::createUrl()]] を使って、いろんなページに跳ぶ URL を作成することが出来ます。 このメソッドは page パラメータを要求し、その page パラメータを含む正しくフォーマットされた URL を作成します。 @@ -102,6 +125,7 @@ UI 要素を手動で構築したい場合は、[[yii\data\Pagination::createUrl // 指定しない場合は、現在リクエストされているルートが使用される $pagination->route = 'article/index'; +<<<<<<< HEAD // /index.php?r=article/index&page=100 を表示 echo $pagination->createUrl(100); @@ -111,3 +135,13 @@ echo $pagination->createUrl(101); > Tip|ヒント: `page` クエリパラメータの名前をカスタマイズするためには、ページネーションオブジェクトを作成する際に [[yii\data\Pagination::pageParam|pageParam]] プロパティを構成します。 >>>>>>> yiichina/master +======= +// /index.php?r=article%2Findex&page=100 を表示 +echo $pagination->createUrl(100); + +// /index.php?r=article%2Findex&page=101 を表示 +echo $pagination->createUrl(101); +``` + +> Tip: `page` クエリパラメータの名前をカスタマイズするためには、ページネーションオブジェクトを作成する際に [[yii\data\Pagination::pageParam|pageParam]] プロパティを構成します。 +>>>>>>> master diff --git a/docs/guide-ja/output-sorting.md b/docs/guide-ja/output-sorting.md index d006609bf2..c10d42a0f0 100644 --- a/docs/guide-ja/output-sorting.md +++ b/docs/guide-ja/output-sorting.md @@ -1,52 +1,98 @@ 並べ替え ======== +<<<<<<< HEAD <<<<<<< HEAD 表示するデータを一つまたはいくつかの属性に従って並べ替えなければならないことがあります。 あなたが [データウィジェット](output-data-widgets.md) の一つとともに [データプロバイダ](output-data-providers.md) を使っている場合は、並べ替えはあなたに代って自動的に処理されます。 そうでない場合は、[[\yii\data\Sort]] のインスタンスを作成して構成し、クエリに適用しなければなりません。 また、[[\yii\data\Sort]] のインスタンスをビューに渡して、属性による並べ替えのためのリンクを作成することが出来ます。 +======= +複数のデータ行を表示する際に、エンドユーザによって指定されるカラムに従ってデータを並べ替えなければならないことがよくあります。 +Yii は [[yii\data\Sort]] オブジェクトを使って並べ替えのスキーマに関する情報を表します。 +具体的に言えば、 +>>>>>>> master -典型的な使用方法の例を次に示します。 +* [[yii\data\Sort::$attributes|attributes]] データの並べ替えに使用できる *属性* を指定します。 + 単純で良ければ、[モデルの属性](structure-models.md#attributes) をこの属性とすることが出来ます。 + また、複数のモデル属性や DB のカラムを結合した合成的な属性を指定することも出来ます。 + 詳細については後述します。 +* [[yii\data\Sort::$attributeOrders|attributeOrders]] 各属性について、現在リクエストされている並べ替えの方向を指定します。 +* [[yii\data\Sort::$orders|orders]] 並べ替えの方向をカラムを使う低レベルな形式で示します。 + +[[yii\data\Sort]] を使用するためには、最初にどの属性が並べ替え可能であるかを宣言します。 +次に、現在リクエストされている並べ替え情報を [[yii\data\Sort::$attributeOrders|attributeOrders]] または [[yii\data\Sort::$orders|orders]] から取得して、データのクエリをカスタマイズします。 +例えば、 ```php -function actionIndex() -{ - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - 'default' => SORT_DESC, - 'label' => 'Name', - ], +use yii\data\Sort; + +$sort = new Sort([ + 'attributes' => [ + 'age', + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + 'default' => SORT_DESC, + 'label' => '氏名', ], - ]); + ], +]); - $models = Article::find() - ->where(['status' => 1]) - ->orderBy($sort->orders) - ->all(); - - return $this->render('index', [ - 'models' => $models, - 'sort' => $sort, - ]); -} +$articles = Article::find() + ->where(['status' => 1]) + ->orderBy($sort->orders) + ->all(); ``` -ビューにおいては、 +上記の例では、[[yii\data\Sort|Sort]] オブジェクトに対して二つの属性が宣言されています。 +すなわち、`age` と `name` です。 + +`age` 属性は `Article` アクティブレコードクラスの `age` 属性に対応する *単純な* 属性です。 +これは、次の宣言と等価です。 ```php -// 並べ替えのアクションに導くリンクを表示 +'age' => [ + 'asc' => ['age' => SORT_ASC], + 'desc' => ['age' => SORT_DESC], + 'default' => SORT_ASC, + 'label' => Inflector::camel2words('age'), +] +``` + +`name` 属性は `Article` の `first_name` と `last_name` によって定義される *合成的な* 属性です。 +これは次のような配列構造を使って宣言されています。 + +- `asc` および `desc` の要素は、それぞれ、この属性を昇順および降順に並べ替える方法を指定します。 + この値が、データの並べ替えに使用されるべき実際のカラムと方向を表します。 + 一つまたは複数のカラムを指定して、単純な並べ替えや合成的な並べ替えを示すことが出来ます。 +- `default` 要素は、最初にリクエストされたときの属性の並べ替えに使用されるべき方向を指定します。 + デフォルト値は昇順です。 + つまり、以前に並べ替えられたことがない状態でこの属性による並べ替えをリクエストすると、この属性の昇順に従ってデータが並べ替えられることになります。 +- `label` 要素は、並べ替えのリンクを作成するために [[yii\data\Sort::link()]] を呼んだときに、どういうラベルを使用すべきかを指定するものです。 + 設定されていない場合は、[[yii\helpers\Inflector::camel2words()]] が呼ばれて、属性名からラベルが生成されます。 + ラベルは HTML エンコードされないことに注意してください。 + +> Info: [[yii\data\Sort::$orders|orders]] の値をデータベースのクエリに直接に供給して、`ORDER BY` 句を構築することが出来ます。 + データベースのクエリが認識できない合成的な属性が入っている場合があるため、[[yii\data\Sort::$attributeOrders|attributeOrders]] を使ってはいけません。 + +[[yii\data\Sort::link()]] を呼んでハイパーリンクを生成すれば、それをクリックして、指定した属性によるデータの並べ替えをリクエストすることが出来るようになります。 +[[yii\data\Sort::createUrl()]] を呼んで並べ替えを実行する URL を生成することも出来ます。 +例えば、 + +```php +// 生成される URL が使用すべきルートを指定する +// これを指定しない場合は、現在リクエストされているルートが使用される +$sort->route = 'article/index'; + +// 氏名による並べ替えと年齢による並べ替えを実行するリンクを表示 echo $sort->link('name') . ' | ' . $sort->link('age'); -foreach ($models as $model) { - // ここで $model を表示 -} +// /index.php?r=article%2Findex&sort=age を表示 +echo $sort->createUrl('age'); ``` +<<<<<<< HEAD 上記においては、並べ替えをサポートする二つの属性、すなわち、`name` と `age` を宣言しています。 並べ替えの情報を Article クエリに渡して、クエリ結果が Sort オブジェクトで指定された順序に従って並べ替えられるようにしています。 ビューにおいては、二つのハイパーリンクを表示して、対応する属性によって並べ替えられたデータを表示するページへ移動できるようにしています。 @@ -141,3 +187,8 @@ echo $sort->createUrl('age'); このクエリパラメータが存在しない場合のデフォルトの並べ替え方法は [[yii\data\Sort::defaultOrder]] によって指定することが出来ます。 また、[[yii\data\Sort::sortParam|sortParam]] プロパティを構成して、このクエリパラメータの名前をカスタマイズすることも出来ます。 >>>>>>> yiichina/master +======= +[[yii\data\Sort]] は、リクエストの `sort` クエリパラメータをチェックして、どの属性による並べ替えがリクエストされたかを判断します。 +このクエリパラメータが存在しない場合のデフォルトの並べ替え方法は [[yii\data\Sort::defaultOrder]] によって指定することが出来ます。 +また、[[yii\data\Sort::sortParam|sortParam]] プロパティを構成して、このクエリパラメータの名前をカスタマイズすることも出来ます。 +>>>>>>> master diff --git a/docs/guide-ja/output-theming.md b/docs/guide-ja/output-theming.md index 3627f3cec8..99bea422a2 100644 --- a/docs/guide-ja/output-theming.md +++ b/docs/guide-ja/output-theming.md @@ -1,58 +1,72 @@ テーマ ====== +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: この節はまだ執筆中です。 +======= +テーマは、元のビューレンダリングのコードに触れる必要なしに、[ビュー](structure-views.md) のセットを別のセットに置き換えるための方法です。 +テーマを使うとアプリケーションのルックアンドフィールを体系的に変更することが出来ます。 +>>>>>>> master -テーマとは、あるディレクトリの下に集められたビューとレイアウトのファイルです。 -テーマの各ファイルが、アプリケーションの対応するファイルをレンダリングの際にオーバーライドします。 -一つのアプリケーションは複数のテーマを使用することが可能で、それぞれのテーマはまったく異なるユーザ体験を提供することが出来ます。 -いつでも一つのテーマだけがアクティブになり得ます。 +テーマを使うためには、`view` アプリケーションコンポーネントの [[yii\base\View::theme|theme]] プロパティを構成しなければなりません。 +このプロパティが、ビューファイルが置換される方法を管理する [[yii\base\Theme]] オブジェクトを構成します。 +指定しなければならない [[yii\base\Theme]] のプロパティは主として以下のものです。 -> Note|注意: ビューはアプリケーションの固有性が強いものですので、通常は、テーマを再配布可能なものとして作ることはしません。 - カスタマイズしたルックアンドフィールを再配布したい場合は、テーマの代りに、[アセットバンドル](structure-assets.md) の形で CSS と JavaScript のファイルを再配布することを検討してください。 +- [[yii\base\Theme::basePath]]: テーマのリソース (CSS、JS、画像など) を含むベースディレクトリを指定します。 +- [[yii\base\Theme::baseUrl]]: テーマのリソースのベース URL を指定します。 +- [[yii\base\Theme::pathMap]]: ビューファイルの置換の規則を指定します。 + 詳細は後述する項で説明します。 -テーマを構成する ----------------- - -テーマの構成情報は、アプリケーションの `view` コンポーネントを通じて指定します。 -`basic application` のビューに対して働くテーマをセットアップするためには、アプリケーションの構成情報ファイルに以下のように記述しなければなりません。 +例えば、`SiteController` で `$this->render('about')` を呼び出すと、ビューファイル `@app/views/site/about.php` をレンダリングすることになります。 +しかし、下記のようにアプリケーション構成情報でテーマを有効にすると、代りに、ビューファイル `@app/themes/basic/site/about.php` がレンダリングされます。 ```php -'components' => [ - 'view' => [ - 'theme' => [ - 'pathMap' => ['@app/views' => '@app/themes/basic'], - 'baseUrl' => '@web/themes/basic', +return [ + 'components' => [ + 'view' => [ + 'theme' => [ + 'basePath' => '@app/themes/basic', + 'baseUrl' => '@web/themes/basic', + 'pathMap' => [ + '@app/views' => '@app/themes/basic', + ], + ], ], ], -], +]; ``` -上記においては、`pathMap` が元のパスからテーマのパスへの割り付けを定義し、`baseUrl` がテーマのファイルによって参照されるリソースのベース URL を定義しています。 +> Info: テーマではパスエイリアスがサポートされています。 + ビューの置換を行う際に、パスエイリアスは実際のファイルパスまたは URL に変換されます。 -私たちの例では、`pathMap` は `['@app/views' => '@app/themes/basic']` です。 -これは、`@app/views` の全てのビューは、最初に `@app/themes/basic` の下で探され、そのテーマのディレクトリにビューが存在していれば、それが元のビューの代りに使われる、ということを意味します。 - -例えば、上記の構成においては、ビューファイル `@app/views/site/index.php` のテーマ版は `@app/themes/basic/site/index.php` になります。 -基本的には、`@app/views/site/index.php` の `@app/views` を `@app/themes/basic` に置き換えるわけです。 - -ランタイムにおいてテーマを構成するためには、ビューをレンダリングする前に次のコードを使用することが出来ます。 -典型的には、コントローラの中に次のコードを置きます。 +[[yii\base\View::theme]] プロパティを通じて [[yii\base\Theme]] オブジェクトにアクセスすることが出来ます。 +例えば、ビューファイルの中では `$this` がビューオブジェクトを指すので、次のようなコードを書くことが出来ます。 ```php -$this->getView()->theme = Yii::createObject([ - 'class' => '\yii\base\Theme', - 'pathMap' => ['@app/views' => '@app/themes/basic'], - 'baseUrl' => '@web/themes/basic', -]); +$theme = $this->theme; + +// $theme->baseUrl . '/img/logo.gif' を返す +$url = $theme->getUrl('img/logo.gif'); + +// $theme->basePath . '/img/logo.gif' を返す +$file = $theme->getPath('img/logo.gif'); ``` -### モジュールにテーマを適用する +[[yii\base\Theme::pathMap]] プロパティが、ビューファイルがどのように置換されるべきかを制御します。 +このプロパティは「キー・値」ペアの配列を取ります。 +キーは置き換えられる元のビューのパスであり、値は対応するテーマのビューのパスです。 +置換は部分一致に基づいて行われます。 +あるビューのパスが [[yii\base\Theme::pathMap|pathMap]] 配列のキーのどれかで始っていると、その一致している部分が対応する配列の値によって置き換えられます。 +上記の構成例を使う場合、`@app/views/site/about.php` は `@app/views` というキーに部分一致するため、`@app/themes/basic/site/about.php` に置き換えられることになります。 -モジュールにテーマを適用するためには、`pathMap` を次のようなものにすることが出来ます。 + +### モジュールにテーマを適用する + +モジュールにテーマを適用するためには、[[yii\base\Theme::pathMap]] を次のように構成します。 ```php +<<<<<<< HEAD 'components' => [ 'view' => [ 'theme' => [ @@ -127,31 +141,37 @@ $file = $theme->getPath('img/logo.gif'); '@app/views' => '@app/themes/basic', '@app/modules' => '@app/themes/basic/modules', // <-- !!! >>>>>>> yiichina/master +======= +'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/modules' => '@app/themes/basic/modules', // <-- !!! +>>>>>>> master ], ``` これによって、`@app/modules/blog/views/comment/index.php` に `@app/themes/basic/modules/blog/views/comment/index.php` というテーマを適用することが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD ### ウィジェットにテーマを適用する +======= +>>>>>>> master -`@app/widgets/currency/views/index.php` に配置されているウィジェットのビューにテーマを適用するためには、ビューコンポーネントのテーマに、次のような構成情報を設定する必要があります。 +### ウィジェットにテーマを適用する + +ウィジェットにテーマを適用するためには、[[yii\base\Theme::pathMap]] を次のように構成します。 ```php -'components' => [ - 'view' => [ - 'theme' => [ - 'pathMap' => ['@app/widgets' => '@app/themes/basic/widgets'], - ], - ], +'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/widgets' => '@app/themes/basic/widgets', // <-- !!! ], ``` -上記の構成によって、`@app/widgets/currency/index.php` ビューのテーマ版を `@app/themes/basic/widgets/currency/index.php` として作成することが出来るようになります。 +これによって、`@app/widgets/currency/views/index.php` に `@app/themes/basic/widgets/currency/index.php` というテーマを適用することが出来ます。 -複数のパスを使う ----------------- +<<<<<<< HEAD 一つのパスを複数のテーマパスに割り付けることが出来ます。例えば、 ======= @@ -176,6 +196,14 @@ $file = $theme->getPath('img/logo.gif'); テーマの継承は、一つのビューパスを複数のターゲットに割り付けることによって設定することが出来ます。 例えば、 >>>>>>> yiichina/master +======= +## テーマの継承 + +場合によっては、基本的なルックアンドフィールを含むアプリケーションの基本テーマを定義しておいて、現在の祝日に基づいてルックアンドフィールを少し変更したい、ということがあるかもしれません。 +テーマの継承を使ってこの目的を達することが出来ます。 +テーマの継承は、一つのビューパスを複数のターゲットに割り付けることによって設定することが出来ます。 +例えば、 +>>>>>>> master ```php 'pathMap' => [ @@ -186,6 +214,7 @@ $file = $theme->getPath('img/logo.gif'); ] ``` +<<<<<<< HEAD <<<<<<< HEAD この場合、最初に `@app/themes/christmas/site/index.php` というビューファイルが探され、それが見つからない場合は、次に `@app/themes/basic/site/index.php` が探されます。 そして、そこにもビューがない場合は、アプリケーションのビューが使用されます。 @@ -196,3 +225,8 @@ $file = $theme->getPath('img/logo.gif'); テーマファイルが両方とも存在する場合は、最初のものが優先されます。 実際の場面では、ほとんどのテーマビューファイルを `@app/themes/basic` に保管し、その中のいくつかを `@app/themes/christmas` でカスタマイズすることになるでしょう。 >>>>>>> yiichina/master +======= +この場合、ビュー `@app/views/site/index.php` には、どちらのテーマファイルが存在するかに従って、`@app/themes/christmas/site/index.php` か `@app/themes/basic/site/index.php` か、どちらかのテーマが適用されます。 +テーマファイルが両方とも存在する場合は、最初のものが優先されます。 +実際の場面では、ほとんどのテーマビューファイルを `@app/themes/basic` に保管し、その中のいくつかを `@app/themes/christmas` でカスタマイズすることになるでしょう。 +>>>>>>> master diff --git a/docs/guide-ja/rest-authentication.md b/docs/guide-ja/rest-authentication.md index cad3ff28d1..f355da252a 100644 --- a/docs/guide-ja/rest-authentication.md +++ b/docs/guide-ja/rest-authentication.md @@ -19,11 +19,15 @@ Yii は上記の全ての認証方法をサポートしています。新しい あなたの API に対して認証を有効にするためには、次のステップを実行します。 +<<<<<<< HEAD <<<<<<< HEAD 1. `user` アプリケーションコンポーネントを構成します。 ======= 1. `user` [アプリケーションコンポーネント](structure-application-components.md) を構成します。 >>>>>>> yiichina/master +======= +1. `user` [アプリケーションコンポーネント](structure-application-components.md) を構成します。 +>>>>>>> master - [[yii\web\User::enableSession|enableSession]] プロパティを `false` に設定します。 - [[yii\web\User::loginUrl|loginUrl]] プロパティを `null` に設定し、ログインページにリダイレクトする代りに HTTP 403 エラーを表示します。 2. REST コントローラクラスにおいて、`authenticator` ビヘイビアを構成することによって、どの認証方法を使用するかを指定します。 @@ -33,19 +37,24 @@ Yii は上記の全ての認証方法をサポートしています。新しい [[yii\web\User::enableSession|enableSession]] が false である場合、ユーザの認証ステータスがセッションを使ってリクエストをまたいで存続することはありません。 その代りに、すべてのリクエストに対して認証が実行されます。このことは、ステップ 2 と 3 によって達成されます。 +<<<<<<< HEAD <<<<<<< HEAD > Tip|情報: RESTful API をアプリケーションの形式で開発する場合は、アプリケーションの構成情報で `user` アプリケーションコンポーネントの [[yii\web\User::enableSession|enableSession]] プロパティを構成することが出来ます。 ======= > Tip|情報: RESTful API をアプリケーションの形式で開発する場合は、アプリケーションの構成情報で `user` アプリケーションコンポーネント(structure-application-components.md) の [[yii\web\User::enableSession|enableSession]] プロパティを構成することが出来ます。 >>>>>>> yiichina/master RESTful API をモジュールとして開発する場合は、次のように、モジュールの `init()` メソッドに一行を追加することが出来ます。 +======= +> Tip: RESTful API をアプリケーションの形式で開発する場合は、アプリケーションの構成情報で `user` アプリケーションコンポーネント(structure-application-components.md) の [[yii\web\User::enableSession|enableSession]] プロパティを構成することが出来ます。 +> RESTful API をモジュールとして開発する場合は、次のように、モジュールの `init()` メソッドに一行を追加することが出来ます。 +>>>>>>> master > ```php -public function init() -{ - parent::init(); - \Yii::$app->user->enableSession = false; -} -``` +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` 例えば、HTTP Basic 認証を使う場合は、`authenticator` ビヘイビアを次のように構成することが出来ます。 @@ -118,5 +127,5 @@ class User extends ActiveRecord implements IdentityInterface ユーザが認証された後、おそらくは、リクエストされたリソースに対してリクエストされたアクションを実行する許可を彼または彼女が持っているかどうかをチェックしたいでしょう。 *権限付与* と呼ばれるこのプロセスについては、[権限付与](security-authorization.md) のセクションで詳細に説明されています。 -あなたのコントローラが [[yii\rest\ActiveController]] から拡張したものである場合は、[[yii\rest\Controller::checkAccess()|checkAccess()]] メソッドをオーバーライドして権限付与のチェックを実行することが出来ます。 +あなたのコントローラが [[yii\rest\ActiveController]] から拡張したものである場合は、[[yii\rest\ActiveController::checkAccess()|checkAccess()]] メソッドをオーバーライドして権限付与のチェックを実行することが出来ます。 このメソッドが [[yii\rest\ActiveController]] によって提供されている内蔵のアクションから呼び出されます。 diff --git a/docs/guide-ja/rest-controllers.md b/docs/guide-ja/rest-controllers.md index 23a8ee2e4a..0be01f759c 100644 --- a/docs/guide-ja/rest-controllers.md +++ b/docs/guide-ja/rest-controllers.md @@ -20,7 +20,7 @@ Yii は、RESTful アクションを作成する仕事を簡単にするため [[yii\rest\ActiveController]] は次の機能を追加で提供します。 * 普通は必要とされる一連のアクション: `index`、`view`、`create`、`update`、`delete`、`options` -* リクエストされたアクションとリソースに関するユーザへの権限付与 +* リクエストされたアクションとリソースに対するユーザへの権限付与 ## コントローラクラスを作成する @@ -49,7 +49,7 @@ public function actionView($id) * [[yii\filters\ContentNegotiator|contentNegotiator]]: コンテントネゴシエーションをサポート。 [レスポンス形式の設定](rest-response-formatting.md) の節で説明します。 * [[yii\filters\VerbFilter|verbFilter]]: HTTP メソッドのバリデーションをサポート。 -* [[yii\filters\AuthMethod|authenticator]]: ユーザ認証をサポート。 +* [[yii\filters\auth\AuthMethod|authenticator]]: ユーザ認証をサポート。 [認証](rest-authentication.md) の節で説明します。 * [[yii\filters\RateLimiter|rateLimiter]]: レート制限をサポート。 [レート制限](rest-rate-limiting.md) の節で説明します。 @@ -74,7 +74,7 @@ public function behaviors() ## `ActiveController` を拡張する -コントローラを [[yii\rest\ActiveController]] から拡張する場合は、このコントローラを通じて提供しようとしているリソースクラスの名前を [[yii\rest\ActiveController::modelClass||modelClass]] プロパティにセットしなければなりません。 +コントローラを [[yii\rest\ActiveController]] から拡張する場合は、このコントローラを通じて提供しようとしているリソースクラスの名前を [[yii\rest\ActiveController::modelClass|modelClass]] プロパティにセットしなければなりません。 リソースクラスは [[yii\db\ActiveRecord]] から拡張しなければなりません。 @@ -144,4 +144,4 @@ public function checkAccess($action, $model = null, $params = []) `checkAccess()` メソッドは [[yii\rest\ActiveController]] のデフォルトのアクションから呼ばれます。 新しいアクションを作成して、それに対してもアクセスチェックをしたい場合は、新しいアクションの中からこのメソッドを明示的に呼び出さなければなりません。 -> Tip|ヒント: [ロールベースアクセス制御 (RBAC) コンポーネント](security-authorization.md) を使って `checkAccess()` を実装することも可能です。 +> Tip: [ロールベースアクセス制御 (RBAC) コンポーネント](security-authorization.md) を使って `checkAccess()` を実装することも可能です。 diff --git a/docs/guide-ja/rest-error-handling.md b/docs/guide-ja/rest-error-handling.md index be98e581d8..d2d591a62e 100644 --- a/docs/guide-ja/rest-error-handling.md +++ b/docs/guide-ja/rest-error-handling.md @@ -74,11 +74,15 @@ return [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; +<<<<<<< HEAD <<<<<<< HEAD if ($response->data !== null && !empty(Yii::$app->request->get('suppress_response_code'))) { ======= if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { >>>>>>> yiichina/master +======= + if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { +>>>>>>> master $response->data = [ 'success' => $response->isSuccessful, 'data' => $response->data, diff --git a/docs/guide-ja/rest-quick-start.md b/docs/guide-ja/rest-quick-start.md index c70b2a630b..3c1efafe9b 100644 --- a/docs/guide-ja/rest-quick-start.md +++ b/docs/guide-ja/rest-quick-start.md @@ -20,19 +20,27 @@ Yii は、RESTful ウェブサービス API を実装する仕事を簡単にす ユーザのデータを RESTful API によって公開したいと仮定しましょう。 <<<<<<< HEAD +<<<<<<< HEAD ユーザのデータは `user` という DB テーブルに保存されており、それにアクセスするための [[yii\db\ActiveRecord|ActiveRecord]] クラス `app\models\User` が既に作成済みであるとします。 ======= ユーザのデータは `user` という DB テーブルに保存されており、それにアクセスするための [アクティブレコード](db-active-record.md) クラス `app\models\User` が既に作成済みであるとします。 >>>>>>> yiichina/master +======= +ユーザのデータは `user` という DB テーブルに保存されており、それにアクセスするための [アクティブレコード](db-active-record.md) クラス `app\models\User` が既に作成済みであるとします。 +>>>>>>> master ## コントローラを作成する +<<<<<<< HEAD <<<<<<< HEAD 最初に、コントローラクラス `app\controllers\UserController` を次のようにして作成します。 ======= 最初に、[コントローラ](structure-controllers.md) クラス `app\controllers\UserController` を次のようにして作成します。 >>>>>>> yiichina/master +======= +最初に、[コントローラ](structure-controllers.md) クラス `app\controllers\UserController` を次のようにして作成します。 +>>>>>>> master ```php namespace app\controllers; @@ -45,6 +53,7 @@ class UserController extends ActiveController } ``` +<<<<<<< HEAD <<<<<<< HEAD このコントローラクラスは、[[yii\rest\ActiveController]] を拡張するものです。 [[yii\rest\ActiveController::modelClass|modelClass]] を `app\models\User` と指定することによって、データの取得と操作にどのモデルが使用できるかをコントローラに教えてやります。 @@ -55,6 +64,13 @@ The controller class extends from [[yii\rest\ActiveController]], which implement By specifying [[yii\rest\ActiveController::modelClass|modelClass]] as `app\models\User`, the controller knows which model can be used for fetching and manipulating data. >>>>>>> yiichina/master +======= +このコントローラクラスは、よく使用される一揃いの RESTful アクションを実装した [[yii\rest\ActiveController]] を拡張するものです。 +[[yii\rest\ActiveController::modelClass|modelClass]] を `app\models\User` と指定することによって、データの取得と操作にどのモデルが使用できるかをコントローラに教えてやります。 +The controller class extends from [[yii\rest\ActiveController]], which implements a common set of RESTful actions. +By specifying [[yii\rest\ActiveController::modelClass|modelClass]] +as `app\models\User`, the controller knows which model can be used for fetching and manipulating data. +>>>>>>> master ## URL 規則を構成する @@ -77,11 +93,15 @@ as `app\models\User`, the controller knows which model can be used for fetching ## JSON の入力を可能にする +<<<<<<< HEAD <<<<<<< HEAD API が JSON 形式で入力データを受け取ることが出来るように、`request` アプリケーションコンポーネントの [[yii\web\Request::$parsers|parsers]] プロパティを構成して、JSON 入力のために [[yii\web\JsonParser]] を使うようにします。 ======= API が JSON 形式で入力データを受け取ることが出来るように、`request` [アプリケーションコンポーネント](structure-application-components.md) の [[yii\web\Request::$parsers|parsers]] プロパティを構成して、JSON 入力のために [[yii\web\JsonParser]] を使うようにします。 >>>>>>> yiichina/master +======= +API が JSON 形式で入力データを受け取ることが出来るように、`request` [アプリケーションコンポーネント](structure-application-components.md) の [[yii\web\Request::$parsers|parsers]] プロパティを構成して、JSON 入力のために [[yii\web\JsonParser]] を使うようにします。 +>>>>>>> master ```php 'request' => [ @@ -91,7 +111,7 @@ API が JSON 形式で入力データを受け取ることが出来るように ] ``` -> Info|情報: 上記の構成はオプションです。 +> Info: 上記の構成はオプションです。 上記のように構成しない場合は、API は `application/x-www-form-urlencoded` と `multipart/form-data` だけを入力形式として認識します。 @@ -110,7 +130,7 @@ API が JSON 形式で入力データを受け取ることが出来るように * `OPTIONS /users`: エンドポイント `/users` に関してサポートされている動詞を示す * `OPTIONS /users/123`: エンドポイント `/users/123` に関してサポートされている動詞を示す -> Info|情報: Yii はコントローラの名前を自動的に複数形にしてエンドポイントとして使用します。 +> Info: Yii はコントローラの名前を自動的に複数形にしてエンドポイントとして使用します。 > この振る舞いは [[yii\rest\UrlRule::$pluralize]] プロパティを使って構成することが可能です。 作成した API は、次のように、`curl` コマンドでアクセスすることが出来ます。 @@ -189,7 +209,7 @@ Content-Type: application/json; charset=UTF-8 {"id":1,"username":"example","email":"user@example.com","created_at":1414674789,"updated_at":1414674789} ``` -> Tip|ヒント: URL `http://localhost/users` を入力すれば、ウェブブラウザ経由で API にアクセスすることも出来ます。 +> Tip: URL `http://localhost/users` を入力すれば、ウェブブラウザ経由で API にアクセスすることも出来ます。 ただし、特殊なリクエストヘッダを送信するためには、何らかのブラウザプラグインが必要になるでしょう。 ご覧のように、レスポンスヘッダの中には、総ユーザ数やページ数などの情報が書かれています。 @@ -200,9 +220,9 @@ Content-Type: application/json; charset=UTF-8 例えば、URL `http://localhost/users?fields=id,email` は、`id` と `email` のフィールドだけを返します。 -> Info|情報: 気がついたかも知れませんが、`http://localhost/users` の結果は、いくつかの公開すべきでないフィールド、例えば `password_hash` や `auth_key` を含んでいます。 +> Info: 気がついたかも知れませんが、`http://localhost/users` の結果は、いくつかの公開すべきでないフィールド、例えば `password_hash` や `auth_key` を含んでいます。 > 当然ながら、これらが API の結果に出現することは避けたいでしょう。 -> [レスポンス形式の設定](rest-response-formatting.md) の節で説明されているように、これらのフィールドを除外することは出来ますし、また、除外しなければなりません。 +> [リソース](rest-resources.md) の節で説明されているように、これらのフィールドを除外することは出来ますし、また、除外しなければなりません。 ## まとめ diff --git a/docs/guide-ja/rest-rate-limiting.md b/docs/guide-ja/rest-rate-limiting.md index fad6207660..98e8226caf 100644 --- a/docs/guide-ja/rest-rate-limiting.md +++ b/docs/guide-ja/rest-rate-limiting.md @@ -13,9 +13,30 @@ * `loadAllowance()`: 許可されているリクエストの残り数と、レート制限が最後にチェックされたときの対応する UNIX タイムスタンプを返します。 * `saveAllowance()`: 許可されているリクエストの残り数と現在の UNIX タイムスタンプの両方を保存します。 -ユーザのテーブルの二つのカラムを追加し、それらを使って、許容されているリクエスト数とタイムスタンプの情報を記録することが出来ます。 +ユーザテーブルに二つのカラムを追加して、許容されているリクエスト数とタイムスタンプの情報を記録するのが良いでしょう。 それらを定義すれば、`loadAllowance()` と `saveAllowance()` は、認証された現在のユーザに対応する二つのカラムの値を読み書きするものとして実装することが出来ます。 -パフォーマンスを向上させるために、これらの情報をキャッシュや NoSQL ストレージに保存することを検討しても良いでしょう。 +パフォーマンスを向上させるために、これらの情報をキャッシュや NoSQL ストレージに保存することを検討しても構いません。 + +`User` モデルにおける実装は次のようなものになります。 + +```php +public function getRateLimit($request, $action) +{ + return [$this->rateLimit, 1]; // 1秒間に $rateLimit 回のリクエスト +} + +public function loadAllowance($request, $action) +{ + return [$this->allowance, $this->allowance_updated_at]; +} + +public function saveAllowance($request, $action, $allowance, $timestamp) +{ + $this->allowance = $allowance; + $this->allowance_updated_at = $timestamp; + $this->save(); +} +``` アイデンティティのクラスに必要なインタフェイスを実装すると、Yii は [[yii\rest\Controller]] のアクションフィルタとして構成された [[yii\filters\RateLimiter]] を使って、自動的にレート制限のチェックを行うようになります。 レート制限を超えると、レートリミッタが [[yii\web\TooManyRequestsHttpException]] を投げます。 diff --git a/docs/guide-ja/rest-resources.md b/docs/guide-ja/rest-resources.md index 6e4f01660a..19e2287c42 100644 --- a/docs/guide-ja/rest-resources.md +++ b/docs/guide-ja/rest-resources.md @@ -85,7 +85,7 @@ public function fields() } ``` -> Warning|警告: デフォルトではモデルの全ての属性がエクスポートされる配列に含まれるため、データを精査して、 +> Warning: デフォルトではモデルの全ての属性がエクスポートされる配列に含まれるため、データを精査して、 > 公開すべきでない情報が含まれていないことを確認すべきです。そういう情報がある場合は、 > `fields()` をオーバーライドして、除去すべきです。上記の例では、`auth_key`、`password_hash` > および `password_reset_token` を選んで除去しています。 diff --git a/docs/guide-ja/rest-response-formatting.md b/docs/guide-ja/rest-response-formatting.md index b956492f2e..b0ffddc4fb 100644 --- a/docs/guide-ja/rest-response-formatting.md +++ b/docs/guide-ja/rest-response-formatting.md @@ -8,12 +8,16 @@ RESTful API のリクエストを処理するとき、アプリケーション 2. リソースオブジェクトを配列に変換します。 [リソース](rest-resources.md) の節で説明したように、この作業は [[yii\rest\Serializer]] によって実行されます。 3. 配列をコンテントネゴシエーションのステップで決定された形式の文字列に変換します。 +<<<<<<< HEAD <<<<<<< HEAD この作業は、[[yii\web\Response::formatters|response]] アプリケーションコンポーネントに登録された [[yii\web\ResponseFormatterInterface|レスポンスフォーマッタ]] によって実行されます。 ======= この作業は、`response` [アプリケーションコンポーネント](structure-application-components.md) の [[yii\web\Response::formatters|formatters]] プロパティに登録された [[yii\web\ResponseFormatterInterface|レスポンスフォーマッタ]] によって実行されます。 >>>>>>> yiichina/master +======= + この作業は、`response` [アプリケーションコンポーネント](structure-application-components.md) の [[yii\web\Response::formatters|formatters]] プロパティに登録された [[yii\web\ResponseFormatterInterface|レスポンスフォーマッタ]] によって実行されます。 +>>>>>>> master ## コンテントネゴシエーション diff --git a/docs/guide-ja/rest-routing.md b/docs/guide-ja/rest-routing.md index 5f92713f8c..b1acb35304 100644 --- a/docs/guide-ja/rest-routing.md +++ b/docs/guide-ja/rest-routing.md @@ -6,10 +6,14 @@ 実際には、綺麗な URL を有効にして HTTP 動詞を利用したいというのが普通でしょう。 例えば、`POST /users` というリクエストが `user/create` アクションへのアクセスを意味するようにする訳です。 <<<<<<< HEAD +<<<<<<< HEAD これは、アプリケーションの構成情報で `urlManager` アプリケーションコンポーネントを次のように構成することによって容易に達成することが出来ます。 ======= これは、アプリケーションの構成情報で `urlManager` [アプリケーションコンポーネント](structure-application-components.md) を次のように構成することによって容易に達成することが出来ます。 >>>>>>> yiichina/master +======= +これは、アプリケーションの構成情報で `urlManager` [アプリケーションコンポーネント](structure-application-components.md) を次のように構成することによって容易に達成することが出来ます。 +>>>>>>> master ```php 'urlManager' => [ @@ -78,7 +82,7 @@ これは、[[yii\rest\UrlRule]] が子 URL 規則を作るときに、コントローラの ID を自動的に複数形にするためです。 この振る舞いは [[yii\rest\UrlRule::pluralize]] を false に設定することで無効にすることが出来ます。 -> Info|情報: コントローラ ID の複数形化は [[yii\helpers\Inflector::pluralize()]] によって行われます。 +> Info: コントローラ ID の複数形化は [[yii\helpers\Inflector::pluralize()]] によって行われます。 このメソッドは特殊な複数形の規則を考慮します。 例えば、`box` という単語の複数形は `boxs` ではなく `boxes` になります。 diff --git a/docs/guide-ja/rest-versioning.md b/docs/guide-ja/rest-versioning.md index ed98002dbb..f4be7a6829 100644 --- a/docs/guide-ja/rest-versioning.md +++ b/docs/guide-ja/rest-versioning.md @@ -8,7 +8,7 @@ BC を損なうかも知れない変更が必要な場合は、それを API の新しいバージョンにおいて導入し、バージョン番号を上げるべきです。 そうすれば、既存のクライアントは、API の古いけれども動作するバージョンを使い続けることが出来ますし、新しいまたはアップグレードされたクライアントは、新しい API バージョンで新しい機能を使うことが出来ます。 -> Tip|ヒント: API のバージョン番号の設計に関する詳細情報は [Semantic Versioning](http://semver.org/) を参照してください。 +> Tip: API のバージョン番号の設計に関する詳細情報は [Semantic Versioning](http://semver.org/) を参照してください。 API のバージョン管理を実装する方法としてよく使われるのは、バージョン番号を API の URL に埋め込む方法です。 例えば、`http://example.com/v1/users` が API バージョン 1 の `/users` エンドポイントを指す、というものです。 diff --git a/docs/guide-ja/runtime-handling-errors.md b/docs/guide-ja/runtime-handling-errors.md index 6fd0000dde..8e600cab32 100644 --- a/docs/guide-ja/runtime-handling-errors.md +++ b/docs/guide-ja/runtime-handling-errors.md @@ -1,11 +1,11 @@ エラー処理 ========== -Yii は、エラー処理を従来よりはるかに快適な経験にしてくれる、内臓の [[yii\web\ErrorHandler|エラーハンドラ]] を持っています。 +Yii が内蔵している [[yii\web\ErrorHandler|エラーハンドラ]] は、エラー処理を従来よりはるかに快適な経験にしてくれます。 具体的には、Yii のエラーハンドラはエラー処理をより良くするために、次のことを行います。 * 致命的でない全ての PHP エラー (警告や通知) は捕捉可能な例外に変換されます。 -* 例外と致命的な PHP エラーは、デバッグモードでは、詳細なコールスタック情報とソースコード行とともに表示されます。 +* 例外および致命的 PHP エラーは、デバッグモードでは、詳細なコールスタック情報とソースコード行とともに表示されます。 * エラーを表示するために専用の [コントローラアクション](structure-controllers.md#actions) を使うことがサポートされています。 * さまざまなエラーレスポンス形式をサポートしています。 @@ -62,7 +62,7 @@ throw new NotFoundHttpException(); `YII_DEBUG` が true である (デバッグモードである) 場合は、エラーハンドラは、デバッグがより容易になるように、例外とともに、詳細なコールスタック情報とソースコード行を表示します。 そして、`YII_DEBUG` が false のときは、アプリケーションに関する公開できない情報の開示を防ぐために、エラーメッセージだけが表示されます。 -> Info|情報: 例外が [[yii\base\UserException]] の子孫である場合は、`YII_DEBUG` の値の如何にかかわらず、コールスタックは表示されません。 +> Info: 例外が [[yii\base\UserException]] の子孫である場合は、`YII_DEBUG` の値の如何にかかわらず、コールスタックは表示されません。 これは、この種の例外はユーザの誤操作によって引き起こされるものであり、開発者は何も修正する必要がないと考えられるからです。 デフォルトでは、[[yii\web\ErrorHandler|エラーハンドラ]] は二つの [ビュー](structure-views.md) を使ってエラーを表示します。 @@ -135,11 +135,21 @@ public function actionError() * `message`: エラーメッセージ。 * `exception`: 例外オブジェクト。これを通じて、更に有用な情報、例えば、HTTP ステータスコード、エラーコード、エラーコールスタックなどにアクセスすることが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD > Info|情報: あなたが [ベーシックアプリケーションテンプレート](start-installation.md) または [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) を使っている場合は、エラーアクションとエラービューは、既にあなたのために定義されています。 ======= > Info|情報: あなたが [ベーシックプロジェクトテンプレート](start-installation.md) または [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) を使っている場合は、エラーアクションとエラービューは、既にあなたのために定義されています。 >>>>>>> yiichina/master +======= +> Info: あなたが [ベーシックプロジェクトテンプレート](start-installation.md) または [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) を使っている場合は、エラーアクションとエラービューは、既にあなたのために定義されています。 + +> Note: エラーハンドラの中でリダイレクトする必要がある場合は、次のようにしてください。 +> ```php +> Yii::$app->getResponse()->redirect($url)->send(); +> return; +> ``` +>>>>>>> master ### エラーのレスポンス形式をカスタマイズする diff --git a/docs/guide-ja/runtime-logging.md b/docs/guide-ja/runtime-logging.md index 6a93ed25ed..f644b6339e 100644 --- a/docs/guide-ja/runtime-logging.md +++ b/docs/guide-ja/runtime-logging.md @@ -30,7 +30,7 @@ Yii のロギングフレームワークを使うためには、下記のステ Yii::trace('平均収益の計算を開始'); ``` -> Info|情報: ログメッセージは文字列でも、配列やオブジェクトのような複雑なデータでも構いません。 +> Info: ログメッセージは文字列でも、配列やオブジェクトのような複雑なデータでも構いません。 ログメッセージを適切に取り扱うのは [ログターゲット](#log-targets) の責任です。 デフォルトでは、ログメッセージが文字列でない場合は、[[yii\helpers\VarDumper::export()]] が呼ばれて文字列に変換されることになります。 @@ -46,7 +46,7 @@ Yii::trace('平均収益の計算を開始', __METHOD__); `__METHOD__` という定数は、それが出現する場所のメソッド名 (完全修飾のクラス名が前置されます) として評価されます。 例えば、上記のコードが `app\controllers\RevenueController::calculate` というメソッドの中で呼ばれている場合は、`__METHOD__` は `'app\controllers\RevenueController::calculate'` という文字列と同じになります。 -> Info|情報: 上記で説明したメソッドは、実際には、[[yii\log\Logger|ロガーオブジェクト]] の [[yii\log\Logger::log()|log()]] メソッドへのショートカットです。 +> Info: 上記で説明したメソッドは、実際には、[[yii\log\Logger|ロガーオブジェクト]] の [[yii\log\Logger::log()|log()]] メソッドへのショートカットです。 [[yii\log\Logger|ロガーオブジェクト]] は `Yii::getLogger()` という式でアクセス可能なシングルトンです。 ロガーオブジェクトは、十分な量のメッセージが記録されたとき、または、アプリケーションが終了するときに、[[yii\log\Dispatcher|メッセージディスパッチャ]] を呼んで、登録された [ログターゲット](#log-targets) に記録されたログメッセージを送信します。 @@ -88,7 +88,7 @@ return [ ]; ``` -> Note|注意: `log` コンポーネントは、ログメッセージをターゲットに即座に送付することが出来るように、[ブートストラップ](runtime-bootstrapping.md) 時にロードされなければなりません。 +> Note: `log` コンポーネントは、ログメッセージをターゲットに即座に送付することが出来るように、[ブートストラップ](runtime-bootstrapping.md) 時にロードされなければなりません。 この理由により、上記の例で示されているように、`bootstrap` の配列に `log` をリストアップしています。 上記のコードでは、二つのログターゲットが [[yii\log\Dispatcher::targets]] プロパティに登録されています。 @@ -149,7 +149,7 @@ Yii は下記のログターゲットをあらかじめ内蔵しています。 ] ``` -> Info|情報: HTTP 例外が [エラーハンドラ](runtime-handling-errors.md) によって捕捉されたときは、`yii\web\HttpException:ErrorCode` という書式のカテゴリ名でエラーメッセージがログに記録されます。 +> Info: HTTP 例外が [エラーハンドラ](runtime-handling-errors.md) によって捕捉されたときは、`yii\web\HttpException:ErrorCode` という書式のカテゴリ名でエラーメッセージがログに記録されます。 例えば、[[yii\web\NotFoundHttpException]] は、`yii\web\HttpException:404` というカテゴリのエラーメッセージを発生させます。 @@ -220,7 +220,7 @@ return [ 上記のアプリケーションの構成は、[[yii\log\Dispatcher::traceLevel|traceLevel]] を `YII_DEBUG` が on のときは 3、`YII_DEBUG` が off のときは 0 に設定します。 これは、`YII_DEBUG` が on のときは、各ログメッセージに対して、ログメッセージが記録されたときのコールスタックを最大 3 レベルまで追加し、`YII_DEBUG` が 0 のときはコールスタックを含めない、ということを意味します。 -> Info|情報: コールスタック情報の取得は軽微な処理ではありません。従って、この機能は開発時またはアプリケーションをデバッグするときに限って使用するべきです。 +> Info: コールスタック情報の取得は軽微な処理ではありません。従って、この機能は開発時またはアプリケーションをデバッグするときに限って使用するべきです。 ### メッセージの吐き出しとエクスポート @@ -242,7 +242,7 @@ return [ ]; ``` -> Info|情報: メッセージの吐き出しは、アプリケーションの終了時にも実行されます。これによって、ログターゲットが完全なログメッセージを受け取ることが保証されます。 +> Info: メッセージの吐き出しは、アプリケーションの終了時にも実行されます。これによって、ログターゲットが完全なログメッセージを受け取ることが保証されます。 [[yii\log\Logger|ロガーオブジェクト]] が [ログターゲット](#log-targets) にログメッセージを吐き出しても、ログメッセージはただちにはエクスポートされません。 そうではなく、ログターゲットが一定数のフィルタされたメッセージを蓄積して初めて、メッセージのエクスポートが発生します。 @@ -276,7 +276,7 @@ return [ ]; ``` -> Note|注意: 頻繁なメッセージの吐き出しとエクスポートはアプリケーションのパフォーマンスを低下させます。 +> Note: 頻繁なメッセージの吐き出しとエクスポートはアプリケーションのパフォーマンスを低下させます。 ### ログターゲットの 有効/無効 を切り替える diff --git a/docs/guide-ja/runtime-overview.md b/docs/guide-ja/runtime-overview.md index bd9cc495d3..dae1e3820b 100644 --- a/docs/guide-ja/runtime-overview.md +++ b/docs/guide-ja/runtime-overview.md @@ -8,10 +8,10 @@ Yii のアプリケーションがリクエストを処理するときは、毎 3. アプリケーションは、[リクエスト](runtime-requests.md) アプリケーションコンポーネントの助けを借りて、リクエストされた [ルート](runtime-routing.md) を解決します。 4. アプリケーションはリクエストを処理するための [コントローラ](structure-controllers.md) のインスタンスを作成します。 5. コントローラは [アクション](structure-controllers.md) のインスタンスを作成して、アクションのためのフィルタを実行します。 -6. フィルタのどれかが失敗すると、アクションはキャンセルされます。 +6. [フィルタ](structure-filters.md)のどれかが失敗すると、アクションはキャンセルされます。 7. すべてのフィルタを無事に通ったら、アクションが実行されます。 -8. アクションはデータモデルを、おそらくはデータベースから、ロードします。 -9. アクションはデータモデルをビューに提供して、ビューをレンダリングします。 +8. アクションはデータ[モデル](structure-models.md)を、おそらくはデータベースから、ロードします。 +9. アクションはデータモデルを[ビュー](structure-views.md)に提供して、ビューをレンダリングします。 10. レンダリングの結果は [レスポンス](runtime-responses.md) アプリケーションコンポーネントに返されます。 11. レスポンスコンポーネントがレンダリングの結果をユーザのブラウザに送信します。 diff --git a/docs/guide-ja/runtime-requests.md b/docs/guide-ja/runtime-requests.md index 634b7b77c5..ed815ebf1f 100644 --- a/docs/guide-ja/runtime-requests.md +++ b/docs/guide-ja/runtime-requests.md @@ -33,7 +33,7 @@ $name = $request->post('name', ''); // $name = isset($_POST['name']) ? $_POST['name'] : ''; と同等 ``` -> Info|情報: 直接に `$_GET` と `$_POST` にアクセスしてリクエストのパラメータを読み出す代りに、上記に示されているように、`request` コンポーネントを通じてそれらを取得することが推奨されます。 +> Info: 直接に `$_GET` と `$_POST` にアクセスしてリクエストのパラメータを読み出す代りに、上記に示されているように、`request` コンポーネントを通じてそれらを取得することが推奨されます。 このようにすると、ダミーのリクエストデータを持った模擬リクエストコンポーネントを作ることが出来るため、テストを書くことがより容易になります。 [RESTful API](rest-quick-start.md) を実装するときは、PUT、PATCH またはその他の [リクエストメソッド](#request-methods) によって送信されたパラメータを読み出さなければならないことがよくあります。 @@ -50,7 +50,7 @@ $params = $request->bodyParams; $param = $request->getBodyParam('id'); ``` -> Info|情報: `GET` パラメータとは異なって、`POST`、`PUT`、`PATCH` などで送信されたパラメータは、リクエストのボディの中で送られます。 +> Info: `GET` パラメータとは異なって、`POST`、`PUT`、`PATCH` などで送信されたパラメータは、リクエストのボディの中で送られます。 上述のメソッドによってこれらのパラメータにアクセスすると、`request` コンポーネントがパラメータを解析します。 [[yii\web\Request::parsers]] プロパティを構成することによって、これらのパラメータが解析される方法をカスタマイズすることが出来ます。 @@ -64,10 +64,10 @@ $param = $request->getBodyParam('id'); ```php $request = Yii::$app->request; -if ($request->isAjax) { // リクエストは AJAX リクエスト } -if ($request->isGet) { // リクエストメソッドは GET } -if ($request->isPost) { // リクエストメソッドは POST } -if ($request->isPut) { // リクエストメソッドは PUT } +if ($request->isAjax) { /* リクエストは AJAX リクエスト */ } +if ($request->isGet) { /* リクエストメソッドは GET */ } +if ($request->isPost) { /* リクエストメソッドは POST */ } +if ($request->isPut) { /* リクエストメソッドは PUT */ } ``` ## リクエストの URL @@ -99,7 +99,7 @@ $headers = Yii::$app->request->headers; // Accept ヘッダの値を返す $accept = $headers->get('Accept'); -if ($headers->has('User-Agent')) { // User-Agent ヘッダが在る } +if ($headers->has('User-Agent')) { /* User-Agent ヘッダが在る */ } ``` `request` コンポーネントは、よく使用されるいくつかのヘッダにすばやくアクセスする方法を提供しています。 @@ -115,7 +115,7 @@ if ($headers->has('User-Agent')) { // User-Agent ヘッダが在る } あなたのアプリケーションが複数の言語をサポートしており、エンドユーザが最も優先する言語でページを表示したいと思う場合は、言語ネゴシエーションメソッド [[yii\web\Request::getPreferredLanguage()]] を使うことが出来ます。 このメソッドはアプリケーションによってサポートされている言語のリストを引数として取り、 [[yii\web\Request::acceptableLanguages|acceptableLanguages]] と比較して、最も適切な言語を返します。 -> Tip|ヒント: [[yii\filters\ContentNegotiator|ContentNegotiator]] フィルタを使用して、レスポンスにおいてどのコンテントタイプと言語を使うべきかを動的に決定することも出来ます。 +> Tip: [[yii\filters\ContentNegotiator|ContentNegotiator]] フィルタを使用して、レスポンスにおいてどのコンテントタイプと言語を使うべきかを動的に決定することも出来ます。 このフィルタは、上記で説明したプロパティとメソッドの上に、コンテントネゴシエーションを実装しています。 diff --git a/docs/guide-ja/runtime-responses.md b/docs/guide-ja/runtime-responses.md index 37ad89a0d3..9b3a6fe0ee 100644 --- a/docs/guide-ja/runtime-responses.md +++ b/docs/guide-ja/runtime-responses.md @@ -72,7 +72,7 @@ $headers->set('Pragma', 'no-cache'); $values = $headers->remove('Pragma'); ``` -> Info|情報: ヘッダ名は大文字小文字を区別しません。 +> Info: ヘッダ名は大文字小文字を区別しません。 そして、新しく登録されたヘッダは、[[yii\web\Response::send()]] メソッドが呼ばれるまで送信されません。 @@ -152,7 +152,7 @@ public function actionInfo() } ``` -> Note|注意: 自分自身のレスポンスオブジェクトを作成しようとする場合は、アプリケーションの構成情報で `response` コンポーネントのために設定した構成情報を利用することは出来ません。 +> Note: 自分自身のレスポンスオブジェクトを作成しようとする場合は、アプリケーションの構成情報で `response` コンポーネントのために設定した構成情報を利用することは出来ません。 しかし、 [依存の注入](concept-di-container.md) を使えば、 共通の構成情報をあなたの新しいレスポンスオブジェクトに適用することが出来ます。 @@ -182,7 +182,7 @@ public function actionOld() \Yii::$app->response->redirect('http://example.com/new', 301)->send(); ``` -> Info|情報: デフォルトでは、[[yii\web\Response::redirect()]] メソッドはレスポンスのステータスコードを 302 にセットします。 +> Info: デフォルトでは、[[yii\web\Response::redirect()]] メソッドはレスポンスのステータスコードを 302 にセットします。 これはブラウザに対して、リクエストされているリソースが *一時的に* 異なる URI に配置されていることを示すものです。 ブラウザに対してリソースが *恒久的に* 配置替えされたことを教えるためには、ステータスコード 301 を渡すことが出来ます。 @@ -190,7 +190,7 @@ public function actionOld() この問題を解決するために、[[yii\web\Response::redirect()]] メソッドは `X-Redirect` ヘッダにリダイレクト先 URL を値としてセットします。 そして、クライアントサイドで、このヘッダの値を読み、それに応じてブラウザをリダイレクトする JavaScript を書くことが出来ます。 -> Info|情報: Yii には `yii.js` という JavaScript ファイルが付属しています。 +> Info: Yii には `yii.js` という JavaScript ファイルが付属しています。 これは、よく使われる一連の JavaScript 機能を提供するもので、その中には `X-Redirect` ヘッダに基づくブラウザのリダイレクトも含まれています。 従って、あなたが ([[yii\web\YiiAsset]] アセットバンドルを登録して) この JavaScript ファイルを使うつもりなら、AJAX のリダイレクトをサポートするためには、何も書く必要がなくなります。 diff --git a/docs/guide-ja/runtime-routing.md b/docs/guide-ja/runtime-routing.md index 6ffd01dc0a..5d5e0772e7 100644 --- a/docs/guide-ja/runtime-routing.md +++ b/docs/guide-ja/runtime-routing.md @@ -9,11 +9,15 @@ Yii のアプリケーションがリクエストされた URL の処理を開 これは、与えられたルートとそれに結び付けられたクエリパラメータから URL を生成するものです。 生成された URL が後でリクエストされたときには、ルーティングのプロセスがその URL を解決して元のルートとクエリパラメータに戻すことが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD ルーティングと URL 生成について主たる役割を果たすのが `urlManager` アプリケーションコンポーネントとして登録されている [[yii\web\UrlManager|URL マネージャ]] です。 ======= ルーティングと URL 生成について主たる役割を果たすのが `urlManager` [アプリケーションコンポーネント](structure-application-components.md) として登録されている [[yii\web\UrlManager|URL マネージャ]] です。 >>>>>>> yiichina/master +======= +ルーティングと URL 生成について主たる役割を果たすのが `urlManager` [アプリケーションコンポーネント](structure-application-components.md) として登録されている [[yii\web\UrlManager|URL マネージャ]] です。 +>>>>>>> master [[yii\web\UrlManager|URL マネージャ]] は、入ってくるリクエストをルートとそれに結び付けられたクエリパラメータとして解析するための [[yii\web\UrlManager::parseRequest()|parseRequest()]] メソッドと、与えられたルートとそれに結び付けられたクエリパラメータから URL を生成するための [[yii\web\UrlManager::createUrl()|createUrl()]] メソッドを提供します。 アプリケーション構成情報の `urlManager` コンポーネントを構成することによって、既存のアプリケーションコードを修正することなく、任意の URL 形式をアプリケーションに認識させることが出来ます。 @@ -30,7 +34,7 @@ $url = Url::to(['post/view', 'id' => 100]); そして、こうして生成された URL が後でリクエストされた場合には、解析されて元のルートとクエリパラメータの値に戻されます。 ``` -/index.php?r=post/view&id=100 +/index.php?r=post%2Fview&id=100 /index.php/post/100 /posts/100 ``` @@ -54,7 +58,7 @@ $url = Url::to(['post/view', 'id' => 100]); ## ルーティング ルーティングは二つのステップを含みます。最初のステップでは、入ってくるリクエストが解析されて、ルートとそれに結び付けられたクエリパラメータに分解されます。 -そして、第二のステップでは、解析されたルートに対応する [コントローラアクション](structure-controllers.md) がリクエストを処理するために生成されます。 +そして、第二のステップでは、解析されたルートに対応する [コントローラアクション](structure-controllers.md#actions) がリクエストを処理するために生成されます。 デフォルトの URL 形式を使っている場合は、リクエストからルートを解析することは、`r` という名前の `GET` クエリパラメータを取得するだけの簡単なことです。 @@ -74,11 +78,11 @@ $url = Url::to(['post/view', 'id' => 100]); 3. 現在の ID がカレントモジュールの [[yii\base\Module::modules|modules]] プロパティのリストに挙げられたモジュールを指すものかどうかを調べます。 もしそうであれば、モジュールのリストで見つかった構成情報に従ってモジュールが生成されます。 そして、新しく生成されたモジュールのコンテキストのもとで、ステップ 2 に戻って、ルートの次の部分を処理します。 -4. 現在の ID をコントローラ ID として扱ってコントローラオブジェクトを生成します。 +4. 現在の ID を [コントローラ ID](structure-controllers.md#controller-ids) として扱ってコントローラオブジェクトを生成します。 そしてルートの残りの部分を持って次のステップに進みます。 5. コントローラは、[[yii\base\Controller::actions()|アクションマップ]] の中に現在の ID があるかどうかを調べます。 もし有れば、マップの中で見つかった構成情報に従ってアクションを生成します。 - もし無ければ、現在の ID に対応するアクションメソッドで定義されるインラインアクションを生成しようと試みます。 + もし無ければ、現在の [アクション ID](structure-controllers.md#action-ids) に対応するアクションメソッドで定義されるインラインアクションを生成しようと試みます。 上記のステップの中で、何かエラーが発生すると、[[yii\web\NotFoundHttpException]] が投げられて、ルーティングのプロセスが失敗したことが示されます。 @@ -113,6 +117,8 @@ $url = Url::to(['post/view', 'id' => 100]); `catchAll` プロパティは配列を取り、最初の要素はルートを指定し、残りの要素 (「名前-値」のペア) は [アクションのパラメータ](structure-controllers.md#action-parameters) を指定するものでなければなりません。 +> Info: このプロパティを有効にすると、開発環境でデバッグパネルが動作しなくなります。 + ## URL を生成する @@ -121,19 +127,19 @@ Yii は、与えられたルートとそれに結び付けられたクエリパ ```php use yii\helpers\Url; -// ルートへの URL を生成する: /index.php?r=post/index +// ルートへの URL を生成する: /index.php?r=post%2Findex echo Url::to(['post/index']); -// パラメータを持つルートへの URL を生成する: /index.php?r=post/view&id=100 +// パラメータを持つルートへの URL を生成する: /index.php?r=post%2Fview&id=100 echo Url::to(['post/view', 'id' => 100]); -// アンカー付きの URL を生成する: /index.php?r=post/view&id=100#content +// アンカー付きの URL を生成する: /index.php?r=post%2Fview&id=100#content echo Url::to(['post/view', 'id' => 100, '#' => 'content']); -// 絶対 URL を生成する: http://www.example.com/index.php?r=post/index +// 絶対 URL を生成する: http://www.example.com/index.php?r=post%2Findex echo Url::to(['post/index'], true); -// https スキームを使って絶対 URL を生成する: https://www.example.com/index.php?r=post/index +// https スキームを使って絶対 URL を生成する: https://www.example.com/index.php?r=post%2Findex echo Url::to(['post/index'], 'https'); ``` @@ -155,19 +161,19 @@ echo Url::to(['post/index'], 'https'); ```php use yii\helpers\Url; -// 現在リクエストされているルート: /index.php?r=admin/post/index +// 現在リクエストされているルート: /index.php?r=admin%2Fpost%2Findex echo Url::to(['']); -// アクション ID だけの相対ルート: /index.php?r=admin/post/index +// アクション ID だけの相対ルート: /index.php?r=admin%2Fpost%2Findex echo Url::to(['index']); -// 相対ルート: /index.php?r=admin/post/index +// 相対ルート: /index.php?r=admin%2Fpost%2Findex echo Url::to(['post/index']); -// 絶対ルート: /index.php?r=post/index +// 絶対ルート: /index.php?r=post%2Findex echo Url::to(['/post/index']); -// /index.php?r=post/index エイリアス "@posts" が "/post/index" と定義されていると仮定 +// /index.php?r=post%2Findex エイリアス "@posts" が "/post/index" と定義されていると仮定 echo Url::to(['@posts']); ``` @@ -180,7 +186,7 @@ echo Url::to(['@posts']); ```php use yii\helpers\Url; -// 現在リクエストされている URL: /index.php?r=admin/post/index +// 現在リクエストされている URL: /index.php?r=admin%2Fpost%2Findex echo Url::to(); // エイリアス化された URL: http://example.com @@ -197,7 +203,7 @@ echo Url::to('/images/logo.gif', true); ```php use yii\helpers\Url; -// ホームページの URL: /index.php?r=site/index +// ホームページの URL: /index.php?r=site%2Findex echo Url::home(); // ベース URL。アプリケーションがウェブルートのサブディレクトリに配置されているときに便利 @@ -244,7 +250,7 @@ echo Url::previous(); * [[yii\web\UrlManager::rules|rules]]: このプロパティが URL を解析および生成するための一連の規則を含みます。 このプロパティが、アプリケーションの固有の要求を満たす形式を持つ URL を生成するために、あなたが主として使うプロパティです。 -> Note|注意: 生成された URL からエントリスクリプト名を隠すためには、[[yii\web\UrlManager::showScriptName|showScriptName]] を false に設定するだけでなく、ウェブサーバを構成して、リクエストされた URL が PHP スクリプトを明示的に指定していない場合でも、正しい PHP スクリプトを特定出来るようにする必要があります。 +> Note: 生成された URL からエントリスクリプト名を隠すためには、[[yii\web\UrlManager::showScriptName|showScriptName]] を false に設定するだけでなく、ウェブサーバを構成して、リクエストされた URL が PHP スクリプトを明示的に指定していない場合でも、正しい PHP スクリプトを特定出来るようにする必要があります。 もしあなたが Apache ウェブサーバを使うつもりなら、[インストール](start-installation.md#recommended-apache-configuration) の節で説明されている推奨設定を参照することが出来ます。 @@ -273,7 +279,7 @@ URL 規則は、パターンがリクエストされた URL と合致する場 ] ``` -> Info|情報: 規則のパターンは URL のパス情報の部分との照合に使用されます。 +> Info: 規則のパターンは URL のパス情報の部分との照合に使用されます。 例えば、`/index.php/post/100?source=ad` のパス情報は `post/100` であり (先頭と末尾のスラッシュは無視します)、これは `post/(\d+)` というパターンに合致します。 URL 規則は、「パターン - ルート」のペアとして宣言する以外に、構成情報配列として宣言することも出来ます。 @@ -302,7 +308,7 @@ URL 規則は、パターンの中で `` の形式で指定さ ここで、`ParamName` はパラメータ名を指定し、`RegExp` はパラメータの値との照合に使われるオプションの正規表現を指定するものです。 `RegExp` が指定されていない場合は、パラメータの値がスラッシュを含まない文字列であるべきことを意味します。 -> Note|注意: 正規表現はパラメータに対してのみ指定できます。パターンの残りの部分はプレーンテキストとして解釈されます。 +> Note: 正規表現はパラメータに対してのみ指定できます。パターンの残りの部分はプレーンテキストとして解釈されます。 規則が URL の解析に使われるときには、URL の対応する部分に合致した値が、結び付けられたパラメータに入れられます。 そして、そのパラメータは、後に `request` アプリケーションコンポーネントによって、`$_GET` に入れられて利用できるようになります。 @@ -355,7 +361,7 @@ URL 規則のルートにはパラメータ名を埋め込むことが出来ま 同じように、`comment/index` というルートの URL を生成するためには、三番目の規則が適用されて、`index.php/comments` という URL が生成されます。 -> Info|情報: ルートをパラメータ化することによって、URL 規則の数を大幅に減らすことが可能になり、[[yii\web\UrlManager|URL マネージャ]] のパフォーマンスを目に見えて改善することが出来ます。 +> Info: ルートをパラメータ化することによって、URL 規則の数を大幅に減らすことが可能になり、[[yii\web\UrlManager|URL マネージャ]] のパフォーマンスを目に見えて改善することが出来ます。 デフォルトでは、規則の中で宣言されたパラメータは必須となります。 リクエストされた URL が特定のパラメータを含まない場合や、特定のパラメータなしで URL を生成する場合には、規則は適用されません。 @@ -407,7 +413,7 @@ URL 規則のパターンには、ウェブサーバ名を含むことが出来 ] ``` -> Note|注意: サーバ名を持つ規則は、エントリスクリプトのサブフォルダをパターンに含むべきではありません。 +> Note: サーバ名を持つ規則は、エントリスクリプトのサブフォルダをパターンに含むべきではありません。 例えば、アプリケーションが `http://www.example.com/sandbox/blog` の下にある場合は、`http://www.example.com/sandbox/blog/posts` ではなく、`http://www.example.com/posts` というパターンを使うべきです。 こうすれば、アプリケーションをどのようなディレクトリに配置しても、アプリケーションのコードを変更する必要がなくなります。 @@ -437,9 +443,9 @@ URL 規則のパターンには、ウェブサーバ名を含むことが出来 上記の構成によって、[[yii\web\UrlManager|URL マネージャ]] は、接尾辞として `.html` の付いた URL を認識し、また、生成するようになります。 -> Tip|ヒント: URL が全てスラッシュで終るようにするためには、URL 接尾辞として `/` を設定することが出来ます。 +> Tip: URL が全てスラッシュで終るようにするためには、URL 接尾辞として `/` を設定することが出来ます。 -> Note|注意: URL 接尾辞を構成すると、リクエストされた URL が接尾辞を持たない場合は、認識できない URL であると見なされるようになります。 +> Note: URL 接尾辞を構成すると、リクエストされた URL が接尾辞を持たない場合は、認識できない URL であると見なされるようになります。 SEO の目的からも、これが推奨されるプラクティスです。 場合によっては、URL によって異なる接尾辞を使いたいことがあるでしょう。 @@ -485,10 +491,10 @@ RESTful API を実装するときは、使用されている HTTP メソッド ] ``` -> Note|注意: URL 規則が HTTP メソッドをパターンに含む場合、その規則は解析目的にだけ使用されます。 +> Note: URL 規則が HTTP メソッドをパターンに含む場合、その規則は解析目的にだけ使用されます。 [[yii\web\UrlManager|URL マネージャ]] が URL 生成のために呼ばれたときは、その規則はスキップされます。 -> Tip|ヒント: RESTful API のルーティングを簡単にするために、Yii は特別な URL 規則クラス [[yii\rest\UrlRule]] を提供しています。 +> Tip: RESTful API のルーティングを簡単にするために、Yii は特別な URL 規則クラス [[yii\rest\UrlRule]] を提供しています。 これは非常に効率的なもので、コントローラ ID の自動的な複数形化など、いくつかの素敵な機能をサポートするものです。 詳細については、RESTful API 開発についての [ルーティング](rest-routing.md) の節を参照してください。 @@ -512,7 +518,7 @@ RESTful API を実装するときは、使用されている HTTP メソッド ] ``` -> Info|情報: 規則の構成情報で `class` を指定しない場合は、デフォルトとして、[[yii\web\UrlRule]] クラスが使われます。 +> Info: 規則の構成情報で `class` を指定しない場合は、デフォルトとして、[[yii\web\UrlRule]] クラスが使われます。 ### 規則を動的に追加する diff --git a/docs/guide-ja/runtime-sessions-cookies.md b/docs/guide-ja/runtime-sessions-cookies.md index 05e68291e0..7103e09784 100644 --- a/docs/guide-ja/runtime-sessions-cookies.md +++ b/docs/guide-ja/runtime-sessions-cookies.md @@ -67,7 +67,7 @@ foreach ($session as $name => $value) ... foreach ($_SESSION as $name => $value) ... ``` -> Info|情報: セッションデータに `session` コンポーネントによってアクセスする場合は、まだ開かれていないときは、自動的にセッションが開かれます。 +> Info: セッションデータに `session` コンポーネントによってアクセスする場合は、まだ開かれていないときは、自動的にセッションが開かれます。 これに対して `$_SESSION` によってセッションデータにアクセスする場合は、`session_start()` を明示的に呼び出すことが必要になります。 配列であるセッションデータを扱う場合、`session` コンポーネントには、配列の要素を直接修正することができない、という制約があります。例えば、 @@ -132,7 +132,7 @@ Yii は、また、さまざまなセッションストレージを実装する これらのセッションクラスは全て一連の同じ API メソッドをサポートします。 その結果として、セッションを使用するアプリケーションコードを修正することなしに、セッションストレージクラスを切り替えることが出来ます。 -> Note|注意: カスタムセッションストレージを使っているときに `$_SESSION` を通じてセッションデータにアクセスしたい場合は、セッションが [[yii\web\Session::open()]] によって既に開始されていることを確認しなければなりません。 +> Note: カスタムセッションストレージを使っているときに `$_SESSION` を通じてセッションデータにアクセスしたい場合は、セッションが [[yii\web\Session::open()]] によって既に開始されていることを確認しなければなりません。 これは、カスタムセッションストレージのハンドラは、この `open()` メソッドの中で登録されるからです。 これらのコンポーネントクラスの構成方法と使用方法については、それらの API ドキュメントを参照してください。 @@ -167,7 +167,7 @@ CREATE TABLE session - PostgreSQL: BYTEA - MSSQL: BLOB -> Note|注意: php.ini の `session.hash_function` の設定によっては、`id` カラムの長さを修正する必要があるかも知れません。 +> Note: php.ini の `session.hash_function` の設定によっては、`id` カラムの長さを修正する必要があるかも知れません。 例えば、`session.hash_function=sha256` である場合は、40 の代りに 64 の長さを使わなければなりません。 @@ -214,13 +214,17 @@ $session->addFlash('alerts', 'あなたのレベルが上りました。'); $alerts = $session->getFlash('alerts'); ``` -> Note|注意: 同じ名前のフラッシュデータに対して、[[yii\web\Session::setFlash()]] と [[yii\web\Session::addFlash()]] を一緒に使わないようにしてください。 +> Note: 同じ名前のフラッシュデータに対して、[[yii\web\Session::setFlash()]] と [[yii\web\Session::addFlash()]] を一緒に使わないようにしてください。 これは、後者のメソッドが、同じ名前のフラッシュデータを追加できるように、フラッシュデータを自動的に配列に変換するからです。 その結果、[[yii\web\Session::getFlash()]] を呼び出したとき、この二つのメソッドの呼び出し順によって、あるときは配列を受け取り、あるときは文字列を受け取るということになってしまいます。 +<<<<<<< HEAD <<<<<<< HEAD ======= > Tip|ヒント: フラッシュメッセージを表示するためには、[[yii\bootstrap\Alert|bootstrap Alert]] ウィジェットを次のように使用することが出来ます。 +======= +> Tip: フラッシュメッセージを表示するためには、[[yii\bootstrap\Alert|bootstrap Alert]] ウィジェットを次のように使用することが出来ます。 +>>>>>>> master > > ```php > echo Alert::widget([ @@ -229,7 +233,10 @@ $alerts = $session->getFlash('alerts'); > ]); > ``` +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ## クッキー @@ -237,6 +244,8 @@ Yii は個々のクッキーを [[yii\web\Cookie]] のオブジェクトとし [[yii\web\Request]] と [[yii\web\Response]] は、ともに、`cookies` という名前のプロパティによって、クッキーのコレクションを保持します。 後者のクッキーコレクションはリクエストの中で送信されてきたクッキーを表し、一方、後者のクッキーコレクションは、これからユーザに送信されるクッキーを表します。 +アプリケーションで、リクエストとレスポンスを直接に操作する部分は、コントローラです。 +従って、クッキーの読み出しと送信はコントローラで実行されるべきです。 ### クッキーを読み出す @@ -289,7 +298,7 @@ unset($cookies['language']); や [[yii\web\Cookie::expire|expire]] など、他のプロパティを定義して、利用可能なクッキー情報の全てを完全に表しています。 クッキーを準備するときに必要に応じてこれらのプロパティを構成してから、レスポンスのクッキーコレクションに追加することが出来ます。 -> Note|注意: セキュリティを向上させるために、[[yii\web\Cookie::httpOnly]] のデフォルト値は true に設定されています。 +> Note: セキュリティを向上させるために、[[yii\web\Cookie::httpOnly]] のデフォルト値は true に設定されています。 これは、クライアントサイドスクリプトが保護されたクッキーにアクセスする危険を軽減するものです (ブラウザがサポートしていれば)。 詳細については、[httpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) を読んでください。 @@ -300,14 +309,14 @@ unset($cookies['language']); アプリケーションは、サインを見て、クッキーがクライアントサイドで修正されたかどうかを知ることが出来ます。 もし、修正されていれば、そのクッキーは `request` コンポーネントの [[yii\web\Request::cookies|クッキーコレクション]] からはアクセスすることが出来なくなります。 -> Note|注意: クッキー検証は値が修正されたクッキーの読み込みを防止するだけです。 +> Note: クッキー検証は値が修正されたクッキーの読み込みを防止するだけです。 検証に失敗した場合でも、`$_COOKIE` を通じてそのクッキーにアクセスすることは引き続いて可能です。 これは、サードパーティのライブラリが、クッキー検証を含まない独自の方法でクッキーを操作することが出来るようにするするためです。 クッキー検証はデフォルトで有効になっています。 [[yii\web\Request::enableCookieValidation]] プロパティを false に設定することによって無効にすることが出来ますが、無効にしないことを強く推奨します。 -> Note|注意: `$_COOKIE` と `setcookie()` によって直接に 読み出し/送信 されるクッキーは検証されません。 +> Note: `$_COOKIE` と `setcookie()` によって直接に 読み出し/送信 されるクッキーは検証されません。 クッキー検証を使用する場合は、前述のハッシュ文字列を生成するために使用される [[yii\web\Request::cookieValidationKey]] を指定しなければなりません。 アプリケーションの構成情報で `request` コンポーネントを構成することによって、そうすることが出来ます。 @@ -322,5 +331,5 @@ return [ ]; ``` -> Info|情報: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] は、あなたのアプリケーションにとって、決定的に重要なものです。 +> Info: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] は、あなたのアプリケーションにとって、決定的に重要なものです。 これは信頼する人にだけ教えるべきものです。バージョンコントロールシステムに保存してはいけません。 diff --git a/docs/guide-ja/security-auth-clients.md b/docs/guide-ja/security-auth-clients.md deleted file mode 100644 index 92ffe86077..0000000000 --- a/docs/guide-ja/security-auth-clients.md +++ /dev/null @@ -1,377 +0,0 @@ -認証クライアント -================ - -Yii は、[OpenID](http://openid.net/)、[OAuth](http://oauth.net/) または [OAuth2](http://oauth.net/2/) のコンシューマとして、外部サービスを使用して認証 および/または 権限付与を行うことを可能にする公式エクステンションを提供しています。 - -エクステンションをインストールする ---------------------------------- - -エクステンションをインストールするためには、Composer を使います。次のコマンドを実行します。 - -``` -composer require --prefer-dist yiisoft/yii2-authclient "*" -``` - -または、あなたの composer.json の `require` セクションに次の行を追加します。 - -```json -"yiisoft/yii2-authclient": "*" -``` - -クライアントを構成する ----------------------- - -エクステンションがインストールされた後に、認証クライアントコレクションのアプリケーションコンポーネントをセットアップする必要があります。 - -```php -'components' => [ - 'authClientCollection' => [ - 'class' => 'yii\authclient\Collection', - 'clients' => [ - 'google' => [ - 'class' => 'yii\authclient\clients\GoogleOpenId' - ], - 'facebook' => [ - 'class' => 'yii\authclient\clients\Facebook', - 'clientId' => 'facebook_client_id', - 'clientSecret' => 'facebook_client_secret', - ], - // etc. - ], - ] - ... -] -``` - -特別な設定なしに使用できる次のクライアントが提供されています。 - -- [[\yii\authclient\clients\Facebook|Facebook]] -- [[yii\authclient\clients\GitHub|GitHub]] -- Google ([[yii\authclient\clients\GoogleOpenId|OpenID]] または [[yii\authclient\clients\GoogleOAuth|OAuth]] で) -- [[yii\authclient\clients\LinkedIn|LinkedIn]] -- [[yii\authclient\clients\Live|Microsoft Live]] -- [[yii\authclient\clients\Twitter|Twitter]] -- [[yii\authclient\clients\VKontakte|VKontakte]] -- Yandex ([[yii\authclient\clients\YandexOpenId|OpenID]] または [[yii\authclient\clients\YandexOAuth|OAuth]] で) - -それぞれのクライアントの構成は少しずつ異なります。 -OAuth では、使おうとしているサービスからクライアント ID と秘密キーを取得することが必要です。 -OpenID では、たいていの場合、何も設定しなくても動作します。 - - -認証データを保存する --------------------- - -外部サービスによって認証されたユーザを認識するために、最初の認証のときに提供された ID を保存し、以後の認証のときにはそれをチェックする必要があります。 -ログインのオプションを外部サービスに限定するのは良いアイデアではありません。 -外部サービスによる認証が失敗して、ユーザがログインする方法がなくなるかもしれないからです。 -そんなことはせずに、外部認証と昔ながらのパスワードによるログインの両方を提供する方が適切です。 - -ユーザの情報をデータベースに保存しようとする場合、スキーマは次のようなものになります。 - -```sql -CREATE TABLE user ( - id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, - username varchar(255) NOT NULL, - auth_key varchar(32) NOT NULL, - password_hash varchar(255) NOT NULL, - password_reset_token varchar(255), - email varchar(255) NOT NULL, - status smallint(6) NOT NULL DEFAULT 10, - created_at int(11) NOT NULL, - updated_at int(11) NOT NULL -); - -CREATE TABLE auth ( - id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id int(11) NOT NULL, - source string(255) NOT NULL, - source_id string(255) NOT NULL -); - -ALTER TABLE auth ADD CONSTRAINT fk-auth-user_id-user-id -FOREIGN KEY user_id REFERENCES auth(id); -``` - -上記の SQL における `user` は、アドバンストアプリケーションテンプレートでユーザ情報を保存するために使われている標準的なテーブルです。 -全てのユーザはそれぞれ複数の外部サービスを使って認証できますので、全ての `user` レコードはそれぞれ複数の `auth` レコードと関連を持ち得ます。 -`auth` テーブルにおいて `source` は使用される認証プロバイダの名前であり、`source_id` はログイン成功後に外部サービスから提供される一意のユーザ識別子です。 - -上記で作成されたテーブルを使って `Auth` モデルを生成することが出来ます。これ以上の修正は必要ありません。 - - -コントローラにアクションを追加する ----------------------------------- - -次のステップでは、ウェブのコントローラ、典型的には `SiteController` に [[yii\authclient\AuthAction]] を追加します。 - -```php -class SiteController extends Controller -{ - public function actions() - { - return [ - 'auth' => [ - 'class' => 'yii\authclient\AuthAction', - 'successCallback' => [$this, 'onAuthSuccess'], - ], - ]; - } - - public function onAuthSuccess($client) - { - $attributes = $client->getUserAttributes(); - - /** @var Auth $auth */ - $auth = Auth::find()->where([ - 'source' => $client->getId(), - 'source_id' => $attributes['id'], - ])->one(); - - if (Yii::$app->user->isGuest) { - if ($auth) { // ログイン - $user = $auth->user; - Yii::$app->user->login($user); - } else { // ユーザ登録 - if (User::find()->where(['email' => $attributes['email']])->exists()) { - Yii::$app->getSession()->setFlash('error', [ - Yii::t('app', "{client} のアカウントと同じメールアドレスを持つユーザが既に存在しますが、まだそのアカウントとリンクされていません。リンクするために、まずメールアドレスを使ってログインしてください。", ['client' => $client->getTitle()]), - ]); - } else { - $password = Yii::$app->security->generateRandomString(6); - $user = new User([ - 'username' => $attributes['login'], - 'email' => $attributes['email'], - 'password' => $password, - ]); - $user->generateAuthKey(); - $user->generatePasswordResetToken(); - $transaction = $user->getDb()->beginTransaction(); - if ($user->save()) { - $auth = new Auth([ - 'user_id' => $user->id, - 'source' => $client->getId(), - 'source_id' => (string)$attributes['id'], - ]); - if ($auth->save()) { - $transaction->commit(); - Yii::$app->user->login($user); - } else { - print_r($auth->getErrors()); - } - } else { - print_r($user->getErrors()); - } - } - } - } else { // ユーザは既にログインしている - if (!$auth) { // 認証プロバイダを追加 - $auth = new Auth([ - 'user_id' => Yii::$app->user->id, - 'source' => $client->getId(), - 'source_id' => $attributes['id'], - ]); - $auth->save(); - } - } - } -} -``` - -外部サービスによるユーザの認証が成功すると `successCallback` メソッドが呼ばれます。 -`$client` インスタンスを通じて、外部サービスから受け取った情報を取得することが出来ます。 -私たちの例では、次のことをしようとしています。 - -- ユーザがゲストであり、auth にレコードが見つかった場合は、そのユーザをログインさせる。 -- ユーザがゲストであり、auth にレコードが見つからなかった場合は、新しいユーザを作成して、auth テーブルにレコードを作成する。そして、ログインさせる。 -- ユーザがログインしており、auth にレコードが見つからなかった場合は、追加のアカウントにも接続するようにする (そのデータを auth テーブルに保存する)。 - -全ての Auth クライアントには違いがありますが、同じインタフェイス [[yii\authclient\ClientInterface]] を共有し、共通の API によって管理されます。 - -各クライアントは、異なる目的に使用できるいくつかの説明的なデータを持っています。 - -- `id` - クライアントを他のクライアントから区別する一意の ID。 - URL やログに使うことが出来ます。 -- `name` - このクライアントが属する外部認証プロバイダの名前。 - 認証クライアントが異なっても、同じ外部認証プロバイダを参照している場合は、同じ名前になることがあります。 - 例えば、Google OpenID のクライアントと Google OAuth のクライアントは同じ名前 "google" を持ちます。 - この属性は内部的にデータベースや CSS スタイルなどにおいて使用することが出来ます。 -- `title` - 外部認証プロバイダのユーザフレンドリな名前。ビューのレイヤにおいて認証クライアントを表示するのに使用されます。 - -それぞれの認証クライアントは異なる認証フローを持ちますが、すべてのものが `getUserAttributes()` メソッドをサポートしており、認証が成功した後にこのメソッドを呼び出すことが出来ます。 - -このメソッドによって、外部のユーザアカウントの情報、例えば、ID、メールアドレス、フルネーム、優先される言語などを取得することが出来ます。 -ただし、プロバイダごとに利用できるフィールドの有無や名前が異なることに注意してください。 - -外部認証プロバイダが返すべき属性を定義するリストは、クライアントのタイプに依存します。 - -- [[yii\authclient\OpenId]]: `requiredAttributes` と `optionalAttributes` の組み合わせ。 -- [[yii\authclient\OAuth1]] と [[yii\authclient\OAuth2]]: `scope` フィールド。 - プロバイダによってスコープの形式が異なることに注意。 - -### API 呼び出しによって追加のデータを取得する - -[[yii\authclient\OAuth1]] と [[yii\authclient\OAuth2]] は、ともに、`api()` メソッドをサポートしており、これによって外部認証プロバイダの REST API にアクセスすることが出来ます。 -ただし、このメソッドは非常に基本的なもので、外部 API の完全な機能にアクセスするためには、十分なものではありません。 -このメソッドは、主として、外部のユーザアカウントの情報を取得するために使用されます。 - -API の呼び出しを使用するためには、API の仕様に従って [[yii\authclient\BaseOAuth::apiBaseUrl]] をセットアップする必要があります。 -そうすれば [[yii\authclient\BaseOAuth::api()]] メソッドを呼ぶことが出来ます。 - -```php -use yii\authclient\OAuth2; - -$client = new OAuth2; - -// ... - -$client->apiBaseUrl = 'https://www.googleapis.com/oauth2/v1'; -$userInfo = $client->api('userinfo', 'GET'); -``` - -ログインビューにウィジェットを追加する --------------------------------------- - -そのまま使える [[yii\authclient\widgets\AuthChoice]] ウィジェットをビューで使用することが出来ます。 - -```php - ['site/auth'], - 'popupMode' => false, -]) ?> -``` - -あなた自身の認証クライアントを作成する --------------------------------------- - -どの外部認証プロバイダでも、あなた自身の認証クライアントを作成して、OpenID または OAuth プロトコルをサポートすることが出来ます。 -そうするためには、最初に、外部認証プロバイダによってどのプロトコルがサポートされているかを見出す必要があります。 -それによって、あなたのエクステンションの基底クラスの名前が決ります。 - - - OAuth 2 のためには [[yii\authclient\OAuth2]] を使います。 - - OAuth 1/1.0a のためには [[yii\authclient\OAuth1]] を使います。 - - OpenID のためには [[yii\authclient\OpenId]] を使います。 - -この段階で、対応するメソッドを宣言することによって、認証クライアントのデフォルトの名前、タイトル、および、ビューオプションを決定することが出来ます。 - -```php -use yii\authclient\OAuth2; - -class MyAuthClient extends OAuth2 -{ - protected function defaultName() - { - return 'my_auth_client'; - } - - protected function defaultTitle() - { - return 'My Auth Client'; - } - - protected function defaultViewOptions() - { - return [ - 'popupWidth' => 800, - 'popupHeight' => 500, - ]; - } -} -``` - -使用する基底クラスによって、宣言し直さなければならないフィールドやメソッドが異なります。 - -### [[yii\authclient\OpenId]] - -必要なことは、`authUrl` フィールドを宣言し直して URL を指定することだけです。 -デフォルトの 必須属性 および/または オプション属性を設定することも可能です。 -例えば、 - -```php -use yii\authclient\OpenId; - -class MyAuthClient extends OpenId -{ - public $authUrl = 'https://www.my.com/openid/'; - - public $requiredAttributes = [ - 'contact/email', - ]; - - public $optionalAttributes = [ - 'namePerson/first', - 'namePerson/last', - ]; -} -``` - -### [[yii\authclient\OAuth2]] - -以下のものを指定する必要があります。 - -- 認証 URL - `authUrl` フィールド。 -- トークンリクエスト URL - `tokenUrl` フィールド。 -- API のベース URL - `apiBaseUrl` フィールド。 -- ユーザ属性取得ストラテジー - `initUserAttributes()` メソッド。 - -例えば、 - -```php -use yii\authclient\OAuth2; - -class MyAuthClient extends OAuth2 -{ - public $authUrl = 'https://www.my.com/oauth2/auth'; - - public $tokenUrl = 'https://www.my.com/oauth2/token'; - - public $apiBaseUrl = 'https://www.my.com/apis/oauth2/v1'; - - protected function initUserAttributes() - { - return $this->api('userinfo', 'GET'); - } -} -``` - -デフォルトの auth スコープを指定することも出来ます。 - -> Note|注意: OAuth プロバイダの中には、OAuth の標準を厳格に遵守せず、標準と異なる仕様を導入しているものもあります。 - そのようなものに対してクライアントを実装するためには、追加の労力が必要になることがあります。 - -### [[yii\authclient\OAuth1]] - -以下のものを指定する必要があります。 - -- 認証 URL - `authUrl` フィールド。 -- リクエストトークン URL - `requestTokenUrl` フィールド。 -- アクセストークン URL - `accessTokenUrl` フィールド。 -- API のベース URL - `apiBaseUrl` フィールド。 -- ユーザ属性取得ストラテジー - `initUserAttributes()` メソッド。 - -例えば、 - -```php -use yii\authclient\OAuth1; - -class MyAuthClient extends OAuth1 -{ - public $authUrl = 'https://www.my.com/oauth/auth'; - - public $requestTokenUrl = 'https://www.my.com/oauth/request_token'; - - public $accessTokenUrl = 'https://www.my.com/oauth/access_token'; - - public $apiBaseUrl = 'https://www.my.com/apis/oauth/v1'; - - protected function initUserAttributes() - { - return $this->api('userinfo', 'GET'); - } -} -``` - -デフォルトの auth スコープを指定することも出来ます。 - -> Note|注意: OAuth プロバイダの中には、OAuth の標準を厳格に遵守せず、標準と異なる仕様を導入しているものもあります。 - そのようなものに対してクライアントを実装するためには、追加の労力が必要になることがあります。 diff --git a/docs/guide-ja/security-authentication.md b/docs/guide-ja/security-authentication.md index 64e11eb825..2a74e1b1c1 100644 --- a/docs/guide-ja/security-authentication.md +++ b/docs/guide-ja/security-authentication.md @@ -1,23 +1,66 @@ 認証 ==== +<<<<<<< HEAD <<<<<<< HEAD > Note|注意: この節はまだ執筆中です。 +======= +認証は、ユーザが誰であるかを確認するプロセスです。 +通常は、識別子 (ユーザ名やメールアドレスなど) と秘密のトークン (パスワードやアクセストークンなど) を使って、ユーザがそうであると主張する通りのユーザであるか否かを判断します。 +認証がログイン機能の基礎となります。 +>>>>>>> master -認証はユーザが誰であるかを確認する行為であり、ログインプロセスの基礎となるものです。 -典型的には、認証は、識別子 (ユーザ名またはメールアドレス) とパスワードの組み合わせを使用します。 -ユーザはこれらの値をフォームを通じて送信し、アプリケーションは送信された情報を以前に (例えば、ユーザ登録時に) 保存された情報と比較します。 +Yii はさまざまなコンポーネントを結び付けてログインをサポートする認証フレームワークを提供しています。 +このフレームワークを使用するために、あなたは主として次の仕事をする必要があります。 -Yii では、このプロセス全体が半自動的に実行されます。 -開発者に残されているのは、認証システムにおいて最も重要なクラスである [[yii\web\IdentityInterface]] を実装することだけです。 -典型的には、`IdentityInterface` の実装は `User` モデルを使って達成されます。 +* [[yii\web\User|user]] アプリケーションコンポーネントを構成する。 +* [[yii\web\IdentityInterface]] インタフェイスを実装するクラスを作成する。 -十分な機能を有する認証の実例を [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) の中に見出すことが出来ます。 -下記にインターフェイスのメソッドだけをリストします。 + +## [[yii\web\User]] を構成する + +[[yii\web\User|user]] アプリケーションコンポーネントがユーザの認証状態を管理します。 +実際の認証ロジックを含む [[yii\web\User::identityClass|ユーザ識別情報クラス]] は、あなたが指定しなければなりません。 +下記のアプリケーション構成情報においては、[[yii\web\User|user]] の [[yii\web\User::identityClass|ユーザ識別情報クラス]] は `app\models\User` であると構成されています。 +`app\models\User` の実装については、次の項で説明します。 + +```php +return [ + 'components' => [ + 'user' => [ + 'identityClass' => 'app\models\User', + ], + ], +]; +``` + +## [[yii\web\IdentityInterface]] を実装する + +[[yii\web\User::identityClass|ユーザ識別情報クラス]] が実装しなければならない [[yii\web\IdentityInterface]] は次のメソッドを含んでいます。 + +* [[yii\web\IdentityInterface::findIdentity()|findIdentity()]]: 指定されたユーザ ID を使ってユーザ識別情報クラスのインスタンスを探します。 + セッションを通じてログイン状態を保持する必要がある場合に、このメソッドが使用されます。 +* [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]: 指定されたアクセストークンを使ってユーザ識別情報クラスのインスタンスを探します。 + 単一の秘密のトークンでユーザを認証する必要がある場合 (ステートレスな RESTful アプリケーションなどの場合) に、このメソッドが使用されます。 +* [[yii\web\IdentityInterface::getId()|getId()]]: ユーザ識別情報クラスのインスタンスによって表されるユーザの ID を返します。 +* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: クッキーベースのログインを検証するのに使用されるキーを返します。 + このキーがログインクッキーに保存され、後でサーバ側のキーと比較されて、ログインクッキーが有効であることが確認されます。 +* [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: クッキーベースのログインキーを検証するロジックを実装します。 + +特定のメソッドが必要でない場合は、中身を空にして実装しても構いません。 +例えば、あなたのアプリケーションが純粋なステートレス RESTful アプリケーションであるなら、実装する必要があるのは [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]] と [[yii\web\IdentityInterface::getId()|getId()]] だけであり、他のメソッドは全て中身を空にしておくことが出来ます。 + +次の例では、[[yii\web\User::identityClass|ユーザ識別情報クラス]] は、`user` データベーステーブルと関連付けられた [アクティブレコード](db-active-record.md) クラスとして実装されています。 ```php +>>>>>> master public static function tableName() { return 'user'; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master /** * 与えられた ID によってユーザ識別情報を探す @@ -129,6 +177,7 @@ class User extends ActiveRecord implements IdentityInterface } ``` +<<<<<<< HEAD <<<<<<< HEAD 概要を述べたメソッドのうち、二つは単純なものです。 `findIdentity` は ID の値を受け取って、その ID と関連付けられたモデルのインスタンスを返します。 @@ -137,20 +186,30 @@ class User extends ActiveRecord implements IdentityInterface `getAuthKey` メソッドは全てのユーザに対してユニークな文字列を返さなければなりません。 `Yii::$app->getSecurity()->generateRandomString()` を使うと、信頼性の高い方法でユニークな文字列を生成することが出来ます。 これをユーザのレコードの一部として保存しておくのは良いアイデアです。 +======= +前述のように、`getAuthKey()` と `validateAuthKey()` は、あなたのアプリケーションがクッキーベースのログイン機能を使用する場合にのみ実装する必要があります。 +この場合、次のコードを使って、各ユーザに対して認証キーを生成して、`user` テーブルに保存しておくことが出来ます。 +>>>>>>> master ```php -public function beforeSave($insert) +class User extends ActiveRecord implements IdentityInterface { - if (parent::beforeSave($insert)) { - if ($this->isNewRecord) { - $this->auth_key = Yii::$app->getSecurity()->generateRandomString(); + ...... + + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + if ($this->isNewRecord) { + $this->auth_key = \Yii::$app->security->generateRandomString(); + } + return true; } - return true; + return false; } - return false; } ``` +<<<<<<< HEAD `validateAuthKey` メソッドでは、パラメータとして渡された `$authKey` 変数 (これ自体はクッキーから読み出されます) をデータベースから読み出された値と比較する必要があるだけです。 ======= 前述のように、`getAuthKey()` と `validateAuthKey()` は、あなたのアプリケーションがクッキーベースのログイン機能を使用する場合にのみ実装する必要があります。 @@ -175,6 +234,9 @@ class User extends ActiveRecord implements IdentityInterface ``` > Note|注意: ユーザ識別情報クラスである `User` と [[yii\web\User]] を混同してはいけません。 +======= +> Note: ユーザ識別情報クラスである `User` と [[yii\web\User]] を混同してはいけません。 +>>>>>>> master 前者は認証のロジックを実装するクラスであり、普通は、ユーザの認証情報を保存する何らかの持続的ストレージと関連付けられた [アクティブレコード](db-active-record.md) クラスとして実装されます。 後者はユーザの認証状態の管理に責任を持つアプリケーションコンポーネントです。 @@ -241,4 +303,7 @@ Yii::$app->user->logout(); これらのイベントに反応して、ログイン監査、オンラインユーザ統計などの機能を実装することが出来ます。 例えば、[[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]] のハンドラの中で、`user` テーブルにログインの日時と IP アドレスを記録することが出来ます。 +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide-ja/security-authorization.md b/docs/guide-ja/security-authorization.md index b9cde248e4..cbdaaee2f1 100644 --- a/docs/guide-ja/security-authorization.md +++ b/docs/guide-ja/security-authorization.md @@ -1,8 +1,6 @@ 権限付与 ======== -> Note|注意: この節はまだ執筆中です。 - 権限付与は、ユーザが何かをするのに十分な許可を有しているか否かを確認するプロセスです。 Yii は二つの権限付与の方法を提供しています。すなわち、アクセス制御フィルタ (ACF) と、ロールベースアクセス制御 (RBAC) です。 @@ -10,13 +8,14 @@ Yii は二つの権限付与の方法を提供しています。すなわち、 アクセス制御フィルタ (ACF) -------------------------- -アクセス制御フィルタ (ACF) は、何らかの単純なアクセス制御だけを必要とするアプリケーションで使うのに最も適した、単純な権限付与の方法です。 -その名前が示すように、ACF は、コントローラまたはモジュールにビヘイビアとしてアタッチすることが出来るアクションフィルタです。 -ACF は一連の [[yii\filters\AccessControl::rules|アクセス規則]] をチェックして、現在のユーザがリクエストしたアクションにアクセスすることが出来るかどうかを確認します。 +アクセス制御フィルタ (ACF) は、[[yii\filters\AccessControl]] として実装される単純な権限付与の方法であり、何らかの単純なアクセス制御だけを必要とするアプリケーションで使うのに最も適したものです。 +その名前が示すように、ACF は、コントローラまたはモジュールで使用することが出来るアクション [フィルタ](structure-filters.md) です。 +ACF は、ユーザがアクションの実行をリクエストしたときに、一連の [[yii\filters\AccessControl::rules|アクセス規則]] をチェックして、現在のユーザがそのアクションにアクセスする許可を持つかどうかを決定します。 -下記のコードは、[[yii\filters\AccessControl]] として実装された ACF の使い方を示すものです。 +下記のコードは、`site` コントローラで ACF を使う方法を示すものです。 ```php +use yii\web\Controller; use yii\filters\AccessControl; class SiteController extends Controller @@ -49,28 +48,29 @@ class SiteController extends Controller 上記のコードにおいて、ACF は `site` コントローラにビヘイビアとしてアタッチされています。 これがアクションフィルタを使用する典型的な方法です。 `only` オプションは、ACF が `login`、`logout`、`signup` のアクションにのみ適用されるべきであることを指定しています。 +`site` コントローラの他の全てのアクションには ACF の影響は及びません。 `rules` オプションは [[yii\filters\AccessRule|アクセス規則]] を指定するものであり、以下のように読むことが出来ます。 - 全てのゲストユーザ (まだ認証されていないユーザ) に、'login' と 'singup' のアクションにアクセスすることを許可します。 - `roles` オプションに疑問符 `?` が含まれていますが、これは「ゲスト」として認識される特殊なトークンです。 + `roles` オプションに疑問符 `?` が含まれていますが、これは「ゲスト」を表す特殊なトークンです。 - 認証されたユーザに、'logout' アクションにアクセスすることを許可します。 - `@` という文字はもう一つの特殊なトークンで、認証されたユーザとして認識されるものです。 + `@` という文字はもう一つの特殊なトークンで、「認証されたユーザ」を表すものです。 ACF が権限のチェックを実行するときには、規則を一つずつ上から下へ、適用されるものを見つけるまで調べます。 そして、適用される規則の `allow` の値が、ユーザが権限を有するか否かを判断するのに使われます。 適用される規則が一つもなかった場合は、ユーザが権限をもたないことを意味し、ACF はアクションの継続を中止します。 -デフォルトでは、ユーザが現在のアクションにアクセスする権限を持っていないと判定した場合は、ACF は以下のことだけを行います。 +ユーザが現在のアクションにアクセスする権限を持っていないと判定した場合は、デフォルトでは、ACF は以下の手段を取ります。 -* ユーザがゲストである場合は、[[yii\web\User::loginRequired()]] を呼び出します。 - このメソッドで、ブラウザをログインページにリダイレクトすることが出来ます。 +* ユーザがゲストである場合は、[[yii\web\User::loginRequired()]] を呼び出して、ユーザのブラウザをログインページにリダイレクトします。 * ユーザが既に認証されている場合は、[[yii\web\ForbiddenHttpException]] を投げます。 -この動作は、[[yii\filters\AccessControl::denyCallback]] プロパティを構成することによって、カスタマイズすることが出来ます。 +この動作は、次のように、[[yii\filters\AccessControl::denyCallback]] プロパティを構成することによって、カスタマイズすることが出来ます。 ```php [ 'class' => AccessControl::className(), + ... 'denyCallback' => function ($rule, $action) { throw new \Exception('このページにアクセスする権限がありません。'); } @@ -90,6 +90,7 @@ ACF が権限のチェックを実行するときには、規則を一つずつ * [[yii\filters\AccessRule::controllers|controllers]]: どのコントローラにこの規則が適用されるかを指定します。 これはコントローラ ID の配列でなければなりません。 +コントローラがモジュールに属する場合は、モジュール ID をコントローラ ID の前に付けます。 比較は大文字と小文字を区別します。 このオプションが空であるか指定されていない場合は、規則が全てのコントローラに適用されることを意味します。 @@ -100,7 +101,7 @@ ACF が権限のチェックを実行するときには、規則を一つずつ - `?`: ゲストユーザ (まだ認証されていないユーザ) を意味します。 - `@`: 認証されたユーザを意味します。 - その他のロール名を使う場合には、RBAC (次の節で説明します) が必要とされ、判断のために [[yii\web\User::can()]] が呼び出されます。 + その他のロール名を使うと、[[yii\web\User::can()]] の呼び出しが惹起されますが、そのためには、RBAC (次の節で説明します) を有効にする必要があります。 このオプションが空であるか指定されていない場合は、規則が全てのロールに適用されることを意味します。 * [[yii\filters\AccessRule::ips|ips]]: どの [[yii\web\Request::userIP|クライアントの IP アドレス]] にこの規則が適用されるかを指定します。 @@ -151,8 +152,7 @@ class SiteController extends Controller ``` -ロールベースアクセス制御 (RBAC) ---------------------------------------- +## ロールベースアクセス制御 (RBAC) ロールベースアクセス制御 (RBAC) は、単純でありながら強力な集中型のアクセス制御を提供します。 RBAC と他のもっと伝統的なアクセス制御スキーマとの比較に関する詳細については、[Wiki 記事](http://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1) を参照してください。 @@ -166,7 +166,7 @@ RBAC を使用することには、二つの作業が含まれます。 説明を容易にするために、まず、いくつかの基本的な RBAC の概念を導入します。 -### 基本的な概念 +### 基本的な概念 ロール (役割) は、*許可* (例えば、記事を作成する、記事を更新するなど) のコレクションです。 一つのロールを一人または複数のユーザに割り当てることが出来ます。 @@ -183,14 +183,15 @@ Yii は、一般的な *半順序* 階層を実装していますが、これは ロールは許可を含むことが出来ますが、許可はロールを含むことが出来ません。 -### RBAC マネージャを構成する +### RBAC を構成する 権限付与データを定義してアクセスチェックを実行する前に、[[yii\base\Application::authManager|authManager]] アプリケーションコンポーネントを構成する必要があります。 Yii は二種類の権限付与マネージャを提供しています。すなわち、[[yii\rbac\PhpManager]] と [[yii\rbac\DbManager]] です。 前者は権限付与データを保存するのに PHP スクリプトファイルを使いますが、後者は権限付与データをデータベースに保存します。 あなたのアプリケーションが非常に動的なロールと許可の管理を必要とするのでなければ、前者を使うことを考慮するのが良いでしょう。 -#### authManager を `PhpManager` で構成する + +#### `PhpManager` を使用する 次のコードは、アプリケーションの構成情報で [[yii\rbac\PhpManager]] クラスを使って `authManager` を構成する方法を示すものです。 @@ -208,10 +209,11 @@ return [ これで `authManager` は `\Yii::$app->authManager` によってアクセスすることが出来るようになります。 -> Tip|ヒント: デフォルトでは、[[yii\rbac\PhpManager]] は RBAC データを `@app/rbac/` ディレクトリの下のファイルに保存します。 - 権限の階層をオンラインで変更する必要がある場合は、必ず、ウェブサーバのプロセスがこのディレクトリとその中の全てのファイルに対する書き込み権限を有するようにしてください。 +デフォルトでは、[[yii\rbac\PhpManager]] は RBAC データを `@app/rbac/` ディレクトリの下のファイルに保存します。 +権限の階層をオンラインで変更する必要がある場合は、必ず、ウェブサーバのプロセスがこのディレクトリとその中の全てのファイルに対する書き込み権限を有するようにしてください。 -#### authManager を `DbManager` で構成する + +#### `DbManager` を使用する 次のコードは、アプリケーションの構成情報で [[yii\rbac\DbManager]] クラスを使って `authManager` を構成する方法を示すものです。 @@ -228,6 +230,9 @@ return [ ]; ``` +> Note: yii2-basic-app テンプレートを使おうとする場合は、`config/web.php` に加えて、`config/console.php` 構成ファイルにおいても `uathManager` を宣言する必要があります。 +> yii2-advanced-app の場合は、`authManager` は `common/config/main.php` で一度だけ宣言されなければなりません。 + `DbManager` は四つのデータベーステーブルを使ってデータを保存します。 - [[yii\rbac\DbManager::$itemTable|itemTable]]: 権限アイテムを保存するためのテーブル。デフォルトは "auth_item"。 @@ -243,7 +248,7 @@ return [ これで `authManager` は `\Yii::$app->authManager` によってアクセスすることが出来るようになります。 -### 権限付与データを構築する +### 権限付与データを構築する 権限付与データを構築する作業は、つまるところ、以下のタスクに他なりません。 @@ -300,6 +305,9 @@ class RbacController extends Controller } ``` +> Note: アドバンストテンプレートを使おうとするときは、`RbacController` を `console/controllers` +ディレクトリの中に置いて、名前空間を `console/controllers` に変更する必要があります。 + `yii rbac/init` によってコマンドを実行した後には、次の権限階層が得られます。 ![単純な RBAC 階層](images/rbac-hierarchy-1.png "単純な RBAC 階層") @@ -308,10 +316,14 @@ class RbacController extends Controller あなたのアプリケーションがユーザ自身によるユーザ登録を許している場合は、新しく登録されたユーザに一度はロールを割り当てる必要があります。 <<<<<<< HEAD +<<<<<<< HEAD 例えば、アドバンストアプリケーションテンプレートにおいては、登録したユーザの全てを「投稿者」にするために、`frontend\models\SignupForm::signup()` を次のように修正しなければなりません。 ======= 例えば、アドバンストプロジェクトテンプレートにおいては、登録したユーザの全てを「投稿者」にするために、`frontend\models\SignupForm::signup()` を次のように修正しなければなりません。 >>>>>>> yiichina/master +======= +例えば、アドバンストプロジェクトテンプレートにおいては、登録したユーザの全てを「投稿者」にするために、`frontend\models\SignupForm::signup()` を次のように修正しなければなりません。 +>>>>>>> master ```php public function signup() @@ -339,7 +351,7 @@ public function signup() 動的に更新される権限付与データを持つ複雑なアクセス制御を必要とするアプリケーションについては、`authManager` が提供する API を使って、特別なユーザインタフェイス (つまり、管理パネル) を開発する必要があるでしょう。 -### 規則を使う +### 規則を使う 既に述べたように、規則がロールと許可に制約を追加します。 規則は [[yii\rbac\Rule]] を拡張したクラスであり、[[yii\rbac\Rule::execute()|execute()]] メソッドを実装しなければなりません。 @@ -398,7 +410,7 @@ $auth->addChild($author, $updateOwnPost); ![規則を持つ RBAC 階層](images/rbac-hierarchy-2.png "規則を持つ RBAC 階層") -### アクセスチェック +### アクセスチェック 権限付与データが準備できてしまえば、アクセスチェックは [[yii\rbac\ManagerInterface::checkAccess()]] メソッドを呼ぶだけの簡単な仕事です。 たいていのアクセスチェックは現在のユーザに関するものですから、Yii は、便利なように、[[yii\web\User::can()]] というショートカットメソッドを提供しています。 @@ -410,7 +422,7 @@ if (\Yii::$app->user->can('createPost')) { } ``` -現在のユーザが ID=1 である Jane であるとすると、`createPost` からスタートして `Jane` まで到達しようと試みます。 +現在のユーザが `ID=1` である Jane であるとすると、`createPost` からスタートして `Jane` まで到達しようと試みます。 ![アクセスチェック](images/rbac-access-check-1.png "アクセスチェック") @@ -435,7 +447,7 @@ Jane の場合は、彼女が管理者であるため、少し簡単になりま ![アクセスチェック](images/rbac-access-check-3.png "アクセスチェック") -### デフォルトロールを使う +### デフォルトロールを使う デフォルトロールというのは、*全て* のユーザに *黙示的* に割り当てられるロールです。 [[yii\rbac\ManagerInterface::assign()]] を呼び出す必要はなく、権限付与データはその割り当て情報を含みません。 diff --git a/docs/guide-ja/security-best-practices.md b/docs/guide-ja/security-best-practices.md index 063f271cb2..35ada54fbe 100644 --- a/docs/guide-ja/security-best-practices.md +++ b/docs/guide-ja/security-best-practices.md @@ -136,16 +136,59 @@ CSRF は、クロスサイトリクエストフォージェリ (cross-site reque 例えば、`an.example.com` というウェブサイトが `/logout` という URL を持っており、この URL を単純な GET でアクセスするとユーザをログアウトさせるようになっているとします。 ユーザ自身によってこの URL がリクエストされる限りは何も問題はありませんが、ある日、悪い奴が、ユーザが頻繁に訪れるフォーラムに `` というリンクを含むコンテントを何とかして投稿することに成功します。 -ブラウザは画像のリクエストとページのリクエストの間に何ら区別を付けませんので、ユーザがそのような `img` タグを含むページを開くと `an.example.com` からログアウトされてしまうことになる訳です。 +ブラウザは画像のリクエストとページのリクエストの間に何ら区別を付けませんので、ユーザがそのような `img` タグを含むページを開くとブラウザはその URL に対して GET リクエストを送信します。 +そして、ユーザが `an.example.com` からログアウトされてしまうことになる訳です。 これは基本的な考え方です。ユーザがログアウトされるぐらいは大したことではない、と言うことも出来るでしょう。 -しかし、POST を送信することも、それほど難しくはありません。 +しかし、悪い奴は、この考え方を使って、もっとひどいことをすることも出来ます。 +例えば、`http://an.example.com/purse/transfer?to=anotherUser&amout=2000` という URL を持つウェブサイトがあると考えて見てください。 +この URL に GET リクエストを使ってアクセスすると、権限を持つユーザアカウントから `anotherUser` に $2000 が送金されるのです。 +私たちは、ブラウザは画像をロードするのに常に GET リクエストを使う、ということを知っていますから、この URL が POST リクエストだけを受け入れるようにコードを修正することは出来ます。 +しかし残念なことに、それで問題が解決する訳ではありません。 +攻撃者は `` タグの代りに何らかの JavaScript コードを書いて、その URL に対する POST リクエストの送信を可能にすることが出来ます。 -CSRF を回避するためには、次のことを守らなければなりません。 +CSRF を回避するためには、常に次のことを守らなければなりません。 1. HTTP の規格、すなわち、GET はアプリケーションの状態を変更すべきではない、という規則に従うこと。 2. Yii の CSRF 保護を有効にしておくこと。 +場合によっては、コントローラやアクションの単位で CSRF 検証を無効化する必要があることがあるでしょう。 +これは、そのプロパティを設定することによって達成することが出来ます。 + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionIndex() + { + // CSRF 検証はこのアクションおよびその他のアクションに対して適用されない + } + +} +``` + +特定のアクションに対して CSRF 検証を無効化したいときは、次のようにすることが出来ます。 +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function beforeAction($action) + { + // ... ここで何らかの条件に従って `$this->enableCsrfValidation` を設定する ... + // 親のメソッドを呼ぶ。プロパティが true であれば、その中で CSRF がチェックされる。 + return parent::beforeAction($action); + } +} +``` + ファイルの曝露を回避する ------------------------ @@ -169,3 +212,18 @@ Gii を使うと、データベース構造とコードに関する情報を得 デバッグツールバーは本当に必要でない限り本番環境では使用を避けるべきです。 これはアプリケーションと構成情報の全ての詳細を曝露することが出来ます。 どうしても必要な場合は、あなたの IP だけに適切にアクセス制限されていることを再度チェックしてください。 + +TLS によるセキュアな接続を使う +------------------------------ + +Yii が提供する機能には、クッキーや PHP セッションに依存するものがあります。 +これらのものは、接続が侵害された場合には、脆弱性となり得ます。 +アプリケーションが TLS によるセキュアな接続を使用している場合は、この危険性を減少させることが出来ます。 + +その設定の仕方については、あなたのウェブサーバのドキュメントの指示を参照してください。 +H5BP プロジェクトが提供する構成例を参考にすることも出来ます。 + +- [Nginx](https://github.com/h5bp/server-configs-nginx) +- [Apache](https://github.com/h5bp/server-configs-apache). +- [IIS](https://github.com/h5bp/server-configs-iis). +- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd). diff --git a/docs/guide-ja/security-cryptography.md b/docs/guide-ja/security-cryptography.md new file mode 100644 index 0000000000..fdba96515c --- /dev/null +++ b/docs/guide-ja/security-cryptography.md @@ -0,0 +1,68 @@ +暗号化 +====== + +個の節では、セキュリティの以下の側面について見ていきます。 + +- 乱数データの生成 +- 暗号化と複合化 +- データの完全性の確認 + + +擬似乱数データを生成する +------------------------ + +擬似乱数データはさまざまな状況で役に立ちます。 +例えば、メール経由でパスワードをリセットするときは、トークンを生成してデータベースに保存し、それをユーザにメールで送信します。 +そして、ユーザはこのトークンを自分がアカウントの所有者であることの証拠として使用します。 +このトークンがユニークかつ推測困難なものであることは非常に重要なことです。 +さもなくば、攻撃者がトークンの値を推測してユーザのパスワードをリセットする可能性があります。 + +Yii のセキュリティヘルパは擬似乱数データの生成を単純な作業にしてくれます。 + + +```php +$key = Yii::$app->getSecurity()->generateRandomString(); +``` + +暗号化と復号化 +-------------- + +Yii は秘密鍵を使ってデータを暗号化/復号化することを可能にする便利なヘルパ関数を提供しています。 +データを暗号化関数に渡して、秘密鍵を持つ者だけが復号化することが出来るようにすることが出来ます。 +例えば、何らかの情報をデータベースに保存する必要があるけれども、(たとえアプリケーションのデータベースが第三者に漏洩した場合でも) 秘密鍵を持つユーザだけがそれを見ることが出来るようにする必要がある、という場合には次のようにします。 + +```php +// $data と $secretKey はフォームから取得する +$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); +// $encryptedData をデータベースに保存する +``` + +そして、後でユーザがデータを読みたいときは、次のようにします。 + +```php +// $secretKey はユーザ入力から取得、$encryptedData はデータベースから取得 +$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); +``` + +[[\yii\base\Security::encryptByKey()]] と [[\yii\base\Security::decryptByKey()]] によって、パスワードの代わりにキーを使うことも可能です。 + + +データの完全性を確認する +------------------------ + +データが第三者によって改竄されたり、更には何らかの形で毀損されたりしていないことを確認する必要がある、という場合があります。 +Yii は二つのヘルパ関数の形で、データの完全性を確認するための簡単な方法を提供しています。 + +秘密鍵とデータから生成されたハッシュをデータにプレフィクスします。 + +```php +// $secretKey はアプリケーションまたはユーザの秘密、$genuineData は信頼できるソースから取得 +$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); +``` + +データの完全性が毀損されていないかチェックします。 + +```php +// $secretKey はアプリケーションまたはユーザの秘密、$data は信頼できないソースから取得 +$data = Yii::$app->getSecurity()->validateData($data, $secretKey); +``` diff --git a/docs/guide-ja/security-overview.md b/docs/guide-ja/security-overview.md new file mode 100644 index 0000000000..29c7d809cd --- /dev/null +++ b/docs/guide-ja/security-overview.md @@ -0,0 +1,14 @@ +セキュリティ +============ + +十分なセキュリティは、すべてのアプリケーションの健全さと成功のために欠くことが出来ないものです。 +不幸なことに、理解が不足しているためか、実装の難易度が高すぎるためか、セキュリティのことになると手を抜く開発者がたくさんいます。 +Yii によって駆動されるあなたのアプリケーションを可能な限り安全にするために、Yii はいくつかの優秀な使いやすいセキュリティ機能を内蔵しています。 + +* [認証](security-authentication.md) +* [権限付与](security-authorization.md) +* [パスワードを扱う](security-passwords.md) +* [暗号化](security-cryptography.md) +* [ビューのセキュリティ](structure-views.md#security) +* [認証クライアント](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide-ja/README.md) +* [ベストプラクティス](security-best-practices.md) diff --git a/docs/guide-ja/security-passwords.md b/docs/guide-ja/security-passwords.md index c7dbfe1327..b3aab85e90 100644 --- a/docs/guide-ja/security-passwords.md +++ b/docs/guide-ja/security-passwords.md @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD セキュリティ ============ ======= @@ -15,6 +16,10 @@ Yii によって駆動されるあなたのアプリケーションを可能な ハッシュとパスワードの検証 -------------------------- +======= +パスワードを扱う +================ +>>>>>>> master ほとんどの開発者はパスワードを平文テキストで保存してはいけないということを知っていますが、パスワードを `md5` や `sha1` でハッシュしてもまだ安全だと思っている開発者がたくさんいます。 かつては、前述のハッシュアルゴリズムを使えば十分であった時もありましたが、現代のハードウェアをもってすれば、そのようなハッシュはブルートフォースアタックを使って非常に簡単に復元することが可能です。 @@ -43,115 +48,3 @@ if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // パスワードが違う } ``` - -擬似乱数データを生成する ------------------------- - -擬似乱数データはさまざまな状況で役に立ちます。 -例えば、メール経由でパスワードをリセットするときは、トークンを生成してデータベースに保存し、それをユーザにメールで送信します。 -そして、ユーザはこのトークンを自分がアカウントの所有者であることの証拠として使用します。 -このトークンがユニークかつ推測困難なものであることは非常に重要なことです。 -さもなくば、攻撃者がトークンの値を推測してユーザのパスワードをリセットする可能性があります。 - -Yii のセキュリティヘルパは擬似乱数データの生成を単純な作業にしてくれます。 - - -```php -$key = Yii::$app->getSecurity()->generateRandomString(); -``` - -暗号論的に安全な乱数データを生成するためには、`openssl` 拡張をインストールしている必要があることに注意してください。 - -暗号化と復号化 --------------- - -Yii は秘密鍵を使ってデータを暗号化/復号化することを可能にする便利なヘルパ関数を提供しています。 -データを暗号化関数に渡して、秘密鍵を持つ者だけが復号化することが出来るようにすることが出来ます。 -例えば、何らかの情報をデータベースに保存する必要があるけれども、(たとえアプリケーションのデータベースが第三者に漏洩した場合でも) 秘密鍵を持つユーザだけがそれを見ることが出来るようにする必要がある、という場合には次のようにします。 - -```php -// $data と $secretKey はフォームから取得する -$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); -// $encryptedData をデータベースに保存する -``` - -そして、後でユーザがデータを読みたいときは、次のようにします。 - -```php -// $secretKey はユーザ入力から取得、$encryptedData はデータベースから取得 -$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); -``` - -データの完全性を確認する ------------------------- - -データが第三者によって改竄されたり、更には何らかの形で毀損されたりしていないことを確認する必要がある、という場合があります。 -Yii は二つのヘルパ関数の形で、データの完全性を確認するための簡単な方法を提供しています。 - -秘密鍵とデータから生成されたハッシュをデータにプレフィクスします。 - -```php -// $secretKey はアプリケーションまたはユーザの秘密、$genuineData は信頼できるソースから取得 -$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); -``` - -データの完全性が毀損されていないかチェックします。 - -```php -// $secretKey はアプリケーションまたはユーザの秘密、$data は信頼できないソースから取得 -$data = Yii::$app->getSecurity()->validateData($data, $secretKey); -``` - - -todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide - -プロパティを設定することによって、CSRF バリデーションをコントローラ および/または アクション単位で無効にすることも出来ます。 - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public $enableCsrfValidation = false; - - public function actionIndex() - { - // CSRF バリデーションはこのアクションおよびその他のアクションに適用されない - } - -} -``` - -特定のアクションに対して CSRF バリデーションを無効にするためには、次のようにします。 - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public function beforeAction($action) - { - // ... ここで、何らかの条件に基づいて `$this->enableCsrfValidation` をセットする ... - // 親のメソッドを呼ぶ。プロパティが true なら、その中で CSRF がチェックされる。 - return parent::beforeAction($action); - } -} -``` - -クッキーを安全にする --------------------- - -- validation -- httpOnly is default - -参照 ----- - -以下も参照してください。 - -- [ビューのセキュリティ](structure-views.md#security) - diff --git a/docs/guide-ja/start-databases.md b/docs/guide-ja/start-databases.md index dadd29a09e..f3fe172072 100644 --- a/docs/guide-ja/start-databases.md +++ b/docs/guide-ja/start-databases.md @@ -32,16 +32,16 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `country` VALUES ('AU','Australia',18886000); -INSERT INTO `country` VALUES ('BR','Brazil',170115000); -INSERT INTO `country` VALUES ('CA','Canada',1147000); -INSERT INTO `country` VALUES ('CN','China',1277558000); -INSERT INTO `country` VALUES ('DE','Germany',82164700); -INSERT INTO `country` VALUES ('FR','France',59225700); -INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `country` VALUES ('IN','India',1013662000); -INSERT INTO `country` VALUES ('RU','Russia',146934000); -INSERT INTO `country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` この時点で、あなたは `yii2basic` という名前のデータベースを持ち、その中に三つのカラムを持つ `country` というテーブルがあり、`country` テーブルは 10 行のデータを持っている、ということになります。 @@ -73,9 +73,14 @@ return [ 上記のようにして構成された DB 接続は、アプリケーションコードの中で `Yii::$app->db` という式でアクセスすることが出来ます。 -> Info|情報: `config/db.php` は、メインのアプリケーション構成情報ファイルである `config/web.php` によってインクルードされます。 +> Info: `config/db.php` は、メインのアプリケーション構成情報ファイルである `config/web.php` によってインクルードされます。 この `config/web.php` が [アプリケーション](structure-applications.md) インスタンスが初期化される仕方を指定するものです。 詳しい情報については、[構成情報](concept-configurations.md) の節を参照してください。 +Yii がサポートを内蔵していないデータベースを扱う必要がある場合は、以下のエクステンションの利用を検討してください。 + +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) アクティブレコードを作成する @@ -98,7 +103,7 @@ class Country extends ActiveRecord `Country` クラスは [[yii\db\ActiveRecord]] を拡張しています。この中には一つもコードを書く必要はありません。 単に上記のコードだけで、Yii は関連付けられたテーブル名をクラス名から推測します。 -> Info|情報: クラス名とテーブル名を直接に合致させることが出来ない場合は、[[yii\db\ActiveRecord::tableName()]] メソッドをオーバーライドして、関連づけられたテーブル名を明示的に指定することが出来ます。 +> Info: クラス名とテーブル名を直接に合致させることが出来ない場合は、[[yii\db\ActiveRecord::tableName()]] メソッドをオーバーライドして、関連づけられたテーブル名を明示的に指定することが出来ます。 `Country` クラスを使うことによって、以下のコード断片で示すように、`country` テーブルの中のデータを簡単に操作することが出来ます。 @@ -119,7 +124,7 @@ $country->name = 'U.S.A.'; $country->save(); ``` -> Info|情報: アクティブレコードは、オブジェクト指向の流儀でデータベースのデータにアクセスし、操作する強力な方法です。 +> Info: アクティブレコードは、オブジェクト指向の流儀でデータベースのデータにアクセスし、操作する強力な方法です。 [アクティブレコード](db-active-record.md) の節で、詳細な情報を得ることが出来ます。 もう一つの方法として、[データアクセスオブジェクト](db-dao.md) と呼ばれる、より低レベルなデータアクセス方法を使ってデータベースを操作することも出来ます。 @@ -216,7 +221,7 @@ use yii\widgets\LinkPager; 上記のコード全てがどのように動作するかを見るために、ブラウザで下記の URL をアクセスします。 ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` ![国リスト](images/start-country-list.png) @@ -227,7 +232,7 @@ http://hostname/index.php?r=country/index 注意深く観察すると、ブラウザの URL も次のように変ったことに気付くでしょう。 ``` -http://hostname/index.php?r=country/index&page=2 +http://hostname/index.php?r=country%2Findex&page=2 ``` 舞台裏では、[[yii\data\Pagination|Pagination]] が、データセットをページ付けするのに必要な全ての機能を提供しています。 @@ -248,6 +253,6 @@ http://hostname/index.php?r=country/index&page=2 この節では、データベースを扱う方法を学びました。 また、[[yii\data\Pagination]] と [[yii\widgets\LinkPager]] の助けを借りて、ページ付けされたデータを取得し表示する方法も学びました。 -次の節では、[Gii](tool-gii.md) と呼ばれる強力なコード生成ツールを使う方法を学びます。 +次の節では、[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) と呼ばれる強力なコード生成ツールを使う方法を学びます。 このツールは、データベーステーブルのデータを取り扱うための「作成・読出し・更新・削除 (CRUD)」操作のような、通常必要とされることが多いいくつかの機能の迅速な実装を手助けしてくれるものです。 実際のところ、あなたがたった今書いたばかりのコードは、Gii ツールを使えば、全部、Yii が自動的に生成してくれるものです。 diff --git a/docs/guide-ja/start-forms.md b/docs/guide-ja/start-forms.md index 33b5b4373c..466b9a103a 100644 --- a/docs/guide-ja/start-forms.md +++ b/docs/guide-ja/start-forms.md @@ -25,10 +25,14 @@ namespace app\models; +<<<<<<< HEAD <<<<<<< HEAD ======= use Yii; >>>>>>> yiichina/master +======= +use Yii; +>>>>>>> master use yii\base\Model; class EntryForm extends Model @@ -49,7 +53,7 @@ class EntryForm extends Model このクラスは、Yii によって提供される基底クラス [[yii\base\Model]] を拡張するものです。 通常、この基底クラスがフォームデータを表現するのに使われます。 -> Info|情報: [[yii\base\Model]] はデータベーステーブルと関連*しない*モデルクラスの親として使われます。 +> Info: [[yii\base\Model]] はデータベーステーブルと関連*しない*モデルクラスの親として使われます。 データベーステーブルと対応するモデルクラスでは、通常は [[yii\db\ActiveRecord]] が親になります。 `EntryForm` クラスは二つのパブリックメンバー、`name` と `email` を持っており、これらがユーザによって入力されるデータを保管するのに使われます。 @@ -57,7 +61,7 @@ class EntryForm extends Model 上記で宣言されている検証規則は次のことを述べています。 * `name` と `email` は、ともに値を要求される -* `email` のデータは構文的に正当なメールアドレスでなければならない +* `email` のデータは構文的に有効なメールアドレスでなければならない ユーザによって入力されたデータを `EntryForm` オブジェクトに投入した後、[[yii\base\Model::validate()|validate()]] を呼んでデータ検証ルーチンを始動することが出来ます。 データ検証が失敗すると [[yii\base\Model::hasErrors|hasErrors]] プロパティが true に設定されます。 @@ -119,7 +123,7 @@ class SiteController extends Controller 次に、モデルに `$_POST` のデータ、Yii においては [[yii\web\Request::post()]] によって提供されるデータを投入しようと試みます。 モデルへのデータ投入が成功した場合(つまり、ユーザが HTML フォームを送信した場合)、アクションは[[yii\base\Model::validate()|validate()]] を呼んで、入力された値が有効なものであるかどうかを確認します。 -> Info|情報: `Yii::$app` という式は [アプリケーション](structure-applications.md) インスタンスを表現します。 +> Info: `Yii::$app` という式は [アプリケーション](structure-applications.md) インスタンスを表現します。 これはグローバルにアクセス可能なシングルトンです。 これは、また、特定の機能性をサポートする `request`、`response`、`db` などのコンポーネントを提供する [サービスロケータ](concept-service-locator.md) でもあります。 上記のコードでは、アプリケーションインスタンスの `request` コンポーネントが `$_POST` データにアクセスするために使われています。 @@ -127,7 +131,7 @@ class SiteController extends Controller すべてが適正である場合、アクションは `entry-confirm` という名前のビューを表示して、データの送信が成功したことをユーザに確認させます。 データが送信されなかったり、データがエラーを含んでいたりする場合は、`entry` ビューが表示され、その中で HTML フォームが (もし有れば) 検証エラーのメッセージとともに表示されます。 -> Note|注意: この簡単な例では、有効なデータ送信に対して単純に確認ページを表示しています。 +> Note: この簡単な例では、有効なデータ送信に対して単純に確認ページを表示しています。 実際の仕事では、[フォーム送信の諸問題](http://en.wikipedia.org/wiki/Post/Redirect/Get) を避けるために、[[yii\web\Controller::refresh()|refresh()]] または [[yii\web\Controller::redirect()|redirect()]] を使うことを考慮すべきです。 @@ -181,10 +185,10 @@ use yii\widgets\ActiveForm; 試してみる ---------- -どのように動作するかを見るために、ブラウザで下記の URL をアクセスしてください。 +どのように動作するかを見るために、ブラウザで下記の URL にアクセスしてください。 ``` -http://hostname/index.php?r=site/entry +http://hostname/index.php?r=site%2Fentry ``` 二つのインプットフィールドを持つフォームを表示するページが表示されるでしょう。 @@ -208,7 +212,7 @@ http://hostname/index.php?r=site/entry ブラウザで JavaScript を無効にした場合でも、`actionEntry()` メソッドで示されているように、サーバ側での検証は引き続き実行されます。 これにより、どのような状況であっても、データの有効性が保証されます。 -> Warning|警告: クライアント側の検証は、ユーザにとってのより良い使い心地のために利便性を提供するものです。 +> Warning: クライアント側の検証は、ユーザにとってのより良い使い心地のために利便性を提供するものです。 クライアント側の検証の有無にかかわらず、サーバ側の検証は常に必要とされます。 インプットフィールドのラベルは、モデルのプロパティ名を使用して、`field()` メソッドによって生成されます。 @@ -221,7 +225,7 @@ http://hostname/index.php?r=site/entry field($model, 'email')->label('メールアドレス') ?> ``` -> Info|情報: Yii はこのようなウィジェットを数多く提供して、複雑で動的なビューを素速く作成することを手助けしてくれます。 +> Info: Yii はこのようなウィジェットを数多く提供して、複雑で動的なビューを素速く作成することを手助けしてくれます。 後で学ぶように、新しいウィジェットを書くことも非常に簡単です。 あなたは、将来のビュー開発を単純化するために、多くのビューコードを再利用可能なウィジェットに変換したいと思うことでしょう。 @@ -229,7 +233,7 @@ http://hostname/index.php?r=site/entry まとめ ------ -ガイドのこの節においては、MVC デザインパターンの全ての部分に触れました。 +ガイドのこの節においては、MVC アーキテクチャパターンの全ての部分に触れました。 そして、ユーザデータを表現し、当該データを検証するモデルクラスを作成する方法を学びました。 また、ユーザからデータを取得する方法と、ブラウザにデータを表示して返す方法も学びました。 diff --git a/docs/guide-ja/start-gii.md b/docs/guide-ja/start-gii.md index 7ea4842558..fef1cd994d 100644 --- a/docs/guide-ja/start-gii.md +++ b/docs/guide-ja/start-gii.md @@ -1,7 +1,7 @@ Gii でコードを生成する ====================== -この節では、[Gii](tool-gii.md) を使って、ウェブサイトの一般的な機能のいくつかを実装するコードを自動的に生成する方法を説明します。 +この節では、[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) を使って、ウェブサイトの一般的な機能のいくつかを実装するコードを自動的に生成する方法を説明します。 Gii を使ってコードを自動生成することは、Gii のウェブページに表示される指示に対して正しい情報を入力するだけのことです。 このチュートリアルを通じて、次のことを学びます。 @@ -15,11 +15,15 @@ Gii を使ってコードを自動生成することは、Gii のウェブペー Gii を開始する -------------- +<<<<<<< HEAD <<<<<<< HEAD [Gii](tool-gii.md) は Yii の [モジュール](structure-modules.md) として提供されています。 ======= [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) は Yii の [モジュール](structure-modules.md) として提供されています。 >>>>>>> yiichina/master +======= +[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) は Yii の [モジュール](structure-modules.md) として提供されています。 +>>>>>>> master Gii は、アプリケーションの [[yii\base\Application::modules|modules]] プロパティの中で構成することで有効にすることが出来ます。 アプリケーションを生成した仕方にもよりますが、`config/web.php` の構成情報ファイルの中に、多分、下記のコードが既に提供されているでしょう。 @@ -28,7 +32,9 @@ $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` @@ -48,7 +54,7 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); http://hostname/index.php?r=gii ``` -> Note|注意: ローカルホスト以外のマシンから Gii にアクセスしようとすると、デフォルトではセキュリティ上の理由でアクセスが拒否されます。 +> Note: ローカルホスト以外のマシンから Gii にアクセスしようとすると、デフォルトではセキュリティ上の理由でアクセスが拒否されます。 > 下記のように Gii を構成して、許可される IP アドレスを追加することが出来ます。 > ```php @@ -116,7 +122,7 @@ Gii を使って CRUD 機能を作成するためには、"CRUD Generator" を どのように動作するかを見るために、ブラウザを使って下記の URL にアクセスしてください。 ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` データグリッドがデータベーステーブルから取得した国を表示しているページが表示されます。 @@ -136,9 +142,9 @@ http://hostname/index.php?r=country/index * Models: `models/Country.php` と `models/CountrySearch.php` * Views: `views/country/*.php` -> Info|情報: Gii は非常にカスタマイズしやすく拡張しやすいコード生成ツールとして設計されています。 +> Info: Gii は非常にカスタマイズしやすく拡張しやすいコード生成ツールとして設計されています。 これを賢く使うと、アプリケーションの開発速度を大いに高めることが出来ます。 - 詳細については、[Gii](tool-gii.md) の節を参照してください。 + 詳細については、[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) の節を参照してください。 まとめ diff --git a/docs/guide-ja/start-hello.md b/docs/guide-ja/start-hello.md index 780e5a9971..7488f738d9 100644 --- a/docs/guide-ja/start-hello.md +++ b/docs/guide-ja/start-hello.md @@ -9,7 +9,7 @@ このチュートリアルを通じて、三つのことを学びます。 -1. リクエストに応える [アクション](structure-controllers.md) を作成する方法 +1. リクエストに応える [アクション](structure-controllers.md#creating-actions) を作成する方法 2. レスポンスのコンテントを作成する [ビュー](structure-views.md) を作成する方法 3. アプリケーションがリクエストを [アクション](structure-controllers.md#creating-actions) に送付する仕組み @@ -20,7 +20,7 @@ 「こんにちは」のタスクのために、リクエストから `message` パラメータを読んで、そのメッセージをユーザに表示して返す `say` [アクション](structure-controllers.md#creating-actions) を作ります。 リクエストが `message` パラメータを提供しなかった場合は、アクションはデフォルト値として "こんにちは" というメッセージを表示するものとします。 -> Info|情報: [アクション](structure-controllers.md#creating-actions) は、エンドユーザが直接に参照して実行できるオブジェクトです。 +> Info: [アクション](structure-controllers.md#creating-actions) は、エンドユーザが直接に参照して実行できるオブジェクトです。 アクションは [コントローラ](structure-controllers.md) によってグループ化されます。 アクションの実行結果が、エンドユーザが受け取るレスポンスです。 @@ -100,7 +100,7 @@ use yii\helpers\Html; アクションとビューを作成したら、下記の URL で新しいページにアクセスすることが出来ます。 ``` -http://hostname/index.php?r=site/say&message=Hello+World +http://hostname/index.php?r=site%2Fsay&message=Hello+World ``` ![Hello World](images/start-hello-world.png) @@ -111,7 +111,7 @@ http://hostname/index.php?r=site/say&message=Hello+World URL から `message` パラメータを省略すると、"こんにちは" を表示するページを見ることになるでしょう。 これは、`message` が `actionSay()` メソッドにパラメータとして渡されるものであり、それが省略された場合には、デフォルト値である `"こんにちは"` が代りに使われるからです。 -> Info|情報: 新しいページは他のページと同じヘッダとフッタを共有していますが、それは [[yii\web\Controller::render()|render()]] メソッドが `say` ビューの結果をいわゆる [レイアウト](structure-views.md#layouts) に自動的に埋め込むからです。 +> Info: 新しいページは他のページと同じヘッダとフッタを共有していますが、それは [[yii\web\Controller::render()|render()]] メソッドが `say` ビューの結果をいわゆる [レイアウト](structure-views.md#layouts) に自動的に埋め込むからです。 レイアウトは、この場合、`views/layouts/main.php` にあります。 上記の URL の `r` パラメータについては、さらに説明が必要でしょう。 @@ -123,7 +123,7 @@ URL から `message` パラメータを省略すると、"こんにちは" を 結果として、`SiteController::actionSay()` メソッドがリクエストを処理するために呼び出されます。 -> Info|情報: アクションと同じく、コントローラもまたアプリケーションの中で一意に定義される ID を持ちます。 +> Info: アクションと同じく、コントローラもまたアプリケーションの中で一意に定義される ID を持ちます。 コントローラ ID も、アクション ID と同じ命名規則を使います。 コントローラクラスの名前は、コントローラ ID からダッシュを削除し、各単語の最初の文字を大文字にし、結果として出来る文字列に `Controller` という接尾辞を追加したものとなります。 例えば、`post-comment` というコントローラ ID に対応するコントローラクラスの名前は `PostCommentController` です。 @@ -132,7 +132,7 @@ URL から `message` パラメータを省略すると、"こんにちは" を まとめ ------ -この節では、MVC デザインパターンのうちのコントローラとビューの部分に触れました。 +この節では、MVC アーキテクチャパターンのうちのコントローラとビューの部分に触れました。 特定のリクエストを処理するためのアクションをコントローラの一部として作成しました。 また、レスポンスのコンテントを作成するためのビューも作成しました。 この単純な例においては、使用される唯一のデータが `message` パラメータであったため、モデルは関係していません。 diff --git a/docs/guide-ja/start-installation.md b/docs/guide-ja/start-installation.md index b9068bc03e..efb53af0c3 100644 --- a/docs/guide-ja/start-installation.md +++ b/docs/guide-ja/start-installation.md @@ -1,19 +1,24 @@ Yii をインストールする ====================== +<<<<<<< HEAD <<<<<<< HEAD Yii は二つの方法でインストールすることが出来ます。すなわち、[Composer](http://getcomposer.org/) を使うか、アーカイブファイルをダウンロードするかです。 +======= +Yii は二つの方法でインストールすることが出来ます。すなわち、[Composer](https://getcomposer.org/) を使うか、アーカイブファイルをダウンロードするかです。 +>>>>>>> master 前者がお薦めの方法です。と言うのは、一つのコマンドを走らせるだけで、新しい [エクステンション](structure-extensions.md) をインストールしたり、Yii をアップデートしたりすることが出来るからです。 -Yii の標準的なインストールを実行すると、フレームワークとアプリケーションテンプレートの両方がダウンロードされてインストールされます。 -アプリケーションテンプレートは、いくつかの基本的な機能、例えば、ログインやコンタクトフォームなどを実装した、動作する Yii アプリケーションです。 +Yii の標準的なインストールを実行すると、フレームワークとプロジェクトテンプレートの両方がダウンロードされてインストールされます。 +プロジェクトテンプレートは、いくつかの基本的な機能、例えば、ログインやコンタクトフォームなどを実装した、動作する Yii アプリケーションです。 そのコードは推奨される方法に従って編成されています。 -そのため、アプリケーションテンプレートは、あなたのプロジェクトのための良い開始点としての役割を果たしうるものです。 +そのため、プロジェクトテンプレートは、あなたのプロジェクトのための良い開始点としての役割を果たしうるものです。 -この節と後続のいくつかの節においては、いわゆる *ベーシックアプリケーションテンプレート* とともに Yii をインストールする方法、および、このテンプレート上に新しい機能を実装する方法を説明します。 -Yii はもう一つ、[アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) と呼ばれるテンプレートも提供しています。 +この節と後続のいくつかの節においては、いわゆる *ベーシックプロジェクトテンプレート* とともに Yii をインストールする方法、および、このテンプレート上に新しい機能を実装する方法を説明します。 +Yii はもう一つ、[アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) と呼ばれるテンプレートも提供しています。 こちらは、チーム開発環境において多層構造のアプリケーションを開発するときに使用する方が望ましいものです。 +<<<<<<< HEAD > Info|情報: ベーシックアプリケーションテンプレートは、ウェブアプリケーションの 90 パーセントを開発するのに適したものです。 アドバンストアプリケーションテンプレートとの主な違いは、コードがどのように編成されているかという点にあります。 あなたが Yii は初めてだという場合は、シンプルでありながら十分な機能を持っているベーシックアプリケーションテンプレートに留まることを強く推奨します。 @@ -34,6 +39,11 @@ Yii はもう一つ、[アドバンストプロジェクトテンプレート](h アドバンストプロジェクトテンプレートとの主な違いは、コードがどのように編成されているかという点にあります。 あなたが Yii は初めてだという場合は、シンプルでありながら十分な機能を持っているベーシックプロジェクトテンプレートに留まることを強く推奨します。 >>>>>>> yiichina/master +======= +> Info: ベーシックプロジェクトテンプレートは、ウェブアプリケーションの 90 パーセントを開発するのに適したものです。 + アドバンストプロジェクトテンプレートとの主な違いは、コードがどのように編成されているかという点にあります。 + あなたが Yii は初めてだという場合は、シンプルでありながら十分な機能を持っているベーシックプロジェクトテンプレートに留まることを強く推奨します。 +>>>>>>> master Composer によるインストール @@ -42,12 +52,19 @@ Composer によるインストール まだ Composer をインストールしていない場合は、[getcomposer.org](https://getcomposer.org/download/) の指示に従ってインストールすることが出来ます。 Linux や Mac OS X では、次のコマンドを実行します。 +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master mv composer.phar /usr/local/bin/composer +======= +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` +>>>>>>> master Windows では、[Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe) をダウンロードして実行します。 @@ -58,12 +75,19 @@ Composer は `composer self-update` コマンドを実行してアップデー Composer がインストールされたら、ウェブからアクセスできるフォルダで下記のコマンドを実行することによって Yii をインストールすることが出来ます。 +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master composer create-project --prefer-dist yiisoft/yii2-app-basic basic +======= +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` +>>>>>>> master 最初のコマンドは [composer アセットプラグイン](https://github.com/francoispluchino/composer-asset-plugin/) をインストールします。 これにより、Composer を通じて bower と npm の依存パッケージを管理することが出来るようになります。 @@ -71,13 +95,15 @@ Composer がインストールされたら、ウェブからアクセスでき 第二のコマンドは `basic` という名前のディレクトリに Yii をインストールします。 必要なら別のディレクトリ名を選ぶことも出来ます。 -> Note|注意: インストール実行中に Composer が あなたの Github のログイン認証情報を求めることがあるかも知れません。 +> Note: インストール実行中に Composer が あなたの Github のログイン認証情報を求めることがあるかも知れません。 > これは、Comoser が依存パッケージの情報を Github から読み出すために十分な API レートを必要とするためで、普通にあることです。 > 詳細については、[Composer ドキュメント](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens) を参照してください。 -> Tip|ヒント: Yii の最新の開発バージョンをインストールしたい場合は、[stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability) を追加した次のコマンドを代りに使うことが出来ます。 +> Tip: Yii の最新の開発バージョンをインストールしたい場合は、[stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability) を追加した次のコマンドを代りに使うことが出来ます。 > -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` > > 開発バージョンは動いているあなたのコードを動かなくするかもしれませんので、本番環境では使うべきでないことに注意してください。 @@ -110,25 +136,41 @@ Composer がインストールされたら、ウェブからアクセスでき * コアフレームワークだけをインストールし、アプリケーション全体を一から構築したい場合は、[アプリケーションを一から構築する](tutorial-start-from-scratch.md) で説明されている指示に従うことが出来ます。 <<<<<<< HEAD +<<<<<<< HEAD * もっと洗練された、チーム開発環境により適したアプリケーションから開始したい場合は、 [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) をインストールすることを考慮することが出来ます。 ======= * もっと洗練された、チーム開発環境により適したアプリケーションから開始したい場合は、 [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) をインストールすることを考慮することが出来ます。 >>>>>>> yiichina/master +======= +* もっと洗練された、チーム開発環境により適したアプリケーションから開始したい場合は、 [アドバンストプロジェクトテンプレート](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/README.md) をインストールすることを考慮することが出来ます。 +>>>>>>> master インストールを検証する ---------------------- -インストール完了後、インストールされた Yii アプリケーションにブラウザを使って下記の URL でアクセスすることが出来ます。 +インストール完了後、あなたのウェブサーバを構成してください (次の説を参照してください)。 +あるいは、プロジェクトの `web` ディレクトリで次のコマンドを実行して、 +[PHP の内蔵ウェブサーバ](https://secure.php.net/manual/ja/features.commandline.webserver.php) を使ってください。 -``` -http://localhost/basic/web/index.php +```bash +php yii serve ``` -この URL は、あなたが Yii を ウェブサーバのドキュメントルートディレクトリ直下の `basic` という名前のディレクトリにインストールしたこと、 -そして、ウェブサーバがローカルマシン (`localhost`) で走っていることを想定しています。 -あなたのインストールの環境に合うように URL を変更する必要があるかもしれません。 +> Note: デフォルトでは、この HTTP サーバは 8080 ポートをリスンします。 +しかし、このポートがすでに使われていたり、複数のアプリケーションをこの方法で動かしたい場合は、どのポートを使うかを指定したいと思うでしょう。 +単に --port 引数を追加して下さい。 + +```bash +php yii serve --port=8888 +``` + +下記の URL によって、インストールされた Yii アプリケーションにブラウザを使ってアクセスすることが出来ます。 + +``` +http://localhost:8080/ +``` ![Yii のインストールが成功](images/start-app-installed.png) @@ -136,10 +178,10 @@ http://localhost/basic/web/index.php もし表示されなかったら、PHP のインストールが Yii の必要条件を満たしているかどうか、チェックしてください。 最低限の必要条件を満たしているかどうかは、次の方法のどちらかによってチェックすることが出来ます。 -* ブラウザを使って URL `http://localhost/basic/requirements.php` にアクセスする。 +* `requirements.php` を `/web/requirements.php` としてコピーし、ブラウザを使って URL `http://localhost/requirements.php` にアクセスする。 * 次のコマンドを実行する。 - ``` + ```bash cd basic php requirements.php ``` @@ -152,7 +194,7 @@ Yii の最低必要条件を満たすように PHP のインストールを構 ウェブサーバを構成する ---------------------- -> Info|情報: もし Yii の試運転をしているだけで、本番サーバに配備する意図がないのであれば、当面、この項は飛ばしても構いません。 +> Info: もし Yii の試運転をしているだけで、本番サーバに配備する意図がないのであれば、当面、この項は飛ばしても構いません。 上記の説明に従ってインストールされたアプリケーションは、[Apache HTTP サーバ](http://httpd.apache.org/) と [Nginx HTTP サーバ](http://nginx.org/) のどちらでも、また、Windows、Mac OS X、Linux のどれでも、PHP 5.4 以上を走らせている環境であれば、そのままの状態で動作するはずです。 Yii 2.0 は、また、facebook の [HHVM](http://hhvm.com/) とも互換性があります。 @@ -163,10 +205,10 @@ Yii 2.0 は、また、facebook の [HHVM](http://hhvm.com/) とも互換性が また、[ルーティングと URL 生成](runtime-routing.md) の節で述べられているように、URL から `index.php` を隠したいとも思うでしょう。 この節では、これらの目的を達するために Apache または Nginx サーバをどのように設定すれば良いかを学びます。 -> Info|情報: `basic/web` をドキュメントルートに設定することは、`basic/web` の兄弟ディレクトリに保存されたプライベートなアプリケーションコードや公開できないデータファイルにエンドユーザがアクセスすることを防止することにもなります。 +> Info: `basic/web` をドキュメントルートに設定することは、`basic/web` の兄弟ディレクトリに保存されたプライベートなアプリケーションコードや公開できないデータファイルにエンドユーザがアクセスすることを防止することにもなります。 `basic/web` 以外のフォルダに対するアクセスを拒否することはセキュリティ強化の一つです。 -> Info|情報: あなたがウェブサーバの設定を修正する権限を持たない共用ホスティング環境でアプリケーションが走る場合であっても、セキュリティ強化のためにアプリケーションの構造を調整することがまだ出来ます。 +> Info: あなたがウェブサーバの設定を修正する権限を持たない共用ホスティング環境でアプリケーションが走る場合であっても、セキュリティ強化のためにアプリケーションの構造を調整することがまだ出来ます。 詳細については、[共有ホスティング環境](tutorial-shared-hosting.md) の節を参照してください。 @@ -175,7 +217,7 @@ Yii 2.0 は、また、facebook の [HHVM](http://hhvm.com/) とも互換性が 下記の設定を Apache の `httpd.conf` ファイルまたはバーチャルホスト設定の中で使います。 `path/to/basic/web` の部分を `basic/web` の実際のパスに置き換えなければならないことに注意してください。 -``` +```apache # ドキュメントルートを "basic/web" に設定 DocumentRoot "path/to/basic/web" @@ -199,7 +241,7 @@ DocumentRoot "path/to/basic/web" 下記の Nginx の設定を使うことができます。 `path/to/basic/web` の部分を `basic/web` の実際のパスに置き換え、`mysite.local` を実際のサーバのホスト名に置き換えてください。 -``` +```nginx server { charset utf-8; client_max_body_size 128M; @@ -216,7 +258,7 @@ server { location / { # 本当のファイルでないものは全て index.php にリダイレクト - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # 存在しない静的ファイルの呼び出しを Yii に処理させたくない場合はコメントを外す @@ -227,7 +269,7 @@ server { location ~ \.php$ { include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; diff --git a/docs/guide-ja/start-looking-ahead.md b/docs/guide-ja/start-looking-ahead.md index bc5e4a51a8..e8cb489d75 100644 --- a/docs/guide-ja/start-looking-ahead.md +++ b/docs/guide-ja/start-looking-ahead.md @@ -3,7 +3,7 @@ 「はじめよう」の章全体を読み通したなら、いまやあなたは、完全な Yii のアプリケーションを作成したことがある、ということになります。 その過程で、あなたは必要とされることが多いいくつかの機能、例えば、HTML フォームを通じてユーザからデータを取得することや、データベースからデータを取得すること、また、ページ付けをしてデータを表示することなどを実装する方法を学びました。 -また、[Gii](tool-gii.md) を使ってコードを自動的に生成する方法も学びました。 +また、[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ja/README.md) を使ってコードを自動的に生成する方法も学びました。 Gii をコード生成に使うと、ウェブ開発のプロセスの大部分が、いくつかのフォームに入力していくだけの簡単な仕事になります。 この節では、Yii フレームワークを使うときの生産性を更に高めるために利用できるリソースについてまとめます。 @@ -27,6 +27,7 @@ Gii をコード生成に使うと、ウェブ開発のプロセスの大部分 * コミュニティ - フォーラム: - IRC チャット: freenode ネットワーク () の #yii チャンネル + - Gitter チャット: - GitHub: - Facebook: - Twitter: diff --git a/docs/guide-ja/start-workflow.md b/docs/guide-ja/start-workflow.md index 3151bbb75b..08930dd902 100644 --- a/docs/guide-ja/start-workflow.md +++ b/docs/guide-ja/start-workflow.md @@ -5,17 +5,23 @@ Yii のインストールが終ると、実際に動く Yii のアプリケー その URL は、`http://hostname/basic/web/index.php` あるいは `http://hostname/index.php` など、設定によって異なります。 この節では、アプリケーションに組み込み済みの機能を紹介し、コードがどのように編成されているか、そして、一般にアプリケーションがリクエストをどのように処理するかを説明します。 -> Info|情報: 話を簡単にするために、この「始めよう」のチュートリアルを通じて、`basic/web` をウェブサーバのドキュメントルートとして設定したと仮定します。 +> Info: 話を簡単にするために、この「始めよう」のチュートリアルを通じて、`basic/web` をウェブサーバのドキュメントルートとして設定したと仮定します。 そして、アプリケーションにアクセスするための URL は `http://hostname/index.php` またはそれに似たものになるように設定したと仮定します。 必要に応じて、説明の中の URL を読み替えてください。 +<<<<<<< HEAD <<<<<<< HEAD ======= フレームワークそのものとは異なり、プロジェクトテンプレートはインストール後は完全にあなたのものであることに注意してください。 必要に応じてコードを追加したり削除したり、完全に書き換えたりするのはあなたの自由です。 >>>>>>> yiichina/master +======= +フレームワークそのものとは異なり、プロジェクトテンプレートはインストール後は完全にあなたのものであることに注意してください。 +必要に応じてコードを追加したり削除したり、完全に書き換えたりするのはあなたの自由です。 + +>>>>>>> master 機能 ---- @@ -32,15 +38,20 @@ Yii のインストールが終ると、実際に動く Yii のアプリケー ヘッダには、異なるページ間を行き来することを可能にするメインメニューバーがあります。 ブラウザのウィンドウの下部にツールバーがあることにも気がつくはずです。 -これは Yii によって提供される便利な [デバッグツールバー](tool-debugger.md) であり、たくさんのデバッグ情報、例えば、ログメッセージ、レスポンスのステータス、実行されたデータベースクエリなどを記録して表示するものです。 +これは Yii によって提供される便利な [デバッグツールバー](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-ja/README.md) であり、たくさんのデバッグ情報、例えば、ログメッセージ、レスポンスのステータス、実行されたデータベースクエリなどを記録して表示するものです。 ウェブアプリケーションに加えて、`yii` というコンソールスクリプトがアプリケーションのベースディレクトリにあります。 +<<<<<<< HEAD このスクリプトをバックグラウンドのタスクまたはメンテナンスのタスクを実行するために使用することが出来ます。 <<<<<<< HEAD これについては、[コンソールアプリケーションの節](tutoral-console.md) で説明されています。 ======= これについては、[コンソールアプリケーションの節](tutorial-console.md) で説明されています。 >>>>>>> yiichina/master +======= +このスクリプトは、バックグラウンドのタスクまたはメンテナンスのタスクを実行するために使用することが出来ます。 +これについては、[コンソールアプリケーションの節](tutorial-console.md) で説明されています。 +>>>>>>> master アプリケーションの構造 ---------------------- @@ -68,7 +79,7 @@ basic/ アプリケーションのベースパス 一般に、アプリケーションのファイルは二種類に分けることが出来ます。すなわち、`basic/web` の下にあるファイルとその他のディレクトリの下にあるファイルです。 前者は HTTP で (すなわちブラウザで) 直接にアクセスすることが出来ますが、後者は直接のアクセスは出来ませんし、許可すべきでもありません。 -Yii は [モデル・ビュー・コントローラ (MVC)](http://wikipedia.org/wiki/Model-view-controller) デザインパターンを実装していますが、それが上記のディレクトリ構成にも反映されています。 +Yii は [モデル・ビュー・コントローラ (MVC)](http://wikipedia.org/wiki/Model-view-controller) アーキテクチャパターンを実装していますが、それが上記のディレクトリ構成にも反映されています。 `models` ディレクトリが全ての [モデルクラス](structure-models.md) を格納し、`views` ディレクトリが全ての [ビュースクリプト](structure-views.md) を格納し、 `controllers` ディレクトリが全ての [コントローラクラス](structure-controllers.md) を格納しています。 diff --git a/docs/guide-ja/structure-application-components.md b/docs/guide-ja/structure-application-components.md index 80875d5d2b..9e7763fc75 100644 --- a/docs/guide-ja/structure-application-components.md +++ b/docs/guide-ja/structure-application-components.md @@ -44,7 +44,7 @@ ] ``` -> Info|情報: 必要なだけ多くのアプリケーションコンポーネントを登録することが出来ますが、慎重にしなければなりません。 +> Info: 必要なだけ多くのアプリケーションコンポーネントを登録することが出来ますが、慎重にしなければなりません。 アプリケーションコンポーネントはグローバル変数のようなものです。 あまり多くのアプリケーションコンポーネントを使うと、コードのテストと保守が困難になるおそれがあります。 多くの場合、必要なときにローカルなコンポーネントを作成して使用するだけで十分です。 @@ -93,11 +93,15 @@ Yii のアプリケーションがユーザリクエストを処理出来るの 詳細は [エラー処理](runtime-handling-errors.md) の節を参照してください。 * [[yii\i18n\Formatter|formatter]]: エンドユーザに表示されるデータに書式を設定します。 例えば、数字が3桁ごとの区切りを使って表示されたり、日付が長い書式で表示されたりします。 +<<<<<<< HEAD <<<<<<< HEAD 詳細は [データの書式設定](output-formatter.md) の節を参照してください。 ======= 詳細は [データの書式設定](output-formatting.md) の節を参照してください。 >>>>>>> yiichina/master +======= + 詳細は [データの書式設定](output-formatting.md) の節を参照してください。 +>>>>>>> master * [[yii\i18n\I18N|i18n]]: メッセージの翻訳と書式設定をサポートします。 詳細は [国際化](tutorial-i18n.md) の節を参照してください。 * [[yii\log\Dispatcher|log]]: ログターゲットを管理します。 diff --git a/docs/guide-ja/structure-applications.md b/docs/guide-ja/structure-applications.md index 6ce98c19e9..d616239f6e 100644 --- a/docs/guide-ja/structure-applications.md +++ b/docs/guide-ja/structure-applications.md @@ -2,9 +2,10 @@ ================ アプリケーションは Yii アプリケーションシステム全体の構造とライフサイクルを統制するオブジェクトです。 -全ての Yii アプリケーションシステムは、それぞれ、[エントリスクリプト](structure-entry-scripts.md) において作成され、`\Yii::$app` という式でグローバルにアクセス可能な、単一のアプリケーションオブジェクトを持ちます。 +全ての Yii アプリケーションシステムは、それぞれ、単一のアプリケーションオブジェクトを持ちます。 +アプリケーションオブジェクトは、[エントリスクリプト](structure-entry-scripts.md) において作成され、`\Yii::$app` という式でグローバルにアクセスすることが出来るオブジェクトです。 -> Info|情報: ガイドの中で「アプリケーション」という言葉は、文脈に応じて、アプリケーションオブジェクトを意味したり、アプリケーションシステムを意味したりします。 +> Info: ガイドの中で「アプリケーション」という言葉は、文脈に応じて、アプリケーションオブジェクトを意味したり、アプリケーションシステムを意味したりします。 二種類のアプリケーション、すなわち、[[yii\web\Application|ウェブアプリケーション]] と [[yii\console\Application|コンソールアプリケーション]] があります。 名前が示すように、前者は主にウェブのリクエストを処理し、後者はコンソールコマンドのリクエストを処理します。 @@ -96,13 +97,13 @@ $config = require(__DIR__ . '/../config/web.php'); これによって、アプリケーションの [[yii\base\Application::bootstrap()|ブートストラップの過程]] において走らせるべきコンポーネントを配列として指定することが出来ます。 例えば、ある [モジュール](structure-modules.md) に [URL 規則](runtime-routing.md) をカスタマイズさせたいときに、モジュールの ID をこのプロパティの要素として挙げることが出来ます。 -このプロパティに挙げるコンポーネントは、それぞれ、以下の形式のいずれかによって指定することが出来ます。 +このプロパティにリストする各コンポーネントは、以下の形式のいずれかによって指定することが出来ます。 -- [components](#components) によって指定されているアプリケーションコンポーネントの ID。 -- [modules](#modules) によって指定されているモジュールの ID。 -- クラス名。 -- 構成情報の配列。 -- コンポーネントを作成して返す無名関数。 +- [components](#components) によって指定されているアプリケーションコンポーネントの ID +- [modules](#modules) によって指定されているモジュールの ID +- クラス名 +- 構成情報の配列 +- コンポーネントを作成して返す無名関数 例えば、 @@ -129,24 +130,28 @@ $config = require(__DIR__ . '/../config/web.php'); ] ``` -> Info|情報: モジュール ID と同じ ID のアプリケーションコンポーネントがある場合は、ブートストラップの過程ではアプリケーションコンポーネントが使われます。 - 代りにモジュールを使いたいときは、次のように、無名関数を使って指定することが出来ます。 +> Info: モジュール ID と同じ ID のアプリケーションコンポーネントがある場合は、ブートストラップの過程ではアプリケーションコンポーネントが使われます。 +> 代りにモジュールを使いたいときは、次のように、無名関数を使って指定することが出来ます。 > ```php -[ - function () { - return Yii::$app->getModule('user'); - }, -] -``` +> [ +> function () { +> return Yii::$app->getModule('user'); +> }, +> ] +> ``` ブートストラップの過程で、各コンポーネントのインスタンスが作成されます。 そして、コンポーネントクラスが [[yii\base\BootstrapInterface]] を実装している場合は、その [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]] メソッドも呼び出されます。 +<<<<<<< HEAD <<<<<<< HEAD もう一つの実用的な例が [ベーシックアプリケーションテンプレート](start-installation.md) のアプリケーションの構成情報の中にあります。 ======= もう一つの実用的な例が [ベーシックプロジェクトテンプレート](start-installation.md) のアプリケーションの構成情報の中にあります。 >>>>>>> yiichina/master +======= +もう一つの実用的な例が [ベーシックプロジェクトテンプレート](start-installation.md) のアプリケーションの構成情報の中にあります。 +>>>>>>> master そこでは、アプリケーションが開発環境で走るときには `debug` モジュールと `gii` モジュールがブートストラップコンポーネントとして構成されています。 ```php @@ -160,7 +165,7 @@ if (YII_ENV_DEV) { } ``` -> Note|注意: あまり多くのコンポーネントを `bootstrap` に置くと、アプリケーションのパフォーマンスを劣化させます。 +> Note: あまり多くのコンポーネントを `bootstrap` に置くと、アプリケーションのパフォーマンスを劣化させます。 なぜなら、リクエストごとに同じ一連のコンポーネントを走らせなければならないからです。 ですから、ブートストラップコンポーネントは賢く使ってください。 @@ -185,6 +190,8 @@ if (YII_ENV_DEV) { ] ``` +> Info: このプロパティを有効にすると、開発環境でデバッグパネルが動作しなくなります。 + #### [[yii\base\Application::components|components]] @@ -210,7 +217,7 @@ if (YII_ENV_DEV) { キーはコンポーネントの ID を示し、値はコンポーネントのクラス名または [構成情報](concept-configurations.md) を示します。 どのようなコンポーネントでもアプリケーションに登録することが出来ます。 -そして登録されたコンポーネントは、後で、`\Yii::$app->ComponentID` という式を使ってグローバルにアクセスすることが出来ます。 +そして登録されたコンポーネントは、後で、`\Yii::$app->componentID` という式を使ってグローバルにアクセスすることが出来ます。 詳細は [アプリケーションコンポーネント](structure-application-components.md) の節を読んでください。 @@ -389,10 +396,14 @@ $width = \Yii::$app->params['thumbnail.size'][0]; このプロパティは、アプリケーションにインストールされて使われている [エクステンション](structure-extensions.md) のリストを指定するものです。 デフォルトでは、`@vendor/yiisoft/extensions.php` というファイルによって返される配列を取ります。 <<<<<<< HEAD +<<<<<<< HEAD `extensions.php` は、[Composer](http://getcomposer.org) を使ってエクステンションをインストールすると、自動的に生成され保守されます。 ======= `extensions.php` は、[Composer](https://getcomposer.org) を使ってエクステンションをインストールすると、自動的に生成され保守されます。 >>>>>>> yiichina/master +======= +`extensions.php` は、[Composer](https://getcomposer.org) を使ってエクステンションをインストールすると、自動的に生成され保守されます。 +>>>>>>> master ですから、たいていの場合、このプロパティをあなたが構成する必要はありません。 エクステンションを手作業で保守したいという特殊なケースにおいては、次のようにしてこのプロパティを構成することが出来ます。 @@ -461,11 +472,15 @@ $width = \Yii::$app->params['thumbnail.size'][0]; #### [[yii\base\Application::vendorPath|vendorPath]] +<<<<<<< HEAD <<<<<<< HEAD このプロパティは、[Composer](http://getcomposer.org) によって管理される vendor ディレクトリを指定するものです。 ======= このプロパティは、[Composer](https://getcomposer.org) によって管理される vendor ディレクトリを指定するものです。 >>>>>>> yiichina/master +======= +このプロパティは、[Composer](https://getcomposer.org) によって管理される vendor ディレクトリを指定するものです。 +>>>>>>> master Yii フレームワークを含めて、あなたのアプリケーションによって使われる全てのサードパーティライブラリを格納するディレクトリです。 デフォルト値は、`@app/vendor` というエイリアスで表現されるディレクトリです。 diff --git a/docs/guide-ja/structure-assets.md b/docs/guide-ja/structure-assets.md index 582e4ed2a5..e8cfc6abc9 100644 --- a/docs/guide-ja/structure-assets.md +++ b/docs/guide-ja/structure-assets.md @@ -25,11 +25,15 @@ Yii はアセットを *アセットバンドル* を単位として管理しま アセットバンドルクラスは [オートロード可能](concept-autoloading.md) でなければなりません。 アセットバンドルクラスは、通常、アセットがどこに置かれているか、バンドルがどういう CSS や JavaScript のファイルを含んでいるか、そして、バンドルが他のバンドルにどのように依存しているかを定義します。 +<<<<<<< HEAD <<<<<<< HEAD 以下のコードは [ベーシックアプリケーションテンプレート](start-installation.md) によって使用されているメインのアセットバンドルを定義するものです。 ======= 以下のコードは [ベーシックプロジェクトテンプレート](start-installation.md) によって使用されているメインのアセットバンドルを定義するものです。 >>>>>>> yiichina/master +======= +以下のコードは [ベーシックプロジェクトテンプレート](start-installation.md) によって使用されているメインのアセットバンドルを定義するものです。 +>>>>>>> master ```php Note|注意: `@webroot/assets` を [[yii\web\AssetBundle::sourcePath|ソースパス]] として使ってはいけません。 +> Note: `@webroot/assets` を [[yii\web\AssetBundle::sourcePath|ソースパス]] として使ってはいけません。 このディレクトリは、デフォルトでは、[[yii\web\AssetManager|アセットマネージャ]] がソースの配置場所から発行されたアセットファイルを保存する場所として使われます。 このディレクトリの中のファイルはすべて一時的なものと見なされており、削除されることがあります。 @@ -129,7 +133,7 @@ class AppAsset extends AssetBundle [[yii\web\AssetBundle::cssOptions|cssOptions]] および [[yii\web\AssetBundle::jsOptions|jsOptions]] のプロパティを指定して、CSS と JavaScript ファイルがページにインクルードされる方法をカスタマイズすることが出来ます。 これらのプロパティの値は、[ビュー](structure-views.md) が CSS と JavaScript ファイルをインクルードするために、[[yii\web\View::registerCssFile()]] および [[yii\web\View::registerJsFile()]] メソッドを呼ぶときに、それぞれ、オプションとして引き渡されます。 -> Note|注意: バンドルクラスでセットしたオプションは、バンドルの中の *全て* の CSS/JavaScript ファイルに適用されます。 +> Note: バンドルクラスでセットしたオプションは、バンドルの中の *全て* の CSS/JavaScript ファイルに適用されます。 いろいろなファイルに別々のオプションを使用したい場合は、別々のアセットバンドルを作成して、個々のバンドルの中では、一組のオプションを使うようにしなければなりません。 例えば、IE9 以下のブラウザに対して CSS ファイルを条件的にインクルードするために、次のオプションを使うことが出来ます。 @@ -175,20 +179,17 @@ class FontAwesomeAsset extends AssetBundle public $css = [ 'css/font-awesome.min.css', ]; - - public function init() - { - parent::init(); - $this->publishOptions['beforeCopy'] = function ($from, $to) { - $dirname = basename(dirname($from)); - return $dirname === 'fonts' || $dirname === 'css'; - }; - } + public $publishOptions = [ + 'only' => [ + 'fonts/', + 'css/', + ] + ]; } ``` 上記の例は、["fontawesome" パッケージ](http://fontawesome.io/) のためのアセットバンドルを定義するものです。 -`beforeCopy` という発行オプションを指定して、`fonts` と `css` サブディレクトリだけが発行されるようにしています。 +発行オプション `only` を指定して、`fonts` と `css` サブディレクトリだけが発行されるようにしています。 ### Bower と NPM のアセット @@ -202,7 +203,7 @@ class FontAwesomeAsset extends AssetBundle [[yii\web\AssetBundle::sourcePath|sourcePath]] プロパティは、`@bower/PackageName` または `@npm/PackageName` としなければなりません。 これは、Composer が Bower または NPM パッケージを、このエイリアスに対応するディレクトリにインストールするためです。 -> Note|注意: パッケージの中には、全ての配布ファイルをサブディレクトリに置くものがあります。 +> Note: パッケージの中には、全ての配布ファイルをサブディレクトリに置くものがあります。 その場合には、そのサブディレクトリを [[yii\web\AssetBundle::sourcePath|sourcePath]] の値として指定しなければなりません。 例えば、[[yii\web\JqueryAsset]] は `@bower/jquery` ではなく `@bower/jquery/dist` を使います。 @@ -217,7 +218,7 @@ use app\assets\AppAsset; AppAsset::register($this); // $this はビューオブジェクトを表す ``` -> Info|情報: [[yii\web\AssetBundle::register()]] メソッドは、[[yii\web\AssetBundle::basePath|basePath]] や [[yii\web\AssetBundle::baseUrl|baseUrl]] など、発行されたアセットに関する情報を含むアセットバンドルオブジェクトを返します。 +> Info: [[yii\web\AssetBundle::register()]] メソッドは、[[yii\web\AssetBundle::basePath|basePath]] や [[yii\web\AssetBundle::baseUrl|baseUrl]] など、発行されたアセットに関する情報を含むアセットバンドルオブジェクトを返します。 他の場所でアセットバンドルを登録しようとするときは、必要とされるビューオブジェクトを提供しなければなりません。 例えば、[ウィジェット](structure-widgets.md) クラスの中でアセットバンドルを登録するためには、`$this->view` によってビューオブジェクトを取得することが出来ます。 @@ -225,7 +226,7 @@ AppAsset::register($this); // $this はビューオブジェクトを表す アセットバンドルがビューに登録されるとき、舞台裏では、依存している全てのアセットバンドルが Yii によって登録されます。 そして、アセットバンドルがウェブからはアクセス出来ないディレクトリに配置されている場合は、アセットバンドルがウェブディレクトリに発行されます。 その後、ビューがページをレンダリングするときに、登録されたバンドルのリストに挙げられている CSS と JavaScript ファイルのための `` タグと ` +``` + +Если вы хотите подключить внешний CSS-файл: + +```php + 'IE 5']) ?> + +В результате получится: + + +``` + +Первый аргумент - URL-адрес. Второй - массив настроек. Помимо обычных настроек можно указать следующие: + +- `condition` для оборачивания тэга ``, таким + образом скрипт будет подключён только в том случае, если у пользователя в браузере нет поддержки JavaScript или же + пользователь сам отключил его. + +Для подключения JavaScript-файла используйте код: + +```php + +``` + +Как и в случае с CSS, первый аргумент указывает ссылку на файл, который должен быть подключен. Настройки задаются во +втором аргументе. Можно указать настройку `condition` таким же образом, каким она указывается для метода `cssFile`. + + +## Ссылки + +Существует удобный метод формирования ссылок: + +```php + $id], ['class' => 'profile-link']) ?> +``` + +Первый аргумент - это текст ссылки. Он не экранируется, поэтому при использовании данных, полученных от конечных +пользователей, необходимо экранировать его с помощью `Html::encode()`. Второй аргумент - это содержимое атрибута `href` +тэга ` +``` + + +## Изображения + +Для формирования тэга изображения используйте следующий код: + +```php + 'Наш логотип']) ?> + +в результате получится: + +Наш логотип +``` + +Помимо [псевдонимов](concept-aliases.md) первый аргумент может принимать маршруты, параметры и URL-адреса таким же образом +как и метод [Url::to()](helper-url.md). + + +## Списки + +Неупорядоченные списки могут быть сформированы следующим образом: + +```php + function($item, $index) { + return Html::tag( + 'li', + $this->render('post', ['item' => $item]), + ['class' => 'post'] + ); +}]) ?> +``` + +Для формирования упорядоченных списков используйте `Html::ol()`. \ No newline at end of file diff --git a/docs/guide-ru/helper-overview.md b/docs/guide-ru/helper-overview.md new file mode 100644 index 0000000000..520f205781 --- /dev/null +++ b/docs/guide-ru/helper-overview.md @@ -0,0 +1,73 @@ +Хелперы +======= + +> Note: Этот раздел находиться в стадии разработки. + +Yii предоставляет много классов, которые помогают упростить общие задачи программирования, такие как манипуляция со строками или массивами, генерация HTML кода, и так далее. Все helper классы организованны в рамках пространства имен `yii\helpers` и являются статическими методами + (это означает, что они содержат в себе только статические свойства и методы и объекты статического класса создать нельзя). + +Вы можете использовать helper класс с помощью вызова одного из статических методов, как показано ниже: + +```php +use yii\helpers\Html; + +echo Html::encode('Test > test'); +``` + +> Note: Помощь в [настройке helper классов](#customizing-helper-classes), в Yii каждый основной helper состоит из двух классов: базовый класс (например `BaseArrayHelper`) и конкретный класс (например `ArrayHelper`). + Когда вы используете helper, вы должны использовать только конкретные версии классов и никогда не испольовать базовые классы. + + +Встроенные хелперы +------------------ + +В этой версии Yii предоставлются следующие основные helper классы: + +- [ArrayHelper](helper-array.md) +- Console +- FileHelper +- FormatConverter +- [Html](helper-html.md) +- HtmlPurifier +- Imagine (provided by yii2-imagine extension) +- Inflector +- Json +- Markdown +- StringHelper +- [Url](helper-url.md) +- VarDumper + + +Настройка хелперов +-------------------------- + +Для настройки основных helper классов (например [[yii\helpers\ArrayHelper]]), вы должны создать расширяющийся класс из помощников соотвествующих базовых классов (например [[yii\helpers\BaseArrayHelper]]) и дать похожее название, вашему классу, с соотвествующим конкретному классу (например [[yii\helpers\ArrayHelper]]), в том числе его пространство имен. Тогда созданный класс заменит оригинальную реальзацию в фреимворке. + +В следующих примерах показывается как настроить [[yii\helpers\ArrayHelper::merge()|merge()]] метод +[[yii\helpers\ArrayHelper]] класса: + +```php + + +Вы можете использовать два метода получения общих URL: домашний URL (Home) и базовый URL (Base) текущего запроса. +Используйте следующий код, чтобы получить домашний URL: + +```php +$relativeHomeUrl = Url::home(); +$absoluteHomeUrl = Url::home(true); +$httpsAbsoluteHomeUrl = Url::home('https'); +``` + +Если вы не передали параметров, то получите относительный URL. Вы можете передать `true`, чтобы получить абсолютный URL +для текущего протокола или явно указать протокол (`https`, `http`). + +Чтобы получить базовый URL текущего запроса: + +```php +$relativeBaseUrl = Url::base(); +$absoluteBaseUrl = Url::base(true); +$httpsAbsoluteBaseUrl = Url::base('https'); +``` + +Единственный параметр данного метода работает также как и `Url::home()`. + +## Создание URL + +Чтобы создать URL для соответствующего роута используйте метод `Url::toRoute()`. Метод использует [[\yii\web\UrlManager]]. +Для того чтобы создать URL: + +```php +$url = Url::toRoute(['product/view', 'id' => 42]); +``` + +Вы можете задать роут строкой, например, `site/index`. А также вы можете использовать массив, если хотите задать +дополнительные параметры запроса для URL. Формат массива должен быть следующим: + +```php +// сгенерирует: /index.php?r=site/index¶m1=value1¶m2=value2 +['site/index', 'param1' => 'value1', 'param2' => 'value2'] +``` + +Если вы хотите создать URL с якорем, то вы можете использовать параметр массива с ключом `#`. Например: + +```php +// сгенерирует: /index.php?r=site/index¶m1=value1#name +['site/index', 'param1' => 'value1', '#' => 'name'] +``` + +Роут может быть и абсолютным, и относительным. Абсолютный URL начинается со слеша (например, `/site/index`), +относительный - без (например, `site/index` or `index`). Относительный URL будет сконвертирован в абсолютный по следующим +правилам: + +- Если роут пустая строка, то будет использовано текущее значение [[\yii\web\Controller::route|route]]; +- Если роут не содержит слешей (например, `index`), то он будет считаться экшеном текущего контролера и будет определен + с помощью [[\yii\web\Controller::uniqueId]]; +- Если роут начинается не со слеша (например, `site/index`), то он будет считаться относительным роутом текущего модуля + и будет определен с помощью [[\yii\base\Module::uniqueId|uniqueId]]. + +Начиная с версии 2.0.2, вы можете задавать роуты с помощью [псевдонимов](concept-aliases.md). В этом случае, сначала +псевдоним будет сконвертирован в соответствующий роут, который будет преобразован в абсолютный в соответствии с вышеописанными +правилами. + +Примеры использования метода: + +```php +// /index.php?r=site/index +echo Url::toRoute('site/index'); + +// /index.php?r=site/index&src=ref1#name +echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /index.php?r=post/edit&id=100 псевдоним "@postEdit" задан как "post/edit" +echo Url::toRoute(['@postEdit', 'id' => 100]); + +// http://www.example.com/index.php?r=site/index +echo Url::toRoute('site/index', true); + +// https://www.example.com/index.php?r=site/index +echo Url::toRoute('site/index', 'https'); +``` + +Другой метод `Url::to()` очень похож на [[toRoute()]]. Единственное отличие: входным параметром должен быть массив. +Если будет передана строка, то она будет воспринята как URL. + +Первый аргумент может быть: + +- массивом: будет вызван [[toRoute()]], чтобы сгенерировать URL. Например: `['site/index']`, `['post/index', 'page' => 2]`. + В разделе [[toRoute()]] подробно описано как задавать роут; +- Строка, начинающаяся с `@`, будет обработана как псевдоним. Будет возвращено соответствующее значение псевдонима; +- Пустая строка: вернет текущий URL; +- Обычная строка: вернет строку без изменений + +Когда у метода задан второй параметр `$scheme` (строка или true), то сгенерированный URL будет с протоколом +(полученным из [[\yii\web\UrlManager::hostInfo]]). Если в `$url` указан протокол, то его значение будет заменено. + +Пример использования: + +```php +// /index.php?r=site/index +echo Url::to(['site/index']); + +// /index.php?r=site/index&src=ref1#name +echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /index.php?r=post/edit&id=100 псевдоним "@postEdit" задан как "post/edit" +echo Url::to(['@postEdit', 'id' => 100]); + +// Текущий URL +echo Url::to(); + +// /images/logo.gif +echo Url::to('@web/images/logo.gif'); + +// images/logo.gif +echo Url::to('images/logo.gif'); + +// http://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', true); + +// https://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', 'https'); +``` + +Начиная с версии 2.0.3, вы можете использовать [[yii\helpers\Url::current()]], чтобы создавать URL на основе текущего +запрошенного роута и его GET-параметров. Вы можете изменить, удалить или добавить новые GET-параметры передав в метод +параметр `$params`. Например: + +```php +// предположим $_GET = ['id' => 123, 'src' => 'google'], а текущий роут "post/view" + +// /index.php?r=post/view&id=123&src=google +echo Url::current(); + +// /index.php?r=post/view&id=123 +echo Url::current(['src' => null]); +// /index.php?r=post/view&id=100&src=google +echo Url::current(['id' => 100]); +``` + + +## Запоминание URL + +Существуют задачи, когда вам необходимо запомнить URL и потом использовать его в процессе одного или нескольких +последовательных запросов. Это может быть достигнуто следующим образом: + +```php +// Запомнить текущий URL +Url::remember(); + +// Запомнить определенный URL. Входные параметры смотрите на примере Url::to(). +Url::remember(['product/view', 'id' => 42]); + +// Запомнить URL под определенным именем +Url::remember(['product/view', 'id' => 42], 'product'); +``` + +В следующем запросе мы можем получить сохраненный URL следующим образом: + +```php +$url = Url::previous(); +$productUrl = Url::previous('product'); +``` + +## Проверить относительность URL + +Чтобы проверить относительный URL или нет (например, если в нем не содержится информации о хосте), вы можете использовать +следующий код: + +```php +$isRelative = Url::isRelative('test/it'); +``` diff --git a/docs/guide-ru/images/rbac-access-check-1.graphml b/docs/guide-ru/images/rbac-access-check-1.graphml new file mode 100644 index 0000000000..44078515cf --- /dev/null +++ b/docs/guide-ru/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-ru/images/rbac-access-check-1.png b/docs/guide-ru/images/rbac-access-check-1.png new file mode 100644 index 0000000000..77ad551c26 Binary files /dev/null and b/docs/guide-ru/images/rbac-access-check-1.png differ diff --git a/docs/guide-ru/images/rbac-access-check-2.graphml b/docs/guide-ru/images/rbac-access-check-2.graphml new file mode 100644 index 0000000000..c521d429ea --- /dev/null +++ b/docs/guide-ru/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-ru/images/rbac-access-check-2.png b/docs/guide-ru/images/rbac-access-check-2.png new file mode 100644 index 0000000000..254f307a89 Binary files /dev/null and b/docs/guide-ru/images/rbac-access-check-2.png differ diff --git a/docs/guide-ru/images/rbac-access-check-3.graphml b/docs/guide-ru/images/rbac-access-check-3.graphml new file mode 100644 index 0000000000..8747cee0da --- /dev/null +++ b/docs/guide-ru/images/rbac-access-check-3.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-ru/images/rbac-access-check-3.png b/docs/guide-ru/images/rbac-access-check-3.png new file mode 100644 index 0000000000..1fdc0d935a Binary files /dev/null and b/docs/guide-ru/images/rbac-access-check-3.png differ diff --git a/docs/guide-ru/images/rbac-hierarchy-1.graphml b/docs/guide-ru/images/rbac-hierarchy-1.graphml new file mode 100644 index 0000000000..927b416d61 --- /dev/null +++ b/docs/guide-ru/images/rbac-hierarchy-1.graphml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-ru/images/rbac-hierarchy-1.png b/docs/guide-ru/images/rbac-hierarchy-1.png new file mode 100644 index 0000000000..7443fc7e71 Binary files /dev/null and b/docs/guide-ru/images/rbac-hierarchy-1.png differ diff --git a/docs/guide-ru/images/rbac-hierarchy-2.graphml b/docs/guide-ru/images/rbac-hierarchy-2.graphml new file mode 100644 index 0000000000..b81887b0e0 --- /dev/null +++ b/docs/guide-ru/images/rbac-hierarchy-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-ru/images/rbac-hierarchy-2.png b/docs/guide-ru/images/rbac-hierarchy-2.png new file mode 100644 index 0000000000..e77c5647c1 Binary files /dev/null and b/docs/guide-ru/images/rbac-hierarchy-2.png differ diff --git a/docs/guide-ru/images/start-country-list.png b/docs/guide-ru/images/start-country-list.png index 6994da2103..375419414d 100644 Binary files a/docs/guide-ru/images/start-country-list.png and b/docs/guide-ru/images/start-country-list.png differ diff --git a/docs/guide-ru/images/tutorial-console-help.png b/docs/guide-ru/images/tutorial-console-help.png new file mode 100644 index 0000000000..15b8b66a03 Binary files /dev/null and b/docs/guide-ru/images/tutorial-console-help.png differ diff --git a/docs/guide-ru/input-file-upload.md b/docs/guide-ru/input-file-upload.md index 0e910e1989..8456489b8a 100644 --- a/docs/guide-ru/input-file-upload.md +++ b/docs/guide-ru/input-file-upload.md @@ -1,6 +1,7 @@ Загрузка файлов =============== +<<<<<<< HEAD Загрузка файлов в Yii выполняется с помощью модели-формы, её правил валидации и некоторого кода в контроллере. Давайте посмотрим подробнее, что необходимо для загрузки файлов. @@ -9,6 +10,18 @@ Для начала требуется создать модель, которая будет обрабатывать загрузку файлов. Создайте `models/UploadForm.php` со следующим содержанием: +======= +Загрузка файлов в Yii, обычно, выполняется при помощи класса [[yii\web\UploadedFile]], который представляет каждый +загруженный файл в виде объекта `UploadedFile`. Используя [[yii\widgets\ActiveForm]] и [модели](structure-models.md) +можно легко создать безопасный механизм загрузки файлов. + + +## Создание моделей + +Как и в случае с обработкой текстового ввода, для загрузки файла можно создать класс модели и использовать его атрибут +для хранения экземпляра объекта `UploadedFile`, содержащего параметры загруженного файла. Так же, возможно +использование правил валидации модели для проверки загруженного файла. Например, +>>>>>>> master ```php namespace app\models; @@ -16,6 +29,7 @@ namespace app\models; use yii\base\Model; use yii\web\UploadedFile; +<<<<<<< HEAD /** * UploadForm is the model behind the upload form. */ @@ -44,6 +58,49 @@ class UploadForm extends Model ### Вид формы Далее создайте вид, который будет выводить форму: +======= +class UploadForm extends Model +{ + /** + * @var UploadedFile + */ + public $imageFile; + + public function rules() + { + return [ + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], + ]; + } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } +} +``` + +В примере выше атрибут `imageFile` используется для хранения экземпляра загруженного файла. Правило валидации `file`, +которое, при помощи валидатора [[yii\validators\FileValidator]], проверяет расширение загруженного файла на +соответствие с `png` или `jpg`. Метод `upload()` выполняет валидацию и сохраняет загруженный файл на сервере. + +Валидатор `file` позволяет проверять расширение, размер, тип MIME и другие параметры загруженного файла. +Подробности в разделе [Встроенные валидаторы](tutorial-core-validators.md#file). + +> Tip: При загрузке изображений лучше использовать соответствующий валидатор `image`. Данный валидатор +реализован классом [[yii\validators\ImageValidator]] и позволяет проверить корректность загруженного +изображения при помощи [расширения Imagine](https://github.com/yiisoft/yii2-imagine). + + +## Представление + +Теперь можно создать представление, отображающее поле загрузки файла: +>>>>>>> master ```php ['enctype' => 'multipart/form-data']]) ?> +<<<<<<< HEAD field($model, 'file')->fileInput() ?> +======= + field($model, 'imageFile')->fileInput() ?> + + +>>>>>>> master ``` +<<<<<<< HEAD `'enctype' => 'multipart/form-data'` необходимо для того, чтобы форма поддерживала отправку файлов. `fileInput()` формирует файловый элемент формы. ### Контроллер Теперь создайте контроллер, который соединит вид формы и модель: +======= +Важно помнить, что для корректной загрузки файла, необходим параметр формы `enctype`. Метод `fileInput()` +выведет тег ``, позволяющий пользователю выбрать файл для загрузки. + + +## Загрузка + +Теперь напишем код действия контроллера, который объединит модель и представление. +>>>>>>> master ```php namespace app\controllers; @@ -81,10 +154,17 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { +<<<<<<< HEAD $model->file = UploadedFile::getInstance($model, 'file'); if ($model->file && $model->validate()) { $model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); +======= + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // file is uploaded successfully + return; +>>>>>>> master } } @@ -93,6 +173,7 @@ class SiteController extends Controller } ``` +<<<<<<< HEAD Вместо `model->load(...)` мы используем `UploadedFile::getInstance(...)`. Так как [[\yii\web\UploadedFile|UploadedFile]] не запускает процесс валидации модели, а только предоставляет информацию о загруженном файле. Поэтому необходимо произвести валидацию самостоятельно, через `$model->validate()`, которая вызовет [[yii\validators\FileValidator|FileValidator]]. @@ -221,6 +302,74 @@ $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ``` В контроллере: +======= +При получении данных, отправленных из формы, для создания из загруженного файла экземпляра объекта `UploadedFile`, +вызывается метод [[yii\web\UploadedFile::getInstance()]]. Далее всю работу по валидации и сохранению загруженного +файла на сервере берет на себя модель. + + +## Загрузка нескольких файлов + +Для загрузки нескольких файлов достаточно внести в предыдущий код несколько небольших изменений. + +Сначала нужно добавить в правило валидации `file` параметр `maxFiles` для ограничения максимального количества +загружаемых одновременно файлов. Установка `maxFiles` равным `0` означает снятие ограничений на количество файлов, +которые могут быть загружены одновременно. Максимально разрешенное количество одновременно закачиваемых файлов +также ограничивается директивой PHP [`max_file_uploads`](http://php.net/manual/ru/ini.core.php#ini.max-file-uploads), +и по умолчанию равно 20. Метод `upload()` нужно изменить для сохранения загруженных файлов по одному. + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile[] + */ + public $imageFiles; + + public function rules() + { + return [ + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], + ]; + } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } +} +``` + +В представлении, в вызов метода `fileInput()`, нужно добавить параметр `multiple` для того, чтобы поле *input* позволяло выбирать несколько файлов одновременно: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> + + + + +``` + +В действии контроллера нужно заменить вызов `UploadedFile::getInstance()` на `UploadedFile::getInstances()` для присвоения атрибуту модели `imageFiles` массива объектов `UploadedFile`. +>>>>>>> master ```php namespace app\controllers; @@ -237,12 +386,19 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { +<<<<<<< HEAD $model->file = UploadedFile::getInstances($model, 'file'); if ($model->file && $model->validate()) { foreach ($model->file as $file) { $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); } +======= + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // file is uploaded successfully + return; +>>>>>>> master } } @@ -250,8 +406,11 @@ class SiteController extends Controller } } ``` +<<<<<<< HEAD Есть два отличия в контроллере от загрузки одного файла. Во-первых используется `UploadedFile::getInstances($model, 'file');` вместо `UploadedFile::getInstance($model, 'file');`. Первый возвратит информацию обо **всех** загруженных файлах, в то время как, как второй вернёт информацию только об одном загруженном файле. Во-вторых отличие в том, что применяется `foreach` для сохранения каждого файла. +======= +>>>>>>> master diff --git a/docs/guide-ru/input-forms.md b/docs/guide-ru/input-forms.md index b5d7fa4376..999af608b7 100644 --- a/docs/guide-ru/input-forms.md +++ b/docs/guide-ru/input-forms.md @@ -1,6 +1,7 @@ Создание форм ============== +<<<<<<< HEAD Основной способ использования форм в Yii является использоваение [[yii\widgets\ActiveForm]]. Этот подход должен быть применён, когда форма основана на модели. Кроме того, имеются дополнительные методы в [[yii\helpers\Html]], которые используются для добавления кнопок и текстовых подсказок к любой форме. @@ -11,6 +12,18 @@ Модель может основываться на классе [Active Record](db-active-record.md), который описывает некоторые данные из базы данных, или модель может основываться на базовом классе Model (происходит от [[yii\base\Model]]), который позволяет использовать произвольный набор элементов формы, например форма входа. +======= +Основной способ использования форм в Yii является использование [[yii\widgets\ActiveForm]]. Этот подход должен быть +применён, когда форма основана на модели. Кроме того, имеются дополнительные методы в [[yii\helpers\Html]], которые +используются для добавления кнопок и текстовых подсказок к любой форме. + +Форма, которая отображается на стороне клиента, в большинстве случаев, соответствует [модели](structure-models.md). +Модель, в свою очередь, проверяет данные из элементов формы на сервере (посмотрите раздел [Валидация](input-validation.md) +для более подробных сведений). Когда создаётся форма, основанная на модели, необходимо определить, что же является моделью. +Модель может основываться на классе [Active Record](db-active-record.md), который описывает некоторые данные из базы данных, +или модель может основываться на базовом классе Model (происходит от [[yii\base\Model]]), который позволяет использовать +произвольный набор элементов формы, например, форма входа. +>>>>>>> master В следующем примере показано, как создать модель формы, основанной на базовом классе Model: @@ -31,8 +44,12 @@ class LoginForm extends \yii\base\Model } ``` +<<<<<<< HEAD В контроллере, будем передать экземпляр этой модели в вид, для [[yii\widgets\ActiveForm|ActiveForm]] виджета, который генерирует форму. +======= +В контроллере будем передавать экземпляр этой модели в представление для виджета [[yii\widgets\ActiveForm|ActiveForm]], который генерирует форму. +>>>>>>> master ```php `. Для просмотра всех доступных настроек, пожалуйста обратитесь к API документации [[yii\widgets\ActiveForm]]. +<<<<<<< HEAD Для создания в форме элемента с меткой и любой применимой Javascript валадиацией, вызывается [[yii\widgets\ActiveForm::field()|ActiveForm::field()]], +======= +Для создания в форме элемента с меткой и любой применимой Javascript валидацией, вызывается [[yii\widgets\ActiveForm::field()|ActiveForm::field()]], +>>>>>>> master который возвращает экземпляр [[yii\widgets\ActiveField]]. Когда этот метод вызывается непосредственно, то результатом будет текстовый элемент (`input type="text"`). Для того, чтобы настроить элемент, можно вызвать одни за одним дополнительные методы [[yii\widgets\ActiveField|ActiveField]]: @@ -78,10 +99,17 @@ CSS класс и идентификатор ID будет прикреплён Впоследствии будет созданы `

+

title) ?>

+ + text) ?> +
+``` + +В вышеописанном коде текущая модель доступна как `$model`. Кроме этого доступны дополнительные переменные: + +- `$key`: mixed, значение ключа в соответствии с данными. +- `$index`: integer, индекс элемента данных в массиве элементов, возвращенных поставщику данных, который начинается с 0. +- `$widget`: ListView, это экземпляр виджета. + +Если необходимо послать дополнительные данные в каждый вид, то можно использовать свойство [[yii\widgets\ListView::$viewParams|$viewParams]] +как ключ-значение, например: + +```php +echo ListView::widget([ + 'dataProvider' => $dataProvider, + 'itemView' => '_post', + 'viewParams' => [ + 'fullView' => true, + 'context' => 'main-page', + // ... + ], +]); +``` + +Они также станут доступны в виде в качестве переменных. + + +GridView +-------- + +Таблица данных или GridView - это один из сверхмощных Yii виджетов. Он может быть полезен, если необходимо быстро создать +административный раздел системы. GridView использует данные, как [провайдер данных](output-data-providers.md) и отображает +каждую строку используя [[yii\grid\GridView::columns|columns]] для предоставления данных в таблице. + +Каждая строка из таблицы представлена данными из одиночной записи и колонка, как правило, представляет собой атрибут +записи (некоторые столбцы могут соответствовать сложным выражениям атрибутов или статическому тексту). + +Минимальный код, который необходим для использования GridView: + +```php +use yii\grid\GridView; +use yii\data\ActiveDataProvider; + +$dataProvider = new ActiveDataProvider([ + 'query' => Post::find(), + 'pagination' => [ + 'pageSize' => 20, + ], +]); +echo GridView::widget([ + 'dataProvider' => $dataProvider, +]); +``` + +В вышеприведённом коде сначала создаётся провайдер данных и затем используется GridView для отображения атрибутов для +каждого элемента из провайдера данных. Отображенная таблица оснащена функционалом сортировки и разбивки на страницы из +коробки. + +### Колонки таблицы + +Колонки таблицы настраиваются с помощью определённых [[yii\grid\Column]] классов, которые настраиваются в свойстве +[[yii\grid\GridView::columns|columns]] виджета GridView. В зависимости от типа колонки и их настроек, данные отображаются +по разному. По умолчанию это класс [[yii\grid\DataColumn]], который представляет атрибут модели с возможностью сортировки +и фильтрации по нему. + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + // Simple columns defined by the data contained in $dataProvider. + // Data from the model's column will be used. + 'id', + 'username', + // More complex one. + [ + 'class' => 'yii\grid\DataColumn', // can be omitted, as it is the default + 'value' => function ($data) { + return $data->name; // $data['name'] for array data, e.g. using SqlDataProvider. + }, + ], + ], +]); +``` + +Учтите, что если [[yii\grid\GridView::columns|columns]] не сконфигурирована, то Yii попытается отобразить все возможные +колонки из провайдера данных. + +### Классы колонок + +Колонки таблицы могут быть настроены, используя различные классы колонок: + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + [ + 'class' => 'yii\grid\SerialColumn', // <-- тут + // тут можно настроить дополнительные свойства + ], +``` + +В дополнение к классам колонок от Yii, вы можете самостоятельно создать свой собственный класс. + +Каждый класс колонки наследуется от [[yii\grid\Column]], так что есть некоторые общие параметры, которые можно установить +при настройке колонок. + +- [[yii\grid\Column::header|header]] позволяет установить содержание для строки заголовка. +- [[yii\grid\Column::footer|footer]] позволяет установить содержание для "подвала". +- [[yii\grid\Column::visible|visible]] определяет, должен ли столбец быть видимым. +- [[yii\grid\Column::content|content]] позволяет передавать действительный обратный вызов, который будет возвращать данные для строки.Формат следующий: + + ```php + function ($model, $key, $index, $column) { + return 'a string'; + } + ``` + +Вы можете задать различные параметры контейнера HTML через массивы: + +- [[yii\grid\Column::headerOptions|headerOptions]] +- [[yii\grid\Column::footerOptions|footerOptions]] +- [[yii\grid\Column::filterOptions|filterOptions]] +- [[yii\grid\Column::contentOptions|contentOptions]] + + +#### DataColumn + +[[yii\grid\DataColumn|Data column]] используется для отображения и сортировки данных. По умолчанию этот тип +используется для всех колонок. + +Основная настройка этой колонки - это свойство [[yii\grid\DataColumn::format|format]]. Значение этого свойства посылается +в методы `formatter` [компонента](structure-application-components.md), который по умолчанию [[\yii\i18n\Formatter|Formatter]] + +```php +echo GridView::widget([ + 'columns' => [ + [ + 'attribute' => 'name', + 'format' => 'text' + ], + [ + 'attribute' => 'birthday', + 'format' => ['date', 'php:Y-m-d'] + ], + ], +]); +``` + +В вышеприведённом коде `text` соответствует [[\yii\i18n\Formatter::asText()]]. В качестве первого аргумента для этого +метода будет передаваться значение колонки. Во второй колонки описано `date`, которая соответствует [[\yii\i18n\Formatter::asDate()]]. +В качестве первого аргумента, опять же, будет передаваться значение колонки, в то время как второй аргумент будет +'php:Y-m-d'. + +Доступный список форматов смотрите в разделе [Форматирование данных](output-formatting.md). + +Для конфигурации колонок данных также доступен короткий вид записи, который описан в API документации для [[yii\grid\GridView::columns|колонок]]. + +#### ActionColumn + +[[yii\grid\ActionColumn|ActionColumn]] отображает кнопки действия, такие как изменение или удаление для каждой строки. + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + [ + 'class' => 'yii\grid\ActionColumn', + // вы можете настроить дополнительные свойства здесь. + ], +``` + +Доступные свойства для конфигурации: + +- [[yii\grid\ActionColumn::controller|controller]] это идентификатор контроллера, который должен обрабатывать действия. + Если не установлен, то будет использоваться текущий активный контроллер. +- [[yii\grid\ActionColumn::template|template]] определяет шаблон для каждой ячейки в колонке действия. Маркеры заключённые + в фигурные скобки являются ID действием контроллера (также называются *именами кнопок* в контексте колонки действия). + Они могут быть заменены, через свойство [[yii\grid\ActionColumn::$buttons|buttons]]. Например, маркер `{view}` будет + заменён результатом из функции, определённой в `buttons['view']`. Если такая функция не может быть найдена, то маркер + заменяется на пустую строку. По умолчанию шаблон имеет вид `{view} {update} {delete}`. +- [[yii\grid\ActionColumn::buttons|buttons]] массив из функций для отображения кнопок. Ключи массива представлены как + имена кнопок (как описывалось выше), а значения представлены в качестве анонимных функций, которые выводят кнопки. Замыкания + должны использоваться в следующем виде: + + ```php + function ($url, $model, $key) { + // возвращаем HTML код для кнопки + } + ``` + где, `$url` - это URL, который будет повешен на как ссылка на кнопку, `$model` - это объект модели для текущей строки и + `$key` - это ключ для модели из провайдера данных. + +- [[yii\grid\ActionColumn::urlCreator|urlCreator]] замыкание, которое создаёт URL используя информацию из модели. Вид + замыкания должен быть таким же как и в [[yii\grid\ActionColumn::createUrl()]]. Если свойство не задано, то URL для кнопки + будет создана используя метод [[yii\grid\ActionColumn::createUrl()]]. +- [[yii\grid\ActionColumn::visibleButtons|visibleButtons]] это массив условий видимости каждой из кнопок. + Ключи массива представлены как имена кнопок (как описывалось выше), а значения представлены как булево значение или + анонимная функция. Если имя кнопки не описано в массиве, она будет отображена по умолчанию. + Замыкания должны использоваться в следующем виде: + + ```php + function ($model, $key, $index) { + return $model->status === 'editable'; // отображать ли кнопку + } + ``` + + Или вы можете передать булево значение: + + ```php + [ + 'update' => \Yii::$app->user->can('update') + ] + ``` + +#### CheckboxColumn + +[[yii\grid\CheckboxColumn|Checkbox column]] отображает колонку как флаг (сheckbox). + +Для добавления CheckboxColumn в виджет GridView, необходимо добавить его в [[yii\grid\GridView::$columns|columns]]: + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + // ... + [ + 'class' => 'yii\grid\CheckboxColumn', + // вы можете настроить дополнительные свойства здесь. + ], + ], +``` + +Пользователи могут нажимать на флаги для выделения строк в таблице. Отмеченные строки могут быть обработаны с помощью +JavaScript кода: + +```javascript +var keys = $('#grid').yiiGridView('getSelectedRows'); +// массив ключей для отмеченных строк +``` + +#### SerialColumn + +[[yii\grid\SerialColumn|Serial column]] выводит в строках номера начиная с `1` и увеличивая их по мере вывода строк. + +Использование очень простое : + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], // <-- тут + // ... +``` + + +### Сортировка данных + +> Note: Эта секция под разработкой +> +> - https://github.com/yiisoft/yii2/issues/1576 + +### Фильтрация данных + +Для фильтрации данных в GridView необходима [модель](structure-models.md), которая описывает форму для фильтрации, внося +условия в запрос поиска для провайдера данных. +Общепринятой практикой считается использование [active records](db-active-record.md) и создание для неё класса модели для +поиска, которая содержит необходимую функциональность(может быть сгенерирована через [Gii](start-gii.md)). Класс модели +для поиска должен описывать правила валидации и реализовать метод `search()`, который будет возвращать провайдер данных. + +Для поиска возможных `Post` моделей, можно создать `PostSearch` наподобие следующего примера: + +```php + $query, + ]); + + // load the search form data and validate + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + // adjust the query by adding the filters + $query->andFilterWhere(['id' => $this->id]); + $query->andFilterWhere(['like', 'title', $this->title]) + ->andFilterWhere(['like', 'creation_date', $this->creation_date]); + + return $dataProvider; + } +} + +``` + +Теперь можно использовать этот метод в контроллере, чтобы получить провайдер данных для GridView: + +```php +$searchModel = new PostSearch(); +$dataProvider = $searchModel->search(Yii::$app->request->get()); + +return $this->render('myview', [ + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, +]); +``` + +и в виде присвоить их `$dataProvider` и `$searchModel` в виджете GridView: + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + // ... + ], +]); +``` + +### Отдельная форма фильтрации + +Фильтров в шапке GridView достаточно для большинства задач, но добавление отдельной формы фильтрации не представляет +особой сложности. Она бывает полезна в случае необходимости фильтрации по полям, которые не отображаются в GridView +или особых условий фильтрации, например по диапазону дат. + +Создайте частичное представление `_search.php` со следующим содержимым: + +```php + + +
+ ['index'], + 'method' => 'get', + ]); ?> + + field($model, 'title') ?> + + field($model, 'creation_date') ?> + +
+ 'btn btn-primary']) ?> + 'btn btn-default']) ?> +
+ + +
+``` + +и добавьте его отображение в `index.php` таким образом: + +```php +render('_search', ['model' => $searchModel]) ?> +``` + +> Note: если вы используете Gii для генерации CRUD кода, отдельная форма фильтрации (`_search.php`) +генерируется по умолчанию, но закомментирована в представлении `index.php`. Вам остается только раскомментировать +эту строку и форма готова к использованию! + +Для фильтра по диапазону дат мы можем добавить дополнительные атрибуты `createdFrom` и `createdTo` в поисковую модель +(их нет в соответствующей таблице модели): + +```php +class PostSearch extends Post +{ + /** + * @var string + */ + public $createdFrom; + + /** + * @var string + */ + public $createdTo; +} +``` + +Расширим условия запроса в методе `search()`: + +```php +$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom]) + ->andFilterWhere(['<=', 'creation_date', $this->createdTo]); +``` + +И добавим соответствующие поля в форму фильтрации: + +```php +field($model, 'creationFrom') ?> + +field($model, 'creationTo') ?> +``` + +### Отображение зависимых моделей + +Бывают случаи, когда необходимо в GridView вывести в колонке значения из зависимой модели для active records, например +имя автора новости, вместо его `id`. Для этого необходимо задать [[yii\grid\GridView::$columns]] как `author.name`, если +же модель `Post` содержит зависимость с именем `author` и имя автора хранится в атрибуте `name`. GridView отобразит +имя автора, но вот сортировка и фильтрации по этому полю будет не доступна. Необходимо дополнить некоторый функционал в +`PostSearch` модель, которая была упомянута в предыдущем разделе. + +Для включения сортировки по зависимой колонки необходимо присоединить зависимую таблицу и добавить правило в компонент +Sort для провайдера данных.: + +```php +$query = Post::find(); +$dataProvider = new ActiveDataProvider([ + 'query' => $query, +]); + +// join with relation `author` that is a relation to the table `users` +// and set the table alias to be `author` +$query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]); +// enable sorting for the related column +$dataProvider->sort->attributes['author.name'] = [ + 'asc' => ['author.name' => SORT_ASC], + 'desc' => ['author.name' => SORT_DESC], +]; + +// ... +``` + +Фильтрации также необходим вызов joinWith, как описано выше. Также необходимо определить для поиска столбец в атрибутах +и правилах: + +```php +public function attributes() +{ + // add related fields to searchable attributes + return array_merge(parent::attributes(), ['author.name']); +} + +public function rules() +{ + return [ + [['id'], 'integer'], + [['title', 'creation_date', 'author.name'], 'safe'], + ]; +} +``` + +В `search()` просто добавляется другое условие фильтрации: + +```php +$query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name')]); +``` + +> Info: В коде, что выше, использует такая же строка, как и имя зависимости и псевдонима таблицы. +> Однако, когда ваш псевдоним и имя связи различаются, вы должны обратить внимание, где вы используете псевдоним, +> а где имя связи. Простым правилом для этого является использование псевдонима в каждом месте, которое используется +> для построения запроса к базе данных, и имя связи во всех других определениях, таких как `attributes()`, `rules()` и т.д. +> +> Например, если вы используете псевдоним `au` для связи с таблицей автора, то joinWith будет выглядеть так: +> +> ```php +> $query->joinWith(['author' => function($query) { $query->from(['au' => 'users']); }]); +> ``` +> Это также возможно вызвать как `$query->joinWith(['author']);`, когда псевдоним определен в определении отношения. +> +> Псевдоним должен быть использован в состоянии фильтра, но имя атрибута остается неизменным: +> +> ```php +> $query->andFilterWhere(['LIKE', 'au.name', $this->getAttribute('author.name')]); +> ``` +> +> То же самое верно и для определения сортировки: +> +> ```php +> $dataProvider->sort->attributes['author.name'] = [ +> 'asc' => ['au.name' => SORT_ASC], +> 'desc' => ['au.name' => SORT_DESC], +> ]; +> ``` +> +> Кроме того, при определении [[yii\data\Sort::defaultOrder|defaultOrder]] для сортировки необходимо использовать имя +> зависимости вместо псевдонима: +> +> ```php +> $dataProvider->sort->defaultOrder = ['author.name' => SORT_ASC]; +> ``` + +> Info: Для подробной информации по `joinWith` и запросам, выполняемым в фоновом режиме, обратитесь к +> [active record документации](db-active-record.md#joining-with-relations). + +#### Использование SQL видов для вывода данных, их сортировки и фильтрации. + +Существует и другой подход, который быстре и более удобен - SQL виды. Например, если необходимо показать таблицу из +пользователей и их профилей, то можно выбрать такой путь: + +```sql +CREATE OR REPLACE VIEW vw_user_info AS + SELECT user.*, user_profile.lastname, user_profile.firstname + FROM user, user_profile + WHERE user.id = user_profile.user_id +``` + +Теперь необходимо создать ActiveRecord, которая будет отображение данных из этого вида: + +```php + +namespace app\models\views\grid; + +use yii\db\ActiveRecord; + +class UserView extends ActiveRecord +{ + + /** + * @inheritdoc + */ + public static function tableName() + { + return 'vw_user_info'; + } + + public static function primaryKey() + { + return ['id']; + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + // define here your rules + ]; + } + + /** + * @inheritdoc + */ + public static function attributeLabels() + { + return [ + // define here your attribute labels + ]; + } + + +} +``` + +Полсе этого вы можете использовать UserView в модели поиска, без каких либо дополнительных условий по сортировки и фильтрации. +Все атрибуты будут работать из коробки. Но такая реализация имеет свои плюсы и минусы: + +- вам не надо определять условия сортировок и фильтраций. Всё работает из коробки; +- это намного быстрее данных, так как некоторые запросы уже выполнены (т.е. для каждой зависимости не нужно выполнять дополнительные запросы) +- поскольку это простое отображение данных из sql вида, то в модели будет отсутствовать некоторая доменная логика, например +такие методы как `isActive`, `isDeleted`, необходимо продублировать в классе, который описывает вид. + +### Несколько GridViews на одной странице + +Вы можете использовать больше одной GridView на одной странице. Для этого нужно внести некоторые дополнительные настройки +для того, чтобы они друг другу не мешали. +При использовании нескольких экземпляров GridView вы должны настроить различные имена параметров для сортировки и ссылки +для разбиения на страницы так, чтобы каждый GridView имел свою индивидуальный сортировку и разбиение на страницы. +Сделать это возможно через настройку [[yii\data\Sort::sortParam|sortParam]] и [[yii\data\Pagination::pageParam|pageParam]] +свойств провайдеров данных [[yii\data\BaseDataProvider::$sort|sort]] и [[yii\data\BaseDataProvider::$pagination|pagination]] + +Допустим мы хотим список моделей `Post` и `User`, для которых мы уже подготовили провайдеры данных `$userProvider` и +`$postProvider`, тогда код будет выглядеть следующим образом: + +```php +use yii\grid\GridView; + +$userProvider->pagination->pageParam = 'user-page'; +$userProvider->sort->sortParam = 'user-sort'; + +$postProvider->pagination->pageParam = 'post-page'; +$postProvider->sort->sortParam = 'post-sort'; + +echo '

Users

'; +echo GridView::widget([ + 'dataProvider' => $userProvider, +]); + +echo '

Posts

'; +echo GridView::widget([ + 'dataProvider' => $postProvider, +]); +``` + +### Использование GridView с Pjax + +> Note: Секция находится в стадии разработки + +TBD diff --git a/docs/guide-ru/output-formatting.md b/docs/guide-ru/output-formatting.md index 22bb200ac1..597c6bbc6a 100644 --- a/docs/guide-ru/output-formatting.md +++ b/docs/guide-ru/output-formatting.md @@ -2,9 +2,17 @@ ============== Для форматирования вывода Yii предоставляет класс, преобразующий данные в человеко понятный формат. +<<<<<<< HEAD [[yii\i18n\Formatter]] это класс-помощник, который зарегистрирован как [компонент приложения](structure-application-components.md), по-умолчанию под именем `formatter`. Он предоставляет набор методов для форматирования таких данных как дата/время, числа и другие часто используемые в целях локализации форматы. +======= +[[yii\i18n\Formatter]] это класс-помощник, который зарегистрирован как +[компонент приложения](structure-application-components.md), по-умолчанию под именем `formatter`. + +Он предоставляет набор методов для форматирования таких данных как дата/время, числа и другие часто используемые в целях +локализации форматы. +>>>>>>> master Formatter может быть использован двумя различными способами. 1. Напрямую используя методы форматирования (все методы форматирования имеют префикс `as`): @@ -19,8 +27,13 @@ Formatter может быть использован двумя различны ``` 2. Используя метод [[yii\i18n\Formatter::format()|format()]] и имя формата. +<<<<<<< HEAD Этот метод также используется в виджетах наподобии [[yii\grid\GridView]] и [[yii\widgets\DetailView]], в которых вы можете задать формат отображения данных в колонке через конфигурацию виджета. +======= + Этот метод также используется в виджетах на подобии [[yii\grid\GridView]] и [[yii\widgets\DetailView]], в которых + вы можете задать формат отображения данных в колонке через конфигурацию виджета. +>>>>>>> master ```php echo Yii::$app->formatter->format('2014-01-01', 'date'); // выведет: January 1, 2014 @@ -29,11 +42,21 @@ Formatter может быть использован двумя различны echo Yii::$app->formatter->format(0.125, ['percent', 2]); // выведет: 12.50% ``` +<<<<<<< HEAD Все данные, отображаемые через компонент formatter, будут локализованы, если [расширение PHP intl](http://php.net/manual/ru/book.intl.php) было установлено. Для этого вы можете настроить свойство [[yii\i18n\Formatter::locale|locale]]. Если оно не было настроено, то будет использован [[yii\base\Application::language|язык приложения]] в качестве локали. Подробнее см. [раздел интернационализация](tutorial-i18n.md). Компонент форматирования будет выбирать корректный формат для даты и чисел в соответствии с локалью, включая имена месяца и дней недели, переведённые на текущий язык. Форматирование дат также зависит от [[yii\i18n\Formatter::timeZone|временной зоны]], которая +======= +Все данные, отображаемые через компонент formatter, будут локализованы, если +[расширение PHP intl](http://php.net/manual/ru/book.intl.php) было установлено. Для этого вы можете настроить свойство +[[yii\i18n\Formatter::locale|locale]]. Если оно не было настроено, то в качестве локали будет использован +[[yii\base\Application::language|язык приложения]]. Подробнее смотрите в разделе «[интернационализация](tutorial-i18n.md)». +Компонент форматирования будет выбирать корректный формат для даты и чисел в соответствии с локалью, включая имена +месяцев и дней недели, переведённые на текущий язык. Форматирование дат также зависит от +[[yii\i18n\Formatter::timeZone|часового пояса]], которая +>>>>>>> master также будет из свойства [[yii\base\Application::timeZone|timeZone]] приложения, если она не была задана явно. Например, форматирование даты, вызванное с разной локалью, отобразит разные результаты:: @@ -42,11 +65,16 @@ Formatter может быть использован двумя различны Yii::$app->formatter->locale = 'en-US'; echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: January 1, 2014 Yii::$app->formatter->locale = 'de-DE'; +<<<<<<< HEAD echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1. Januar 2014 +======= +echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1. January 2014 +>>>>>>> master Yii::$app->formatter->locale = 'ru-RU'; echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1 января 2014 г. ``` +<<<<<<< HEAD > Обратите внимание, что форматирование может различаться между различными версиями библиотеки ICU, собранных с PHP, а также на основе того установлено ли > [расширение PHP intl] (http://php.net/manual/ru/book.intl.php) или нет. Таким образом, чтобы гарантировать, что ваш сайт будет одинаково отображать данные > во всех окружениях рекомендуется установить расширение PHP intl во всех окружениях и проверить, что версия библиотеки ICU совпадает. @@ -55,15 +83,35 @@ echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1 январ > Отметим также, что даже если установлено расширение PHP intl, форматирование даты и времени для значений года >=2038 или <=1901 > на 32-ух разрядных системах будет обращаться к реализации PHP, которая не обеспечивает локализованные имена месяца и дня, > потому что в этом случае intl будет использовать 32-ух битный UNIX timestamp. На 64-битной системе intl formatter будет работать во всех случаях, если, конечно, intl был установлен. +======= +> Обратите внимание, что форматирование может различаться между различными версиями библиотеки ICU, собранных с PHP, +> а также на основе того установлено ли [расширение PHP intl] (http://php.net/manual/ru/book.intl.php) или нет. +> Таким образом, чтобы гарантировать, что ваш сайт будет одинаково отображать данные во всех окружениях рекомендуется +> установить расширение PHP intl во всех окружениях и проверить, что версия библиотеки ICU совпадает. +> См. также: [Настройка PHP окружения для интернационализации](tutorial-i18n.md#setup-environment). +> +> Отметим также, что даже если установлено расширение PHP intl, форматирование даты и времени для значений года >=2038 +> или <=1901 на 32-ух разрядных системах будет обращаться к реализации PHP, которая не обеспечивает локализованные +> имена месяца и дня, потому что в этом случае intl будет использовать 32-ух битный UNIX timestamp. На 64-битной системе +> intl formatter будет работать во всех случаях, если, конечно, intl был установлен. +>>>>>>> master Настройка форматирования ------------------------- +<<<<<<< HEAD Форматы по-умолчанию, используемые в методах форматирования, можно настраивать через свойства [[yii\i18n\Formatter|класса форматирования]]. Вы можете задать форматирование по-умолчанию для всего приложения, настроив компонент `formatter` в вашей [конфигурации приложения](concept-configurations.md#application-configurations). Ниже приведён пример конфигурации. Чтобы узнать больше о доступных свойствах см. [[yii\i18n\Formatter|API документацию к классу Formatter]] и следующим подсекциям. +======= +Форматы по-умолчанию, используемые в методах форматирования, можно настраивать через свойства +[[yii\i18n\Formatter|класса форматирования]]. Вы можете задать форматирование по-умолчанию для всего приложения, настроив +компонент `formatter` в вашей [конфигурации приложения](concept-configurations.md#application-configurations). Ниже +приведён пример конфигурации. Чтобы узнать больше о доступных свойствах см. [[yii\i18n\Formatter|API документацию к классу Formatter]] +и следующие подсекции. +>>>>>>> master ```php 'components' => [ @@ -83,6 +131,7 @@ echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1 январ - [[yii\i18n\Formatter::asDate()|date]] - значение будет отформатировано как дата, например `January 01, 2014`. - [[yii\i18n\Formatter::asTime()|time]] - значение будет отформатировано как время, например `14:23`. +<<<<<<< HEAD - [[yii\i18n\Formatter::asDatetime()|datetime]] - значение будет отформатировано как дата и время, например `January 01, 2014 14:23`. - [[yii\i18n\Formatter::asTimestamp()|timestamp]] - значение будет отформатировано как [unix timestamp](http://en.wikipedia.org/wiki/Unix_time), например, `1412609982`. - [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - значение будет отформатировано как временной промежуток между заданной датой и @@ -95,6 +144,25 @@ echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1 январ По-умолчанию, форматирование использует сокращенный формат, который интерпретируется по-разному в зависимости от активной в данный момент локали. Поэтому дата и время будут отформатированы наиболее часто используемым способом в стране и языке пользователя. Доступны 4 разных сокращенных формата: +======= +- [[yii\i18n\Formatter::asDatetime()|datetime]] - значение будет отформатировано как дата и время, например + `January 01, 2014 14:23`. +- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - значение будет отформатировано как + [unix timestamp](http://en.wikipedia.org/wiki/Unix_time), например, `1412609982`. +- [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - значение будет отформатировано как временной промежуток между + заданной датой и текущим временем в человеко понятном формате, например: `1 час назад`. +- [[yii\i18n\Formatter::asDuration()|duration]]: значение будет отформатировано как продолжительность в человеко понятном + формате, например `1 день, 2 минуты`. + +Форматирование даты и времени для методов [[yii\i18n\Formatter::asDate()|date]], [[yii\i18n\Formatter::asTime()|time]] и +[[yii\i18n\Formatter::asDatetime()|datetime]] может быть задано глобально через конфигурацию свойств форматирования +[[yii\i18n\Formatter::$dateFormat|$dateFormat]], [[yii\i18n\Formatter::$timeFormat|$timeFormat]] и +[[yii\i18n\Formatter::$datetimeFormat|$datetimeFormat]]. + +По-умолчанию, форматирование использует сокращенный формат, который интерпретируется по-разному в зависимости от активной +в данный момент локали. Поэтому дата и время будут отформатированы наиболее часто используемым способом в стране и языке +пользователя. Доступны 4 разных сокращенных формата: +>>>>>>> master - `short` в локале `en_GB` отобразит, например, `06/10/2014` для даты и `15:58` для времени, в то время как - `medium` будет отображать `6 Oct 2014` и `15:58:42` соответственно, @@ -102,9 +170,15 @@ echo Yii::$app->formatter->asDate('2014-01-01'); // выведет: 1 январ - `full` будет отображать `Monday, 6 October 2014` и `15:58:42 GMT` соответственно. Дополнительно вы можете задать специальный формат, используя синтаксис, заданный [ICU Project](http://site.icu-project.org/), +<<<<<<< HEAD который описан в руководстве ICU по следущему адресу: . Также вы можете использовать синтаксис, который распознаётся PHP-функций [date()](http://php.net/manual/ru/function.date.php), используя строку с префиксом `php:`. +======= +который описан в руководстве ICU по следующему адресу: +. Также вы можете использовать синтаксис, который распознаётся +PHP-функцией [date()](http://php.net/manual/ru/function.date.php), используя строку с префиксом `php:`. +>>>>>>> master ```php // ICU форматирование @@ -113,12 +187,22 @@ echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06 echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06 ``` +<<<<<<< HEAD ### Временные зоны Для форматирования значений даты и времении Yii будет преобразовывать их в соответствии с [[yii\i18n\Formatter::timeZone|настроенной временной зоной]]. Поэтому предполагается, что входные значения будут в UTC, если часовой пояс не был указан явно. По этой причине рекомендуется хранить все значения даты и времени в формате UTC, предпочтительно в виде UNIX timestamp, которая всегда во временной зоне UTC по определению. Если входное значение находится в часовом поясе, отличном от UTC, часовой пояс должен быть указан явно, как в следующем примере: +======= +### Часовые пояса + +Для форматирования значений даты и времени Yii будет преобразовывать их в соответствии с +[[yii\i18n\Formatter::timeZone|настроенным часовым поясом]]. Поэтому предполагается, что входные значения будут в UTC, +если часовой пояс не был указан явно. По этой причине рекомендуется хранить все значения даты и времени в формате UTC, +предпочтительно в виде UNIX timestamp, которая всегда в часовом поясе UTC по определению. Если входное значение +находится в часовом поясе, отличном от UTC, часовой пояс должен быть указан явно, как в следующем примере: +>>>>>>> master ```php // при условии Yii::$app->timeZone = 'Europe/Berlin'; @@ -127,6 +211,7 @@ echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00 echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 ``` +<<<<<<< HEAD Начиная с версии 2.0.1 стало возможно настраивать временную зону для предполагаемых timestamp, которые не включают в себя временную зону, как во втором примере в коде выше. Вы можете задать [[yii\i18n\Formatter::defaultTimeZone]] временной зоной, которую вы используете для хранения данных. @@ -134,6 +219,16 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 > это значит, что вы, вероятно, не имеете самую свежую информацию в базе данных временных зон, установленной на вашем сервере. > Вы можете обратиться к [ICU руководству](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) > для получения подробностей об обновлении базы данных временных зон. +======= +Начиная с версии 2.0.1 стало возможно настраивать часовой пояс для предполагаемых timestamp, которые не включают в себя +часовой пояс, как во втором примере в коде выше. Вы можете задать [[yii\i18n\Formatter::defaultTimeZone]] часовым поясом, +который вы используете для хранения данных. + +> Note: Поскольку часовые пояса являются субъектом ответственности правительств по всему миру и могут часто меняться, +> это значит, что вы, вероятно, не имеете самую свежую информацию в базе данных часовых поясов, установленной на вашем сервере. +> Вы можете обратиться к [ICU руководству](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) +> для получения подробностей об обновлении базы данных часовых поясов. +>>>>>>> master > См. также: [Настройка вашего PHP окружения для интернационализации](tutorial-i18n.md#setup-environment). @@ -143,6 +238,7 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 Для форматирования числовых значений класс форматирования предоставляет следующие методы: - [[yii\i18n\Formatter::asInteger()|integer]] - значение будет отформатировано как целое число, например `42`. +<<<<<<< HEAD - [[yii\i18n\Formatter::asDecimal()|decimal]] - значение будет отформатировано как дробное число, состоящее из целого и дробной части, например: `2,542.123` или `2.542,123`. - [[yii\i18n\Formatter::asPercent()|percent]] - значение будет отформатировано как процентное значение, например `42%`. - [[yii\i18n\Formatter::asScientific()|scientific]] - значение будет отформатировано в научном формате, например: `4.2E4`. @@ -150,6 +246,19 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 Обратите внимание, чтобы эта функция работала правильно, локаль должна включать в себя часть со страной, например: `en_GB` или` en_US` потому что только язык будет неоднозначным в этом случае. - [[yii\i18n\Formatter::asSize()|size]] - значение будет отформатировано как количество байт в человеко понятном формате, например: `410 kibibytes`. - [[yii\i18n\Formatter::asShortSize()|shortSize]] - сокращённая версия [[yii\i18n\Formatter::asSize()|size]], например: `410 KiB`. +======= +- [[yii\i18n\Formatter::asDecimal()|decimal]] - значение будет отформатировано как дробное число, состоящее из целого и + дробной части, например: `2,542.123` или `2.542,123`. +- [[yii\i18n\Formatter::asPercent()|percent]] - значение будет отформатировано как процентное значение, например `42%`. +- [[yii\i18n\Formatter::asScientific()|scientific]] - значение будет отформатировано в научном формате, например: `4.2E4`. +- [[yii\i18n\Formatter::asCurrency()|currency]] - значение будет отформатировано в денежном формате, например: `£420.00`. + Обратите внимание, чтобы эта функция работала правильно, локаль должна включать в себя часть со страной, например: `en_GB` + или` en_US` потому что только язык будет неоднозначным в этом случае. +- [[yii\i18n\Formatter::asSize()|size]] - значение будет отформатировано как количество байт в человеко понятном формате, + например: `410 kibibytes`. +- [[yii\i18n\Formatter::asShortSize()|shortSize]] - сокращённая версия [[yii\i18n\Formatter::asSize()|size]], например: + `410 KiB`. +>>>>>>> master Форматирование чисел может быть скорректирована с помощью [[yii\i18n\Formatter::decimalSeparator|дробного разделителя]] и [[yii\i18n\Formatter::thousandSeparator|тысячного разделителя]], которые были заданы в соответствии с локалью. @@ -157,7 +266,12 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 Для более сложной конфигурации, [[yii\i18n\Formatter::numberFormatterOptions]] и [[yii\i18n\Formatter::numberFormatterTextOptions]] могут быть использованы для настройки внутренне используемого [класса NumberFormatter](http://php.net/manual/ru/class.numberformatter.php) +<<<<<<< HEAD Например, чтобы настроить максимальное и минимальное количество знаков после запятой, вы можете настроить свойство [[yii\i18n\Formatter::numberFormatterOptions]] как в примере ниже: +======= +Например, чтобы настроить максимальное и минимальное количество знаков после запятой, вы можете настроить свойство +[[yii\i18n\Formatter::numberFormatterOptions]] как в примере ниже: +>>>>>>> master ```php 'numberFormatterOptions' => [ @@ -169,6 +283,7 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 Остальное форматирование ---------------- +<<<<<<< HEAD Кроме форматирование даты, времени и чисел, Yii предоставляет набор других полезных средств форматирования для различных ситуаций: - [[yii\i18n\Formatter::asRaw()|raw]] - значением будет отображено как есть, это псевдо-форматирование, которое не имеет никакого эффекта, @@ -184,10 +299,38 @@ echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 - [[yii\i18n\Formatter::asUrl()|url]] - значение будет отформатировано как ссылка . - [[yii\i18n\Formatter::asBoolean()|boolean]] - значение форматируется как логическое. По-умолчанию `true` будет отображено как `Yes` и `false` как `No`, переведенное на язык приложения. Вы можете настроить это через свойство [[yii\i18n\Formatter::booleanFormat]]. +======= +Кроме форматирование даты, времени и чисел, Yii предоставляет набор других полезных средств форматирования для различных +ситуаций: + +- [[yii\i18n\Formatter::asRaw()|raw]] - значением будет отображено как есть, это псевдо-форматирование, которое не даёт + никакого эффекта, + кроме значений `null`, которые будет отформатированы в соответствии с [[nullDisplay]]. +- [[yii\i18n\Formatter::asText()|text]] - значением будет экранированный от HTML текст. + Это формат по-умолчанию, используемый в [GridView DataColumn](output-data-widgets.md#data-column). +- [[yii\i18n\Formatter::asNtext()|ntext]] - значением будет экранированный от HTML текст с новыми строками, + сконвертированными в разрывы строк. +- [[yii\i18n\Formatter::asParagraphs()|paragraphs]] - значением будет экранированный от HTML текст с параграфами, + обрамлёнными в `

` теги. +- [[yii\i18n\Formatter::asHtml()|html]] - значение будет очищено, используя [[HtmlPurifier]] с целью предотвратить XSS + атаки. Вы можете задать дополнительные параметры, такие как `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`. +- [[yii\i18n\Formatter::asEmail()|email]] - значение будет отформатировано как ссылка `mailto`. +- [[yii\i18n\Formatter::asImage()|image]] - значение будет отформатировано как тег картинки. +- [[yii\i18n\Formatter::asUrl()|url]] - значение будет отформатировано как ссылка . +- [[yii\i18n\Formatter::asBoolean()|boolean]] - значение форматируется как логическое. По-умолчанию `true` будет + отображено как `Yes` и `false` как `No`, переведенное на язык приложения. Вы можете настроить это через свойство + [[yii\i18n\Formatter::booleanFormat]]. +>>>>>>> master `null` значения ------------- Для значений `null` в PHP, класс форматирования будет отображать вместо пустой строки маркер, по-умолчанию это +<<<<<<< HEAD `(not set)`, переведенный на язык приложения. Вы можете настроить свойство [[yii\i18n\Formatter::nullDisplay|nullDisplay]] для установки собственного маркера. Если вы не хотите обрабатывать `null` значения, то установите свойство [[yii\i18n\Formatter::nullDisplay|nullDisplay]] в `null`. +======= +`(not set)`, переведенный на язык приложения. Вы можете настроить свойство [[yii\i18n\Formatter::nullDisplay|nullDisplay]] +для установки собственного маркера. Если вы не хотите обрабатывать `null` значения, то установите свойство +[[yii\i18n\Formatter::nullDisplay|nullDisplay]] в `null`. +>>>>>>> master diff --git a/docs/guide-ru/output-pagination.md b/docs/guide-ru/output-pagination.md index 6eaedcb7fa..59dac20f77 100644 --- a/docs/guide-ru/output-pagination.md +++ b/docs/guide-ru/output-pagination.md @@ -5,7 +5,7 @@ разделяется на несколько частей, каждая из которых содержит и отображает только часть данных за один раз. Такие части называются страницами, а сам процесс называется постраничным разделением данных. -Если вы используете [источник данных](output-data-providers.md) с одним из [виджетов данных](output-data-widgets.md), +Если вы используете [провайдер данных](output-data-providers.md) с одним из [виджетов данных](output-data-widgets.md), то в этом случае будет автоматически использовано постраничное разделение данных. В противном случае вам требуется создать объект [[\yii\data\Pagination]], заполнить его такими данными как [[\yii\data\Pagination::$totalCount|общее количество элементов]], [[\yii\data\Pagination::$pageSize|количество элементов на одной странице]] и [[\yii\data\Pagination::$page|текущая страница]], затем применить diff --git a/docs/guide-ru/output-sorting.md b/docs/guide-ru/output-sorting.md index 3d54f17ec6..8b7d241ed1 100644 --- a/docs/guide-ru/output-sorting.md +++ b/docs/guide-ru/output-sorting.md @@ -2,8 +2,8 @@ ======= Иногда выводимые данные требуется отсортировать в соответствии с одним или несколькими атрибутами. -Если вы используете [источник данных](output-data-providers.md) с одним из [виджетов данных](output-data-widgets.md), -сортировка будет применена автоматически. В противном случае вам должны создать экземпляр [[yii\data\Sort]], +Если вы используете [провайдер данных](output-data-providers.md) с одним из [виджетов данных](output-data-widgets.md), +сортировка будет применена автоматически. В противном случае вы должны создать экземпляр [[yii\data\Sort]], настроить его и применить к запросу. Он также может быть передан в представление, где будет использован для создания ссылок на сортировку по определенным атрибутам. @@ -50,7 +50,7 @@ foreach ($models as $model) { В примере выше, мы объявляем два атрибута, которые поддерживают сортировку: `name` and `age`. Мы передаем информацию о сортировке в запрос статьи, поэтому результаты запроса будут отсортированы согласно сортировке, установленной в объекте Sort. В представлении, мы отображаем две ссылки, -которые ведут на страницы с данными, отсортированными по соответствующим аттрибутам. +которые ведут на страницы с данными, отсортированными по соответствующим атрибутам. Класс [[yii\data\Sort|Sort]] будет автоматически принимать параметры, переданные с запросом и в соответствии с ними настраивать параметры сортировки. Вы можете регулировать список принимаемых diff --git a/docs/guide-ru/output-theming.md b/docs/guide-ru/output-theming.md index d295f6cb6a..4a4a0449b4 100644 --- a/docs/guide-ru/output-theming.md +++ b/docs/guide-ru/output-theming.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD ========= @@ -16,13 +17,37 @@ , `$this->render('about')` `SiteController`, `@app/views/site/about.php`. , , `@app/themes/basic/site/about.php`. +======= +Темизация +========= + +Темизация — это способ заменить один набор [представлений](structure-views.md) другим без переписывания кода, что +замечательно подходит для изменения внешнего вида приложения. + +Для того, чтобы начать использовать темизацию, настройте свойство [[yii\base\View::theme|theme]] компонента +приложения `view`. Конфигурация настраивает объект [[yii\base\Theme]], который отвечает за то, как как именно +заменяются файлы отображений. Главным образом, стоит настроить следующие свойства [[yii\base\Theme]]: + +- [[yii\base\Theme::basePath]]: базовая директория, в которой размещены темизированные ресурсы (CSS, JS, изображения, + и так далее). +- [[yii\base\Theme::baseUrl]]: базовый URL для доступа к темизированным ресурсам. +- [[yii\base\Theme::pathMap]]: правила замены файлов представлений. Подробно описаны далее. + +Например, если вы вызываете `$this->render('about')` в `SiteController`, то будет использоваться файл отображения +`@app/views/site/about.php`. Тем не менее, если вы включите темизацию как показано ниже, то вместо него будет +использоваться `@app/themes/basic/site/about.php`. +>>>>>>> master ```php return [ 'components' => [ 'view' => [ 'theme' => [ +<<<<<<< HEAD 'basePath' => '@app/themes/basic' +======= + 'basePath' => '@app/themes/basic', +>>>>>>> master 'baseUrl' => '@web/themes/basic', 'pathMap' => [ '@app/views' => '@app/themes/basic', @@ -33,11 +58,19 @@ return [ ]; ``` +<<<<<<< HEAD > : . URL. [[yii\base\Theme]] [[yii\base\View::theme]]. , , ( view `$this`): +======= +> Info: При настройке тем поддерживаются псевдонимы пути. При замене отображений они преобразуются в реальные + пути в файловой системе или URL. + +Вы можете обратиться к объекту [[yii\base\Theme]] через свойство [[yii\base\View::theme]]. Например, +в файле отображения, это будет выглядеть следующим образом (объект view доступен как `$this`): +>>>>>>> master ```php $theme = $this->theme; @@ -49,6 +82,7 @@ $url = $theme->getUrl('img/logo.gif'); $file = $theme->getPath('img/logo.gif'); ``` +<<<<<<< HEAD [[yii\base\Theme::pathMap]] , . - , , . : @@ -60,6 +94,19 @@ $file = $theme->getPath('img/logo.gif'); ## , , [[yii\base\Theme::pathMap]] : +======= +Свойство [[yii\base\Theme::pathMap]] определяет то, как заменяются файлы представлений. Свойство принимает массив пар +ключ-значение где ключи являются путями к оригинальным файлам, которые мы хотим заменить, а значения — соответствующими +путями к файлам из темы. Замена основана на частичном совпадении: если путь к представлению начинается с любого из ключей +массива [[yii\base\Theme::pathMap|pathMap]], то соответствующая ему часть будет заменена значением из того же массива. +Для приведённой выше конфигурации `@app/views/site/about.php` частично совпадает с ключом `@app/views` и будет +заменён на `@app/themes/basic/site/about.php`. + + +## Темизация модулей + +Для того, чтобы темизировать модули, свойство [[yii\base\Theme::pathMap]] может быть настроено следующим образом: +>>>>>>> master ```php 'pathMap' => [ @@ -68,12 +115,21 @@ $file = $theme->getPath('img/logo.gif'); ], ``` +<<<<<<< HEAD `@app/modules/blog/views/comment/index.php` `@app/themes/basic/modules/blog/views/comment/index.php`. ## , [[yii\base\Theme::pathMap]] : +======= +Это позволит вам темизировать `@app/modules/blog/views/comment/index.php` в `@app/themes/basic/modules/blog/views/comment/index.php`. + + +## Темизация виджетов + +Для того, чтобы темизировать виджеты вы можете настроить свойство [[yii\base\Theme::pathMap]] следующим образом: +>>>>>>> master ```php 'pathMap' => [ @@ -82,6 +138,7 @@ $file = $theme->getPath('img/logo.gif'); ], ``` +<<<<<<< HEAD `@app/widgets/currency/views/index.php` `@app/themes/basic/widgets/currency/index.php`. @@ -90,6 +147,16 @@ $file = $theme->getPath('img/logo.gif'); , , , . . : +======= +Это позволит вам темизировать `@app/widgets/currency/views/index.php` в `@app/themes/basic/widgets/currency/index.php`. + + +## Наследование тем + +Иногда требуется создать базовую тему, задающую общий вид приложения и далее изменять этот вид в зависимости, например, +от сегодняшнего праздника. Добиться этого можно при помощи наследования тем. При этом один путь к файлу ставится в +соответствие нескольким путям из темы: +>>>>>>> master ```php 'pathMap' => [ @@ -100,7 +167,14 @@ $file = $theme->getPath('img/logo.gif'); ] ``` +<<<<<<< HEAD `@app/views/site/about.php` `@app/themes/christmas/site/index.php`, `@app/themes/basic/site/index.php` , . , . `@app/themes/basic`, `@app/themes/christmas`. +======= +В этом случае представление `@app/views/site/index.php` темизируется либо в `@app/themes/christmas/site/index.php`, +либо в `@app/themes/basic/site/index.php` в зависимости от того, в какой из тем есть нужный файл. Если файлы присутствуют +и там и там, используется первый из них. На практике большинство темизированных файлов будут расположены +в `@app/themes/basic`, а их версии для праздников в `@app/themes/christmas`. +>>>>>>> master diff --git a/docs/guide-ru/rest-authentication.md b/docs/guide-ru/rest-authentication.md index 84b89b5db4..7b69341f58 100644 --- a/docs/guide-ru/rest-authentication.md +++ b/docs/guide-ru/rest-authentication.md @@ -4,7 +4,7 @@ В отличие от Web-приложений, RESTful API обычно не сохраняют информацию о состоянии, а это означает, что сессии и куки использовать не следует. Следовательно, раз состояние аутентификации пользователя не может быть сохранено в сессиях или куках, каждый запрос должен приходить вместе с определенным видом параметров аутентификации. Общепринятая практика состоит в том, -что для аутентификации пользователя с каждый запросом отправляется секретный токен доступа. Так как токен доступа +что для аутентификации пользователя с каждым запросом отправляется секретный токен доступа. Так как токен доступа может использоваться для уникальной идентификации и аутентификации пользователя, **запросы к API всегда должны отсылаться через протокол HTTPS, чтобы предотвратить атаки «человек посередине» (англ. "man-in-the-middle", MitM)**. @@ -25,7 +25,8 @@ Yii поддерживает все выше перечисленные мето Чтобы включить аутентификацию для ваших API, выполните следующие шаги: -1. У компонента приложения `user` установите свойство [[yii\web\User::enableSession|enableSession]] равным false. +1. У [компонента приложения](structure-application-components.md) `user` установите свойство + [[yii\web\User::enableSession|enableSession]] равным false. 2. Укажите, какие методы аутентификации вы планируете использовать, настроив поведение `authenticator` в ваших классах REST-контроллеров. 3. Реализуйте метод [[yii\web\IdentityInterface::findIdentityByAccessToken()]] в вашем [[yii\web\User::identityClass|классе UserIdentity]]. @@ -35,16 +36,16 @@ Yii поддерживает все выше перечисленные мето аутентификации пользователя НЕ БУДЕТ сохраняться между запросами с использованием сессий. Вместо этого аутентификация будет выполняться для каждого запроса, что достигается шагами 2 и 3. -> Подсказка: если вы разрабатываете RESTful API в пределах приложения, вы можете настроить свойство - [[yii\web\User::enableSession|enableSession]] компонента приложения `user` в конфигурации приложения. Если вы - разрабатываете RESTful API как модуль, можете добавить следующую строчку в метод `init()` модуля: +> Tip: если вы разрабатываете RESTful API в пределах приложения, вы можете настроить свойство +> [[yii\web\User::enableSession|enableSession]] компонента приложения `user` в конфигурации приложения. Если вы +> разрабатываете RESTful API как модуль, можете добавить следующую строчку в метод `init()` модуля: > ```php -public function init() -{ - parent::init(); - \Yii::$app->user->enableSession = false; -} -``` +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` Например, для использования HTTP Basic Auth, вы можете настроить свойство `authenticator` следующим образом: diff --git a/docs/guide-ru/rest-controllers.md b/docs/guide-ru/rest-controllers.md index b7024828cd..c8ca10713f 100644 --- a/docs/guide-ru/rest-controllers.md +++ b/docs/guide-ru/rest-controllers.md @@ -53,7 +53,7 @@ public function actionView($id) * [[yii\filters\ContentNegotiator|contentNegotiator]]: обеспечивает согласование содержимого, более подробно описан в разделе [Форматирование ответа](rest-response-formatting.md); * [[yii\filters\VerbFilter|verbFilter]]: обеспечивает проверку HTTP-метода; -* [[yii\filters\AuthMethod|authenticator]]: обеспечивает аутентификацию пользователя, более подробно описан +* [[yii\filters\auth\AuthMethod|authenticator]]: обеспечивает аутентификацию пользователя, более подробно описан в разделе [Аутентификация](rest-authentication.md); * [[yii\filters\RateLimiter|rateLimiter]]: обеспечивает ограничение частоты запросов, более подробно описан в разделе [Ограничение частоты запросов](rest-rate-limiting.md). @@ -80,7 +80,7 @@ public function behaviors() ## Наследование от `ActiveController` Если ваш класс контроллера наследуется от [[yii\rest\ActiveController]], вам следует установить -значение его свойства [[yii\rest\ActiveController::modelClass||modelClass]] равным имени класса ресурса, +значение его свойства [[yii\rest\ActiveController::modelClass|modelClass]] равным имени класса ресурса, который вы планируете обслуживать с помощью этого контроллера. Класс ресурса должен быть унаследован от [[yii\db\ActiveRecord]]. @@ -150,4 +150,4 @@ public function checkAccess($action, $model = null, $params = []) Метод `checkAccess()` будет вызван действиями по умолчанию контроллера [[yii\rest\ActiveController]]. Если вы создаёте новые действия и хотите в них выполнять контроль доступа, вы должны вызвать этот метод явно в своих новых действиях. -> Подсказка: вы можете реализовать метод `checkAccess()` с помощью ["Контроля доступа на основе ролей" (RBAC)](security-authorization.md). +> Tip: вы можете реализовать метод `checkAccess()` с помощью ["Контроля доступа на основе ролей" (RBAC)](security-authorization.md). diff --git a/docs/guide-ru/rest-error-handling.md b/docs/guide-ru/rest-error-handling.md index 003df4665e..feb2626d6b 100644 --- a/docs/guide-ru/rest-error-handling.md +++ b/docs/guide-ru/rest-error-handling.md @@ -17,10 +17,13 @@ Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 { +<<<<<<< HEAD <<<<<<< HEAD "type": "yii\\web\\NotFoundHttpException", ======= >>>>>>> yiichina/master +======= +>>>>>>> master "name": "Not Found Exception", "message": "The requested resource was not found.", "code": 0, @@ -41,17 +44,24 @@ Content-Type: application/json; charset=UTF-8 * `403`: Аутентифицированному пользователю не разрешен доступ к указанной точке входа API. * `404`: Запрошенный ресурс не существует. * `405`: Метод не поддерживается. Сверьтесь со списком поддерживаемых HTTP-методов в заголовке `Allow`. -* `415`: Неподдерживаемый тип данных. Запрашивается неправильный тип данных или номер версии. +* `415`: Не поддерживаемый тип данных. Запрашивается неправильный тип данных или номер версии. * `422`: Проверка данных завершилась неудачно (в ответе на `POST`-запрос, например). Подробные сообщения об ошибках смотрите в теле ответа. * `429`: Слишком много запросов. Запрос отклонен из-за превышения ограничения частоты запросов. * `500`: Внутренняя ошибка сервера. Возможная причина — ошибки в самой программе. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master ## Свой формат ответа с ошибкой Вам может понадобиться изменить формат ответа с ошибкой. Например, вместо использования разных статусов ответа HTTP +<<<<<<< HEAD для разных ошибок, вы можете всегда отавать статус 200, а реальный код статуса отдавать как часть JSON ответа: +======= +для разных ошибок, вы можете всегда отдавать статус 200, а реальный код статуса отдавать как часть JSON ответа: +>>>>>>> master ``` HTTP/1.1 200 OK @@ -95,5 +105,9 @@ return [ ``` Приведённый выше код изменит формат ответа (как для удачного запроса, так и для ошибок) если передан `GET`-параметр +<<<<<<< HEAD `suppress_response_code`. >>>>>>> yiichina/master +======= +`suppress_response_code`. +>>>>>>> master diff --git a/docs/guide-ru/rest-quick-start.md b/docs/guide-ru/rest-quick-start.md index ad3655f710..64e82532fa 100644 --- a/docs/guide-ru/rest-quick-start.md +++ b/docs/guide-ru/rest-quick-start.md @@ -57,12 +57,29 @@ class UserController extends ActiveController ] ``` -Настройки выше добавляет правило для контроллера `user`, которое предоставляет доступ к данным пользователя через красивые +Настройки выше добавляют правило для контроллера `user`, которое предоставляет доступ к данным пользователя через красивые URL и логичные глаголы HTTP. + +## Включение JSON на прием данных + +Для того чтобы API мог принимать данные в формате JSON, сконфигурируйте [[yii\web\Request::$parsers|parsers]] свойство у компонента `request` [application component](structure-application-components.md) на использование [[yii\web\JsonParser]] JSON данных на входе: + +```php +'request' => [ + 'parsers' => [ + 'application/json' => 'yii\web\JsonParser', + ] +] +``` + +> Note: Конфигурация, приведенная выше необязательна. Без приведенной выше конфигурации, API сможет определить только + `application/x-www-form-urlencoded` и `multipart/form-data` форматы. + + ## Пробуем -Вот так просто мы и создали RESTful API для доступа к данным пользователя. Api нашего сервиса, сейчас включает в себя: +Вот так просто мы и создали RESTful API для доступа к данным пользователя. API нашего сервиса сейчас включает в себя: * `GET /users`: получение постранично списка всех пользователей; * `HEAD /users`: получение метаданных листинга пользователей; @@ -74,7 +91,7 @@ URL и логичные глаголы HTTP. * `OPTIONS /users`: получение поддерживаемых методов, по которым можно обратится к `/users`; * `OPTIONS /users/123`: получение поддерживаемых методов, по которым можно обратится к `/users/123`. -> Информация: Yii автоматически использует множественное число от имени контроллера в URL. +> Info: Yii автоматически использует множественное число от имени контроллера в URL. Пробуем получить ответы по API используя `curl`: @@ -142,11 +159,11 @@ Content-Type: application/xml ``` -> Подсказка: Вы можете получить доступ к API через веб-браузер, введя адрес `http://localhost/users`. Но в этом случае, +> Tip: Вы можете получить доступ к API через веб-браузер, введя адрес `http://localhost/users`. Но в этом случае для передачи определённых заголовков вам, скорее всего, потребуются дополнительные плагины для браузера. Если внимательно посмотреть результат ответа, то можно обнаружить, что в заголовках есть информация об общем числе записей, -количестве страниц и т.д. Тут так же можно обнаружить ссылки на другие страницы, как, например, +количестве страниц и т. д. Тут так же можно обнаружить ссылки на другие страницы, как, например, `http://localhost/users?page=2`. Перейдя по ней можно получить вторую страницу данных пользователей. Используя параметры `fields` и `expand` в URL, можно указать, какие поля должны быть включены в результат. Например, @@ -154,14 +171,14 @@ Content-Type: application/xml только `id` и `email`. -> Информация: Вы наверное заметили, что при обращении к `http://localhost/users` мы получаем информацию с полями, +> Info: Вы наверное заметили, что при обращении к `http://localhost/users` мы получаем информацию с полями, > которые нежелательно показывать, такими как `password_hash` и `auth_key`. Вы можете и должны отфильтровать их как -> описано в разделе «[Форматирование ответа](rest-response-formatting.md)». +> описано в разделе «[Ресурсы](rest-resources.md)». ## Резюме -Используя Yii в качестве RESTful API фреймворка, мы используем реализуем точки входа API как действия контроллеров. +Используя Yii в качестве RESTful API фреймворка, мы реализуем точки входа API как действия контроллеров. Контроллер используется для организации действий, которые относятся к определённому типу ресурса. Ресурсы представлены в виде моделей данных, которые наследуются от класса [[yii\base\Model]]. diff --git a/docs/guide-ru/rest-rate-limiting.md b/docs/guide-ru/rest-rate-limiting.md index 7c4c900320..da06e2218b 100644 --- a/docs/guide-ru/rest-rate-limiting.md +++ b/docs/guide-ru/rest-rate-limiting.md @@ -20,6 +20,28 @@ с данными текущего аутентифицированного пользователя. Для улучшения производительности можно попробовать хранить эту информацию в кэше или NoSQL хранилище. +Реализация в модели `User` может быть, например, такой: + +```php +public function getRateLimit($request, $action) +{ + return [$this->rateLimit, 1]; // $rateLimit запросов в секунду +} + +public function loadAllowance($request, $action) +{ + return [$this->allowance, $this->allowance_updated_at]; +} + +public function saveAllowance($request, $action, $allowance, $timestamp) +{ + $this->allowance = $allowance; + $this->allowance_updated_at = $timestamp; + $this->save(); +} +``` + + Как только соответствующий интерфейс будет реализован в классе identity, Yii начнёт автоматически проверять ограничения частоты запросов при помощи [[yii\filters\RateLimiter]], фильтра действий для [[yii\rest\Controller]]. При превышении ограничений будет выброшено исключение [[yii\web\TooManyRequestsHttpException]]. diff --git a/docs/guide-ru/rest-resources.md b/docs/guide-ru/rest-resources.md index 6730f1eeaa..82f1568b0b 100644 --- a/docs/guide-ru/rest-resources.md +++ b/docs/guide-ru/rest-resources.md @@ -19,8 +19,8 @@ RESTful API строятся вокруг доступа к *ресурсам* ## Поля -Когда ресурс включается в ответ RESTful API, необходимо сеарилизовать его в строку. Yii разбивает этот процесс на два этапа. -Сначала ресурс конвертируется в массив при помощи [[yii\rest\Serializer]]. На втором этапе массив сеарилизуется в строку +Когда ресурс включается в ответ RESTful API, необходимо серилеазовать его в строку. Yii разбивает этот процесс на два этапа. +Сначала ресурс конвертируется в массив при помощи [[yii\rest\Serializer]]. На втором этапе массив сериализуется в строку заданного формата (например, JSON или XML) при помощи [[yii\web\ResponseFormatterInterface|форматтера ответа]]. Именно на этом стоит сосредоточится при разработке класса ресурса. @@ -84,7 +84,7 @@ public function fields() } ``` -> Внимание: По умолчанию все атрибуты модели будут включены в ответы API. Вы должны убедиться в том, что отдаются +> Warning: По умолчанию все атрибуты модели будут включены в ответы API. Вы должны убедиться в том, что отдаются > только безопасные данные. В противном случае для исключения небезопасных полей необходимо переопределить метод > `fields()`. В приведённом выше примере мы исключаем `auth_key`, `password_hash` и `password_reset_token`. diff --git a/docs/guide-ru/rest-response-formatting.md b/docs/guide-ru/rest-response-formatting.md index 27b9698882..388db1ee72 100644 --- a/docs/guide-ru/rest-response-formatting.md +++ b/docs/guide-ru/rest-response-formatting.md @@ -8,17 +8,21 @@ 2. Конвертирует объекты ресурсов в массивы, как описано в секции [Ресурсы](rest-resources.md). Этим занимается [[yii\rest\Serializer]]. 3. Конвертирует массивы в строки исходя из формата, определенного на этапе согласование содержимого. Это задача для - [[yii\web\ResponseFormatterInterface|форматера ответов]], регистрируемого с помощью компонента приложения + [[yii\web\ResponseFormatterInterface|форматтера ответов]], регистрируемого с помощью компонента приложения [[yii\web\Response::formatters|response]]. ## Согласование содержимого +<<<<<<< HEAD <<<<<<< HEAD Yii поддерживает согласование содержимого с помощью фильтра [yii\filters\ContentNegotiator]]. Базовый класс ======= Yii поддерживает согласование содержимого с помощью фильтра [[yii\filters\ContentNegotiator]]. Базовый класс >>>>>>> yiichina/master +======= +Yii поддерживает согласование содержимого с помощью фильтра [[yii\filters\ContentNegotiator]]. Базовый класс +>>>>>>> master контроллера RESTful API - [[yii\rest\Controller]] - использует этот фильтр под именем `contentNegotiator`. Фильтр обеспечивает соответствие формата ответа и определяет используемый язык. Например, если RESTful API запрос содержит следующий заголовок: diff --git a/docs/guide-ru/rest-routing.md b/docs/guide-ru/rest-routing.md index d6ca0e9931..66a96c1caa 100644 --- a/docs/guide-ru/rest-routing.md +++ b/docs/guide-ru/rest-routing.md @@ -6,8 +6,8 @@ На деле вам обычно хочется включить «красивые» URL-адреса и использовать все преимущества HTTP-методов (HTTP-verbs). Например, чтобы запрос `POST /users` означал обращение к действию `user/create`. -Это может быть легко сделано с помощью настройки компонента приложения `urlManager` в -конфигурации приложения следующим образом: +Это может быть легко сделано с помощью настройки [компонента приложения](structure-application-components.md) +`urlManager` в конфигурации приложения следующим образом: ```php 'urlManager' => [ @@ -70,10 +70,12 @@ 'extraPatterns' => [ 'GET search' => 'search', ], +] ``` Как вы могли заметить, ID контроллера `user` в этих точках входа используется в форме множественного числа (как `users`). <<<<<<< HEAD +<<<<<<< HEAD Это происходит потому, что [[yii\rest\UrlRule]] автоматически приводит идентификаторы контроллеров к множественной форме для использования в точках входа. Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false, или, если вы хотите использовать какие-то особые имена, вы можете настроить свойство [[yii\rest\UrlRule::controller]]. @@ -82,6 +84,12 @@ Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false. > Информация: Приведение ID контроллера к множественной форме производится в методе [[yii\helpers\Inflector::pluralize()]]. +======= +Это происходит потому, что [[yii\rest\UrlRule]] автоматически приводит идентификаторы контроллеров к множественной форме. +Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false. + +> Info: Приведение ID контроллера к множественной форме производится в методе [[yii\helpers\Inflector::pluralize()]]. +>>>>>>> master При этом соблюдаются правила английского языка. Например, `box` будет преобразован в `boxes`, а не в `boxs`. В том случае, если автоматическое приведение к множественному числу вам не подходит, вы можете настроить @@ -94,4 +102,7 @@ 'controller' => ['u' => 'user'], ] ``` +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide-ru/rest-versioning.md b/docs/guide-ru/rest-versioning.md index 07f83f3ccb..02885d827b 100644 --- a/docs/guide-ru/rest-versioning.md +++ b/docs/guide-ru/rest-versioning.md @@ -1,6 +1,7 @@ Версионирование =============== +<<<<<<< HEAD <<<<<<< HEAD Свои API вам следует версионировать. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом, при работе с API у вас обычно нет контроля над клиентским кодом, работающим с вашими API. Поэтому всегда, когда это только возможно, @@ -8,14 +9,21 @@ следует увеличить номер версии. Почитайте про [семантическое версионирование](http://semver.org/) для получения более подробной информации о возможных способах нумерации различных версий ваших API. ======= +======= +>>>>>>> master Хороший API должен быть *версионирован*: изменения и новые возможности реализуются в новых версиях API, а не в одной и той же версии. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом, API используются клиентами, код которых вы не контролируете. Поэтому, обратная совместимость (BC) должна по возможности сохраняться. Если ломающее её изменение необходимо, делать его нужно в новой версии API. Существующие клиенты могут продолжать использовать старую, совместимую с ними версию API. Новые или обновлённые клиенты могут использовать новую версию. +<<<<<<< HEAD >>>>>>> yiichina/master +======= + +> Tip: Чтобы узнать больше о выборе версий обратитесь к [Semantic Versioning](http://semver.org/). +>>>>>>> master Общей практикой при реализации версионирования API является включение номера версии в URL-адрес вызова API-метода. Например, `http://example.com/v1/users` означает вызов API `/users` версии 1. Другой способ версионирования API, @@ -74,9 +82,13 @@ api/ User.php Post.php <<<<<<< HEAD +<<<<<<< HEAD ======= Module.php >>>>>>> yiichina/master +======= + Module.php +>>>>>>> master v2/ controllers/ UserController.php @@ -85,9 +97,13 @@ api/ User.php Post.php <<<<<<< HEAD +<<<<<<< HEAD ======= Module.php >>>>>>> yiichina/master +======= + Module.php +>>>>>>> master ``` Конфигурация вашего приложения могла бы выглядеть так: @@ -96,6 +112,7 @@ api/ return [ 'modules' => [ 'v1' => [ +<<<<<<< HEAD <<<<<<< HEAD 'basePath' => '@app/modules/v1', ], @@ -107,6 +124,12 @@ return [ 'v2' => [ 'class' => 'app\modules\v2\Module', >>>>>>> yiichina/master +======= + 'class' => 'app\modules\v1\Module', + ], + 'v2' => [ + 'class' => 'app\modules\v2\Module', +>>>>>>> master ], ], 'components' => [ @@ -126,11 +149,15 @@ return [ В результате `http://example.com/v1/users` возвратит список пользователей API версии 1, в то время как `http://example.com/v2/users` вернет список пользователей версии 2. +<<<<<<< HEAD <<<<<<< HEAD При использовании модулей код API различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно ======= Благодаря использованию модулей код API различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно >>>>>>> yiichina/master +======= +Благодаря использованию модулей код API различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно +>>>>>>> master повторное использование кода между модулями через общие базовые классы и другие разделяемые классы. Для работы с минорными номерами версий вы можете использовать преимущества согласования содержимого, @@ -141,6 +168,7 @@ return [ Например, если запрос посылается с HTTP-заголовком `Accept: application/json; version=v1`, то после согласования содержимого свойство [[yii\web\Response::acceptParams]] будет содержать значение `['version' => 'v1']`. +<<<<<<< HEAD <<<<<<< HEAD На основе информации о версии из `acceptParams` вы можете написать условный код в таких местах, как действия, классы ресурсов, сериализаторы и т.д. @@ -148,6 +176,10 @@ return [ На основе информации о версии из `acceptParams` вы можете выбирать поведение в действиях, классах ресурсов, сериализаторах и т.д. >>>>>>> yiichina/master +======= +На основе информации о версии из `acceptParams` вы можете выбирать поведение в действиях, классах ресурсов, +сериализаторах и т.д. +>>>>>>> master Так как минорные версии требуют поддержания обратной совместимости, будем надеяться, что в вашем коде не так уж много проверок на номер версии. В противном случае велики шансы, что вам нужна новая мажорная версия. diff --git a/docs/guide-ru/runtime-bootstrapping.md b/docs/guide-ru/runtime-bootstrapping.md index b77f3a99c3..62c5e40caa 100644 --- a/docs/guide-ru/runtime-bootstrapping.md +++ b/docs/guide-ru/runtime-bootstrapping.md @@ -27,15 +27,21 @@ участвовать в полном жизненном цикле процесса обработки запроса. Например, если модуль должен зарегистрировать дополнительные правила парсинга URL, то он должен быть указан в свойстве [предзагрузка](structure-applications.md#bootstrap), чтобы новые правила URL были учтены при обработке запроса. +<<<<<<< HEAD <<<<<<< HEAD В производственном режиме включите байткод кэшеры, такие как APC, для минимизации времени необходимого на подключение и парсинг php файлов. ======= +======= +>>>>>>> master В производственном режиме включите байткод кэшеры, такие как [PHP OPcache] или [APC], для минимизации времени подключения и парсинг php файлов. [PHP OPcache]: http://php.net/manual/ru/intro.opcache.php [APC]: http://php.net/manual/ru/book.apc.php +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Некоторые большие приложения могут иметь сложную [конфигурацию](concept-configurations.md), которая разделена на несколько мелких файлов. Если это тот самый случай, возможно вам стоит кэшировать весь конфигурационный файл и загружать его прямо из кэша до создания объекта diff --git a/docs/guide-ru/runtime-handling-errors.md b/docs/guide-ru/runtime-handling-errors.md index cbfcab56e8..8379d0d620 100644 --- a/docs/guide-ru/runtime-handling-errors.md +++ b/docs/guide-ru/runtime-handling-errors.md @@ -1,14 +1,46 @@ Обработка ошибок ================ +<<<<<<< HEAD <<<<<<< HEAD > Раздел в разработке. +======= +В состав Yii входит встроенный [[yii\web\ErrorHandler|обработчик ошибок]], делающий работу с ошибками гораздо более +приятным занятием. А именно: +>>>>>>> master -Обработка ошибок в Yii происходит несколько иначе, чем в обычном PHP. Во-первых, все нефатальные ошибки в Yii преобразуются в *исключения*: +* Все не фатальные ошибки PHP (то есть warning, notice) конвертируются в исключения, которые можно перехватывать. +* Исключения и фатальные ошибки PHP отображаются в режиме отладки с детальным стеком вызовов и исходным кодом. +* Можно использовать для отображения ошибок [действие контроллера](structure-controllers.md#actions). +* Поддерживаются различные форматы ответа. + +По умолчанию [[yii\web\ErrorHandler|обработчик ошибок]] включен. Вы можете выключить его объявив константу +`YII_ENABLE_ERROR_HANDLER` со значением false во [входном скрипте](structure-entry-scripts.md) вашего приложения. + + +## Использование обработчика ошибок + +[[yii\web\ErrorHandler|Обработчик ошибок]] регистрируется в качестве [компонента приложения](structure-application-components.md) +с именем `errorHandler`. Вы можете настраивать его следующим образом: + +```php +return [ + 'components' => [ + 'errorHandler' => [ + 'maxSourceLines' => 20, + ], + ], +]; +``` + +С приведённой выше конфигурацией на странице ошибки будет отображаться до 20 строк исходного кода. + +Как уже было упомянуто, обработчик ошибок конвертирует все не фатальные ошибки PHP в перехватываемые исключения. +Это означает что можно поступать с ошибками следующим образом: ```php -use yii\base\ErrorException; use Yii; +<<<<<<< HEAD ======= В состав Yii входит встроенный [[yii\web\ErrorHandler|обработчик ошибок]], делающий работу с ошибками гораздо более приятным занятием. А именно: @@ -46,31 +78,66 @@ return [ use Yii; use yii\base\ErrorException; >>>>>>> yiichina/master +======= +use yii\base\ErrorException; +>>>>>>> master try { 10/0; } catch (ErrorException $e) { +<<<<<<< HEAD <<<<<<< HEAD Yii::warning("Попытка деления на ноль."); ======= Yii::warning("Деление на ноль."); >>>>>>> yiichina/master +======= + Yii::warning("Деление на ноль."); +>>>>>>> master } // можно продолжать выполнение ``` +<<<<<<< HEAD <<<<<<< HEAD Как это видно из примера, вы можете обрабатывать ошибки, используя конструкцию `try`-`catch`. +======= +Если вам необходимо показать пользователю страницу с ошибкой, говорящей ему о том, что его запрос не верен или не +должен был быть сделан, вы можете выкинуть [[yii\web\HttpException|исключение HTTP]], такое как +[[yii\web\NotFoundHttpException]]. Обработчик ошибок корректно выставит статус код HTTP для ответа и использует +подходящий вид страницы ошибки. -Во-вторых, даже фатальные ошибки в Yii показываются в красивом виде. Это значит, что при отладке кода, вы можете отслеживать причины фатальных -ошибок. Это позволяет быстрее находить причины возникших проблем. +```php +use yii\web\NotFoundHttpException; + +throw new NotFoundHttpException(); +``` -Рендеринг ошибок в произвольном действии контроллера ----------------------------------------------------- +## Настройка отображения ошибок -Обычная страница вывода ошибок Yii не только хороша в разработке, но и приемлема для уже развернутых проектов, если `YII_DEBUG` выключена в начальном загрузочном скрипте `index.php`. Но иногда хочется изменить внешний вид страницы с ошибками, чтобы лучше приспособить ее под свой проект. +[[yii\web\ErrorHandler|Обработчик ошибок]] меняет отображение ошибок в зависимости от значения константы `YII_DEBUG`. +При `YII_DEBUG` равной true (режим отладки), обработчик ошибок будет отображать для облегчения отладки детальный стек +вызовов и исходный код. При `YII_DEBUG` равной false отображается только сообщение об ошибке, тем самым не позволяя +получить информацию о внутренностях приложения. +>>>>>>> master +> Info: Если исключение является наследником [[yii\base\UserException]], стек вызовов не отображается вне + зависимости от значения `YII_DEBUG` так как такие исключения считаются ошибками пользователя и исправлять что-либо + разработчику не требуется. + +По умолчанию [[yii\web\ErrorHandler|обработчик ошибок]] показывает ошибки используя два [представления](structure-views.md): + +* `@yii/views/errorHandler/error.php`: используется для отображения ошибок БЕЗ стека вызовов. + При `YII_DEBUG` равной false используется только это преставление. +* `@yii/views/errorHandler/exception.php`: используется для отображения ошибок СО стеком вызовов. + +Вы можете настроить свойства [[yii\web\ErrorHandler::errorView|errorView]] и [[yii\web\ErrorHandler::exceptionView|exceptionView]] +для того, чтобы использовать свои представления. + +### Использование действий для отображения ошибок + +<<<<<<< HEAD Самый легкий способ создать свою страницу для отображения ошибок - использовать свое действие (action) для рендеринга сообщений об ошибке. Сначала нужно дать приложению понять, что вы хотите использовать свое действие для обработки ошибок. Для этого нужно сконфигурировать компонент `errorHandler` в конфигурационном файле приложения: ======= @@ -110,6 +177,10 @@ throw new NotFoundHttpException(); Лучшим способом изменения отображения ошибок является использование [действий](structure-controllers.md) путём конфигурирования свойства [[yii\web\ErrorHandler::errorAction|errorAction]] компонента `errorHandler`: >>>>>>> yiichina/master +======= +Лучшим способом изменения отображения ошибок является использование [действий](structure-controllers.md) путём +конфигурирования свойства [[yii\web\ErrorHandler::errorAction|errorAction]] компонента `errorHandler`: +>>>>>>> master ```php // ... @@ -121,8 +192,11 @@ throw new NotFoundHttpException(); ] ``` <<<<<<< HEAD +<<<<<<< HEAD С вышеуказанной конфигурацией, если происходит ошибка, Yii запустит действие `error` контроллера `site`. Это действие запрашивает у компонента `errorHandler`, было ли выброшено исключение, и, если да, отображает соответствующий вид, передавая ему объект исключения в качестве параметра. ======= +======= +>>>>>>> master Свойство [[yii\web\ErrorHandler::errorAction|errorAction]] принимает [маршрут](structure-controllers.md#routes) действия. Конфигурация выше означает, что для отображения ошибки без стека вызовов будет использовано действие `site/error`. @@ -153,56 +227,102 @@ class SiteController extends Controller Вместо использования [[yii\web\ErrorAction]] вы можете создать действие `error` как обычный метод: +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ```php public function actionError() { +<<<<<<< HEAD <<<<<<< HEAD $exception = \Yii::$app->errorHandler->exception; ======= $exception = Yii::$app->errorHandler->exception; >>>>>>> yiichina/master +======= + $exception = Yii::$app->errorHandler->exception; +>>>>>>> master if ($exception !== null) { return $this->render('error', ['exception' => $exception]); } } ``` +<<<<<<< HEAD <<<<<<< HEAD После создания `action` нужно создать соответствующий вид, который и будет отображать информацию об исключении. Объект исключения, передаваемый в вид, имеет следующие свойства: +======= +Вы должны создать файл представления `views/site/error.php`. В этом файле, если используется [[yii\web\ErrorAction]], +вам доступны следующие переменные: -- `statusCode`: HTTP статус (например, 403, 500). Доступен только для [[yii\web\HttpException|HTTP exceptions]]. -- `code`: код исключения. -- `message`: сообщение об ошибке. -- `file`: имя файла PHP скрипта, в котором произошла ошибка. -- `line`: номер строки в коде, где произошла ошибка. -- `trace`: стэк вызовов, приведших к ошибке. +* `name`: имя ошибки; +* `message`: текст ошибки; +* `exception`: объект исключения, из которого можно получить дополнительную информацию, такую как статус HTTP, + код ошибки, стек вызовов и т.д. + +> Info: Если вы используете шаблоны приложения [basic](start-installation.md) или [advanced](tutorial-advanced-app.md), + действие error и файл представления уже созданы за вас. + +### Изменение формата ответа -Рендеринг ошибок без создания отдельного действия -------------------------------------------------- +Обработчик ошибок отображает ошибки в соответствии с выбранным форматом [ответа](runtime-responses.md). +Если [[yii\web\Response::format|формат ответа]] задан как `html`, будут использованы представления для ошибок и +исключений, как описывалось ранее. Для остальных форматов ответа обработчик ошибок присваивает массив данных, +представляющий ошибку свойству [[yii\web\Response::data]]. Оно далее конвертируется в необходимый формат. Например, +если используется формат ответа `json`, вы получите подобный ответ: +>>>>>>> master -Вместо создания отдельного действия внутри контроллера Site, вы можете просто указать Yii какой класс использовать для обработки ошибок: +``` +HTTP/1.1 404 Not Found +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 -```php -public function actions() { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - ]; + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 } ``` -После задания вышеуказанной связки ошибки с классом обработчиком, создайте вид `views/site/error.php`, который будет использоваться автоматически. -Виду передаются три параметра: +Изменить формат можно в обработчике события `beforeSend` компонента `response` в конфигурации приложения: -- `$name`: название ошибки -- `$message`: сообщение об ошибке -- `$exception`: обрабатываемое исключение +```php +return [ + // ... + 'components' => [ + 'response' => [ + 'class' => 'yii\web\Response', + 'on beforeSend' => function ($event) { + $response = $event->sender; + if ($response->data !== null) { + $response->data = [ + 'success' => $response->isSuccessful, + 'data' => $response->data, + ]; + $response->statusCode = 200; + } + }, + ], + ], +]; +``` +Приведённый код изменит формат ответа на подобный: + +``` +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +<<<<<<< HEAD Объект `$exception` имеет те же свойства, которые были указаны ранее. ======= Вы должны создать файл представления `views/site/error.php`. В этом файле, если используется [[yii\web\ErrorAction]], @@ -271,6 +391,8 @@ Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 +======= +>>>>>>> master { "success": false, "data": { @@ -281,4 +403,7 @@ Content-Type: application/json; charset=UTF-8 } } ``` +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide-ru/runtime-logging.md b/docs/guide-ru/runtime-logging.md index 434eb21f5d..1a8bde89b4 100644 --- a/docs/guide-ru/runtime-logging.md +++ b/docs/guide-ru/runtime-logging.md @@ -1,116 +1,313 @@ Логгирование ============ -> Раздел находится в разработке +Yii предоставляет мощную, гибко настраиваемую и легко расширяемую систему логгирования. Эта система логгирования позволяет удобным способом сохранять сообщения разных типов и фильтровать их. Сообщения могут быть сохранены в файлы, базы данных или отправлены на email. -В Yii встроен гибкий и расширяемый логгер, который способен обрабатывать сообщения в соответствии с их уровнем важности и типом. -С его помощью также можно фильтровать сообщения по разными критериям и пересылать их в файлы, email, в дебаггер и т.п. +Использование Системы логгирования Yii включает следующие шаги: +* Запись [сообщений лога](#log-messages) в различных частях кода приложения; +* Настройка [целей лога](#log-targets) в конфигурации приложения; +* Изучение отфильтрованных сообщений лога, например, при помощи [Отладчика Yii](tool-debugger.md). -Основы логгирования -------------------- +В данном разделе, будем рассматривать первые два шага. -В самом простом виде логгирование - это просто вызов метода, как в примере ниже: +## Сообщения лога + +Запись сообщений лога осуществляется вызовом одного из следующих методов: + +* [[Yii::trace()]]: записывает сообщения для отслеживания выполнения кода приложения. Используется, в основном, при разработке. +* [[Yii::info()]]: записывает сообщение, содержащее какую-либо полезную информацию. +* [[Yii::warning()]]: записывает *тревожное* сообщение при возникновении неожиданного события. +* [[Yii::error()]]: записывает критическую ошибку, на которую нужно, как можно скорее, обратить внимаение. + +Эти методы позволяют записывать сообщения разных *уровней важности* и *категорий*. Они имеют одинаковое описание функции `function ($message, $category = 'application')`, где `$message` передает сообщение для записи, а `$category` - категорию сообщения. В следующем примере будет записано *trace* сообщение с категорией по умолчанию `application`: ```php -\Yii::info('Привет, я - тестовое сообщение лога'); +Yii::trace('start calculating average revenue'); ``` -Вы можете логгировать как данные строкового типа, так и более сложные структуры данных, такие как массивы и объекты. -Если логгируемые данные - не строка, обработчики логов по умолчанию сериализуют значение, используя [[yii\helpers\Vardumper::export()]]. +> Note: Сообщение может быть как строкой так и объектом или массивом. За корректную работу с содержимым сообщения отвечают [цели лога](#log-targets). По умолчанию, если сообщение не является строкой, оно будет приведено к строковому типу при помощи [[yii\helpers\VarDumper::export()]]. -### Категории сообщений - -Вы можете указать категорию сообщения, чтобы разделить сообщения разного типа в дальнейшем, и по разному их обработать. -Категория сообщения передается вторым аргументом методов логгирования. По умолчанию присваивается категория `application`. - -### Уровни важности - -Доступно несколько уровней важности и соответствующим им методов логгирования: - -- [[Yii::trace]] в основном используется в разработке, чтобы логгировать прогресс выполнения кода. Заметьте, что он работает только в -режиме разработки, когда константа `YII_DEBUG` имеет значение `true`. -- [[Yii::error]] используется в случае невосстановимой ошибки. -- [[Yii::warning]] используется, когда произошла ошибка, но исполнение может быть продолжено. -- [[Yii::info]] используется, чтобы фиксировать информацию о важных событиях, таких как логин администратора. - -Цели сообщений ------------------- - -Когда вызывается один из логгирующих методов, сообщение передается компоненту [[yii\log\Logger]], доступному через -`Yii::getLogger()`. Логгер хранит сообщения в памяти, и когда сообщений достаточно для отправки, или когда -заканчивается текущий запрос, отправляет сообщения по целям назначения, таким как файл или email. - - -Вы можете конфигурировать цели сообщений таким образом: +Для упрощения работы с сообщениями лога и их фильтрации, рекомендуется явно указывать подходящую категорию для каждого сообщения. Возможно использование иерархической системы именования категорий, что значительно упростит [целям лога](#log-targets) фильтрацию сообщений по категориям. Простым и эффективным способом именования категорий является использование магической PHP константы `__METHOD__`. Такой подход используется в ядре фреймворка Yii. Например, ```php -[ - 'bootstrap' => ['log'], // убеждаемся, что логгер загружается до запуска приложения +Yii::trace('начало вычисления среднего дохода', __METHOD__); +``` + +Константа `__METHOD__` вычисляется как имя метода (включая полное имя класса), в котором она использована. Например, её значение будет вычислено как `'app\controllers\RevenueController::calculate'`, если показанный выше код вызывается в соответствующем методе. + +> Info: методы логгирования, описанные выше являются, на самом деле, ярлыками для метода [[yii\log\Logger::log()|log()]] [[yii\log\Logger|объекта логгера]], который доступен как синглтон `Yii::getLogger()`. +При определенном количестве записанных сообщений или завершении приложения, объект логгера вызывает [[yii\log\Dispatcher|message dispatcher]] для отправки записанных сообщений зарегистрированным [целям логов](#log-targets). + + +## Цели логов + +Цель логов - это экземпляр класса [[yii\log\Target]] или класса, унаследованного от него. Цель фильтрует сообщения логов по уровню важности и категории, а затем выгружает их в соответствующее хранилище. Например, [[yii\log\DbTarget|database target]] выгружает отфильтрованные сообщения логов в таблицу базы данных, а [[yii\log\EmailTarget|email target]] отправляет сообщения логов на заданные адреса email. + +При помощи компонента приложения `log` возможна регистрация нескольких целей логов. Пример конфигурации приложения: + +```php +return [ + // Компонент "log" должен быть загружен на этапе предзагрузки + 'bootstrap' => ['log'], + 'components' => [ 'log' => [ 'targets' => [ - 'file' => [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['trace', 'info'], - 'categories' => ['yii\*'], - ], - 'email' => [ - 'class' => 'yii\log\EmailTarget', + [ + 'class' => 'yii\log\DbTarget', 'levels' => ['error', 'warning'], + ], + [ + 'class' => 'yii\log\EmailTarget', + 'levels' => ['error'], + 'categories' => ['yii\db\*'], 'message' => [ - 'to' => ['admin@example.com', 'developer@example.com'], - 'subject' => 'Новое сообщение логгера example.com', + 'from' => ['log@example.com'], + 'to' => ['admin@example.com', 'developer@example.com'], + 'subject' => 'Ошибки базы данных на сайте example.com', ], ], ], ], ], +]; +``` + +> Note: Компонент `log` должен быть загружен в процессе [предзагрузки](runtime-bootstrapping.md), тогда он сможет оперативно передавать сообщения целям логов. Поэтому он указан в массиве `bootstrap`. + +В приведенном выше коде в свойстве [[yii\log\Dispatcher::targets]] зарегистрированы две цели логов: + +* первая цель выбирает ошибки и предупреждения и сохраняет их в базу данных; +* вторая цель выбирает ошибки с категорией, имя которой начинается с `yii\db\` и шлет сразу на два адреса email `admin@example.com` и `developer@example.com`. + +На данный момент, Yii содержит следующие встроенные цели логов. В документации по API подробно описана настройка и использование этих классов. + +* [[yii\log\DbTarget]]: сохраняет сообщения логов в таблицу базы данных. +* [[yii\log\EmailTarget]]: шлет сообщения логов на заранее указанный email. +* [[yii\log\FileTarget]]: сохраняет сообщения логов в файлы. +* [[yii\log\SyslogTarget]]: сохраняет сообщения логов в системный лог используя функцию PHP `syslog()`. + +Дальше рассмотрим общие для этих четырех классов возможности. + + +### Фильтрация сообщений + +Для каждой цели можно настроить свойства [[yii\log\Target::levels|levels]] и [[yii\log\Target::categories|categories]], которые указывают уровни важности и категории сообщений логов, которые цель должна обрабатывать. + +Свойство [[yii\log\Target::levels|levels]] принимает массив, содержащий одно или несколько следующих значений: + +* `error`: соответствует сообщениям, сохраненным методом [[Yii::error()]]. +* `warning`: соответствует сообщениям, сохраненным методом [[Yii::warning()]]. +* `info`: соответствует сообщениям, сохраненным методом [[Yii::info()]]. +* `trace`: соответствует сообщениям, сохраненным методом [[Yii::trace()]]. +* `profile`: соответствует сообщениям, сохраненным методами [[Yii::beginProfile()]] и [[Yii::endProfile()]], подробнее о которых написано в подразделе [Профилирование производительности](#performance-profiling). + +Если свойство [[yii\log\Target::levels|levels]] не задано, цель логов будет обрабатывать сообщения с *любым* уровнем важности. + +Свойство [[yii\log\Target::categories|categories]] принимает массив, содержащий имена категорий или шаблоны. +Цель будет обрабатывать только те сообщения, категория которых совпадает с одним из значений или шаблонов этого массива. Шаблон категории должен состоять из префикса имени категории и звездочки `*` на конце. Имя категории совпадает с шаблоном, если оно начинается с префикса шаблона. Например, `yii\db\Command::execute` и `yii\db\Command::query` используются в качестве имен категорий сообщений, записанных в классе [[yii\db\Command]]. Оба они совпадают с шаблоном `yii\db\*`. + +Если свойство [[yii\log\Target::categories|categories]] не задано, цель будет обрабатывать сообщения любой категории. + +Кроме списка включаемый категорий, заданного свойством [[yii\log\Target::categories|categories]], при помощи свойства [[yii\log\Target::except|except]] возможно задать список исключаемых категорий. Если категория сообщения совпадает со значением или шаблоном из списка исключаемых категорий, такое сообщение не будет обработано. + +В следующем примере показан вариант конфигурации цели логов, которая должна обрабатывать только сообщения об ошибках и предупреждениях в категориях `yii\db\*` и `yii\web\HttpException:*`, за исключением `yii\web\HttpException:404`. + +```php +[ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], + 'categories' => [ + 'yii\db\*', + 'yii\web\HttpException:*', + ], + 'except' => [ + 'yii\web\HttpException:404', + ], ] ``` -В конфигурации выше мы назначает две цели: [[yii\log\FileTarget|file]] и [[yii\log\EmailTarget|email]]. В обоих случаях -мы фильтруем сообщения по важности, а в случае с записью в файл еще и по категории. `yii\*` значит все категории, начинающиеся с `yii\`. +> Note: При обработке HTTP исключения [обработчиком ошибок](runtime-handling-errors.md), сообщение будет сохранено с категорией вида `yii\web\HttpException:ErrorCode`. Например, исключение [[yii\web\NotFoundHttpException]] вызовет сообщение об ошибке с категорией `yii\web\HttpException:404`. -Каждая цель может иметь имя, и к ней можно обращаться через [[yii\log\Logger::targets|targets]] следующим образом: + +### Форматирование сообщений + +Цели логов выгружают отфильтрованные сообщения в определенном формате. Например, цель классна [[yii\log\FileTarget]] сохранит сообщение следующего формата в файле `runtime/log/app.log`: + +``` +2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug +``` + +По умолчанию сообщения логов форматируются методом [[yii\log\Target::formatMessage()]]: + +``` +Временная метка [IP адрес][ID пользователя][ID сессии][Уровень важности][Категория] Текст сообщения +``` + +Этот формат может быть изменен при помощи свойства [[yii\log\Target::prefix]], которое получает анонимную функцию, возвращающую нужный префикс сообщения. Например, следующий код позволяет настроить вывод идентификатор текущего пользователя в качестве префикса для всех сообщений. + +```php +[ + 'class' => 'yii\log\FileTarget', + 'prefix' => function ($message) { + $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null; + $userID = $user ? $user->getId(false) : '-'; + return "[$userID]"; + } +] +``` + +Кроме префиксов сообщений, также возможно добавление общей информации для каждого набора сообщений лога. +По умолчанию, включаются значения следующих глобальных PHP переменных: `$_GET`, `$_POST`, `$_FILES`, `$_COOKIE`, +`$_SESSION` и `$_SERVER`. Эта возможность настраивается при помощи свойства [[yii\log\Target::logVars]], содержащего массив имен переменных, которые необходимо включить в лог. Например, следующий код позволяет настроить цель логов так, чтобы к сообщениям присоединялось только содержимое переменной `$_SERVER`. + +```php +[ + 'class' => 'yii\log\FileTarget', + 'logVars' => ['_SERVER'], +] +``` + +При задании значением свойства `logVars` пустого массива, общая информация не будет выводиться. +Для определения собственного алгоритма подключения общей информации, следует переопределить метод [[yii\log\Target::getContextMessage()]]. + + +### Уровень отслеживания выполнения кода + +В процессе разработки, часто бывает необходимость видеть источники сообщений. Для этого нужно использовать свойство [[yii\log\Dispatcher::traceLevel|traceLevel]] компонента `log`. Например, + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [...], + ], + ], +]; +``` + +При такой настройке свойство [[yii\log\Dispatcher::traceLevel|traceLevel]] будет равно 3 при `YII_DEBUG` равном `true` и 0 при `YII_DEBUG` равном `false`. Это означает, что при включенном `YII_DEBUG`, каждое сообщение лога будет содержать до трех уровней стека вызовов, а при выключенном `YII_DEBUG` информация о стеке вызовов не будет включаться в лог. + +> Info: Получение информации стека вызовов является не простым процессом. Поэтому такую возможность следует использовать только при разработке или отладке приложения. + + +### Передача на обработку и выгрузка сообщений + +Как упоминалось выше, сообщения логов обрабатываются в массиве [[yii\log\Logger|объектом логгера]]. Для ограничения объема памяти, занятого этим массивом, при накоплении определенного числа сообщений, логгер передает их на обработку [целям логов](#log-targets). Максимальное количество сообщений определяется свойством [[yii\log\Dispatcher::flushInterval|flushInterval]] компонента `log`: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'flushInterval' => 100, // по умолчанию 1000 + 'targets' => [...], + ], + ], +]; +``` + +> Info: При завершении приложения, так же происходит передача сообщений на обработку. + +После передачи сообщений [[yii\log\Logger|объектом логгера]] в [цели логов](#log-targets), сообщения не выгружаются немедленно. Вместо этого, выгрузка сообщений происходит когда цель логов накопит определенное количество фильтрованных сообщений. Максимальное количество сообщений определяется свойством [[yii\log\Target::exportInterval|exportInterval]] [цели логов](#log-targets). Например, + +```php +[ + 'class' => 'yii\log\FileTarget', + 'exportInterval' => 100, // по умолчанию 1000 +] +``` + +Из-за того, что значения максимального количества сообщений для передачи и выгрузки по умолчанию достаточно велико, при вызове метода `Yii::trace()`, или любого другого метода логгирования, сообщение не появится сразу в файле или таблице базы данных. Такое поведение может стать проблемой, например, в консольных приложениях с большим временем исполнения. Для того, чтобы все сообщения логов сразу же попадали в лог, необходимо установить значения свойств [[yii\log\Dispatcher::flushInterval|flushInterval]] и [[yii\log\Target::exportInterval|exportInterval]] равными 1, например так: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'flushInterval' => 1, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'exportInterval' => 1, + ], + ], + ], + ], +]; +``` + +> Note: Частая передача и выгрузка сообщений может сильно снизить производительность приложения. + + +### Переключение целей логов + +Свойство [[yii\log\Target::enabled|enabled]] отвечает за включение или отключение цели логов. Возможно управлением этим свойством как в конфигурации приложения, так и при помощи следующего PHP кода: ```php Yii::$app->log->targets['file']->enabled = false; ``` -Когда приложение заканчивает работу, или когда достигнут предел количества сообщений [[yii\log\Logger::flushInterval|flushInterval]], логгер -вызовет метод [[yii\log\Logger::flush()|flush()]] для отправки сообщений по маршрутам. +В данном примере используется цель логов `file`, которая может быть настроена в конфигурации приложения следующим образом: -> Обратите внимание, что в примере выше мы добавили компонент `log` в список [автозагрузки](runtime-bootstrapping.md) компонентов, чтобы -он инициализировался в самом начале жизненного цикла приложения, чтобы, в свою очередь, убедиться, что логгирование будет происходить с самого начала -приложения. +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'targets' => [ + 'file' => [ + 'class' => 'yii\log\FileTarget', + ], + 'db' => [ + 'class' => 'yii\log\DbTarget', + ], + ], + ], + ], +]; +``` -Профилирование --------------- +### Создание новых целей -Профилирование - особый тип сообщений, который может быть использован для замера времени, необходимого определенным -блокам кода для отработки, чтобы выяснить, где можно улучшить производительность. +Создание новой цели логов не является сложной задачей. В общем случае, нужно реализовать метод [[yii\log\Target::export()]], выгружающий массив [[yii\log\Target::messages]] в место хранения логов. Возможно использование метода [[yii\log\Target::formatMessage()]] для форматирования сообщения. Детали реализации можно подсмотреть в исходном коде любого из классов целей логов, включенных в состав Yii. -Чтобы пользоваться профилированием нужно понять, какие блоки кода нужно профилировать. Затем нужно отметить начало и конец каждого блока, вызывая -нижеследующие методы: + +## Профилирование производительности + +Профилирование производительности - это специальный тип сообщений логов, используемый для измерения времени выполнения определенных участков кода и определения проблем производительности. Например, класс [[yii\db\Command]] использует профилирование производительности для определения времени исполнения каждого запроса базы данных. + +Для использования профилирования производительности нужно определить участок кода для измерения и *обернуть* его вызовами методов [[Yii::beginProfile()]] и [[Yii::endProfile()]]. Например, ```php \Yii::beginProfile('myBenchmark'); -...блок кода для профилирования... + +...участок кода для профилирования... + \Yii::endProfile('myBenchmark'); ``` -где `myBenchmark` - уникальный идентификатор блока кода. +где `myBenchmark` является уникальным идентификатором данного измеряемого участка кода. +В дальнейшем, при изучении результатов профилирования, уникальный идентификатор поможет определить время выполнения соответствующего участка кода. -Заметьте, что блоки кода должны быть правильно вложены друг в друга. Посмотрите пример ниже: +Очень важно соблюдать уровни вложенности пар `beginProfile` и `endProfile`. Например, ```php \Yii::beginProfile('block1'); - // код блока 1 + + // код для профилирования + \Yii::beginProfile('block2'); - // код блока два, который входит в блок один + // другой код для профилирования \Yii::endProfile('block2'); + \Yii::endProfile('block1'); ``` -Результаты профилирования [можно отображать в дебаггере](module-debug.md). +Если пропустить `\Yii::endProfile('block1')` или поменять местами `\Yii::endProfile('block1')` и +`\Yii::endProfile('block2')`, профилирование производительности не будет работать. + +Для каждого участка кода, будет записано сообщение лога с уровнем важности `profile`. Для сбора таких сообщений можно настроить [цель логов](#log-targets) или воспользоваться [Отладчиком Yii](tool-debugger.md), который имеет встроенную панель профилирования производительности, отображающую результаты измерений. diff --git a/docs/guide-ru/runtime-overview.md b/docs/guide-ru/runtime-overview.md new file mode 100644 index 0000000000..2a40491c87 --- /dev/null +++ b/docs/guide-ru/runtime-overview.md @@ -0,0 +1,22 @@ +Обзор +======== + +Все запросы, обрабатываемые Yii приложением, проходят подобный путь. + +1. Пользователь создает запрос ко [входному скрипту](structure-entry-scripts.md) `web/index.php`. +2. Входной скрипт загружает [конфигурацию](concept-configurations.md) и создает экземпляр [приложения](structure-applications.md) для обработки запроса. +3. Приложение определяет запрошенный [маршрут](runtime-routing.md) при помощи компонента [request](runtime-requests.md). +4. Приложение создает экземпляр [контроллера](structure-controllers.md) для обработки запроса. +5. Контроллер создает экземпляр [действия](structure-controllers.md) и выполняет фильтры для этого действия. +6. При неудачном выполнении любого [фильтра](structure-filters.md), действие не выполняется. +7. При успешном выполнении всех фильтров, выполняется действие. +8. Действие загружает [модель](structure-models.md) данных, возможно, из базы данных. +9. Действие рендерит [представление](structure-views.md) и передает ему модель данных. +10. Результат рендеринга передается в компонент приложения [response](runtime-responses.md). +11. Компонент response посылает готовые данные пользователю. + +Ниже представлена диаграмма обработки запроса приложением. + +![Request Lifecycle](images/request-lifecycle.png) + +В данном разделе описаны подробности некоторых этапов обработки запроса. diff --git a/docs/guide-ru/runtime-requests.md b/docs/guide-ru/runtime-requests.md index 3e6e608a8d..bfe0cfa603 100644 --- a/docs/guide-ru/runtime-requests.md +++ b/docs/guide-ru/runtime-requests.md @@ -30,7 +30,7 @@ $name = $request->post('name', ''); // эквивалентно: $name = isset($_POST['name']) ? $_POST['name'] : ''; ``` -> Информация: Вместо того, чтобы обращаться напрямую к переменным `$_GET` и `$_POST` для получения параметров запроса, рекомендуется +> Info: Вместо того, чтобы обращаться напрямую к переменным `$_GET` и `$_POST` для получения параметров запроса, рекомендуется чтобы вы обращались к ним через компонент `request` как было показано выше. Это упростит написание тестов, поскольку вы можете создать mock компонент запроса с не настоящими данными запроса. При реализации [RESTful API](rest-quick-start.md), зачастую вам требуется получить параметры, которые были отправлены через PUT, PATCH или другие [методы запроса](#request-methods). Вы можете получить эти параметры, вызвав метод [[yii\web\Request::getBodyParam()]]. Например, @@ -45,7 +45,7 @@ $params = $request->bodyParams; $param = $request->getBodyParam('id'); ``` -> Информация: В отличии от `GET` параметров, параметры, которые были переданы через `POST`, `PUT`, `PATCH` и д.р. отправляются в теле запроса. +> Info: В отличии от `GET` параметров, параметры, которые были переданы через `POST`, `PUT`, `PATCH` и д.р. отправляются в теле запроса. Компонент `request` будет обрабатывать эти параметры, когда вы попробуете к ним обратиться через методы, описанные выше. Вы можете настроить способ обработки этих параметров через настройку свойства [[yii\web\Request::parsers]]. @@ -59,10 +59,10 @@ $param = $request->getBodyParam('id'); ```php $request = Yii::$app->request; -if ($request->isAjax) { // является ли текущий запрос AJAX запросом } -if ($request->isGet) { // является ли текущий запрос GET запросом } -if ($request->isPost) { // является ли текущий запрос POST запросом } -if ($request->isPut) { // является ли текущий запрос PUT запросом } +if ($request->isAjax) { /* текущий запрос является AJAX запросом */ } +if ($request->isGet) { /* текущий запрос является GET запросом */ } +if ($request->isPost) { /* текущий запрос является POST запросом */ } +if ($request->isPut) { /* текущий запрос является PUT запросом */ } ``` ## URL запроса @@ -94,7 +94,7 @@ $headers = Yii::$app->request->headers; // возвращает значения заголовка Accept $accept = $headers->get('Accept'); -if ($headers->has('User-Agent')) { // есть ли в запросе заголовок User-Agent } +if ($headers->has('User-Agent')) { /* в запросе есть заголовок User-Agent */ } ``` Компонент `request` также предоставляет доступ к некоторым часто используемым заголовкам, включая @@ -111,7 +111,7 @@ if ($headers->has('User-Agent')) { // есть ли в запросе загол Этот метод принимает список поддерживаемых языков в вашем приложении, сравнивает их с [[yii\web\Request::acceptableLanguages|acceptableLanguages]] и возвращает наиболее подходящий язык. -> Подсказка: Вы также можете использовать фильтр [[yii\filters\ContentNegotiator|ContentNegotiator]] для динамического определения +> Tip: Вы также можете использовать фильтр [[yii\filters\ContentNegotiator|ContentNegotiator]] для динамического определения какой тип содержимого и язык должен использоваться в ответе. Фильтр реализует согласование содержимого на основе свойств и методов, описанных выше. diff --git a/docs/guide-ru/runtime-responses.md b/docs/guide-ru/runtime-responses.md index 22d85d7d64..49ff7490ba 100644 --- a/docs/guide-ru/runtime-responses.md +++ b/docs/guide-ru/runtime-responses.md @@ -72,7 +72,7 @@ $headers->set('Pragma', 'no-cache'); $values = $headers->remove('Pragma'); ``` -> Информация: названия заголовков не чувствительны к регистру символов. Заново зарегистрированные заголовки не отсылаются +> Info: названия заголовков не чувствительны к регистру символов. Заново зарегистрированные заголовки не отсылаются пользователю до вызова [[yii\web\Response::send()]]. @@ -149,7 +149,7 @@ public function actionInfo() } ``` -> Примечание: создавая собственные объекты ответов, вы не сможете воспользоваться конфигурацией компонента `response`, +> Note: создавая собственные объекты ответов, вы не сможете воспользоваться конфигурацией компонента `response`, настроенной вами в конфигурации приложения. Тем не менее, вы можете воспользоваться [внедрением зависимости](concept-di-container.md), чтобы применить общую конфигурацию к вашим новым объектам ответа. @@ -181,7 +181,7 @@ public function actionOld() \Yii::$app->response->redirect('http://example.com/new', 301)->send(); ``` -> Информация: По умолчанию метод [[yii\web\Response::redirect()]] устанавливает код состояния ответа равным 302, сообщая +> Info: По умолчанию метод [[yii\web\Response::redirect()]] устанавливает код состояния ответа равным 302, сообщая браузеру, что запрашиваемый ресурс *временно* находится по другому URI-адресу. Вы можете передать код состояния 301, чтобы сообщить браузеру, что ресурс перемещён *навсегда*. @@ -190,7 +190,7 @@ public function actionOld() заголовка `X-Redirect` равным URL для перенаправления. На стороне клиента вы можете написать JavaScript-код для чтения значения этого заголовка и перенаправления браузера соответственно. -> Информация: Yii поставляется с JavaScript-файлом `yii.js`, который предоставляет набор часто используемых +> Info: Yii поставляется с JavaScript-файлом `yii.js`, который предоставляет набор часто используемых JavaScript-утилит, включая и перенаправление браузера на основе заголовка `X-Redirect`. Следовательно, если вы используете этот JavaScript-файл (зарегистрировав пакет ресурсов [[yii\web\YiiAsset]]), вам не нужно писать дополнительный код для поддержки AJAX-перенаправления. diff --git a/docs/guide-ru/runtime-routing.md b/docs/guide-ru/runtime-routing.md index 2c9dedb32a..55ea96f554 100644 --- a/docs/guide-ru/runtime-routing.md +++ b/docs/guide-ru/runtime-routing.md @@ -1,286 +1,464 @@ -Работа с URL +Разбор и генерация URL ============ -> Замечание: раздел находится в разработке. +При обработке запрошенного URL, Yii приложение первым делом разбирает URL в [маршрут](structure-controllers.md#marsruty). Полученный маршрут используется при создании +соответствующего экземпляра действия контроллера для обработки запроса. Этот процесс называется *роутинг*. -Концепция работы с URL в Yii довольно проста. Предполагается, что в приложении используются внутренние маршруты и параметры вместо жестко заданных URL. Тогда фреймворк сам преобразует маршруты в URL и обратно, в соответствии с конфигурацией URL менеджера. Такой подход позволяет изменять вид URL на всем сайте, редактируя единственный конфигурационный файл не трогая код самого приложения. +Обратный роутингу процесс называется *Создание URL*, он отвечает за создание URL из заданного маршрута и соответствующих параметров запроса. При необходимости, созданный URL всегда может быть преобразован в +первоначальные маршрут и параметры запроса. -Внутренние маршруты -------------------- +В основе роутинга и создания URL лежит использование [[yii\web\UrlManager|URL manager]], +зарегистрированного в качестве [компонента приложения](structure-application-components.md) `urlManager`. +[[yii\web\UrlManager|URL manager]] содержит метод [[yii\web\UrlManager::parseRequest()|parseRequest()]] +для разбора входящего запроса на маршрут и параметры запроса, и метод [[yii\web\UrlManager::createUrl()|createUrl()]] +для создания URL из заданного маршрута и параметров запроса. -При создании приложения с помощью Yii, вы будете работать с внутренними маршрутами, которые также часто называются маршрутами c параметрами. Каждому контроллеру и действию соответствует внутренний маршрут, например `site/index` или `user/create`. В первом примере, site - это `ID контроллера` а `create` это ID действия. Если контроллер находится в модуле, то перед его ID контроллера в маршруте ставится `ID модуля`, например так: `blog/post/index`. Здесь `blog` - это ID модуля, а `post` и `index` - ID контроллера и действия соответственно. - -Создание URL ------------- - -Самое важное при работе с URL = всегда создавать их через URL manager. Это встроенный компонент приложения, к которому можно обращаться по имени `urlManager` как в консольном, так и в веб-приложении, таким образом: `\Yii::$app->urlManager`. Через этот компонент доступны следующие методы создания URL: -- `createUrl($params)` -- `createAbsoluteUrl($params, $schema = null)` - -Метод `createUrl` создает относительный от корня приложения URL, например `index.php/site/index` . Метод `createAbsoluteUrl` создает абсолютный URL, добавляя к относительному протокол и имя хоста: `http://www.example.com/index.php/site/index` . `createUrl` больше подходит для URL внутри самого приложения, тогда как `createAbsoluteUrl` - для создания ссылок на внешние ресурсы и для них, для решения задач отправки почты, генерации RSS и т.п. - -Примеры: - -```php -echo \Yii::$app->urlManager->createUrl(['site/page', 'id' => 'about']); -// /index.php/site/page/id/about/ -echo \Yii::$app->urlManager->createUrl(['date-time/fast-forward', 'id' => 105]) -// /index.php?r=date-time/fast-forward&id=105 -echo \Yii::$app->urlManager->createAbsoluteUrl('blog/post/index'); -// http://www.example.com/index.php/blog/post/index/ -``` - -Точный формат URL зависит от конфигурации URL manager. В другой конфигурации примеры выше могут выводить: - -* `/site/page/id/about/` -* `/index.php?r=site/page&id=about` -* `/index.php?r=date-time/fast-forward&id=105` -* `/index.php/date-time/fast-forward?id=105` -* `http://www.example.com/blog/post/index/` -* `http://www.example.com/index.php?r=blog/post/index` - -Чтобы упростить создание URL рекомендуется пользоваться встроенным хелпером [[yii\helpers\Url]]. Покажем, как работает хелпер на примерах. Предположим, что мы находимся на /index.php?r=management/default/users&id=10 . Тогда: +Настройка компонента `urlManager` в конфигурации приложения, позволяет приложению распознавать различные +форматы URL без внесения изменений в существующий код приложения. Например, для +создания URL для действия `post/view`, можно использовать следующий код: ```php use yii\helpers\Url; -// текущий активный маршрут -// /index.php?r=management/default/users -echo Url::to(''); +// Url::to() вызывает UrlManager::createUrl() для создания URL +$url = Url::to(['post/view', 'id' => 100]); +``` -// тот же контроллер, другое действие -// /index.php?r=management/default/page&id=contact -echo Url::toRoute(['page', 'id' => 'contact']); +В зависимости от настройки `urlManager`, URL может быть создан в одном из следующих форматов (или любом другом формате). При последующем запросе URL в таком формате, он будет разобран на исходные маршрут и параметры запроса. +``` +/index.php?r=post/view&id=100 +/index.php/post/100 +/post/100 +``` -// тот же модуль, другие контроллер и действие -// /index.php?r=management/post/index -echo Url::toRoute('post/index'); +## Форматы URL -// абсолютный маршрут вне зависимости от того, в каком контроллере происходит вызов -// /index.php?r=site/index -echo Url::toRoute('/site/index'); +[[yii\web\UrlManager|URL manager]] Поддерживает два формата URL: обычный и ЧПУ (человекопонятные URL). -// url для регистрозависимого действия hiTech текущего контроллера -// /index.php?r=management/default/hi-tech -echo Url::toRoute('hi-tech'); +Обычный формат URL использует параметр `r` для передачи маршрута и любые другие параметры для передачи остальных параметров запроса. Например, URL `/index.php?r=post/view&id=100` задает маршрут `post/view` и параметр `id`, равный 100. Данный формат не требует специальной конфигурации [[yii\web\UrlManager|URL manager]] и работает с любыми настройками Веб сервера. -// url для регистрозависимого контроллера, `DateTimeController::actionFastForward` -// /index.php?r=date-time/fast-forward&id=105 -echo Url::toRoute(['/date-time/fast-forward', 'id' => 105]); +Человекопонятный формат URL представляет собой дополнительный путь, следующий за именем входного скрипта, описывающий маршрут и остальные параметров запроса. Например, дополнительный путь в URL `/index.php/post/100` - это `/post/100`, который может представлять маршрут `post/view` и параметр `id` со значением равным 100, при наличии соответствующего [[yii\web\UrlManager::rules|правила]]. Для использования ЧПУ, необходимо создать набор правил, соответствующих требованиям к URL. -// получение URL через alias -// http://google.com/ -Yii::setAlias('@google', 'http://google.com/'); -echo Url::to('@google'); +Переключение между двумя форматами URL осуществляется при помощи свойства [[yii\web\UrlManager::enablePrettyUrl|enablePrettyUrl]] компонента [[yii\web\UrlManager|URL manager]] без внесения изменений в код приложения. -// получение URL "домой" -// /index.php?r=site/index +## Роутинг + +Роутинг осуществляется в два этапа. На первом этапе, входящий запрос разбирается в маршрут и параметры запроса. На втором этапе, для обработки запроса создается [действие контроллера](structure-controllers.md#actions), соответствующее полученному маршруту. + +При использовании простого формата URL, получение маршрута из запроса заключается в получении параметра `r` из массива `GET`. + +При использовании ЧПУ, компонент [[yii\web\UrlManager|URL manager]] ищет среди зарегистрированных [[yii\web\UrlManager::rules|правил]] подходящее для разрешения запроса в маршрут. +Если такое правило не найдено, вызывается исключение [[yii\web\NotFoundHttpException]]. + +После того, как из запроса получен маршрут, самое время создать действие контроллера, соответствующее этому маршруту. +Маршрут разделяется на несколько частей, метками деления служат прямые слеши. Например, маршрут `site/index` будет разделен на `site` и `index`. Каждая из частей представляет собой *идентификатор*, который может ссылаться на модуль, контроллер или действие. Начиная с первой части маршрута, приложение следует следующему алгоритму для создания модуля (если есть), контроллера и действия: + +1. Текущим модулем считаем приложение. +2. Проверяем, содержит ли [[yii\base\Module::controllerMap|карта контроллеров]] текущего модуля текущий *идентификатор*. + Если содержит, в соответствии с конфигурацией контроллера, найденной в карте, создаем объект контроллера и переходим в п. 5 для обработки оставшейся части маршрута. +3. Проверяем, есть ли модуль, соответствующий *идентификатору* в списке модулей (свойство [[yii\base\Module::modules|modules]]) текущего модуля. Если есть, в соответствии с конфигурацией модуля, найденной в списке модулей, создаем модуль и переходим в п. 2, считая только что созданный модуль текущим. +4. Рассматриваем *идентификатор* как [идентификатор контроллера](structure-controllers.md#id-kontrollerov) и создаем объект контроллера. Для оставшейся части маршрута выполняем п. 5. +5. Контроллер ищет текущий *идентификатор* в его [[yii\base\Controller::actions()|карте действий]]. В случае нахождения, контроллер создает действие, в соответствии с конфигурацией, найденной в карте. Иначе, контроллер пытается создать встроенное действие, описанное методом, соответствующим текущему [идентификатору действия](structure-controllers.md#id-dejstvij). + +При возникновении ошибок на любом из описанных выше этапов, вызывается исключение [[yii\web\NotFoundHttpException]], указывающее на ошибку в процессе роутинга. + +### Маршрут по умолчанию + +В случае, если в результате разбора запроса получен пустой маршрут, вместо него будет использован, так называемый, маршрут по умолчанию. Изначально, маршрут по умолчанию имеет значение `site/index`, и указывает на действие `index` контроллера `site`. Указать свое значение можно при помощи свойства приложения [[yii\web\Application::defaultRoute|defaultRoute]], например так: + +```php +[ + // ... + 'defaultRoute' => 'main/index', +]; +``` + +### Маршрут `catchAll` + +Иногда возникает необходимость временно перевести приложение в режим обслуживания и отображать одно информационное сообщение для всех запросов. Существует много вариантов реализации этой задачи. Но одним из самых простых, является использование свойства [[yii\web\Application::catchAll]], например так: + +```php +[ + // ... + 'catchAll' => ['site/offline'], +]; +``` + +В данном случае, действие `site/offline` будет обрабатывать все входящие запросы. + +Свойство `catchAll` должно принимать массив, первый элемент которого определяет маршрут, а остальные элементы (пары ключ-значение) определяют параметры, [передаваемые действию](structure-controllers.md#parametry-dejstvij). + +## Создание URL + +Для создания разных видов URL из заданных маршрутов и параметров, Yii предоставляет метод-помощник [[yii\helpers\Url::to()]]. Примеры: + +```php +use yii\helpers\Url; + +// создает URL для маршрута: /index.php?r=post/index +echo Url::to(['post/index']); + +// создает URL для маршрута с параметрами: /index.php?r=post/view&id=100 +echo Url::to(['post/view', 'id' => 100]); + +// создает якорный URL: /index.php?r=post/view&id=100#content +echo Url::to(['post/view', 'id' => 100, '#' => 'content']); + +// создает абсолютный URL: http://www.example.com/index.php?r=post/index +echo Url::to(['post/index'], true); + +// создает абсолютный URL с использованием схемы https: https://www.example.com/index.php?r=post/index +echo Url::to(['post/index'], 'https'); +``` + +Обратите внимание, что в последнем примере подразумевается использование обычного формата URL. При использовании ЧПУ, будут созданы другие URL, соответствующие [[yii\web\UrlManager::rules|правилам создания URL]]. + +Маршрут, переданный методу [[yii\helpers\Url::to()]], является контекстно зависимым. Он может быть *относительным* или *абсолютным*, в зависимости от следующих правил: + +- Если маршрут является пустой строкой, будет использован текущий [[yii\web\Controller::route|маршрут]]; +- Если маршрут не содержит слешей вообще, он рассматривается как *идентификатор* действия текущего контроллера и будет дополнен значением [[\yii\web\Controller::uniqueId|uniqueId]] текущего контроллера в качестве префикса; +- Если маршрут не содержит слеша в начале, он будет рассматриваться как маршрут относительно текущего модуля и будет дополнен значением [[\yii\base\Module::uniqueId|uniqueId]] текущего модуля, в качестве префикса. + +Начиная с версии 2.0.2, при составлении маршрутов, стало возможным использовать [псевдонимы](concept-aliases.md). В таком случае, псевдоним будет преобразован в маршрут, который будет использован для создания URL по правилам, указанным выше. + +Для примера, будем считать, что текущим модулем является `admin`, а текущим контроллером - `post`, + +```php +use yii\helpers\Url; + +// запрошенный маршрут: /index.php?r=admin/post/index +echo Url::to(['']); + +// относительный маршрут с указанием только идентификатора действия: /index.php?r=admin/post/index +echo Url::to(['index']); + +// относительный маршрут: /index.php?r=admin/post/index +echo Url::to(['post/index']); + +// абсолютный маршрут: /index.php?r=post/index +echo Url::to(['/post/index']); + +// /index.php?r=post/index псевдоним "@posts" определен как "/post/index" +echo Url::to(['@posts']); +``` + +В основе реализации метода [[yii\helpers\Url::to()]] лежит использование двух методов компонента [[yii\web\UrlManager|URL manager]]: [[yii\web\UrlManager::createUrl()|createUrl()]] и [[yii\web\UrlManager::createAbsoluteUrl()|createAbsoluteUrl()]]. Ниже будут рассмотрены способы конфигурации [[yii\web\UrlManager|URL manager]] для создания URL в различных форматах. + +Метод [[yii\helpers\Url::to()]], так же, поддерживает создание URL не связанных с маршрутами приложения. +В данном случае, нужно передать в качестве первого параметра строку, а не массив. Например, + +```php +use yii\helpers\Url; + +// запрошенный URL: /index.php?r=admin/post/index +echo Url::to(); + +// URL из псевдонима: http://example.com +Yii::setAlias('@example', 'http://example.com/'); +echo Url::to('@example'); + +// абсолютный URL: http://example.com/images/logo.gif +echo Url::to('/images/logo.gif', true); +``` + +Кроме метода `to()`, класс [[yii\helpers\Url]] предоставляет и другие удобные методы для создания URL. Например, + +```php +use yii\helpers\Url; + +// домашний URL: /index.php?r=site/index echo Url::home(); -Url::remember(); // сохранить URL, чтобы использовать его позже -Url::previous(); // получить ранее сохраненный URL +// базовый URL, удобно использовать в случае, когда приложение расположено в подкаталоге +// относительно корневого каталога Веб сервера +echo Url::base(); + +// канонический URL запрошенного URL +// подробнее https://support.google.com/webmasters/answer/139066?hl=ru +echo Url::canonical(); + +// запомнить запрошенный URL и восстановить его при следующих запросах +Url::remember(); +echo Url::previous(); ``` ->**Совет**: чтобы сгенерировать URL с хэштэгом, например `/index.php?r=site/page&id=100#title`, укажите параметр `#` в хелпере таким образом: -`Url::to(['post/read', 'id' => 100, '#' => 'title'])`. +## Использование человекопонятных URL -Также существует метод `Url::canonical()`, который позволяет создать [канонический URL](https://en.wikipedia.org/wiki/Canonical_link_element) (статья на англ., перевода пока нет) для текущего действия. Этот метод при создании игнорирует все параметры действия кроме тех, которые были переданы как аргументы. -Пример: +Для активации ЧПУ, необходимо настроить компонент `urlManager` в конфигурации приложения следующим образом: ```php -namespace app\controllers; - -use yii\web\Controller; -use yii\helpers\Url; - -class CanonicalController extends Controller -{ - public function actionTest($page) - { - echo Url::canonical(); - } -} -``` - -При этом при текущем URL `/index.php?r=canonical/test&page=hello&number=42` канонический URL будет `/index.php?r=canonical/test&page=hello`. - -Модификация URL ------------------- - -По умолчанию Yii использует url формата query string, например `/index.php?r=news/view&id=100`. Чтобы сделать более [человеко-понятные URL](https://ru.wikipedia.org/wiki/%D0%A7%D0%9F%D0%A3_%28%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82%29), например для улучшения их читабельности, необходимо сконфигурировать компонент `urlManager` в конфигурационном файле приложения. Придав параметру `enablePrettyUrl` значение `true`, мы тем самым получим URL такого вида: `/index.php/news/view?id=100`, а выставив параметр `showScriptName' => false` мы исключим index.php из URL. Вот фрагмент конфигурационного файла: - -```php - [ 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, + 'enableStrictParsing' => false, + 'rules' => [ + // ... + ], ], ], -]; -``` - -Обратите внимание, что конфигурация с `'showScriptName' => false` будет работать только, если веб сервер был должным образом сконфигурирован. Смотрите раздел [installation](installation.md#recommended-apache-configuration) (ссылка не работает). - -###Именованные параметры - -Правило может быть связано с несколькими `GET` параметрами. Эти `GET` параметры появляются в паттерне правила как специальные управляющие конструкции в таком формате: - -``` -<ИмяПараметра:ПаттернПараметра> -``` - -`ИмяПараметра` это имя `GET` параметра, а необязательный `ПаттернПараметра` - это регулярное выражение, которое используется для того, чтобы найти определенный `GET` параметр. В случае, если `ПаттернПараметра` не указан, это значит, что значение параметра это любая последовательность символов, кроме `/`. Правила используются и при парсинге URL, и при их создании; при создании URL параметры будут заменены на соответствующие значения, а при парсинге - `GET` параметры с соответствующими именами получат соответствующие значения из URL. - -Приведем несколько примеров, чтобы пояснить, как работают правила. Предположим, что наш массив правил содержит три правила: - -```php -[ - 'posts'=>'post/list', - 'post/'=>'post/read', - 'post//'=>'post/read', ] ``` +Свойство [[yii\web\UrlManager::enablePrettyUrl|enablePrettyUrl]] является ключевым, активирует формат ЧПУ. +Остальные свойства не обязательные. Однако, в примере выше, показан самый популярный вариант конфигурации ЧПУ. -- Вызов `Url::toRoute('post/list')` генерирует `/index.php/posts`. Применяется первое правило. -- Вызов `Url::toRoute(['post/read', 'id' => 100])` генерирует `/index.php/post/100`. Применяется второе правило. -- Вызов `Url::toRoute(['post/read', 'year' => 2008, 'title' => 'a sample post'])` генерирует - `/index.php/post/2008/a%20sample%20post`. Применяется третье правило. -- Вызов `Url::toRoute('post/read')` генерируется `/index.php/post/read`. Не применяется ни одного правила, вместо этого URL формируется по алгоритму по умолчанию. +* [[yii\web\UrlManager::showScriptName|showScriptName]]: это свойство определяет необходимость включения имени входного скрипта в создаваемый URL. Например, при его значении `false`, вместо `/index.php/post/100`, будет сгенерирован URL `/post/100`. +* [[yii\web\UrlManager::enableStrictParsing|enableStrictParsing]]: это свойство позволяет включить строгий разбор URL. Если строгий разбор URL включен, запрошенный URL должен соответствовать хотя бы одному из [[yii\web\UrlManager::rules|правил]], иначе будет вызвано исключение [[yii\web\NotFoundHttpException]]. Если строгий разбор URL отключен и ни одно из [[yii\web\UrlManager::rules|правил]] не подходит для разбора запрошенного URL, часть этого URL, представляющая путь, будет использована как маршрут. +* [[yii\web\UrlManager::rules|rules]]: это свойство содержит набор правил для разбора и создания URL. Это основное свойство, с которым нужно работать, что бы URL создавались в формате, соответствующем требованиям приложения. -Таким образом, при использовании `createUrl` для генерации URL, маршрут и переданные методу `GET` параметры используются для принятия решения о том, какое правило применить. -Конкретное правило используется для генерации URL, если каждый параметр, указанный в правиле, может быть найден среди `GET` параметров, переданных `createUrl`, и сам маршрут, указанный в правиле, соответствует маршруту, переданному в качестве параметра. +> Note: Для того, чтобы скрыть имя входного скрипта в создаваемых URL, кроме установки значения свойства [[yii\web\UrlManager::showScriptName|showScriptName]] в `false`, необходимо настроить Веб сервер, чтобы он мог правильно определять PHP скрипт, который должен быть запущен, если в запрошенном URL он не указан явно. Рекомендованные настройки для Apache и Nginx описаны в разделе [Установка Yii](start-installation.md#rekomenduemye-nastrojki-apache). -Если параметров, переданных `Url::toRoute` больше, чем указано в правиле, то дополнительные параметры будут указаны в формате строки запроса. Например, при вызове `Url::toRoute(['post/read', 'id' => 100, 'year' => 2008])`, мы получим `/index.php/post/100?year=2008`. +### Правила URL <span id="url-rules"></span> -Как было сказано ранее, другое назначение правил - парсинг URL. Это процесс, обратный их созданию. Например, когда пользователь запрашивает `/index.php/post/100`, будет применено второе правило из примера выше, которое извлечет маршрут `post/read` и `GET` параметр `['id' => 100]` (который можно получить так: -`Yii::$app->request->get('id')`). +Правила URL - это экземпляр класса [[yii\web\UrlRule]] или класса, унаследованного от него. Каждое правило состоит из шаблона, используемого для поиска пути в запрошенном URL, маршрута и нескольких параметров запроса. Правило может быть использовано для разбора запроса в том случае, если шаблон правила совпадает с запрошенным URL. Правило может быть использовано для создания URL в том случае, если его маршрут и параметры запроса совпадают с заданными. -### Параметры в маршрутах +При включенном режиме ЧПУ, компонент [[yii\web\UrlManager|URL manager]] использует правила URL, содержащиеся в его свойстве [[yii\web\UrlManager::rules|rules]], для разбора входящих запросов и создания URL. Обычно, при разборе входящего запроса, [[yii\web\UrlManager|URL manager]] проверяет все правила в порядке их следования, до *первого* правила, соответствующего запрошенному URL. Найденное правило используется для разбора URL на маршрут и параметры запроса. Аналогично для создания URL компонент [[yii\web\UrlManager|URL manager]] ищет первое правило, соответствующее заданному маршруту и параметрам и использует его для создания URL. -Мы можем обращаться к именованным параметрам в маршрутной части правила. Это позволяет нескольким маршрутам подпадать под правила, в соответствии с переданными параметрами. Это также может помочь минимизировать количество правил URL в приложении, тем самым улучшив его общую производительность. +[[yii\web\UrlManager::rules|Правила]] задаются ассоциативным массивом, где ключи определяют шаблоны, а значения соответствующие маршруты. Каждая пара шаблон-маршрут составляет правило разбора URL. Например, следующие [[yii\web\UrlManager::rules|правила]] определяют два правила разбора URL. Первое правило задает соответствие URL `post` маршруту `post/index`. Второе правило задает соответствие URL, соответствующего регулярному выражению `post/(\d+)` маршруту `post/view` и параметру `id`. -Вот пример, показывающий как использовать маршруты с именованными параметрами. +```php +[ + 'posts' => 'post/index', + 'post/<id:\d+>' => 'post/view', +] +``` +> Note: Шаблон правила используется для поиска соответствия с частью URL, определяющей путь. Например, в URL `/index.php/post/100?source=ad` путь определяет часть `post/100` (начальный и конечный слеши игнорируются), соответствующая регулярному выражению `post/(\d+)`. + +Правила URL можно определять не только в виде пар шаблон-маршрут, но и в виде массива. Каждый массив используется для определения одного правила. Такой вид определения правил используется в случаях, когда необходимо указать другие параметры правила URL. Например, + +```php +[ + // ...другие правила URL... + + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], +] +``` + +По умолчанию, если в конфигурации правила URL не указан явно параметр `class`, будет создано правило класса [[yii\web\UrlRule]]. + + +### Именованные параметры <span id="named-parameters"></span> + +Правило URL может содержать несколько именованных параметров запроса, которые указываются в шаблоне в следующем формате: `<ParamName:RegExp>`, где `ParamName` определяет имя параметра, а `RegExp` - необязательное регулярное выражение, используемое для определения значения параметра. В случае, если `RegExp` не указан, значением параметра будет любая последовательность символов кроме слешей. + +> Note: Возможно указание только регулярного выражения для параметров. В таком случае, остальная часть шаблона будет считаться простым текстом. + +После разбора URL, параметры запроса, соответствующие шаблону правила, будут доступны в массиве `$_GET` через компонент приложения `request`. +При создании URL, значения указанных параметров будут вставлены в URL в соответствии с шаблоном правила. + +Рассмотрим несколько примеров работы с именованными параметрами. Допустим, мы определили следующие три правила URL: + +```php +[ + 'posts/<year:\d{4}>/<category>' => 'post/index', + 'posts' => 'post/index', + 'post/<id:\d+>' => 'post/view', +] +``` + +При разборе следующих URL: + +- `/index.php/posts` будет разобран в маршрут `post/index` при помощи второго правила; +- `/index.php/posts/2014/php` будет разобран на маршрут `post/index` и параметры `year` со значением 2014, `category` со значением `php` при помощи первого правила; +- `/index.php/post/100` будет разобран на маршрут `post/view` и параметр `id` со значением 100 при помощи третьего правила; +- `/index.php/posts/php` вызовет исключение [[yii\web\NotFoundHttpException]], если [[yii\web\UrlManager::enableStrictParsing]] имеет значение `true`, так как правило для разбора данного URL отсутствует. Если [[yii\web\UrlManager::enableStrictParsing]] имеет значение `false` (по умолчанию), значение `posts/php` будет возвращено в качестве маршрута. + +При создании URL: + +- `Url::to(['post/index'])` создаст `/index.php/posts` при помощи второго правила; +- `Url::to(['post/index', 'year' => 2014, 'category' => 'php'])` создаст `/index.php/posts/2014/php` при помощи первого правила; +- `Url::to(['post/view', 'id' => 100])` создаст `/index.php/post/100` при помощи третьего правила; +- `Url::to(['post/view', 'id' => 100, 'source' => 'ad'])` создаст `/index.php/post/100?source=ad` при помощи третьего правила. + Параметр `source` не указан в правиле, поэтому он добавлен в созданный URL в качестве параметра запроса. +- `Url::to(['post/index', 'category' => 'php'])` создаст `/index.php/post/index?category=php` без использования правил. При отсутствии подходящего правила, URL будет создан простым соединением маршрута, как части пути, и параметров, как части запроса. + +### Параметры в маршрутах <span id="parameterizing-routes"></span> + +В маршруте правила URL возможно указание имен параметров. Это позволяет использовать правило URL для обработки нескольких маршрутов. Например, следующие правила содержат параметры `controller` и `action` в маршрутах. ```php [ '<controller:(post|comment)>/<id:\d+>/<action:(create|update|delete)>' => '<controller>/<action>', - '<controller:(post|comment)>/<id:\d+>' => '<controller>/read', - '<controller:(post|comment)>s' => '<controller>/list', + '<controller:(post|comment)>/<id:\d+>' => '<controller>/view', + '<controller:(post|comment)>s' => '<controller>/index', ] ``` -В этом примере, мы использовали два именованных параметра в маршрутной части правил: `controller` и `action`. Первый параметр подходит в случае, если controller ID - это post или comment, а последний - в случае, если action ID - create, update или delete. Имена параметров могут быть разными, но не должны совпадать по имени с GET-параметрами, которые могут появляться в URL. +Для разбора URL `/index.php/comment/100/create` будет использовано первое правило, которое установит значения параметров `controller` равным `comment` и `action` равным `create`. Таким образом, маршрут `<controller>/<action>` дубет разрешен в `comment/create`. -Используя эти правила, URL `/index.php/post/123/create` после парсинга будет иметь маршрут `post/create` с `GET` параметром `id=123`. А с маршрутом `comment/list` и `GET` параметром `page=2` можно создать URL `/index.php/comments?page=2`. +Аналогично, для маршрута `comment/index`, при помощи третьего правила, будет создан URL `comment/index`. -### Параметры в имени хоста +> Note: Использование параметров в маршрутах позволяет значительно уменьшить количество правил URL и улучшить производительность компонента [[yii\web\UrlManager|URL manager]]. -Существует возможность включать имена хостов в правила парсинга и создания URL. Например, может возникнуть необходимость получить часть имени хоста в качестве `GET` параметра, чтобы работать с поддоменами. Например, URL `http://admin.example.com/en/profile` может быть разобран на `GET` параметры `user=admin` и `lang=en`. Аналогично, правила с именами хостов могут использоваться для создания URL. +По умолчанию, все параметры, указанные в правиле, являются обязательными. Если запрошенный URL не содержит обязательный параметр, или если URL создается без обязательного параметра, данное правило не будет применено. Свойство [[yii\web\UrlRule::defaults]] позволяет сделать нужные параметры не обязательными. Параметры, перечисленные в данном свойстве, будут иметь заданные значения, в случае если они пропущены. -Чтобы использовать параметры в имени хоста, просто объявите правило с именем хоста, например, +В следующем правиле описаны необязательные параметры `page` и `tag`, которые примут значения `1` и `пустая строка` в случае, если они будут пропущены. ```php [ - 'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile', + // ...другие правила... + [ + 'pattern' => 'posts/<page:\d+>/<tag>', + 'route' => 'post/index', + 'defaults' => ['page' => 1, 'tag' => ''], + ], ] ``` -В этом примере первый сегмент имени хоста обрабатывается как имя пользователя (user), тогда как первый сегмент пути обрабатывается как параметр языка (lang). Правило относится к маршруту `user/profile`. +Выше приведенное правило может быть использовано для разбора или создания следующих URL: -Обратите внимание, что атрибут [[yii\web\UrlManager::showScriptName]] не оказывает действия на правила, в которых используются параметры в имени хоста. +* `/index.php/posts`: `page` равно 1, `tag` равно ''. +* `/index.php/posts/2`: `page` равно 2, `tag` равно ''. +* `/index.php/posts/2/news`: `page` равно 2, `tag` равно `'news'`. +* `/index.php/posts/news`: `page` равно 1, `tag` равно `'news'`. -Также учтите, что любое правило с параметрами в имени хоста не должно содержать подпапку, если приложение размещено в подпапке web рута. Например, если приложение находится в `http://www.example.com/sandbox/blog`, тогда нам все равно в правиле нужно его прописать без `sandbox/blog`. +Без использования необязательных параметров понадобилось бы создать 4 правила для достижения того же результата. -### Добавка суффикса URL + +### Правила с именами серверов <span id="rules-with-server-names"></span> + +Существует возможность включать имена серверов в шаблон правил URL. Главным образом, это удобно, когда требуется разное поведение приложения, в зависимости от разных имен Веб серверов. Например, следующее правило позволит разобрать URL `http://admin.example.com/login` в маршрут `admin/user/login` и `http://www.example.com/login` в `site/login`. ```php -<?php -return [ - // ... - 'components' => [ - 'urlManager' => [ - 'suffix' => '.html', - ], - ], -]; +[ + 'http://admin.example.com/login' => 'admin/user/login', + 'http://www.example.com/login' => 'site/login', +] ``` -### Обработка REST запросов - -TBD: -- RESTful маршрутизация: [[yii\filters\VerbFilter]], [[yii\web\UrlManager::$rules]] -- Json API: - - response: [[yii\web\Response::format]] - - request: [[yii\web\Request::$parsers]], [[yii\web\JsonParser]] - - -Парсинг URL ------------ - -Помимо создания URL Yii также умеет парсить URL, получая на выходе маршруты и параметры. - -### Строгий парсинг URL - -По умолчанию если для URL не подошло ни одного заданного правила и URL соответствует стандартному формату, как, например `/site/page`, Yii делает попытку запустить указанный action соответствующего контроллера. Можно запретить обрабатывать URL стандартного формата, тогда, если URL не соответствует ни одному правилу, будет выброшена 404 ошибка. +Также возможно комбинирование параметров и имени сервера для динамического извлечения данных из него. Например, следующее правило позволит разобрать URL `http://en.example.com/posts` на маршрут и параметр `language=en`. ```php -<?php -return [ - // ... +[ + 'http://<language:\w+>.example.com/posts' => 'post/index', +] +``` + +> Note: Правила, содержащие имя сервера, НЕ должны содержать в шаблоне подкаталог пути ко входному скрипту. Например, если приложение расположено в `http://www.example.com/sandbox/blog`, шаблон должен быть `http://www.example.com/posts`, вместо `http://www.example.com/sandbox/blog/posts`. Это позволит изменять расположение приложения без необходимости внесения изменений в его код. + +### Суффиксы в URL <span id="url-suffixes"></span> + +Компонент предоставляет возможность добавления к URL суффиксов. Например, можно добавить к URL `.html`, что бы они выглядели как статические HTML страницы; можно добавить к URL суффикс `.json`, для указания на ожидаемый тип данных ответа. Настроить суффиксы в URL можно при помощи соответствующего свойства [[yii\web\UrlManager::suffix]] в конфигурации приложения: + +```php +[ 'components' => [ 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, 'enableStrictParsing' => true, + 'suffix' => '.html', + 'rules' => [ + // ... + ], ], ], -]; +] ``` -Создание классов для произвольных правил ------------------------------- +Данная конфигурация позволяет компоненту [[yii\web\UrlManager|URL manager]] разбирать и создавать URL с суффиксом `.html`. -[[yii\web\UrlRule]] класс используется для парсинга URL на параметры и создания URL по параметрам. Обычное решение для правил подойдет для большинства проектов, но бывают ситуации, когда лучшим выбором будет использования своего класса для задания правил. Например, на сайте автомобильного дилера могут быть URL типа `/Производитель/Модель`, где и `Производитель` и `Модель` хранятся в таблице БД. Обычное правило не сработает, т.к. оно основывается на статически заданных регулярных выражений, и извлечение информации из БД в нем не предусмотрено. +> Tip: При установке суффикса `/`, все URL будут заканчиваться слешем. -Мы можем создать новый класс для правил URL, унаследовав его от [[yii\web\UrlRule]] и использовав его в одном или нескольких URL правил. Ниже - реализация для вышеописанного примера с сайтом автомобильного дилера. Вот указание произвольного правила в конфигурации приложения: +> Note: При настроенном суффиксе, все URL не содержащие этот суффикс будут расценены как неизвестные URL. Такое поведение рекомендовано для SEO (поисковая оптимизация). + +Иногда возникает необходимость использовать разные суффиксы для разных URL. Добиться этого можно настройкой свойства [[yii\web\UrlRule::suffix|suffix]] у каждого правила. Когда это свойство установлено, оно имеет приоритет перед общей конфигурацией компонента [[yii\web\UrlManager|URL manager]]. Например, Следующая конфигурация содержит правило URL, `.json` as its suffix instead of the global one `.html`. ```php -// ... -'components' => [ - 'urlManager' => [ - 'rules' => [ - '<action:(login|logout|about)>' => 'site/<action>', - - // ... - - ['class' => 'app\components\CarUrlRule', 'connectionID' => 'db', /* ... */], +[ + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'enableStrictParsing' => true, + 'suffix' => '.html', + 'rules' => [ + // ... + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], + ], ], ], -], +] ``` -В примере мы используем произвольное URL правило `CarUrlRule` для обработки URL формата `/Производитель/Модель`. -Код самого класса может быть примерно таким: +### HTTP методы <span id="http-methods"></span> + +При реализации RESTful API, зачастую бывает необходимость в том, чтобы один и тот же URL был разобран в разные маршруты, в зависимости от HTTP метода запроса. Это легко достигается указанием HTTP методов, поддерживаемых правилом в начале шаблона. Если правило поддерживает несколько HTTP методов, их имена разделяются запятыми. Например, следующие правила имеют шаблон `post/<id:\d+>` с разными поддерживаемыми HTTP методами. Запрос `PUT post/100` будет разобран в маршрут `post/create`, в то время, как запрос `GET post/100` будер разобран в `post/view`. + +```php +[ + 'PUT,POST post/<id:\d+>' => 'post/create', + 'DELETE post/<id:\d+>' => 'post/delete', + 'post/<id:\d+>' => 'post/view', +] +``` + +> Note: Если правило URL содержит HTTP метод в шаблоне, это правило будет использовано только при разборе URL. Такое правило не будет учитываться компонентом [[yii\web\UrlManager|URL manager]] при создании URL. + +> Tip: Для упрощения маршрутизации RESTful API, Yii предоставляет специальный класс [[yii\rest\UrlRule]], который достаточно эффективен и предоставляет такие удобные возможности, как автоматическое приведение идентификаторов контроллеров к множественной форме. Более подробную информацию можно найти в разделе Веб-сервисы REST [Роутинг](rest-routing.md). + + +### Гибкая настройка правил <span id="customizing-rules"></span> + +В предыдущих примерах, преимущественно, приводились правила URL, заданные парами шаблон-маршрут. Это самый распространенный, краткий формат. В некоторых случаях возникает необходимость более гибкой настройки правил, например указание суффикса при помощи свойства [[yii\web\UrlRule::suffix]]. Пример конфигурации правила URL при помощи массива был рассмотрен в главе [Суффиксы в URL](#suffiksy-v-url): + +```php +[ + // ...другие правила URL... + + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], +] +``` + +> Info: По умолчанию, если в конфигурации правила явно незадан параметр `class`, будет создано правило класса [[yii\web\UrlRule]]. + + +### Добавление правил URL динамически <span id="adding-rules"></span> + +Правила URL могут быть динамически добавлены в компонент [[yii\web\UrlManager|URL manager]]. Часто это необходимо подключаемым [модулям](structure-modules.md) для настройки своих правил URL. Для того, что бы динамически добавленные правила могли влиять на процесс роутинга, они должны быть добавлены в процессе [предзагрузки](runtime-bootstrapping.md). В частности, модули должны реализовываться интерфейс [[yii\base\BootstrapInterface]] и добавлять правила в методе [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]], например: + +```php +public function bootstrap($app) +{ + $app->getUrlManager()->addRules([ + // правила URL описываются здесь + ], false); +} +``` + +Так же, необходимо включить данный модуль в [[yii\web\Application::bootstrap]], чтобы он смог участвовать в процессе [предзагрузки](runtime-bootstrapping.md). + + +### Создание классов правил <span id="creating-rules"></span> + +Несмотря на то, что встроенный класс [[yii\web\UrlRule]] достаточно функционален для большинства проектов, иногда возникает необходимость в создании своего класса правил URL. Например, на сайте продавца автомобилей существует необходимость поддержки URL в таком формате: `/Manufacturer/Model`, где и `Manufacturer` и `Model` должны соответствовать данным, хранящимся в базе данных. Стандартный класс [[yii\web\UrlRule]] не подойдет, так как он рассчитан на работу со статичными шаблонами. + +Для решения данной проблемы можно создать такой класс правила URL. ```php namespace app\components; -use yii\web\UrlRule; +use yii\web\UrlRuleInterface; +use yii\base\Object; -class CarUrlRule extends UrlRule +class CarUrlRule extends Object implements UrlRuleInterface { - public $connectionID = 'db'; - - public function init() - { - if ($this->name === null) { - $this->name = __CLASS__; - } - } public function createUrl($manager, $route, $params) { @@ -291,21 +469,42 @@ class CarUrlRule extends UrlRule return $params['manufacturer']; } } - return false; // это правило не подходит + return false; // данное правило не применимо } public function parseRequest($manager, $request) { $pathInfo = $request->getPathInfo(); if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) { - // check $matches[1] and $matches[3] to see - // if they match a manufacturer and a model in the database - // If so, set $params['manufacturer'] and/or $params['model'] - // and return ['car/index', $params] + // Ищем совпадения $matches[1] и $matches[3] + // с данными manufacturer и model в базе данных + // Если нашли, устанавливаем $params['manufacturer'] и/или $params['model'] + // и возвращаем ['car/index', $params] } - return false; // это правило не подходит + return false; // данное правило не применимо } } ``` -Кроме использования произвольных классов URL в случаях, подобных примеру выше, их также можно использовать для других целей. Например, мы можем написать класс правила для логгирования парсинга URL и создания запросов. Это может быть полезно на этапе разработки. Мы также можем написать класс правила чтобы показывать особую 404 страницу в случае, если другие правила не подошли для обработки текущего запроса. В этом случае произвольное правило должно стоять последним. +И использовать новый класс [[yii\web\UrlManager::rules]] при определении правил URL: + +```php +[ + // ...другие правила... + + [ + 'class' => 'app\components\CarUrlRule', + // ...настройка других параметров правила... + ], +] +``` + +## Производительность <span id="performance-consideration"></span> + +При разработке сложных Веб приложений, важно оптимизировать правила URL так, чтобы разбор запросов и создание URL занимали минимальное время. + +Использование параметров в маршрутах позволяет уменьшить количество правил, что значительно увеличивает производительность. + +При разборе или создании URL, компонент [[yii\web\UrlManager|URL manager]] проверяет правила в порядке их определения. Поэтому следует более узконаправленные и/или часто используемые правила размещать раньше прочих. + +В случае, если несколько правил имеют один и тот же префикс в шаблоне или маршруте, можно рассмотреть использование [[yii\web\GroupUrlRule]], что позволит компоненту [[yii\web\UrlManager|URL manager]] более эффективно обрабатывать правила группами. Часто это бывает полезно в случае, если приложение состоит из модулей, каждый из которых имеет свой набор правил с идентификатором модуля в качестве общего префикса. diff --git a/docs/guide-ru/runtime-sessions-cookies.md b/docs/guide-ru/runtime-sessions-cookies.md index d2f4e56931..4160f99636 100644 --- a/docs/guide-ru/runtime-sessions-cookies.md +++ b/docs/guide-ru/runtime-sessions-cookies.md @@ -16,7 +16,11 @@ ```php $session = Yii::$app->session; +<<<<<<< HEAD // проверям что сессия уже открыта +======= +// проверяем что сессия уже открыта +>>>>>>> master if ($session->isActive) ... // открываем сессию @@ -64,7 +68,11 @@ foreach ($session as $name => $value) ... foreach ($_SESSION as $name => $value) ... ``` +<<<<<<< HEAD > Информация: При получении данных из сессии через компонент `session`, сессия будет автоматически открыта, если она не была открыта до этого. В этом заключатеся отличие от получения данных из глобальной переменной `$_SESSION`, которое требует обязательного вызова `session_start()`. +======= +> Info: При получении данных из сессии через компонент `session`, сессия будет автоматически открыта, если она не была открыта до этого. В этом заключается отличие от получения данных из глобальной переменной `$_SESSION`, которое требует обязательного вызова `session_start()`. +>>>>>>> master При работе с сессионными данными, являющимися массивами, компонент `session` имеет ограничение, запрещающее прямую модификацию отдельных элементов массива. Например, @@ -114,18 +122,30 @@ $session['captcha.lifetime'] = 3600; Для улучшения производительности и читаемости кода рекомендуется использовать последний прием. Другими словами, вместо того, чтобы хранить массив как одну переменную сессии, мы сохраняем каждый элемент массива как обычную сессионную переменную с общим префиксом. +<<<<<<< HEAD ### Пользовательское хранилице для сессии <span id="custom-session-storage"></span> По умолчанию класс [[yii\web\Session]] сохраняет данные сессии в виде файлов на сервере. Однако Yii предоставляет ряд классов, которые реализуют различные способы хранения данных сессии: * [[yii\web\DbSession]]: сохраненяет данные сессии в базе данных. +======= +### Пользовательское хранилище для сессии <span id="custom-session-storage"></span> + +По умолчанию класс [[yii\web\Session]] сохраняет данные сессии в виде файлов на сервере. Однако Yii предоставляет ряд классов, которые реализуют различные способы хранения данных сессии: + +* [[yii\web\DbSession]]: сохраняет данные сессии в базе данных. +>>>>>>> master * [[yii\web\CacheSession]]: хранение данных сессии в предварительно сконфигурированном компоненте кэша [кэш](caching-data.md#cache-components). * [[yii\redis\Session]]: хранение данных сессии в [redis](http://redis.io/). * [[yii\mongodb\Session]]: хранение сессии в [MongoDB](http://www.mongodb.org/). Все эти классы поддерживают одинаковый набор методов API. В результате вы можете переключаться между различными хранилищами сессий без модификации кода приложения. +<<<<<<< HEAD > Замечание: Если вы хотите получить данные из переменной `$_SESSION` при использовании пользовательского хранилища, вы должны быть уверены, что сессия уже стартовала [[yii\web\Session::open()]], в связи с тем, что обработчики хранения пользовательских сессий регистрируются в этом методе. +======= +> Note: Если вы хотите получить данные из переменной `$_SESSION` при использовании пользовательского хранилища, вы должны быть уверены, что сессия уже стартовала [[yii\web\Session::open()]], в связи с тем, что обработчики хранения пользовательских сессий регистрируются в этом методе. +>>>>>>> master Чтобы узнать, как настроить и использовать эти компоненты, обратитесь к документации по API. Ниже приведен пример конфигурации [[yii\web\DbSession]] для использования базы данных для хранения сессии: @@ -152,18 +172,30 @@ CREATE TABLE session ) ``` +<<<<<<< HEAD где 'BLOB' соответствует типу данных предопчитаемой вами DBMS. Ниже приведены примеры соответствия типов BLOB в наиболее популярных DBMS: +======= +где 'BLOB' соответствует типу данных предпочитаемой вами DBMS. Ниже приведены примеры соответствия типов BLOB в наиболее популярных DBMS: +>>>>>>> master - MySQL: LONGBLOB - PostgreSQL: BYTEA - MSSQL: BLOB +<<<<<<< HEAD > Замечание: В зависимости от настроек параметра `session.hash_function` в вашем php.ini, может понадобиться изменить длину поля `id`. Например, если `session.hash_function=sha256`, нужно установить длину поля в 64 вместо 40. +======= +> Note: В зависимости от настроек параметра `session.hash_function` в вашем php.ini, может понадобиться изменить длину поля `id`. Например, если `session.hash_function=sha256`, нужно установить длину поля в 64 вместо 40. +>>>>>>> master ### Flash-сообщения <span id="flash-data"></span> +<<<<<<< HEAD Flash-сообщения - это особый тип данных в сессии, которые устанавливаются один раз во время запроса и доступны только на протяжении следующего запроса, затем они автоматически удаляются. Такой способ хранения информации в сессии наиболее часто используется для реализации сообщений, которые будует отображены конечному пользователю один раз, например подтверждение об успешной отправке формы. +======= +Flash-сообщения - это особый тип данных в сессии, которые устанавливаются один раз во время запроса и доступны только на протяжении следующего запроса, затем они автоматически удаляются. Такой способ хранения информации в сессии наиболее часто используется для реализации сообщений, которые будут отображены конечному пользователю один раз, например подтверждение об успешной отправке формы. +>>>>>>> master Установить и получить flash-сообщения можно через компонент приложения `session`. Например: @@ -172,7 +204,11 @@ $session = Yii::$app->session; // Запрос #1 // установка flash-сообщения с названием "postDeleted" +<<<<<<< HEAD $session->setFlash('postDeleted', 'Вы успешно удалил пост.'); +======= +$session->setFlash('postDeleted', 'Вы успешно удалили пост.'); +>>>>>>> master // Запрос #2 // отображение flash-сообщения "postDeleted" @@ -194,7 +230,11 @@ $session = Yii::$app->session; // Запрос #1 // добавить новое flash-сообщение с названием "alerts" +<<<<<<< HEAD $session->addFlash('alerts', 'Вы успешно удалии пост.'); +======= +$session->addFlash('alerts', 'Вы успешно удалили пост.'); +>>>>>>> master $session->addFlash('alerts', 'Вы успешно добавили нового друга.'); $session->addFlash('alerts', 'Благодарим.'); @@ -203,7 +243,11 @@ $session->addFlash('alerts', 'Благодарим.'); $alerts = $session->getFlash('alerts'); ``` +<<<<<<< HEAD > Замечание: Старайтесь не использовать [[yii\web\Session::setFlash()]] совместно с [[yii\web\Session::addFlash()]] для flash-сообщений с одинаковым названием. Это связано с тем, что последний метод автоматически преобразует хранимые данные в массив, чтобы иметь возможность хранить и добавлять новые данные в flash-сообщения с тем же названием. В результате, при вызове [[yii\web\Session::getFlash()]] можно обнаружить, что возвращается массив, в то время как ожидалась строка. +======= +> Note: Старайтесь не использовать [[yii\web\Session::setFlash()]] совместно с [[yii\web\Session::addFlash()]] для flash-сообщений с одинаковым названием. Это связано с тем, что последний метод автоматически преобразует хранимые данные в массив, чтобы иметь возможность хранить и добавлять новые данные в flash-сообщения с тем же названием. В результате, при вызове [[yii\web\Session::getFlash()]] можно обнаружить, что возвращается массив, в то время как ожидалась строка. +>>>>>>> master ## Куки <span id="cookies"></span> @@ -261,18 +305,30 @@ unset($cookies['language']); Кроме свойств [[yii\web\Cookie::name|name]] и [[yii\web\Cookie::value|value]], класс [[yii\web\Cookie]] также предоставляет ряд свойств для получения информации о куках: [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]]. Эти свойства можно сконфигурировать и затем добавить куку в коллекцию для HTTP-ответа. +<<<<<<< HEAD > Замечение: Для большей безопасности значение свойства [[yii\web\Cookie::httpOnly]] по умолчанию установлено в true. Это уменьшает риски доступа к защищенной куке на клиентской стороне (если браузер поддерживает такую возможность). Вы можете обратиться к [httpOnly wiki](https://www.owasp.org/index.php/HttpOnly) для дополнительной информации. +======= +> Note: Для большей безопасности значение свойства [[yii\web\Cookie::httpOnly]] по умолчанию установлено в true. Это уменьшает риски доступа к защищенной куке на клиентской стороне (если браузер поддерживает такую возможность). Вы можете обратиться к [httpOnly wiki](https://www.owasp.org/index.php/HttpOnly) для дополнительной информации. +>>>>>>> master ### Валидация кук <span id="cookie-validation"></span> Во время записи и чтения кук через компоненты `request` и `response`, как будет показано в двух последующих подразделах, фреймворк предоставляет автоматическую валидацию, которая обеспечивает защиту кук от модификации на стороне клиента. Это достигается за счет подписи каждой куки секретным ключом, позволяющим приложению распознать куку, которая была модифицирована на клиентской стороне. В таком случае кука НЕ БУДЕТ доступна через свойство [[yii\web\Request::cookies|cookie collection]] компонента `request`. +<<<<<<< HEAD > Замечение: Валидация кук защищает только от их модификации. Если валидация не была пройдена, получить доступ к кукам все еще можно через глобальную переменную `$_COOKIE`. Это связано с тем, что дополнительные пакеты и библиотеки могут манипулировать куками без вызова валидации, которую обеспепичает Yii. +======= +> Note: Валидация кук защищает только от их модификации. Если валидация не была пройдена, получить доступ к кукам все еще можно через глобальную переменную `$_COOKIE`. Это связано с тем, что дополнительные пакеты и библиотеки могут манипулировать куками без вызова валидации, которую обеспечивает Yii. +>>>>>>> master По-умолчанию валидация кук включена. Её можно отключить, установив свойство [[yii\web\Request::enableCookieValidation]] в false, однако мы настоятельно не рекомендуем это делать. +<<<<<<< HEAD > Замечание: Куки, которые напрямую читаются/пишутся через `$_COOKIE` и `setcookie()` НЕ БУДУТ валидироваться. +======= +> Note: Куки, которые напрямую читаются/пишутся через `$_COOKIE` и `setcookie()` НЕ БУДУТ валидироваться. +>>>>>>> master При использовании валидации кук необходимо указать значение свойства [[yii\web\Request::cookieValidationKey]], которое будет использовано для генерации вышеупомянутого секретного ключа. Это можно сделать, настроив компонент `request` в конфигурации приложения: @@ -286,5 +342,9 @@ return [ ]; ``` +<<<<<<< HEAD > Замечение: Свойство [[yii\web\Request::cookieValidationKey|cookieValidationKey]] является секретным значением и должно быть известно только людям, которым вы доверяете. Не помещайте эту информацию под систему контроля версий. +======= +> Note: Свойство [[yii\web\Request::cookieValidationKey|cookieValidationKey]] является секретным значением и должно быть известно только людям, которым вы доверяете. Не помещайте эту информацию под систему контроля версий. +>>>>>>> master diff --git a/docs/guide-ru/security-authentication.md b/docs/guide-ru/security-authentication.md index a49537bc79..1824d21e57 100644 --- a/docs/guide-ru/security-authentication.md +++ b/docs/guide-ru/security-authentication.md @@ -1,6 +1,7 @@ Аутентификация ============== +<<<<<<< HEAD Аутентификация это процесс проверки подлинности пользователя. Обычно используется идентификатор (например `username` или адрес электронной почты) и секретный токен (например пароль или ключ доступа), чтобы судить о том что пользователь именно тот за кого себя выдаёт. Аутентификация является основной функуцией формы входа. @@ -10,12 +11,27 @@ Yii обеспечивает фреймворк авторизации с раз * Настроить компонент приложения [[yii\web\User|user]]; * Создать класс реализующии интерфейс [[yii\web\IdentityInterface]]. +======= +Аутентификация — это процесс проверки подлинности пользователя. Обычно используется идентификатор +(например, `username` или адрес электронной почты) и секретный токен (например, пароль или ключ доступа), чтобы судить о +том, что пользователь именно тот, за кого себя выдаёт. Аутентификация является основной функцией формы входа. + +Yii предоставляет фреймворк авторизации с различными компонентами, обеспечивающими процесс входа. +Для использования этого фреймворка вам нужно проделать следующее: + +* Настроить компонент приложения [[yii\web\User|user]]; +* Создать класс, реализующий интерфейс [[yii\web\IdentityInterface]]. +>>>>>>> master ## Настройка [[yii\web\User]] <span id="configuring-user"></span> Компонент [[yii\web\User|user]] управляет статусом аутентификации пользователя. +<<<<<<< HEAD Он требует, чтоб вы указали [[yii\web\User::identityClass|identity class]], который будет содержать +======= +Он требует, чтобы вы указали [[yii\web\User::identityClass|identity class]], который будет содержать +>>>>>>> master текущую логику аутентификации. В следующей конфигурации приложения, [[yii\web\User::identityClass|identity class]] для [[yii\web\User|user]] задан как `app\models\User`, реализация которого будет объяснена в следующем разделе: @@ -38,22 +54,38 @@ return [ * [[yii\web\IdentityInterface::findIdentity()|findIdentity()]]: Этот метод находит экземпляр `identity class`, используя ID пользователя. Этот метод используется, когда необходимо поддерживать состояние аутентификации через сессии. * [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]: Этот метод находит экземпляр `identity class`, +<<<<<<< HEAD используя токен доступа. Этот метод используется когда нужно аутентифицировать пользователя по единственному секретному токену (например в RESTful приложениях, не сохраняющих состояние между запросами). * [[yii\web\IdentityInterface::getId()|getId()]]: Этот метод возвращает ID пользователя представленного данным экземпляром `identity`. * [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: Этот метод возвращает ключ используемый для основанной на `cookie` аутентификации. Ключ сохраняется в аутентификационной cookie и позже сравниватеся с версией находящейся на сервере, чтоб удостоверится что аутентификационная `cookie` верная. +======= + используя токен доступа. Метод используется, когда требуется аутентифицировать пользователя + только по секретному токену (например в RESTful приложениях, не сохраняющих состояние между запросами). +* [[yii\web\IdentityInterface::getId()|getId()]]: Этот метод возвращает ID пользователя, представленного данным экземпляром `identity`. +* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: Этот метод возвращает ключ, используемый для основанной на `cookie` аутентификации. + Ключ сохраняется в аутентификационной cookie и позже сравнивается с версией, находящейся на сервере, + чтобы удостоверится, что аутентификационная `cookie` верная. +>>>>>>> master * [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: Этот метод реализует логику проверки ключа для основанной на `cookie` аутентификации. Если какой-то из методов не требуется, то можно реализовать его с пустым телом. Для примера, если у вас RESTful приложение, не сохраняющее состояние между запросами, вы можете реализовать только [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]] +<<<<<<< HEAD и [[yii\web\IdentityInterface::getId()|getId()]] тогда как остальные методы оставить пустыми. В следующем примере, [[yii\web\User::identityClass|identity class]] реализован как класс [Active Record](db-active-record.md) связанный с таблицей `user`. +======= +и [[yii\web\IdentityInterface::getId()|getId()]], тогда как остальные методы оставить пустыми. + +В следующем примере, [[yii\web\User::identityClass|identity class]] реализован +как класс [Active Record](db-active-record.md), связанный с таблицей `user`. +>>>>>>> master ```php <?php @@ -117,8 +149,13 @@ class User extends ActiveRecord implements IdentityInterface } ``` +<<<<<<< HEAD Как обьяснялось ранее, вам нужно реализовать только `getAuthKey()` и `validateAuthKey()` если ваше приложение использует только аутентификацию основанную на `cookie`. В этом случае, вы можете использовать следующий код для генерации +======= +Как объяснялось ранее, вам нужно реализовать только `getAuthKey()` и `validateAuthKey()`, если ваше приложение использует +только аутентификацию основанную на `cookie`. В этом случае вы можете использовать следующий код для генерации +>>>>>>> master ключа аутентификации для каждого пользователя и хранения его в таблице `user`: ```php @@ -139,14 +176,21 @@ class User extends ActiveRecord implements IdentityInterface } ``` +<<<<<<< HEAD > Замечание: Не путайте `identity` класс `User` с классом [[yii\web\User]]. Первый является классом реализующим логику аутентификации пользователя. Он часто реализуется как класс [Active Record](db-active-record.md) связанный с некоторым постоянным хранилищем где лежит информация о пользователях. Второй, это класс компонента приложения +======= +> Note: Не путайте `identity` класс `User` с классом [[yii\web\User]]. Первый является классом, реализующим + логику аутентификации пользователя. Он часто реализуется как класс [Active Record](db-active-record.md), связанный + с некоторым постоянным хранилищем, где лежит информация о пользователях. Второй — это класс компонента приложения, +>>>>>>> master отвечающий за управление состоянием аутентификации пользователя. ## Использование [[yii\web\User]] <span id="using-user"></span> +<<<<<<< HEAD В основном класс [[yii\web\User]] используют как компонент приложения `user`. Можно получить `identity` текущего пользователя используя выражение `Yii::$app->user->identity`. Оно вернёт экземпляр @@ -159,13 +203,31 @@ class User extends ActiveRecord implements IdentityInterface $identity = Yii::$app->user->identity; // ID текущего пользователя. `Null` если пользователь не аутентифицирован. +======= +В основном класс [[yii\web\User]] используют как компонент приложения `user`. + +Можно получить `identity` текущего пользователя, используя выражение `Yii::$app->user->identity`. Оно вернёт экземпляр +[[yii\web\User::identityClass|identity class]], представляющий текущего аутентифицированного пользователя, +или `null`, если текущий пользователь не аутентифицирован (например, гость). Следующий код показывает, как получить +другую связанную с аутентификацией информацию из [[yii\web\User]]: + +```php +// `identity` текущего пользователя. `Null`, если пользователь не аутентифицирован. +$identity = Yii::$app->user->identity; + +// ID текущего пользователя. `Null`, если пользователь не аутентифицирован. +>>>>>>> master $id = Yii::$app->user->id; // проверка на то, что текущий пользователь гость (не аутентифицирован) $isGuest = Yii::$app->user->isGuest; ``` +<<<<<<< HEAD Для залогинивания пользователя, вы можете использовать следующий код: +======= +Для залогинивания пользователя вы можете использовать следующий код: +>>>>>>> master ```php // найти identity с указанным username. @@ -178,12 +240,20 @@ Yii::$app->user->login($identity); Метод [[yii\web\User::login()]] устанавливает `identity` текущего пользователя в [[yii\web\User]]. Если сессии [[yii\web\User::enableSession|включены]], то `identity` будет сохраняться в сессии, так что состояние +<<<<<<< HEAD статуса аутентификации будет поддерживаться на всём протяжении сессии. Если основанный на cookie вход (так называемый "запомни меня" вход) [[yii\web\User::enableAutoLogin|включен]], то `identity` также будет сохранена в `cookie` так чтобы статус аутентификации пользователя мог быть восстановлен на протяжении всего времени жизни `cookie`. Для включения входа основанного на `cookie`, вам нужно установить [[yii\web\User::enableAutoLogin]] в `true` в конфигурации приложения. Вы также можете настроить время жизни передав его при вызове метода [[yii\web\User::login()]]. +======= +статуса аутентификации будет поддерживаться на всём протяжении сессии. Если [[yii\web\User::enableAutoLogin|включен]] вход, основанный на cookie (так называемый "запомни меня" вход), то `identity` также будет сохранена +в `cookie` так, чтобы статус аутентификации пользователя мог быть восстановлен на протяжении всего времени жизни `cookie`. + +Для включения входа, основанного на `cookie`, вам нужно установить [[yii\web\User::enableAutoLogin]] в `true` +в конфигурации приложения. Вы также можете настроить время жизни, передав его при вызове метода [[yii\web\User::login()]]. +>>>>>>> master Для выхода пользователя, просто вызовите @@ -193,14 +263,22 @@ Yii::$app->user->logout(); Обратите внимание: выход пользователя имеет смысл только если сессии включены. Метод сбрасывает статус аутентификации сразу и из памяти и из сессии. И по умолчанию, будут также уничтожены *все* сессионные данные пользователя. +<<<<<<< HEAD Если вы хотите сохранить сессионные данные, вы должны вызвать `Yii::$app->user->logout(false)`, вместо этого. +======= +Если вы хотите сохранить сессионные данные, вы должны вместо этого вызвать `Yii::$app->user->logout(false)`. +>>>>>>> master ## События аутентификации <span id="auth-events"></span> Класс [[yii\web\User]] вызывает несколько событий во время процессов входа и выхода. +<<<<<<< HEAD * [[yii\web\User::EVENT_BEFORE_LOGIN|EVENT_BEFORE_LOGIN]]: вызвается перед вызовом [[yii\web\User::login()]]. +======= +* [[yii\web\User::EVENT_BEFORE_LOGIN|EVENT_BEFORE_LOGIN]]: вызывается перед вызовом [[yii\web\User::login()]]. +>>>>>>> master Если обработчик устанавливает свойство [[yii\web\UserEvent::isValid|isValid]] объекта в `false`, процесс входа будет прерван. * [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]]: вызывается после успешного входа. @@ -209,6 +287,11 @@ Yii::$app->user->logout(); процесс выхода будет прерван. * [[yii\web\User::EVENT_AFTER_LOGOUT|EVENT_AFTER_LOGOUT]]: вызывается после успешного выхода. +<<<<<<< HEAD Вы можете использовать эти события для реазлизации функции аудита входа, статистике онлайн пользователей. Для примера, в обработчике для [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]], вы можете сделать запись о времени и IP +======= +Вы можете использовать эти события для реализации функции аудита входа, сбора статистики онлайн пользователей. Например, +в обработчике для [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]] вы можете сделать запись о времени и IP +>>>>>>> master адресе входа в таблицу `user`. diff --git a/docs/guide-ru/security-authorization.md b/docs/guide-ru/security-authorization.md new file mode 100644 index 0000000000..7cc4456cdc --- /dev/null +++ b/docs/guide-ru/security-authorization.md @@ -0,0 +1,517 @@ +Авторизация +=========== + +> Note: этот раздел находится на стадии разработки. + +Авторизация — это процесс проверки того, что пользователь имеет достаточно прав, чтобы выполнить какие-то действия. Yii предоставляет два метода авторизации: фильтры контроля доступа (ACF) и контроль доступа на основе ролей (RBAC). + + +Фильтры контроля доступа +------------------------ + +Фильтры контроля доступа (ACF) являются простым методом, который лучше всего использовать в приложениях с простым +контролем доступа. Как видно из их названия, ACF — это фильтры, которые могут присоединяться к контроллеру +или модулю как поведение. ACF проверяет набор [[yii\filters\AccessControl::rules|правил доступа]], чтобы убедиться, +что пользователь имеет доступ к запрошенному действию. + +Код ниже показывает, как использовать ACF фильтр, реализованный в [[yii\filters\AccessControl]]: + +```php +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['login', 'signup'], + 'roles' => ['?'], + ], + [ + 'allow' => true, + 'actions' => ['logout'], + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +} +``` + +Код выше показывает ACF фильтр, связанный с контроллером `site` через поведение. Это типичный способ использования фильтров действий. +Параметр `only` указывает, что фильтр ACF нужно применять только к действиям `login`, `logout` и `signup`. +Параметр `rules` задаёт [[yii\filters\AccessRule|правила доступа]], которые означают следующее: + +- Разрешить всем гостям (ещё не прошедшим авторизацию) доступ к действиям `login` и `signup`. + Опция `roles` содержит знак вопроса `?`, это специальный токен обозначающий "гостя". +- Разрешить аутентифицированным пользователям доступ к действию `logout`. Символ `@` — это другой специальный токен, + обозначающий аутентифицированного пользователя. + +Когда фильтр ACF проводит проверку авторизации, он проверяет правила по одному сверху вниз, пока не найдёт совпадение. +Значение опции `allow` выбранного правила указывает, авторизовывать пользователя или нет. Если ни одно из правил +не совпало, то пользователь считается НЕавторизованным, и фильтр ACF останавливает дальнейшее выполнение действия. + +По умолчанию, когда у пользователя отсутствует доступ к текущему действию, ACF делает следующее: + +* Если пользователь гость, вызывается [[yii\web\User::loginRequired()]], который перенаправляет браузер на страницу входа. +* Если пользователь авторизован, генерируется исключение [[yii\web\ForbiddenHttpException]]. + +Вы можете переопределить это поведение, настроив свойство [[yii\filters\AccessControl::denyCallback]]: + +```php +[ + 'class' => AccessControl::className(), + 'denyCallback' => function ($rule, $action) { + throw new \Exception('У вас нет доступа к этой странице'); + } +] +``` + +[[yii\filters\AccessRule|Правила доступа]] поддерживают набор свойств. Ниже дано краткое описание поддерживаемых опций. +Вы также можете расширить [[yii\filters\AccessRule]], чтобы создать свой собственный класс правил доступа. + + * [[yii\filters\AccessRule::allow|allow]]: задаёт какое это правило, "allow" или "deny". + + * [[yii\filters\AccessRule::actions|actions]]: задаёт действия, соответствующие этому правилу. +Значение должно быть массивом идентификаторов действий. Сравнение — регистрозависимо. Если свойство пустое или не задано, +то правило применяется ко всем действиям. + + * [[yii\filters\AccessRule::controllers|controllers]]: задаёт контроллеры, которым соответствует правило. +Значение должно быть массивом с идентификаторами контроллеров. Сравнение регистрозависимо. Если свойство +пустое или не задано, то правило применяется ко всем контроллерам. + + * [[yii\filters\AccessRule::roles|roles]]: задаёт роли пользователей, соответствующих этому правилу. + Распознаются две специальные роли, которые проверяются с помощью [[yii\web\User::isGuest]]: + + - `?`: соответствует гостевому пользователю (не аутентифицирован), + - `@`: соответствует аутентифицированному пользователю. + + Использование других имён ролей будет приводить к вызову метода [[yii\web\User::can()]], который требует включения + RBAC (будет описано дальше). Если свойство пустое или не задано, то правило применяется ко всем ролям. + + * [[yii\filters\AccessRule::ips|ips]]: задаёт [[yii\web\Request::userIP|IP адреса пользователей]], для которых применяется это правило. IP адрес может содержать `*` в конце, так чтобы он соответствовал IP адресу с таким же префиксом. +Для примера, '192.168.*' соответствует всем IP адресам в сегменте '192.168.'. Если свойство пустое или не задано, +то правило применяется ко всем IP адресам. + + * [[yii\filters\AccessRule::verbs|verbs]]: задаёт http метод (например, `GET`, `POST`), соответствующий правилу. +Сравнение — регистронезависимо. + + * [[yii\filters\AccessRule::matchCallback|matchCallback]]: задаёт PHP колбек, который вызывается для определения, +что правило должно быть применено. + + * [[yii\filters\AccessRule::denyCallback|denyCallback]]: задаёт PHP колбек, который будет вызван, если доступ будет +запрещён при вызове этого правила. + +Ниже показан пример, показывающий использование опции `matchCallback`, которая позволяет писать произвольную +логику проверки доступа: + +```php +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['special-callback'], + 'rules' => [ + [ + 'actions' => ['special-callback'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], + ], + ], + ]; + } + + // Колбек сработал! Эта страница может быть отображена только 31-ого октября + public function actionSpecialCallback() + { + return $this->render('happy-halloween'); + } +} +``` + + +Контроль доступа на основе ролей (RBAC) +--------------------------------------- + +Управление доступом на основе ролей (RBAC) обеспечивает простой, но мощный централизованный контроль доступа. +Пожалуйста, обратитесь к [Wikipedia](https://ru.wikipedia.org/wiki/%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%BE%D0%BC_%D0%BD%D0%B0_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B5_%D1%80%D0%BE%D0%BB%D0%B5%D0%B9) +для получения информации о сравнении RBAC с другими, более традиционными, системами контроля доступа. + +Yii реализует общую иерархическую RBAC, следуя [NIST RBAC model](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). +Обеспечивается функциональность RBAC через [компонент приложения](structure-application-components.md) [[yii\rbac\ManagerInterface|authManager]]. + +Использование RBAC состоит из двух частей. Первая часть — это создание RBAC данных авторизации, и вторая часть — это +использование данных авторизации для проверки доступа в том месте, где это нужно. + +Для облегчения последующего описания, мы сначала введём некоторые основные понятия RBAC. + + +### Основные концепции + +Роль представляет собой набор разрешений (*permissions*) (например, создание сообщения, обновление сообщения). +Роль может быть назначена на одного или многих пользователей. Чтобы проверить, имеет ли пользователь указанные +разрешения, мы должны проверить, назначена ли пользователю роль, которая содержит данное разрешение. + +С каждой ролью или разрешением может быть связано правило (*rule*). Правило представляет собой кусок кода, который будет +выполняться в ходе проверки доступа для определения может ли быть применена соответствующая роль или разрешение +к текущему пользователю. Например, разрешение "обновление поста" может иметь правило, которое проверяет является ли +текущий пользователь автором поста. Во время проверки доступа, если пользователь не является автором поста, он/она будет +считаться не имеющими разрешения "обновление поста". + +И роли, и разрешения могут быть организованы в иерархию. В частности, роль может содержать другие роли или разрешения; и +разрешения могут содержать другие разрешения. Yii реализует *частично упорядоченную* иерархию, которая включает в себя +специальные *деревья* иерархии. Роль может содержать разрешение, но обратное не верно. + + +### Настройка RBAC Manager + +Перед определением авторизационных данных и проверкой прав доступа, мы должны настроить компонент приложения +[[yii\base\Application::authManager|authManager]]. Yii предоставляет два типа менеджеров авторизации: +[[yii\rbac\PhpManager]] и [[yii\rbac\DbManager]]. Первый использует файл с PHP скриптом для хранения данных авторизации, +второй сохраняет данные в базе данных. Вы можете использовать первый, если ваше приложение не требует слишком динамичного +управления ролями и разрешениями. + +#### Настройка authManager с помощью `PhpManager` + +Следующий код показывает как настроить в конфигурации приложения `authManager` с использованием класса [[yii\rbac\PhpManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + ], + // ... + ], +]; +``` + +Теперь `authManager` может быть доступен через `\Yii::$app->authManager`. + +> Замечание: По умолчанию, [[yii\rbac\PhpManager]] сохраняет данные RBAC в файлах в директории `@app/rbac/`. Убедитесь + что данная директория и файлы в них доступны для записи Web серверу, если иерархия разрешений должна меняться онлайн. + +#### Настройка authManager с помощью `DbManager` + +Следующий код показывает как настроить в конфигурации приложения `authManager` с использованием класса [[yii\rbac\DbManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', + ], + // ... + ], +]; +``` + +`DbManager` использует четыре таблицы для хранения данных: + +- [[yii\rbac\DbManager::$itemTable|itemTable]]: таблица для хранения авторизационных элементов. По умолчанию "auth_item". +- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: таблица для хранения иерархии элементов. По умолчанию "auth_item_child". +- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: таблица для хранения назначений элементов авторизации. По умолчанию "auth_assignment". +- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: таблица для хранения правил. По умолчанию "auth_rule". + +Прежде чем вы начнёте использовать этот менеджер, вам нужно создать таблицы в базе данных. Чтобы сделать это, +вы можете использовать миграцию хранящуюся в файле `@yii/rbac/migrations`: + +`yii migrate --migrationPath=@yii/rbac/migrations` + +Теперь `authManager` может быть доступен через `\Yii::$app->authManager`. + +### Создание данных авторизации + +Для создания данных авторизации нужно выполнить следующие задачи: + +- определение ролей и разрешений; +- установка отношений между ролями и правами доступа; +- определение правил; +- связывание правил с ролями и разрешениями; +- назначение ролей пользователям. + +В зависимости от требований к гибкости авторизации перечисленные задачи могут быть выполнены разными путями. + +Если иерархия прав не меняется, и количество пользователей зафиксировано, вы можете создать +[консольную команду](tutorial-console.md#create-command), которая будет единожды инициализировать данные +через APIs, предоставляемое `authManager`: + +```php +<?php +namespace app\commands; + +use Yii; +use yii\console\Controller; + +class RbacController extends Controller +{ + public function actionInit() + { + $auth = Yii::$app->authManager; + + // добавляем разрешение "createPost" + $createPost = $auth->createPermission('createPost'); + $createPost->description = 'Create a post'; + $auth->add($createPost); + + // добавляем разрешение "updatePost" + $updatePost = $auth->createPermission('updatePost'); + $updatePost->description = 'Update post'; + $auth->add($updatePost); + + // добавляем роль "author" и даём роли разрешение "createPost" + $author = $auth->createRole('author'); + $auth->add($author); + $auth->addChild($author, $createPost); + + // добавляем роль "admin" и даём роли разрешение "updatePost" + // а также все разрешения роли "author" + $admin = $auth->createRole('admin'); + $auth->add($admin); + $auth->addChild($admin, $updatePost); + $auth->addChild($admin, $author); + + // Назначение ролей пользователям. 1 и 2 это IDs возвращаемые IdentityInterface::getId() + // обычно реализуемый в модели User. + $auth->assign($author, 2); + $auth->assign($admin, 1); + } +} +``` + +После выполнения команды `yii rbac/init` мы получим следующую иерархию: + +![Простая иерархия RBAC](images/rbac-hierarchy-1.png "Простая иерархия RBAC") + +Автор может создавать пост, администратор может обновлять пост и делать всё, что может делать автор. + +Если ваше приложение позволяет регистрировать пользователей, то вам необходимо сразу назначать роли этим новым пользователям. +Например, для того, чтобы все вошедшие пользователи могли стать авторами в расширенном шаблоне проекта, вы должны изменить +`frontend\models\SignupForm::signup()` как показано ниже: + +```php +public function signup() +{ + if ($this->validate()) { + $user = new User(); + $user->username = $this->username; + $user->email = $this->email; + $user->setPassword($this->password); + $user->generateAuthKey(); + $user->save(false); + + // нужно добавить следующие три строки: + $auth = Yii::$app->authManager; + $authorRole = $auth->getRole('author'); + $auth->assign($authorRole, $user->getId()); + + return $user; + } + + return null; +} +``` + +Для приложений, требующих комплексного контроля доступа с динамически обновляемыми данными авторизации, существуют +специальные пользовательские интерфейсы (так называемые админ-панели), которые могут быть разработаны с +использованием API, предлагаемого `authManager`. + + +### Использование правил + +Как упомянуто выше, правила добавляют дополнительные ограничения на роли и разрешения. Правила — это классы, расширяющие +[[yii\rbac\Rule]]. Они должны реализовывать метод [[yii\rbac\Rule::execute()|execute()]]. В иерархии, созданной нами ранее, +автор не может редактировать свой пост. Давайте исправим это. Сначала мы должны создать правило, проверяющее +что пользователь является автором поста: + +```php +namespace app\rbac; + +use yii\rbac\Rule; + +/** + * Проверяем authorID на соответствие с пользователем, переданным через параметры + */ +class AuthorRule extends Rule +{ + public $name = 'isAuthor'; + + /** + * @param string|integer $user the user ID. + * @param Item $item the role or permission that this rule is associated width. + * @param array $params parameters passed to ManagerInterface::checkAccess(). + * @return boolean a value indicating whether the rule permits the role or permission it is associated with. + */ + public function execute($user, $item, $params) + { + return isset($params['post']) ? $params['post']->createdBy == $user : false; + } +} +``` + +Правило выше проверяет, что `post` был создан `$user`. Мы создадим специальное разрешение `updateOwnPost` в команде, +которую мы использовали ранее: + +```php +$auth = Yii::$app->authManager; + +// add the rule +$rule = new \app\rbac\AuthorRule; +$auth->add($rule); + +// добавляем разрешение "updateOwnPost" и привязываем к нему правило. +$updateOwnPost = $auth->createPermission('updateOwnPost'); +$updateOwnPost->description = 'Update own post'; +$updateOwnPost->ruleName = $rule->name; +$auth->add($updateOwnPost); + +// "updateOwnPost" будет использоваться из "updatePost" +$auth->addChild($updateOwnPost, $updatePost); + +// разрешаем "автору" обновлять его посты +$auth->addChild($author, $updateOwnPost); +``` + +Теперь мы имеем следующую иерархию: + +![Иерархия RBAC с правилом](images/rbac-hierarchy-2.png "Иерархия RBAC с правилом") + +### Проверка доступа + +С готовыми авторизационными данными проверка доступа — это просто вызов метода [[yii\rbac\ManagerInterface::checkAccess()]]. +Так как большинство проверок доступа относятся к текущему пользователю, для удобства Yii предоставляет сокращённый метод +[[yii\web\User::can()]], который можно использовать как показано ниже: + +```php +if (\Yii::$app->user->can('createPost')) { + // create post +} +``` + +Если текущий пользователь Jane с ID=1, мы начнём с `createPost` и попробуем добраться до `Jane`: + +![Проверка доступа](images/rbac-access-check-1.png "Проверка доступа") + +Для того чтобы проверить, может ли пользователь обновить пост, нам надо передать дополнительный параметр, +необходимый для правила `AuthorRule`, описанного ранее: + +```php +if (\Yii::$app->user->can('updatePost', ['post' => $post])) { + // update post +} +``` + +Вот что происходит если текущим пользователем является John: + +![Проверка доступа](images/rbac-access-check-2.png "Проверка доступа") + +Мы начинаем с `updatePost` и переходим к `updateOwnPost`. Для того чтобы это произошло, правило `AuthorRule` должно вернуть +`true` при вызове метода `execute`. Метод получает `$params`, переданный при вызове метода `can`, значение которого равно +`['post' => $post]`. Если всё правильно, мы увидим, что `author` привязан к John. + +В случае Jane это немного проще, потому что она admin: + +![Проверка доступа](images/rbac-access-check-3.png "Проверка доступа") + +### Использование роли по умолчанию + +Роль по умолчанию — это роль, которая *неявно* присваивается *всем* пользователям. Вызов метода +[[yii\rbac\ManagerInterface::assign()]] не требуется, и авторизационные данные не содержат информации о назначении. + +Роль по умолчанию обычно связывают с правилом, определяющим к какой роли принадлежит каждый пользователь. + +Роли по умолчанию обычно используются в приложениях, которые уже имеют какое-то описание ролей. Для примера, приложение +может иметь столбец "group" в таблице пользователей, и каждый пользователь принадлежит к какой-то группе. Если каждая +группа может быть сопоставлена роли в модели RBAC, вы можете использовать роль по умолчанию для автоматического назначения +каждому пользователю роли RBAC. Давайте используем пример, чтобы понять как это можно сделать. + +Предположим что в таблице пользователей у вас есть столбец `group`, в котором значение 1 представляет группу "администратор", +а 2 — группу "автор". Вы планируете иметь две RBAC роли: `admin` и `author`, представляющие разрешения для двух +соответствующих групп. Вы можете настроить данные роли как показано ниже. + +```php +namespace app\rbac; + +use Yii; +use yii\rbac\Rule; + +/** + * Checks if user group matches + */ +class UserGroupRule extends Rule +{ + public $name = 'userGroup'; + + public function execute($user, $item, $params) + { + if (!Yii::$app->user->isGuest) { + $group = Yii::$app->user->identity->group; + if ($item->name === 'admin') { + return $group == 1; + } elseif ($item->name === 'author') { + return $group == 1 || $group == 2; + } + } + return false; + } +} + +$auth = Yii::$app->authManager; + +$rule = new \app\rbac\UserGroupRule; +$auth->add($rule); + +$author = $auth->createRole('author'); +$author->ruleName = $rule->name; +$auth->add($author); +// ... add permissions as children of $author ... + +$admin = $auth->createRole('admin'); +$admin->ruleName = $rule->name; +$auth->add($admin); +$auth->addChild($admin, $author); +// ... add permissions as children of $admin ... +``` + +Обратите внимание, так как "author" добавлен как дочерняя роль к "admin", следовательно в реализации метода `execute()` +класса правила вы должны учитывать эту иерархию. Именно поэтому для роли "author" метод `execute()` вернёт истину, +если пользователь принадлежит к группам 1 или 2 (это означает, что пользователь находится в группе +администраторов или авторов) + +Далее настроим `authManager` с помощью перечисления ролей в свойстве [[yii\rbac\BaseManager::$defaultRoles]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + 'defaultRoles' => ['admin', 'author'], + ], + // ... + ], +]; +``` + +Теперь, если вы выполните проверку доступа, для обоих ролей `admin` и `author` будет выполнена проверка правила, +асоциированного с ними. Если правило вернёт истину, это будет означать, что роль применяется к текущему пользователю. +На основании правила, реализованного выше: если пользователь входит в группу 1, пользователю будет назначена роль `admin`; +и если значение `group` равно 2, будет применена роль `author`. diff --git a/docs/guide-ru/security-best-practices.md b/docs/guide-ru/security-best-practices.md new file mode 100644 index 0000000000..edf2666e5e --- /dev/null +++ b/docs/guide-ru/security-best-practices.md @@ -0,0 +1,177 @@ +Лучшие практики безопасности +============================ + +Ниже мы рассмотрим основные принципы безопасности и опишем, как избежать угроз при разработке на Yii. + +Основные принципы +----------------- + +Есть два основных принципа безопасности, независимо от того, какое приложение разрабатывается: + +1. Фильтрация ввода. +2. Экранирование вывода. + + +### Фильтрация ввода + +Фильтрация ввода означает, что входные данные никогда не должны считаться безопасными и вы всегда должны проверять, +являются ли полученные данные допустимыми. Например, если мы знаем, что сортировка может быть осуществлена только +по трём полям `title`, `created_at` и `status`, и поле может передаваться через ввод пользователем, лучше проверить +значение там, где мы его получили: + +```php +$sortBy = $_GET['sort']; +if (!in_array($sortBy, ['title', 'created_at', 'status'])) { + throw new Exception('Invalid sort value.'); +} +``` + +В Yii, вы, скорее всего, будете использовать [валидацию форм](input-validation.md), чтоб делать такие проверки. + + +### Экранирование вывода + +Экранирование вывода означает, что данные в зависимости от контекста должны экранироваться, например в контексте +HTML вы должны экранировать `<`, `>` и похожие специальные символы. В контексте JavaScript или SQL будет другой набор +символов. Так как ручное экранирование черевато ошибками, Yii предоставляет различные утилиты для экранирования в +различных контекстах. + +Как избежать SQL-иньекций +------------------------- + +SQL-иньекции происходят, когда текст запроса формируется склеиванием не экранированных строк, как показано ниже: + +```php +$username = $_GET['username']; +$sql = "SELECT * FROM user WHERE username = '$username'"; +``` + +Вместо того, чтобы подставлять корректное имя пользователя, злоумышленник может передать вам в приложение что-то вроде +`'; DROP TABLE user; --`. +В результате SQL будет следующий: + +```sql +SELECT * FROM user WHERE username = ''; DROP TABLE user; --' +``` + +Это валидный запрос, который сначала будет искать пользователей с пустым именем, а затем удалит таблицу `user`. +Скорее всего будет сломано приложение и будут потеряны данные (вы ведь делаете регулярное резервное копирование?). + +Большинство запросов к базе данных в Yii происходит через [Active Record](db-active-record.md), который правильно +использует подготовленные запросы PDO внутри. При использовании подготовленных запросов невозможно манипулировать +запросом как это показано выше. + +Тем не менее, иногда нужны [сырые запросы](db-dao.md) или [построитель запросов](db-query-builder.md). В этом случае +вы должны использовать безопасные способы передачи данных. Если данные используются для сравнения со значением +столбцов предпочтительнее использовать подготовленные запросы: + +```php +// query builder +$userIDs = (new Query()) + ->select('id') + ->from('user') + ->where('status=:status', [':status' => $status]) + ->all(); + +// DAO +$userIDs = $connection + ->createCommand('SELECT id FROM user where status=:status') + ->bindValues([':status' => $status]) + ->queryColumn(); +``` + +Если данные используются в качестве имён столбцов или таблиц, то лучший путь - это разрешить только предопределённый +набор значений: + +```php +function actionList($orderBy = null) +{ + if (!in_array($orderBy, ['name', 'status'])) { + throw new BadRequestHttpException('Only name and status are allowed to order by.') + } + + // ... +} +``` + +Если это невозможно, то имена столбцов и таблиц должны экранироваться. Yii использует специальный синтаксис +для экранирования для всех поддерживаемых баз данных: + +```php +$sql = "SELECT COUNT([[$column]]) FROM {{table}}"; +$rowCount = $connection->createCommand($sql)->queryScalar(); +``` + +Вы можете получить подробную информацию о синтаксисе в [Экранирование имён таблиц и столбцов](db-dao.md#quoting-table-and-column-names). + + +Как избежать XSS +---------------- + +XSS или кросс-сайтинговый скриптинг становится возможен, когда не экранированный выходной HTML попадает в браузер. +Например, если пользователь должен ввести своё имя, но вместо `Alexander` он вводит `<script>alert('Hello!');</script>`, то +все страницы, которые его выводят без экранирования, будут выполнять JavaScript `alert('Hello!');`, и в результате +будет выводиться окно сообщения в браузере. В зависимости от сайта, вместо невинных скриптов с выводом всплывающего +hello, злоумышленниками могут быть отправлены скрипты, похищающие личные данные пользователей сайта, +либо выполняющие операции от их имени. + +В Yii избежать XSS легко. На месте вывода текста необходимо выбрать один из двух вариантов: + +1. Вы хотите вывести данные в виде обычного текста. +2. Вы хотите вывести данные в виде HTML. + +Если вам нужно вывести простой текст, то экранировать лучше следующим образом: + +```php +<?= \yii\helpers\Html::encode($username) ?> +``` + +Если нужно вывести HTML, вам лучше воспользоваться HtmlPurifier: + +```php +<?= \yii\helpers\HtmlPurifier::process($description) ?> +``` + +Обратите внимание, что обработка с помощью HtmlPurifier довольно тяжела, так что неплохо бы задуматься о кешировании. + +Как избежать CSRF +----------------- + +CSRF - это аббревиатура для межсайтинговой подмены запросов. Идея заключается в том, что многие приложения предполагают, +что запросы, приходящие от браузера, отправляются самим пользователем. Это может быть неправдой. + +Например, сайт `an.example.com` имеет URL `/logout`, который, используя простой GET, разлогинивает пользователя. Пока +это запрос выполняется самим пользователем - всё в порядке, но в один прекрасный день злоумышленники размещают код +`<img src="http://an.example.com/logout">` на форуме с большой посещаемостью. Браузер не делает никаких отличий +между запросом изображения и запросом страницы, так что когда пользователь откроет страницу с таким тегом `img`, браузер отправит GET запрос на указанный адрес, и пользователь будет разлогинен. + +Вот основная идея. Можно сказать, что в разлогировании пользователя нет ничего серьёзного, но с помощью этой уязвимости, можно выполнять опасные операции. Представьте, что существует страница http://an.example.com/purse/transfer?to=anotherUser&amout=2000, обращение к которой с помощью GET запроса, приводит к перечислению 2000 единиц валюты со счета авторизированного пользователя на счет пользователя с логином anotherUser. Учитывая, что браузер для загрузки контента отправляет GET запросы, можно подумать, что разрешение на выполнение такой операции только POST запросом на 100% обезопасит от проблем. К сожалению, это не совсем правда. Учитывайте, что вместо тега <img>, злоумышленник может внедрить JavaScript код, который будет отправлять нужные POST запросы на этот URL. + +Для того, чтоб избежать CSRF вы должны всегда: + +1. Следуйте спецификациям HTTP, например запрос GET не должен менять состояние приложения. +2. Держите защиту CSRF в Yii включенной. + + +Как избежать нежелательного доступа к файлам +-------------------------------------------- + +По умолчанию, webroot сервера указывает на каталог `web`, где лежит `index.php`. В случае использования виртуального +хостинга, это может быть недостижимо, в конечном итоге весь код, конфиги и логи могут оказаться в webroot сервера. + +Если это так, то нужно запретить доступ ко всему, кроме директории `web`. Если на вашем хостинге такое невозможно, +рассмотрите возможность смены хостинга. + +Как избежать отладочной информации и утилит в продуктиве +-------------------------------------------------------- + +В режиме отладки, Yii отображает довольно подробные ошибки, которые полезны во время разработки. Дело в том, что +подробные ошибки удобны для нападающего, так как могут раскрыть структуру базы данных, параметров конфигурации и части +вашего кода. Никогда не запускайте продуктивное приложение с `YII_DEBUG` установленным в `true` в вашем `index.php`. + +Вы никогда не должны включать Gii на продуктиве. Он может быть использован для получения информации о структуре +базы данных, кода и может позволить заменить файлы, генерируемые Gii автоматически. + +Также следует избегать включения на продуктиве панели отладки, если только в этом нет острой необходимости. +Она раскрывает всё приложение и детали конфигурации. Если вам все таки нужно запустить панель отладки на продуктиве, +проверьте дважды что доступ ограничен только вашими IP-адресами. diff --git a/docs/guide-ru/security-passwords.md b/docs/guide-ru/security-passwords.md new file mode 100644 index 0000000000..94b2a4bf7e --- /dev/null +++ b/docs/guide-ru/security-passwords.md @@ -0,0 +1,152 @@ +Работа с паролями +================= + +> Note: этот раздел находится на стадии разработки. + +Хорошая безопасность является жизненно важной для жизни и успеха любого приложения. +К сожалению, многие разработчики предпочитают упрощать, когда речь заходит о безопасности, +либо из-за отсутствия понимания, либо потому, что считают реализацию слишком сложной. +Для того, чтобы сделанное вами на Yii приложение было как можно более безопасным, в Yii есть несколько удобных +и простых в использовании функций обеспечения безопасности. + + +Хеширование и проверка пароля +----------------------------- + +Многие разработчики знают, что хранить пароль открытым текстом нельзя, но многие до сих пор считают безопасным +использование для хеширования паролей `md5` или `sha1`. Раньше упомянутых алгоритмов было достаточно, но современное +оборудование позволяет подобрать эти хеши очень быстро, методом простого перебора. + +Для того, чтобы обеспечить повышенную безопасность паролей ваших пользователей даже в худшем случае (ваше +приложение взломано), нужно использовать алгоритм шифрования, устойчивый к атаке перебором. Лучший вариант в текущий +момент `bcrypt`. В PHP вы можете использовать хеши `bcrypt` через [функцию crypt](http://php.net/manual/en/function.crypt.php). +Yii обеспечивает две вспомогательные функции, которые упрощают использование функции `crypt` для генерации и проверки +пароля. + +Когда пользователь задаёт пароль (например во время регистрации), пароль должен быть захеширован: + + +```php +$hash = Yii::$app->getSecurity()->generatePasswordHash($password); +``` + +Хеш можно связать с соответствующим атрибутом модели, так чтобы он сохранялся в базе для последующего использования. + +Когда пользователь попытается войти, отправленный пароль должен быть хеширован и сравнён с ранее сохранённым хешем: + +```php +if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { + // всё хорошо, пользователь может войти +} else { + // неправильный пароль +} +``` + +Генерация псевдослучайных данных +-------------------------------- + +Псевдослучайные данные полезны во многих ситуациях. Например, для сброса пароля с помощью электронной почты +вам необходимо сгенерировать специальный токен, сохранить его в БД, и отправить по почте конечному пользователю, +который в свою очередь подтвердит им свою личность. Очень важно, чтобы этот маркер был уникальным и сложно +подделываемым и злоумышленник не мог предсказать токен и сбросить пароль пользователя. + +Помощник безопасности Yii делает генерацию псевдослучайных данных простой: + +```php +$key = Yii::$app->getSecurity()->generateRandomString(); +``` + +Обратите внимание, что у вас должно быть установлено расширение `openssl` для генерации криптографически безопасных данных. + +Шифрование и Расшифровка +------------------------ + +Yii предоставляет удобные вспомогательные функции, которые позволяют шифровать/дешифровать данные, используя секретный ключ. +Данные, переданные в функцию шифрования, могут быть расшифрованы только человеком, имеющим секретный ключ. Например, нам +нужно хранить некоторую информацию в базе данных, но мы должны быть уверены, что только пользователь, который имеет +секретный ключ, сможет посмотреть их (даже если база данных будет скомпрометирована): + +```php +// $data и $secretKey передаются из формы +$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); +// сохраняем $encryptedData в базу данных +``` + +Позднее, когда пользователь захочет прочитать данные: + +```php +// $secretKey получается из формы, $encryptedData получается из базы данных +$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); +``` + +Подтверждение целостности данных +-------------------------------- + +Есть ситуации, в которых вам нужно убедиться, что ваши данные не были подделаны третьей стороной, или как-то повреждены. +Yii обеспечивает простой способ подтверждения целостности данных в виде двух вспомогательных функций. + +Префикс данных генерируются из секретного ключа и данных + +```php +// $secretKey получается от приложения или от пользователя, $genuineData получаются из надёжного источника +$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); +``` + +Проверка целостности данных + +```php +// $secretKey получается от приложения или от пользователя, $data данные полученные из ненадёжного источника +$data = Yii::$app->getSecurity()->validateData($data, $secretKey); +``` + + +todo: предотвращение XSS, CSRF, защита cookie, смотрите руководство 1.1 + +Вы также можете отключить проверку CSRF для контроллера и/или действия, через настройку его свойства: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionIndex() + { + // CSRF валидация не будет проводится для этого и других действий + } + +} +``` + +Чтобы отключить проверку CSRF в отдельном действии: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function beforeAction($action) + { + // ...установите здесь `$this->enableCsrfValidation` в зависимости от каких-то условий... + // вызываем родительский метод для проверки CSRF если свойство установлено в `true` + return parent::beforeAction($action); + } +} +``` + +Безопасные Cookies +------------------ + +- валидация +- httpOnly по умолчанию + +Смотрите также +-------------- + +- [Безопасность представлений](structure-views.md#security) + diff --git a/docs/guide-ru/start-databases.md b/docs/guide-ru/start-databases.md index 7a465c2501..0f10f45039 100644 --- a/docs/guide-ru/start-databases.md +++ b/docs/guide-ru/start-databases.md @@ -28,16 +28,16 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `country` VALUES ('AU','Australia',18886000); -INSERT INTO `country` VALUES ('BR','Brazil',170115000); -INSERT INTO `country` VALUES ('CA','Canada',1147000); -INSERT INTO `country` VALUES ('CN','China',1277558000); -INSERT INTO `country` VALUES ('DE','Germany',82164700); -INSERT INTO `country` VALUES ('FR','France',59225700); -INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `country` VALUES ('IN','India',1013662000); -INSERT INTO `country` VALUES ('RU','Russia',146934000); -INSERT INTO `country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` На данный момент у вас есть база данных под названием `yii2basic`, и внутри неё таблица `country` с тремя столбцами, содержащими десять строк данных. @@ -64,10 +64,18 @@ return [ Подключение к БД, настроенное выше, доступно в коде приложения через выражение `Yii::$app->db`. -> Информация: файл `config/db.php` будет подключен главной конфигурацией приложения `config/web.php`, +> Info: файл `config/db.php` будет подключен главной конфигурацией приложения `config/web.php`, описывающей то, как экземпляр [приложения](structure-applications.md) должен быть инициализирован. Для детальной информации, пожалуйста, обратитесь к разделу [Конфигурации](concept-configurations.md). +Если вам необходимо работать с базами данных, поддержка которых не включена непосредственно в фреймворк, стоит обратить +внимание на следующие расширения: + +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) + + Создаём потомка Active Record <span id="creating-active-record"></span> ------------------------- @@ -87,7 +95,7 @@ class Country extends ActiveRecord Класс `Country` наследуется от [[yii\db\ActiveRecord]]. Вам не нужно писать ни строчки кода внутри него! С кодом, приведённым выше, Yii свяжет имя таблицы с именем класса. -> Информация: Если нет возможности задать прямой зависимости между именем таблицы и именем класса, вы можете переопределить +> Info: Если нет возможности задать прямой зависимости между именем таблицы и именем класса, вы можете переопределить метод [[yii\db\ActiveRecord::tableName()]], чтобы явно задать имя связанной таблицы. Используя класс `Country`, вы можете легко манипулировать данными в таблице `country`, как показано в этих фрагментах: @@ -109,7 +117,7 @@ $country->name = 'U.S.A.'; $country->save(); ``` -> Информация: Active Record - мощный способ доступа и манипулирования данными БД в объектно-ориентированном стиле. +> Info: Active Record - мощный способ доступа и манипулирования данными БД в объектно-ориентированном стиле. Вы можете найти подробную информацию в разделе [Active Record](db-active-record.md). В качестве альтернативы, вы также можете взаимодействовать с базой данных, используя более низкоуровневый способ доступа, называемый [Data Access Objects](db-dao.md). @@ -210,9 +218,9 @@ http://hostname/index.php?r=country/index&page=2 * Виджет [[yii\widgets\LinkPager|LinkPager]] выводит кнопки страниц используя URL'ы, созданные [[yii\data\Pagination::createUrl()|Pagination]]. Эти URL'ы будут содержать параметр запроса `page`, который представляет различные номера страниц. * Если вы кликните по кнопке "2", сработает и обработается новый запрос для маршрута `country/index`. Таким образом новый запрос стран будет иметь параметры `LIMIT 5 OFFSET 5` и вернет следующие пять стран для отображения. -Резюме <span id="summary"></span> +Заключение <span id="summary"></span> ------- В этом разделе вы научились работать с базой данных. Также вы научились получать и отображать данные с постраничной разбивкой с помощью [[yii\data\Pagination]] и [[yii\widgets\LinkPager]]. -В следующем разделе вы научитесь использовать мощный инструмент генерации кода, называемый [Gii](tool-gii.md), чтобы с его помощью быстро осуществлять некоторые частоиспользуемые функции, такие, как операции Create-Read-Update-Delete (CRUD) для работы с данными в таблице базы данных. На самом деле код, который вы только что написали, в Yii может быть полностью сгенерирован автоматически с использованием Gii. +В следующем разделе вы научитесь использовать мощный инструмент генерации кода, называемый [Gii](tool-gii.md), чтобы с его помощью быстро осуществлять некоторые часто используемые функции, такие, как операции Create-Read-Update-Delete (CRUD) для работы с данными в таблице базы данных. На самом деле код, который вы только что написали, в Yii может быть полностью сгенерирован автоматически с использованием Gii. diff --git a/docs/guide-ru/start-forms.md b/docs/guide-ru/start-forms.md index 7c549f12ae..6bddbd5de5 100644 --- a/docs/guide-ru/start-forms.md +++ b/docs/guide-ru/start-forms.md @@ -78,11 +78,15 @@ class SiteController extends Controller public function actionEntry() { +<<<<<<< HEAD <<<<<<< HEAD $model = new EntryForm; ======= $model = new EntryForm(); >>>>>>> yiichina/master +======= + $model = new EntryForm(); +>>>>>>> master if ($model->load(Yii::$app->request->post()) && $model->validate()) { // данные в $model удачно проверены @@ -106,7 +110,7 @@ class SiteController extends Controller В противном случае будет отображено представление `entry`, которое содержит HTML форму и ошибки проверки данных, если они есть. -> Информация: `Yii::$app` представляет собой глобально доступный экземпляр-одиночку +> Info: `Yii::$app` представляет собой глобально доступный экземпляр-одиночку [приложения](structure-applications.md) (singleton). Одновременно это [Service Locator](concept-service-locator.md), дающий доступ к компонентам вроде `request`, `response`, `db` и так далее. В коде выше для доступа к данным из `$_POST` был использован компонент `request`. @@ -199,11 +203,11 @@ http://hostname/index.php?r=site/entry <?= $form->field($model, 'email')->label('Ваш Email') ?> ``` -> Информация: В Yii есть множество виджетов, позволяющих быстро строить сложные и динамичные представления. +> Info: В Yii есть множество виджетов, позволяющих быстро строить сложные и динамичные представления. Как вы узнаете позже, разрабатывать новые виджеты очень просто. Многое из представлений можно вынести в виджеты, чтобы использовать это повторно в других местах и упростить тем самым разработку в будущем. -Резюме <span id="summary"></span> +Заключение <span id="summary"></span> ----------------------------- В данном разделе вы попробовали каждую часть шаблона проектирования MVC. Вы изучили как создавать классы моделей diff --git a/docs/guide-ru/start-gii.md b/docs/guide-ru/start-gii.md index 0311768150..7d9c6ad41a 100644 --- a/docs/guide-ru/start-gii.md +++ b/docs/guide-ru/start-gii.md @@ -15,7 +15,7 @@ Запускаем Gii <span id="starting-gii"></span> ------------ -[Gii](tool-gii.md) предствален в Yii как [модуль](structure-modules.md). Вы можете активировать Gii, +[Gii](tool-gii.md) представлен в Yii как [модуль](structure-modules.md). Вы можете активировать Gii, настроив его в свойстве [[yii\base\Application::modules|modules]]. В зависимости от того, каким образом вы создали приложение, вы можете удостовериться в наличии следующего кода в конфигурационном файле `config/web.php`, ```php @@ -23,7 +23,9 @@ $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` @@ -42,7 +44,7 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); ``` http://hostname/index.php?r=gii ``` -> Замечание: Если вы пытаетесь получить доступ к Gii не с локального хоста, по умолчанию, в целях обеспечения безопасности, +> Note: Если вы пытаетесь получить доступ к Gii не с локального хоста, по умолчанию, в целях обеспечения безопасности, > доступ будет запрещён. Вы можете изменить настройки Gii, чтобы добавить разрешённые IP адреса, как указано ниже ```php @@ -117,7 +119,7 @@ http://hostname/index.php?r=country/index * Модели: `models/Country.php` и `models/CountrySearch.php` * Вид: `views/country/*.php` -> Информация: Gii разработан как тонконастраиваемый и расширяемый инструмент генерации кода. Используя его с умом, вы можете значительно ускорить скорость разработки приложений. Для более подробной информации, пожалуйста, обратитесь к разделу [Gii](tool-gii.md). +> Info: Gii разработан как тонконастраиваемый и расширяемый инструмент генерации кода. Используя его с умом, вы можете значительно ускорить скорость разработки приложений. Для более подробной информации, пожалуйста, обратитесь к разделу [Gii](tool-gii.md). Заключение <span id="summary"></span> diff --git a/docs/guide-ru/start-hello.md b/docs/guide-ru/start-hello.md index 91f6d70554..6f699f7088 100644 --- a/docs/guide-ru/start-hello.md +++ b/docs/guide-ru/start-hello.md @@ -21,7 +21,7 @@ запроса и отображает его значение пользователю. Если в запросе не содержится параметра `message`, то действие будет выводить «Привет». -> Информация: [Действия](structure-controllers.md) могут быть запущены непосредственно пользователем и сгруппированы в +> Info: [Действия](structure-controllers.md) могут быть запущены непосредственно пользователем и сгруппированы в [контроллеры](structure-controllers.md). Результатом выполнения действия является ответ, который получает пользователь. Действия объявляются в [контроллерах](structure-controllers.md). Для простоты, вы можете объявить действие @@ -49,7 +49,7 @@ class SiteController extends Controller Yii использует префикс `action` чтобы различать методы-действия и обычные методы. Название после префикса `action` считается идентификатором соответствующего действия. -> Информация: Идентификаторы действий задаются в нижнем регистре. Если идентификатор состоит из нескольких слов, они +> Info: Идентификаторы действий задаются в нижнем регистре. Если идентификатор состоит из нескольких слов, они соединяются дефисами, то есть `create-comment`. Имена методов действий получаются путём удаления дефисов из идентификатора, преобразования первой буквы каждого слова в верхний регистр и добавления префикса `action`. Например, идентификатор действия `create-comment` соответствует методу `actionCreateComment`. @@ -103,7 +103,7 @@ http://hostname/index.php?r=site/say&message=Привет+мир Если вы не укажете параметр `message`, то увидите на странице «Привет». Это происходит потому, как `message` передаётся в метод `actionSay()` и значение по умолчанию — «Привет». -> Информация: Новая страница использует ту же шапку и футер, что и другие страницы, потому что метод +> Info: Новая страница использует ту же шапку и футер, что и другие страницы, потому что метод [[yii\web\Controller::render()|render()]] автоматически вставляет результат представления `say` в, так называемый, [макет](structure-views.md) `views/layouts/main.php`. @@ -114,14 +114,14 @@ http://hostname/index.php?r=site/say&message=Привет+мир В нашем случае маршрут `site/say` будет соответствовать контроллеру `SiteController` и его действию `say`. В результате, для обработки запроса будет вызван метод `SiteController::actionSay()`. -> Информация: Как и действия, контроллеры также имеют идентификаторы, которые однозначно определяют их в приложении. +> Info: Как и действия, контроллеры также имеют идентификаторы, которые однозначно определяют их в приложении. Идентификаторы контроллеров используют те же правила именования, что и идентификаторы действий. Имена классов контроллеров получаются путём удаления дефисов из идентификатора, преобразования первой буквы каждого слова в верхний регистр и добавления в конец `Controller`. Например, идентификатор контроллера `post-comment` соответствует имени класса контроллера `PostCommentController`. -Резюме <span id="summary"></span> +Заключение <span id="summary"></span> ----------------------------- В этом разделе вы затронули тему контроллеров и представлений в паттерне MVC. Вы создали действие как часть контроллера, diff --git a/docs/guide-ru/start-installation.md b/docs/guide-ru/start-installation.md index 50f563386c..e7d49e27d1 100644 --- a/docs/guide-ru/start-installation.md +++ b/docs/guide-ru/start-installation.md @@ -1,15 +1,19 @@ Установка Yii ============== +<<<<<<< HEAD <<<<<<< HEAD Вы можете установить Yii двумя способами: используя [Composer](http://getcomposer.org/) или скачав архив. ======= Вы можете установить Yii двумя способами: используя [Composer](https://getcomposer.org/) или скачав архив. >>>>>>> yiichina/master +======= +Вы можете установить Yii двумя способами: используя [Composer](https://getcomposer.org/) или скачав архив. +>>>>>>> master Первый способ предпочтительнее так как позволяет установить новые [расширения](structure-extensions.md) или обновить Yii одной командой. -> Примечание: В отличие от Yii 1, после стандартной установки Yii 2 мы получаем как фреймворк, так и шаблон приложения. +> Note: В отличие от Yii 1, после стандартной установки Yii 2 мы получаем как фреймворк, так и шаблон приложения. Установка при помощи Composer <span id="installing-via-composer"></span> @@ -19,6 +23,7 @@ [getcomposer.org](https://getcomposer.org/download/), или одним из нижеперечисленных способов. На Linux или Mac используйте следующую команду: +<<<<<<< HEAD ``` <<<<<<< HEAD curl -s http://getcomposer.org/installer | php @@ -27,6 +32,12 @@ >>>>>>> yiichina/master mv composer.phar /usr/local/bin/composer ``` +======= +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` +>>>>>>> master На Windows, скачайте и запустите [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). @@ -37,12 +48,19 @@ После установки Composer устанавливать Yii можно запустив следующую команду в папке доступной через веб: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master composer create-project --prefer-dist yiisoft/yii2-app-basic basic +======= +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` +>>>>>>> master Первая команда устанавливает [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), который позволяет управлять зависимостями пакетов bower и npm через Composer. Эту команду достаточно выполнить один раз. @@ -50,16 +68,18 @@ Composer установит Yii (шаблонное приложение basic) в папку `basic`. -> Примечание: В процессе установки Composer может запросить логин и пароль от Github потому как у API Github имеется +> Note: В процессе установки Composer может запросить логин и пароль от Github потому как у API Github имеется > ограничение на количество запросов. Это нормально потому как Composer в процессе работы запрашивает у Github большое > количество информации для каждого пакета. Вход на Github повышает ограничение по запросам API и Composer может > продолжить свою работу. Подробнее об этом можно прочитать в > [документации Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). -> Подсказка: Если вы хотите установить последнюю нестабильную ревизию Yii, можете использовать следующую команду, +> Tip: Если вы хотите установить последнюю нестабильную ревизию Yii, можете использовать следующую команду, > в которой присутствует [опция stability](https://getcomposer.org/doc/04-schema.md#minimum-stability): > -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` > > Старайтесь не использовать нестабильную версию Yii на рабочих серверах потому как она может внезапно поломать код. @@ -74,10 +94,10 @@ Composer установит Yii (шаблонное приложение basic) 3. В файле `config/web.php` добавьте секретный ключ в значение `cookieValidationKey` (при установке через Composer это происходит автоматически): - ```php - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => 'enter your secret key here', - ``` +```php +// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation +'cookieValidationKey' => 'enter your secret key here', +``` Другие опции установки <span id="other-installation-options"></span> -------------------------- @@ -90,7 +110,7 @@ Composer установит Yii (шаблонное приложение basic) * Если вам нужен только сам фреймворк и вы хотели бы создать приложение с нуля, воспользуйтесь инструкцией, описанной в разделе «[Создание приложения с нуля](tutorial-start-from-scratch.md)». * Если хотите начать с более продвинутого приложения, хорошо подходящего для работы в команде, используйте -[шаблон приложения advanced](tutorial-advanced-app.md). +[шаблон приложения advanced](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). Проверка установки <span id="verifying-installation"></span> @@ -112,10 +132,10 @@ http://localhost/basic/web/index.php * Браузером перейдите по адресу `http://localhost/basic/requirements.php` * Или выполните команду в консоли: - ``` - cd basic - php requirements.php - ``` +```bash +cd basic +php requirements.php +``` Для корректной работы фреймворка вам необходима установка PHP, соответствующая его минимальным требованиям. Основное требование — PHP версии 5.4 и выше. Если ваше приложение работает с базой данных, необходимо установить @@ -126,7 +146,7 @@ http://localhost/basic/web/index.php Настройка веб сервера <span id="configuring-web-servers"></span> ----------------------- -> Информация: можете пропустить этот подраздел если вы только начали знакомиться с фреймворком и пока не разворачиваете +> Info: можете пропустить этот подраздел если вы только начали знакомиться с фреймворком и пока не разворачиваете его на рабочем сервере. Приложение, установленное по инструкциям, приведённым выше, будет работать сразу как с [Apache](http://httpd.apache.org/), @@ -137,12 +157,12 @@ http://localhost/basic/web/index.php На рабочем сервере вам наверняка захочется изменить URL приложения с `http://www.example.com/basic/web/index.php` на `http://www.example.com/index.php`. Для этого необходимо изменить корневую директорию в настройках веб сервера так, чтобы та указывала на `basic/web`. Дополнительно можно спрятать `index.php` следуя описанию в разделе -«[Разбор и генерация URL](runtime-url-handling.md)». Далее будет показано как настроить Apache и Nginx. +«[Разбор и генерация URL](runtime-routing.md)». Далее будет показано как настроить Apache и Nginx. -> Информация: Устанавливая `basic/web` корневой директорией веб сервера вы защищаете от нежелательного доступа код и данные, +> Info: Устанавливая `basic/web` корневой директорией веб сервера вы защищаете от нежелательного доступа код и данные, находящиеся на одном уровне с `basic/web`. Это делает приложение более защищенным. -> Информация: Если приложение работает на хостинге где нет доступа к настройкам веб сервера, то можно изменить структуру +> Info: Если приложение работает на хостинге где нет доступа к настройкам веб сервера, то можно изменить структуру приложения как описано в разделе «[Работа на Shared хостинге](tutorial-shared-hosting.md)». @@ -187,11 +207,15 @@ server { root /path/to/basic/web; index index.php; +<<<<<<< HEAD <<<<<<< HEAD access_log /path/to/project/log/access.log main; ======= access_log /path/to/project/log/access.log; >>>>>>> yiichina/master +======= + access_log /path/to/project/log/access.log; +>>>>>>> master error_log /path/to/project/log/error.log; location / { diff --git a/docs/guide-ru/start-looking-ahead.md b/docs/guide-ru/start-looking-ahead.md index bd3d7a8bdb..d782edd386 100644 --- a/docs/guide-ru/start-looking-ahead.md +++ b/docs/guide-ru/start-looking-ahead.md @@ -4,10 +4,9 @@ В итоге вы создали полноценное приложение на Yii и узнали, как реализовать некоторые наиболее часто используемые функции, такие, как получение данных от пользователя при помощи HTML форм, выборки данных из базы данных и их отображения в разбитом на страницы виде. -Так же вы узнали, как использовать [Gii](tool-gii.md) для автоматической генерации кода, что превращает программирование в настолько простую задачу, -как простое заполнение какой-либо формы. -В этом разделе мы обобщим ресурсы о Yii, -которые помогут вам быть более продуктивным при использовании Yii. +Так же вы узнали, как использовать [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) для +автоматической генерации кода, что превращает программирование в настолько простую задачу, как простое заполнение +какой-либо формы. В этом разделе мы обобщим ресурсы о Yii, которые помогут вам быть более продуктивным при использовании Yii. * Документация - Подробное руководство: @@ -28,6 +27,7 @@ которые могут быть легко подключены в ваши приложения и сделать разработку приложений ещё быстрее и проще. * Сообщество - [Форум](http://www.yiiframework.com/forum/) + - [Чат Gitter](https://gitter.im/yiisoft/yii2/rus) - [GitHub](https://github.com/yiisoft/yii2) - [Facebook](https://www.facebook.com/groups/yiitalk/) - [Twitter](https://twitter.com/yiiframework) diff --git a/docs/guide-ru/start-workflow.md b/docs/guide-ru/start-workflow.md index f419461d4b..eacb590f3f 100644 --- a/docs/guide-ru/start-workflow.md +++ b/docs/guide-ru/start-workflow.md @@ -3,7 +3,10 @@ После установки Yii базовое приложение будет доступно либо по URL `http://hostname/basic/web/index.php`, либо по `http://hostname/index.php`, в зависимости от настроек Web сервера. Данный раздел - общее введение в организацию кода, встроенный функционал и обработку обращений приложением Yii. -> Информация: далее в данном руководстве предполагается, что Yii установлен в директорию `basic/web`, которая, в свою очередь, установлена как корневой каталог в настройках Web сервера. В результате, обратившись по URL `http://hostname/index.php`, Вы получите доступ к приложению, расположенному в `basic/web`. Детальнее с процессом начальной настройки можно познакомиться в разделе [Установка Yii](start-installation.md). +> Info: далее в данном руководстве предполагается, что Yii установлен в директорию `basic/web`, которая, в свою очередь, установлена как корневой каталог в настройках Web сервера. В результате, обратившись по URL `http://hostname/index.php`, Вы получите доступ к приложению, расположенному в `basic/web`. Детальнее с процессом начальной настройки можно познакомиться в разделе [Установка Yii](start-installation.md). + +Отметим, что в отличие от фреймворка как только приложение установлено, оно становится целиком вашим. Вы можете изменять +его код как угодно. Функциональность <span id="functionality"></span> --------------- @@ -13,18 +16,29 @@ * домашняя страница, отображается при переходе по URL `http://hostname/index.php` * страница "About" ("О нас") * на странице "Contact" находится форма обратной связи, на которой пользователь может обратиться к разработчику по e-mail -* на странице "Login" отображается форма авторизации. Попытайтесь авторизоваться с логином/паролем "admin/admin". Обратите внимание на изменение раздела "Login" в главном меню на "Logout". +* на странице "Login" отображается форма авторизации. Попытайтесь авторизоваться с логином/паролем "admin/admin". + Обратите внимание на изменение раздела "Login" в главном меню на "Logout". -Эти страницы используют смежный хедер (шапка сайта) и футер (подвал). В "шапке" находится главное меню, при помощи которого пользователь перемещается по сайту. В "подвале" - копирайт и общая информация. +Эти страницы используют смежный хедер (шапка сайта) и футер (подвал). В "шапке" находится главное меню, при помощи +которого пользователь перемещается по сайту. В "подвале" - копирайт и общая информация. -В самой нижней части окна Вы будете видеть системные сообщения Yii - журнал, отладочную информацию, сообщения об ошибках, запросы к базе данных и т.п. Выводом данной информации руководит [встроенный отладчик](tool-debugger.md), он записывает и отображает информацию о ходе выполнения приложения. +В самой нижней части окна Вы будете видеть системные сообщения Yii - журнал, отладочную информацию, сообщения об ошибках, +запросы к базе данных и т.п. Выводом данной информации руководит +[встроенный отладчик](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md), он записывает и отображает +информацию о ходе выполнения приложения. +<<<<<<< HEAD <<<<<<< HEAD ======= В дополнение к веб приложению имеется консольный скрипт с названием `yii`, который находится в базовой директории приложения. Этот скрипт может быть использован для выполнения фоновых задач и обслуживания приложения. Всё это описано в разделе [Консольные команды](tutorial-console.md). >>>>>>> yiichina/master +======= +В дополнение к веб приложению имеется консольный скрипт с названием `yii`, который находится в базовой директории приложения. +Этот скрипт может быть использован для выполнения фоновых задач и обслуживания приложения. Всё это описано в разделе +[Консольные команды](tutorial-console.md). +>>>>>>> master Структура приложения Yii <span id="application-structure"></span> --------------------- @@ -51,7 +65,7 @@ basic/ корневой каталог приложения В целом, приложение Yii можно разделить на две категории файлов: расположенные в `basic/web` и расположенные в других директориях. Первая категория доступна через Web (например, браузером), вторая не доступна из вне и не должна быть доступной т.к. содержит служебную информацию. -В Yii реализована схема проектирования [модель-вид-контроллер (MVC)](http://ru.wikipedia.org/wiki/Model-View-Controller), +В Yii реализован [архитектурный паттерн MVC](http://ru.wikipedia.org/wiki/Model-View-Controller), которая соответствует структуре директорий приложения. В директории `models` находятся [Модели](structure-models.md), в `views` расположены [Виды](structure-views.md), а в каталоге `controllers` все [Контроллеры](structure-controllers.md) приложения. diff --git a/docs/guide-ru/structure-application-components.md b/docs/guide-ru/structure-application-components.md index abd140812d..01cdbba928 100755 --- a/docs/guide-ru/structure-application-components.md +++ b/docs/guide-ru/structure-application-components.md @@ -1,21 +1,23 @@ Компоненты приложения ===================== -Приложения являются [сервис локаторами](concept-service-locators.md). Они хранят множество так называемых +Приложения являются [сервис локаторами](concept-service-locator.md). Они хранят множество так называемых *компонентов приложения*, которые предоставляют различные средства для обработки запросов. Например, -компоненты `urlManager` ответственен за маршрутизацию веб запросов к нужному контроллеру; компонент `db` предоставляет +компонент `urlManager` ответственен за маршрутизацию веб запросов к нужному контроллеру; компонент `db` предоставляет средства для работы с базой данных; и т. д. Каждый компонент приложения имеет свой уникальный ID, который позволяет идентифицировать его среди других различных компонентов в одном и том же приложении. Вы можете получить доступ к компоненту следующим образом: ```php -\Yii::$app->ComponentID +\Yii::$app->componentID ``` Например, вы можете использовать `\Yii::$app->db` для получения [[yii\db\Connection|соединения с БД]], и `\Yii::$app->cache` для получения доступа к основному компоненту [[yii\caching\Cache|кэша]], зарегистрированному в приложении. +Компонент приложения будет создан при первом обращении к нему через вышеуказанное выражение. Любые дальнейшие обращения будут возвращать тот же экземпляр компонента. + Компонентами приложения могут быть любые объекты. Вы можете зарегистрировать их с помощью свойства [[yii\base\Application::components]] в [конфигурации](structure-applications.md#application-configurations) приложения. Например, @@ -42,11 +44,29 @@ ] ``` -> Информация: Хотя вы можете зарегистрировать столько компонентов в приложении сколько вам нужно, +> Info: Хотя вы можете зарегистрировать столько компонентов в приложении сколько вам нужно, все таки стоит это делать разумно. Компоненты приложения похожи на глобальные переменные. Использование слишком большого количества компонентов приложения может потенциально сделать ваш код сложным для разработки и тестирования. В большинстве случаев вы можете просто создать локальный компонент и использовать его при необходимости. +## Компоненты начальной загрузки <span id="bootstrapping-components"></span> + +Как упоминалось выше, компонент приложения будет создан только при первом обращении к нему. Однако может возникнуть необходимость в наличии созданного компонента при каждом запросе, даже если напрямую к нему ни разу не обращались. Для этого необходимо указать ID компонента в качестве элемента свойства [[yii\base\Application::bootstrap|bootstrap]]. + +К примеру, при данной конфигурации компонент `log` всегда подгружается при загрузке: + +```php +[ + 'bootstrap' => [ + 'log', + ], + 'components' => [ + 'log' => [ + // конфигурация для компонента `log` + ], + ], +] +``` ## Встроенные компоненты приложения <span id="core-application-components"></span> @@ -60,20 +80,24 @@ Когда вы конфигурируете встроенный компонент приложения и не указываете класс этого компонента, то значение по умолчанию будет использовано. * [[yii\web\AssetManager|assetManager]]: используется для управления и опубликования ресурсов приложения. - Более детальная информация представлена в разделе [Ресурсы](output-assets.md); + Более детальная информация представлена в разделе [Ресурсы](structure-assets.md); * [[yii\db\Connection|db]]: представляет собой соединение с базой данных, через которое вы можете выполнять запросы. Обратите внимание, что когда вы конфигурируете данный компонент, вы должны указать класс компонента также как и остальные необходимые параметры, такие как [[yii\db\Connection::dsn]]. Более детальная информация представлена в разделе [Объекты доступа к данным (DAO)](db-dao.md); * [[yii\base\Application::errorHandler|errorHandler]]: осуществляет обработку PHP ошибок и исключений. Более детальная информация представлена в разделе [Обработка ошибок](runtime-handling-errors.md); -* [[yii\base\Formatter|formatter]]: форматирует данные для отображения их конечному пользователю. Например, число может +* [[yii\i18n\Formatter|formatter]]: форматирует данные для отображения их конечному пользователю. Например, число может быть отображено с различными разделителями, дата может быть отображена в формате `long`. +<<<<<<< HEAD <<<<<<< HEAD Более детальная информация представлена в разделе [Форматирование данных](output-formatter.md); ======= Более детальная информация представлена в разделе [Форматирование данных](output-formatting.md); >>>>>>> yiichina/master +======= + Более детальная информация представлена в разделе [Форматирование данных](output-formatting.md); +>>>>>>> master * [[yii\i18n\I18N|i18n]]: используется для перевода сообщений и форматирования. Более детальная информация представлена в разделе [Интернационализация](tutorial-i18n.md); * [[yii\log\Dispatcher|log]]: обработка и маршрутизация логов. @@ -87,7 +111,7 @@ * [[yii\web\Session|session]]: информация о сессии. Данный компонент доступен только в [[yii\web\Application|веб приложениях]]. Более детальная информация представлена в разделе [Сессии и куки](runtime-sessions-cookies.md); * [[yii\web\UrlManager|urlManager]]: используется для разбора и создания URL. - Более детальная информация представлена в разделе [Разбор и генерация URL](runtime-url-handling.md); + Более детальная информация представлена в разделе [Разбор и генерация URL](runtime-routing.md); * [[yii\web\User|user]]: представляет собой информацию аутентифицированного пользователя. Данный компонент доступен только в [[yii\web\Application|веб приложениях]]. Более детальная информация представлена в разделе [Аутентификация](security-authentication.md); diff --git a/docs/guide-ru/structure-applications.md b/docs/guide-ru/structure-applications.md index 8d5eb6ec64..97f03f0757 100644 --- a/docs/guide-ru/structure-applications.md +++ b/docs/guide-ru/structure-applications.md @@ -5,7 +5,7 @@ Каждая прикладная система Yii включает в себя один объект приложения, который создается во [входном скрипте](structure-entry-scripts.md) и глобально доступен через `\Yii::$app`. -> Информация: В зависимости от контекста, когда мы говорим "приложение", это может означать как объект приложения так и +> Info: В зависимости от контекста, когда мы говорим "приложение", это может означать как объект приложения так и приложение как прикладную систему в целом. Существует два вида приложений: [[yii\web\Application|веб приложения]] и [[yii\console\Application|консольные приложения]]. @@ -66,7 +66,7 @@ $config = require(__DIR__ . '/../config/web.php'); Свойство [[yii\base\Application::basePath|basePath]] часто используется для указания других важных путей (например, путь к директории runtime, используемой приложением). По этой причине, псевдоним пути `@app` предустановлен и содержит данный путь. Производные пути могут быть получены с использованием этого псевдонима пути (например, `@app/runtime` указывает на -времененную дирректорию runtime). +времененную директорию runtime). ### Важные свойства <span id="important-properties"></span> @@ -97,7 +97,7 @@ $config = require(__DIR__ . '/../config/web.php'); Данное свойство является очень удобным, оно позволяет указать массив компонентов, которые должны быть загружены в процессе [[yii\base\Application::bootstrap()|начальной загрузки]] приложения. Например, если вы хотите, чтобы -[модуль](structure-modules.md) производил тонкую настройку [URL правил](runtime-url-handling.md), вы можете указать его +[модуль](structure-modules.md) производил тонкую настройку [URL правил](runtime-routing.md), вы можете указать его ID в качестве элемента данного свойства. Каждый из элементов данного свойства, может быть указан в одном из следующих форматов: @@ -160,7 +160,7 @@ if (YII_ENV_DEV) { } ``` -> Примечание: Указывание слишком большого количества компонентов в [`bootstrap`](runtime-bootstrapping.md) приведет +> Note: Указывание слишком большого количества компонентов в [`bootstrap`](runtime-bootstrapping.md) приведет к снижению производительности приложения, потому что для каждого запроса одно и то же количество компонентов должно быть загружено. Таким образом вы должны использовать начальную загрузку разумно. @@ -211,7 +211,7 @@ if (YII_ENV_DEV) { в то время как значение представляет собой название класса или [конфигурацию](concept-configurations.md). Вы можете зарегистрировать любой компонент в приложении, позже этот компонент будет глобально доступен через -выражение `\Yii::$app->ComponentID`. +выражение `\Yii::$app->componentID`. Более подробная информация приведена в разделе [Компоненты приложения](structure-application-components.md). @@ -250,7 +250,7 @@ if (YII_ENV_DEV) { контроллера (без пространства имен) будет равен `PostController`, а полное название класса будет равно `app\controllers\PostController`. Класс контроллера может также находиться в поддиректории директории, соответствующей этому пространству имен. -Например, ID контроллера `admin/post`, будет соответветствовать полное имя класса контроллера `app\controllers\admin\PostController`. +Например, ID контроллера `admin/post`, будет соответствовать полное имя класса контроллера `app\controllers\admin\PostController`. Очень важно, чтобы полное имя класса контроллера могло быть использовано [автозагрузкой](concept-autoloading.md) и соответствующее пространство имен вашего контроллера соответствовало данному свойству. Иначе, Вы получите ошибку @@ -387,6 +387,7 @@ $width = \Yii::$app->params['thumbnail.size'][0]; `post/create`, `admin/post/create`. Если действие не указано, то будет использовано значение по умолчанию указанное в [[yii\base\Controller::defaultAction]]. +<<<<<<< HEAD <<<<<<< HEAD Для [yii\web\Application|веб приложений], значение по умолчанию для данного свойства равно `'site'`, что означает контроллер `SiteController` и его действие по умолчанию должно быть использовано. Таким образом, если вы попытаетесь @@ -400,6 +401,13 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Для [[yii\console\Application|консольных приложений]], значение по умолчанию равно `'help'`, означающее, что встроенная >>>>>>> yiichina/master +======= +Для [[yii\web\Application|веб приложений]], значение по умолчанию для данного свойства равно `'site'`, что означает +контроллер `SiteController` и его действие по умолчанию должно быть использовано. Таким образом, если вы попытаетесь +получить доступ к приложению не указав маршрут, оно покажет вам результат действия `app\controllers\SiteController::actionIndex()`. + +Для [[yii\console\Application|консольных приложений]], значение по умолчанию равно `'help'`, означающее, что встроенная +>>>>>>> master команда [[yii\console\controllers\HelpController::actionIndex()]] должна быть использована. Таким образом, если вы выполните команду `yii` без аргументов, вам будет отображена справочная информация. @@ -409,10 +417,14 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Данное свойство указывает список [расширений](structure-extensions.md), которые установлены и используются приложением. По-умолчанию, значением данного свойства будет массив, полученный из файла `@vendor/yiisoft/extensions.php`. Файл `extensions.php` <<<<<<< HEAD +<<<<<<< HEAD генерируется и обрабатывается автоматически, когда вы используете [Composer](http://getcomposer.org) для установки расширений. ======= генерируется и обрабатывается автоматически, когда вы используете [Composer](https://getcomposer.org) для установки расширений. >>>>>>> yiichina/master +======= +генерируется и обрабатывается автоматически, когда вы используете [Composer](https://getcomposer.org) для установки расширений. +>>>>>>> master Таким образом, в большинстве случаев вам не нужно настраивать данное свойство. В особых случаях, когда вы хотите обрабатывать расширения в ручную, вы можете указать данное свойство следующим образом: @@ -481,11 +493,15 @@ $width = \Yii::$app->params['thumbnail.size'][0]; #### [[yii\base\Application::vendorPath|vendorPath]] <span id="vendorPath"></span> +<<<<<<< HEAD <<<<<<< HEAD Свойство указывает папку сторонних библиотек, которые используются и управляются [Composer](http://getcomposer.org). ======= Свойство указывает папку сторонних библиотек, которые используются и управляются [Composer](https://getcomposer.org). >>>>>>> yiichina/master +======= +Свойство указывает папку сторонних библиотек, которые используются и управляются [Composer](https://getcomposer.org). +>>>>>>> master Она содержит все сторонние библиотеки используемые приложением, включая Yii фреймворк. Значение по умолчанию представляет собой псевдоним `@app/vendor`. diff --git a/docs/guide-ru/structure-assets.md b/docs/guide-ru/structure-assets.md index 9c03cdf1cb..91e1b9ec21 100644 --- a/docs/guide-ru/structure-assets.md +++ b/docs/guide-ru/structure-assets.md @@ -3,7 +3,11 @@ Ресурс в Yii это файл который может быть задан в Web странице. Это может быть CSS файл, JavaScript файл, изображение или видео файл и т.д. Ресурсы располагаются в Web доступных директориях и обслуживаются непосредственно Web серверами. +<<<<<<< HEAD Желательно, управлять ресурсами программно. Например, при использовании виджета [[yii\jui\DatePicker]] в странице, автоматически включаются необходимые CSS и JavaScript файлы, вместо того чтобы просить Вас в ручную найти эти файлы и включить их. И когда Вы обновляете виджет до новой версии, будут автоматически использованны новые версии файлов-ресурсов. В этом руководстве будет описана мощная возможность управления ресурсами представленная в Yii. +======= +Желательно, управлять ресурсами программно. Например, при использовании виджета [[yii\jui\DatePicker]] в странице, автоматически включаются необходимые CSS и JavaScript файлы, вместо того чтобы просить Вас в ручную найти эти файлы и включить их. И когда Вы обновляете виджет до новой версии, будут автоматически использованы новые версии файлов-ресурсов. В этом руководстве будет описана мощная возможность управления ресурсами представленная в Yii. +>>>>>>> master ## Комплекты ресурсов <span id="asset-bundles"></span> @@ -44,8 +48,13 @@ class AppAsset extends AssetBundle * [[yii\web\AssetBundle::basePath|basePath]]: задаёт Web доступную директорию, которая содержит файлы ресурсов текущего комплекта. Когда Вы задаёте свойство [[yii\web\AssetBundle::sourcePath|sourcePath]] [Менеджер ресурсов](#asset-manager) опубликует ресурсы текущего комплекта в Web доступную директорию и перезапишет соответственно данное свойство. Вы должны задать данное свойство если Ваши файлы ресурсов уже в Web доступной директории и не нужно опубликовывать ресурсы. Здесь могут быть использованы [псевдонимы путей](concept-aliases.md). * [[yii\web\AssetBundle::baseUrl|baseUrl]]: задаёт URL соответствующий директории [[yii\web\AssetBundle::basePath|basePath]]. Также как и для [[yii\web\AssetBundle::basePath|basePath]], если Вы задаёте свойство [[yii\web\AssetBundle::sourcePath|sourcePath]] [Менеджер ресурсов](#asset-manager) опубликует ресурсы и перезапишет это свойство соответственно. Здесь могут быть использованы [псевдонимы путей](concept-aliases.md). * [[yii\web\AssetBundle::js|js]]: массив, перечисляющий JavaScript файлы, содержащиеся в данном комплекте. Заметьте, что только прямая косая черта (forward slash - "/") может быть использована, как разделитель директорий. Каждый JavaScript файл может быть задан в одном из следующих форматов: +<<<<<<< HEAD - относительный путь, представленный локальным JavaScript файлом (например `js/main.js`). Актуальный путь файла может быть определён путём добавления [[yii\web\AssetManager::basePath]] к относительному пути, и актуальный URL файла может быть определён путём добавления [[yii\web\AssetManager::baseUrl]] к относительному пути. - абсолютный URL, представленный внешним JavaScript файлом. Например, +======= + - относительный путь, представленный локальным JavaScript файлом (например `js/main.js`). Актуальный путь файла может быть определён путём добавления [[yii\web\AssetManager::basePath]] к относительному пути, и актуальный URL файла может быть определён путём добавления [[yii\web\AssetManager::baseUrl]] к относительному пути. + - абсолютный URL, представленный внешним JavaScript файлом. Например, +>>>>>>> master `http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js` или `//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js`. * [[yii\web\AssetBundle::css|css]]: массив, перечисляющий CSS файлы, содержащиеся в данном комплекте. Формат этого массива такой же, как и у [[yii\web\AssetBundle::js|js]]. @@ -58,7 +67,11 @@ class AppAsset extends AssetBundle Ресурсы, в зависимости от их расположения, могут быть классифицированы как: +<<<<<<< HEAD * исходные ресурсы: файлы ресурсов, расположенные вместе с исходным кодом PHP, которые не могут быть непосредственно доступны через Web. Для того, чтобы использовать исходные ресурсы на странице, они должны быть скопированы в Web директорию и превратиться в так называемые опубликованные ресурсы. Этот процесс называется *публикацией ресурсов*, который более подробно будет описан в ближайшее время. +======= +* исходные ресурсы: файлы ресурсов, расположенные вместе с исходным кодом PHP, которые не могут быть непосредственно доступны через Web. Для того, чтобы использовать исходные ресурсы на странице, они должны быть скопированы в Web директорию и превратиться в так называемые опубликованные ресурсы. Этот процесс называется *публикацией ресурсов*, который более подробно описан ниже +>>>>>>> master * опубликованные ресурсы: файлы ресурсов, расположенные в Web директории и, таким образом, могут быть напрямую доступны через Web. * внешние ресурсы: файлы ресурсов, расположенные на другом Web сервере, отличного от веб-хостинга вашего приложения. @@ -68,7 +81,11 @@ class AppAsset extends AssetBundle Для [расширений](structure-extensions.md), в связи с тем, что их ресурсы располагаются вместе с их исходным кодом в директориях, которые не являются веб-доступными, необходимо указать свойство [[yii\web\AssetBundle::sourcePath|sourcePath]] при задании класса комплекта ресурсов для них. +<<<<<<< HEAD > Примечание: Не используйте `@webroot/assets` как [[yii\web\AssetBundle::sourcePath|source path]]. Эта директория по умолчанию используется менеджером ресурсов [[yii\web\AssetManager|asset manager]] для сохранения файлов ресурсов, опубликованных из их исходного месторасположения. Любое содержимое этой директории расценивается как временное и может быть удалено. +======= +> Note: Не используйте `@webroot/assets` как [[yii\web\AssetBundle::sourcePath|source path]]. Эта директория по умолчанию используется менеджером ресурсов [[yii\web\AssetManager|asset manager]] для сохранения файлов ресурсов, опубликованных из их исходного месторасположения. Любое содержимое этой директории расценивается как временное и может быть удалено. +>>>>>>> master ### Зависимости ресурсов <span id="asset-dependencies"></span> @@ -82,7 +99,11 @@ class AppAsset extends AssetBundle Вы можете задать свойства [[yii\web\AssetBundle::cssOptions|cssOptions]] и [[yii\web\AssetBundle::jsOptions|jsOptions]], чтобы настроить путь для включения CSS и JavaScript файлов в страницу. Значения этих свойств будут приняты методами [[yii\web\View::registerCssFile()]] и [[yii\web\View::registerJsFile()]] соответственно, когда они (методы) вызываются [представлением](structure-views.md) происходит включение CSS и JavaScript файлов. +<<<<<<< HEAD > Примечание: Параметры, заданные в комплекте класса применяются для *каждого* CSS/JavaScript-файла в комплекте. Если Вы хотите использовать различные параметры для разных файлов, Вы должны создать раздельные комплекты ресурсов, и использовать одну установку параметров для каждого комплекта. +======= +> Note: Параметры, заданные в комплекте класса применяются для *каждого* CSS/JavaScript-файла в комплекте. Если Вы хотите использовать различные параметры для разных файлов, Вы должны создать раздельные комплекты ресурсов, и использовать одну установку параметров для каждого комплекта. +>>>>>>> master Например, условно включим CSS файл для браузера IE9 или ниже. Для этого Вы можете использовать следующий параметр: @@ -148,7 +169,11 @@ class FontAwesomeAsset extends AssetBundle Это происходит потому, что Composer устанавливает Bower или NPM пакет в директорию, соответствующую этим псевдонимам. +<<<<<<< HEAD > Примечание: В некоторых пакетах файлы дистрибутива могут находиться в поддиректории. В этом случае, Вы должны задать поддиреторию как значение [[yii\web\AssetBundle::sourcePath|sourcePath]]. Например, [[yii\web\JqueryAsset]] использует `@bower/jquery/dist` вместо `@bower/jquery`. +======= +> Note: В некоторых пакетах файлы дистрибутива могут находиться в поддиректории. В этом случае, Вы должны задать поддиректорию как значение [[yii\web\AssetBundle::sourcePath|sourcePath]]. Например, [[yii\web\JqueryAsset]] использует `@bower/jquery/dist` вместо `@bower/jquery`. +>>>>>>> master ## Использование Комплекта Ресурсов<span id="using-asset-bundles"></span> @@ -160,7 +185,11 @@ use app\assets\AppAsset; AppAsset::register($this); // $this - представляет собой объект представления ``` +<<<<<<< HEAD > Для справки: Метод [[yii\web\AssetBundle::register()]] возвращает объект комплекта ресурсов, содержащий информацию о публикуемых ресурсах, таких как [[yii\web\AssetBundle::basePath|basePath]] или [[yii\web\AssetBundle::baseUrl|baseUrl]]. +======= +> Info: Метод [[yii\web\AssetBundle::register()]] возвращает объект комплекта ресурсов, содержащий информацию о публикуемых ресурсах, таких как [[yii\web\AssetBundle::basePath|basePath]] или [[yii\web\AssetBundle::baseUrl|baseUrl]]. +>>>>>>> master Если Вы регистрируете комплект ресурсов в других местах (т.е. не в представлении), Вы должны обеспечить необходимый объект представления. Например, при регистрации комплекта ресурсов в классе [widget](structure-widgets.md), Вы можете взять за объект представления `$this->view`. @@ -239,7 +268,11 @@ return [ Ключи [[yii\web\AssetManager::assetMap|assetMap]] - это имена ресурсов, которые Вы хотите исправить, а значения - это требуемые пути для ресурсов. Когда регистрируется комплект ресурсов в представлении, каждый соответствующий файл ресурса в [[yii\web\AssetBundle::css|css]] или [[yii\web\AssetBundle::js|js]] массивах будет рассмотрен в соответствии с этой привязкой. И, если какой-либо из ключей найден, как последняя часть пути до файла ресурса (путь на который начинается с [[yii\web\AssetBundle::sourcePath]] по возможности), то соответствующее значение заменит ресурс и будет зарегистрировано в представлении. Например, путь до файла ресурса `my/path/to/jquery.js` - это соответствует ключу `jquery.js`. +<<<<<<< HEAD > Примечание: Ресурсы заданные только с использованием относительного пути могут использоваться в привязке ресурсов. Пути ресурсов должны быть абсолютные URLs или путь относительно [[yii\web\AssetManager::basePath]]. +======= +> Note: Ресурсы заданные только с использованием относительного пути могут использоваться в привязке ресурсов. Пути ресурсов должны быть абсолютные URLs или путь относительно [[yii\web\AssetManager::basePath]]. +>>>>>>> master ### Публикация Ресурсов<span id="asset-publishing"></span> @@ -348,14 +381,22 @@ return [ В примере выше, Вы задали поддержку расширенного синтаксиса через [[yii\web\AssetConverter::commands]] свойство. Ключи массива - это имена расширений файлов (без ведущей точки), а значения массива - это образующийся файл ресурса имён расширений и команд для выполнения конвертации ресурса. Маркеры `{from}` и `{to}` в командах будут заменены соответственно исходным путём файла ресурсов и путём назначения файла ресурсов. +<<<<<<< HEAD > Примечание: Существуют другие способы работы с ресурсами расширенного синтаксиса, кроме того, который указан выше. +======= +> Note: Существуют другие способы работы с ресурсами расширенного синтаксиса, кроме того, который указан выше. +>>>>>>> master Например, Вы можете использовать инструменты построения, такие как [grunt](http://gruntjs.com/) для отслеживания и автоматической конвертации ресурсов расширенного синтаксиса. В этом случае, Вы должны перечислить конечные CSS/JavaScript файлы в комплекте ресурсов вместо исходных файлов. ## Объединение и Сжатие Ресурсов<span id="combining-compressing-assets"></span> Web страница может включать много CSS и/или JavaScript файлов. Чтобы сократить количество HTTP запросов и общий размер загрузки этих файлов, общепринятой практикой является объединение и сжатие нескольких CSS/JavaScript файлов в один или в более меньшее количество, а затем включение этих сжатых файлов вместо исходных в Web страницы. +<<<<<<< HEAD > Примечание: Комбинирование и сжатие ресурсов обычно необходимо, когда приложение находится в режиме продакшена. +======= +> Note: Комбинирование и сжатие ресурсов обычно необходимо, когда приложение находится в режиме продакшена. +>>>>>>> master В режиме разработки, использование исходных CSS/JavaScript файлов часто более удобно для отладочных целей. Далее, мы представим подход комбинирования и сжатия файлов ресурсов без необходимости изменения Вашего существующего кода приложения. @@ -377,7 +418,11 @@ Web страница может включать много CSS и/или JavaSc У Вас есть два пути, чтобы разделить эти комплекты ресурсов. Первый - использовать одну группу, включающую в себя все комплекты ресурсов. Другой путь - положить комплект А в группу Х, D в группу Y, а (B, C) в группу S. Какой из этих вариантов лучше? Это зависит. Первый способ имеет преимущество в том, что в обоих страницах одинаково скомбинированы файлы CSS и JavaScript, что делает HTTP кэширование более эффективным. С другой стороны, поскольку одна группа содержит все комплекты, размер скомбинированных CSS и JavaScript файлов будет больше, и таким образом увеличится время отдачи файла (загрузки страницы). Для простоты в этом примере, мы будем использовать первый способ, то есть использовать единую группу, содержащую все пакеты. +<<<<<<< HEAD > Примечание: Разделение комплекта ресурсов на группы это не тривиальная задача. Это, как правило, требует анализа реальных данных о трафике различных ресурсов на разных страницах. В начале вы можете начать с одной группы, для простоты. +======= +> Note: Разделение комплекта ресурсов на группы это не тривиальная задача. Это, как правило, требует анализа реальных данных о трафике различных ресурсов на разных страницах. В начале вы можете начать с одной группы, для простоты. +>>>>>>> master Используйте существующие инструменты (например [Closure Compiler](https://developers.google.com/closure/compiler/), [YUI Compressor](https://github.com/yui/yuicompressor/)) для объединения и сжатия CSS и JavaScript файлов во всех комплектах. Обратите внимание, что файлы должны быть объединены в том порядке, который удовлетворяет зависимости между комплектами. Например, если комплект A зависит от В, который зависит от С и D, то Вы должны перечислить файлы ресурсов начиная с С и D, затем B, и только после этого А. @@ -430,7 +475,11 @@ return [ Yii предоставляет консольную команду с именем `asset` для автоматизации подхода, который мы только что описали. +<<<<<<< HEAD Чтобы использовать эту команду, Вы должны сначала создать файл конфигурации для описания того, как комплекты ресурсов должны быть скомбинированны, и как они должны быть сгруппированы. Затем Вы можете использовать подкомманду `asset/template`, чтобы сгенерировать первый шаблон и затем отредактировать его под свои нужды. +======= +Чтобы использовать эту команду, Вы должны сначала создать файл конфигурации для описания того, как комплекты ресурсов должны быть скомбинированы, и как они должны быть сгруппированы. Затем Вы можете использовать подкомманду `asset/template`, чтобы сгенерировать первый шаблон и затем отредактировать его под свои нужды. +>>>>>>> master ``` yii asset/template assets.php @@ -474,7 +523,11 @@ return [ Вы должны изменить этот файл и указать в `bundles` параметре, какие комплекты Вы планируете объединить. В параметре `targets` вы должны указать, как комплекты должны быть поделены в группы. Вы можете указать одну или несколько групп, как уже было сказано выше. +<<<<<<< HEAD > Примечание: Так как псевдонимы путей `@webroot` и `@web` не могут быть использованны в консольном приложении, Вы должны явно задать их в файле конфигурации. +======= +> Note: Так как псевдонимы путей `@webroot` и `@web` не могут быть использованы в консольном приложении, Вы должны явно задать их в файле конфигурации. +>>>>>>> master JavaScript файлы объединены, сжаты и записаны в `js/all-{hash}.js`, где {hash} перенесён из хэша результирующего файла. diff --git a/docs/guide-ru/structure-controllers.md b/docs/guide-ru/structure-controllers.md index fb3039e587..7817999509 100644 --- a/docs/guide-ru/structure-controllers.md +++ b/docs/guide-ru/structure-controllers.md @@ -2,7 +2,7 @@ =========== Контроллеры являются частью [MVC](https://ru.wikipedia.org/wiki/Model-View-Controller) архитектуры. Это объекты классов, унаследованных -от [[yii\base\Controller]] и отвечающие за обработку запроса и генерирование ответа. В сущности, после обработки запроса [приложениями](structure-applications.md), +от [[yii\base\Controller]], отвечающие за обработку запроса и генерирование ответа. В сущности, после обработки запроса [приложениями](structure-applications.md), контроллеры проанализируют входные данные, передадут их в [модели](structure-models.md), вставят результаты модели в [представления](structure-views.md), и в конечном итоге сгенерируют исходящие ответы. @@ -71,7 +71,7 @@ class PostController extends Controller Маршруты могут иметь следующий формат: -``` +```php ControllerID/ActionID ``` @@ -139,9 +139,9 @@ ID контроллеров также могут содержать префи Классы контроллеров должны быть [автозагружаемыми](concept-autoloading.md). Именно по этой причине, в вышеприведенном примере, контроллер `article` должен быть сохранен в файл, [псевдоним](concept-aliases.md) которого `@app/controllers/ArticleController.php`; -в то время как контроллер `admin/post2-comment` должен находиться в файле `@app/controllers/admin/Post2CommentController.php`. +в то время как контроллер `admin/post-comment` должен находиться в файле `@app/controllers/admin/PostCommentController.php`. -> Информация: Последний пример `admin/post2-comment` показывает каким образом вы можете расположить контроллер в подпапке +> Info: Последний пример `admin/post-comment` показывает каким образом вы можете расположить контроллер в подпапке [[yii\base\Application::controllerNamespace|пространства имен контроллеров]]. Это очень удобно, когда вы хотите организовать свои контроллеры в несколько категорий и не хотите использовать [модули](structure-modules.md). @@ -158,15 +158,13 @@ ID контроллеров также могут содержать префи ```php [ 'controllerMap' => [ - [ - // объявляет "account" контроллер, используя название класса - 'account' => 'app\controllers\UserController', + // объявляет "account" контроллер, используя название класса + 'account' => 'app\controllers\UserController', - // объявляет "article" контроллер, используя массив конфигурации - 'article' => [ - 'class' => 'app\controllers\PostController', - 'enableCsrfValidation' => false, - ], + // объявляет "article" контроллер, используя массив конфигурации + 'article' => [ + 'class' => 'app\controllers\PostController', + 'enableCsrfValidation' => false, ], ], ] @@ -176,10 +174,14 @@ ID контроллеров также могут содержать префи Каждое приложение имеет контроллер по умолчанию, указанный через свойство [[yii\base\Application::defaultRoute]]. <<<<<<< HEAD +<<<<<<< HEAD Когда в запросе не указан [маршрут](#ids-routes), тогда будет использован маршрут указанный в данном свойстве. ======= Когда в запросе не указан [маршрут](#routes), тогда будет использован маршрут указанный в данном свойстве. >>>>>>> yiichina/master +======= +Когда в запросе не указан [маршрут](#routes), тогда будет использован маршрут указанный в данном свойстве. +>>>>>>> master Для [[yii\web\Application|Веб приложений]], это значение `'site'`, в то время как для [[yii\console\Application|консольных приложений]], это `'help'`. Таким образом, если задан URL `http://hostname/index.php`, это означает, что контроллер `site` выполнит обработку запроса. @@ -245,13 +247,13 @@ class SiteController extends Controller Например, `index` соответствует `actionIndex`, а `hello-world` соответствует `actionHelloWorld`. -> Примечание: Названия имен действий являются *регистрозависимыми*. Если у вас есть метод `ActionIndex`, он не будет +> Note: Названия имен действий являются *регистрозависимыми*. Если у вас есть метод `ActionIndex`, он не будет учтен как метод действия, таким образом, запрос к действию `index` приведет к выбросу исключению. Также следует учесть, что методы действий должны иметь область видимости public. Методы имеющие область видимости private или protected НЕ определяют методы встроенных действий. Встроенные действия в основном используются, потому что для их создания не нужного много усилий. Тем не менее, если вы планируете повторно -использовать некоторые действия в различных местах, или если вы хотите перераспределить действия, вы должны определить его как *отдельной действие*. +использовать некоторые действия в различных местах, или если вы хотите перераспределить действия, вы должны определить его как *отдельное действие*. ### Отдельные действия <span id="standalone-actions"></span> @@ -383,10 +385,14 @@ public function actionView(array $id, $version = null) Каждый контроллер имеет действие, указанное через свойство [[yii\base\Controller::defaultAction]]. <<<<<<< HEAD +<<<<<<< HEAD Когда [маршрут](#ids-routes) содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию ======= Когда [маршрут](#routes) содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию >>>>>>> yiichina/master +======= +Когда [маршрут](#routes) содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию +>>>>>>> master было запрошено. По-умолчанию, это действие имеет значение `index`. Если вы хотите изменить это значение, просто переопределите данное @@ -423,7 +429,7 @@ class SiteController extends Controller * В противном случае, будет выброшено исключение [[yii\base\InvalidRouteException]]. 3. Контроллер последовательно вызывает метод `beforeAction()` приложения, модуля (если контроллер принадлежит модулю) и самого контроллера. - * Если один из методов вернул `false`, то остальные, невызванные методы `beforeAction` будут пропущены, а выполнение + * Если один из методов вернул `false`, то остальные, не вызванные методы `beforeAction` будут пропущены, а выполнение действия будет отменено; * По-умолчанию, каждый вызов метода `beforeAction()` вызовет событие `beforeAction`, на которое вы можете назначить обработчики. 4. Контроллер запускает действие: @@ -435,12 +441,13 @@ class SiteController extends Controller ## Лучшие практики <span id="best-practices"></span> -В хорошо-организованных приложения, контроллеры обычно очень тонкие, и содержат лишь несколько строк кода. -Если ваш контроллер слишком сложный, это обычно означает, что вам надо провести рефакториг его и перенести какой-либо код +В хорошо организованных приложениях контроллеры обычно очень тонкие и содержат лишь несколько строк кода. +Если ваш контроллер слишком сложный, то обычно это означает, что вам надо провести его рефакторинг и перенести часть кода в другие места. В целом, контроллеры +<<<<<<< HEAD <<<<<<< HEAD * могут иметь доступ к данным [запроса]runtime-requests.md); * могут вызывать методы [моделей](structure-models.md) и других компонентов системы с данными запроса; @@ -452,4 +459,10 @@ class SiteController extends Controller * могут использовать [представления](structure-views.md) для формирования ответа; * не должны заниматься обработкой данных, это должно происходить в [слое моделей](structure-models.md); >>>>>>> yiichina/master +======= +* могут иметь доступ к данным [запроса](runtime-requests.md); +* могут вызывать методы [моделей](structure-models.md) и других компонентов системы с данными запроса; +* могут использовать [представления](structure-views.md) для формирования ответа; +* не должны заниматься обработкой данных, это должно происходить в [слое моделей](structure-models.md); +>>>>>>> master * должны избегать использования HTML или другой разметки, лучше это делать в [представлениях](structure-views.md). diff --git a/docs/guide-ru/structure-entry-scripts.md b/docs/guide-ru/structure-entry-scripts.md index ce288c3dfe..1d2b959c51 100644 --- a/docs/guide-ru/structure-entry-scripts.md +++ b/docs/guide-ru/structure-entry-scripts.md @@ -17,10 +17,14 @@ * Объявляют глобальные константы; <<<<<<< HEAD +<<<<<<< HEAD * Регистрируют загрузчик классов [Composer](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * Регистрируют загрузчик классов [Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* Регистрируют загрузчик классов [Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * Подключают файл класса [[Yii]]; * Загружают конфигурацию приложения; * Создают и конфигурируют объект [приложения](structure-applications.md); @@ -68,10 +72,6 @@ $config = require(__DIR__ . '/../config/web.php'); defined('YII_DEBUG') or define('YII_DEBUG', true); -// fcgi не имеет констант STDIN и STDOUT, они определяются по умолчанию -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - // регистрация загрузчика классов Composer require(__DIR__ . '/vendor/autoload.php'); diff --git a/docs/guide-ru/structure-extensions.md b/docs/guide-ru/structure-extensions.md index 39ada85c52..436eddc681 100644 --- a/docs/guide-ru/structure-extensions.md +++ b/docs/guide-ru/structure-extensions.md @@ -2,17 +2,22 @@ ========== Расширения - это распространяемые программные пакеты, специально разработанные для использования в приложениях Yii и -содержащие готовые функции. Например, расширение [yiisoft/yii2-debug](tool-debugger.md) добавляет удобную отладочную +содержащие готовые функции. Например, расширение [yiisoft/yii2-debug](https://github.com/yiisoft/yii2-debug) добавляет удобную отладочную панель в нижнюю часть каждой страницы вашего приложения, чтобы помочь вам разобраться в том, как генерируются страницы. Вы можете использовать расширения для ускорения процесса разработки. Вы также можете оформить ваш код как расширение, чтобы поделиться с другими людьми результатами вашей работы. +<<<<<<< HEAD > Информация: Мы используем термин "расширение" для специфичных для Yii программных пакетов. Программные пакеты <<<<<<< HEAD общего назначения, которые могут быть использованы с Yii, мы будем называть "пакет" или "библиотека". ======= общего назначения, которые могут быть использованы без Yii, мы будем называть "пакет" или "библиотека". >>>>>>> yiichina/master +======= +> Info: Мы используем термин "расширение" для специфичных для Yii программных пакетов. Программные пакеты + общего назначения, которые могут быть использованы без Yii, мы будем называть "пакет" или "библиотека". +>>>>>>> master ## Использование расширений <span id="using-extensions"></span> @@ -53,7 +58,7 @@ После установки вы можете увидеть директорию `yiisoft/yii2-imagine`, находящуюся по пути `BasePath/vendor`. Также вы можете увидеть директорию `imagine/imagine`, которая содержит зависимый пакет. -> Информация: `yiisoft/yii2-imagine` является базовым расширением, которое разрабатывает и поддерживает команда +> Info: `yiisoft/yii2-imagine` является базовым расширением, которое разрабатывает и поддерживает команда разработчиков Yii. Все базовые расширения размещены на [Packagist](https://packagist.org/) и называются `yiisoft/yii2-xyz`, где `xyz` является названием расширения. @@ -69,7 +74,7 @@ Image::thumbnail('@webroot/img/test-image.jpg', 120, 120) ->save(Yii::getAlias('@runtime/thumb-test-image.jpg'), ['quality' => 50]); ``` -> Информация: Классы расширений автоматически загружаются [автозагрузчиком классов Yii](concept-autoloading.md). +> Info: Классы расширений автоматически загружаются [автозагрузчиком классов Yii](concept-autoloading.md). ### Ручная установка расширений <span id="installing-extensions-manually"></span> @@ -319,7 +324,7 @@ class MyBootstrapClass implements BootstrapInterface отображает содержимое конечному пользователю, вы должны попробовать [интернационализовать и локализовать](tutorial-i18n.md) его. В частности, -- Если расширение отображает сообщения, предназначенные для конечных пользователей, сообщения должны быть обёрнуты в +- Если расширение отображает сообщения, предназначенные для конечных пользователей, сообщения должны быть обернуты в метод `Yii::t()` так, чтобы они могли быть переведены. Сообщения, предназначенные для разработчиков (например, внутренние сообщения исключений), не нужно переводить. - Если расширение отображает числа, даты и т.п., они должны быть отформатированы, используя [[yii\base\Formatter]] с @@ -369,10 +374,10 @@ class MyBootstrapClass implements BootstrapInterface можете обратиться к [файлу класса Object](https://github.com/yiisoft/yii2/blob/master/framework/base/Object.php), чтобы узнать, как нужно документировать код. -> Информация: Ваши комментарии к коду могут быть написаны в формате Markdown. Расширение `yiisoft/yii2-apidoc` +> Info: Ваши комментарии к коду могут быть написаны в формате Markdown. Расширение `yiisoft/yii2-apidoc` предоставляет инструмент для генерации документации API на основе ваших комментариев. -> Информация: Пока это не обязательно, но мы всё-таки рекомендуем вам придерживаться определённого стиля кодирования. +> Info: Пока это не обязательно, но мы всё-таки рекомендуем вам придерживаться определённого стиля кодирования. Вы можете обратиться к [стилю кодирования фреймворка](https://github.com/yiisoft/yii2/wiki/Core-framework-code-style). diff --git a/docs/guide-ru/structure-filters.md b/docs/guide-ru/structure-filters.md index 690d8dc959..cb04d2fc45 100644 --- a/docs/guide-ru/structure-filters.md +++ b/docs/guide-ru/structure-filters.md @@ -40,7 +40,7 @@ public function behaviors() В этом случае они применяются ко *всем* действиям контроллеров, находящихся в этом модуле или приложении если не заданы свойства [[yii\base\ActionFilter::only|only]] и [[yii\base\ActionFilter::except|except]] как было описано выше. -> Примечание: При объявлении фильтров в модулях или приложениях, следует использовать [маршруты](structure-controllers.md#routes) +> Note: При объявлении фильтров в модулях или приложениях, следует использовать [маршруты](structure-controllers.md#routes) вместо идентификаторов действий в свойствах [[yii\base\ActionFilter::only|only]] и [[yii\base\ActionFilter::except|except]] так как сами по себе, идентификаторы действий не могут полностью идентифицировать действие в контексте модуля или приложения. @@ -62,7 +62,7 @@ public function behaviors() При создании нового фильтра действия, необходимо наследоваться от [[yii\base\ActionFilter]] и переопределить методы [[yii\base\ActionFilter::beforeAction()|beforeAction()]] и/или [[yii\base\ActionFilter::afterAction()|afterAction()]]. -Первый из них будет вызыван перед выполнением действия, а второй после. Возвращаемое +Первый из них будет вызван перед выполнением действия, а второй после. Возвращаемое [[yii\base\ActionFilter::beforeAction()|beforeAction()]] значение определяет, будет ли действие выполняться или нет. Если вернётся `false`, то оставшиеся фильтры не будут применены и действие выполнено не будет. @@ -220,7 +220,7 @@ use yii\web\Response; ]; ``` -> Информация: В случае, если предпочтительный тип содержимого и язык не могут быть определены из запроса, будут +> Info: В случае, если предпочтительный тип содержимого и язык не могут быть определены из запроса, будут использованы первый формат и язык, описанные в [[formats]] и [[languages]]. @@ -286,7 +286,7 @@ public function behaviors() Ограничитель количества запросов в единицу времени *(RateLimiter)* реализует алгоритм ограничения запросов, основанный на [алгоритме leaky bucket](http://en.wikipedia.org/wiki/Leaky_bucket). В основном, он используется при создании RESTful API. -Подробнее об использовании данного фильтра пожно прочитать в разделе [Ограничение запросов](rest-rate-limiting.md). +Подробнее об использовании данного фильтра можно прочитать в разделе [Ограничение запросов](rest-rate-limiting.md). ### [[yii\filters\VerbFilter|VerbFilter]] <span id="verb-filter"></span> @@ -317,11 +317,11 @@ public function behaviors() ### [[yii\filters\Cors|Cors]] <span id="cors"></span> -Совместное использование разными источниками [CORS](https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS) - это -механизм, который позволяет использовать различные ресурсы (шрифты, скрипты, и т.д.) с отличных от основного сайта доменов. -В частности, AJAX вызовы JavaScript могут использовать механизм XMLHttpRequest. В противном случае, такие "междоменные" -запросы были бы запрещены из-за политики безопасности same origin. -CORS задаёт способ взаимодействия сервера и браузера, определяющий возможность делать междоменные запросы. +Совместное использование разными источниками [CORS](https://developer.mozilla.org/ru/docs/Web/HTTP/Access_control_CORS) +- это механизм, который позволяет использовать различные ресурсы (шрифты, скрипты, и т.д.) с отличных от основного сайта +доменов. В частности, AJAX вызовы JavaScript могут использовать механизм XMLHttpRequest. В противном случае, такие +"междоменные" запросы были бы запрещены из-за политики безопасности same origin. CORS задаёт способ взаимодействия +сервера и браузера, определяющий возможность делать междоменные запросы. Фильтр [[yii\filters\Cors|Cors filter]] следует определять перед фильтрами Аутентификации / Авторизации, для того чтобы быть уверенными, что заголовки CORS будут всегда посланы. diff --git a/docs/guide-ru/structure-models.md b/docs/guide-ru/structure-models.md index 38e7df21a2..c6ab92b20d 100644 --- a/docs/guide-ru/structure-models.md +++ b/docs/guide-ru/structure-models.md @@ -5,7 +5,11 @@ Вы можете создавать классы моделей путём расширения класса [[yii\base\Model]] или его дочерних классов. Базовый класс [[yii\base\Model]] поддерживает много полезных функций: +<<<<<<< HEAD * [Атрибуты](#attributes): представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массыва; +======= +* [Атрибуты](#attributes): представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массива; +>>>>>>> master * [Метки атрибутов](#attribute-labels): задают отображение атрибута; * [Массовое присвоение](#massive-assignment): поддержка заполнения нескольких атрибутов в один шаг; * [Правила проверки](#validation-rules): обеспечивают ввод данных на основе заявленных правил проверки; @@ -13,7 +17,11 @@ Класс `Model` также является базовым классом для многих расширенных моделей, таких как [Active Record](db-active-record.md). Пожалуйста, обратитесь к соответствующей документации для более подробной информации об этих расширенных моделях. +<<<<<<< HEAD > Для справки: Вы не обязаны основывать свои классы моделей на [[yii\base\Model]]. Однако, поскольку в yii есть много компонентов, созданных для поддержки [[yii\base\Model]], обычно так делать предпочтительнее для базового класса модели. +======= +> Info: Вы не обязаны основывать свои классы моделей на [[yii\base\Model]]. Однако, поскольку в yii есть много компонентов, созданных для поддержки [[yii\base\Model]], обычно так делать предпочтительнее для базового класса модели. +>>>>>>> master ## Атрибуты <span id="attributes"></span> @@ -128,7 +136,11 @@ public function attributeLabels() ## Сценарии <span id="scenarios"></span> +<<<<<<< HEAD Модель может быть использованна в различных *сценариях*. Например, модель `User` может быть использованна для коллекции входных логинов пользователей, а также может быть использованна для цели регистрации пользователей. +======= +Модель может быть использована в различных *сценариях*. Например, модель `User` может быть использована для коллекции входных логинов пользователей, а также может быть использована для цели регистрации пользователей. +>>>>>>> master В различных сценариях, модель может использовать различные бизнес-правила и логику. Например, атрибут `email` может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему. Модель использует свойство [[yii\base\Model::scenario]], чтобы отслеживать сценарий, в котором она используется. По умолчанию, модель поддерживает только один сценарий с именем `default`. В следующем коде показано два способа установки сценария модели: @@ -136,10 +148,17 @@ public function attributeLabels() ```php // сценарий задается как свойство $model = new User; +<<<<<<< HEAD $model->scenario = 'login'; // сценарий задается через конфигурацию $model = new User(['scenario' => 'login']); +======= +$model->scenario = User::SCENARIO_LOGIN; + +// сценарий задается через конфигурацию +$model = new User(['scenario' => User::SCENARIO_LOGIN]); +>>>>>>> master ``` По умолчанию сценарии, поддерживаемые моделью, определяются [правилами валидации](#validation-rules) объявленными @@ -152,17 +171,32 @@ use yii\db\ActiveRecord; class User extends ActiveRecord { +<<<<<<< HEAD public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], +======= + const SCENARIO_LOGIN = 'login'; + const SCENARIO_REGISTER = 'register'; + + public function scenarios() + { + return [ + self::SCENARIO_LOGIN => ['username', 'password'], + self::SCENARIO_REGISTER => ['username', 'email', 'password'], +>>>>>>> master ]; } } ``` +<<<<<<< HEAD > Для справки: В приведенном выше и следующих примерах, классы моделей расширяются от [[yii\db\ActiveRecord]] потому, что использование нескольких сценариев обычно происходит от классов [Active Record](db-active-record.md). +======= +> Info: В приведенном выше и следующих примерах, классы моделей расширяются от [[yii\db\ActiveRecord]] потому, что использование нескольких сценариев обычно происходит от классов [Active Record](db-active-record.md). +>>>>>>> master Метод `scenarios()` возвращает массив, ключами которого являются имена сценариев, а значения - соответствующие *активные атрибуты*. Активные атрибуты могут быть [массово присвоены](#massive-assignment) и подлежат [валидации](#validation-rules). В приведенном выше примере, атрибуты `username` и `password` это активные атрибуты сценария `login`, а в сценарии `register` так же активным атрибутом является `email` вместе с `username` и `password`. @@ -175,11 +209,22 @@ use yii\db\ActiveRecord; class User extends ActiveRecord { +<<<<<<< HEAD public function scenarios() { $scenarios = parent::scenarios(); $scenarios['login'] = ['username', 'password']; $scenarios['register'] = ['username', 'email', 'password']; +======= + const SCENARIO_LOGIN = 'login'; + const SCENARIO_REGISTER = 'register'; + + public function scenarios() + { + $scenarios = parent::scenarios(); + $scenarios[self::SCENARIO_LOGIN] = ['username', 'password']; + $scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password']; +>>>>>>> master return $scenarios; } } @@ -233,10 +278,17 @@ public function rules() { return [ // username, email и password требуются в сценарии "register" +<<<<<<< HEAD [['username', 'email', 'password'], 'required', 'on' => 'register'], // username и password требуются в сценарии "login" [['username', 'password'], 'required', 'on' => 'login'], +======= + [['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER], + + // username и password требуются в сценарии "login" + [['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN], +>>>>>>> master ]; } ``` @@ -244,7 +296,11 @@ public function rules() Если не указать свойство `on`, то правило применяется во всех сценариях. Правило называется *активным правилом* если оно может быть применено в текущем сценарии [[yii\base\Model::scenario|scenario]]. Атрибут будет проверяться тогда и только тогда если он является активным атрибутом объявленным в `scenarios()` и +<<<<<<< HEAD связаным с одним или несколькими активными правилами, объявленными в `rules()`. +======= +связанным с одним или несколькими активными правилами, объявленными в `rules()`. +>>>>>>> master ## Массовое Присвоение <span id="massive-assignment"></span> @@ -267,21 +323,36 @@ $model->body = isset($data['body']) ? $data['body'] : null; ### Безопасные Атрибуты <span id="safe-attributes"></span> +<<<<<<< HEAD Массовое присвоение применяется только к так называемым *безопасным атрибутам*, которые являются атрибутами, перечисленными в [[yii\base\Model::scenarios()]] в текущем сценарии [[yii\base\Model::scenario|scenario]] модели. Например, если модель `User` имеет следующий заданный сценарий, в данном случае это сценарий `login`, то только `username` и `password` могут быть массово присвоены. Любые другие атрибуты остануться нетронутыми. +======= +Массовое присвоение применяется только к так называемым *безопасным атрибутам*, которые являются атрибутами, перечисленными в [[yii\base\Model::scenarios()]] в текущем сценарии [[yii\base\Model::scenario|scenario]] модели. Например, если модель `User` имеет следующий заданный сценарий, в данном случае это сценарий `login`, то только `username` и `password` могут быть массово присвоены. Любые другие атрибуты останутся нетронутыми. +>>>>>>> master ```php public function scenarios() { return [ +<<<<<<< HEAD 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], +======= + self::SCENARIO_LOGIN => ['username', 'password'], + self::SCENARIO_REGISTER => ['username', 'email', 'password'], +>>>>>>> master ]; } ``` +<<<<<<< HEAD > Для справки: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель `User` имеет атрибут `permission`, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс. По умолчанию реализация [[yii\base\Model::scenarios()]] будет возвращать все сценарии и атрибуты найденные в [[yii\base\Model::rules()]], если не переопределить этот метод, это будет означать, что атрибуты являются безопасными до тех пор пока они не появятся в одном из активных правил проверки. +======= +> Info: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель `User` имеет атрибут `permission`, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс. + +По умолчанию [[yii\base\Model::scenarios()]] будет возвращать все сценарии и атрибуты найденные в [[yii\base\Model::rules()]], если не переопределить этот метод, атрибут будет считаться безопасным только в случае, если он участвует в любом из активных правил проверки. +>>>>>>> master По этой причине существует специальный валидатор с псевдонимом `safe`, он предоставляет возможность объявить атрибут безопасным без фактической его проверки. Например, следующие правила определяют, что оба атрибута `title` и `description` являются безопасными атрибутами. @@ -303,7 +374,11 @@ public function rules() public function scenarios() { return [ +<<<<<<< HEAD 'login' => ['username', 'password', '!secret'], +======= + self::SCENARIO_LOGIN => ['username', 'password', '!secret'], +>>>>>>> master ]; } ``` @@ -379,7 +454,11 @@ public function fields() } ``` +<<<<<<< HEAD > Внимание: по умолчанию все атрибуты модели будут включены в экспортируемый массив, вы должны проверить ваши данные и убедиться, что они не содержат конфиденциальной информации. Если такая информация присутствует, вы должны переопределить `fields()` и отфильтровать поля. В приведенном выше примере мы выбираем и отфильтровываем `auth_key`, `password_hash` и `password_reset_token`. +======= +> Warning: по умолчанию все атрибуты модели будут включены в экспортируемый массив, вы должны проверить ваши данные и убедиться, что они не содержат конфиденциальной информации. Если такая информация присутствует, вы должны переопределить `fields()` и отфильтровать поля. В приведенном выше примере мы выбираем и отфильтровываем `auth_key`, `password_hash` и `password_reset_token`. +>>>>>>> master ## Лучшие практические методики разработки моделей <span id="best-practices"></span> @@ -399,4 +478,8 @@ public function fields() * Определить набор базовых классов моделей, которые являются общими для разных [приложений](structure-applications.md) или [модулей](structure-modules.md). Эти классы моделей должны содержать минимальный набор правил и логики, которые являются общими среди всех используемых приложений или модулей. * В каждом [приложении](structure-applications.md) или [модуле](structure-modules.md) в котором используется модель, определить конкретный класс модели (или классы моделей), отходящий от соответствующего базового класса модели. Конкретный класс модели должен содержать правила и логику, которые являются специфическими для данного приложения или модуля. +<<<<<<< HEAD Например, в [Дополнительном Шаблоне Проекта](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), Вы можете определить базовым классом модели `common\models\Post`. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели `frontend\models\Post`, который расширяется от `common\models\Post`. И аналогичным образом для backend приложения, Вы определяете `backend\models\Post`. С помощью такой стратегии, можно быть уверенным, что код в `frontend\models\Post` используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение. +======= +Например, в [шаблоне приложения advanced](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), Вы можете определить базовым классом модели `common\models\Post`. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели `frontend\models\Post`, который расширяется от `common\models\Post`. И аналогичным образом для backend приложения, Вы определяете `backend\models\Post`. С помощью такой стратегии, можно быть уверенным, что код в `frontend\models\Post` используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение. +>>>>>>> master diff --git a/docs/guide-ru/structure-modules.md b/docs/guide-ru/structure-modules.md index e086539f8d..b4e2751bc1 100644 --- a/docs/guide-ru/structure-modules.md +++ b/docs/guide-ru/structure-modules.md @@ -1,16 +1,22 @@ <<<<<<< HEAD +<<<<<<< HEAD Модули ======= Модули +>>>>>>> master +======= +Модули >>>>>>> yiichina/master ======= -Модули - это законченные программные блоки, состоящие из [моделей](structure-models.md), [представлений](structure-views.md), [контроллеров](structure-controllers.md) и других вспомогательных компонентов. При установке модулей в [приложение](structure-applications.md), конечный пользователь получает доступ к их контроллерам. По этой причины модули часто рассматриваются как миниатюрные приложения. В отличии от [приложений](structure-applications.md), модули нельзя развертывать отдельно. Модули должны находиться внутри приложений. +Модули - это законченные программные блоки, состоящие из [моделей](structure-models.md), [представлений](structure-views.md), [контроллеров](structure-controllers.md) и других вспомогательных компонентов. При установке модулей в [приложение](structure-applications.md), конечный пользователь получает доступ к их контроллерам. По этой причине модули часто рассматриваются как миниатюрные приложения. В отличии от [приложений](structure-applications.md), модули нельзя развертывать отдельно. Модули должны находиться внутри приложений. ## Создание модулей <span id="creating-modules"></span> -Модуль помещается в директорию, которая называется [[yii\base\Module::basePath|базовым путем]] модуля. Так же как и в директории приложения, в этой директории существуют поддиректории `controllers`, `models`, `views` и другие, в которых размещаются контроллеры, модели, представления и другие элементы. В следующем примере показано примерное содержимое модуля: +Модуль помещается в директорию, которая называется [[yii\base\Module::basePath|базовым путем]] модуля. Так же как и в +директории приложения, в этой директории существуют поддиректории `controllers`, `models`, `views` и другие, в которых +размещаются контроллеры, модели, представления и другие элементы. В следующем примере показано примерное содержимое модуля: ``` forum/ @@ -27,7 +33,11 @@ forum/ ### Классы модулей <span id="module-classes"></span> -Каждый модуль объявляется с помощью уникального класса, который наследуется от [[yii\base\Module]]. Этот класс должен быть помещен в корне [[yii\base\Module::basePath|базового пути]] модуля и поддерживать [автозагрузку](concept-autoloading.md). Во время доступа к модулю будет создан один экземпляр соответствующего класса модуля. Как и [экземпляры приложения](structure-applications.md), экземпляры модулей нужны, чтобы код модулей мог получить общий доступ к данным и компонентам. +Каждый модуль объявляется с помощью уникального класса, который наследуется от [[yii\base\Module]]. Этот класс должен +быть помещен в корне [[yii\base\Module::basePath|базового пути]] модуля и поддерживать [автозагрузку](concept-autoloading.md). +Во время доступа к модулю будет создан один экземпляр соответствующего класса модуля. Как и +[экземпляры приложения](structure-applications.md), экземпляры модулей нужны, чтобы код модулей мог получить общий +доступ к данным и компонентам. Приведем пример того, как может выглядеть класс модуля: @@ -46,7 +56,8 @@ class Module extends \yii\base\Module } ``` -Если метод `init()` стал слишком громоздким из-за кода, который задает свойства модуля, эти свойства можно сохранить в виде [конфигурации](concept-configurations.md), а затем загрузить в методе `init()` следующим образом: +Если метод `init()` стал слишком громоздким из-за кода, который задает свойства модуля, эти свойства можно сохранить +в виде [конфигурации](concept-configurations.md), а затем загрузить в методе `init()` следующим образом: ```php public function init() @@ -57,7 +68,8 @@ public function init() } ``` -При этом в конфигурационном файле `config.php` может быть код следующего вида, аналогичный [конфигурации приложения](structure-applications.md#application-configurations): +При этом в конфигурационном файле `config.php` может быть код следующего вида, аналогичный +[конфигурации приложения](structure-applications.md#application-configurations): ```php <?php @@ -74,7 +86,10 @@ return [ ### Контроллеры в модулях <span id="controllers-in-modules"></span> -При создании контроллеров модуля принято помещать классы контроллеров в подпространство `controllers` пространства имен класса модуля. Это также подразумевает, что файлы классов контроллеров должны располагаться в директории `controllers` [[yii\base\Module::basePath|базового пути]] модуля. Например, чтобы описать контроллер `post` в модуле `forum` из предыдущего примера, класс контроллера объявляется следующим образом: +При создании контроллеров модуля принято помещать классы контроллеров в подпространство `controllers` пространства +имён класса модуля. Это также подразумевает, что файлы классов контроллеров должны располагаться в директории `controllers` +[[yii\base\Module::basePath|базового пути]] модуля. Например, чтобы описать контроллер `post` в модуле `forum` из +предыдущего примера, класс контроллера объявляется следующим образом: ```php namespace app\modules\forum\controllers; @@ -87,19 +102,56 @@ class PostController extends Controller } ``` -Изменить пространство имен классов контроллеров можно задав свойство [[yii\base\Module::controllerNamespace]]. Если какие-либо контроллеры выпадают из этого пространства имен, доступ к ним можно осуществить, настроив свойство [[yii\base\Module::controllerMap]], аналогично тому, [как это делается в приложении](structure-applications.md#controller-map). +Изменить пространство имен классов контроллеров можно задав свойство [[yii\base\Module::controllerNamespace]]. Если +какие-либо контроллеры выпадают из этого пространства имен, доступ к ним можно осуществить, настроив свойство +[[yii\base\Module::controllerMap]], аналогично тому, [как это делается в приложении](structure-applications.md#controller-map). ### Представления в модулях <span id="views-in-modules"></span> -Представления модуля также следует поместить в в поддиректорию `views` [[yii\base\Module::basePath|базового пути]] модуля. Виды, которые рендерит контроллер модуля, должны располагаться в директории `views/ControllerID`, где `ControllerID` соответствует [идентификатору контроллера](structure-controllers.md#routes). Например, если контроллер реализуется классом `PostController`, представления следует разместить в поддиректории `views/post` [[yii\base\Module::basePath|базового пути]] модуля. +Представления модуля также следует поместить в в поддиректорию `views` [[yii\base\Module::basePath|базового пути]] +модуля. Виды, которые рендерит контроллер модуля, должны располагаться в директории `views/ControllerID`, где `ControllerID` +соответствует [идентификатору контроллера](structure-controllers.md#routes). Например, если контроллер реализуется +классом `PostController`, представления следует разместить в поддиректории `views/post` +[[yii\base\Module::basePath|базового пути]] модуля. + +В модуле можно задать [шаблон](structure-views.md#layouts), который будет использоваться для рендеринга всех представлений +контроллерами модуля. По умолчанию шаблон помещается в директорию `views/layouts`, а свойство [[yii\base\Module::layout]] +должно указывать на имя этого шаблона. Если не задать свойство `layout`, модуль будет использовать шаблон, заданный +в приложении. + +### Консольные команды в модулях <span id="console-commands-in-modules"></span> + +Ваш модуль также может объявлять команды, которые будут доступны через [консоль](tutorial-console.md). + +Для того, чтобы команда стала доступна, надо изменить свойство [[yii\base\Module::controllerNamespace]] для консольного +режима так, чтобы оно содержало пространство имён ваших команд. + +Этого можно добиться проверяя класс экземпляра приложения Yii в методе `init` модуля: + +```php +public function init() +{ + parent::init(); + if (Yii::$app instanceof \yii\console\Application) { + $this->controllerNamespace = 'app\modules\forum\commands'; + } +} +``` + +Ваши команды будут доступны из командной строки как: + +``` +yii <module_id>/<command>/<sub_command> +``` -В модуле можно задать [шаблон](structure-views.md#layouts), который будет использоваться для рендеринга всех представлений контроллерами модуля. По умолчанию шаблон помещается в директорию `views/layouts`, а свойство [[yii\base\Module::layout]] должно указывать на имя этого шаблона. Если не задать свойство `layout`, модуль будет использовать шаблон, заданный в приложении. ## Использование модулей <span id="using-modules"></span> -Чтобы задействовать модуль в приложении, достаточно включить его в свойство [[yii\base\Application::modules|modules]] в конфигурации приложения. Следующий код в [конфигурации приложения](structure-applications.md#application-configurations) задействует модуль `forum`: +Чтобы задействовать модуль в приложении, достаточно включить его в свойство [[yii\base\Application::modules|modules]] +в конфигурации приложения. Следующий код в [конфигурации приложения](structure-applications.md#application-configurations) +задействует модуль `forum`: ```php [ @@ -112,25 +164,40 @@ class PostController extends Controller ] ``` -Свойству [[yii\base\Application::modules|modules]] присваивается массив, содержащий конфигурацию модуля. Каждый ключ массива представляет собой *идентификатор модуля*, который однозначно определяет модуль среди других модулей приложения, а соответствующий массив - это [конфигурация](concept-configurations.md) для создания модуля. +Свойству [[yii\base\Application::modules|modules]] присваивается массив, содержащий конфигурацию модуля. Каждый ключ массива +представляет собой *идентификатор модуля*, который однозначно определяет модуль среди других модулей приложения, +а соответствующий массив - это [конфигурация](concept-configurations.md) для создания модуля. ### Маршруты <span id="routes"></span> -Как маршруты приложения используются для обращения к контроллерам приложения, [маршруты](structure-controllers.md#routes) модуля используются, чтобы обращаться к контроллерам этого модуля. Маршрут контроллера в модуле должен начинаться с идентификатора модуля, за которым следуют идентификатор контроллера и идентификатор действия. Например, если в приложении задействован модуль `forum`, то маршрут `forum/post/index` соответствует действию `index` контроллера `post` этого модуля. Если маршрут состоит только из идентификатора модуля, то контроллер и действие определяются исходя из свойства [[yii\base\Module::defaultRoute]], которое по умолчанию равно `default`. Таким образом, маршрут `forum` соответствует контроллеру `default` модуля `forum`. +Как маршруты приложения используются для обращения к контроллерам приложения, [маршруты](structure-controllers.md#routes) +модуля используются, чтобы обращаться к контроллерам этого модуля. Маршрут контроллера в модуле должен начинаться с +идентификатора модуля, за которым следуют [идентификатор контроллера](structure-controllers.md#controller-ids) и +[идентификатор действия](structure-controllers.md#action-ids). Например, если в приложении задействован модуль `forum`, +то маршрут `forum/post/index` соответствует действию `index` контроллера `post` этого модуля. Если маршрут состоит только +из идентификатора модуля, то контроллер и действие определяются исходя из свойства [[yii\base\Module::defaultRoute]], +которое по умолчанию равно `default`. Таким образом, маршрут `forum` соответствует контроллеру `default` модуля `forum`. ### Получение доступа к модулям <span id="accessing-modules"></span> -Зачастую внутри модуля может потребоваться доступ к экземпляру [класса модуля](#module-classes), через который получаются идентификатор модуля, его параметры, компоненты, и т. п. Это можно сделать с помощью следующей конструкции: +Зачастую внутри модуля может потребоваться доступ к экземпляру [класса модуля](#module-classes), через который получаются +идентификатор модуля, его параметры, компоненты, и т. п. Это можно сделать с помощью следующей конструкции: ```php $module = MyModuleClass::getInstance(); ``` -где `MyModuleClass` соответствует имени класса модуля, доступ к которому нужно получить. Метод `getInstance()` возвращает запрошенный в данный момент экземпляр класса модуля. Если модуль не запрошен, метод вернет null. Учтите, что обычно экземпляры класса модуля вручную не создаются, так как созданный вручную экземпляр будет отличаться от экземпляра, созданного Yii в качестве ответа на запрос. +где `MyModuleClass` соответствует имени класса модуля, доступ к которому нужно получить. Метод `getInstance()` возвращает +запрошенный в данный момент экземпляр класса модуля. Если модуль не запрошен, метод вернет null. Учтите, что обычно +экземпляры класса модуля вручную не создаются, так как созданный вручную экземпляр будет отличаться от экземпляра, +созданного Yii в качестве ответа на запрос. -> Информация: При разработке модуля нельзя исходить из предположения, что модулю будет назначен конкретный идентификатор. Это связано с тем, что идентификатор, назначаемый модулю при использовании в приложении или в другом модуле, может быть выбран совершенно произвольно. Чтобы получить идентификатор модуля, нужно вначале выбрать экземпляр модуля, как это описано выше, а затем получить доступ к идентификатору через свойство `$module->id`. +> Info: При разработке модуля нельзя исходить из предположения, что модулю будет назначен конкретный идентификатор. +Это связано с тем, что идентификатор, назначаемый модулю при использовании в приложении или в другом модуле, может быть +выбран совершенно произвольно. Чтобы получить идентификатор модуля, нужно вначале выбрать экземпляр модуля, как это +описано выше, а затем получить доступ к идентификатору через свойство `$module->id`. Доступ к экземпляру модуля можно получить следующими способами: @@ -142,7 +209,8 @@ $module = \Yii::$app->getModule('forum'); $module = \Yii::$app->controller->module; ``` -Первый подход годится только если известен идентификатор модуля, а второй подход наиболее полезен, если известно, какой контроллер запрошен. +Первый подход годится только если известен идентификатор модуля, а второй подход наиболее полезен, если известно, +какой контроллер запрошен. Имея экземпляр модуля можно получить доступ к параметрам и компонентам, зарегистрированным в модуле. Например, @@ -153,7 +221,9 @@ $maxPostCount = $module->params['maxPostCount']; ### Предзагрузка модулей <span id="bootstrapping-modules"></span> -Может потребоваться запускать некоторые модули при каждом запросе. Модуль [[yii\debug\Module|debug]] - один из таких модулей. Для этого список идентификаторов таких модулей необходимо указать в свойстве [[yii\base\Application::bootstrap|bootstrap]] приложения. +Может потребоваться запускать некоторые модули при каждом запросе. Модуль [[yii\debug\Module|debug]] - один из таких +модулей. Для этого список идентификаторов таких модулей необходимо указать в свойстве +[[yii\base\Application::bootstrap|bootstrap]] приложения. Например, следующая конфигурация приложения обеспечивает загрузку модуля `debug` при каждом запросе: @@ -172,7 +242,9 @@ $maxPostCount = $module->params['maxPostCount']; ## Вложенные модули <span id="nested-modules"></span> -Модули могут вкладываться друг в друга без ограничений по глубине. Иными словами, в модуле содержится модуль, в который входит еще один модуль, и т. д. Первый модуль называется *родительским*, остальные - *дочерними*. Дочерние модули объявляются в свойстве [[yii\base\Module::modules|modules]] родительских модулей. Например, +Модули могут вкладываться друг в друга без ограничений по глубине. Иными словами, в модуле содержится модуль, в который +входит еще один модуль, и т. д. Первый модуль называется *родительским*, остальные - *дочерними*. Дочерние модули +объявляются в свойстве [[yii\base\Module::modules|modules]] родительских модулей. Например, ```php namespace app\modules\forum; @@ -193,13 +265,21 @@ class Module extends \yii\base\Module } ``` -Маршрут к контроллеру вложенного модуля должен содержать идентификаторы всех его предков. Например, маршрут `forum/admin/dashboard/index` соответствует действию `index` контроллера `dashboard` модуля `admin`, который в свою очередь является дочерним модулем модуля `forum`. +Маршрут к контроллеру вложенного модуля должен содержать идентификаторы всех его предков. Например, маршрут +`forum/admin/dashboard/index` соответствует действию `index` контроллера `dashboard` модуля `admin`, который в свою +очередь является дочерним модулем модуля `forum`. -> Информация: Метод [[yii\base\Module::getModule()|getModule()]] возвращает только те дочерние модули, которые принадлежат родительскому модулю непосредственно. В свойстве [[yii\base\Application::loadedModules]] содержится список загруженных модулей, в том числе прямых и косвенных потомков, с индексированием по имени класса. +> Info: Метод [[yii\base\Module::getModule()|getModule()]] возвращает только те дочерние модули, которые +принадлежат родительскому модулю непосредственно. В свойстве [[yii\base\Application::loadedModules]] содержится +список загруженных модулей, в том числе прямых и косвенных потомков, с индексированием по имени класса. ## Лучшие практики<span id="best-practices"></span> -Модули лучше всего подходят для крупных приложений, функционал которых можно разделить на несколько групп, в каждой из которых функции тесно связаны между собой. Каждая группа функций может разрабатываться в виде модуля, над которым работает один разработчик или одна команда. +Модули лучше всего подходят для крупных приложений, функционал которых можно разделить на несколько групп, в каждой из +которых функции тесно связаны между собой. Каждая группа функций может разрабатываться в виде модуля, над которым работает +один разработчик или одна команда. -Модули - это хороший способ повторно использовать код на уровне групп функций. В виде модулей можно реализовать такой функционал как управление пользователями или управление комментариями, а затем использовать эти модули в будущих разработках. +Модули - это хороший способ повторно использовать код на уровне групп функций. В виде модулей можно реализовать такую +функциональность, как управление пользователями или управление комментариями, а затем использовать эти модули в будущих +разработках. diff --git a/docs/guide-ru/structure-overview.md b/docs/guide-ru/structure-overview.md index 642ceda8cc..c1560813e2 100644 --- a/docs/guide-ru/structure-overview.md +++ b/docs/guide-ru/structure-overview.md @@ -1,7 +1,7 @@ Обзор ===== -Yii приложения организованы согласно шаблону проектирования [модель-представление-поведение (MVC)](http://ru.wikipedia.org/wiki/Model-View-Controller). +Yii приложения организованы согласно шаблону проектирования [модель-представление-контроллер (MVC)](http://ru.wikipedia.org/wiki/Model-View-Controller). [Модели](structure-models.md) представляют собой данные, бизнес логику и бизнес правила; [представления](structure-views.md) отвечают за отображение информации, в том числе и на основе данных, полученных из моделей; [контроллеры](structure-controllers.md) принимают входные данные от пользователя и преобразовывают их в понятный для [моделей](structure-models.md) формат и команды, а также отвечают за отображение @@ -23,4 +23,4 @@ Yii приложения организованы согласно шаблон Ниже на диаграмме представлена структурная схема приложения: -![Static Structure of Application](images/application-structure.png) \ No newline at end of file +![Static Structure of Application](images/application-structure.png) diff --git a/docs/guide-ru/structure-views.md b/docs/guide-ru/structure-views.md index 4f232511f0..2aacc004ec 100644 --- a/docs/guide-ru/structure-views.md +++ b/docs/guide-ru/structure-views.md @@ -69,7 +69,7 @@ use yii\helpers\HtmlPurifier; </div> ``` -> Совет: Несмотря на то, что HTMLPurifier отлично справляется с тем, чтобы сделать вывод безопасным, работает он довольно медленно. Если от приложения требуется высокая производительность, рассмотрите возможность [кэширования](caching-overview.md) отфитрованного результата +> Tip: Несмотря на то, что HTMLPurifier отлично справляется с тем, чтобы сделать вывод безопасным, работает он довольно медленно. Если от приложения требуется высокая производительность, рассмотрите возможность [кэширования](caching-overview.md) отфильтрованного результата ### Организация видов <span id="organizing-views"></span> @@ -205,11 +205,15 @@ echo \Yii::$app->view->renderFile('@app/views/site/license.php'); Т.е. вид будет искаться в [[yii\base\Application::viewPath|папке видов приложения по умолчанию]]. Например, `//site/about` будет преобразован в `@app/views/site/about.php`. * Если имя вида начинается с одинарного слеша `/`, то вид будет искаться в [[yii\base\Module::viewPath|папке видов по умолчанию]] текущего [модуля](structure-modules.md) . Если активного модуля на данный момент нет, будет использована папка видов приложения по умолчанию, т.е. вид будет искаться в `@app/views`, как в одном из примеров выше. * Если вид рендеринтся с помощью [[yii\base\View::context|контекста]] и контекст реализует интерфейс [[yii\base\ViewContextInterface]], путь к виду образуется путем присоединения [[yii\base\ViewContextInterface::getViewPath()|пути видов]] контекста к имени вида. В основном это применимо к видам, которые рендерятся из контроллеров и виджетов. Например, +<<<<<<< HEAD <<<<<<< HEAD `site/about` будет преобразован в `@app/views/site/about.php` если контекстом является контроллер `SiteController`. ======= `about` будет преобразован в `@app/views/site/about.php` если контекстом является контроллер `SiteController`. >>>>>>> yiichina/master +======= + `about` будет преобразован в `@app/views/site/about.php` если контекстом является контроллер `SiteController`. +>>>>>>> master * Если вид рендерится из другого вида, папка, в которой находится текущий вид будет добавлена к пути вложенного вида. Например, `item` будет преобразован в `@app/views/post/item` если он рендерится из вида `@app/views/post/index.php`. @@ -236,9 +240,12 @@ echo $this->render('report', [ ```php ID контроллера: <?= $this->context->id ?> <<<<<<< HEAD +<<<<<<< HEAD ?> ======= >>>>>>> yiichina/master +======= +>>>>>>> master ``` Явная передача данных в вид обычно более предпочтительна, т.к. она делает виды независимыми от контекста. Однако, у нее есть недостаток - необходимость каждый раз вручную строить массив данных, что может быть довольно утомительно и привести к ошибкам, если вид рендерится в разных местах. @@ -302,7 +309,7 @@ use yii\helpers\Html; Как видите, шаблон генерирует HTML тэги, которые присутствуют на всех страницах. Внутри секции `<body>`, шаблон выводит переменную `$content`, которая содержит результат рендеринга видов контента, который передается в шаблон, при работе метода [[yii\base\Controller::render()]]. -Большинство шаблонов вызывают методы, аналогично тому, как это сделано в примере выше, чтобы скрипты и тэги, зарегистированные в других местах приложения могли быть правильно отображены в местах вызова (например, в шаблоне). +Большинство шаблонов вызывают методы, аналогично тому, как это сделано в примере выше, чтобы скрипты и тэги, зарегистрированные в других местах приложения могли быть правильно отображены в местах вызова (например, в шаблоне). - [[yii\base\View::beginPage()|beginPage()]]: Этот метод нужно вызывать в самом начале шаблона. Он вызывает событие [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], которое происходит при начале обработки страницы. @@ -524,14 +531,14 @@ $this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php' <meta name="keywords" content="yii, framework, php"> ``` -Обратите внимание, что при вызове метода [[yii\web\View::registerMetaTag()]] несколько раз мета тэги будут регистироваться +Обратите внимание, что при вызове метода [[yii\web\View::registerMetaTag()]] несколько раз мета тэги будут регистрироваться каждый раз без проверки на уникальность. Чтобы убедиться, что зарегистрирован только один экземпляр одного типа мета тэгов, вы можете указать ключ мета тэга в качестве второго параметра при вызове метода. К примеру, следующий код регистрирует два мета тэга "description", однако отрендерен будет только второй. -```html +```php $this->registerMetaTag(['name' => 'description', 'content' => 'Мой сайт сделан с помощью Yii!'], 'description'); $this->registerMetaTag(['name' => 'description', 'content' => 'Это сайт о забавных енотах.'], 'description'); ``` diff --git a/docs/guide-ru/structure-widgets.md b/docs/guide-ru/structure-widgets.md index 6fbd769dfa..89be1567a6 100644 --- a/docs/guide-ru/structure-widgets.md +++ b/docs/guide-ru/structure-widgets.md @@ -71,6 +71,15 @@ use yii\helpers\Html; рендеринга, метод [[yii\base\Widget::begin()]] возвращает экземпляр виджета, который может быть использован в дальнейшем для формирования его внутреннего содержимого. +### Задание глобальных умолчаний + +Глобальные умолчания для определённого типа виджета могут быть заданы через DI контейнер: + +```php +\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); +``` + +Подробнее это описано в [подразделе «Практическое использование» раздела «Контейнер внедрения зависимостей»](concept-di-container.md#practical-usage). ## Создание Виджетов <span id="creating-widgets"></span> @@ -145,7 +154,7 @@ class HelloWidget extends Widget Как Вы можете видеть, в методе `init()` происходит включение буферизации вывода PHP таким образом, что весь вывод между вызовами `init()` и `run()` может быть перехвачен, обработан и возвращен в `run()`. -> Информация: При вызове метода [[yii\base\Widget::begin()]] будет создан новый экземпляр виджета, при этом +> Info: При вызове метода [[yii\base\Widget::begin()]] будет создан новый экземпляр виджета, при этом вызов метода `init()` произойдет сразу после выполнения остального кода в конструкторе виджета. При вызове метода [[yii\base\Widget::end()]], будет вызван метод `run()`, а возвращенное им значение будет выведено методом `end()`. diff --git a/docs/guide-ru/test-acceptance.md b/docs/guide-ru/test-acceptance.md new file mode 100644 index 0000000000..7b27ab27de --- /dev/null +++ b/docs/guide-ru/test-acceptance.md @@ -0,0 +1,11 @@ +Приёмочное тестирование +======================= + +> Note: Данный раздел находится в разработке. + +- [Codeception Acceptance Tests](http://codeception.com/docs/03-AcceptanceTests) + +Запуск тестов в шаблонах проектов basic и advanced +-------------------------------------------------- + +Инструкции приведены в `apps/advanced/tests/README.md` и `apps/basic/tests/README.md`. diff --git a/docs/guide-ru/test-environment-setup.md b/docs/guide-ru/test-environment-setup.md new file mode 100644 index 0000000000..a62ee253f9 --- /dev/null +++ b/docs/guide-ru/test-environment-setup.md @@ -0,0 +1,49 @@ +Настройка тестового окружения +============================= + +> Note: Данный раздел находится в разработке. + +Yii 2 официально поддерживает интеграцию с фреймворком для тестирования [`Codeception`](https://github.com/Codeception/Codeception), +который позволяет вам проводить следующие типы тестов: + +- [Модульное тестирование](test-unit.md) - проверяет что отдельный модуль кода работает верно; +- [Функциональное тестирование](test-functional.md) - проверяет пользовательские сценарии через эмуляцию браузера; +- [Приёмочное тестирование](test-acceptance.md) - проверяет пользовательские сценарии в браузере. + +Все три типа тестов представлены в шаблонах проектов +[`yii2-basic`](https://github.com/yiisoft/yii2-app-basic) и +[`yii2-advanced`](https://github.com/yiisoft/yii2-app-advanced). + +Для того, чтобы запустить тесты необходимо установить [Codeception](https://github.com/Codeception/Codeception). +Сделать это можно как локально, то есть только для текущего проекта, так и глобально для компьютера разработчика. + +Для локальной установки используйте следующие команды: + +``` +composer require "codeception/codeception=2.0.*" +composer require "codeception/specify=*" +composer require "codeception/verify=*" +``` + +Для глобальной установки необходимо добавить директиву `global`: + +``` +composer global require "codeception/codeception=2.0.*" +composer global require "codeception/specify=*" +composer global require "codeception/verify=*" +``` + +Если вы никогда не пользовались Composer для установки глобальных пакетов, запустите `composer global status`. +На выходе вы должны получить: + +``` +Changed current directory to <directory> +``` + +Затем `<directory>/vendor/bin` добавьте в переменную окружения `PATH`. После этого можно использовать `codecept` глобально +из командной строки. + +> Note: глобальная установка позволяет вам использовать Codeception для всех проектов на компьютере разработчика + путём запуска команды `codecept` без указания пути. Тем не менее, данный подход может не подойти. К примеру, в двух + разных проектах может потребоваться установить разные версии Codeception. Для простоты все команды в разделах про + тестирование используются так, будто Codeception установлен глобально. diff --git a/docs/guide-ru/test-fixtures.md b/docs/guide-ru/test-fixtures.md new file mode 100644 index 0000000000..f7fc694f0e --- /dev/null +++ b/docs/guide-ru/test-fixtures.md @@ -0,0 +1,383 @@ +Фикстуры +======== + +Фикстуры (англ. fixtures) - это важная составляющая тестирования. Их основная задача заключается в подготовке окружения +с заранее фиксированным/известным состоянием для гарантии повторяемости процесса тестирования. Yii предоставляет +фреймворк, который позволяет легко и точно определять фикстуры и использовать их в ваших тестах. + +Ключевым понятием в фреймворке фикстур Yii является так называемый *объект фикстуры*. Объект фикстуры представляет собой +особый аспект тестового окружения, который наследуется от [[yii\test\Fixture]] или его наследников. Например, вы можете +использовать `UserFixture` для того, чтобы быть уверенным, что таблица пользователей содержит известный набор данных. Вы +загружаете один или несколько объектов фикстур перед запуском теста и выгружаете их после его завершения. + +Фикстура может зависеть от других фикстур, заданных через свойство [[yii\test\Fixture::depends]]. +Когда фикстура загружается, фикстуры от которых она зависит будут автоматически загружены ДО нее, а когда она +выгружается все зависимые фикстуры будут выгружены ПОСЛЕ нее. + + +Объявление фикстуры +------------------- + +Для объявления фикстуры создайте новый класс унаследованный от [[yii\test\Fixture]] или [[yii\test\ActiveFixture]]. +Первый лучше всего подходит для фикстур общего назначения, в то время как последний имеет расширенные функции, +специально предназначенные для работы с базой данных и ActiveRecord. + +Следующий код показывает как объявить фикстуру для модели ActiveRecord `User`, которая соответствует таблице пользователей. + + +```php +<?php +namespace app\tests\fixtures; + +use yii\test\ActiveFixture; + +class UserFixture extends ActiveFixture +{ + public $modelClass = 'app\models\User'; +} +``` + +> Tip: каждая `ActiveFixture` предназначена для подготовки таблицы базы данных для тестирования. Вы можете указать +> таблицу как через свойство [[yii\test\ActiveFixture::tableName]], так и через свойство [[yii\test\ActiveFixture::modelClass]]. +> Если последнее, то в этом случае имя таблицы будет взято из модели `ActiveRecord`, указанной в `modelClass`. + +> Note: [[yii\test\ActiveFixture]] используется только для реляционных баз данных. Для NoSQL решений Yii +> предоставляет следующие классы `ActiveFixture`: +> +> - Mongo DB: [[yii\mongodb\ActiveFixture]] +> - Elasticsearch: [[yii\elasticsearch\ActiveFixture]] (начиная с версии 2.0.2) + +Данные для фикстуры `ActiveFixture` как правило находятся в файле `FixturePath/data/TableName.php`, +где `FixturePath` указывает на директорию в которой располагается файл класса фикстуры, а `TableName` имя таблицы +с которой она ассоциируется. Для примера выше, данные должны должны быть в файле `@app/tests/fixtures/data/user.php`. +Данный файл должен вернуть массив данных для строк, которые будут вставлены в таблицу пользователей. Например + +```php +<?php +return [ + 'user1' => [ + 'username' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + 'user2' => [ + 'username' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` + +Вы можете задать псевдоним строке для того, чтобы в будущем вы могли ссылаться на нее в ваших тестах. В примере выше +2 строки имеют псевдонимы `user1` и `user2`, соответственно. + +Также вам не нужно указывать данные для столбцов с авто-инкрементом. Yii автоматически заполнит значения данных столбцов +в момент загрузки фикстуры. + +> Tip: вы можете указать свой путь до файла данных через свойство [[yii\test\ActiveFixture::dataFile]]. +> Вы также можете переопределить метод [[yii\test\ActiveFixture::getData()]], чтобы предоставить данные. + +Как мы описали ранее, фикстура может зависеть от других фикстур. Например, для `UserProfileFixture` возможно потребуется +зависимость от `UserFixture` так как таблица пользовательских профилей содержит внешний ключ, указывающий на таблицу пользователей. +Зависимость указывается через свойство [[yii\test\Fixture::depends]], как в следующем примере + +```php +namespace app\tests\fixtures; + +use yii\test\ActiveFixture; + +class UserProfileFixture extends ActiveFixture +{ + public $modelClass = 'app\models\UserProfile'; + public $depends = ['app\tests\fixtures\UserFixture']; +} +``` + +Зависимость также гарантирует, что фикстуры загружаются и выгружаются в определенном порядке. В предыдущем примере `UserFixture` +будет автоматически загружена до `UserProfileFixture`, тем самым гарантируя существование всех внешних ключей, и будет выгружена +после того как выгрузится `UserProfileFixture` по тем же причинам. + +Выше мы показали как объявить фикстуру для таблицы базы данных. Для объявления фикстуры не связанной с базой данных (например, +фикстуры для определенных файлов и директорий), вам следует унаследовать ее от класса [[yii\test\Fixture]] +и переопределить методы [[yii\test\Fixture::load()|load()]] и [[yii\test\Fixture::unload()|unload()]]. + + +Использование фикстур +--------------------- + +Если вы используете [Codeception](http://codeception.com/) для тестирования вашего кода, вам следует рассмотреть вопрос +об использовании расширения `yii2-codeception`, которое имеет встроенную поддержку загрузки фикстур и доступа к ним. +Если вы используете другой фреймворк для тестирования, вы можете использовать [[yii\test\FixtureTrait]] в ваших тестах для +этих целей. + +Далее мы опишем как написать класс модульного тестирования для модели `UserProfile` с использованием расширения `yii2-codeception`. + +Объявите какие фикстуры вы хотите использовать в методе [[yii\test\FixtureTrait::fixtures()|fixtures()]] вашего класса модульного +тестирования, унаследованного от [[yii\codeception\DbTestCase]] или [[yii\codeception\TestCase]]. Например, + +```php +namespace app\tests\unit\models; + +use yii\codeception\DbTestCase; +use app\tests\fixtures\UserProfileFixture; + +class UserProfileTest extends DbTestCase +{ + public function fixtures() + { + return [ + 'profiles' => UserProfileFixture::className(), + ]; + } + + // ...методы тестирования... +} +``` + +Фикстуры перечисленные в методе `fixtures()` будут автоматически загружены перед выполнением каждого метода тестирования тест-кейса +и выгружены после завершения каждого метода тестирования. И, как мы описали ранее, когда фикстура загружается, все зависимые от нее +фикстуры будут автоматически загружены в первую очередь. В приведенном выше примере, при выполнении любого метода тестирования +в тест-кейсе последовательно будут загружены две фикстуры: `UserFixture` и `UserProfileFixture`, поскольку `UserProfileFixture` +зависит от `UserFixture`. + +Для определения фикстур в методе `fixtures()` вы можете использовать либо имя класса, либо массив настроек. С помощью массива +настроек вы можете настроить свойства фикстуры, которые будут установлены при ее загрузке. + +Вы также можете назначить фикстуре псевдоним. В примере выше, `profiles` является псевдонимом фикстуры `UserProfileFixture`. +С помощью псевдонима вы можете получить объект фикстуры в ваших методах тестирования. Например, `$this->profiles` вернет +объект `UserProfileFixture`. + +Поскольку `UserProfileFixture` наследуется от `ActiveFixture`, вы можете также использовать следующий синтаксис для доступа к +данным фикстуры: + +```php +// вернет строку данных для псевдонима 'user1' +$row = $this->profiles['user1']; +// вернет модель UserProfile, соответствующую строке данных для псевдонима 'user1' +$profile = $this->profiles('user1'); +// обход данных фикстуры в цикле +foreach ($this->profiles as $row) ... +``` + +> Info: `$this->profiles` продолжает быть объектом класса `UserProfileFixture`. Указанные особенности доступа реализуются +через магические методы PHP. + + +Определение и использование глобальных фикстур +---------------------------------------------- + +Фикстуры, описанные выше, в основном используются в рамках определенных тест-кейсов. В большинстве случаев, вам также нужны +глобальные фикстры, которые применяются во ВСЕХ или большинстве тест-кейсов. Примером является фикстура [[yii\test\InitDbFixture]], +которая делает 2 вещи: + +* Запускает скрипт `@app/tests/fixtures/initdb.php` для выполнения ряда общих задач инициализации тестового окружения; +* Отключает проверку целостности данных перед загрузкой остальных фикстур, и включает ее обратно после того как все остальные фикстуры будут выгружены. + +Использование глобальных фикстур схоже с использованием не глобальных. Единственное отличие в том, что вы должны объявить эти +фикстуры в методе [[yii\codeception\TestCase::globalFixtures()]], а не `fixtures()`. Когда тест-кейс загружает фикстуры, сначала +загружаются глобальные фикстуры, затем все остальные. + +По умолчанию, фикстура `InitDbFixture` уже обяъвлена в методе `globalFixtures()` класса [[yii\codeception\DbTestCase]]. +Это означает, что вы должны работать только с файлом `@app/tests/fixtures/initdb.php`, если вы хотите чтобы перед каждым тестом +выполнялись определенные подготовительные работы. В противном случае вы просто можете сфокусироваться на разработке +конкретных тест-кейсов и соответствующих фикстур. + + +Организация классов фикстур и файлов с данными +---------------------------------------------- + +По умолчанию, классы фикстур ищут соответствующие файлы данных в директории `data`, которая является подпапкой папки, содержащей +файлы классов фикстур. Вы можете следовать этому соглашению при работе над простыми проектами. Есть вероятность, что на больших +проектах вам потребуется менять набор данных для одного и того же класса фикстур в разных тестах. Таким образом, мы рекомендуем +вам организовать файлы данных иерархически, подобно пространству имен ваших классов. Например, + +``` +# в папке tests\unit\fixtures + +data\ + components\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php + models\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php +# и так далее +``` + +Таким образом вы избежите коллизий файлов данных фикстур между тестами и будете использовать их, как вам нужно. + +> Note: в примере выше файлы данных фикстур названы так только в качестве примера. В реальных жизни вам следует +> называть их в соответствии с тем от какого класса наследуется ваш класс фикстуры. Например, при наследовании от [[yii\test\ActiveFixture]] +> для фикстур БД вам следует использовать имя таблицы в качестве имени файла данных; при наследовании от [[yii\mongodb\ActiveFixture]] +> для фикстур MongoDB вам следует использовать имя коллекции в качестве имени файла. + +Вы можете использовать похожую иерархию для организации файлов классов фикстур. Чтобы избежать конфликта с файлами данных +вы можете использовать в качестве корневой директории `fixtures` вместо `data`. + + +Резюме +------ + +> Note: Этот раздел находится в разработке. + +Выше мы описали как объявлять и использовать фикстуры. Ниже приведен типовой сценарий выполнения модульных тестов, связанных с БД: + +1. Используйте команду `yii migrate` для обновления тестовой БД до последней версии; +2. Выполнить тест-кейс: + - Загрузка фикстур: очищение соответствующих таблиц БД и заполнение их данными фикстур; + - Выполнение теста; + - Выгрузка фикстур. +3. Повторение шага 2 до тех пор, пока не выполнятся все тесты. + + +**Будет доработано** + +Управление фикстурами +===================== + +> Note: Данный раздел находится в разработке. +> +> todo: данный раздел может быть объединен с предыдущими частями test-fixtures.md + +Фикстуры являются важной составляющей тестирования. Их основная задача в предоставлении набора данных, необходимого для тестирования +различных сценариев работы вашего приложения. С этими данными использование ваших тестов становятся более эффективным и полезным. + +Yii поддерживает фикстуры через утилиту командной строки `yii fixture`. Эта утилита поддерживает: + +* Загрузку фикстур в различные хранилища, такие как: RDBMS, NoSQL и другие; +* Выгрузку фикстур разными способами (как правило очищает хранилище); +* Автоматическую генерацию фикстур и наполнение их случайными данными + + +Формат фикстуры +--------------- + +Фикстуры - это объекты с различными методами и конфигурацией, с которыми вы можете ознакомиться в официальной [документации](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md). + +Давайте предположим, что у нас есть следующий набор данных фикстуры для загрузки: + +``` +# файл users.php в директории файлов данных фикстур, по умолчанию @tests\unit\fixtures\data + +return [ + [ + 'name' => 'Chase', + 'login' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + [ + 'name' => 'Celestine', + 'login' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` + +Если вы используете фикстуру, которая загружает данные в базу данных, то эти строки будут применены к таблице `users`. +Если вы используете фикстуру для загрузки данных в nosql, например, фикстура для `mongodb`, то данные будут применены к коллекции `users`. +Для того, чтобы узнать о реализации различных сценариях загрузки фикстур, обратитесь к официальной [документации](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md). +Предыдущий пример фикстуры был сгенерирован автоматически с использованием расширения `yii2-faker`, подробнее про это читайте в этом [разделе](#auto-generating-fixtures). +Имя класса фикстуры должно быть в единственном числе. + +Загрузка фикстур +---------------- + +Класс фикстур должны содержать суффикс `Fixture`. По умолчанию поиск фикстур выполняется в пространстве имен `tests\unit\fixtures`, но вы можете изменить это поведение +через конфигурационный файл или параметры команды. Вы можете исключить некоторые фикстуры из загрузки или выгрузки добавив `-` перед их именем, например `-User`. + +Чтобы загрузить фикстуру, выполните следующую команду:: + +``` +yii fixture/load <fixture_name> +``` + +Обязательный параметр `fixture_name` указываем на имя фикстуры, которая должна быть загружена. Вы можете загрузить несколько фикстур за раз. +Ниже указаны примеры корректного использования данной команды: + +``` +// загрузить фикстуру `User` +yii fixture/load User + +// тоже что и выше, т.к. "load" является действие по умолчанию для команды "fixture" +yii fixture User + +// загрузить нескольких фикстур +yii fixture User UserProfile + +// загрузить все фикстуры +yii fixture/load "*" + +// тоже что и выше +yii fixture "*" + +// загрузить все фикстуры кроме указанной +yii fixture "*" -DoNotLoadThisOne + +// загрузка фикстур, но искать их следует в другом пространстве имен. Пространство имен по умолчанию: tests\unit\fixtures. +yii fixture User --namespace='alias\my\custom\namespace' + +// загрузить глобальную фикстуру `some\name\space\CustomFixture` перед загрузкой остальных фикстур. +// По умолчанию данный параметр установлен в `InitDbFixture` для включения/отключения проверки целостности данных. +// Вы можете задать несколько глобальных фикстур, указав их через запятую +yii fixture User --globalFixtures='some\name\space\Custom' +``` + +Выгрузка фикстур +---------------- + +Для выгрузки фикстур выполните следующую команду: + +``` +// выгрузить фикстуру `Users`, по умолчанию будут удалены все данные из таблицы "users", или из коллекции "users" если это фикстура mongodb +yii fixture/unload User + +// выгрузить несколько фикстур +yii fixture/unload User,UserProfile + +// выгрузить все фикстуры +yii fixture/unload "*" + +// выгрузить все фикстуры за исключением указанной +yii fixture/unload "*" -DoNotUnloadThisOne + +``` + +При выгрузке фикстур вы также можете использовать параметры `namespace` и `globalFixtures`. + +Глобальная настройка команды +---------------------------- + +Хотя параметры командой строки и позволяют нам настраивать команду миграции на лету, иногда нам может понадобиться настроить +команду один раз для всех сценариев запуска. Например, вы можете настроить различные пути до файлов с фикстурами как в примере ниже: + +``` +'controllerMap' => [ + 'fixture' => [ + 'class' => 'yii\console\controllers\FixtureController', + 'namespace' => 'myalias\some\custom\namespace', + 'globalFixtures' => [ + 'some\name\space\Foo', + 'other\name\space\Bar' + ], + ], +] +``` + +Автоматическая генерация фикстур +-------------------------------- + +Yii также может автоматически генерировать для вас фикстуры на основе некоторого шаблона. Вы можете генерировать фикстуры с +различных набором данных на разных языках и в разных форматах. Данная возможность основана на использовании библиотеки [Faker](https://github.com/fzaninotto/Faker) +и расширения `yii2-faker`. + +Для получения дополнительной информации ознакомьтесь с [руководством](https://github.com/yiisoft/yii2-faker). diff --git a/docs/guide-ru/test-functional.md b/docs/guide-ru/test-functional.md new file mode 100644 index 0000000000..3c2fbf7fae --- /dev/null +++ b/docs/guide-ru/test-functional.md @@ -0,0 +1,11 @@ +Функциональные тесты +==================== + +> Note: Данный раздел находится в разработке. + +- [Codeception Functional Tests](http://codeception.com/docs/04-FunctionalTests) + +Запуск функциональных тестов для шаблонов проектов basic и advanced +------------------------------------------------------------------- + +Следуйте инструкциям в `apps/advanced/tests/README.md` и `apps/basic/tests/README.md`. diff --git a/docs/guide-ru/test-overview.md b/docs/guide-ru/test-overview.md new file mode 100644 index 0000000000..9486dcd9eb --- /dev/null +++ b/docs/guide-ru/test-overview.md @@ -0,0 +1,76 @@ +Тестирование +============ + +Тестирование является важной составляющей разработки программного обеспечения. Мы проводим тестирование непрерывно, осознаем мы это или нет. +Например, когда мы пишем класс на языке PHP, мы можем отлаживать его шаг за шагом или просто использовать `echo` или `die` для проверки, что +реализация работает в соответствии с намеченным планом. В случае веб приложения, мы вводим некоторые тестовые данные в форму для того, чтобы +убедиться, что страница взаимодействует с нами, как ожидается. + +Процесс тестирования может быть автоматизирован так, что каждый раз когда нам нужно что-то проверить, мы просто должны +вызвать код, который сделает это за нас. Код, который проверяет, что результат совпадает с тем, что мы планировали, называется тестом, а процесс +создания тестов и их последующего использования - автоматизированным тестированием, что и является главной темой данного раздела. + + +Разработка с тестами +-------------------- + +Разработка через тестирование (TDD) и разработка через поведение (BDD) - это подходы разработки программного обеспечения, в рамках которых +поведение части кода или целая фича описывается в виде набора сценариев или тестов ДО написания фактического кода и только +затем создается реализация. Тем самым мы можем использовать данные тесты для проверки, что достигается заданное поведение. + +Процесс разработки фичи следующий: + +- Создать новый тест, который описывает функцию, которая будет реализована. +- Запустить новый тест и убедиться, что он терпит неудачу. Это ожидаемо, т.к. на данный момент еще нет конкретной реализации. +- Написать простой код, чтобы новый тест отрабатывал без ошибок. +- Запустить все тесты и убедиться, что они отрабатывают без ошибок +- Улучшить код и убедиться, что все тесты все еще отрабатывают без ошибок + +После того как это завершено процесс повторяется снова для другой фичи или улучшения. Если существующая фича должна быть изменена, то и тесты +также должны быть изменены. + +> Tip: Если вы чувствуете, что вы теряете время выполняя много мелких и простых итераций, попробуйте покрыть это +> вашим тестовым сценарием перед следующим выполнением тестов. Если вы слишком много отлаживаете, попробуйте сделать обратное. + +Написание тестов до реализации конкретного функционала позволяет нам сосредоточиться на том, что мы хотим достичь и полностью +погрузиться в "как это сделать" впоследствии. + +Обычно это приводит к лучшим абстракциям и более легкой поддержке тестов, когда речь идет о корректировки фичи или уменьшении связанности компонентов. + +Таким образом плюсы этого подхода следующие: + +- Позволяет вам сосредоточиться на одной вещи, что в свою очередь приводит к улучшению планирования и реализации. +- Более подробное покрытие тестами функционала, таким образом, если все тесты отрабатывают без ошибок, скорее всего, ничего не сломано. + +В долгосрочной перспективе это, как правило, дает вам хороший эффект экономии времени. + +> Tip: Если вы хотите узнать больше о принципах сбора требования программного обеспечения и моделирования +> предметной области, рекомендуем изучить [Проблемно-ориентированное проектирование (DDD)](https://en.wikipedia.org/wiki/Domain-driven_design). + +Когда и как тестировать +----------------------- + +Принцип разработки описанный выше имеет смысл применять для долгосрочных и относительно сложных проектов, в то время как для простых это может быть +излишним. Есть несколько показателей того, когда данный подход уместен: + +- Проект уже большой и сложный. +- Требования к проекту начинают усложняться. Проект постоянно растет. +- Долгосрочный проект. +- Цена ошибки очень высока + +Нет ничего плохого в создании тестов, покрывающих поведение существующей реализации. + +- Legacy-проект который постоянно обновляется. +- Вам поручили работу над проектом, в котором нет ни одного теста. + +В некоторых случаях автоматизированно тестирование может быть излишним: + +- Проект простой и не станет более сложным. +- Это одноразовый проект, который больше не будет дорабатываться. + +Тем не менее, если у вас есть время, было бы хорошо автоматизировать тестирование и в этих случаях. + +Что почитать +------------ + +- Экстремальное программирование. Разработка через тестирование / Кент Бек. ISBN: 0321146530. \ No newline at end of file diff --git a/docs/guide-ru/test-unit.md b/docs/guide-ru/test-unit.md new file mode 100644 index 0000000000..da42fc6932 --- /dev/null +++ b/docs/guide-ru/test-unit.md @@ -0,0 +1,24 @@ +Модульные тесты +=============== + +> Note: Данный раздел находится в разработке. + +Модульный тест проверяет что отдельный модуль кода работает верно. В ООП самым базовым модулем является класс. То есть +модульный тест проверяет все методы интерфейса класса. На вход подаются различные параметры и тест проверяет, что методы +возвращают ожидаемые значения. Модульные тесты обычно пишутся тем же, кто реализует тестируемый класс. + +Модульное тестирование в Yii использует PHPUnit и, опционально, Codeception. Рекомендуется проверить его документацию: + +- [Документация PHPUnit начиная с главы 2](http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html). +- [Codeception Unit Tests](http://codeception.com/docs/05-UnitTests). + +Запуск тестов шаблонов проектов basic и advanced +------------------------------------------------ + +Следуйте инструкциям в `apps/advanced/tests/README.md` и `apps/basic/tests/README.md`. + +Модульные тесты фреймворка +-------------------------- + +Если вам необходимо запустить набор модульных тестов для самого Yii, прочитайте +"[Подготовка к разработке Yii2](https://github.com/yiisoft/yii2/blob/master/docs/internals-ru/getting-started.md)". diff --git a/docs/guide-ru/tutorial-console.md b/docs/guide-ru/tutorial-console.md new file mode 100644 index 0000000000..0b2ceb10e1 --- /dev/null +++ b/docs/guide-ru/tutorial-console.md @@ -0,0 +1,210 @@ +Консольное приложение +===================== + +Кроме богатых возможностей для построения веб приложений, Yii также имеет полноценную поддержку консольных приложений, +которые обычно используются для создания фоновых и служебных задач, поддерживающих сайт. + +Структура консольных приложений очень похожа на структуру веб приложения. Она состоит из одного и более классов +[[yii\console\Controller]], которые часто называют командами в консольной среде. Каждый контроллер может иметь одно +или более действий, как и веб контроллеры. + +В обоих шаблонах проектов уже есть консольное приложение. +Вы можете запустить его, вызвав скрипт yii, который находится в основной директории вашего приложения. +Вы получите список доступных команд, если вызовете его без параметров: + +![Запуск команды ./yii для вывода помощи](images/tutorial-console-help.png) + +Как вы можете видеть на скриншоте, в Yii уже определён набор доступных по умолчанию команд: + +- [[yii\console\controllers\AssetController|AssetController]] - Позволяет вам объединять и сжимать ваши JavaScript и CSS файлы. + Больше об этой команде вы можете узнать в [Assets Section](structure-assets.md#using-the-asset-command). +- [[yii\console\controllers\CacheController|CacheController]] - Позволяет вам сбрасывать кеш приложения. +- [[yii\console\controllers\FixtureController|FixtureController]] - Управляет загрузкой и выгрузкой данных фикстур для тестирования. + Данная команда более подробно описана в [Testing Section about Fixtures](test-fixtures.md#managing-fixtures). +- [[yii\console\controllers\HelpController|HelpController]] - Обеспечивает справочную информацию о консольных командах, + это команда по умолчанию и она печатает текст, который вы видели выше. +- [[yii\console\controllers\MessageController|MessageController]] - Извлекает сообщения для перевода из файлов с исходными тестами. + Больше об этой команде вы можете узнать в [I18N Section](tutorial-i18n.md#message-command). +- [[yii\console\controllers\MigrateController|MigrateController]] - Управление миграциями приложения. + Миграции базы данных более детально описаны в [Database Migration Section](db-migrations.md). +- [[yii\console\controllers\ServeController|ServeController]] - Позволяет запускать встроенный вебсервер PHP. + + +Использование <span id="usage"></span> +------------- + +Вы можете запустить действие консольного контроллера, используя следующий синтаксис: + +``` +yii <route> [--option1=value1 --option2=value2 ... argument1 argument2 ...] +``` + +В приведённом выше примере, `<route>` относится к действию контроллера. Параметры будут подставляться в свойства +класса и в аргументы метода действия. + +Для примера, [[yii\console\controllers\MigrateController::actionUp()|MigrateController::actionUp()]] +с [[yii\console\controllers\MigrateController::$migrationTable|MigrateController::$migrationTable]] установкой `migrations` +и лимитом в 5 миграций может быть вызвано следующим образом: + +``` +yii migrate/up 5 --migrationTable=migrations +``` + +> Note: При использовании в консоли `*`, не забудьте поместить её в кавычки `"*"` чтобы избежать её интерпретации +> и замены на все имена файлов в данной директории. + + +Входной скрипт <span id="entry-script"></span> +-------------- + +Входной скрипт консольного приложения - это подобие файла `index.php`, используемого в веб приложении. +Входной скрипт консоли, как правило, называется `yii` и располагается в основной директории приложения. +Он содержит код похожий на следующее: + +```php +#!/usr/bin/env php +<?php +/** + * Yii console bootstrap file. + */ + +defined('YII_DEBUG') or define('YII_DEBUG', true); + +require(__DIR__ . '/vendor/autoload.php'); +require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php'); + +$config = require(__DIR__ . '/config/console.php'); + +$application = new yii\console\Application($config); +$exitCode = $application->run(); +exit($exitCode); +``` + +Этот скрипт будет создан как часть вашего приложения; вы можете его редактировать, если вам это необходимо. +`YII_DEBUG` можете установить в `false` если вам не нужно видеть отладочный вывод при ошибке, и/или если вы хотите +улучшить общую производительность. В обоих шаблонах приложения, во входном скрипте приложения отладка включена по +умолчанию для обеспечения более дружественного к разработчику окружения. + + +Настройка <span id="configuration"></span> +--------- + +Как видно из приведённого выше кода, консольное приложение использует свой собственный файл конфигурации, названый `console.php`. +В этом файле вы должны произвести настройку различных [компонентов приложения](structure-application-components.md) и +свойств консольного приложения. + +Если ваше веб и консольное приложение имеет много общих параметров конфигурации, вы можете выделить общую часть в +отдельный файл, и включить его в оба файла конфигурации (веб и консоль). +Вы можете посмотреть пример в "продвинутом" шаблоне проекта. + +> Tip: Иногда, вам может потребоваться запустить консольную команду используя конфигурацию, отличную от той, что +> указано во входном скрипте. Для примера, вы можете использовать команду `yii migrate` для обновления тестовой +> базы данных, которая настраивается для каждого отдельного набора тестов. Для изменения файла конфигурации, +> просто укажите свой конфигурационный файл через опцию `appconfig` при запуске команды: +> +> ``` +> yii <route> --appconfig=path/to/config.php ... +> ``` + + +Создание ваших собственных команд <span id="create-command"></span> +---------------------------------- + +### Консольный контроллер и действие + +Консольная команда определяется как класс контроллера расширяющий [[yii\console\Controller]]. В классе контроллера, +вы определяете одно или несколько действий, которые соответствуют суб-командам контроллера. В каждом действии +вы пишете код, который реализует соответствующие данной суб-команде задачи. + +При запуске команды, вам необходимо указать маршрут к действию. Например, маршрут `migrate/create` вызывает суб-команду, +которая соответствует методу [[yii\console\controllers\MigrateController::actionCreate()|MigrateController::actionCreate()]]. +Если маршрут, предложенный при вызове команды, не содержит указания идентификатора действия, будет вызвано действие +по умолчанию (так же как и в веб приложении). + +### Опции + +Для переопределения [[yii\console\Controller::options()]] метода, вы можете указать опции, которые доступны в консольной +команде (controller/actionID). Метод должен возвращать список публичных атрибутов класса. При запуске команды вы +можете указать значение опций, используя синтаксис `--OptionName=OptionValue`. Это свяжет `OptionValue` с атрибутом +`OptionName` класса контроллера. + +Если значение по умолчанию опции - это массив, то при установке этой опции, при выполнении команды, значение будет +преобразовано в массив путём разделения входящей строки по запятым. + +### Аргументы + +Кроме опций, команда может получать аргументы. Аргументы будут переданы в качестве параметров в метод действия, +соответствующего запрошенной суб-команде. Первый аргумент соответствует первому параметру, второй соответственно второму, +и так далее. Если переданных аргументов при вызове команды будет недостаточно, то параметрам будут назначены по +умолчанию, если они определены. Если значения по умолчанию не определены, и не были переданы, команда завершит +выполнение с ошибкой. + +Вы можете использовать указание типа `array`, чтобы указать, что аргумент должен рассматриваться как массив. Массив +будет сгенерирован путём разделения входной строки по запятым. + +Следующий пример показывает как описывать аргументы: + +```php +class ExampleController extends \yii\console\Controller +{ + // Команда "yii example/create test" вызовет "actionCreate('test')" + public function actionCreate($name) { ... } + + // Команда "yii example/index city" вызовет "actionIndex('city', 'name')" + // Команда "yii example/index city id" вызовет "actionIndex('city', 'id')" + public function actionIndex($category, $order = 'name') { ... } + + // Команда "yii example/add test" вызовет "actionAdd(['test'])" + // Команда "yii example/add test1,test2" вызовет "actionAdd(['test1', 'test2'])" + public function actionAdd(array $name) { ... } +} +``` + + +### Код возврата + +При разработке консольного приложения принято использовать код возврата. Принято, код `0` означает, что команда выполнилась +удачно. Если команда вернула код больше нуля, то это говорит об ошибке. Номер, который был возвращён при ошибке, +потенциально может быть использован для поиска более детальной информации об ошибке. +Для примера `1` может указывать на неизвестную ошибку, а все коды выше могут быть зарезервированы под специфичные +ошибки: ошибки ввода, повреждённые файлы, и что-то другое. + +Для того, чтобы ваша консольная команда возвращала код возврата, просто верните целое число в методе действия контроллера: + +```php +public function actionIndex() +{ + if (/* возникла проблема */) { + echo "Возникла проблема!\n"; + return 1; + } + // делаем что-нибудь + return 0; +} +``` + +Есть несколько предопределённых констант, которые вы можете использовать: + +- [[yii\console\Controller::EXIT_CODE_NORMAL|Controller::EXIT_CODE_NORMAL]] со значением `0`; +- [[yii\console\Controller::EXIT_CODE_ERROR|Controller::EXIT_CODE_ERROR]] со значением `1`. + +Хорошая практика, определять значимые для вашего контроллера константы в случае, если вы используете больше типов ошибок. + +### Форматирование и цвета + +Консоль Yii поддерживает форматирование вывода, который автоматически деградирует до не форматированного, если это не поддерживается +в терминале, где запускается команда. + +Вывод форматированных строк прост. Вот как можно вывести некоторый жирный текст: + +```php +$this->stdout("Hello?\n", Console::BOLD); +``` + +Если вам нужно собрать строку динамически объединяя несколько стилей, лучше использовать +[[yii\helpers\Console::ansiFormat()|ansiFormat()]]: + +```php +$name = $this->ansiFormat('Alex', Console::FG_YELLOW); +echo "Hello, my name is $name."; +``` diff --git a/docs/guide-ru/tutorial-core-validators.md b/docs/guide-ru/tutorial-core-validators.md index 9233c84d95..eac8a54d58 100644 --- a/docs/guide-ru/tutorial-core-validators.md +++ b/docs/guide-ru/tutorial-core-validators.md @@ -37,7 +37,7 @@ public function rules() - `falseValue`: значение, соответствующее *false*. По умолчанию - `'0'`. - `strict`: должна ли проверка учитывать соответствие типов данных `trueValue` или `falseValue`. По умолчанию - `false`. -> Примечание: Из-за того, что как правило данные, полученные из HTML-форм, представляются в виде строки, обычно вам стоит +> Note: Из-за того, что как правило данные, полученные из HTML-форм, представляются в виде строки, обычно вам стоит оставить свойство [[yii\validators\BooleanValidator::strict|strict]] равным false. @@ -140,7 +140,7 @@ function foo($model, $attribute) { } ``` -> Информация: Как определить, является значение пустым или нет, более подробно описано в отдельной статье +> Info: Как определить, является значение пустым или нет, более подробно описано в отдельной статье в секции [Пустые значения](input-validation.md#handling-empty-inputs). @@ -228,11 +228,15 @@ function foo($model, $attribute) { [ // проверяет, что "primaryImage" - это загруженное изображение в формате PNG, JPG или GIF // размер файла должен быть меньше 1MB +<<<<<<< HEAD <<<<<<< HEAD ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024], ======= ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024], >>>>>>> yiichina/master +======= + ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024], +>>>>>>> master ] ``` @@ -244,7 +248,10 @@ function foo($model, $attribute) { допустимы. - `mimeTypes`: список MIME-типов, которые допустимы для загрузки. Это может быть или массив, или строка, содержащая MIME-типы файлов, разделенные пробелом или запятой (пример: "image/jpeg, image/png"). - Имена mime-типов не чувствительны к регистру. По умолчанию - null, что значит, что допустимы все MIME-типы. + В именах MIME-типов допустимо использовать символ `*` для выбора группы mime-типов. + Например, `image/*` разрешит все типы, которы начинаются с `image/` (пример: `image/jpeg`, `image/png`). + Имена MIME-типов не чувствительны к регистру. По умолчанию - null, что значит, что допустимы все MIME-типы. + Более детальную информацию можно найти в [списке MIME-типов](https://ru.wikipedia.org/wiki/Список_MIME-типов). - `minSize`: минимальный размер файла в байтах, разрешенный для загрузки. По умолчанию - null, что значит, что нет минимального лимита. - `maxSize`: максимальный размер файла в байтах, разрешенный для загрузки. По умолчанию - null, что значит, что нет @@ -308,6 +315,78 @@ function foo($model, $attribute) { - `maxHeight`: максимальная высота изображения. По умолчанию null, что значит, что нет верхнего лимита. +## [[yii\validators\IpValidator|ip]] <span id="ip"></span> +```php +[ + // проверяет, что "ip_address" - это валидный IPv4 или IPv6 адрес + ['ip_address', 'ip'], + + + // проверяет, что "ip_address" - это валидный IPv6 адрес или подсеть, + // значение будет развернуто в формат полной записи IPv6 адреса + ['ip_address', 'ip', 'ipv4' => false, 'subnet' => null, 'expandIPv6' => true], + + // проверяет, что "ip_address" - это валидный IPv4 или IPv6 адрес, + // разрешает использования символа отрацания `!` + ['ip_address', 'ip', 'negation' => true], +] +``` + +Этот валидатор проверяет, является ли входящее значение валидным IPv4/IPv6 адресом или подсетью. +Он также может изменять значение атрибута, если включена нормализация или развертывание IPv6 адресов. + +Валидатор имеет такие свойства: + +- `ipv4`: может ли проверяемое значение быть IPv4 адрессом. По умолчанию true. +- `ipv6`: может ли проверяемое значение быть IPv6 адрессом. По умолчанию true. +- `subnet`: может ли проверяемое значение быть IP адресом с CIDR (подсетью), например `192.168.10.0/24` + * `true` - указание подсети обязательно; + * `false` - указание подсети запрещено; + * `null` - указание подсети возможно, но не обязательно. + + По умолчанию false. +- `normalize`: нормализировать ли проверяемый IP адрес без CIDR к IP адресу с наименьшим CIDR +(32 для IPv4 или 128 для IPv6). Свойство действует только если `subnet` не установлен в `false`. Например: + * `10.0.1.5` будет приведен к `10.0.1.5/32` + * `2008:db0::1` будет приведен к `2008:db0::1/128` +- `negation`: может ли проверяемое значение иметь символ отрицания `!` в начале, например `!192.168.0.1`. +По умолчанию false. +- `expandIPv6`: разворачивать ли IPv6 адрес в формат полной записи. +Например, IPv6 адрес `2008:db0::1` будет развернут в `2008:0db0:0000:0000:0000:0000:0000:0001`. По умолчанию false. +- `ranges`: массив IPv4 или IPv6 диапазонов, которые разрешены или запрещены. + + Если свойство не установлено, все IP адреса разрешены. Иначе, правила будут проверяться последовательно до первого +вхождения. IP адрес будет запрещен, если не подпадет ни под одно правило. Например: + ```php + [ + 'client_ip', 'ip', 'ranges' => [ + '192.168.10.128' + '!192.168.10.0/24', + 'any' // разрешает все остальные IP адреса + ] + ] + ``` +В этом примере, доступ разрешен для всех IPv4 и IPv6 адресов кроме подсети `192.168.10.0/24`. +IPv4 адрес `192.168.10.128` также разрешен, так как находится перед запрещающим правилом. +- `networks`: массив псевдониимов, которые могут быть использованы в `ranges`. Формат массива: + * ключ - имя псевдонима + * значение - массив строк. Строка может быть как диапазоном адресов, так и другим псведонимом. Строка может иметь + символ отрицания `!` в начале (не зависит от свойства `negation`). + + Следующие псевдонимы определены по умолчанию: + + * `*`: `any` + * `any`: `0.0.0.0/0, ::/0` + * `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8` + * `multicast`: `224.0.0.0/4, ff00::/8` + * `linklocal`: `169.254.0.0/16, fe80::/10` + * `localhost`: `127.0.0.0/8', ::1` + * `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32` + * `system`: `multicast, linklocal, localhost, documentation` + +> Info: Этот валидатор стал доступным начиная с версии 2.0.7. + + ## [[yii\validators\RangeValidator|in]] <span id="in"></span> ```php @@ -317,7 +396,7 @@ function foo($model, $attribute) { ] ``` -Этот валидатор проверяет, что вхоящее значение соответствует одному из значений, указанных в `range`. +Этот валидатор проверяет, что входящее значение соответствует одному из значений, указанных в `range`. - `range`: список значений, с которыми будет сравниваться входящее значение. - `strict`: должно ли сравнение входящего значения со списком значений быть строгим (учитывать тип данных). @@ -398,7 +477,7 @@ function foo($model, $attribute) { Если `requiredValue` установлено, сравнение между входящими данными и `requiredValue` будет также учитывать тип данных, если это свойство установлено в true. -> Информация: как определить, является ли значение пустым или нет, подробнее рассказывается +> Info: как определить, является ли значение пустым или нет, подробнее рассказывается в секции [Пустые значения](input-validation.md#handling-empty-inputs). diff --git a/docs/guide-ru/tutorial-i18n.md b/docs/guide-ru/tutorial-i18n.md index f98e500e2a..ee7d67d9d7 100644 --- a/docs/guide-ru/tutorial-i18n.md +++ b/docs/guide-ru/tutorial-i18n.md @@ -1,7 +1,7 @@ Интернационализация ==================== -> Примечание: Этот раздел находится в разработке +> Note: Этот раздел находится в разработке Интернационализация (I18N) является частью процесса разработки приложения, которое может быть адаптировано для нескольких языков без изменения программной логики. Это особенно важно для веб-приложений, так как потенциальные @@ -13,7 +13,7 @@ Yii располагает несколькими средствами, приз Локализация и языки ------------------- -В Yii приложении определены два языка: [[yii\base\Application::$sourceLanguage|исходный язык]] н [[yii\base\ +В Yii приложении определены два языка: [[yii\base\Application::$sourceLanguage|исходный язык]] и [[yii\base\ Application::$language|язык перевода]]. На "исходном языке" написаны сообщения в коде приложения. Если мы определяем исходным языком английский, то @@ -36,7 +36,7 @@ return [ ] ``` -> **Подсказка**: значение по умолчанию для [[yii\base\Application::$sourceLanguage|исходного языка]] - английский. +> Tip: значение по умолчанию для [[yii\base\Application::$sourceLanguage|исходного языка]] - английский. Вы можете установить значение текущего языка в самом приложении в соответствии с языком, который выбрал пользователь. Это необходимо сделать до того, как будет сгенерирован какой-либо вывод, чтобы не возникло проблем с его @@ -50,7 +50,7 @@ return [ соответствии со стандартом [ISO-639](http://www.loc.gov/standards/iso639-2/), а `CC` - это код страны в соответствии со стандартом [ISO-3166](http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html). -> **Примечание**: больше информации о синтаксисе и концепции локалей можно получить в [документации проекта ICU](http://userguide.icu-project.org/locale#TOC-The-Locale-Concept). +> Note: больше информации о синтаксисе и концепции локалей можно получить в [документации проекта ICU](http://userguide.icu-project.org/locale#TOC-The-Locale-Concept). Перевод сообщений ----------------- @@ -129,7 +129,7 @@ $sum = 42; echo \Yii::t('app', 'Balance: {0}', $sum); ``` -> **Подсказка**: старайтесь сохранять читаемость сообщений и избегать избыточного использования позиционных +> Tip: старайтесь сохранять читаемость сообщений и избегать избыточного использования позиционных параметров. Помните, что переводчик, скорее всего, будет располагать только файлом со строками и для него должно быть очевидно, на что будет заменён тот или иной указатель. @@ -180,7 +180,7 @@ echo \Yii::t('app', 'Today is {0, date, short}', time()); Используя свой формат: ```php -echo \Yii::t('app', 'Today is {0, date, yyyy-MM-dd}', time()); +echo \Yii::t('app', 'Today is {0, date,yyyy-MM-dd}', time()); ``` [Описание форматирования](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). @@ -200,7 +200,7 @@ echo \Yii::t('app', 'It is {0, time, short}', time()); Используя свой формат: ```php -echo \Yii::t('app', 'It is {0, date, HH:mm}', time()); +echo \Yii::t('app', 'It is {0, date,HH:mm}', time()); ``` [Описание форматирования](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). @@ -415,7 +415,7 @@ class Menu extends Widget Вместо использования `fileMap`, вы можете прибегнуть к соглашению, что имя категории совпадает с именем файла и писать `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` напрямую. -> **Примечание**: для виджетов вы можете использовать i18n представления. На них распространяются +> Note: для виджетов вы можете использовать i18n представления. На них распространяются > те же правила, что и на контроллеры. @@ -447,7 +447,7 @@ Yii поставляется с набором сообщений по умол когда источник не содержит искомой строки. Для этого следует использовать обработку события [[yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION|missingTranslation]] компонента [[yii\i18n\MessageSource]]. -Например, чтобы отметить все непереведённые строки, чтобы их было легче находить на странице, необходимо +Например, чтобы отметить все не переведённые строки, чтобы их было легче находить на странице, необходимо создать обработчик события. Изменим конфигурацию приложения: ```php @@ -479,11 +479,15 @@ use yii\i18n\MissingTranslationEvent; class TranslationEventHandler { +<<<<<<< HEAD <<<<<<< HEAD public static function(MissingTranslationEvent $event) { ======= public static function handleMissingTranslation(MissingTranslationEvent $event) { >>>>>>> yiichina/master +======= + public static function handleMissingTranslation(MissingTranslationEvent $event) { +>>>>>>> master $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @"; } } @@ -492,7 +496,7 @@ class TranslationEventHandler Если [[yii\i18n\MissingTranslationEvent::translatedMessage]] установлен, как обработчик события, на странице будет выведен соответствующий результат перевода. -> Внимание: каждый источник обрабатывает недостающие переводы самостоятельно. Если вы используете несколько разных +> Warning: каждый источник обрабатывает недостающие переводы самостоятельно. Если вы используете несколько разных > источников сообщений и хотите обрабатывать недостающие переводы одинаково для всех, назначьте соответствующий > обработчик события для каждого источника. @@ -506,18 +510,22 @@ class TranslationEventHandler текущего контроллера или виджета и создайте файл для русского языка: `views/site/ru-RU/index.php`. Yii загрузит файл для текущего языка, если он существует, или использует исходный `views/site/index.php`, если не сможет найти локализацию. -> **Примечание**: если язык был определён, как `en-US` и соответствующих представлений не было найдено, Yii попробует +> Note: если язык был определён, как `en-US` и соответствующих представлений не было найдено, Yii попробует > найти представления в папке `en` перед тем, как использовать исходные. Форматирование чисел и дат -------------------------- +<<<<<<< HEAD <<<<<<< HEAD См. описание [форматирования дат](output-formatter.md). ======= См. описание [форматирования дат](output-formatting.md). >>>>>>> yiichina/master +======= +См. описание [форматирования дат](output-formatting.md). +>>>>>>> master Настройка PHP-окружения <span id="setup-environment"></span> @@ -539,7 +547,7 @@ class TranslationEventHandler Чтобы узнать, какая версия ICU используется текущим PHP интерпретатором, используйте следующий скрипт: -``` +```php <?php echo "PHP: " . PHP_VERSION . "\n"; echo "ICU: " . INTL_ICU_VERSION . "\n"; @@ -548,4 +556,9 @@ echo "ICU: " . INTL_ICU_VERSION . "\n"; Чтобы иметь доступ ко всем возможностям, описанным в документации, мы рекомендуем использовать ICU версии 49 или новее. В более ранних версиях отсутствует указатель `#` в правилах склонений. На сайте <http://site.icu-project.org/download> вы можете ознакомиться со списком доступных версий ICU. Обратите внимание, что схема нумерации версий изменилась после -версии 4.8 и последовательность версий выглядит так: ICU 4.8, ICU 49, ICU 50. +версии 4.8 и последовательность версий выглядит так: ICU 4.8, ICU 49, ICU 50, ICU 51 и так далее. + +Известные проблемы <span id="known-problems"></span> +-------------------------------------------------------- + + - В ICU версии 52.1 было испорчено форматирование множественных чисел (`plural`) в русском языке. Проблема решается обновлением ICU до версии 53.1 или старше. diff --git a/docs/guide-ru/tutorial-mailing.md b/docs/guide-ru/tutorial-mailing.md new file mode 100644 index 0000000000..bcc4d669e6 --- /dev/null +++ b/docs/guide-ru/tutorial-mailing.md @@ -0,0 +1,222 @@ +Отправка почты +======= + +> Note: Этот раздел находиться в стадии разработки. + +Yii позволяет оформлять и посылать E-mail сообщения. Однако, ядро фреимворка предоставляет только +функциональность оформления и основной интерфейс. Фактический механизм отправки почты должен быть предоставлен с помощью расширения, потому что различным проектам могут потребоваться различные реализации и обычно они зависят от внешних сервисов и бибилотек. + +Для наиболее распространенных ситуаций вы можете использовать официальное расширение [yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer). + + +Настройка +------------- + +Настройка почтового компонента зависит от расширения, которое вы выбрали. +В целом настройка вашего приложения должна выглядеть так: + +```php +return [ + //.... + 'components' => [ + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + + +Основы использования +----------- + +Когда 'mailer' компонент настроен, вы можете использовать следующий код, чтобы отправить почтовое сообщение: + +```php +Yii::$app->mailer->compose() + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Тема сообщения') + ->setTextBody('Текст сообщения') + ->setHtmlBody('<b>текст сообщения в формате HTML</b>') + ->send(); +``` + +В показанном выше примере метод `compose()` создает экземпляр почтового сообщения, который затем заполняется и отправляется. +Вы можете использовать более сложную логику в этом процессе, если вам понадобится: + +```php +$message = Yii::$app->mailer->compose(); +if (Yii::$app->user->isGuest) { + $message->setFrom('from@domain.com') +} else { + $message->setFrom(Yii::$app->user->identity->email) +} +$message->setTo(Yii::$app->params['adminEmail']) + ->setSubject('Тема сообщения') + ->setTextBody('Текст сообщения') + ->send(); +``` + +> Note: каждое 'mailer' расширение имеет два главных класса: 'Mailer' и 'Message'. 'Mailer' всегда знает имя класса и специфику 'Message'. Не пытайтесь создать экземпляр объекта 'Message' напрямую - + всегда используйте для этого метод `compose()`. + +Вы также можете послать несколько сообщений за раз: + +```php +$messages = []; +foreach ($users as $user) { + $messages[] = Yii::$app->mailer->compose() + // ... + ->setTo($user->email); +} +Yii::$app->mailer->sendMultiple($messages); +``` + +В некоторых почтовых расширениях этот подход может быть полезен, так как использует одиночное сетевое сообщение. + + +Компоновка почтовых сообщений +---------------------- + +Yii предоставляет возможность оформления содержания почтовых сообщений через специальные файлы виды. +По умолчанию эти файлы должны быть расположены в директории '@app/mail'. + +Пример содержания почтового файла вида: + +```php +<?php +use yii\helpers\Html; +use yii\helpers\Url; + + +/* @var $this \yii\web\View view component instance */ +/* @var $message \yii\mail\BaseMessage instance of newly created mail message */ + +?> +<h2>This message allows you to visit our site home page by one click</h2> +<?= Html::a('Go to home page', Url::home('http')) ?> +``` + +Для того, чтобы оформить содержание сообщения через файл вида, просто передайте название файла вида в `compose()` метод: + +```php +Yii::$app->mailer->compose('home-link') // здесь устанавливается результат рендеринга вида в тело сообщения + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Message subject') + ->send(); +``` + +Вы можете передать допольнительный параметр, относящийся к виду в `compose()` метод, который будет доступен внутри файла вида: + +```php +Yii::$app->mailer->compose('greetings', [ + 'user' => Yii::$app->user->identity, + 'advertisement' => $adContent, +]); +``` + +Вы можете указать разные файлы видов для HTML и простого текста в содержании сообщения: + +```php +Yii::$app->mailer->compose([ + 'html' => 'contact-html', + 'text' => 'contact-text', +]); +``` + +Если вы укажете название вида как строку, результат рендеринга в теле сообщения будет использоваться как HTML, в то время как при обычном тексте в теле сообщения при компоновке будут удаляться все HTML теги. + +Результат рендеринга вида может быть вставлен в макет (layout), который может быть установлен, используя [[yii\mail\BaseMailer::htmlLayout]] +и [[yii\mail\BaseMailer::textLayout]]. Это будет работать аналогично макетам в обычном веб приложении. +Макет может использовать CSS стили или другие общие элементы страниц для использования в сообщении: + +```php +<?php +use yii\helpers\Html; + +/* @var $this \yii\web\View view component instance */ +/* @var $message \yii\mail\MessageInterface the message being composed */ +/* @var $content string main view render result */ +?> +<?php $this->beginPage() ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" /> + <style type="text/css"> + .heading {...} + .list {...} + .footer {...} + </style> + <?php $this->head() ?> +</head> +<body> + <?php $this->beginBody() ?> + <?= $content ?> + <div class="footer">With kind regards, <?= Yii::$app->name ?> team</div> + <?php $this->endBody() ?> +</body> +</html> +<?php $this->endPage() ?> +``` + + +Прикрепление файлов +--------------- + +Вы можете прикрепить вложения к сообщению с помощью методов `attach()` и `attachContent()`: + +```php +$message = Yii::$app->mailer->compose(); + +// Прикрепление файла из локальной файловой системы: +$message->attach('/path/to/source/file.pdf'); + +// Прикрепить файл на лету +$message->attachContent('Attachment content', ['fileName' => 'attach.txt', 'contentType' => 'text/plain']); +``` + + +Вложение изображений +---------------- + +Вы можете вставить изображения в содержание сообщения через `embed()` метод. Этот метод возвращает id прикрепленной картинки, +которые должны быть доступны в 'img' тегах. +Этот метод легко использовать, когда сообщение составляется через файлы представления: + +```php +Yii::$app->mailer->compose('embed-email', ['imageFileName' => '/path/to/image.jpg']) + // ... + ->send(); +``` + +Внутри файла представления вы можете использовать следующий код: + +```php +<img src="<?= $message->embed($imageFileName); ?>"> +``` + + +Тестирование и отладка +--------------------- + +Разработчикам часто надо проверять, что почтовые сообщения отправляются из приложения, их содержание и так далее. +Такая возможность предоставляется в Yii через `yii\mail\BaseMailer::useFileTransport`. Если это опция включена, то она принудительно сохраняет данные почтовых сообщений в локальный файл вместо его отправки. Эти файлы будут сохранены в директории +`yii\mail\BaseMailer::fileTransportPath`, которая по умолчанию '@runtime/mail'. + +> Note: вы можете либо сохранить сообщения в файл, либо послать его фактическим получателям, но не используйте оба варианта одновременно. + +Файл почтового сообщения может быть открыт обычным текстовым редактором, также вы можете просматривать фактические заголовки сообщений, их содержание и так далее. +Этот механизм может понадобиться во время отладки приложения, либо прогонки unit тестов. + +> Note: содержание файла почтового сообщения формируется через `\yii\mail\MessageInterface::toString()`, правда это зависит от почтового расширения, которое вы используете в своем приложении. + + +Создание вашего собственного решения +------------------------------- + +Для того, чтобы создать свое собственное решение, вам надо будет создать два класса: одно для 'Mailer' и другое для 'Message'. +Вы можете использовать `yii\mail\BaseMailer` и `yii\mail\BaseMessage` как базовые классы для вашего решения. Эти классы уже содержат базовую логику, которая описана в этом руководстве. Однако, их испольщование не обязательно, достаточно унаследоваться от `yii\mail\MailerInterface` и `yii\mail\MessageInterface` интерфейсов. +Затем вам необходимо обеспечить выполнение всех абстрактных методов этих интерфейсов для построения вашего решения. diff --git a/docs/guide-ru/tutorial-performance-tuning.md b/docs/guide-ru/tutorial-performance-tuning.md new file mode 100644 index 0000000000..7da065ab83 --- /dev/null +++ b/docs/guide-ru/tutorial-performance-tuning.md @@ -0,0 +1,216 @@ +Оптимизация производительности +================== + +Существует много факторов, влияющих на производительность веб-приложения. Какие-то относятся к окружению, какие-то +к вашему коду, а какие-то к самому Yii. В этом разделе мы перечислим большинство из них и объясним, как можно улучшить +производительность приложения, регулируя эти факторы. + + +## Оптимизация окружения PHP <span id="optimizing-php"></span> + +Хорошо сконфигурированное окружение PHP очень важно. Для получения максимальной производительности, + +- Используйте последнюю стабильную версию PHP. Мажорные релизы PHP могут принести значительные улучшения производительности. +- Включите кеширование байткода в [Opcache](http://php.net/opcache) (PHP 5.5 и старше) или [APC](http://ru2.php.net/apc) + (PHP 5.4 и более ранние версии). Кеширование байткода позволяет избежать затрат времени на обработку и подключение PHP + скриптов при каждом входящем запросе. + +## Отключение режима отладки <span id="disable-debug"></span> + +При запуске приложения в производственном режиме, вам нужно отключить режим отладки. Yii использует значение константы +`YII_DEBUG` чтобы указать, следует ли включить режим отладки. Когда режим отладки включен, Yii тратит дополнительное +время чтобы создать и записать отладочную информацию. + +Вы можете разместить следующую строку кода в начале [входного скрипта](structure-entry-scripts.md) чтобы +отключить режим отладки: + +```php +defined('YII_DEBUG') or define('YII_DEBUG', false); +``` + +> Info: Значение по умолчанию для константы `YII_DEBUG` — false. +Так что, если вы уверены, что не изменяете значение по умолчанию где-то в коде приложения, можете просто удалить эту +строку, чтобы отключить режим отладки. + + +## Использование техник кеширования <span id="using-caching"></span> + +Вы можете использовать различные техники кеширования чтобы значительно улучшить производительность вашего приложения. +Например, если ваше приложение позволяет пользователям вводить текст в формате Markdown, вы можете закешировать +разобранное содержимого Markdown, чтобы избежать разбора одной и той же разметки Markdown неоднократно +при каждом запросе. Пожалуйста, обратитесь к разделу [Кеширование](caching-overview.md) чтобы узнать о поддержке +кеширования, которую предоставляет Yii. + + +## Включение кеширования схемы <span id="enable-schema-caching"></span> + +Кэширование схемы - это специальный *тип кеширования*, который должен быть включен при использовании [Active Record](db-active-record.md). +Как вы знаете, Active Record достаточно умен, чтобы обнаружить информацию о схеме (например, имена столбцов, типы столбцов, +ограничения) таблицы БД без необходимости описывать ее вручную. Active Record получает эту информацию, выполняя +дополнительные SQL запросы. При включении кэширования схемы, полученная информация о схеме будет сохранена в кэше и +повторно использована при последующих запросах. + +Чтобы включить кеширование схемы, сконфигурируйте [компонент приложения](structure-application-components.md) `cache` +для хранения информации о схеме и установите [[yii\db\Connection::enableSchemaCache]] в `true` в [конфигурации приложения](concept-configurations.md): + +```php +return [ + // ... + 'components' => [ + // ... + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=mydatabase', + 'username' => 'root', + 'password' => '', + 'enableSchemaCache' => true, + + // Продолжительность кеширования схемы. + 'schemaCacheDuration' => 3600, + + // Название компонента кеша, используемого для хранения информации о схеме + 'schemaCache' => 'cache', + ], + ], +]; +``` + + +## Объединение и минимизация ресурсов <span id="optimizing-assets"></span> + +Сложные веб-страницы часто подключают много CSS и/или JavaScript файлов. Для уменьшения числа HTTP запросов +и общего размера загрузки этих ресурсов, вы должны рассмотреть вопрос об их объединении в один файл и его сжатии. +Это может сильно увеличить скорость загрузки страницы и снизить нагрузку на сервер. Для получения более подробной +информации обратитесь, пожалуйста, к разделу [Ресурсы](structure-assets.md) + + +## Оптимизация хранилища сессий <span id="optimizing-session"></span> + +По умолчанию данные сессий хранятся в файлах. Это удобно для разработки или в маленьких проектах. +Но когда дело доходит до обработки множества параллельных запросов, то лучше использовать более сложные хранилища, +такие как базы данных. Yii поддерживает различные хранилища "из коробки". +Вы можете использовать эти хранилища, сконфигурировав компонент `session` в +[конфигурации приложения](concept-configurations.md) как показано ниже, + +```php +return [ + // ... + 'components' => [ + 'session' => [ + 'class' => 'yii\web\DbSession', + + // Установите следующее, если вы хотите использовать компонент БД, с названием + // отличным от значения по умолчанию 'db'. + // 'db' => 'mydb', + + // Чтобы перезаписать таблицу сессий, заданную по умолчанию, установите + // 'sessionTable' => 'my_session', + ], + ], +]; +``` + +Приведенная выше конфигурация использует таблицу базы данных для хранения сессионных данных. По умолчанию, используется +компонент приложения `db` для подключения к базе данных и сохранения сессионных данных в таблице `session`. Вам нужно будет +создать таблицу `session` заранее: + +```sql +CREATE TABLE session ( + id CHAR(40) NOT NULL PRIMARY KEY, + expire INTEGER, + data BLOB +) +``` + +Вы также можете хранить сессионные данные в кеше с помощью [[yii\web\CacheSession]]. Теоретически, вы можете использовать +любое поддерживаемое [хранилище кеша](caching-data.md#supported-cache-storage). Тем не менее, помните, что некоторые +хранилища кеша могут *сбрасывать* закешированные данные при достижении лимитов хранилища. По этой причине, вы должны в +основном использовать хранилища кеша, которые не имеют таких лимитов. + +Если на вашем сервере установлен [Redis](http://redis.io/), настоятельно рекомендуется выбрать его в качестве +хранилища сессий используя [[yii\redis\Session]]. + + +## Оптимизация базы данных <span id="optimizing-databases"></span> + +Выполнение запросов к БД и выборки данных часто являются узким местом производительности веб-приложения. +Хотя использование техник [кэширования данных](caching-data.md) может *смягчить* снижение производительности, оно не +решает проблему полностью. Когда база данных содержит огромное количество данных, и данные в кэше невалидны, получение +свежих данных без правильного проектирования БД и запросов может быть чрезмерно ресурсоемкой операцией. + +Общей методикой для повышения производительности запросов к БД является создание индексов для тех столбцов таблицы, по которым делается выборка. +Например, если вам нужно найти запись о пользователе по `username`, вам надо создать индекс на `username`. +Обратите внимание, что в то время как индексирование может сделать SELECT запросы намного быстрее, оно будет замедлять INSERT, UPDATE и DELETE запросы. + +Для сложных запросов к БД рекомендуется создавать представления базы данных (views), чтобы сэкономить время подготовки и разбора запросов. + +Последнее, хотя и не менее важное: используйте `LIMIT` в ваших `SELECT` запросах. Это позволяет избежать извлечения +большого количество данных из базы данных и исчерпания памяти, выделенной для PHP. + + +## Использование обычных массивов <span id="using-arrays"></span> + +Хотя [Active Record](db-active-record.md) очень удобно использовать, это не так эффективно, как использование простых +массивов, когда вам нужно получить большое количество данных из БД. В этом случае, вы можете вызвать `asArray()` при +использовании Active Record для получения данных, чтобы извлеченные данные были представлены в виде массивов вместо +громоздких записей Active Record. Например, + +```php +class PostController extends Controller +{ + public function actionIndex() + { + $posts = Post::find()->limit(100)->asArray()->all(); + + return $this->render('index', ['posts' => $posts]); + } +} +``` + +В приведенном выше коде, `$posts` будет заполнего массивом строк из таблицы. Каждая строка - это обычный массив. Чтобы +получить доступ к столбцу `title` в i-й строке, вы можете использовать выражение `$posts[$i]['title']`. + +Вы также можете использовать [DAO](db-dao.md) для создания запросов и извлечения данных в виде обычных массивов. + + +## Оптимизация автозагрузчика Composer <span id="optimizing-autoloader"></span> + + +Поскольку автозагрузчик Composer'а используется для подключения большого количества файлов сторонних классов, вы должны +оптимизировать его, выполнив следующую команду: + +``` +composer dumpautoload -o +``` + + +## Асинхронная обработка данных <span id="processing-data-offline"></span> + +Когда запрос включает в себя некоторые ресурсоемкие операции, вы должны подумать о том, чтобы выполнить эти операции +асинхронно, не заставляя пользователя ожидать их окончания. + +Существует два метода асинхронной обработки данных: pull и push. + +В методе pull, всякий раз, когда запрос включает в себя некоторые сложные операции, вы создаете задачу и сохраняете ее в +постоянном хранилище, таком как база данных. Затем в отдельном процессе (таком как задание cron) получаете эту задачу и +обрабатываете ее. + +Этот метод легко реализовать, но у него есть некоторые недостатки. Например, задачи надо периодически забирать из +места их хранения. Если делать это слишком редко, задачи будут обрабатываться с большой задержкой; если слишком часто - +это будет создавать большие накладные расходы. + +В методе push, вы можете использовать очереди сообщений (например, RabbitMQ, ActiveMQ, Amazon SQS, и т.д.) для управления задачами. +Всякий раз, когда новая задача попадает в очередь, это инициирует обработку этой задачи обработчиком. + + +## Профилирование производительности <span id="performance-profiling"></span> + +Вы должны профилировать код, чтобы определить узкие места в производительности и принять соответствующие меры. +Следующие инструменты для профилирования могут оказаться полезными: + +- [Отладочный тулбар Yii и отладчик](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) +- [Профайлер XDebug](http://xdebug.org/docs/profiler) +- [XHProf](http://www.php.net/manual/en/book.xhprof.php) diff --git a/docs/guide-ru/tutorial-shared-hosting.md b/docs/guide-ru/tutorial-shared-hosting.md new file mode 100644 index 0000000000..ddd2e2141f --- /dev/null +++ b/docs/guide-ru/tutorial-shared-hosting.md @@ -0,0 +1,102 @@ +Окружение виртуального хостинга +========================== + +Зачастую окружение виртуальных хостингов весьма ограничено как в настройках конфигурации, так и в настройках структуры директорий. В большинстве случаев, однако, возможно запустить Yii2 на виртуальном хостинге, внеся некоторые корректировки. + +Установка приложения Basic. +--------------------------- + +Поскольку на виртуальном хостинге обычно только один webroot, то лучше использовать шаблонное приложение Basic. Прочитайте раздел [Установка Yii](start-installation.md) и локально установите приложение. После того как оно начнет работать, можно внести необходимые корректировки, которые помогут разместить Basic на виртуальном хостинге. + +### Переименование webroot <span id="renaming-webroot"></span> + +Подключитесь к вашему виртуальному хостингу, используя FTP или другой способ. Скорее всего вы увидите следующее: + +``` +config +logs +www +``` + +В приведенном выше описании `www` - это webroot директория веб-сервера. Она может называться по-другому. Возможные названия: `www`, `htdocs` или `public_html`. + +В Basic webroot называется `web`. Перед загрузкой своего приложения на виртуальный хостинг, переименуйте локальный webroot на название webroot виртуального хостинга. Например, `web` в `www` или `public_html`, в зависимости от наименования webroot вашего хостинга. + +### Корневая директория FTP доступна для записи + +Если вы можете записать в корневую директорию, где располагаются `config`, `logs` и `www`, то загрузите сюда же `assets`, `commands` и остальные директории, так же, как и у вас, локально. + +### Добавим настройки для веб-сервера <span id="add-extras-for-webserver"></span> + +В случае, если ваш сервер Apache, добавьте в директорию `web` или аналогичную, где располагается `index.php`, файл `.htaccess` со следующим содержимым: + +``` +Options +FollowSymLinks +IndexIgnore */* + +RewriteEngine on + +# if a directory or a file exists, use it directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# otherwise forward it to index.php +RewriteRule . index.php +``` + +В случае использования nginx не требуется каких-либо дополнительных настроек. + +### Проверка требований + +Для того чтобы запустить Yii, ваш веб-сервер должен соответствовать его требованиям. Минимальное требование к PHP - это его версия 5.4. Для того чтобы проверить требования, скопируйте `requirements.php` из корневого каталога в каталог webroot и запустите его с помощью браузера, используя url `http://example.com/requirements.php`. Не забудьте после проверки требований удалить файл `requirements.php`. + +Установка шаблона приложения Advanced +--------------------------------- + +Установка шаблона Advanced немного сложнее, чем установка Basic, из-за того, что в Advanced имеются две директории webroot, работа с которыми на виртуальном хостинге не поддерживается. По этой причине нам потребуется внести изменения в структуру директорий. + +### Перемещение входных скриптов в одну директорию webroot + +Для начала нам необходима директория webroot. Создайте новую директорию и назовите её так же, как на виртуальном хостинге, например, `www` или `public_html`, как описывалось выше в разделе [Переименование webroot](#renaming-webroot). Затем создайте следующую структуру в `www`: + +``` +www + admin +backend +common +console +environments +frontend +... +``` + +Нашей фронтенд директорией будет `www`. Переместите в неё всё из `frontend/web`. Так же поступите и для `backend/web`, скопировав всё в `www/admin`. В каждом случае нужно настроить пути внутри файлов `index.php` и `index-test.php`. + +### Отдельные сессии и куки + +Изначально подразумевалось, что приложения бекенд и фронтенд располагаются на разных доменах. Теперь, когда мы перенесли всё на один домен, куки и сессии из бекенда и фронтенда стали пересекаться. Для решения этой проблемы требуется внести следующие настройки в конфигурацию бекенд-приложения `backend/config/main.php`: + +```php +'components' => [ + 'request' => [ + 'csrfParam' => '_backendCSRF', + 'csrfCookie' => [ + 'httpOnly' => true, + 'path' => '/admin', + ], + ], + 'user' => [ + 'identityCookie' => [ + 'name' => '_backendIdentity', + 'path' => '/admin', + 'httpOnly' => true, + ], + ], + 'session' => [ + 'name' => 'BACKENDSESSID', + 'cookieParams' => [ + 'path' => '/admin', + ], + ], +], +``` diff --git a/docs/guide-ru/tutorial-start-from-scratch.md b/docs/guide-ru/tutorial-start-from-scratch.md new file mode 100644 index 0000000000..8d89e80787 --- /dev/null +++ b/docs/guide-ru/tutorial-start-from-scratch.md @@ -0,0 +1,51 @@ +Создание своей структуры приложения +======================================= + +> Note: Этот раздел находится на стадии разработки. + +Пока шаблоны проектов [basic](https://github.com/yiisoft/yii2-app-basic) и [advanced](https://github.com/yiisoft/yii2-app-advanced) великолепно справляются с большинством ваших потребностей, но вы можете захотеть создать свой собственный шаблон проекта, с которого будете начинать делать ваши проекты. + +Шаблоны проектов в Yii - это просто репозитарии, содержащие `composer.json` файл, и зарегистрированные как Composer пакет. +Любые репозитарии, которые могут быть определены как Composer пакеты, становятся установочными через Composer команду `create-project`. + +Чтобы построить весь свой шаблон с нуля, нужно затратить много энергии, поэтому лучше использовать один из встроенных шаблонов, как базовый. + +Клонирование базового шаблона +---------------------------------------- + +Первый шаг для клонирования базового Yii шаблона из Git репозитария: + +```bash +git clone git@github.com:yiisoft/yii2-app-basic.git +``` + +Затем необходимо подождать, чтобы репозитарии загрузился на ваш компьютер. С внесенными изменениями шаблон должен быть "запушен"(push) обратно, затем вы можете удалить `.git` директорию и весь загруженный контент на вашем компьютере. + +Измените файлы +------------ + +Следующее, вам надо изменить `composer.json` в соответствии с вашим шаблоном. Измените значения `name`(имя), `description`(описание), `keywords`(ключевые слова), `homepage`(адрес домашней страницы), `license`(лицензия), и `support`(поддержка) +для описания вашего нового шаблона. Также установите `require`(зависимости фреимворка), `require-dev`(зависимости от расширений), `suggest`, и другие опции, которые необходимы для вашего шаблона. + +> Note: В файле `composer.json` используйте `writable` параметр внутри `extra`, чтобы указать +> права доступа к файлам, которые будут установлены, после создания приложения на основе данного шаблона. + +Следующее, измените структуру и содержание приложения, по вашему вкусу. В заключении обновите README файл, чтобы он соответствовал конечному варианту вашего шаблона. + +Создание пакета +-------------- + +Создайте Git репозитарий из созданного шаблона, и запушьте(push) его. Если вы собираетесь сделать ваш шаблон с открытым исходным кодом, [Github](http://github.com) - это лучшее место, чтобы разместить его. Если вы собираетесь сохранить ваш шаблон для личных целей, используйте любой Git, предназначенный для этих целей. + +Затем, вам необходимо зарегистрировать ваш пакет в Composer. Пакет с публичным шаблоном должен быть зарегистрирован на [Packagist](https://packagist.org/). +Для приватных шаблонов, зарегистрировать шаблона немного сложнее. Для получения инструкции загляните в [Composer documentation](https://getcomposer.org/doc/05-repositories.md#hosting-your-own). + +Использование шаблона +------ + +Это все, что требуется для создания нового шаблона проекта в Yii. Сейчас вы можете создавать проекты, использующие ваш шаблон: + +``` +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project +``` diff --git a/docs/guide-ru/tutorial-template-engines.md b/docs/guide-ru/tutorial-template-engines.md new file mode 100644 index 0000000000..fedd79751a --- /dev/null +++ b/docs/guide-ru/tutorial-template-engines.md @@ -0,0 +1,47 @@ +Использование шаблонизаторов +====================== + +По умолчанию, Yii использует PHP в шаблонах, но вы можете настроить Yii на поддержку других шаблонизаторов,таких как +[Twig](http://twig.sensiolabs.org/) или [Smarty](http://www.smarty.net/), которые доступны в расширениях. + +`view` компонент, отвественный за генерацию видов. Вы можете добавить шаблонизатор, с помощью перенастройки поведения компонента: + +```php +[ + 'components' => [ + 'view' => [ + 'class' => 'yii\web\View', + 'renderers' => [ + 'tpl' => [ + 'class' => 'yii\smarty\ViewRenderer', + //'cachePath' => '@runtime/Smarty/cache', + ], + 'twig' => [ + 'class' => 'yii\twig\ViewRenderer', + 'cachePath' => '@runtime/Twig/cache', + // Array of twig options: + 'options' => [ + 'auto_reload' => true, + ], + 'globals' => ['html' => '\yii\helpers\Html'], + 'uses' => ['yii\bootstrap'], + ], + // ... + ], + ], + ], +] +``` + +В коде, показанном выше, оба шаблонизатора Smarty и Twig настроены, чтобы использоваться в файле вида. Но чтобы добавить эти расширения в ваш проект, вам необходимо также изменить ваш `composer.json` файл. Добавить в него: + +``` +"yiisoft/yii2-smarty": "*", +"yiisoft/yii2-twig": "*", +``` +Это код вставляется в секцию `require` файла `composer.json`. После изменения и сохранения этого файла, вы можете установить расширение, запустив `composer update --prefer-dist` в командной строке. + +Для получения подробной информации об использовании конкретного шаблонизатора обратитесь в их документации: + +- [Twig guide](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) +- [Smarty guide](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) \ No newline at end of file diff --git a/docs/guide-ru/tutorial-yii-integration.md b/docs/guide-ru/tutorial-yii-integration.md index d5e85c8408..4257d73312 100644 --- a/docs/guide-ru/tutorial-yii-integration.md +++ b/docs/guide-ru/tutorial-yii-integration.md @@ -1,4 +1,4 @@ -Работа со сторонним кодом +Работа со сторонним кодом ============================= Иногда необходимо использовать сторонний код в приложениях Yii. Или же есть потребность использовать Yii в качестве библиотеки в сторонних системах. В этом разделе мы рассмотрим, как это происходит. @@ -63,11 +63,15 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; Если сторонняя система использует для управления зависимостями Composer, Yii можно просто установить с помощью следующих команд: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= + composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer require "yiisoft/yii2:*" composer install @@ -98,7 +102,7 @@ new yii\web\Application($yiiConfig); // НЕ ВЫЗЫВАЙТЕ run() в это Если в прошлом вам приходилось использовать Yii 1, не исключено, что у вас до сих пор где-то используются приложения на этой платформе. Вместо того, чтобы переписывать все приложение под Yii 2, может быть целесообразно расширить его используя отдельные функции, которые появились в Yii 2. Для этого нужно выполнить следующие действия. -> Примечание: Yii 2 требует PHP 5.4 или выше. Убедитесь, что и сервер, и существующее приложение поддерживают это. +> Note: Yii 2 требует PHP 5.4 или выше. Убедитесь, что и сервер, и существующее приложение поддерживают это. Во-первых, установите Yii 2 в существующем приложении, выполняя действия, описанные в [предыдущем подразделе](#using-yii-in-others). diff --git a/docs/guide-uk/README.md b/docs/guide-uk/README.md index a41a5a554e..01c9cf5a8b 100644 --- a/docs/guide-uk/README.md +++ b/docs/guide-uk/README.md @@ -1,16 +1,21 @@ <<<<<<< HEAD +<<<<<<< HEAD Повний посібник до 2.0 ====================== ======= Повний посібник з Yii 2.0 ========================= >>>>>>> yiichina/master +======= +Повний посібник з Yii 2.0 +========================= +>>>>>>> master Даний посібник випущено відповідно до [положень про документацію Yii](http://www.yiiframework.com/doc/terms/). All Rights Reserved. -2014 © Yii Software LLC. +2014 (c) Yii Software LLC. Введення @@ -24,11 +29,11 @@ All Rights Reserved. ---------------- * [Встановлення Yii](start-installation.md) -* [Запуск додатка](start-workflow.md) -* [Говоримо «Привіт»](start-hello.md) +* [Виконання додатків](start-workflow.md) +* [Говоримо "Привіт"](start-hello.md) * [Робота з формами](start-forms.md) * [Робота з базами даних](start-databases.md) -* [Генерація коду за допомогою Gii](start-gii.md) +* [Генерування коду за допомогою Gii](start-gii.md) * [Наступні кроки](start-looking-ahead.md) @@ -42,6 +47,7 @@ All Rights Reserved. * [Контролери](structure-controllers.md) <<<<<<< HEAD * [Моделі](structure-models.md) +<<<<<<< HEAD * [Представлення](structure-views.md) * [Модулі](structure-modules.md) * [Фільтри](structure-filters.md) @@ -50,18 +56,24 @@ All Rights Reserved. * [Розширення](structure-extensions.md) ======= * **TBD** [Моделі](structure-models.md) +======= +>>>>>>> master * **TBD** [Представлення](structure-views.md) * **TBD** [Модулі](structure-modules.md) * **TBD** [Фільтри](structure-filters.md) * **TBD** [Віджети](structure-widgets.md) * **TBD** [Ресурси](structure-assets.md) * **TBD** [Розширення](structure-extensions.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Обробка запитів --------------- +<<<<<<< HEAD <<<<<<< HEAD * [Огляд](runtime-overview.md) * [Bootstrapping](runtime-bootstrapping.md) @@ -74,18 +86,27 @@ All Rights Reserved. ======= * **TBD** [Огляд](runtime-overview.md) * **TBD** [Bootstrapping](runtime-bootstrapping.md) +======= +* **TBD** [Огляд](runtime-overview.md) +* **TBD** [Початкове завантаження](runtime-bootstrapping.md) +>>>>>>> master * **TBD** [Маршрутизація та створення URL](runtime-routing.md) * **TBD** [Запити](runtime-requests.md) * **TBD** [Відповіді](runtime-responses.md) * **TBD** [Сесії та кукі](runtime-sessions-cookies.md) * **TBD** [Обробка помилок](runtime-handling-errors.md) +<<<<<<< HEAD * **TBD** [Логування](runtime-logging.md) >>>>>>> yiichina/master +======= +* **TBD** [Журналювання](runtime-logging.md) +>>>>>>> master Основні поняття --------------- +<<<<<<< HEAD <<<<<<< HEAD * [Компоненти](concept-components.md) * [Властивості](concept-properties.md) @@ -107,11 +128,23 @@ All Rights Reserved. * **TBD** [Service Locator](concept-service-locator.md) * **TBD** [Dependency Injection Container](concept-di-container.md) >>>>>>> yiichina/master +======= +* **TBD** [Компоненти](concept-components.md) +* **TBD** [Властивості](concept-properties.md) +* **TBD** [Події](concept-events.md) +* **TBD** [Поведінки](concept-behaviors.md) +* **TBD** [Конфігурації](concept-configurations.md) +* [Псевдоніми](concept-aliases.md) +* [Автозавантаження класів](concept-autoloading.md) +* **TBD** [Локатор служб](concept-service-locator.md) +* **TBD** [Dependency Injection Container](concept-di-container.md) +>>>>>>> master Робота з базами даних --------------------- +<<<<<<< HEAD <<<<<<< HEAD * [Обʼєкти доступу до даних (DAO)](db-dao.md) - Зʼєднання з базою даних, прості запити, транзакції і робота зі схемою * [Конструктор запитів](db-query-builder.md) - Запити до бази даних через простий шар абстракції @@ -126,16 +159,26 @@ All Rights Reserved. * **TBD** [Конструктор запитів](db-query-builder.md) - Запити до бази даних через простий шар абстракції * **TBD** [Active Record](db-active-record.md) - Отримання обʼєктів AR, робота з ними та визначення звʼязків * **TBD** [Міграції](db-migrations.md) - Контроль версій схеми даних при роботі в команді +======= +* **TBD** [Обʼєкти доступу до даних (DAO)](db-dao.md): Зʼєднання з базою даних, прості запити, транзакції і робота зі схемою +* **TBD** [Конструктор запитів](db-query-builder.md): Запити до бази даних через простий шар абстракції +* **TBD** [Active Record](db-active-record.md): Отримання обʼєктів AR, робота з ними та визначення звʼязків +* **TBD** [Міграції](db-migrations.md): Контроль версій схеми даних при роботі в команді +>>>>>>> master * [Sphinx](https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide/README.md) * [Redis](https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/README.md) * [MongoDB](https://github.com/yiisoft/yii2-mongodb/blob/master/docs/guide/README.md) * [ElasticSearch](https://github.com/yiisoft/yii2-elasticsearch/blob/master/docs/guide/README.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Отримання даних від користувача ------------------------------- +<<<<<<< HEAD <<<<<<< HEAD * [Створення форм](input-forms.md) * [Валідація вводу](input-validation.md) @@ -146,12 +189,19 @@ All Rights Reserved. * **TBD** [Завантаження файлів](input-file-uploading.md) * **TBD** [Збір табличного вводу](input-tabular-input.md) >>>>>>> yiichina/master +======= +* **TBD** [Створення форм](input-forms.md) +* **TBD** [Перевірка вводу](input-validation.md) +* **TBD** [Завантаження файлів](input-file-uploading.md) +* **TBD** [Збір табличного вводу](input-tabular-input.md) +>>>>>>> master * **TBD** [Робота з декількома моделями](input-multiple-models.md) Відображення даних ------------------ +<<<<<<< HEAD <<<<<<< HEAD * [Форматування даних](output-formatter.md) * [Посторінкове розбиття](output-pagination.md) @@ -163,17 +213,25 @@ All Rights Reserved. ======= * **TBD** [Форматування даних](output-formatting.md) * **TBD** [Посторінкове розбиття](output-pagination.md) +======= +* **TBD** [Форматування даних](output-formatting.md) +* **TBD** [Розділення на сторінки](output-pagination.md) +>>>>>>> master * **TBD** [Сортування](output-sorting.md) * **TBD** [Провайдери даних](output-data-providers.md) * **TBD** [Віджети даних](output-data-widgets.md) * **TBD** [Робота з клієнтськими скриптами](output-client-scripts.md) * **TBD** [Темізація](output-theming.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Безпека ------- +<<<<<<< HEAD <<<<<<< HEAD * [Аутентифікація](security-authentication.md) * [Авторизація](security-authorization.md) @@ -181,17 +239,23 @@ All Rights Reserved. * **TBD** [Клієнти авторизації](security-auth-clients.md) * [Кращі практики](security-best-practices.md) ======= +======= +>>>>>>> master * **TBD** [Аутентифікація](security-authentication.md) * **TBD** [Авторизація](security-authorization.md) * **TBD** [Робота з паролями](security-passwords.md) * [Клієнти авторизації](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) * **TBD** [Кращі практики](security-best-practices.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Кешування --------- +<<<<<<< HEAD <<<<<<< HEAD * [Огляд](caching-overview.md) * [Кешування даних](caching-data.md) @@ -205,11 +269,19 @@ All Rights Reserved. * **TBD** [Кешування сторінок](caching-page.md) * **TBD** [HTTP кешування](caching-http.md) >>>>>>> yiichina/master +======= +* **TBD** [Огляд](caching-overview.md) +* **TBD** [Кешування даних](caching-data.md) +* [Кешування фрагментів](caching-fragment.md) +* **TBD** [Кешування сторінок](caching-page.md) +* **TBD** [HTTP кешування](caching-http.md) +>>>>>>> master -RESTful веб-сервіси +Веб-сервіси RESTful ------------------- +<<<<<<< HEAD <<<<<<< HEAD * [Швидкий старт](rest-quick-start.md) * [Ресурси](rest-resources.md) @@ -231,11 +303,23 @@ RESTful веб-сервіси * **TBD** [Версіонування](rest-versioning.md) * **TBD** [Обробка помилок](rest-error-handling.md) >>>>>>> yiichina/master +======= +* **TBD** [Швидкий старт](rest-quick-start.md) +* **TBD** [Ресурси](rest-resources.md) +* **TBD** [Контролери](rest-controllers.md) +* **TBD** [Маршрутизація](rest-routing.md) +* **TBD** [Форматування відповіді](rest-response-formatting.md) +* **TBD** [Аутентифікація](rest-authentication.md) +* [Обмеження частоти запитів](rest-rate-limiting.md) +* **TBD** [Версіонування](rest-versioning.md) +* **TBD** [Обробка помилок](rest-error-handling.md) +>>>>>>> master Інструменти розробника ---------------------- +<<<<<<< HEAD <<<<<<< HEAD * [Відладочна панель та відладчик](tool-debugger.md) * [Генерація коду з Gii](tool-gii.md) @@ -245,11 +329,17 @@ RESTful веб-сервіси * **TBD** [Генерація коду з Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md) * [Генератор документації API (en)](https://github.com/yiisoft/yii2-apidoc) >>>>>>> yiichina/master +======= +* [Панель налагодження та налагоджувач](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-uk/README.md) +* [Генерування коду за допомогою Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md) +* **TBD** [Генерування документації API](https://github.com/yiisoft/yii2-apidoc) +>>>>>>> master Тестування ---------- +<<<<<<< HEAD <<<<<<< HEAD * [Огляд](test-overview.md) * [Налаштування середовища тестування](test-environment-setup.md) @@ -258,27 +348,36 @@ RESTful веб-сервіси * [Приймальні тести](test-acceptance.md) * [Фікстури](test-fixtures.md) ======= +======= +>>>>>>> master * **TBD** [Огляд](test-overview.md) * **TBD** [Налаштування середовища тестування](test-environment-setup.md) * **TBD** [Модульні тести](test-unit.md) * **TBD** [Функціональні тести](test-functional.md) * **TBD** [Приймальні тести](test-acceptance.md) * **TBD** [Фікстури](test-fixtures.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Спеціальні теми --------------- +<<<<<<< HEAD <<<<<<< HEAD * [Розширений шаблон додатка](tutorial-advanced-app.md) +======= +* **TBD** [Розширений шаблон проекту](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-uk/README.md) +>>>>>>> master * [Створення додатка з нуля](tutorial-start-from-scratch.md) -* [Консольні команди](tutorial-console.md) -* [Основні валідатори](tutorial-core-validators.md) -* [Інтернаціонализація](tutorial-i18n.md) -* [Робота з поштою](tutorial-mailing.md) -* [Вдосконалення продуктивності](tutorial-performance-tuning.md) -* [Робота на shared хостингу](tutorial-shared-hosting.md) +* [Консольні додатки](tutorial-console.md) +* **TBD** [Основні валідатори](tutorial-core-validators.md) +* **TBD** [Інтернаціоналізація](tutorial-i18n.md) +* **TBD** [Робота з поштою](tutorial-mailing.md) +* **TBD** [Покращення швидкодії](tutorial-performance-tuning.md) +* **TBD** [Робота на віртуальному хостингу](tutorial-shared-hosting.md) * [Шаблонізатори](tutorial-template-engines.md) * [Робота із стороннім кодом](tutorial-yii-integration.md) ======= @@ -307,18 +406,23 @@ RESTful веб-сервіси * LinkPager: **TBD** link to demo page * LinkSorter: **TBD** link to demo page <<<<<<< HEAD +<<<<<<< HEAD * [Віджети Bootstrap](widget-bootstrap.md) * [Віджети jQuery UI](widget-jui.md) ======= * [Віджети Bootstrap](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-uk/README.md) * [Віджети jQuery UI](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-uk/README.md) >>>>>>> yiichina/master +======= +* [Віджети Bootstrap](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-uk/README.md) +* [Віджети jQuery UI](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-uk/README.md) +>>>>>>> master Хелпери ------- -* [Огляд](helper-overview.md) -* [ArrayHelper](helper-array.md) -* [Html](helper-html.md) -* [Url](helper-url.md) +* **TBD** [Огляд хелперів](helper-overview.md) +* **TBD** [ArrayHelper](helper-array.md) +* **TBD** [Html](helper-html.md) +* **TBD** [Url](helper-url.md) diff --git a/docs/guide-uk/blocktypes.json b/docs/guide-uk/blocktypes.json new file mode 100644 index 0000000000..4bdb5e1455 --- /dev/null +++ b/docs/guide-uk/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Увага:", + "Note:": "Примітка:", + "Info:": "Інформація:", + "Tip:": "Підказка:" +} diff --git a/docs/guide-uk/caching-fragment.md b/docs/guide-uk/caching-fragment.md index da566675f5..1dbd27d5ee 100644 --- a/docs/guide-uk/caching-fragment.md +++ b/docs/guide-uk/caching-fragment.md @@ -10,16 +10,16 @@ ```php if ($this->beginCache($id)) { - // ... тут створюємо зміст ... + // ... тут створюємо вміст ... $this->endCache(); } ``` -Таким чином, розташуйте логіку генерації вмісту у комбінацію викликів [[yii\base\View::beginCache()|beginCache()]] та +Таким чином, розташуйте логіку генерування вмісту у комбінацію викликів [[yii\base\View::beginCache()|beginCache()]] та [[yii\base\View::endCache()|endCache()]]. Якщо вміст буде знайдено у кеші, [[yii\base\View::beginCache()|beginCache()]] -відобразить закешований вміст і поверне false, оминаючи генерацію вмісту. -В іншому випадку, буде виконано логіку генерації вмісту і з викликом [[yii\base\View::endCache()|endCache()]] +відобразить закешований вміст і поверне false, оминаючи генерування вмісту. +В іншому випадку, буде виконано логіку генерування вмісту і з викликом [[yii\base\View::endCache()|endCache()]] згенерований вміст буде записаний до кешу. Подібно до [кешування даних](caching-data.md), для кешування фрагментів необхідний унікальний ідентифікатор @@ -36,12 +36,12 @@ if ($this->beginCache($id)) { ### Тривалість <span id="duration"></span> Мабуть найбільш часто використовуваною опцією кешування фрагмента є [[yii\widgets\FragmentCache::duration|duration]]. -Вона визначає на скільки секунд зміст може залишатися дійсним у кеші. Код нижче кешує фрагмент не більше, ніж на одну: +Вона визначає на скільки секунд вміст може залишатися дійсним у кеші. Код нижче кешує фрагмент не більше, ніж на одну: ```php if ($this->beginCache($id, ['duration' => 3600])) { - // ... тут створюємо зміст ... + // ... тут створюємо вміст ... $this->endCache(); } @@ -67,7 +67,7 @@ $dependency = [ if ($this->beginCache($id, ['dependency' => $dependency])) { - // ... тут створюємо зміст ... + // ... тут створюємо вміст ... $this->endCache(); } @@ -87,7 +87,7 @@ if ($this->beginCache($id, ['dependency' => $dependency])) { ```php if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { - // ... тут створюємо зміст ... + // ... тут створюємо вміст ... $this->endCache(); } @@ -104,7 +104,7 @@ if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { ```php if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) { - // ... тут створюємо зміст ... + // ... тут створюємо вміст ... $this->endCache(); } @@ -142,17 +142,17 @@ if ($this->beginCache($id1)) { в іншому випадку застарілі внутрішні фрагменти можуть зберігатися в зовнішньому фрагменті. -## Динамічний зміст <span id="dynamic-content"></span> +## Динамічний вміст <span id="dynamic-content"></span> -При використанні кешування фрагментів, ви можете зіткнутися із ситуацією, коли великий фрагмент змісту є відносно статичним, +При використанні кешування фрагментів, ви можете зіткнутися із ситуацією, коли великий фрагмент вмісту є відносно статичним, за винятком одного або декількох місць. Наприклад, заголовок сторінки може відображатися у головному меню разом -з імʼям поточного користувача. Інша проблема в тому, що закешований зміст може містити код PHP, який повинен -виконуватися при кожному запиті (наприклад, код для реєстрації пакету ресурсів). Обидві ці проблеми можуть бути -вирішені за допомогою так званої функції *динамічного змісту*. +з імʼям поточного користувача. Інша проблема в тому, що закешований вміст може містити код PHP, який повинен +виконуватися при кожному запиті (наприклад, код для реєстрації колекції ресурсів). Обидві ці проблеми можуть бути +вирішені за допомогою так званої функції *динамічного вмісту*. -Динамічний зміст означає фрагмент виведення, який не повинен кешуватися, навіть якщо він укладений у кешування фрагментів. -Для того, щоб зробити зміст динамічним постійно, він повинен бути створений за допомогою деякого PHP коду для кожного запиту, -навіть якщо зміст віддаєтся із кеша. +Динамічний вміст означає фрагмент виведення, який не повинен кешуватися, навіть якщо він укладений у кешування фрагментів. +Для того, щоб зробити вміст динамічним постійно, він повинен бути створений за допомогою деякого коду PHP для кожного запиту, +навіть якщо вміст віддаєтся із кеша. Ви можете викликати [[yii\base\View::renderDynamic()]] в кеші фрагмента для вставки динамічного контенту у потрібне місце, як у прикладі нижче: @@ -171,5 +171,5 @@ if ($this->beginCache($id1)) { ``` Метод [[yii\base\View::renderDynamic()|renderDynamic()]] бере деяку частину коду PHP в якості параметра. -Значення, що повертається від коду PHP, трактується як динамічний зміст. Цей код PHP буде виконуватися при кожному запиті, +Значення, що повертається від коду PHP, трактується як динамічний вміст. Цей код PHP буде виконуватися при кожному запиті, незалежно від того, чи віддається фрагмент із кешу або ні. diff --git a/docs/guide-uk/concept-aliases.md b/docs/guide-uk/concept-aliases.md index d224c45fa1..6c41d63265 100644 --- a/docs/guide-uk/concept-aliases.md +++ b/docs/guide-uk/concept-aliases.md @@ -1,12 +1,16 @@ Псевдоніми ========== -Псевдоніми використовуються для позначення шляхів до файлів або URL адрес і допомагають уникнути використання абсолютних шляхів -або URL в коді. Для того, щоб не переплутати псевдонім із звичайним шляхом до файлу або URL, він повинен починатися з `@`. В Yii -є багато заздалегідь визначених псевдонімів. Наприклад, `@yii` вказує на директорію, в яку був встановлений -Yii framework, а `@web` можна використовувати для отримання базового URL поточного додатку. - +Псевдоніми використовуються для представлення шляхів до файлів або URL адрес +і допомагають уникнути використання абсолютних шляхів або URL у коді. +Для того, щоб не переплутати псевдонім із звичайним шляхом до файлу або URL, +він повинен починатися із символу `@`. Якщо псевдонім не починається із +символу `@` - його буде додано автоматично. +В Yii є багато заздалегідь визначених псевдонімів. Наприклад, +`@yii` вказує на директорію, в яку був встановлений Yii framework, +а `@web` можна використовувати для отримання базового URL поточного додатку. + Створення псевдонімів <span id="defining-aliases"></span> --------------------- @@ -20,7 +24,7 @@ Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@bar', 'http://www.example.com'); ``` -> Примітка: псевдонім шляху до файлу або URL *не* обовʼязково вказує на існуючий файл або ресурс. +> Note: псевдонім шляху до файлу або URL *не* обовʼязково вказує на наявний файл або ресурс. Використовуючи вже заданий псевдонім, ви можете отримати на основі нього новий (без виклику [[Yii::setAlias()]]), додавши в його кінець `/`, за яким слідує один або більше сегментів шляху. Псевдоніми, визначені за допомогою @@ -64,7 +68,7 @@ echo Yii::getAlias('@foo/bar/file.php'); // виведе: /path/to/foo/bar/file Шлях або URL, представлений похідним псевдонімом, визначається шляхом заміни в ньому частині, що відповідає кореневого псевдоніму, на відповідний йому шлях або URL. -> Примітка: Метод [[Yii::getAlias()]] не перевіряє фактичного існування одержуваного шляху або URL. +> Note: Метод [[Yii::getAlias()]] не перевіряє фактичного існування одержуваного шляху або URL. Кореневий псевдонім може містити знаки `/`. При цьому метод [[Yii::getAlias()]] коректно визначить, яка частина псевдоніма є кореневої і вірно сформує шлях або URL: @@ -102,18 +106,22 @@ $cache = new FileCache([ В Yii заздалегідь визначені псевдоніми для часто використовуваних шляхів до файлів і URL: -- `@yii`: директорія, в якій знаходиться файл `BaseYii.php` (директорія фреймворка). +- `@yii`: директорія, в якій знаходиться файл `BaseYii.php` (директорія фреймворку). - `@app`: [[yii\base\Application::basePath|базовий шлях]] поточного додатку. - `@runtime`: [[yii\base\Application::runtimePath|директорія runtime]] поточного додатку. За замовчуванням `@app/runtime`. <<<<<<< HEAD +<<<<<<< HEAD - `@webroot`, коренева веб-директорія поточного веб-додатку. Визначається на основі директорії розташування вхідного скрипта. ======= - `@webroot`, коренева веб-директорія поточного веб-додатку. Визначається на основі директорії розташування [вхідного скрипта](structure-entry-scripts.md). >>>>>>> yiichina/master +======= +- `@webroot`, коренева веб-директорія поточного веб-додатку. Визначається на основі директорії розташування [вхідного скрипта](structure-entry-scripts.md). +>>>>>>> master - `@web`, базовий URL поточного додатку. Має таке ж значення, як і [[yii\web\Request::baseUrl]]. - `@vendor`, [[yii\base\Application::vendorPath|директорія vendor Composer]]. За замовчуванням `@app/vendor`. -- `@bower`, директорія, що містить [пакети bower](http://bower.io/). За замовчуванням `@vendor/bower`. -- `@npm`, директорія, що містить [пакети npm](https://www.npmjs.org/). За замовчуванням `@vendor/npm`. +- `@bower`, директорія, що містить [пакунки Bower](http://bower.io/). За замовчуванням `@vendor/bower`. +- `@npm`, директорія, що містить [пакунки NPM](https://www.npmjs.org/). За замовчуванням `@vendor/npm`. Псевдонім `@yii` задається в момент підключення файлу `Yii.php` у [вхідному скрипті](structure-entry-scripts.md). Решта псевдонімів задаються в конструкторі додатка в момент застосування [конфигурації](concept-configurations.md). @@ -124,7 +132,7 @@ $cache = new FileCache([ Для кожного [розширення](structure-extensions.md), що встановлюється через Composer, автоматично задається псевдонім. Його імʼя відповідає кореневому простору імен розширення відповідно до його `composer.json`, і кожен псевдонім представляє -шлях до кореневої директорії пакета. Наприклад, якщо ви встановите розширення `yiisoft/yii2-jui`, +шлях до кореневої директорії пакунка. Наприклад, якщо ви встановите розширення `yiisoft/yii2-jui`, то вам автоматично стане доступним псевдонім `@yii/jui`, який будет створено на етапі [первинного завантаження (bootstrapping)](runtime-bootstrapping.md) наступним чином: diff --git a/docs/guide-uk/concept-autoloading.md b/docs/guide-uk/concept-autoloading.md index 64b3df938f..cdfe199097 100644 --- a/docs/guide-uk/concept-autoloading.md +++ b/docs/guide-uk/concept-autoloading.md @@ -7,7 +7,7 @@ [PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md), який встановлюється в момент підключення файлу `Yii.php`. -> Примітка: Для простоти опису, в цьому розділі ми будемо говорити тільки про автозавантаження класів. +> Note: Для простоти опису, в цьому розділі ми будемо говорити тільки про автозавантаження класів. Тим не менш, все описане також стосується до інтерфейсів і трейтів. @@ -16,6 +16,7 @@ Для використання автозавантажувача класів Yii слід дотримуватися два простих правила створення і іменування класів: +<<<<<<< HEAD <<<<<<< HEAD * Кожен клас повинен належати простору імен (тобто `foo\bar\MyClass`) * Кожен клас повинен знаходитися в окремому файлі, шлях до якого визначаться наступним правилом: @@ -29,6 +30,13 @@ ```php // $className — це абсолютне імʼя класу без початкового "\" >>>>>>> yiichina/master +======= +* Кожен клас повинен належати до [простору імен](http://php.net/manual/en/language.namespaces.php) (наприклад, `foo\bar\MyClass`) +* Кожен клас повинен знаходитися в окремому файлі, шлях до якого визначаться наступним правилом: + +```php +// $className — це абсолютне імʼя класу без початкового "\" +>>>>>>> master $classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); ``` @@ -44,19 +52,24 @@ $classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); У [розширеному шаблоні додатка](tutorial-advanced-app.md) кожен рівень додатку володіє власним кореневим псевдонімом. Наприклад, для frontend кореневим псевдонімом є `@frontend`, а для backend — `@backend`. Це дозволяє розмістити класи <<<<<<< HEAD +<<<<<<< HEAD frontend в простір імен `frontend`, а класи backend в простір імен `backend`. При цьому класи будуть завантажені автоматично. ======= frontend в простір імен `frontend`, а класи backend - в простір імен `backend`. Це дозволить вказаним класам бути автоматично завантаженими засобами Yii. >>>>>>> yiichina/master +======= +frontend в простір імен `frontend`, а класи backend - в простір імен `backend`. +Це дозволить вказаним класам бути автоматично завантаженими засобами Yii. +>>>>>>> master Мапа класів <span id="class-map"></span> ----------- Автозавантажувач Yii підтримує *мапу класів*. Ця можливість дозволяє вказати шлях до файлу для кожного імені класу. При завантаженні класу автозавантажувач перевіряє наявність класу в мапі. Якщо він там є, відповідний файл буде завантажений -безпосередньо без будь-яких додаткових перевірок. Це робить автозагрузку дуже швидкою. Всі класи самого фреймворка +безпосередньо без будь-яких додаткових перевірок. Це робить автозагрузку дуже швидкою. Всі класи самого фреймворку завантажуються саме цим способом. Ви маєте можливість додати клас в мапу `Yii::$classMap` наступним чином: @@ -66,7 +79,7 @@ Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php'; ``` Для вказівки шляхів до файлів класів можна використовувати [псевдоніми](concept-aliases.md). Мапу класів необхідно сформувати -в процесі [первинного завантаження](runtime-bootstrapping.md), так як вона повинна бути сформована до використання класів. +в процесі [початкового завантаження](runtime-bootstrapping.md), бо вона повинна бути сформована до використання класів. Використання інших автозавантажувачів <span id="using-other-autoloaders"></span> @@ -89,8 +102,8 @@ require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); Ви можете використовувати автозавантажувач Composer без автозавантажувачa Yii, однак, швидкість автозавантаження в цьому випадку може зменшиться. До того ж вам буде необхідно слідувати правилам автозавантажувача Composer. -> Інформація: Якщо ви не хочете використовувати автозавантажувач Yii, створіть свою версію файлу `Yii.php` - і підключіть його у [вхідному скрипті](structure-entry-scripts.md). +> Info: Якщо ви не хочете використовувати автозавантажувач Yii, створіть свою версію файлу `Yii.php` +і підключіть його у [вхідному скрипті](structure-entry-scripts.md). Автозавантаження класів розширень <span id="autoloading-extension-classes"></span> diff --git a/docs/guide-uk/images/application-lifecycle.graphml b/docs/guide-uk/images/application-lifecycle.graphml index cdcb96ad98..7e73be2b44 100644 --- a/docs/guide-uk/images/application-lifecycle.graphml +++ b/docs/guide-uk/images/application-lifecycle.graphml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"> - <!--Created by yEd 3.13--> +<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"> + <!--Created by yEd 3.14.2--> <key for="graphml" id="d0" yfiles.type="resources"/> <key for="port" id="d1" yfiles.type="portgraphics"/> <key for="port" id="d2" yfiles.type="portgeometry"/> @@ -20,10 +20,10 @@ <y:ProxyAutoBoundsNode> <y:Realizers active="0"> <y:GroupNode> - <y:Geometry height="570.7368214925131" width="763.2772213171534" x="-1269.9373595143054" y="-206.46394602457679"/> + <y:Geometry height="570.5239308675131" width="763.2772213171534" x="-1269.9373595143054" y="-206.25105539957679"/> <y:Fill color="#FFCC0024" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="763.2772213171534" x="0.0" y="0.0">Вхідний скрипт (index.php або yii)</y:NodeLabel> + <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="763.2772213171534" x="0.0" y="0.0">Вхідний скрипт (index.php або yii)</y:NodeLabel> <y:Shape type="rectangle"/> <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/> @@ -33,7 +33,7 @@ <y:Geometry height="50.0" width="50.0" x="313.2978515625" y="225.33495140075684"/> <y:Fill color="#F5F5F5" transparent="false"/> <y:BorderStyle color="#000000" type="dashed" width="1.0"/> - <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</y:NodeLabel> + <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="68.19677734375" x="-9.098388671875" y="0.0">Folder 4</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/> @@ -49,7 +49,7 @@ <y:Geometry height="30.0" width="324.9258883570935" x="-1249.511914911339" y="-169.79793039957679"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="219.583984375" x="52.67095199104665" y="5.93359375">Завантаження конфігурації додатка<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="233.646484375" x="45.63970199104665" y="6.015625">Завантаження конфігурації додатка<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -66,10 +66,10 @@ <y:ProxyAutoBoundsNode> <y:Realizers active="0"> <y:GroupNode> - <y:Geometry height="308.66601562499994" width="330.35133296005984" x="-1254.9373595143054" y="35.983324686686274"/> + <y:Geometry height="308.45312499999994" width="330.35133296005984" x="-1254.9373595143054" y="36.196215311686274"/> <y:Fill color="#FFEFD6" transparent="false"/> <y:BorderStyle hasColor="false" type="dashed" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FF9900" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="330.35133296005984" x="0.0" y="0.0">Створення екземпляру додатка</y:NodeLabel> + <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FF9900" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="330.35133296005984" x="0.0" y="0.0">Створення екземпляру додатка</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/> @@ -79,7 +79,7 @@ <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/> <y:Fill color="#F5F5F5" transparent="false"/> <y:BorderStyle color="#000000" type="dashed" width="1.0"/> - <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel> + <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="68.19677734375" x="-9.098388671875" y="0.0">Folder 5</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/> @@ -95,7 +95,7 @@ <y:Geometry height="30.0" width="294.92588835709347" x="-1234.511914911339" y="72.64934031168627"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="49.814453125" x="122.55571761604665" y="5.93359375">preInit()<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="55.01171875" x="119.95708480354665" y="6.015625">preInit()<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -112,7 +112,7 @@ <y:Geometry height="30.0" width="294.92588835709347" x="-1234.511914911339" y="122.64524027506516"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="192.185546875" x="51.37017074104665" y="5.93359375">Реєстрація обробника помилок<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="202.978515625" x="45.97368636604665" y="6.015625">Реєстрація обробника помилок<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -129,7 +129,7 @@ <y:Geometry height="30.0" width="294.92588835709347" x="-1234.511914911339" y="174.96110788981125"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="226.3515625" x="34.28716292854665" y="5.93359375">Налаштування властивостей додатка<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="242.13671875" x="26.39458480354665" y="6.015625">Налаштування властивостей додатка<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -146,7 +146,7 @@ <y:Geometry height="30.0" width="294.92588835709347" x="-1234.511914911339" y="226.56779181162517"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.677734375" x="132.12407699104665" y="5.93359375">init()<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="33.58984375" x="130.66802230354665" y="6.015625">init()<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -163,7 +163,7 @@ <y:Geometry height="30.0" width="294.9258883570935" x="-1234.511914911339" y="280.4944433848063"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="68.283203125" x="113.32134261604665" y="5.93359375">bootstrap()<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="71.869140625" x="111.52837386604665" y="6.015625">bootstrap()<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -182,10 +182,10 @@ <y:ProxyAutoBoundsNode> <y:Realizers active="0"> <y:GroupNode> - <y:Geometry height="410.9838918050131" width="324.9258883570935" x="-846.5860265542456" y="-169.08748118082679"/> + <y:Geometry height="411.7397511800131" width="324.9258883570935" x="-846.5860265542456" y="-168.87459055582679"/> <y:Fill color="#FFEFD6" transparent="false"/> <y:BorderStyle hasColor="false" type="dashed" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FF9900" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="324.9258883570935" x="0.0" y="0.0">Виконання додатка</y:NodeLabel> + <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#FF9900" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="324.9258883570935" x="0.0" y="0.0">Виконання додатка</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/> @@ -195,7 +195,7 @@ <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/> <y:Fill color="#F5F5F5" transparent="false"/> <y:BorderStyle color="#000000" type="dashed" width="1.0"/> - <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 3</y:NodeLabel> + <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="68.19677734375" x="-9.098388671875" y="0.0">Folder 3</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/> @@ -211,7 +211,7 @@ <y:Geometry height="30.0" width="294.9258883570935" x="-831.5860265542456" y="-132.42146555582667"/> <y:Fill color="#99CC00" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="148.84375" x="73.04106917854676" y="5.93359375">EVENT_BEFORE_REQUEST<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="176.95703125" x="58.98442855354676" y="6.015625">EVENT_BEFORE_REQUEST<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -228,10 +228,10 @@ <y:ProxyAutoBoundsNode> <y:Realizers active="0"> <y:GroupNode> - <y:Geometry height="203.666015625" width="294.9258883570935" x="-831.5860265542456" y="-78.08748118082679"/> + <y:Geometry height="203.453125" width="294.9258883570935" x="-831.5860265542456" y="-77.87459055582679"/> <y:Fill color="#99336635" transparent="false"/> <y:BorderStyle hasColor="false" type="dashed" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#993366" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#FFFFFF" visible="true" width="294.9258883570935" x="0.0" y="0.0">Обробка запиту</y:NodeLabel> + <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#993366" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#FFFFFF" visible="true" width="294.9258883570935" x="0.0" y="0.0">Обробка запиту</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/> @@ -241,7 +241,7 @@ <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/> <y:Fill color="#F5F5F5" transparent="false"/> <y:BorderStyle color="#000000" type="dashed" width="1.0"/> - <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</y:NodeLabel> + <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.453125" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="68.19677734375" x="-9.098388671875" y="0.0">Folder 4</y:NodeLabel> <y:Shape type="roundrectangle"/> <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/> <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/> @@ -257,7 +257,7 @@ <y:Geometry height="30.0" width="264.9258883570935" x="-816.5860265542456" y="-41.421465555826785"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="199.9375" x="32.49419417854676" y="5.93359375">Запит -> маршрут та параметри<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="261.44921875" x="1.7383348035467634" y="6.015625">Розбір запиту на маршрут та параметри<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -274,7 +274,7 @@ <y:Geometry height="30.0" width="264.9258883570935" x="-816.5860265542456" y="18.578534444173215"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="228.267578125" x="18.329155116046763" y="5.93359375">Створення модуля, контролера та дії<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="240.109375" x="12.408256678546763" y="6.015625">Створення модуля, контролера та дії<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -291,7 +291,7 @@ <y:Geometry height="30.0" width="264.9258883570935" x="-816.5860265542456" y="72.64934031168627"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="86.78125" x="89.07231917854676" y="5.93359375">Виконання дії<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="93.138671875" x="85.89360824104676" y="6.015625">Виконання дії<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -310,7 +310,7 @@ <y:Geometry height="30.0" width="294.9258883570935" x="-831.5860265542456" y="149.20206960042316"/> <y:Fill color="#99CC00" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="141.982421875" x="76.47173324104676" y="5.93359375">EVENT_AFTER_REQUEST<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="166.2109375" x="64.35747542854676" y="6.015625">EVENT_AFTER_REQUEST<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -327,7 +327,7 @@ <y:Geometry height="30.0" width="294.92588835709347" x="-831.5860265542456" y="196.89641062418633"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="263.611328125" x="15.657280116046763" y="5.93359375">Відправка відповіді кінцевому користувачу<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" modelName="custom" textColor="#000000" visible="true" width="152.072265625" x="71.42681136604676" y="-0.96875">Відправлення відповіді кінцевому користувачу<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -346,7 +346,7 @@ <y:Geometry height="30.0" width="324.9258883570935" x="-846.5860265542456" y="319.2728754679363"/> <y:Fill color="#FFFFFF" transparent="false"/> <y:BorderStyle color="#000000" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="173.9921875" x="75.46685042854676" y="5.93359375">Завершення обробки запиту<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="185.095703125" x="69.91509261604676" y="6.015625">Завершення обробки запиту<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -375,7 +375,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#666666" type="line" width="2.0"/> <y:Arrows source="none" target="standard"/> - <y:EdgeLabel alignment="center" backgroundColor="#99CCFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="120.455078125" x="-137.9645704847153" y="78.82421875000006">Масив конфігурації<y:LabelModel> + <y:EdgeLabel alignment="center" backgroundColor="#99CCFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="128.259765625" x="-141.86749983556274" y="79.01269531250006">Масив конфігурації<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -493,7 +493,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#666666" type="line" width="2.0"/> <y:Arrows source="none" target="standard"/> - <y:EdgeLabel alignment="center" backgroundColor="#99CCFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="64.90234375" x="-96.45114634054266" y="28.626707953545406">Exit status<y:LabelModel> + <y:EdgeLabel alignment="center" backgroundColor="#99CCFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="94.404296875" x="-111.20212290304266" y="28.228032788725613">Статус виходу<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> diff --git a/docs/guide-uk/images/application-lifecycle.png b/docs/guide-uk/images/application-lifecycle.png index f475c0e40b..805fa1c346 100644 Binary files a/docs/guide-uk/images/application-lifecycle.png and b/docs/guide-uk/images/application-lifecycle.png differ diff --git a/docs/guide-uk/images/application-structure.graphml b/docs/guide-uk/images/application-structure.graphml index d5d0d32f09..cc0ddf86aa 100644 --- a/docs/guide-uk/images/application-structure.graphml +++ b/docs/guide-uk/images/application-structure.graphml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"> - <!--Created by yEd 3.12.2--> +<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"> + <!--Created by yEd 3.14.2--> <key for="graphml" id="d0" yfiles.type="resources"/> <key for="port" id="d1" yfiles.type="portgraphics"/> <key for="port" id="d2" yfiles.type="portgeometry"/> @@ -20,7 +20,7 @@ <y:Geometry height="35.0" width="100.0" x="872.1807999999999" y="-14.764159999999947"/> <y:Fill color="#FFCC99" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="69.712890625" x="15.1435546875" y="1.3671875">компонент + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" modelName="custom" textColor="#000000" visible="true" width="73.017578125" x="13.4912109375" y="1.53125">компонент додатка<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> @@ -38,7 +38,8 @@ <y:Geometry height="35.0" width="100.0" x="702.4223999999999" y="-91.97375999999994"/> <y:Fill color="#FFCC00" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="70.28125" x="14.859375" y="8.43359375">вхідний скрипт<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" modelName="custom" textColor="#000000" visible="true" width="52.744140625" x="23.6279296875" y="1.53125">вхідний +скрипт<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -55,7 +56,7 @@ <y:Geometry height="35.0" width="100.0" x="702.4223999999999" y="-14.764159999999947"/> <y:Fill color="#FF9900" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="68.21875" x="15.890625" y="8.43359375">додаток<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="54.51953125" x="22.740234375" y="8.515625">додаток<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -72,7 +73,7 @@ <y:Geometry height="35.0" width="100.0" x="702.4223999999999" y="62.44544000000004"/> <y:Fill color="#FF9900" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="60.267578125" x="19.8662109375" y="8.433593750000007">контролер<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="70.673828125" x="14.6630859375" y="8.515625000000007">контролер<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -89,7 +90,7 @@ <y:Geometry height="35.0" width="100.0" x="872.1807999999999" y="62.44544000000005"/> <y:Fill color="#FFCC99" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.43359375" x="34.283203125" y="8.43359375">фільтр<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="45.70703125" x="27.146484375" y="8.515625">фільтр<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -106,7 +107,7 @@ <y:Geometry height="35.0" width="100.0" x="532.664" y="23.901600000000087"/> <y:Fill color="#FF9900" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="47.728515625" x="26.1357421875" y="8.43359375">модуль<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="49.1640625" x="25.41796875" y="8.515625">модуль<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -123,7 +124,7 @@ <y:Geometry height="35.0" width="100.0" x="618.4047999999991" y="139.65504000000004"/> <y:Fill color="#99CC00" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.611328125" x="35.1943359375" y="8.43359375">представлення<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="99.73046875" x="0.134765625" y="8.515625">представлення<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -140,7 +141,7 @@ <y:Geometry height="35.0" width="100.0" x="786.161599999999" y="139.65504000000004"/> <y:Fill color="#99CCFF" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="40.28125" x="29.859375" y="8.43359375">модель<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="49.205078125" x="25.3974609375" y="8.515625">модель<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -157,7 +158,7 @@ <y:Geometry height="35.0" width="100.0" x="532.664" y="216.86464000000004"/> <y:Fill color="#99CC00" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.923828125" x="28.5380859375" y="8.43359375">віджет<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="46.76171875" x="26.619140625" y="8.515625">віджет<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -174,7 +175,8 @@ <y:Geometry height="35.0" width="100.0" x="702.4223999999999" y="216.86464000000004"/> <y:Fill color="#99CC00" transparent="false"/> <y:BorderStyle hasColor="false" type="line" width="1.0"/> - <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="77.986328125" x="11.0068359375" y="8.43359375">asset bundle<y:LabelModel> + <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" modelName="custom" textColor="#000000" visible="true" width="60.07421875" x="19.962890625" y="1.53125">колекція +ресурсів<y:LabelModel> <y:SmartNodeLabelModel distance="4.0"/> </y:LabelModel> <y:ModelParameter> @@ -191,7 +193,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="22.97265625" x="2.5392475585936154" y="-25.635106635436955">1:1<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="23.3125" x="2.369325683593729" y="-25.50056340427648">1:1<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -209,7 +211,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-48.97602119140629" y="-23.09200633216852">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-49.11136368282337" y="-23.00997508216852">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -227,7 +229,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="15.193962466822086" y="30.674065521861735">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="15.0445484043222" y="30.756096771861735">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -245,7 +247,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="2.7719538085937074" y="-26.04470613037104">1..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="2.6225397460937074" y="-25.9149367156797">1..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -263,7 +265,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-57.39355288912964" y="-63.846685518352906">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-57.41127147104078" y="-63.734752657990555">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -283,7 +285,7 @@ </y:Path> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-17.888505566406252" y="-42.41848039245597">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-18.037919628906252" y="-42.33644914245597">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -301,7 +303,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-12.223966589163297" y="-24.76668258085064">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-12.453120279866653" y="-24.611373055849555">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -319,7 +321,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-9.313146217848043" y="-26.098020948340803">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-9.3884511336189" y="-25.947659244081734">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -337,7 +339,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-9.797233483694527" y="-25.82443358614151">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-9.871202356268668" y="-25.673070518330803">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -355,7 +357,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-15.633139430038" y="-24.050683308790553">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-15.868632344503453" y="-23.891138042957834">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -373,7 +375,7 @@ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-47.337621191406356" y="-22.682408449706998">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-47.487035253906356" y="-22.600377199706998">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -386,13 +388,12 @@ </data> </edge> <edge id="e11" source="n9" target="n8"> - <data key="d9"/> <data key="d10"> <y:PolyLineEdge> <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-44.265598339843905" y="-19.61040553222648">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-44.441396598124356" y="-19.52837428222651">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> @@ -405,13 +406,12 @@ </data> </edge> <edge id="e12" source="n7" target="n6"> - <data key="d9"/> <data key="d10"> <y:PolyLineEdge> <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/> <y:LineStyle color="#000000" type="line" width="1.0"/> <y:Arrows source="none" target="diamond"/> - <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.96484375" x="-44.81721357421975" y="-19.610410805664003">0..*<y:LabelModel> + <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.263671875" x="-44.9806249718132" y="-19.528379555664003">0..*<y:LabelModel> <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/> </y:LabelModel> <y:ModelParameter> diff --git a/docs/guide-uk/images/application-structure.png b/docs/guide-uk/images/application-structure.png index a748ea184e..a88ba69548 100644 Binary files a/docs/guide-uk/images/application-structure.png and b/docs/guide-uk/images/application-structure.png differ diff --git a/docs/guide-uk/images/start-country-list.png b/docs/guide-uk/images/start-country-list.png index 6994da2103..375419414d 100644 Binary files a/docs/guide-uk/images/start-country-list.png and b/docs/guide-uk/images/start-country-list.png differ diff --git a/docs/guide-uk/images/tutorial-console-help.png b/docs/guide-uk/images/tutorial-console-help.png new file mode 100644 index 0000000000..15b8b66a03 Binary files /dev/null and b/docs/guide-uk/images/tutorial-console-help.png differ diff --git a/docs/guide-uk/intro-upgrade-from-v1.md b/docs/guide-uk/intro-upgrade-from-v1.md index edea6ae3ae..8d025197f9 100644 --- a/docs/guide-uk/intro-upgrade-from-v1.md +++ b/docs/guide-uk/intro-upgrade-from-v1.md @@ -1,23 +1,23 @@ Оновлення із версії 1.1 ======================= -Між версіями 1.1 і 2.0 існує багато відмінностей, так як Yii був повністю переписаний для версії 2.0. +Між версіями 1.1 і 2.0 існує багато відмінностей, оскільки Yii був повністю переписаний для версії 2.0. Таким чином, оновлення з версії 1.1 не є таким же тривіальним, як оновлення між мінорними версіями. У цьому посібнику з оновлення наведено основні відмінності між двома версіями. Якщо раніше ви не використовували Yii 1.1, ви можете пропустити цей розділ і перейти до розділу "[Встановлення Yii](start-installation.md)". -Також врахуйте, що Yii 2.0 включає більше нового функціоналу, ніж той, що буде описано тут. Наполегливо рекомендується, -що ви прочитаєте весь посібник, щоб дізнатися який функціонал було додано. Можливо, що необхідний функціонал, -який ви до цього розробляли самі, тепер є частиною фреймворка. +Також врахуйте, що Yii 2.0 надає більше нового функціоналу, ніж той, що буде описано тут. Наполегливо рекомендується +прочитати посібник повністю, щоб дізнатися весь доступний функціонал. Можливо, що необхідний функціонал, +який ви до цього розробляли самі, тепер є частиною фреймворку. Встановлення ------------ -Yii 2.0 повністю заснований на [Composer](https://getcomposer.org/), який де факто є менеджером залежностей для PHP. -Установка фреймворка, також як і розширень, здійснюється через Composer. Більш детальні відомості по встановленню Yii 2.0 +Yii 2.0 використовує [Composer](https://getcomposer.org/), який де-факто є менеджером пакунків для PHP. +Встановлення фреймворку, також як і розширень, здійснюється через Composer. Більш детальні відомості по встановленню Yii 2.0 приведені в розділі [Встановлення Yii](start-installation.md). Відомості про те, як створювати розширення для Yii 2.0 або адаптувати вже наявні розширення для версії 1.1 під версію 2.0, наведені в розділі [Створення розширень](structure-extensions.md#creating-extensions). @@ -39,17 +39,17 @@ Yii 2.0 використовує PHP 5.4 або вище, який включа - [Пізнє статичне звʼязування (LSB)](http://php.net/manual/en/language.oop5.late-static-bindings.php); - [Класи для дати та часу](http://php.net/manual/en/book.datetime.php); - [Трейти](http://php.net/manual/en/language.oop5.traits.php); -- [Інтернаціонализація (Intl)](http://php.net/manual/en/book.intl.php); Yii 2.0 використовує розширення PHP `intl` +- [Інтернаціонализація (intl)](http://php.net/manual/en/book.intl.php); Yii 2.0 використовує розширення PHP `intl` для різного функціоналу інтернаціоналізації. Простори імен ------------- -Одним з основних змін в Yii 2.0 є використання просторів імен. Майже кожен клас фреймворку знаходиться у просторі імен, -наприклад, `yii\web\Request`. Префікс "С" більше не використовується в іменах класів. Угода іменування відповідає -структурі каталога, в якій розташовується клас. Наприклад, `yii\web\Request` означає, що відповідний клас знаходиться -у файлі `web/Request.php` в каталогу Yii фреймворка. (Завдяки завантажувачу класів Yii, ви можете використовувати +Однією з основних змін в Yii 2.0 є використання просторів імен. Майже кожен клас фреймворку знаходиться у просторі імен, +наприклад, `yii\web\Request`. Префікс "С" більше не використовується в іменах класів. Схема іменування +зараз відповідає структурі директорій. Наприклад, `yii\web\Request` означає, що відповідний клас знаходиться +у файлі `web/Request.php` в директорії фреймворку. (Завдяки завантажувачу класів Yii, ви можете використовувати будь-який клас фреймворку без необхідності безпосередньо підключати його). @@ -57,8 +57,8 @@ Yii 2.0 використовує PHP 5.4 або вище, який включа ------------------- В Yii 2.0 клас `CComponent` із версії 1.1 був розділений на два класи: [[yii\base\Object]] і [[yii\base\Component]]. -Клас [[yii\base\Object|Object]] є простим базовим класом, який дозволяє використовувати -[геттери та сеттери](concept-properties.md) для властивостей. Клас [[yii\base\Component|Component]] наслідується +Клас [[yii\base\Object|Object]] є простим базовим класом, який дозволяє керувати [властивостями обʼєкта](concept-properties.md) +за допомогою геттерів та сеттерів. Клас [[yii\base\Component|Component]] наслідується від класа [[yii\base\Object|Object]] та підтримує [події](concept-events.md) та [поведінки](concept-behaviors.md). Якщо вашому класу не потрібно використовувати функціонал подій та поведінок, ви можете використати @@ -95,7 +95,7 @@ class MyClass extends \yii\base\Object ключ-значення для ініціалізації властивостей обʼєкта. Ви можете перевизначити метод [[yii\base\Object::init()|init()]] для ініціалізації обʼєкту після того, як до нього була застосована конфігурація. -Слідуючи цій угоді, ви зможете створювати і конфігурувати нові обʼєкти за допомогою масиву конфігурації: +Дотримуючись цієї угоди, ви зможете створювати і конфігурувати нові обʼєкти за допомогою масиву конфігурації: ```php $object = Yii::createObject([ @@ -105,14 +105,14 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` -Більш детальна інформація про конфігурацію представлена у розділі [Конфігурації обʼєктів](concept-configurations.md). +Більш детальна інформація про конфігурацію представлена у розділі [Конфігурації](concept-configurations.md). Події ----- -В Yii1, події створювалися за допомогою оголошення методу `on` (наприклад, `onBeforeSave`). -В Yii2 ви можете тепер використовувати будь-яке імʼя події. Ви ініціюєте подію за допомогою виклику методу +В Yii 1, події створювалися за допомогою оголошення методу `on` (наприклад, `onBeforeSave`). +В Yii 2 ви можете тепер використовувати будь-яке імʼя події. Ви ініціюєте подію за допомогою виклику методу [[yii\base\Component::trigger()|trigger()]]. ```php @@ -120,48 +120,47 @@ $event = new \yii\base\Event; $component->trigger($eventName, $event); ``` -Для прикріплення обробника події використовуйте метод [[yii\base\Component::on()|on()]]. +Для приєднання обробника події використовуйте метод [[yii\base\Component::on()|on()]]. ```php $component->on($eventName, $handler); -// To detach the handler, use: +// прибрати обробник: // $component->off($eventName, $handler); ``` -Є також і інші покращення у функціоналі подій. Більш детальна інформація про конфігурація представлена у розділі -[Події](concept-events.md). +Є також інші покращення у функціоналі подій. Більш детальна інформація представлена у розділі [Події](concept-events.md). Псевдоніми шляху ---------------- -Yii 2.0 розширює спосіб використання псевдонімів шляху як для файлів і каталогів, так і для URL. +Yii 2.0 розширює спосіб використання псевдонімів шляху як для файлів та директорій, так і для URL. У Yii 2.0 тепер також потрібно, щоб імʼя псевдоніма починалося із символу `@`, для розмежування псевдонімів від -звичайних шляхів файлів/каталогів і URL. Наприклад, псевдонім `@yii` відповідає каталогу встановлення Yii. -Псевдоніми шляху використовуються в багатьох місцях коду Yii. Наприклад, [[yii\caching\FileCache::cachePath]] -може використовувати як псевдонім шляху, так і звичайний шлях до каталогу. +звичайних шляхів файлів/директорій і URL. Наприклад, псевдонім `@yii` відповідає директорії встановлення Yii. +Псевдоніми шляху підтримуються в багатьох місцях коду Yii. Наприклад, [[yii\caching\FileCache::cachePath]] +може мати значення як псевдоніму шляху, так і звичайного шляху до директорії. -Псевдоніми шляху тісно повʼязані з простором імен класів. Рекомендується, що ви визначите псевдонім шляху +Псевдоніми шляху тісно повʼязані з простором імен класів. Рекомендується призначити псевдонім шляху для кожного базового простору імен, таким чином завантажувач класів Yii може використовуватися без будь-якої -додаткової конфігурації. Наприклад, `@yii` відповідає каталогу встановлення Yii, тому клас `yii\webRequest` -може бути завантажений. Якщо ви використовуєте сторонні бібліотеки, такі як Zend Framework, ви можете також визначити -псевдонім шляху `@Zend`, який відповідає каталогу встановлення фреймворка. Одного разу зробивши це - Yii буде +додаткової конфігурації. Наприклад, оскільки `@yii` посилається на директорію де встановленно Yii, клас `yii\web\Request` +може бути завантажений автоматично. Якщо ви використовуєте сторонні бібліотеки, такі як Zend Framework, ви можете також визначити +псевдонім шляху `@Zend`, який відповідає директорії встановлення цього фреймворку. Після чого Yii буде здатний автоматично завантажувати будь-який клас Zend Framework. -Більш детальна інформація про конфігурації представлена у розділі [Псевдонімів](concept-aliases.md). +Більш детальна інформація про псевдоніми шляху представлена у розділі [Псевдоніми](concept-aliases.md). Представлення ------------- Однією із основних змін в Yii2 є те, що спеціальна змінна `$this` у представленні більше не відповідає -поточному контролеру або віджету. Замість цього, `$this` тепер відповідає обʼєкту *представлення*, нової можливості, +поточному контролеру або віджету. Замість цього, `$this` тепер відповідає обʼєкту *представлення*, новій можливості, яка була введена у версії 2.0. Обʼєкт представлення має тип [[yii\web\View]], який являє собою частину *представлення* -у шаблоні проектування MVC. Якщо ви хочете отримати доступ до контролера або віджету, то використовуйте вираз `$this->context`. +у шаблоні проектування Модель-Представления-Контролер (MVC). Якщо ви хочете отримати доступ до контролера або віджету, то використовуйте вираз `$this->context`. -Для рендеринга часткових представлень тепер використовується метод `$this->render()`, а не `$this->renderPartial()`. -Результат виклику методу `render` тепер повинен бути виведений безпосередньо, так як `render` повертає результат -рендеринга, а не відображає його одразу. Наприклад, +Для формування часткових представлень тепер використовується метод `$this->render()`, а не `$this->renderPartial()`. +Результат виклику методу `render` тепер повинен бути виведений безпосередньо, тому що `render` повертає результат, +а не відображає його одразу. Наприклад, ```php echo $this->render('_item', ['item' => $item]); @@ -169,7 +168,7 @@ echo $this->render('_item', ['item' => $item]); Крім використання PHP у якості основного шаблонізатору, Yii 2.0 також включає офіційні розширення для основних популярних шаблонізаторів: Smarty і Twig. Шаблонізатор Prado більше не підтримується. Для використання вказаних -шаблонізаторів вам необхідно налаштувати компонент додатка `view` за допомогою вказівки властивостей +шаблонізаторів вам необхідно сконфігурувати компонент додатка `view` за допомогою налаштування властивості [[yii\base\View::$renderers|View::$renderers]]. Більш детальна інформація представлена у розділі [Шаблонізатори](tutorial-template-engines.md). @@ -196,15 +195,15 @@ public function scenarios() ``` У прикладі вище, оголошено два сценарії: `backend` і `frontend`. Для сценарію `backend` обидва атрибута `email` і `role` -є безпечними і можуть бути масово привласнені. Для сценарію `frontend` атрибут `email` може бути масово присвоєний, -а атрибут `role` - ні. Обидва атрибути `email` та `role` повинні бути перевірені за допомогою правил валідації. +є безпечними та можуть бути масово призначені. Для сценарію `frontend` атрибут `email` може бути масово призначений, +але атрибут `role` - ні. Обидва атрибути `email` та `role` повинні бути перевірені за допомогою правил валідації. Метод [[yii\base\Model::rules()|rules()]] як і раніше використовується для оголошення правил валідації. -Зверніть увагу, що у звʼязку з появою нового методу [[yii\base\Model::scenarios()|scenarios()]] - +Зверніть увагу, що у звʼязку з появою нового методу [[yii\base\Model::scenarios()|scenarios()]], більше не підтримується валідатор `unsafe`. У більшості випадків вам не потрібно перевизначати метод [[yii\base\Model::scenarios()|scenarios()]], -якщо метод [[yii\base\Model::rules()|rules()]] повністю вказує всі існуючі сценарії і якщо немає потреби +якщо метод [[yii\base\Model::rules()|rules()]] повністю вказує всі сценарії, що існують, та якщо немає потреби в оголошенні атрибутів небезпечними. Більш детальна інформація представлена у розділі [Моделі](structure-models.md). @@ -216,7 +215,7 @@ public function scenarios() В якості базового класу для контролерів в Yii 2.0 використовується [[yii\web\Controller]], який є аналогічним `CController` у Yii 1.1. Базовим класом для всіх дій є [[yii\base\Action]]. -Однією із основних змін є те, що дія контролера тепер має повернути результат замість того, щоб напряму виводити його: +Однією із основних змін є те, що дія контролера тепер має повернути результат замість того, щоб безпосередньо виводити його: ```php @@ -269,8 +268,10 @@ ActiveForm::end(); представлення із темізованим файлом. Наприклад, якщо використовується співставлення шляхів `['/web/views' => '/web/themes/basic']`, то темізована версія файлу представлення `/web/views/site/index.php` буде знаходитися у `/web/themes/basic/site/index.php`. З цієї причини теми можуть бути застосовані до будь-якого файлу -представлення, навіть до представлення, яке відрендерене всередині контексту контролера або віджету. Також, більше -не існує компонента `CThemeManager`. Замість цього, `theme` є конфігурованою властивістю компонента додатка `view`. +представлення, навіть до представлення, яке сформоване за межами контексту контролера або віджету. + +Також, більше не існує компонента `CThemeManager`. Замість цього є властивість `theme` компонента додатка `view`, +яку можна сконфігурувати. Більш детальна інформація представлена у розділі [Темізація](output-theming.md). @@ -278,7 +279,7 @@ ActiveForm::end(); Консольні додатки ----------------- -Консольні додатки тепер організовані як контролери, аналогічно веб додаткам. Консольні контролери +Консольні додатки тепер організовані як контролери, аналогічно веб-додаткам. Консольні контролери повинні бути успадковані від класу [[yii\console\Controller]], аналогічного `CConsoleCommand` у версії 1.1. Для виконання консольної команди, використовуйте `yii <маршрут>`, де `<маршрут>` це маршрут контролера @@ -286,7 +287,7 @@ ActiveForm::end(); контролера, у той час, як іменовані аргументи будуть передані у відповідності із оголошеннями у [[yii\console\Controller::options()]]. -Yii 2.0 підтримує автоматичну генерацію довідкової інформації із блоків коментарів. +Yii 2.0 підтримує автоматичне генерування довідкової інформації із блоків коментарів. Більш детальна інформація представлена у розділі [Консольні команди](tutorial-console.md). @@ -295,10 +296,10 @@ I18N ---- У Yii 2.0 були прибрані вбудовані форматтери часу та чисел на користь -[PECL intl PHP розширення](http://pecl.php.net/package/intl). +[PECL розширення PHP intl](http://pecl.php.net/package/intl). -Переклад повідомлень тепер здійснюється через компонент додатка `i18n`. Даний компонент управляє безліччю вихідних сховищ -повідомлень, що дозволяє вам використовувати різні сховища для вихідних повідомлень залежно від категорії повідомлення. +Переклад повідомлень тепер здійснюється через компонент додатка `i18n`. Даний компонент управляє наборами джерел +повідомлень, що дозволяє вам використовувати різні джерела повідомлень, залежно від категорії повідомлення. Більш детальна інформація представлена у розділі [Інтернаціоналізація](tutorial-i18n.md). @@ -306,8 +307,8 @@ I18N Фільтри дій ----------- -Фільтри дій тепер зроблені за допомогою поведінок. Для визначення нового фільтру - успадкуйте від [[yii\base\ActionFilter]]. -Для використання фільтра - прикріпіть його до контролера у якості поведінки. Наприклад, для використання фільтра +Фільтри дій тепер зроблені за допомогою поведінок. Новостворюваний фільтр необхідно успадкувати від [[yii\base\ActionFilter]]. +Для використання фільтра - приєднайте його до контролера у якості поведінки. Наприклад, для використання фільтра [[yii\filters\AccessControl]] слід зробити наступне: ```php @@ -330,13 +331,13 @@ public function behaviors() Ресурси ------- -У Yii 2.0 представлена нова можливість *звʼязки ресурсів*, яка замінює концепт пакетів скриптів у Yii 1.1. +У Yii 2.0 представлена нова можливість *колекції ресурсів*, яка замінює концепт пакунків скриптів у Yii 1.1. -Звʼязка ресурсів - це колекція файлів ресурсів (наприклад, Javascript файли, CSS файли, файли зображень, і т. п.) -у певній папці. Кожна звʼязка ресурсів представлена​класом, успадкованим від [[yii\web\AssetBundle]]. -Звʼязка ресурсів стає доступною через веб, за допомогою реєстрації її методом [[yii\web\AssetBundle::register()]]. -На відміну від Yii 1.1, сторінка, яка реєструє звʼязку ресурсів, автоматично буде містити посилання на -Javascript і CSS файли, які зазначені у звʼязці. +Колекція ресурсів - це колекція файлів ресурсів (наприклад: Javascript файли, CSS файли, файли зображень, і т. п.) +у певній директорії. Кожна колекція ресурсів представлена​ класом, успадкованим від [[yii\web\AssetBundle]]. +Колекція ресурсів стає доступною через веб, за допомогою реєстрації її методом [[yii\web\AssetBundle::register()]]. +На відміну від Yii 1.1, сторінка, яка реєструє колекцію ресурсів, автоматично буде містити посилання на +Javascript і CSS файли, які зазначені у колекції. Більш детальна інформація представлена у розділі [Ресурси](structure-assets.md). @@ -352,7 +353,7 @@ Javascript і CSS файли, які зазначені у звʼязці. * [[yii\helpers\FileHelper]] * [[yii\helpers\Json]] -Більш детальна інформація представлена у розділі [Хелпери](helper-overview.md). +Більш детальна інформація представлена у розділі [Огляд хелперів](helper-overview.md). Форми @@ -373,7 +374,7 @@ Yii 2.0 вводить нове поняття *поле* для побудов <?php yii\widgets\ActiveForm::end(); ?> ``` -Більш детальна інформація представлена у розділі [Робота з формами](input-forms.md). +Більш детальна інформація представлена у розділі [Створення форм](input-forms.md). Конструктор запитів @@ -381,7 +382,7 @@ Yii 2.0 вводить нове поняття *поле* для побудов У версії 1.1, побудова запиту була розкидана серед декількох класів, включаючи `CDbCommand`, `CDbCriteria` та `CDbCommandBuilder`. У Yii 2.0 запит до БД представлений в рамках обʼєкта [[yii\db\Query|Query]], -який може бути перетворений у SQL вираз за допомогою [[yii\db\QueryBuilder|QueryBuilder]]. Наприклад, +який може бути перетворений у SQL-вираз за допомогою [[yii\db\QueryBuilder|QueryBuilder]]. Наприклад, ```php $query = new \yii\db\Query(); @@ -402,10 +403,10 @@ $rows = $command->queryAll(); Active Record ------------- -У Yii 2.0 внесено безліч змін в роботу [Active Record](db-active-record.md). Два основних з них включають в себе -побудову запитів і роботу із звʼязками. +У Yii 2.0 внесено безліч змін в роботу [Active Record](db-active-record.md). Основними двома є +побудова запитів і робота із звʼязками. -Клас `CDbCriteria` у версії 1.1 був замінений на [[yii\db\ActiveQuery]] у Yii 2.0. Це клас успадковується від +Клас `CDbCriteria` у версії 1.1 був замінений на [[yii\db\ActiveQuery]] у Yii 2.0. Цей клас успадковується від [[yii\db\Query]] і таким чином отримує всі методи, які необхідні для побудови запиту. Для побудови запиту вам слід викликати метод [[yii\db\ActiveRecord::find()]]: @@ -417,7 +418,7 @@ $customers = Customer::find() ->all(); ``` -Для оголошення звʼязку слід просто оголосити геттер, який повертає обʼєкт [[yii\db\ActiveQuery|ActiveQuery]]. +Для оголошення звʼязку слід просто призначити геттер, який повертає обʼєкт [[yii\db\ActiveQuery|ActiveQuery]]. Імʼя властивості, яке визначене геттером, представляє собою назву звʼязку. Наприклад, наступний код оголошує звʼязок `orders` (у версії 1.1 вам потрібно було б оголосити звʼязки в одному центральному місці - `relations()`): @@ -445,15 +446,15 @@ Yii 2.0 здійснює жадібне завантаження (eager loading) Замість того, щоб повертати обʼєкти [[yii\db\ActiveRecord|ActiveRecord]], ви можете використовувати метод [[yii\db\ActiveQuery::asArray()|asArray()]] при побудові запиту, для вибірки великої кількості записів. -Це змусить повернути результат запиту у якості масива, що може суттєво знизити час, який потрібен ЦПУ і памʼяті +Це змусить повернути результат запиту у якості масиву, що може суттєво знизити витрати процесорного часу і памʼяті при великій кількості записів. Наприклад: ```php $customers = Customer::find()->asArray()->all(); ``` -Ще одна зміна повʼязана з тим, що ви більше не можете визначати значення по-замовчуванням як властивостей. -Ви повинні встановити їх у методі `init` вашого класу, якщо це потрібно. +Ще одна зміна повʼязана з тим, що ви більше не можете визначати значення атрибутів за замовчуванням з допомогою public властивостей. +Ви можете встановити їх у методі `init` вашого класу запису, якщо це потрібно. ```php public function init() @@ -463,21 +464,21 @@ public function init() } ``` -Також у версії 1.1 були деякі проблеми із перевизначенням конструктора ActiveRecord. Дані проблеми не присутні +Також у версії 1.1 були деякі проблеми із перевизначенням конструктора ActiveRecord. Дані проблеми відсутні у версії 2.0. Зверніть увагу, що при додаванні параметрів у конструктор, вам, можливо, знадобиться перевизначити метод [[yii\db\ActiveRecord::instantiate()]]. Перевизначення може не знадобитися, якщо параметри, передані в конструктор матимуть значення за умовчанням, наприклад `null`. -Існує також безліч інших покращень у ActiveRecord. Більш детальна інформація про конфігурацію представлена у розділі +Є також безліч інших змін та покращень у ActiveRecord. Більш детальна інформація про них представлена у розділі [Active Record](db-active-record.md). Поведінки Active Record ----------------------- -У версії 2.0 ми позбулися від класу базової поведінки `CActiveRecordBehavior`. Якщо ви хочете створити поведінку -Active Record, ви повинні будете розширити класс `yii\base\Behavior`. Якщо класу поведінки необхідно реагувати на -деякі події власника, ви повинні перевизначити метод `events()`, як показано нижче, +У версії 2.0 розробники позбулися класу базової поведінки `CActiveRecordBehavior`. Новостворюваний клас поведінки Active Record +має бути успадкованим безпосередньо від класу `yii\base\Behavior`. Якщо класу поведінки необхідно реагувати на +деякі події власника, потрібно перевизначити метод `events()`, як показано нижче, ```php namespace app\components; @@ -508,17 +509,17 @@ User та IdentityInterface ------------------------- Клас `CWebUser` у версії 1.1 тепер замінений класом [[yii\web\User]], а також більше не існує класу `CUserIdentity`. -Замість цього, ви повинні надати реалізацію інтерфейсу [[yii\web\IdentityInterface]], що набагато простіше у використанні. +Замість цього, необхідно реалізувати інтерфейс [[yii\web\IdentityInterface]], що набагато простіше у використанні. Більш детальна інформація представлена у розділах [Аутентифікація](security-authentication.md), -[Авторизація](security-authorization.md) та [Шаблон додатка advanced](tutorial-advanced-app.md). +[Авторизація](security-authorization.md) та [Розширений шаблон додатка](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-uk/README.md). -Управління URL --------------- +Керування URL +------------- -Робота з URL в Yii 2.0 аналогічна тій, що була у версії 1.1. Основна зміна полягає в тому, що тепер підтримуються -додаткові параметри. Наприклад, якщо у вас є правило, оголошене наступним чином, то воно співпаде з `post/popular` та +Робота з URL в Yii 2.0 аналогічна тій, що була у версії 1.1. Основне покращення полягає в тому, що тепер підтримуються +додаткові параметри. Наприклад, якщо у вас є правило, оголошене нижче, то воно співпаде з `post/popular` та `post/1/popular`. У версії 1.1 вам довелося б використовувати два правила для отримання того ж результату. ```php @@ -529,11 +530,11 @@ User та IdentityInterface ] ``` -Більш детальна інформація представлена у розділі [Розбір та генерація URL](runtime-routing.md). +Більш детальна інформація представлена у розділі [Маршрутизація та створення URL](runtime-routing.md). Використання Yii 1.1 разом із 2.x --------------------------------- Інформація про використання коду для Yii 1.1 разом із Yii 2.0 представлена у розділі -[Одночасне використання Yii 1.1 та 2.0](tutorial-yii-integration.md). +[Робота із стороннім кодом](tutorial-yii-integration.md). diff --git a/docs/guide-uk/intro-yii.md b/docs/guide-uk/intro-yii.md index a846d4bd76..dcf119c6fa 100644 --- a/docs/guide-uk/intro-yii.md +++ b/docs/guide-uk/intro-yii.md @@ -1,6 +1,7 @@ Що таке Yii? ============ +<<<<<<< HEAD <<<<<<< HEAD Yii – це високопродуктивний компонентний PHP фреймворк, призначений для швидкої розробки сучасних веб додатків. Слово Yii (вимовляється як `Йі` `[ji:]`) в китайській мові означає "простий та еволюційний". @@ -10,11 +11,17 @@ Yii – це високопродуктивний компонентний PHP Слово Yii (вимовляється як `Йі` `[ji:]`) в китайській мові означає "простий та еволюційний". Також Yii може розшифровуватись як акронім для **Yes It Is**! >>>>>>> yiichina/master +======= +Yii – це високопродуктивний компонентний PHP-фреймворк, призначений для швидкої розробки сучасних веб-додатків. +Слово Yii (вимовляється як `Йі` `[ji:]`) в китайській мові означає "простий та еволюційний". +Також Yii може розшифровуватись як акронім для **Yes It Is**! +>>>>>>> master Для яких завдань найбільше підходить Yii? ----------------------------------------- +<<<<<<< HEAD <<<<<<< HEAD Yii – це універсальний фреймворк і може бути задіяний у всіх типах веб додатків, що використовують PHP. Завдяки його компонентній структурі і відмінній підтримці кешування, фреймворк особливо підходить для розробки @@ -24,6 +31,11 @@ Yii – це універсальний фреймворк і може бути Завдяки його компонентній структурі і відмінній підтримці кешування, фреймворк особливо підходить для розробки таких великих проектів як портали, форуми, системи керування контентом (CMS), інтернет-магазини або RESTful-додатки. >>>>>>> yiichina/master +======= +Yii – це універсальний фреймворк і може бути задіяний у всіх типах веб-додатків, що використовують PHP. +Завдяки його компонентній структурі і відмінній підтримці кешування, фреймворк особливо підходить для розробки +таких великих проектів як портали, форуми, системи керування вмістом (CMS), інтернет-магазини або RESTful-додатки. +>>>>>>> master Порівняння Yii з іншими фреймворками @@ -31,6 +43,7 @@ Yii – це універсальний фреймворк і може бути Якщо ви вже знайомі з іншими фреймворками, вам напевно буде цікаво порівняти їх із Yii: +<<<<<<< HEAD <<<<<<< HEAD - Як і багато інших PHP фреймворків, для організації коду Yii використовує модель MVC (Model-View-Controller) та сприяє у організації коду на цій моделі. @@ -42,18 +55,30 @@ Yii – це універсальний фреймворк і може бути - Yii дотримується філософії простого й елегантного коду. Yii ніколи не буде намагатись пере-ускладнювати дизайн тільки заради слідування будь-яким шаблонам проектування. >>>>>>> yiichina/master +======= +- Як і багато інших PHP-фреймворків, Yii втілює архітектурний шаблон MVC (Model-View-Controller) та + сприяє організації коду відповідно до вимог шаблону. +- Yii дотримується філософії простого й елегантного коду. Yii ніколи не буде намагатись пере-ускладнювати дизайн + тільки заради слідування будь-яким шаблонам проектування. +>>>>>>> master - Yii є full-stack фреймворком і включає в себе перевірені можливості, які добре себе зарекомендували: конструктори запитів та ActiveRecord для реляційних та NoSQL баз даних, підтримка REST API, багаторівневе кешування та інші. - Yii надзвичайно розширюваний. Ви можете налаштувати або замінити практично будь-яку частину основного коду. +<<<<<<< HEAD <<<<<<< HEAD Використовуючи архітектуру розширень - досить легко використовувати або створювати публічні розширення. - Висока продуктивність завжди є головною ціллю Yii. +======= + Завдяки надійній архітектурі розширень Yii, досить легко використовувати або розробляти поширюванні розширення. +- Висока швидкодія завжди є головною ціллю Yii. +>>>>>>> master -Yii — не проект однієї людини. Він підтримується і розвивається [сильною командою][] і великою спільнотою розробників, -які їй допомагають. Команда розробників Yii фреймворка стежать за тенденціями веб розробки і розвитком інших проектів. -Найбільш значимі можливості і кращі практики регулярно впроваджуються у фреймворк у вигляді простих і елегантних інтерфейсів. +Yii — не проект однієї людини. Він підтримується і розвивається [сильною командою][about_yii] і великою спільнотою розробників, +які їй допомагають. Команда розробників фреймворку Yii стежать за тенденціями веб-розробки і розвитком інших проектів. +Найбільш значимі можливості та кращі практики регулярно впроваджуються у фреймворк у вигляді простих й елегантних інтерфейсів. +<<<<<<< HEAD [сильна команда розробників]: http://www.yiiframework.com/about/ ======= Завдяки надійній архітектурі розширень Yii, досить легко використовувати або розробляти поширюванні розширення. @@ -65,17 +90,24 @@ Yii — не проект однієї людини. Він підтримуєт [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Версії Yii ---------- +<<<<<<< HEAD <<<<<<< HEAD На даний момент існує дві основні гілки Yii: 1.1 та 2.0. Гілка 1.1 є попереднім поколінням і знаходиться у стані підтримки. ======= На даний момент існує дві основні версії Yii: 1.1 та 2.0. Версія 1.1 є попереднім поколінням і знаходиться у стані підтримки. >>>>>>> yiichina/master +======= +На даний момент існує дві основні версії Yii: 1.1 та 2.0. Версія 1.1 є попереднім поколінням і знаходиться у стані підтримки. +>>>>>>> master Версія 2.0 - це повністю переписаний Yii, що використовує останні технології і протоколи, такі як Composer, PSR, простори імен, -типажі (traits) і багато іншого. 2.0 - поточне покоління фреймворка. На цій версії будуть зосереджені основні зусилля +трейти і багато іншого. 2.0 - поточне покоління фреймворку. На цій версії будуть зосереджені основні зусилля кілька наступних років. Даний посібник призначений в основному для версії 2.0. @@ -83,8 +115,8 @@ Yii — не проект однієї людини. Він підтримуєт -------------------- Yii 2.0 потребує PHP 5.4.0 та вище. Щоб дізнатися вимоги для окремих можливостей ви можете запустити скрипт перевірки вимог, -який поставляється із кожним релізом фреймворка. +який поставляється із кожним релізом фреймворку. -Для розробки на Yii необхідне загальне розуміння ООП, так як фреймворк повністю слідує цій парадигмі. +Для розробки на Yii необхідне загальне розуміння ООП, оскільки фреймворк повністю слідує цій парадигмі. Також слід вивчити такі сучасні можливості PHP як [простори імен](http://www.php.net/manual/en/language.namespaces.php) -і [типажі](http://www.php.net/manual/en/language.oop5.traits.php). +і [трейти](http://www.php.net/manual/en/language.oop5.traits.php). diff --git a/docs/guide-uk/rest-rate-limiting.md b/docs/guide-uk/rest-rate-limiting.md new file mode 100644 index 0000000000..69dfaea5ff --- /dev/null +++ b/docs/guide-uk/rest-rate-limiting.md @@ -0,0 +1,66 @@ +Обмеження частоти запитів +=============================== + +Для того, щоб уникнути зловживань, вам слід подумати про додавання обмеження частоти запитів до вашого API. Наприклад, +ви можете обмежити використання вашого API до 100 запитів протягом 10 хвилин для кожного користувача. Якщо від користувача +протягом цього періода часу надходить більша кількість запитів, буде повернута відповідь з кодом 429 +("занадто багато запитів"). + +Для того, щоб увімкнути обмеження частоти запитів, *[[yii\web\User::identityClass|клас user identity]]* повинен реалізовувати +інтерфейс [[yii\filters\RateLimitInterface]]. Цей інтерфейс вимагає реалізації наступних трьох методів: + +* `getRateLimit()`: повертає максимальну кількість дозволених запитів та період часу, наприклад `[100, 600]`, що + означає не більше 100 викликів API прогятом 600 секунд. +* `loadAllowance()`: повертає кількість дозволених запитів, що залишились, та мітку часу *UNIX* останньої перевірки обмеження. +* `saveAllowance()`: зберігає кількість дозволених запитів та поточну мітку часу *UNIX*. + +Ви можете використовувати два стовпці в таблиці user для зберігання кількості дозволених запитів та час останньої перевірки. +У методах `loadAllowance()` та `saveAllowance()` можна реалізувати зчитування та зберігання значень цих стовбців відповідно +до даних поточного аутентифікованого користувача. Для покращення швидкодії можна спробувати зберігати цю +інформацію в кеш чи NoSQL-сховищі. + +Реалізація у моделі `User` може виглядати наступним чином: + +```php +public function getRateLimit($request, $action) +{ + return [$this->rateLimit, 1]; // $rateLimit запитів на секунду +} + +public function loadAllowance($request, $action) +{ + return [$this->allowance, $this->allowance_updated_at]; +} + +public function saveAllowance($request, $action, $allowance, $timestamp) +{ + $this->allowance = $allowance; + $this->allowance_updated_at = $timestamp; + $this->save(); +} +``` + +Як тільки відповідний інтерфейс буде реалізований у класі identity, Yii почне автоматично перевіряти обмеження +частоти запитів за допомогою фільтра дій [[yii\filters\RateLimiter]] для [[yii\rest\Controller]]. При перевищенні +обмежень буде викинуто виключення [[yii\web\TooManyRequestsHttpException]]. + +Ви можете налаштувати обмеження частоти викликів у ваших класах REST-контролерів наступним чином: + +```php +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['rateLimiter']['enableRateLimitHeaders'] = false; + return $behaviors; +} +``` + +При увімкненому обмеженні частоти запитів кожна відповідь, за замовчуванням, повертається з наступними HTTP-заголовками, +що містять таку інформацію про поточні обмеження: + +* `X-Rate-Limit-Limit`: максимальна кількість запитів, дозволена протягом періоду часу +* `X-Rate-Limit-Remaining`: скільки залишилось дозволених запитів в поточний період часу +* `X-Rate-Limit-Reset`: скільки часу у секундах потрібно почекати до отримання максимальної кількості дозволених запитів + +Ви можете відключити ці заголовки, встановивши властивість [[yii\filters\RateLimiter::enableRateLimitHeaders]] у false, +як показано у прикладі вище. diff --git a/docs/guide-uk/start-databases.md b/docs/guide-uk/start-databases.md index 31fc88adeb..368c742cd3 100644 --- a/docs/guide-uk/start-databases.md +++ b/docs/guide-uk/start-databases.md @@ -1,29 +1,30 @@ Робота з базами даних ===================== -Цей розділ описує, як створити нову сторінку, яка буде відображати дані країни, отримані з таблиці бази даних `country`. -Для цього, вам необхідно буде налаштувати зʼєднання з базою даних, створити клас [Active Record](db-active-record.md), -визначити [дію](structure-controllers.md) та створити [представлення](structure-views.md). +Цей розділ описує, як створити нову сторінку, яка буде відображати дані країни, отримані з +таблиці `country` бази даних. Для цього, вам необхідно буде налаштувати зʼєднання з базою даних, +створити клас [Active Record](db-active-record.md), визначити [дію](structure-controllers.md) +та створити [представлення](structure-views.md). -За допомогою даного посібника ви дізнаєтесь як: +В даному розділі ви дізнаєтесь як: -* Налаштувати зʼєднання з базою даних -* Оголосити Active Record класс -* Запитувати дані за допомогою класу Active Record -* Відображати дані у представленні із розбиттям по сторінках +* налаштувати зʼєднання з базою даних; +* визначити клас Active Record; +* запитувати дані за допомогою класу Active Record; +* відображати дані у представленні із розділенням на сторінки. Зверніть увагу, що для того, щоб закінчити цей розділ, ви повинні мати базові знання і досвід використання баз даних. -Зокрема, ви повинні знати, як створювати бази даних, як виконувати SQL-запити за допомогою клієнтських додатків баз даних. +Зокрема, ви повинні знати, як створювати бази даних та як виконувати SQL-запити за допомогою клієнтських додатків баз даних. Підготовка бази даних <span id="preparing-database"></span> --------------------- Для початку, створіть базу даних `yii2basic`, з якої і будете надалі отримувати дані. -Ви можете використовувати SQLite, MySQL, PostgreSQL, MSSQL або Oracle бази даних, Yii має вбудовану підтримку для багатьох баз даних. -Для простоти, будемо вважати що використовується MySQL у подальшому описі. +Ви можете створити базу даних SQLite, MySQL, PostgreSQL, MSSQL або Oracle, Yii має вбудовану підтримку для багатьох баз даних. +Для простоти, у подальшому описі будемо вважати що використовується MySQL. -Далі, створіть таблицю `country`, і внесіть декілька прикладів. Можете використати наступний SQL-запит, як приклад: +Далі, створіть таблицю `country` у базі даних, і внесіть декілька зразків даних. Можете використати наступний SQL-запит, як приклад: ```sql CREATE TABLE `country` ( @@ -32,27 +33,28 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `country` VALUES ('AU','Australia',18886000); -INSERT INTO `country` VALUES ('BR','Brazil',170115000); -INSERT INTO `country` VALUES ('CA','Canada',1147000); -INSERT INTO `country` VALUES ('CN','China',1277558000); -INSERT INTO `country` VALUES ('DE','Germany',82164700); -INSERT INTO `country` VALUES ('FR','France',59225700); -INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `country` VALUES ('IN','India',1013662000); -INSERT INTO `country` VALUES ('RU','Russia',146934000); -INSERT INTO `country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` -На даний момент, у вас є база даних `yii2basic` і таблиця `country` з трьома колонками, що містять десять рядків даних. +На даний момент, у вас є база даних `yii2basic` і таблиця `country` з трьома колонками, що містить десять рядків даних. Налаштування підключення до БД <span id="configuring-db-connection"></span> ------------------------------ -Перш ніж продовжити, переконайтеся, що у вас налаштовано [PDO](http://www.php.net/manual/en/book.pdo.php) PHP розширення -і PDO драйвер для вашої БД (наприклад `pdo_mysql` для MySQL). Це є основною вимогою, якщо ваш додаток використовує реляційну базу даних. +Перш ніж продовжити, переконайтеся, що у вас встановлено розширення PHP [PDO](http://www.php.net/manual/en/book.pdo.php) +та драйвер PDO для вашої БД (наприклад `pdo_mysql` для MySQL). Це є основною вимогою, +якщо ваш додаток використовує реляційну базу даних. -Згідно того, що у вас встановлено, відкрийте файл `config/db.php` і замініть на коректні дані вашої БД. +Коли вони встановлені, відкрийте файл `config/db.php` і змініть параметри для відповідності вашій БД. За замовчуванням, файл містить наступне: ```php @@ -68,21 +70,21 @@ return [ ``` Файл конфігурації `config/db.php` є типовим інструментом [налаштування](concept-configurations.md) на основі файлів. -Даний файл конфігурації визначає параметри, які необхідні для створення і ініціалізації [[yii\db\Connection]] примірника, -через який ви можете робити SQL-запити до основної бази даних. +Даний файл конфігурації визначає параметри, які необхідні для створення та ініціалізації екземпляру [[yii\db\Connection]], +через який ви можете робити SQL-запити до зазначеної бази даних. З’єднання з БД, описане вище, може бути доступне в коді додатка за допомогою виразу `Yii::$app->db`. -> Інформація: Файл конфігурації `config/db.php` буде включений до конфігурації головного додатка `config/web.php`, - який визначає як має бути проініційований сам [додаток](structure-applications.md). Для отримання додаткової інформації, - будь ласка, зверніться до розділу [Налаштування](concept-configurations.md). +> Info: Файл конфігурації `config/db.php` буде включений до конфігурації головного додатка `config/web.php`, +який визначає як має бути проініціалізований екземпляр [додатку](structure-applications.md). +Для отримання додаткової інформації, будь ласка, зверніться до розділу [Конфігурації](concept-configurations.md). Створення Active Record <span id="creating-active-record"></span> ----------------------- -Для відображення і отримання даних з таблиці `country` створіть [Active Record](db-active-record.md) клас -з іменем `Country`, і збережіть в файл `models/Country.php`. +Для репрезентації та отримання даних з таблиці `country` створіть похідний від [Active Record](db-active-record.md) +клас з іменем `Country`, і збережіть його в файл `models/Country.php`. ```php <?php @@ -99,10 +101,10 @@ class Country extends ActiveRecord Клас `Country` наслідує [[yii\db\ActiveRecord]]. Вам не потрібно писати ніякого коду всередині нього! Всього лише за допомогою описаного вище коду, Yii самостійно вгадає відповідне імʼя таблиці з імені класу. -> Інформація: Якщо немає прямого співпадіння з імені класу і таблиці, - ви можете використати метод [[yii\db\ActiveRecord::tableName()]] щоб задати відповідне імʼя таблиці. +> Info: Якщо неможливо отримати прямої відповідності імені класу до імені таблиці, ви можете +перевизначити метод [[yii\db\ActiveRecord::tableName()]], щоб точно задати відповідне імʼя таблиці. -Використовуючи клас `Country`, ви можете легко маніпулювати даними з таблиці `country`, як показано в наступному фрагменті: +Використовуючи клас `Country`, ви можете легко маніпулювати даними з таблиці `country`, як показано у цих фрагментах коду: ```php use app\models\Country; @@ -116,14 +118,14 @@ $country = Country::findOne('US'); // відобразити "United States" echo $country->name; -// оновити назву країни на "U.S.A." і зберегти в БД +// змінити назву країни на "U.S.A." і зберегти в БД $country->name = 'U.S.A.'; $country->save(); ``` -> Інформація: Active Record є потужним засобом для доступу і управління даними в базі даних в обʼєктно-орієнтованому стилі. - Ви можете знайти більш детальну інформацію в розділі [Active Record](db-active-record.md). Крім того, ви також можете - взаємодіяти з базою даних, використовуючи для доступу метод передачі даних нижнього рівня під назвою [Data Access Objects](db-dao.md). +> Info: Active Record є потужним засобом для доступу і управління даними бази даних в обʼєктно-орієнтованому стилі. +Ви можете знайти більш детальну інформацію в розділі [Active Record](db-active-record.md). Крім того, ви також можете +взаємодіяти з базою даних, використовуючи для доступу метод передачі даних нижнього рівня під назвою [Data Access Objects](db-dao.md). Створення дії <span id="creating-action"></span> @@ -168,23 +170,25 @@ class CountryController extends Controller Збережіть цей код у файл `controllers/CountryController.php`. -Дія `index` викликає метод `Country::find()`. Цей метод Active Record будує запит бази даних і отримує всі дані з таблиці `country`. -Щоб обмежити кількість країн, які будуть отримуватись в кожному запиті, сам запит розбивається на сторінки, за допомогою об’єкта +Дія `index` викликає метод `Country::find()`. Цей метод Active Record будує запит до бази даних і отримує всі дані з таблиці `country`. +Щоб обмежити кількість країн, які будуть отримуватись в кожному запиті, сам запит розділяється на сторінки, за допомогою об’єкта [[yii\data\Pagination]]. Об’єкт `Pagination` служить двом цілям: -* Встановлює `offset` і `limit` для SQL-запиту так, щоб він повертав лише одну сторінку даних за один раз +* Встановлює директиви `offset` і `limit` для SQL-запиту так, щоб він повертав лише одну сторінку даних за один раз (не більше 5 рядків на сторінці). -* Використовується у представленнях для відображення пейджера, який складається із переліку кнопок переходу по сторінкам, +* Використовується у представленнях для відображення пейджера, який складається із переліку кнопок переходу по сторінках, про що буде описано у наступному підрозділі. -Наприкінці, дія `index` повертає представлення `index` і передає дані по країнах, з розбивкою на сторінки. +Наприкінці, дія `index` формує представлення з іменем `index` та передає до нього дані по країнах із інформацією про посторінкове +розділення. Створення представлення <span id="creating-view"></span> ----------------------- -В директорії `views` створіть спочатку підкаталог `country`. Цей каталог буде використовуватись для всіх представленнь -контролера `country`. В каталозі `views/country`, створіть файл з іменем`index.php` що містить наступне: +В директорії `views` створіть спочатку під-директорію `country`. Цей каталог буде використовуватись для всіх +представлень контролера `country`. В директорії `views/country`, створіть файл з іменем `index.php`, +що містить наступне: ```php <?php @@ -204,19 +208,19 @@ use yii\widgets\LinkPager; <?= LinkPager::widget(['pagination' => $pagination]) ?> ``` -Дане представлення містить два розділи для відображення даних по країнам. У першій частині, відображаються дані про країни -у вигляді невпорядкованого списку HTML. У другій частині, [[yii\widgets\LinkPager]] віджет з використанням інформації -про нумерацію сторінок. Віджет `LinkPager` відображаться у вигляді переліку кнопок. -При натисканні на будь-якій з них будуть оновлюватись дані країн на відповідній сторінці. +Дане представлення містить дві секції для відображення даних про країни. У першій частині, відображаються передані дані про країни +у вигляді невпорядкованого списку HTML. У другій частині, віджет [[yii\widgets\LinkPager]] з використанням інформації +про нумерацію сторінок. Віджет `LinkPager` відображається у вигляді переліку кнопок. +При натисканні на будь-якій з них будуть виводитись дані країн для відповідної сторінки. -Спробуєм <span id="trying-it-out"></span> --------- +Спробуємо <span id="trying-it-out"></span> +--------- -Щоб побачити все, що було створено під час роботи, відкрийте в браузері наступний URL: +Щоб побачити як весь вищезазначений код працює, відкрийте в браузері наступний URL: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` ![Перелік країн](images/start-country-list.png) @@ -226,19 +230,19 @@ http://hostname/index.php?r=country/index Придивившись більш уважно, ви побачите, що URL в браузері також змінюється на ``` -http://hostname/index.php?r=country/index&page=2 +http://hostname/index.php?r=country%2Findex&page=2 ``` -За лаштунками, [[yii\data\Pagination|Pagination]] надає всю необхідну функціональність для розбиття набору даних на сторінки: +За лаштунками, [[yii\data\Pagination|Pagination]] надає всю необхідну функціональність для розділення набору даних на сторінки: * Спочатку, [[yii\data\Pagination|Pagination]] представляє першу сторінку, яка відображає країни запитом SELECT з умовою `LIMIT 5 OFFSET 0`. В результаті, будуть відображені перші знайдені пʼять країн. -* [[yii\widgets\LinkPager|LinkPager]] віджет відображає кнопки сторінок з URL-адресами створеними за допомогою +* Віджет [[yii\widgets\LinkPager|LinkPager]] відображає кнопки сторінок з URL-адресами створеними за допомогою [[yii\data\Pagination::createUrl()|Pagination]]. URL-адреси будуть містити параметр запиту `page`, який представляє різні номери сторінок. -* Якщо ви натиснете кнопку "2", спрацює новий запит, який буде відправлений на `country/index` з подальшим опрацюванням. - [[yii\data\Pagination|Pagination]] зчитає параметр `page` з URL-запиту і встановить номер поточної сторінки в 2-ку. - Таким чином, новий запит буде мати визначення `LIMIT 5 OFFSET 5` і поверне наступні пʼять країн для відображення. +* Якщо натиснути кнопку "2", новий запит для маршруту `country/index` спрацьовує і обробляється. + [[yii\data\Pagination|Pagination]] зчитає параметр `page` з URL-запиту і встановить для поточної сторінки номер 2. + Таким чином, новий запит до БД буде мати умову `LIMIT 5 OFFSET 5` і поверне наступні пʼять країн для відображення. Підсумок <span id="summary"></span> @@ -247,7 +251,7 @@ http://hostname/index.php?r=country/index&page=2 В цьому розділі ви дізналися, як працювати з базою даних. Ви також дізналися, як вибирати і відображати дані на сторінках за допомогою [[yii\data\Pagination]] і [[yii\widgets\LinkPager]]. -У наступному розділі ви дізнаєтеся, як використовувати потужний інструмент генерації коду, що називається [Gii](tool-gii.md), -який допоможе вам швидко здійснювати деякі часто необхідні функції, такі як Create-Read-Update-Delete (CRUD) -операції для роботи з даними в таблицях баз даних. -Насправді, код, який ви щойно написали, Yii може автоматично сгенерувати з допомогою функції Gii. +У наступному розділі ви дізнаєтеся, як використовувати потужний інструмент генерування коду, що називається [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md), +який допоможе вам швидко створювати деякі часто необхідні функції, такі як Create-Read-Update-Delete (CRUD) +операції для роботи з даними в таблицях баз даних. Насправді, код, який ви щойно написали, +в Yii можливо автоматично згенерувати за допомогою інструменту Gii. diff --git a/docs/guide-uk/start-forms.md b/docs/guide-uk/start-forms.md index 6576959800..968f1c04c5 100644 --- a/docs/guide-uk/start-forms.md +++ b/docs/guide-uk/start-forms.md @@ -1,24 +1,25 @@ Робота з формами ================ -В даному розділі ми обговоримо отримання даних від користувачів. На сторінці буде розміщена форма з полями, -де можна буде вказати ім’я та email. Отримані дані будуть зображені на сторінці для їх підтвердження. +В даному розділі буде описано як створити нову сторінку з формою для отримання даних від користувачів. +На сторінці буде розміщена форма з полями, де можна буде вказати ім’я та адресу електронної пошти. +Після отримання цих двох фрагментів інформації від користувача, сторінка відобразить введені значення знову для підтвердження. -Для того, щоб досягти дану ціль, крім створення [дії](structure-controllers.md) і двох [представлень](structure-views.md) -ви створите [модель](structure-models.md). +Для досягнення даної цілі, крім створення [дії](structure-controllers.md) і +двох [представлень](structure-views.md), ви також створите [модель](structure-models.md). -В даному керівництві ви дізнаєтесь: +В даному керівництві ви дізнаєтесь як: -* Як створити [модель](structure-models.md) для даних, вказаних користувачем -* Як оголосити правила перевірки введених даних -* Як створити HTML форму в [представленні](structure-views.md) +* створити [модель](structure-models.md) для даних, введених користувачем через форму; +* оголосити правила перевірки введених даних; +* створити HTML-форму в [представленні](structure-views.md). Створення моделі <span id="creating-model"></span> ---------------- -Створіть клас моделі `EntryForm` та збережіть у файлі `models/EntryForm.php` як показано нижче. Він буде використовуватись -для зберігання даних, введених користувачем. Детальніше про присвоєння імен файлам класів читайте в розділі +Дані, отримувані від користувача, будуть представлятись моделлю класу `EntryForm` як показано нижче, який +зберігатиметься у файлі `models/EntryForm.php`. Детальніше про присвоєння імен файлам класів читайте в розділі [Автозавантаження класів](concept-autoloading.md). ```php @@ -26,6 +27,7 @@ namespace app\models; +use Yii; use yii\base\Model; class EntryForm extends Model @@ -43,22 +45,23 @@ class EntryForm extends Model } ``` -Даний клас розширює клас [[yii\base\Model]], який є складовою частиною фреймворка і зазвичай використовується для роботи з даними форм. +Даний клас успадкований від класу [[yii\base\Model]], який є складовою частиною фреймворку і зазвичай використовується для +роботи з даними форм. -> Інформація: Клас [[yii\base\Model]] використовується як батьківський клас для класів моделей, які *не* асоційовані - з таблицями бази даних. Клас [[yii\db\ActiveRecord]] зазвичай є батьківським для класів моделей, що відповідають - таблицям бази даних. +> Info: Клас [[yii\base\Model]] використовується як батьківський клас для класів моделей, які *не* асоційовані з таблицями бази даних. +Клас [[yii\db\ActiveRecord]] зазвичай є батьківським для класів моделей, що відповідають таблицям бази даних. -Клас містить 2 публічні властивості `name` і `email`, які використовуються для зберігання даних, вказаних користувачем. -Він також містить метод `rules()`, який повертає набір правил перевірки даних. Правила перевірки, -оголошені в коді вище означають наступне: +Клас `EntryForm` містить дві публічні властивості `name` та `email`, які використовуються для зберігання +даних, вказаних користувачем. Він також містить метод `rules()`, який повертає набір +правил перевірки даних. Правила перевірки, оголошені у вищезазначеному коді означають наступне: -* Поля `name` і `email` обов’язкові для заповнення -* Поле `email` повино містити правильну адресу email +* поля `name` і `email` обов’язкові для заповнення; +* поле `email` повинно містити правильну адресу електронної пошти. Якщо об’єкт `EntryForm` заповнений даними користувача, то для їх перевірки ви можете викликати метод -[[yii\base\Model::validate()|validate()]]. У випадку невдалої перевірки властивість [[yii\base\Model::hasErrors|hasErrors]] -дорівнюватиме `true`. За допомогою [[yii\base\Model::getErrors|errors]] можна дізнатись, які саме виникли помилки. +цього об’єкту [[yii\base\Model::validate()|validate()]]. У випадку +невдалої перевірки властивість [[yii\base\Model::hasErrors|hasErrors]] дорівнюватиме `true`. +За допомогою [[yii\base\Model::getErrors|errors]] можна дізнатись, які саме помилки перевірки виникли. ```php <?php @@ -66,10 +69,10 @@ $model = new EntryForm(); $model->name = 'Qiang'; $model->email = 'bad'; if ($model->validate()) { - // Good! + // Все добре! } else { - // Failure! - // Use $model->getErrors() + // Невдача! + // Використовуйте $model->getErrors() } ``` @@ -77,8 +80,8 @@ if ($model->validate()) { Створення дії <span id="creating-action"></span> ------------- -Далі створіть дію `entry` в контролері `site`, точно так, як ви робили це раніше. -Процес створення та використання дій описано у розділі [Говоримо «Привіт»](start-hello.md). +Далі необхідно створити дію `entry` в контролері `site`, яка буде використовувати нову модель. Процес +створення та використання дій описано у розділі [Говоримо "Привіт"](start-hello.md). ```php <?php @@ -91,20 +94,24 @@ use app\models\EntryForm; class SiteController extends Controller { - // ...існуючий код... + // ...наявний код... public function actionEntry() { +<<<<<<< HEAD <<<<<<< HEAD $model = new EntryForm; ======= $model = new EntryForm(); >>>>>>> yiichina/master +======= + $model = new EntryForm(); +>>>>>>> master if ($model->load(Yii::$app->request->post()) && $model->validate()) { // дані в $model успішно перевірені - // робимо щось корисне з $model ... + // тут робимо щось корисне з $model ... return $this->render('entry-confirm', ['model' => $model]); } else { @@ -115,28 +122,32 @@ class SiteController extends Controller } ``` -Подія створює об’єкт `EntryForm`. Потім вона намагається заповнити модель даними із масива `$_POST`, доступ -до якого забеспечує Yii за допомогою [[yii\web\Request::post()]]. Якщо модель успішно заповнена, тобто користувач відправив -дані з HTML форми, то для перевірки даних буде викликаний метод [[yii\base\Model::validate()|validate()]]. +Дія спочатку створює об’єкт `EntryForm`. Потім вона намагається заповнити модель +даними із масиву `$_POST`, доступ до якого забезпечується в Yii за допомогою [[yii\web\Request::post()]]. +Якщо модель успішно заповнена, тобто користувач відправив дані з HTML-форми, +то для перевірки даних буде викликаний метод [[yii\base\Model::validate()|validate()]]. -> Інформація: `Yii::$app` являє собою глобально доступний екземпляр-одинак [додатка](structure-applications.md) - (singleton). Одночасно це є [Service Locator](concept-service-locator.md), який надає доступ до компонентів, типу - `request`, `response`, `db` і так далі. В коді выще для доступу до даних з `$_POST` був використаний компонент `request`. +> Info: `Yii::$app` являє собою глобально доступний екземпляр-одинак [додатка](structure-applications.md) +(singleton). Одночасно це є [Service Locator](concept-service-locator.md), який +надає компоненти, типу `request`, `response`, `db` і так далі для доступу до специфічної функціональності. +У вищезазначеному коді для доступу до даних з `$_POST` був використаний компонент екземпляру додатка `request`. -Якщо все гаразд, подія зобразить представлення `entry-confirm`, яке покаже користувачу вказані ним дані. -В іншому випадку буде зображено представлення `entry`, яке містить HTML форму і помилки перевірки даних, якщо вони є. +Якщо все гаразд, дія зобразить представлення `entry-confirm`, яке покаже користувачу вказані ним дані. +Якщо дані не передані або містять помилки, буде зображено представлення `entry` з +HTML-формою та повідомленнями про присутні помилки перевірки даних. -> Примітка: У цьому дуже простому прикладі ми просто відобразили сторінку підтвердження відправки даних. - На практиці, ви повинні розглянути використання [[yii\web\Controller::refresh()|refresh()]] або [[yii\web\Controller::redirect()|redirect()]] - для уникнення [проблеми повторного відправлення даних форми](http://en.wikipedia.org/wiki/Post/Redirect/Get). +> Note: У цьому дуже простому прикладі ми просто відобразили сторінку підтвердження відправлення даних. На практиці, +ви повинні розглянути використання [[yii\web\Controller::refresh()|refresh()]] або [[yii\web\Controller::redirect()|redirect()]] +для уникнення [проблеми повторного відправлення даних форми](http://en.wikipedia.org/wiki/Post/Redirect/Get). Створення представлення <span id="creating-views"></span> ----------------------- -На завершення, створюємо два представлення з іменами `entry-confirm` і `entry`, котрі зображаються дією `entry` з минулого підрозділу. +На завершення, створюємо два файли представлення з іменами `entry-confirm` і `entry`, котрі зображаються дією `entry` +з минулого підрозділу. -Представлення `entry-confirm` просто зображає ім’я та email. Воно мусить бути збережене у файлі `views/site/entry-confirm.php`. +Представлення `entry-confirm` просто зображає ім’я та адресу електронної пошти. Воно мусить бути збережене у файлі `views/site/entry-confirm.php`. ```php <?php @@ -145,12 +156,12 @@ use yii\helpers\Html; <p>Ви вказали наступну інформацію:</p> <ul> - <li><label>Name</label>: <?= Html::encode($model->name) ?></li> - <li><label>Email</label>: <?= Html::encode($model->email) ?></li> + <li><label>Ім’я</label>: <?= Html::encode($model->name) ?></li> + <li><label>Адреса електронної пошти</label>: <?= Html::encode($model->email) ?></li> </ul> ``` -Представлення `entry` відображає HTML форму. Воно мусить бути збережене у файлі `views/site/entry.php`. +Представлення `entry` відображає HTML-форму. Воно мусить бути збережене у файлі `views/site/entry.php`. ```php <?php @@ -170,10 +181,12 @@ use yii\widgets\ActiveForm; <?php ActiveForm::end(); ?> ``` -Для побудови HTML форми представлення використовує потужний [віджет](structure-widgets.md) [[yii\widgets\ActiveForm|ActiveForm]]. -Методи `begin()` і `end()` виводять відкриваючий і закриваючий теги форми. Між цими викликами створюються поля для заповнення -за допомогою метода [[yii\widgets\ActiveForm::field()|field()]]. Першим іде поле для "name", другим — для "email". -Далі для генерації кнопки відправлення даних викликається метод [[yii\helpers\Html::submitButton()]]. +Для побудови HTML-форми представлення використовує потужний [віджет](structure-widgets.md) +[[yii\widgets\ActiveForm|ActiveForm]]. Методи `begin()` і `end()` виводять відкриваючий і закриваючий теги форми +відповідно. Між цими викликами створюються поля для заповнення за допомогою метода +[[yii\widgets\ActiveForm::field()|field()]]. Першим іде поле "name", +другим — "email". Далі для генерування кнопки відправлення даних викликається метод +[[yii\helpers\Html::submitButton()]]. Спробуємо <span id="trying-it-out"></span> @@ -182,54 +195,57 @@ use yii\widgets\ActiveForm; Щоб побачити, як це працює, відкрийте в браузері наступний URL: ``` -http://hostname/index.php?r=site/entry +http://hostname/index.php?r=site%2Fentry ``` Ви побачите сторінку з формою і двома полями для заповнення. Перед кожним полем є надпис, який вказує, яку саме -інформацію слід вказувати. Якщо ви натиснете на кнопку відправлення даних без самих даних або якщо вкажете email в невірному -форматі, то ви побачите повідомлення з помилкою біля кожного проблемного поля. +інформацію слід вказувати. Якщо ви натиснете на кнопку відправлення даних без самих даних або якщо вкажете адресу +електронної пошти в невірному форматі, то ви побачите повідомлення про помилку біля кожного проблемного поля. -![Форма з помилками](images/start-form-validation.png) +![Форма з помилками перевірки](images/start-form-validation.png) -Після введення вірних даних і їх відправки, ви побачите сторінку з даними, які щойно вказали. +Після введення вірних даних і їх відправлення, ви побачите нову сторінку з даними, які щойно вказали. ![Підтвердження введених даних](images/start-entry-confirmation.png) ### Як працює вся ця "магія" <span id="magic-explained"></span> -Ви, більш за все, ставите питанням про те, як все ж ця HTML форма працює насправді і яким чином. -Весь процес може здатися трохи магічним: те як зображаються підписи до полів, помилки перевірки даних при некоректному -заповненні і те що все це відбувається без перезавантаження сторінки. +Ви можливо здивовані роботою HTML-форми поза лаштунками, тому що може здаватись магічним те, як +зображається підпис до кожного поля та виводяться повідомлення про помилки при некоректному заповненні +й те що все це відбувається без перезавантаження сторінки. Так, початкова перевірка даних дійсно проходить на стороні клієнта за допомогою JavaScript, і повторно перевіряється на стороні сервера (PHP). [[yii\widgets\ActiveForm]] достатньо продуманий, щоб взяти правила перевірки, які ви оголосили в `EntryForm`, перетворити їх в JavaScript код і використовувати його для проведення перевірки. -На випадок, якщо в браузері буде вимкнено JavaScript валідація проходить і на стороні сервера, як показано в методі +На випадок, якщо в браузері буде вимкнено JavaScript перевірка проходить і на стороні сервера, як показано в методі `actionEntry()`. Це дає впевненість в тому, що дані коректні за будь-яких обставин. -> Попередження: перевірка даних на стороні клієнта це зручність, яке забезпечує кращий користувальницький досвід. - Перевірка на стороні сервера завжди обов’язкова, незважаючи на клієнтську. +> Warning: Перевірка даних на стороні клієнта - це зручність, яка забезпечує краще сприйняття користувачем. +Перевірка на стороні сервера завжди обов’язкова, не зважаючи на клієнтську. -Підписи для полів генеруються методом `field()`, на основі імен властивостей моделі. Наприклад, підпис `Name` генерується -для властивості `name`. Ви можете модифікувати підписи наступним чином: +Підписи для полів генеруються методом [[yii\widgets\ActiveForm::field()|field()]], на основі імен властивостей моделі. +Наприклад, підпис `Name` генерується для властивості `name`. + +Ви можете модифікувати підписи в межах представлення +наступним чином: ```php <?= $form->field($model, 'name')->label('Ваше ім’я') ?> -<?= $form->field($model, 'email')->label('Ваш Email') ?> +<?= $form->field($model, 'email')->label('Ваша адреса електронної пошти') ?> ``` -> Інформація: У Yii є велика кількість віджетів, які дозволяють швидко будувати складні і динамічні представлення. - Як ви дізнаєтесь пізніше, розробляти нові віджети доволі просто. Багато із представленнь можна винести у віджети, - щоб використовувати їх повторно в інших частинах і тим самим спростити розробку в майбутньому. +> Info: У Yii є велика кількість віджетів, які дозволяють швидко будувати складні та динамічні представлення. +Як ви дізнаєтесь пізніше, розробляти нові віджети доволі просто. Багато із представлень можна винести у віджети, +щоб використовувати їх повторно в інших частинах і тим самим спростити розробку в майбутньому. Підсумок <span id="summary"></span> -------- -В даному розділі ви випробували кожну частину шаблона проектування MVC. Ви дізналися як створювати класи моделей -для опрацювання і перевірки даних вказаних користувачем. +В даному розділі ви випробували кожну частину шаблона проектування MVC. Ви дізналися, як +створити клас моделі для репрезентації даних, вказаних користувачем, і як перевіряти ці дані. -Також, ви дізналися як отримати дані від користувача і як їх відобразити тому ж користувачу. Ця задача може займати +Також ви дізналися, як отримувати дані від користувачів та як повертати їх для відображення у браузері. Ця задача може займати багато часу в процесі розробки. Yii надає потужні віджети, які роблять задачу максимально простою. В наступному розділі ви дізнаєтесь як працювати з базами даних, що необхідно в більшості додатків. diff --git a/docs/guide-uk/start-gii.md b/docs/guide-uk/start-gii.md index 3e8e441693..66045644a6 100644 --- a/docs/guide-uk/start-gii.md +++ b/docs/guide-uk/start-gii.md @@ -1,6 +1,7 @@ -Генерація коду за допомогою Gii -=============================== +Генерування коду за допомогою Gii +================================= +<<<<<<< HEAD <<<<<<< HEAD Цей розділ описує, як використовувати [Gii](tool-gii.md) для автоматичної генерації коду, процедура якої має певні спільні риси з веб-сайтом. Використання Gii для автоматичного створення коду є простою процедурою введення @@ -11,18 +12,25 @@ автоматичного створення коду є простою процедурою введення правильної інформації згідно інструкцій, які містяться на веб-сторінках Gii. >>>>>>> yiichina/master +======= +Цей розділ описує, як використовувати [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md) +для автоматичного генерування коду, який реалізовує найбільш поширений функціонал для веб-сайту. Використання Gii для +автоматичного створення коду є простою процедурою введення правильної інформації згідно з інструкціями, які містяться +на веб-сторінках Gii. +>>>>>>> master -В даному керівництві ви дізнаєтесь: +В даному керівництві ви дізнаєтесь як: -* Увімкнути Gii у вашому додатку -* Використання Gii для генерації класів Active Record -* Використання Gii для генерації коду по операціям CRUD бази даних -* Налаштування коду, що був згенерований Gii +* увімкнути Gii у вашому додатку; +* використовувати Gii для генерування класів Active Record; +* використовувати Gii для генерування коду, який реалізовує операції CRUD для таблиці бази даних; +* налаштовувати код, що був згенерований Gii. Підготовка Gii <span id="starting-gii"></span> -------------- +<<<<<<< HEAD <<<<<<< HEAD [Gii](tool-gii.md) надається як [модуль](structure-modules.md). Ви можете підключити модуль Gii, налаштувавши відповідну властивість [[yii\base\Application::modules|modules]] в налаштуваннях додатка. В залежності від налаштувань вашого додатка, @@ -33,36 +41,42 @@ в налаштуваннях додатка. В залежності від налаштувань вашого додатка, ви можете знайти наступний код в конфігураційному файлі `config/web.php`: >>>>>>> yiichina/master +======= +[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md) постачається в Yii як [модуль](structure-modules.md). +Ви можете підключати модуль Gii, налаштувавши відповідну властивість [[yii\base\Application::modules|modules]] додатка. В залежності +від того, як ви створили ваш додаток, наступний код можливо вже присутній в конфігураційному файлі `config/web.php`: +>>>>>>> master ```php $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` -Наведена вище конфігурація підключає модуль `gii` у тому випадку, коли ваш додаток знаходиться в -[середовищі розробки](concept-configurations.md#environment-constants) і наслідує клас [[yii\gii\Module]]. +Наведена вище конфігурація підключає модуль `gii`, представлений класом [[yii\gii\Module]], у тому випадку, +коли ваш додаток знаходиться в [середовищі розробки](concept-configurations.md#environment-constants). -Якщо ви перевірите [вхідний скрипт](structure-entry-scripts.md) `web/index.php` вашого додатку, то знайдете наступний -рядок `YII_ENV_DEV`, який переводить додаток в середовище розробки. +Якщо ви перевірите [вхідний скрипт](structure-entry-scripts.md) `web/index.php` вашого додатку, то +знайдете наступний рядок, який визначає `YII_ENV_DEV`. ```php defined('YII_ENV') or define('YII_ENV', 'dev'); ``` Завдяки даному рядку, ваш додаток знаходиться в режимі розробки, і буде підключати Gii, із вищевказаною конфігурацією. -Тепер ви можете отримати доступ до Gii за наступною адресою: +Тепер ви можете отримати доступ до Gii за наступною URL-адресою: ``` http://hostname/index.php?r=gii ``` -> Примітка: Якщо ви звертаєтеся до Gii від машини, крім локальної, доступ буде заборонений за замовчуванням із міркувань - безпеки. Ви можете налаштувати Gii, додавши дозволені IP адреси, як показано нижче, -> +> Note: Якщо ви звертаєтесь до Gii від не локальної машини, доступ буде заборонений за замовчуванням із міркувань +безпеки. Ви можете налаштувати Gii, додавши дозволені IP-адреси, як показано нижче ```php 'gii' => [ 'class' => 'yii\gii\Module', @@ -73,10 +87,10 @@ http://hostname/index.php?r=gii ![Gii](images/start-gii.png) -Генерація класу Active Record <span id="generating-ar"></span> ------------------------------ +Генерування класу Active Record <span id="generating-ar"></span> +------------------------------- -Використовуючи Gii для генерації класу Active Record, виберіть "Model Generator" (натиснувши на посилання на сторінці Gii). +Використовуючи Gii для генерування класу Active Record, виберіть "Model Generator" (натиснувши на посилання на сторінці Gii). Далі заповніть форму наступними даними: * Ім’я таблиці: `country` @@ -90,65 +104,61 @@ http://hostname/index.php?r=gii Якщо при використанні Gii, раніше вже був створений файл моделі, то він буде перезаписаний. Для того, щоб переглянути відмінності в коді натисніть на кнопку `diff` поруч з ім’ям файлу. -![Прев’ю генератора моделі](images/start-gii-model-preview.png) +![Генератор моделі: попередній перегляд](images/start-gii-model-preview.png) -При перезаписі існуючого файлу, встановіть прапорець поруч із чекбоксом "перезаписати" ("overwrite"), а потім натисніть -кнопку "Створити" ("Generate"). При створенні нового файлу, ви можете просто натиснути на кнопку "Створити" ("Generate"). +При перезаписі наявного файлу, позначте пункт "перезаписати" ("overwrite"), а потім натисніть кнопку "Створити" ("Generate"). При створенні нового файлу, ви можете просто натиснути на кнопку "Створити" ("Generate"). -Далі, ви побачите сторінку підтвердження із відображенням коду, який був сгенерований. Якщо ви перезаписували вже існуючий +Далі, ви побачите сторінку підтвердження із відображенням коду, який був успішно згенерований. Якщо ви перезаписували вже наявний файл, то побачите повідомлення про те, що він був переписаний і замінений на щойно згенерований код. -Генерація коду CRUD <span id="generating-crud"></span> -------------------- +Генерування коду CRUD <span id="generating-crud"></span> +--------------------- -CRUD розшифровується як Створити, Прочитати, Оновити, і Видалити, це операції що вирішують чотири спільні завдання -з маніпулюванням даними на більшості веб-сайтів. Щоб створити CRUD інтерфейс використовуючи Gii, оберіть -"CRUD Generator" (натиснувши відповідну кнопку на сторінці Gii). Наприклад, для таблиці "country", заповніть наступні поля форми: +CRUD - це акронім від англійських слів Create, Read, Update, Delete (Створити, Прочитати, Оновити, Видалити), що представляє чотири основні операції над даними на більшості веб-сайтів. +Щоб реалізувати функціонал CRUD використовуючи Gii, оберіть "CRUD Generator" (натиснувши відповідну кнопку на сторінці Gii). Наприклад, для таблиці "country", заповніть форму наступним чином: * Клас моделі: `app\models\Country` * Клас моделі пошуку: `app\models\CountrySearch` * Клас контролера: `app\controllers\CountryController` -![CRUD генератор](images/start-gii-crud.png) +![Генератор CRUD](images/start-gii-crud.png) -Далі, натисніть на кнопку "Перегляду" ("Preview"). Ви побачите файл `models/Country.php` який буде створений в -результаті даних дій. Ви можете натиснути на ім’я файлу класу для перегляду його вмісту. +Далі, натисніть на кнопку "Перегляду" ("Preview"). Ви побачите список файлів, які будуть створені, як показано нижче. -![Прев’ю CRUD генератор](images/start-gii-crud-preview.png) +![Генератор CRUD: попередній перегляд](images/start-gii-crud-preview.png) Якщо ви попередньо створили контролер `controllers/CountryController.php` і файл представлення `views/country/index.php` -(в розділі "Робота з базами даних" даного посібника), оберіть чекбокс "перезаписати" і замініть їх. -(Попередні версії файлів на мають повного CRUD функціоналу.) +(в розділі "Робота з базами даних" даного посібника), позначте пункт "перезаписати" і замініть їх. (Попередні версії файлів на мають повного функціоналу CRUD.) Спробуємо <span id="trying-it-out"></span> --------- -Щоб побачити все, що було створено під час роботи, відкрийте в браузері наступний URL: +Щоб побачити як це працює, відкрийте в браузері наступний URL: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` -Ви побачите таблицю даних, що показує країни з таблиці бази даних. Ви зможете відсортувати сітку, або відфільтрувати -пошук, вказавши умови фільтрації в заголовках стовпців. +Ви побачите таблицю даних, що показує країни з таблиці бази даних. Ви можете відсортувати дані, +або відфільтрувати їх, вказавши умови фільтрації в заголовках колонок. -Для кожної країни, що відображається в таблиці, ви можете використати функції перегляду деталей, оновлення даних, або -взагалі видалити її. Ви також можете натиснути на кнопку "Створити країну" зверху сітки відображення, яка переадресує -вас на форму створення нової країни. +Для кожної країни, що відображається в таблиці, ви можете використати функції перегляду деталей, оновлення даних, або взагалі видалити її. +Ви також можете натиснути на кнопку "Створити країну" ("Create Country") над таблицею, яка переадресує вас на форму створення нової країни. -![Сітка даних країн](images/start-gii-country-grid.png) +![Таблиця даних країн](images/start-gii-country-grid.png) ![Оновлення даних країни](images/start-gii-country-update.png) -Нижче наведено перелік файлів, згенерованих Gii, у тому разі, якщо ви захочете дослідити, як реалізовані ці можливості, -або доналаштувати їх під свої потреби: +Нижче наведено перелік файлів, згенерованих Gii, у тому разі, якщо ви захочете дослідити як реалізовані ці можливості, +або налаштувати їх під свої потреби: * Контролер: `controllers/CountryController.php` * Моделі: `models/Country.php` і `models/CountrySearch.php` * Представлення: `views/country/*.php` +<<<<<<< HEAD > Інформація: Gii це гнучкий і розширюваний інструмент для генерації коду. При правильному використувані, від дозволить <<<<<<< HEAD вам значно прискорити розробку ваших додатків. Для більш докладної інформації, будьласка, зверніться до розділу [Gii](tool-gii.md). @@ -156,6 +166,11 @@ http://hostname/index.php?r=country/index вам значно прискорити розробку ваших додатків. Для більш докладної інформації, будьласка, зверніться до розділу [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md). >>>>>>> yiichina/master +======= +> Info: Gii - це гнучкий і розширюваний інструмент для генерування коду. При правильному використані, він дозволить +вам значно прискорити розробку ваших додатків. Для більш докладної інформації, будь ласка, зверніться до розділу +[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md). +>>>>>>> master Підсумок <span id="summary"></span> diff --git a/docs/guide-uk/start-hello.md b/docs/guide-uk/start-hello.md index df0bf8f531..dae433bcf7 100644 --- a/docs/guide-uk/start-hello.md +++ b/docs/guide-uk/start-hello.md @@ -1,30 +1,34 @@ -Говоримо «Привіт» +Говоримо "Привіт" ================= -В даному розділі розглянемо як створити нову сторінку з надписом «Привіт». В процесі вирішеня задачі ви створите -[дію контролера](structure-controllers.md) і [представлення](structure-views.md): +В даному розділі розглянемо як створити нову сторінку з надписом "Привіт". +У процесі вирішення задачі ви створите [дію контролера](structure-controllers.md#creating-actions) і +[представлення](structure-views.md): -* Додаток опрацює запит і передасть управління відповідній дії -* Дія, в свою чергу, відобразить представлення з надписом "Привіт" кінцевому користувачу +* Додаток опрацює запит і передасть управління відповідній дії, +* а дія, у свою чергу, відобразить представлення з надписом "Привіт" кінцевому користувачу. -З допомогою даного керівництва ви вивчите: +В даному керівництві ви дізнаєтесь як: -1. Як створити [дію](structure-controllers.md), яка буде відповідати на запити -2. Як створити [представлення](structure-views.md), щоб формувати зміст відповіді -3. Як додаток відправляє запити до [дії](structure-controllers.md). +1. створити [дію](structure-controllers.md#creating-actions), яка буде відповідати на запити; +2. створити [представлення](structure-views.md), щоб скомпонувати вміст відповіді; +3. додаток відправляє запити до [дії](structure-controllers.md#creating-actions). Створення дії <span id="creating-action"></span> ------------- -Для нашої задачі знадобиться [дія](structure-controllers.md#creating-actions) `say`, котра читає параметр `message` із -запиту і відображає його значення користувачу. Якщо в запиті відсутній параметр `message`, то дія буде відображати «Привіт». +Для нашої задачі знадобиться [дія](structure-controllers.md#creating-actions) `say`, яка зчитує +параметр `message` із запиту і відображає його значення користувачу. Якщо в запиті +відсутній параметр `message`, то дія буде відображати "Привіт". -> Інформація: [Дії](structure-controllers.md#creating-actions) можуть бути запущені безпосередньо користувачем і - згруповані в [контролери](structure-controllers.md). Результатом виконання дії є відповідь, яку отримує користувач. +> Інформація: [Дії](structure-controllers.md#creating-actions) являються об’єктами, на які користувач може посилатись для +виконання. Дії згруповані в [контролери](structure-controllers.md). Результатом виконання +дії є відповідь, яку отримує користувач. -Дії оголошуються в [контролерах](structure-controllers.md). Для зручності, ви можете оголосити дію -`say` в уже існуючому контролері `SiteController`, який оголошений у файлі класа `controllers/SiteController.php`: +Дії повинні оголошуватись в [контролерах](structure-controllers.md). Для простоти, ви можете +оголосити дію `say` в уже наявному контролері `SiteController`, який визначений +у файлі класу `controllers/SiteController.php`: ```php <?php @@ -35,7 +39,7 @@ use yii\web\Controller; class SiteController extends Controller { - // ...існуючий код... + // ...наявний код... public function actionSay($message = 'Привіт') { @@ -44,34 +48,33 @@ class SiteController extends Controller } ``` +В наведеному коді дія `say` визначена як метод `actionSay` в класі `SiteController`. +Yii використовує префікс `action` для того, щоб відрізняти методи-дії від звичайних методів у класі контролеру. +Назва після префікса `action` вважається ідентифікатором відповідної дії. -В наведеному коді дія `say` оголошена як метод `actionSay` в класі `SiteController`. -Yii використовує префікс `action` для того, щоб відрізняти методи-дії і звичайні методи. Назва після префікса `action` -вважається ідентифікатором відповідної дії. +Коли черга доходить до іменування дій, слід розуміти як Yii поводиться з ідентифікаторами дій. Ідентифікатори дій можуть +бути лише в нижньому регістрі. Якщо ідентифікатор складається з декількох слів, вони з’єднуються дефісами +(наприклад, `create-comment`). Імена методів дій отримуються шляхом видалення дефісів з ідентифікатора, +перетворення першої літери кожного слова у верхній регістр і додавання префікса `action`. Наприклад, +ідентифікатор дії `create-comment` відповідає методу `actionCreateComment`. -Коли черга доходить до іменування дій, слід розуміти як Yii відноситься до ідентифікаторів дій. -Ідентифікатори дій задаються в нижньому регістрі. Якщо ідентифікатор складається з декількох слів, вони -з’єднуються дефісами, тобто `create-comment`. Імена методів дій отримуються шляхом видалення дефісів з ідентифікатора, -перетворення першої літери кожного слова у верхній регістр і додавання префікса `action`. -Наприклад, ідентифікатор дії `create-comment` відповідає методу `actionCreateComment`. +Метод дії у нашому прикладі приймає параметр `$message`, який за замовчуванням визначено як `"Привіт"` (так само, +як ви звикли визначати значення за замовчуванням для аргументів функції у PHP). Коли додаток +отримує запит і визначає, що дія `say` відповідає за його опрацювання, додаток призначає +цьому параметру значення однойменного параметра із запиту. Іншими словами, якщо запит містить +параметр `message` зі значенням `"До побачення"`, то змінній `$message` всередині дії буде призначено це значення. -Метод дії приймає параметр `$message`, який за замовчуванням дорівнює `"Привіт"` (так само, як ви звикли визначати -значення за замовчуванням для аргументів методу у PHP). Коли додаток отримує запит і визначає, -що дія `say` відповідає за його опрацювання, параметр наповнюється одноіменним значенням із запиту. -Іншими словами, якщо запит містить параметр `message` із значенням `"До побачення"`, то змінній `$message` -всередині дії буде призначено це значення. - - -Всередені метода дії, для відображення [представлення](structure-views.md) з ім’ям `say`, використовується метод -[[yii\web\Controller::render()|render()]]. Для того, щоб вивести повідомлення, у представлення передається параметр `message`. -Результат відображення з допомогою `return` передається додатку, котрий віддає його користувачу. +Всередині метода дії, для відображення [представлення](structure-views.md) з ім’ям `say`, використовується метод +[[yii\web\Controller::render()|render()]]. Для того, щоб вивести повідомлення, +у представлення передається параметр `message`. Результат формування повертається методом дії. Цей результат приймається +додатком та відображається кінцевому користувачу в браузері (як частина цілої HTML-сторінки). Створення представлення <span id="creating-view"></span> ----------------------- -[Представлення](structure-views.md) є скриптами, які використовуються для формування тіла відповіді. Для нашого -додатку ви створите представлення `say`, яке буде виводити параметр `message`, отриманий із метода дії: +[Представлення](structure-views.md) є скриптами, які використовуються для генерування вмісту відповіді. +Для нашого додатку ви створите представлення `say`, яке буде виводити параметр `message`, отриманий із методу дії: ```php <?php @@ -81,16 +84,16 @@ use yii\helpers\Html; ``` Представлення `say` мусить бути збережено у файлі `views/site/say.php`. Коли метод [[yii\web\Controller::render()|render()]] -викликається в дії, він буде шукати PHP файл з ім’ям виду `views/ControllerID/ActionID/ViewName.php`. +викликається в дії, він буде шукати PHP-файл з ім’ям виду `views/ControllerID/ViewName.php`. -Варто відмітити, що в коді вище параметр `message` [[yii\helpers\Html::encode()|екранується для HTML]] перед відображенням. -Це обов’язково, так як параметр приходить від користувача, котрий може спробувати провести +Варто зазначити, що у вищезазначеному коді параметр `message` [[yii\helpers\Html::encode()|екранується для HTML]] +перед відображенням. Це обов’язково, бо параметр приходить від користувача, котрий може спробувати провести [XSS атаку](http://uk.wikipedia.org/wiki/%D0%9C%D1%96%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D0%B8%D0%B9_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%96%D0%BD%D0%B3), шляхом вставки небезпечного JavaScript кода. -Ви можете доповнити представлення `say` HTML тегами, текстом або кодом PHP. Фактично, представлення `say` є -простим PHP скриптом, який виконується методом [[yii\web\Controller::render()|render()]]. Зміст, який відображається -скриптом представлення, буде передано додатком користувачу. +Ви можете доповнити представлення `say` HTML-тегами, текстом або кодом PHP. +Фактично, представлення `say` є простим PHP-скриптом, який виконується методом [[yii\web\Controller::render()|render()]]. +Вміст, який формується скриптом представлення, буде передано додатком користувачу. Спробуємо <span id="trying-it-out"></span> @@ -99,41 +102,42 @@ use yii\helpers\Html; Після створення дії і представлення, ви можете перейти на нову сторінку по наступному URL: ``` -http://hostname/index.php?r=site/say&message=Привіт+світ +http://hostname/index.php?r=site%2Fsay&message=Привіт+світ ``` ![Привіт, світ](images/start-hello-world.png) -Буде відображена сторінка з надписом "Привіт світ". Вона використовує ту ж шапку і футер, що і решта сторінок додатка. +Буде відображена сторінка з надписом "Привіт світ". Вона використовує ту ж шапку та футер, що і решта сторінок додатка. -Якщо ви не вкажете параметр `message`, то побичите на сторінці лише «Привіт». Це відбувається тому, що `message` передається -в метод `actionSay()` і значення за замовчуванням — «Привіт». +Якщо ви не вкажете параметр `message` в URL, то побачите на сторінці лише "Привіт". Це відбувається тому, що `message` передається як параметр в метод `actionSay()`, а коли він не вказаний, +використовується значення за замовчуванням "Привіт". -> Інформація: Нова сторінка використовує ту ж шапку і футер, що і решта сторінок, тому що метод - [[yii\web\Controller::render()|render()]] автоматично підставляється в результат представлення `say` в, так званий, - [макет](structure-views.md#layouts) `views/layouts/main.php`. +> Info: Нова сторінка використовує ту ж шапку та футер, що й решта сторінок, тому що метод [[yii\web\Controller::render()|render()]] +автоматично підставляє результат формування представлення `say` в, так званий, [макет](structure-views.md#layouts), який в даному +випадку розміщено у `views/layouts/main.php`. -Параметр `r` потребує додаткових пояснень. Він пов’язаний з [маршрутом (route)](runtime-routing.md), який являє -собою унікальний ідентифікатор, який вказує на дію. Його формат `ControllerID/ActionID`. Коли додаток отримує запит, -він перевіряє цей параметр і, використовуючи `ControllerID`, визначає який контролер слід використовувати для -опрацювання запиту. Потім, контролер використовує частину `ActionID`, щоб визначити яка дія виконує реальну роботу. -В нашому випадку маршрут `site/say` буде відповідати контролеру `SiteController` і його дії `say`. -В результаті, для відпрацювання запиту буде викликано метод `SiteController::actionSay()`. +Параметр `r` у вищезазначеному URL потребує додаткових пояснень. Він вказує [маршрут](runtime-routing.md), що являє собою унікальний для додатка ідентифікатор, +який вказує на дію. Його формат `ControllerID/ActionID`. Коли додаток отримує запит, +він перевіряє цей параметр і, використовуючи частину `ControllerID`, визначає який контролер +слід використовувати для опрацювання запиту. Потім, контролер використовує частину `ActionID`, +щоб визначити яку дію використовувати для виконання реальної роботи. В нашому випадку маршрут `site/say` +буде відповідати контролеру `SiteController` і його дії `say`. В результаті, +для опрацювання запиту буде викликано метод `SiteController::actionSay()`. -> Інформація: Як і дії, контролери також мають ідентифікатори, котрі однозначно визначають їх в додатку. - Ідентифікатори контролерів використовують ті ж самі правила, що і ідентифікатори дій. Імена класів - контролерів отримуються шляхом видалення дефісів з ідентифікатора, перетворення першої літери кожного слова у верхній - регістр і додавання в кінець `Controller`. Наприклад, ідентифікатор контролера `post-comment` відповідає - імені класа контролера `PostCommentController`. +> Info: Як і дії, контролери також мають ідентифікатори, котрі однозначно визначають їх в додатку. +Ідентифікатори контролерів використовують ті ж самі правила іменування, що і ідентифікатори дій. Імена класів контролерів +отримуються шляхом видалення дефісів з ідентифікатора, перетворення першої літери кожного слова у верхній регістр +і додавання в кінець слова `Controller`. Наприклад, ідентифікатор контролера `post-comment` відповідає +імені класу контролера `PostCommentController`. Підсумок <span id="summary"></span> -------- -В даному розділі ви торкнулися теми контролерів і представлень в паттерні MVC. Ви створили дію, як частину контролера, -який опрацьовує запити, і представлення, яке приймає участь у формувані відповіді. В цьому процесі ніяк не була задіяна -модель, так як в ролі даних виступає тільки простий параметр `message`. +В даному розділі ви торкнулися таких частин шаблону проектування MVC як контролери та представлення. +Ви створили дію, як частину контролера, для опрацювання специфічного запиту. А також ви створили представлення +для формування вмісту відповіді. В цьому процесі ніяк не використовувалась модель, оскільки в ролі даних виступає тільки простий параметр `message`. Також ви ознайомились із концепцією маршрутизації, котра є сполучною ланкою між запитом користувача і дією контролера. -В наступному розділі ви дізнаєтесь як створювати моделі і добавляти нові сторінки з HTML формами. +В наступному розділі ви дізнаєтесь як створити модель та додати нову сторінку з HTML-формою. diff --git a/docs/guide-uk/start-installation.md b/docs/guide-uk/start-installation.md index b1965f89ab..44c62e068a 100644 --- a/docs/guide-uk/start-installation.md +++ b/docs/guide-uk/start-installation.md @@ -1,6 +1,7 @@ Встановлення Yii ================ +<<<<<<< HEAD <<<<<<< HEAD Ви можете встановити Yii двома шляхами: використовуючи [Composer](http://getcomposer.org/) або завантаживши архів. Перший варіант э бажанішим, тому що дозволить встановити всі нові [розширення](structure-extensions.md) @@ -10,56 +11,85 @@ або завантаживши архів. Перший варіант э бажанішим, тому що дозволить встановити всі нові [розширення](structure-extensions.md) або оновити Yii однією командою. >>>>>>> yiichina/master +======= +Ви можете встановити Yii двома шляхами: використовуючи менеджер пакунків [Composer](https://getcomposer.org/) або завантаживши файл архіву. +Перший варіант є бажанішим, тому що дозволяє встановлювати нові [розширення](structure-extensions.md) або оновлювати Yii простим виконанням однієї команди. +>>>>>>> master -> Примітка: На відміну від Yii 1, після стандартного встановлення Yii 2 ми отримуємо як фреймворк, так і шаблон додатка. +Після стандартного встановлення Yii ми отримуємо як фреймворк, так і шаблон проекту. Шаблон проекту - це робочий проект Yii, +в якому реалізовано деякий базовий функціонал, такий як система входу/виходу користувачів, форма зворотнього зв’язку і т. д. +Його код організовано в рекомендований спосіб. Таким чином, це може слугувати гарною відправною точкою для ваших проектів. + +У цьому та у наступних кількох розділах буде описано, як встановити Yii з так званим *Базовим шаблоном проекту* та +як реалізувати нові можливості на базі цього шаблону. Також Yii надає інший шаблон із назвою +[Розширений шаблон проекту](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-uk/README.md), який краще використовувати у середовищі розробки в команді +для розробки складних додатків. + +> Info: Базовий шаблон проекту підходить для розробки 90 відсотків веб-додатків. Він відрізняється + від Розширеного шаблону проекту в основному організацією коду. Якщо ви малознайомі з Yii, наполегливо + рекомендується використовувати Базовий шаблон проекту із-за його простоти, але й достатньої функціональності. Встановлення за допомогою Composer <span id="installing-via-composer"></span> ---------------------------------- -Якщо у вас все ще не вставновлено Composer, то це можна зробити за допомогою інструкції на [getcomposer.org](https://getcomposer.org/download/). -Користувачам Linux та Mac OS X потрібно виконати наступні команди: +Якщо у вас все ще не встановлено Composer, то це можна зробити за допомогою інструкції на +[getcomposer.org](https://getcomposer.org/download/). Користувачам Linux та Mac OS X потрібно виконати наступні команди: +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master +======= +```bash + curl -sS https://getcomposer.org/installer | php +>>>>>>> master mv composer.phar /usr/local/bin/composer +``` При роботі з Windows, необхідно завантажити та запустити [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). -В разі наявності проблем або якщо вам необхідна додаткова інформація, зверніться до [документації Composer](https://getcomposer.org/doc/). +В разі наявності проблем або якщо вам необхідна додаткова інформація, +зверніться до [документації Composer](https://getcomposer.org/doc/). -Якщо ж Composer вже було встановлено раніше, переконайтесь, що використовуюєте його останню версію. +Якщо ж Composer вже було встановлено раніше, переконайтесь, що використовуєте його останню версію. Ви можете оновити Composer простою командою `composer self-update`. Після встановлення Composer, встановити Yii можна виконавши наступну команду з директорії, яка доступна через Web: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= +```bash + composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` Перша команда встановить [плагін ресурсів composer (composer-asset-plugin)](https://github.com/francoispluchino/composer-asset-plugin/), -що дозволить керувати залежностями пакетів Bower та NPM за допомогою Composer. Цю команду потрібно виконати лише один раз. -Друга команда встановить Yii у директорію під назвою `basic`. За бажанням, ви можете обрати іншу директорію. +що дозволить керувати залежностями пакунків Bower та NPM за допомогою Composer. Цю команду потрібно виконати лише +один раз. Друга команда встановить Yii у директорію під назвою `basic`. За бажанням, ви можете обрати інше ім’я для директорії. -> Примітка: Під час встановлення може статися так, що Composer запитає облікові дані від вашого профілю на Github, -> через встановлені обмеження запитів Github API. Це є нормальним, оскільки Composer повинен отримати багато інформації -> для всіх пакетів із Github. Надання облікових даних профіля Github збільшить кількість запитів до API, потрібних для -> подальшої роботи Composer. Для більш детальної інформації, будь ласка, зверніться до -> [документації Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). +> Note: Під час встановлення може статися так, що Composer запитає облікові дані від вашого профілю на GitHub, +через встановлені обмеження запитів GitHub API. Це є нормальним, оскільки Composer повинен отримати багато інформації +для всіх пакунків із GitHub. Надання облікових даних профілю GitHub збільшить кількість запитів до API, потрібних для +подальшої роботи Composer. Для більш детальної інформації, будь ласка, зверніться до +[документації Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). -> Підказка: Якщо ви хочете встановити останню нестабільну версію Yii, ви можете виконати наступну команду, -> яка додає опцію [stability](https://getcomposer.org/doc/04-schema.md#minimum-stability): -> -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic -> -> Варто зауважити, що нестабільну версію Yii не можна використовувати на робочому сервері, оскільки вона може порушити -> виконання робочого коду. + +> Tip: Якщо ви хочете встановити останню нестабільну версію Yii, ви можете виконати наступну команду, +яка додає опцію [stability](https://getcomposer.org/doc/04-schema.md#minimum-stability): +```bash + composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +``` +Варто зауважити, що нестабільну версію Yii не можна використовувати на робочому сервері, оскільки вона може порушити +виконання робочого коду. Встановлення з архіву <span id="installing-from-archive-file"></span> @@ -69,11 +99,11 @@ 1. Завантажте архів за адресою [yiiframework.com](http://www.yiiframework.com/download/); 2. Розпакуйте архів в директорію, доступну через Web. -3. Відредагуйте файл конфігурації `config/web.php` - необхідно заповнити секретний ключ до пункту `cookieValidationKey` - (це виконуєтся автоматично при вставленні Yii через Composer): +3. Відредагуйте файл конфігурації `config/web.php` - необхідно ввести таємний ключ до пункту `cookieValidationKey` + (це виконується автоматично при вставленні Yii через Composer): ```php - // !!! встановити секретний ключ до наступного пункту (якщо порожній) - це необхідно для валідації кукі + // !!! встановити таємний ключ до наступного пункту (якщо порожній) - це необхідно для перевірки кукі 'cookieValidationKey' => 'enter your secret key here', ``` @@ -81,74 +111,92 @@ Інші параметри встановлення <span id="other-installation-options"></span> --------------------------- -Вище наведені інструкції по встановленню Yii, які також створюють базовий веб-додаток, готового до роботи. -Це відмінний варіант для невеликих проектів або для тих, хто тільки розпочинає вивчати Yii. +Вище наведені інструкції показують як встановити Yii та створити базовий веб-додаток, який працює "з коробки". +Цей підхід є гарною відправною точкою для більшості проектів, як малих так і великих. Це особливо підходить для тих, хто тільки +розпочинає вивчати Yii. Але є ще й інші варіанти встановлення: * Якщо вам потрібен тільки один фреймворк і ви хотіли б створити додаток з нуля, використовуйте інструкцію, що описана у розділі [Створення додатка з нуля](tutorial-start-from-scratch.md). -* Якщо хочете розпочати з більш насиченого додатка, який добре підходить для роботи в команді, використовуйте - [разширений шаблон додатка](tutorial-advanced-app.md). +* Якщо ви хочете розпочати з більш складного додатка, який краще підходить для роботи в команді, використовуйте + [Розширений шаблон проекту](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-uk/README.md). Перевірка встановлення <span id="verifying-installation"></span> ---------------------- -Після встановлення, ви можете перевірити за допомогою браузера свій встановлений додаток Yii за наступним URL: +Після успішного встановлення ви можете налаштувати свій веб-сервер (див. наступний розділ) +або використати [вбудований веб-сервер PHP](https://secure.php.net/manual/en/features.commandline.webserver.php), +виконавши наступну консольну команду із директорії `web`: -``` -http://localhost/basic/web/index.php +```bash +php yii serve ``` -Даний URL передбачає встановлення додатка в директорію `basic` базової директорії вашого локального веб-сервера (`localhost`). -Можливо вам знадобиться підкорегувати налаштування свого сервера. +> Note: За замовчуванням, HTTP-server буде прослуховувати порт 8080. Проте, +якщо цей порт вже використовується, або ви бажаєте таким чинов використовувати +кілька додатків одразу - ви можете встановити, який саме порт використовувати. +Тільки додайтие аргумент --port: -![Успішно встановленний Yii](images/start-app-installed.png) +```bash +php yii serve --port=8888 +``` -Ви повинні побачити сторінку браузера із привітанням "Congratulations!". Якщо ні — провірте, чи задовільняють -налаштування PHP вимогам Yii одним із способів: +Тепер ви можете використати свій браузер для доступу до встановленого Yii додатку +за наступним посиланням: -* Браузером перейдіть на URL `http://localhost/basic/requirements.php` -* Або виконайте наступні команди в консолі: +``` +http://localhost:8080/ +``` - ``` +![Успішно встановлений Yii](images/start-app-installed.png) + +Ви повинні побачити сторінку із привітанням "Congratulations!" у вашому браузері. Якщо ж ні, будь ласка, перевірте, чи задовольняють +налаштування PHP вимоги Yii. Це можна зробити одним із наведених способів: + +* Скопіюйте файл `/requirements.php` до `/web/requirements.php` та використайте браузер для доступу до URL `http://localhost/requirements.php` +* Виконайте наступні команди в консолі: + + ```bash cd basic php requirements.php ``` -Для коректної роботи фреймворка вам необхідно мати PHP, який відповідає його мінімальним вимогам. -Основна вимога — PHP версії 5.4 або вище. Якщо ваш додаток працює з базою даних, необхідно встановити -[розширення PHP PDO](http://www.php.net/manual/en/pdo.installation.php) та відповідний драйвер -(наприклад, `pdo_mysql` для MySQL). +Необхідно налаштувати PHP таким чином, щоб він відповідав мінімальним вимогам Yii. Основна вимога — PHP версії 5.4 або вище. +Якщо ваш додаток працює з базою даних, необхідно встановити [розширення PHP PDO](http://www.php.net/manual/en/pdo.installation.php) +та відповідний драйвер (наприклад, `pdo_mysql` для MySQL). -Налаштування веб серверів <span id="configuring-web-servers"></span> +Налаштування веб-серверів <span id="configuring-web-servers"></span> ------------------------- -> Інформація: можете пропустити даний підрозділ, якщо ви тільки розпочали знайомитися з фреймворком +> Info: можете пропустити даний підрозділ, якщо ви тільки розпочали знайомитися з фреймворком і не розгортаєте його на робочому сервері. -Додаток, встановлений за інструкціями, наведеними вище, буде працювати одразу як з [Apache HTTP server](http://httpd.apache.org/), -так і з [Nginx HTTP server](http://nginx.org/) під Windows, Mac OS X чи Linux із встановленим PHP 5.4 або вище. -Yii 2.0 також сумісний із віртуальною машиною [HHVM](http://hhvm.com/) фейсбука, однак є деякі крайні випадки, -де HHVM поводиться інакше, ніж рідний PHP, тому ви повинні бути дуже уважними при використанні HHVM. +Додаток, встановлений за інструкціями, наведеними вище, буде працювати одразу як +з [Apache HTTP server](http://httpd.apache.org/), так і з [Nginx HTTP server](http://nginx.org/) на +Windows, Mac OS X чи Linux із встановленим PHP 5.4 або вище. Yii 2.0 також сумісний із віртуальною машиною Фейсбука +[HHVM](http://hhvm.com/), однак є деякі крайні випадки, де HHVM поводиться інакше, +ніж рідний PHP, тому ви повинні бути дуже уважними при використанні HHVM. -На рабочому сервері вам напевно захочеться змінити URL додатку з `http://www.example.com/basic/web/index.php` -на `http://www.example.com/index.php`. Для цього необхідно змінити кореневу директорію в налаштуваннях веб сервера на `basic/web`. -Додатково можно сховати `index.php` із URL, як це описано у розділі [Маршрутизація та створення URL](runtime-routing.md). -Далі буде показано як налаштувати Apache і Nginx для цих цілей. +На робочому сервері вам напевно захочеться змінити URL додатку з `http://www.example.com/basic/web/index.php` +на `http://www.example.com/index.php`. Для цього необхідно змінити кореневу директорію в налаштуваннях веб-сервера на `basic/web`. +Додатково можна сховати `index.php` із URL, як це описано у розділі [Маршрутизація та створення URL](runtime-routing.md). +Далі буде описано як налаштувати Apache або Nginx для цих цілей. -> Інформація: Встанновлюючи `basic/web` кореневою директорією веб-сервера, ви забороняєте кінцевим користувачам доступ - до приватного коду додатка та важливим даним, які знаходяться на одному рівні з `basic/web`. Це робить додаток більш захищенним. +> Info: Встановлюючи `basic/web` кореневою директорією веб-сервера, ви забороняєте кінцевим користувачам доступ + до приватного коду додатка та важливих даних, які знаходяться на одному рівні + з `basic/web`. Це робить додаток більш захищеним. -> Інформація: Якщо додаток працює на хостингу, де немає доступу до налаштувань сервера, ви всеодно можете змінити структуру - додатка для покращення безпеки, як описано в розділі [Робота на shared хостингу](tutorial-shared-hosting.md). +> Info: Якщо додаток працює на хостингу, де немає доступу + до налаштувань сервера, ви все ще можете змінити структуру додатка для покращення безпеки, + як описано в розділі [Робота на віртуальному хостингу](tutorial-shared-hosting.md). ### Рекомендовані налаштування Apache <span id="recommended-apache-configuration"></span> -Додайте наступний код до файлу конфігурации Apache `httpd.conf` або в конфігураційний файл віртуального хоста. +Додайте наступний код до файлу конфігурації `httpd.conf` веб-сервера Apache або в конфігурацію віртуального хоста. Не забудьте замінити `path/to/basic/web` на коректний шлях до `basic/web`. ``` @@ -172,34 +220,38 @@ DocumentRoot "path/to/basic/web" ### Рекомендовані налаштування Nginx <span id="recommended-nginx-configuration"></span> Для використання [Nginx](http://wiki.nginx.org/) вам потрібно встановити PHP як [FPM SAPI](http://php.net/install.fpm). -Використовуйте наступні параметри Nginx, замінивши `path/to/basic/web` на коректний шлях до `basic/web`, -а `mysite.local` на бажаний домен. +Використовуйте наступні параметри Nginx, замінивши `path/to/basic/web` на коректний шлях до +`basic/web`, а `mysite.local` на актуальний домен. ``` server { charset utf-8; client_max_body_size 128M; - listen 80; ## listen for ipv4 - #listen [::]:80 default_server ipv6only=on; ## слухаємо ipv6 + listen 80; ## "слухаємо порт" для ipv4 + #listen [::]:80 default_server ipv6only=on; ## "слухаємо порт" для ipv6 server_name mysite.local; root /path/to/basic/web; index index.php; +<<<<<<< HEAD <<<<<<< HEAD access_log /path/to/basic/log/access.log main; ======= access_log /path/to/basic/log/access.log; >>>>>>> yiichina/master +======= + access_log /path/to/basic/log/access.log; +>>>>>>> master error_log /path/to/basic/log/error.log; location / { - # Перенаправляємо всі запити від неіснуючих директорій або файлів на index.php + # Перенаправляємо всі запити на index.php, якщо це не наявна директорія або файл try_files $uri $uri/ /index.php?$args; } - # розкоментуйте строки нижче для запобігання обробки звернень Yii до неіснуючих статичних файлів + # розкоментуйте рядки нижче для запобігання обробки звернень Yii до не наявних статичних файлів #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { # try_files $uri =404; #} @@ -218,7 +270,8 @@ server { } ``` -Використовуючи дану конфігурацію встановіть `cgi.fix_pathinfo=0` в `php.ini`, щоб запобігти зайвим системним викликам `stat()`. +Використовуючи дану конфігурацію встановіть `cgi.fix_pathinfo=0` у файлі `php.ini`, +щоб запобігти зайвим системним викликам `stat()`. -Врахуйте також, що при використанні HTTPS необхідно задавати `fastcgi_param HTTPS on;` щоб Yii міг корректно -визначати захищене з’єднання. +Врахуйте також, що при використанні HTTPS необхідно задавати `fastcgi_param HTTPS on;` щоб Yii +міг коректно визначати захищене з’єднання. diff --git a/docs/guide-uk/start-looking-ahead.md b/docs/guide-uk/start-looking-ahead.md index f305a7f2a6..a1f072eb28 100644 --- a/docs/guide-uk/start-looking-ahead.md +++ b/docs/guide-uk/start-looking-ahead.md @@ -1,33 +1,34 @@ Наступні кроки ============== -Якщо ви прочитали весь розділ "Приступаючи до роботи", то, напевне, ви створили повноцінний додаток Yii. У процесі ви дізналися, -як реалізувати деякі найбільш часто використовувані функції, таких, як отримання даних від користувачів за допомогою HTML-форми, -вибірка даних з бази даних і відображення даних із розбиттям по сторінкам. Також ви дізналися, як використовувати -[Gii](tool-gii.md) для автоматичної генерації коду, що перетворює більшу частину процесу веб-розробки у завдання настільке просте, -як заповнення деяких форм. +Якщо ви прочитали всю главу "Перше знайомство", то, напевно, ви створили повноцінний додаток Yii. У процесі ви дізналися, як реалізувати деякі найбільш часто +використовувані функції, такі, як отримання даних від користувачів за допомогою HTML-форми, вибірка даних з бази даних +і відображення даних із розділенням на сторінки. Також ви дізналися, як використовувати [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-uk/README.md) +для автоматичного генерування коду, що перетворює більшу частину процесу веб-розробки у завдання настільки просте, як заповнення деяких форм. + +У цьому розділі зібрані ресурси пов’язані з Yii, які допоможуть вам бути більш продуктивними при використанні фреймворку. * Документація - [Докладний Посібник](http://www.yiiframework.com/doc-2.0/guide-README.html): - Як випливає з назви, посібник точно визначає, як Yii повинен працювати і дає вам загальні вказівки щодо його застосування. - Це найважливіший Yii підручник, з яким ви маєте ознайомитись, перш ніж писати Yii код. - - [Опис класів](http://www.yiiframework.com/doc-2.0/index.html): - Визначає використання кожного класу, представленого в Yii. - Їм слід користуватися, коли ви пишете код і хочете розібратися у використанні конкретного класу, методу, властивості. - Опис класів найкраще використовувати після контекстного розуміння всього фреймворка. - - [Wiki статті](http://www.yiiframework.com/wiki/?tag=yii2): - Wiki статті написані користувачами Yii на основі їх власного досвіду. - Більшість з них написані як рецепти з куховарської книги, які показують, як вирішити конкретні проблеми з використанням Yii. + Як випливає з назви, посібник точно визначає, як Yii повинен працювати і дає вам загальні вказівки + щодо його застосування. Це найважливіший Yii підручник, з яким ви маєте ознайомитись, перш ніж писати Yii код. + - [Довідник класів](http://www.yiiframework.com/doc-2.0/index.html): + Визначає використання кожного класу, представленого в Yii. Ним слід користуватися, коли ви пишете + код і хочете розібратися у використанні конкретного класу, методу, властивості. Довідник класів найкраще використовувати після контекстного розуміння всього фреймворку. + - [Wiki-статті](http://www.yiiframework.com/wiki/?tag=yii2): + Wiki-статті написані користувачами Yii на основі їх власного досвіду. Більшість з них написані + як рецепти з куховарської книги, які показують, як вирішити конкретні проблеми, використовуючи Yii. Варто зауважити, що якість даних статей може бути не такою гарною, як у Докладному Посібнику, але вони корисні тим, - що вони охоплюють ширші теми і часто можуть забезпечити готові рішення для подальшого використання. + що охоплюють ширші теми і часто можуть забезпечити готовими рішеннями для подальшого використання. - [Книги](http://www.yiiframework.com/doc/) * [Розширення](http://www.yiiframework.com/extensions/): Yii пишається бібліотекою із тисяч розширень, внесених користувачами, які можуть бути легко підключені у ваші додатки та зробити розробку додатків ще швидшим і простішим. * Спільнота - Форум: <http://www.yiiframework.com/forum/> - - IRC chat: The #yii channel on the freenode network (<irc://irc.freenode.net/yii>) + - Чат IRC: Канал #yii мережі freenode (<irc://irc.freenode.net/yii>) + - Чат Gitter: <https://gitter.im/yiisoft/yii2> - GitHub: <https://github.com/yiisoft/yii2> - - Facebook: <https://www.facebook.com/groups/yiitalk/> - - Twitter: <https://twitter.com/yiiframework> + - Фейсбук: <https://www.facebook.com/groups/yiitalk/> + - Твіттер: <https://twitter.com/yiiframework> - LinkedIn: <https://www.linkedin.com/groups/yii-framework-1483367> - Stackoverflow: <http://stackoverflow.com/questions/tagged/yii2> diff --git a/docs/guide-uk/start-workflow.md b/docs/guide-uk/start-workflow.md index 605ac395c2..0bf17e2f1a 100644 --- a/docs/guide-uk/start-workflow.md +++ b/docs/guide-uk/start-workflow.md @@ -1,6 +1,7 @@ -Запуск додатка -============== +Виконання додатків +================== +<<<<<<< HEAD <<<<<<< HEAD Після встановлення Yii, базовий додаток буде доступний або по URL `http://hostname/basic/web/index.php`, або по `http://hostname/index.php`, в залежності від налаштування Web сервера. Даний розділ - загальне введення в @@ -17,26 +18,48 @@ яка, в свою чергу, встановлена, як коренева директорія в налаштуваннях Web сервера. В результаті, звернувшись до URL >>>>>>> yiichina/master `http://hostname/index.php` ви отримаєте доступ до додатку. Відрегулюйте URL-адреси для ваших потреб. +======= +Після встановлення Yii, базовий додаток буде доступний або по URL `http://hostname/basic/web/index.php`, +або по `http://hostname/index.php`, в залежності від налаштування веб-сервера. Даний розділ - загальне введення в +організацію коду, вбудований функціонал і опрацювання запитів додатком Yii. + +> Info: Для спрощення, далі в даному посібнику припускається, що Yii встановлений в директорію `basic/web`, + яка, в свою чергу, встановлена, як коренева директорія в налаштуваннях веб-сервера. В результаті, звернувшись до URL + на зразок `http://hostname/index.php`, ви отримаєте доступ до додатку. + Будь ласка, відредагуйте URL-адреси наведені у прикладах відповідно до ваших потреб. +>>>>>>> master Функціонал <span id="functionality"></span> ---------- -Вбудований шаблон простого додатку складається з чотирьох сторінок: +Встановлений базовий додаток складається з чотирьох сторінок: +<<<<<<< HEAD * домашня сторінка, відображається при переході по URL `http://hostname/index.php` * "About" ("Про нас") * на сторінці "Contact" знаходиться форма зворотнього зв’язку, на якій користувач може звернутися до розробника по e-mail <<<<<<< HEAD * на сторінці "Login" відображається форма авторизації. Спробуйте авторизуватись з логіном/паролем "admin/admin". Зверніть увагу на зміну розділу "Login" в головному меню на "Logout". +======= +* домашня сторінка, відображається при переході по URL `http://hostname/index.php`; +* сторінка "About" ("Про нас"); +* сторінка "Contact", що відображає форму зворотнього зв’язку, через яку користувач може звернутися до розробника за допомогою електронної пошти; +* сторінка "Login", на якій відображається форма для аутентифікації користувачів. Спробуйте увійти з логіном/паролем + "admin/admin". Зверніть увагу на зміну пункту "Login" в головному меню на "Logout". +>>>>>>> master -Ці сторінки використовують спільний хедер (шапка сайта) і футер (підвал). В "шапці" знаходиться головне меню, за -допомогою якого користувач переміщається по сайту. +Ці сторінки використовують спільну шапку та футер. Шапка містить головне меню, за +допомогою якого здійснюється навігація по сайту. -У нижній частині вікна ви зможете бачити системні повідомлення Yii - журнал, відлагоджувальну інформацію, -повідомлення про помилки, запити до бази даних і т.п. Відображенням данної інформацію керує -[вбудований відладчик](tool-debugger.md), він записує і відображає інформацію про хід виконання додатку. +У нижній частині вікна ви зможете бачити системні повідомлення Yii - налагоджувальну інформацію, +повідомлення про помилки, запити до бази даних і т. п. Відображенням даної інформацію керує +[вбудований налагоджувач](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-uk/README.md), він записує і відображає інформацію про хід виконання додатку. + +Крім веб-додатка, існує консольний скрипт `yii`, що розташований в базовій директорії додатка. +Цей скрипт може бути використаний для виконання фонових завдань або завдань обслуговування додатка. +Все це описано у розділі [Консольні команди](tutorial-console.md). ======= * на сторінці "Login" відображається форма авторизації. Спробуйте авторизуватись з логіном/паролем "admin/admin". @@ -61,38 +84,45 @@ Нижче наведений перелік основних директорій і файлів вашого додатку (вважаємо, що додаток встановлений в директорію `basic`): ``` -basic/ кореневий каталог додатка +basic/ базова директорія додатка composer.json використовується Composer'ом, містить опис додатку - config/ конфігураційні файли + config/ містить конфігураційні файли console.php конфігурація консольного додатка - web.php конфігурація Web додатка + web.php конфігурація веб-додатка commands/ містить класи консольних команд - controllers/ контролери - models/ моделі - runtime/ файли, які генерує Yii під час виконання додатку (логи, кеш і т.п.) + controllers/ містить класи контролерів + models/ містить класи моделей + runtime/ містить файли, які генерує Yii під час виконання додатку (журнали, кеш і т. п.) vendor/ містить пакунки Composer'а і, власне, сам фреймворк Yii - views/ представлення додатку - web/ коренева директорія Web додатку. Містить файли, доступні через Web - assets/ скрипти, які використовуються додатком (js, css) - index.php місце входження в додаток Yii. З нього розпочинається виконання додатку - yii скрипт виконання консольного додатку Yii + views/ містить представлення додатку + web/ базова веб-директорія, містить файли, доступні через Web + assets/ містить файли ресурсів (javascript та css) + index.php вхідний (або bootsrap) скрипт для додатку Yii, з нього розпочинається виконання додатку + yii скрипт виконання консольних команд Yii ``` +<<<<<<< HEAD <<<<<<< HEAD В цілому, додаток Yii можна розділити на дві категорії файлів: розміщенні в `basic/web` і розміщенні в інших директоріях. ======= В цілому, додаток Yii можна розділити на дві категорії файлів: розміщенні в `basic/web` і розміщенні в інших директоріях. >>>>>>> yiichina/master Перша категорія доступна через HTTP (наприклад, браузером), друга недоступна зовні, та і не повинна бути, так як містить службову інформацію. +======= +В цілому, додаток Yii можна розділити на дві категорії файлів: розміщенні в `basic/web` і розміщенні в інших директоріях. +Перша категорія доступна через HTTP (наприклад, браузером), друга недоступна зовні, та і не повинна бути, оскільки містить службову інформацію. +>>>>>>> master -В Yii реалізована схема проектування [модель-представлення-контролер (MVC)](http://http://uk.wikipedia.org/wiki/Model-View-Controller), -яка відповідає структурі директорій додатка. В директорії `models` знаходяться класи [моделей](structure-models.md), -в `views` розміщені скрипти [представлень](structure-views.md), а в каталозі `controllers` всі класи [контролерів](structure-controllers.md) додатка. +В Yii реалізований шаблон проектування [модель-представлення-контролер (MVC)](http://uk.wikipedia.org/wiki/Model-View-Controller), +що відображається на вищезазначеній структурі директорій додатка. В директорії `models` знаходяться всі класи [моделей](structure-models.md), +у `views` розміщені всі скрипти [представлень](structure-views.md), а в директорії `controllers` +всі класи [контролерів](structure-controllers.md) додатка. Діаграма нижче демонструє статичну структуру додатка. ![Статична структура додатка](images/application-structure.png) +<<<<<<< HEAD <<<<<<< HEAD В кожному додатку Yii є місце входження в додаток, `web/index.php` - це єдиний PHP-скрипт доступний для виконання через Web. Він отримує вхідний запит і створює екземпляр [додатку](structure-applications.md). [Додаток](structure-applications.md) @@ -103,6 +133,13 @@ basic/ кореневий каталог додатка опрацьовує вхідні запити з допомогою [компонентів](concept-components.md) і відправляє запит контролеру. >>>>>>> yiichina/master [Віджети](structure-widgets.md) використовуються у [представленнях](structure-views.md) для побудови динамічних інтерфейсів сайта. +======= +В кожному додатку Yii є місце входження в додаток, `web/index.php` - це єдиний PHP-скрипт доступний для виконання через Web. +Він отримує вхідний запит і створює екземпляр [додатку](structure-applications.md). +[Додаток](structure-applications.md) опрацьовує запит з допомогою його [компонентів](concept-components.md) +і відправляє запит елементам MVC. [Віджети](structure-widgets.md) використовуються у [представленнях](structure-views.md) +для побудови складних та динамічних елементів інтерфейсу користувача. +>>>>>>> master Життєвий цикл запиту <span id="request-lifecycle"></span> @@ -112,19 +149,24 @@ basic/ кореневий каталог додатка ![Життєвий цикл запиту](images/request-lifecycle.png) +<<<<<<< HEAD 1. Користувач робить запит до [місця входження](structure-entry-scripts.md) `web/index.php`. <<<<<<< HEAD 2. Скрипт завантажує конфігурацію [configuration](concept-configurations.md) і створює екземпляр ======= 2. Скрипт завантажує конфігурацію [configuration](concept-configurations.md) і створює екземпляр >>>>>>> yiichina/master +======= +1. Користувач робить запит до [вхідного скрипту](structure-entry-scripts.md) `web/index.php`. +2. Вхідний скрипт завантажує [конфігурацію](concept-configurations.md) додатка та створює екземпляр +>>>>>>> master [додатку](structure-applications.md) для наступного опрацювання запиту. 3. Додаток визначає [маршрут](runtime-routing.md) запиту за допомогою компонента [запиту](runtime-requests.md) додатка. 4. Додаток створює екземпляр [контролера](structure-controllers.md) для виконання запиту. 5. Контролер, в свою чергу, створює [дію](structure-controllers.md) і накладає на неї фільтри. 6. Якщо хоч один фільтр поверне помилку - виконання додатку зупиняється. 7. Якщо всі фільтри пройдені - додаток виконується. -8. Дія завантажує модель даних. Скоріше за все із бази даних. -9. Дія генерує представлення, відображаючи в ньому дані (в т.ч. і отримані із моделі). -10. Згенерований вид додатку передається як компонент [відповіді](runtime-responses.md). -11. Компонент "відповіді" відправляє готовий результат роботи додатку браузеру користувача. +8. Дія завантажує модель даних, скоріше за все із бази даних. +9. Дія формує представлення та відображає в ньому дані (в т. ч. і отримані із моделі). +10. Сформований результат передається компоненту [відповіді](runtime-responses.md) додатка. +11. Компонент "відповіді" відправляє готовий результат роботи додатка браузеру користувача. diff --git a/docs/guide-uk/structure-application-components.md b/docs/guide-uk/structure-application-components.md index 4c56f3f47e..8af8cbc2e4 100644 --- a/docs/guide-uk/structure-application-components.md +++ b/docs/guide-uk/structure-application-components.md @@ -1,35 +1,35 @@ Компоненти додатка ================== -Додатки є [сервіс локаторами](concept-service-locators.md). Вони зберігають багато так званих -*компонентів додатку*, які надають різноманітні інструменти для обробки запитів. Наприклад, -компонент `urlManager` відповідає за маршрутизацію веб запитів до потрібного контролера; -компонент `db` надає інструменти для работи з базою даних; і т. д. +Додатки є [Локаторами служб](concept-service-locators.md). Вони зберігають багато так званих +*компонентів додатку*, які надають різноманітні служби для обробки запитів. Наприклад, +компонент `urlManager` відповідає за маршрутизацію веб-запитів до потрібного контролера; +компонент `db` надає служби для роботи з базою даних; і т. д. -Кожний компонент додатка має свій унікальний ID, який дозволяє ідентифікувати його серед інших різноманітних компонентів -в одному і тому ж додатку. Ви можете отримати доступ до компонента наступним чином: +Кожний компонент додатка має свій унікальний ідентифікатор, який дозволяє ідентифікувати його серед інших різноманітних компонентів +в одному і тому ж додатку. Ви можете отримати доступ до компонента за допомогою виразу: ```php \Yii::$app->componentID ``` Наприклад, ви можете використовувати `\Yii::$app->db` для отримання [[yii\db\Connection|з’єднання з БД]], -і `\Yii::$app->cache` для отримання доступу до основного компонента [[yii\caching\Cache|кеша]], зареєстрованого в додатку. +і `\Yii::$app->cache` для отримання доступу до основного компонента [[yii\caching\Cache|кешу]], зареєстрованих в додатку. Компонент додатка створюється при першому звертанні через попередній вираз. -Будь-які подальші звертання будуть повертати той же примірник компонента. +Будь-які подальші звертання будуть повертати той же екземпляр компонента. Компонентами додатку можуть бути будь-які об’єкти. Ви можете зареєструвати їх за допомогою властивості -[[yii\base\Application::components]] в [конфігурації](structure-applications.md#application-configurations) додатка. +[[yii\base\Application::components]] в [конфігурації додатка](structure-applications.md#application-configurations). Наприклад, ```php [ 'components' => [ - // реєстрація "cache" компонента за допомогою назви класу + // реєстрація компонента "cache" за допомогою назви класу 'cache' => 'yii\caching\ApcCache', - // реєстрація "db" компонента за допомогою масива конфігурації + // реєстрація компонента "db" за допомогою масиву конфігурації 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=demo', @@ -37,7 +37,7 @@ 'password' => '', ], - // реєстрація "search" компонента за допомогою анонімної функції + // реєстрація компонента "search" за допомогою анонімної функції 'search' => function () { return new app\components\SolrService; }, @@ -45,18 +45,18 @@ ] ``` -> Інформація: Хоча ви можете зареєструвати стільки компонентів в додатку скільки вам потрібно, - все ж таки варто робити це осмислено. Компоненти додатку схожі на глобальні змінні. - Використання дуже великої кількості компонетів додатку може потенційно зробити ваш код складним для розробки і тестування. - У більшості випадків ви можете просто створити локальний компонент і використовувати його при необхідності. +> Info: Хоча ви можете зареєструвати стільки компонентів в додатку скільки вам потрібно, все ж таки варто робити це осмислено. + Компоненти додатку схожі на глобальні змінні. Використання дуже великої кількості компонентів додатку може потенційно зробити + ваш код складним для тестування і подальшої підтримки. У більшості випадків ви можете просто створити локальний компонент + і використовувати його при необхідності. ## Попереднє завантаження компонентів <span id="bootstrapping-components"></span> Як згадувалося вище, компонент додатка буде створено при першому звертанні. Якщо до нього не буде зроблено звертань під час запиту - його взагалі не буде створено. -Однак іноді, ви можете створити екземпляр компонента додатка для кожного запиту, навіть якщо до нього не зверталися явно. -Щоб зробити це, ви можете додати ідентифікатор до властивості [[yii\base\Application::bootstrap|bootstrap]] додатка. +Однак, за необхідності, ви можете створювати екземпляр компонента додатка для кожного запиту, навіть якщо до нього не зверталися явно. +Щоб зробити це, ви можете додати ідентифікатор компонента до властивості [[yii\base\Application::bootstrap|bootstrap]] додатка. Наприклад, наступна конфігурація додатка завжди гарантує створення компонента `log`: @@ -76,25 +76,26 @@ ## Вбудовані компоненти додатку <span id="core-application-components"></span> -В Yii є декілька *вбудованих* компонентів додатку із фіксованими ID та конфігураціями за замовчуванням. +В Yii є декілька *вбудованих* компонентів додатку із фіксованими ідентифікаторами та конфігураціями за замовчуванням. Наприклад, компонент [[yii\web\Application::request|request]] використовується для збору інформації про запит користувача і розбору його у певний [маршрут](runtime-routing.md); компонент [[yii\base\Application::db|db]] -являє собою з’єднання з базою даних, через яке ви можете виконувати запити. -Саме з допомогою цих вбудованих компонентів Yii додатки можуть обрабляти запит користувача. +являє собою з’єднання з базою даних, через яке ви можете виконувати запити до бази даних. +Саме з допомогою цих вбудованих компонентів Yii додатки можуть обробляти запити користувача. -Нижче наведено перелік вбудованих компонентів додатку. Ви можете конфігурувати їх так само, як і інші компоненти додатку. -Коли ви конфігуруєте вбудований компонент додатку і не вказуєте клас цього компонента, то буде використовуватись +Нижче наведено перелік вбудованих компонентів додатку. Ви можете налаштовувати їх так само, як і інші компоненти додатку. +Коли ви сконфігуруєте вбудований компонент додатку і не вкажете клас цього компонента, то буде використовуватись значення за замовчуванням. -* [[yii\web\AssetManager|assetManager]]: використовується для керування і відображенням ресурсів додатку. - Більш детальна інформація наведена у розділі [Ресурси](output-assets.md). -* [[yii\db\Connection|db]]: являє собою з’єднання з базою даних, через яке ви можете виконувати запити. - Зверніть увагу, що, коли конфігуруєте даний компонент, ви маєте вказати клас компонента, разом із рештою +* [[yii\web\AssetManager|assetManager]]: керує колекціями ресурсів та публікацією ресурсів. + Більш детальна інформація наведена у розділі [Ресурси](structure-assets.md). +* [[yii\db\Connection|db]]: являє собою з’єднання з базою даних, через яке ви можете виконувати запити до бази даних. + Зверніть увагу, що, при конфігурації даного компоненту, ви маєте вказати клас компонента, разом із рештою необхідних параметрів, наприклад [[yii\db\Connection::dsn]]. Більш детальна інформація наведена у розділі [Об’єкти доступу до даних (DAO)](db-dao.md). -* [[yii\base\Application::errorHandler|errorHandler]]: здійснює обработку PHP помилок і виключень. +* [[yii\base\Application::errorHandler|errorHandler]]: здійснює обробку помилок і виключень PHP. Більш детальна інформація наведена у розділі [Обробка помилок](runtime-handling-errors.md). * [[yii\i18n\Formatter|formatter]]: форматує дані для відображення їх кінцевому користувачу. Наприклад, число може +<<<<<<< HEAD бути відображене із різноманітними роздільниками, дата може бути зображена у розширеному форматі. <<<<<<< HEAD Більш детальна інформація наведена у розділі [Форматування даних](output-formatter.md). @@ -102,22 +103,27 @@ Більш детальна інформація наведена у розділі [Форматування даних](output-formatting.md). >>>>>>> yiichina/master * [[yii\i18n\I18N|i18n]]: використовується для перекладу повідомлень і форматування. +======= + бути відображене із роздільниками тисячних розрядів, дата може бути зображена у розширеному форматі. + Більш детальна інформація наведена у розділі [Форматування даних](output-formatting.md). +* [[yii\i18n\I18N|i18n]]: підтримує переклад і форматування повідомлень. +>>>>>>> master Більш детальна інформація наведена у розділі [Інтернаціоналізація](tutorial-i18n.md). -* [[yii\log\Dispatcher|log]]: обробка і маршрутизація логів. - Більш детальна інформація наведена у розділі [Логування](runtime-logging.md). -* [[yii\swiftmailer\Mailer|mail]]: надає можливості для побудови і відправки поштових повідомлень. - Більш детальна інформація наведена у розділі [Відправлення пошти](tutorial-mailing.md). +* [[yii\log\Dispatcher|log]]: керує призначенням журналу. + Більш детальна інформація наведена у розділі [Журналювання](runtime-logging.md). +* [[yii\swiftmailer\Mailer|mail]]: надає можливості для побудови і відправлення поштових повідомлень. + Більш детальна інформація наведена у розділі [Робота з поштою](tutorial-mailing.md). * [[yii\base\Application::response|response]]: являє собою об’єкт відповіді сервера кінцевим користувачам. Більш детальна інформація наведена у розділі [Відповіді](runtime-responses.md). * [[yii\base\Application::request|request]]: являє собою об’єкт запиту, який сервер отримує від кінцевих користувачів. Більш детальна інформація наведена у розділі [Запити](runtime-requests.md). -* [[yii\web\Session|session]]: надає інформація про сесії. - Даний компонент доступний тільки у [[yii\web\Application|веб додатках]]. - Більш детальна інформація наведена у розділі [Сесії і куки](runtime-sessions-cookies.md). +* [[yii\web\Session|session]]: надає інформацію про сесію. + Даний компонент доступний тільки у [[yii\web\Application|веб-додатках]]. + Більш детальна інформація наведена у розділі [Сесії та кукі](runtime-sessions-cookies.md). * [[yii\web\UrlManager|urlManager]]: використовується для розбору і побудови URL. Більш детальна інформація наведена у розділі [Маршрутизація та створення URL](runtime-routing.md). -* [[yii\web\User|user]]: являє собою інформацію авторизованого користувача. - Даний компонент доступний тільки у [[yii\web\Application|веб додатках]]. +* [[yii\web\User|user]]: надає інформацію про аутентифікацію користувача. + Даний компонент доступний тільки у [[yii\web\Application|веб-додатках]]. Більш детальна інформація наведена у розділі [Аутентифікація](security-authentication.md). -* [[yii\web\View|view]]: використовується для відображення представлень. +* [[yii\web\View|view]]: використовується для формування представлень. Більш детальна інформація наведена у розділі [Представлення](structure-views.md). diff --git a/docs/guide-uk/structure-applications.md b/docs/guide-uk/structure-applications.md index 994f5b0f3a..b988cfaeac 100644 --- a/docs/guide-uk/structure-applications.md +++ b/docs/guide-uk/structure-applications.md @@ -2,14 +2,15 @@ ======= Додатки це об’єкти, які керують всією структурою і життєвим циклом прикладної системи Yii. -Кожна Yii прикладна система містить у собі один об’єкт додатка, який створюється у -[вхідному скрипті](structure-entry-scripts.md) і глобально доступний через `\Yii::$app`. +Кожна прикладна система Yii містить у собі один об’єкт додатка, який створюється у +[вхідному скрипті](structure-entry-scripts.md) і глобально доступний через вираз `\Yii::$app`. -> Інформація: В залежності від контексту, коли ми говорим "додаток", це може означати як об’єкт додатка, - так і прикладну систему додатка вцілому. +> Info: Термін "додаток", в залежності від контексту, в якому він використовується, може означати як об’єкт додатка, + так і прикладну систему додатка в цілому. -Існує два типи додатків: [[yii\web\Application|веб додатки]] та [[yii\console\Application|консольні додатки]]. -Як можна здогадатися із назв, перший тип, в основному, займається обробкою веб запитів, а другий - консольними командами. +Існує два типи додатків: [[yii\web\Application|веб-додатки]] та [[yii\console\Application|консольні додатки]]. +Як можна здогадатися із назв, перший тип, в основному, займається обробкою веб-запитів, а другий обробляє +запити консольних команд. ## Конфігурації додатка <span id="application-configurations"></span> @@ -24,24 +25,25 @@ require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); // завантаження конфігурації додатка $config = require(__DIR__ . '/../config/web.php'); -// створення об’єкта додатка і його налаштування +// створення екземпляру додатка і його налаштування (new yii\web\Application($config))->run(); ``` -Як і звичайні [конфігурації](concept-configurations.md), конфігурації додатка вказують як саме слід ініціювати -властивості об’єктів додатка. Через те, що конфігурації додатків часто є складними, вони розбиваються на декілька -[конфігураційних файлів](concept-configurations.md#configuration-files), наприклад, файл `web.php` у наведеному вище прикладі. +Як і звичайні [конфігурації](concept-configurations.md), конфігурації додатка вказують як саме +слід ініціалізувати властивості об’єктів додатка. Через те, що конфігурації додатків часто +є складними, вони зберігаються у декількох [конфігураційних файлах](concept-configurations.md#configuration-files), +наприклад, файл `web.php` у наведеному вище прикладі. ## Властивості додатка <span id="application-properties"></span> -Існує багато важливих властивостей додатка, які ви налаштовуєте в конфігураціях додатка. +Існує багато важливих властивостей додатка, які ви будете налаштовувати в конфігураціях додатка. Ці властивості, зазвичай, описують середовище, у якому працює додаток. Наприклад, додаток мусить знати яким чином завантажувати [контролери](structure-controllers.md), -де зберігати тимчасові файли, і т.п. Нижче ми розглянемо дані властивості. +де зберігати тимчасові файли, і т. п. Нижче ми розглянемо дані властивості. -### Об’язкові властивості <span id="required-properties"></span> +### Обов'язкові властивості <span id="required-properties"></span> В кожному додатку, ви маєте налаштувати мінімум дві властивості: [[yii\base\Application::id|id]] та [[yii\base\Application::basePath|basePath]]. @@ -49,25 +51,25 @@ $config = require(__DIR__ . '/../config/web.php'); #### [[yii\base\Application::id|id]] <span id="id"></span> -Властивість [[yii\base\Application::id|id]] є унікальним ID додатка, який відрізняє його від решти інших додатків. -Здебільшого, це використовується всередені системи. Хоч і не є обов’язковим, але для кращої сумістності рекомендується -використовувати буквено-цифрові символи при налаштуванні ID додатка. +Властивість [[yii\base\Application::id|id]] є унікальним ідентифікатором додатка, який відрізняє його від решти інших додатків. +Здебільшого, це використовується всередині системи. Хоч і не є обов’язковим, але для кращої сумісності рекомендується +використовувати буквено-цифрові символи при налаштуванні ідентифікатора додатка. #### [[yii\base\Application::basePath|basePath]] <span id="basePath"></span> -Властивість [[yii\base\Application::basePath|basePath]] вказує на кореневу директорію додатка. Ця директорія, -яка містить весь код прикладної системи додатка. В цій директорії, зазвичай, можуть знаходитись підкаталоги `models`, -`views`, `controllers`, які містять код, що відповідає шаблону проектування MVC. +Властивість [[yii\base\Application::basePath|basePath]] вказує на кореневу директорію додатка. Це директорія, +яка містить весь код прикладної системи додатка. В цій директорії, зазвичай, знаходяться під-директорії `models`, +`views`, та `controllers`, які містять код, що відповідає шаблону проектування MVC. Ви можете налаштувати властивість [[yii\base\Application::basePath|basePath]], вказавши прямий шлях до директорії -через [псевдонім шляху](concept-aliases.md). В обох випадках, вказана директорія має існувати, інакше буде отримано -виняток. Шлях буде нормалізовано за допомогою виклику функції `realpath()`. +або через [псевдонім шляху](concept-aliases.md). В обох випадках, вказана директорія має існувати, інакше буде отримано +виключення. Шлях буде нормалізовано за допомогою виклику функції `realpath()`. -Властивість [[yii\base\Application::basePath|basePath]] часто використовується для важливих шляхів (наприклад, шлях до -runtime директорії). Саме з цієї причини, псевдонім шляху `@app` є зумовлений вказувати на дану директорію. -Похідні шляхи потім можуть бути сформовані за допомогою цього псевдоніму шляху (наприклад, `@app/runtime` для -звертання до runtime директорії). +Властивість [[yii\base\Application::basePath|basePath]] часто використовується для отримання інших важливих шляхів +(наприклад, шлях до директорії runtime). Саме з цієї причини, псевдонім шляху `@app` визначений представляти цей +шлях. Похідні шляхи потім можуть бути сформовані за допомогою цього псевдоніму шляху (наприклад, `@app/runtime` для +звертання до директорії runtime). ### Важливі властивості <span id="important-properties"></span> @@ -78,8 +80,9 @@ runtime директорії). Саме з цієї причини, псевдо #### [[yii\base\Application::aliases|aliases]] <span id="aliases"></span> -Дана властивість дозволяє налаштувати вам безліч [псевдонімів](concept-aliases.md) у рамках масиву. -Ключами масива є імена псевдонімів, а значеннами - відповідні значення шляхів. Наприклад, +Дана властивість дозволяє налаштувати вам набір [псевдонімів](concept-aliases.md) у рамках масиву. +Ключами масиву є імена псевдонімів, а значеннями - відповідні визначення шляхів. +Наприклад, ```php [ @@ -90,34 +93,34 @@ runtime директорії). Саме з цієї причини, псевдо ] ``` -Ця властивість доступна таким чином, аби ви змогли вказувати псевдоніми в рамках конфігурації додатка, +Ця властивість призначена для того, щоб ви мали змогу вказувати псевдоніми в рамках конфігурації додатка, а не викликаючи метод [[Yii::setAlias()]]. #### [[yii\base\Application::bootstrap|bootstrap]] <span id="bootstrap"></span> -Дана властивість є дуже зручною, вона дозволяє вказувати масив компонентів, котрі мусять бути завантажені -у процесі [[yii\base\Application::bootstrap()|початкового завантаження]] додатка. Наприклад, якщо ви хочете, -щоб [модуль](structure-modules.md) виконував тонке налаштування [URL правил](runtime-routing.md), -ви можете вказати його ID в якості елемента даної властивості. +Дана властивість є дуже зручною, вона дозволяє вказувати масив компонентів, які повинні бути виконані +у процесі [[yii\base\Application::bootstrap()|початкового завантаження]] додатка. Наприклад, якщо вам потрібен +[модуль](structure-modules.md), який виконує тонке налаштування [URL-правил](runtime-routing.md), +ви можете вказати його ідентифікатор в якості елемента даної властивості. -Кожен із елементів даної властивості може вказуватись в одному із наступних форматів: +Кожен з елементів даної властивості може вказуватись в одному із наступних форматів: -- ID компонента додатка, що вказаний у [компонентах](#components). -- ID модуля, що вказаний у [модулях](#modules). -- назва класа. -- масив конфігурації. -- анонімна функциія, яка створює та повертає об’єкт компонента. +- ідентифікатор компонента додатка, що вказаний у [компонентах](#components); +- ідентифікатор модуля, що вказаний у [модулях](#modules); +- назва класу; +- масив конфігурації; +- анонімна функція, яка створює та повертає об’єкт компонента. Наприклад, ```php [ 'bootstrap' => [ - // ID компонента додатка або модуля + // ідентифікатор компонента додатка або модуля 'demo', - // назва класа + // назва класу 'app\components\Profiler', // масив конфігурації @@ -134,10 +137,10 @@ runtime директорії). Саме з цієї причини, псевдо ] ``` -> Інформація: Якщо ID модуля співпадає з ID компонента додатка, перевага буде віддана ініціалізації компонента додатка - під час початкового завантаження. Якщо ж ви хочете використати модуль, вам потрібно використати анонімну функцію, - як показано нижче: -> ```php +> Note: Якщо ідентифікатор модуля збігається з ідентифікатором компонента додатка, перевага буде віддана компоненту додатка +під час початкового завантаження. Якщо ж ви хочете використати модуль, вам потрібно визначити анонімну функцію, +як показано нижче: +```php [ function () { return Yii::$app->getModule('user'); @@ -146,15 +149,17 @@ runtime директорії). Саме з цієї причини, псевдо ``` -Під час початкового завантаження, буде створено кожний компонент. Якщо клас компонента реалізує інтерфейс -[[yii\base\BootstrapInterface]], то буде викликаний його метод [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]]. +Під час початкового завантаження, буде створено екземпляр кожного компоненту. Якщо клас компонента реалізує +інтерфейс [[yii\base\BootstrapInterface]], то його метод [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]] +буде також викликаний. -Ще одним практичним прикладом є конфігурація [базового шаблону додатка](start-installation.md), у якій модулі -`debug` і `gii` визначені як компоненти початкового завантаження, коли додаток знаходиться режимі розробки. +Ще одним практичним прикладом є конфігурація додатку в [базовому шаблоні проекту](start-installation.md), +у якій модулі `debug` та `gii` визначені як компоненти початкового завантаження, коли додаток знаходиться +в середовищі розробки: ```php if (YII_ENV_DEV) { - // налаштування конфігурації для середовища 'dev' + // налаштування конфігурації для середовища розробки ('dev') $config['bootstrap'][] = 'debug'; $config['modules']['debug'] = 'yii\debug\Module'; @@ -163,19 +168,19 @@ if (YII_ENV_DEV) { } ``` -> Примітка: Якщо вказувати велику кількість компонентів у `bootstrap` - це негативно позначиться на продуктивності +> Note: Якщо вказувати велику кількість компонентів у `bootstrap` - це негативно позначиться на швидкодії додатка, оскільки для кожного запиту буде виконуватись один й той самий набір компонентів. Таким чином, потрібно розсудливо використовувати компоненти початкового завантаження. #### [[yii\web\Application::catchAll|catchAll]] <span id="catchAll"></span> -Дана властивість підтримується тільки [[yii\web\Application|веб додатками]]. Вона вказує на -[дії контролера](structure-controllers.md), які мусять обробляти всі вхідні запити від користувача. Переважно, +Дана властивість підтримується тільки [[yii\web\Application|веб-додатками]]. Вона вказує на +[дію контролера](structure-controllers.md), яка мусить обробляти всі вхідні запити від користувача. Переважно, це використовується, коли додаток знаходиться в режимі обслуговування і повинен обробити всі запити через одну дію. -Конфігурація є масивом, перший елемент якого вказує на маршрут події. Решта елементів масиву у форматі ключ-значення -вказують на додаткові параметри, які мають бути передані події. Наприклад, +Конфігурація є масивом, перший елемент якого вказує на маршрут дії. Решта елементів масиву у форматі ключ-значення +вказують на додаткові параметри, які мають бути передані дії. Наприклад, ```php [ @@ -190,8 +195,8 @@ if (YII_ENV_DEV) { #### [[yii\base\Application::components|components]] <span id="components"></span> -Дана властивість є найважливішою. Вона дозволяє вам зареєструвати іменні компоненти у -[компонентах додатку](structure-application-components.md), які ви можете використовувати в інших місцях. Наприклад, +Дана властивість є найважливішою. Вона дозволяє вам зареєструвати список іменованих компонентів, так званих +[компонентів додатку](structure-application-components.md), які ви можете використовувати в інших місцях. Наприклад, ```php [ @@ -207,8 +212,8 @@ if (YII_ENV_DEV) { ] ``` -Кожен компонент додатка вказаний масивом у форматі ключ-значення. Ключ являє собою ID компонента додатка, -у той час як значення являє собою назву класа або [конфігурацію](concept-configurations.md). +Кожен компонент додатка визначений у форматі ключ-значення в масиві. Ключ являє собою ідентифікатор компонента, +у той час як значення являє собою назву класу або [конфігурацію](concept-configurations.md). Ви можете зареєструвати будь-який компонент у додатку, який потім буде доступний глобально за допомогою виразу `\Yii::$app->componentID`. @@ -218,10 +223,10 @@ if (YII_ENV_DEV) { #### [[yii\base\Application::controllerMap|controllerMap]] <span id="controllerMap"></span> -Дана властивість дозволяє вам встановлювати відповідність між ID контролера та його класом. За замовчуванням, -Yii встановлює відповідність між ID контролера та його класом згідно [домовленості](#controllerNamespace) -(таким чином, ID `post` буде відповідати `app\controllers\PostController`). За допомогою налаштування даної властивості -ви можете змінити домовленість для необхідних контролерів. У наведеному прикладі, `account` буде +Дана властивість дозволяє вам встановлювати відповідність між ідентифікатором контролера та його класом. За замовчуванням, +Yii встановлює відповідність між ідентифікатором контролера та його класом згідно [домовленості](#controllerNamespace) +(наприклад, ідентифікатор `post` буде відповідати класу `app\controllers\PostController`). За допомогою налаштування +даної властивості ви можете обійти домовленість для необхідних контролерів. У наведеному прикладі, `account` буде відповідати `app\controllers\UserController`, в той час, як `article` буде відповідати `app\controllers\PostController`. ```php @@ -238,43 +243,43 @@ Yii встановлює відповідність між ID контролер ] ``` -Ключами масиву даної властивості є ID контролерів, а значеннями є назви класів контролерів або -[конфігурації](concept-configurations.md). +Ключами масиву даної властивості є ідентифікатори контролерів, а значеннями є назви відповідних +класів контролерів або [конфігурації](concept-configurations.md). #### [[yii\base\Application::controllerNamespace|controllerNamespace]] <span id="controllerNamespace"></span> -Дана властивість вказує на простір імен за замовчуванням, під яким повинні знаходитись класи контролерів. -За замовчуванням, це значення рівне `app\controllers`. Якщо ID контролера є `post`, то, згідно домовленості, -відповідна назва класу контролера (без простору імен) буде `PostController`, а повна назва класу буде -`app\controllers\PostController`. +Дана властивість вказує на простір імен за замовчуванням, в якому повинні знаходитись класи контролерів. За замовчуванням, +це значення рівне `app\controllers`. Якщо ідентифікатор контролера є `post`, то, за домовленістю, відповідна назва класу +контролера (без простору імен) буде `PostController`, а повна назва класу буде `app\controllers\PostController`. -Класи контролерів можуть також знаходитись у підкаталогах директорії, що відповідає її простору імені. -Наприклад, ID контролера `admin/post` відповідає повне ім’я класа контролера `app\controllers\admin\PostController`. +Класи контролерів можуть також знаходитись у під-директоріях директорії, відповідно до їх простору імен. +Наприклад, ідентифікатору контролера `admin/post` відповідає повне ім’я класу контролера +`app\controllers\admin\PostController`. -Дуже важливо, щоб повне ім’я класа контролера могло бути використане [автозавантаженням](concept-autoloading.md) +Дуже важливо, щоб повне ім’я класу контролера могло бути використане [автозавантаженням](concept-autoloading.md) і фактичний простір імен вашого контролера відповідав цій властивості. В іншому випадку, ви отримаєте помилку -із заголовком "Сторінка не знайдена", коли спробуєте отримати доступ до додатка. +із заголовком "Сторінка не знайдена" ("Page Not Found"), коли спробуєте отримати доступ до додатка. -У випадку, якщо ви хочете змінити домовленість, яку розглянуто вище, ви можете використовувати властивість +У випадку, якщо ви хочете обійти домовленість, яку розглянуто вище, ви можете використовувати властивість [controllerMap](#controllerMap). #### [[yii\base\Application::language|language]] <span id="language"></span> -Дана властивість вказує на мову додатка, на якій додаток повинен відображати зміст кінцевому користувачу. -За замовчуванням, значення даної властивості рівне `en`, означаючи англійську мову. -Якщо ваш додаток підтримує декілька мов, ви необхідно налаштувати дану властивість. +Дана властивість вказує мову, якою додаток повинен відображати вміст кінцевому користувачу. +За замовчуванням значення даної властивості рівне `en`, що означає англійську мову. Необхідно налаштувати дану властивість, +якщо ваш додаток потребує підтримки декількох мов. Значення даної властивості визначає кілька різних аспектів [інтернаціоналізації](tutorial-i18n.md), в тому числі переклади повідомлень, форматування дат, форматування чисел, і т. п. Наприклад, віджет [[yii\jui\DatePicker]] -використовує дану властивість для визначення мови за замовчуванням, на якій має бути зображений календар і -формат даних для календаря. +використовує значення даної властивості для визначення мови, на якій має бути зображений календар і який +формат дати повинен бути. -Рекомендується вказувати мову у рамках стандатру [IETF](http://en.wikipedia.org/wiki/IETF_language_tag). +Рекомендується вказувати мову у рамках стандарту [IETF](http://en.wikipedia.org/wiki/IETF_language_tag). Наприклад, для англійської мови використовується `en`, в той час як для англійської в США - `en-US`. -Більш детальна інформація наведена у розділі [Інтернаціоналізація](tutorial-i18n.md). +Більш детальна інформація про цю властивість може бути знайдена у розділі [Інтернаціоналізація](tutorial-i18n.md). #### [[yii\base\Application::modules|modules]] <span id="modules"></span> @@ -282,7 +287,7 @@ Yii встановлює відповідність між ID контролер Дана властивість визначає [модулі](structure-modules.md), які містить додаток. Значенням властивості є масив імен класів модулів або [конфігурацій](concept-configurations.md), а ключами цього масиву -виступають ID модулів. Наприклад, +виступають ідентифікатори модулів. Наприклад, ```php [ @@ -304,18 +309,18 @@ Yii встановлює відповідність між ID контролер #### [[yii\base\Application::name|name]] <span id="name"></span> -Дана властивість вказує на ім’я додатка, яке може бути відображене кінцевому користувачу. На відміну від властивості +Дана властивість вказує ім’я додатка, яке може бути відображене кінцевому користувачу. На відміну від властивості [[yii\base\Application::id|id]], яка має бути унікальною, значення даної властивості потрібне в основному для -відображення і не є обов’язково є унікальною. +відображення і не повинно обов’язково бути унікальним. Якщо ваш код не використовує дану властивість, то ви можете не налаштовувати її. #### [[yii\base\Application::params|params]] <span id="params"></span> -Дана властивість описує масив глобально доступних параметрів додатка. Замість того, щоб використовувати жорстко -фіксовані числа і строки у вашому коді, краще оголосити їх параметрами додатка в одному місці і використовувати -в необхідних місцях коду. Наприклад, ви можете визначити розмір прев’ю-зображень для зображеннь наступним чином: +Дана властивість визначає масив глобально доступних параметрів додатка. Замість того, щоб використовувати жорстко +фіксовані числа і текстові рядки у вашому коді, краще оголосити їх параметрами додатка в одному місці і використовувати +в необхідних місцях коду. Наприклад, ви можете визначити розмір мініатюр зображень як параметр наступним чином: ```php [ @@ -325,34 +330,34 @@ Yii встановлює відповідність між ID контролер ] ``` -Потім, коли вам потрібно використати дані значення у вашому коді, ви можете зробити це наступним чином: +Потім, коли вам потрібно використати задані значення у вашому коді, ви можете зробити це наступним чином: ```php $size = \Yii::$app->params['thumbnail.size']; $width = \Yii::$app->params['thumbnail.size'][0]; ``` -Якщо пізніше вам знадобиться змінити розмір прев’ю зображення, то вам потрібно буде змінити це значення лише у +Якщо пізніше вам знадобиться змінити розмір мініатюр зображень, то вам потрібно буде змінити це значення лише у конфігураційному файлі додатка, не змінюючи будь-який залежний код. #### [[yii\base\Application::sourceLanguage|sourceLanguage]] <span id="sourceLanguage"></span> Дана властивість вказує мову, на якій написаний код додатка. За замовчуванням значення рівне `'en-US'`, -що означає англійську мову (США). Ви повинні змінити дану властивість, якщо мовою змісту у вашому коді +що означає англійську мову (США). Ви повинні змінити дану властивість, якщо мовою текстового вмісту у вашому коді є не англійська мова. Аналогічно властивості [language](#language), ви повинні вказати дану властивість у рамках стандарту [IETF](http://en.wikipedia.org/wiki/IETF_language_tag). Наприклад, для англійської мови використовується `en`, в той час як для англійської в США - `en-US`. -Більш детальна інформація наведена у розділі [Інтернаціоналізація](tutorial-i18n.md). +Більш детальна інформація про цю властивість може бути знайдена у розділі [Інтернаціоналізація](tutorial-i18n.md). #### [[yii\base\Application::timeZone|timeZone]] <span id="timeZone"></span> Дана властивість надає альтернативний спосіб встановлення часової зони за замовчуванням у процесі роботи додатка. -Таким чином, вказуючи дану властивість, ви, по суті, викликаєте PHP функцію +Таким чином, вказуючи дану властивість, ви, по суті, викликаєте PHP-функцію [date_default_timezone_set()](http://php.net/manual/en/function.date-default-timezone-set.php). Наприклад, ```php @@ -364,13 +369,13 @@ $width = \Yii::$app->params['thumbnail.size'][0]; #### [[yii\base\Application::version|version]] <span id="version"></span> -Дана властивість вказує на версію додатка. За замовчуванням значення рівне `'1.0'`. Ви можете не змінювати +Дана властивість вказує версію додатка. За замовчуванням значення рівне `'1.0'`. Ви можете не змінювати дану властивість, якщо ваш код не використовує її. ### Корисні властивості <span id="useful-properties"></span> -Властивості, які перераховані в даному розділі, не є часто змінюваними, так як їх значення за замовчуванням +Властивості, які перераховані в даному підрозділі, не є часто змінюваними, оскільки їх значення за замовчуванням відповідають загальноприйнятим домовленостям. Однак, ви можете їх налаштувати, якщо вам потрібно використовувати інші значення. @@ -378,36 +383,40 @@ $width = \Yii::$app->params['thumbnail.size'][0]; #### [[yii\base\Application::charset|charset]] <span id="charset"></span> Властивість вказує кодування, яке використовує додаток. За замовчуванням значення рівне `'UTF-8'`, -яке має бути незмінним для більшості додатків, тільки якщо ви не працюєте із застарілим кодом, який використовує -значний об’єм не юнікод даних. +яке має бути незмінним для більшості додатків, тільки якщо ви не працюєте із застарілою системою, яка використовує +значний об’єм даних закодованих не в Unicode. #### [[yii\base\Application::defaultRoute|defaultRoute]] <span id="defaultRoute"></span> -Властивість вказує [маршрут](runtime-routing.md), який повинен використовувати додаток, коли його не вказано у -вхідному запиті. Маршрут може складатись із ID модуля, ID контролера і/або ID дії. Наприклад, `help`, -`post/create`, `admin/post/create`. Якщо дію не вказано, то буде використано значення за замовчуванням, -що вказане у [[yii\base\Controller::defaultAction]]. +Властивість вказує [маршрут](runtime-routing.md), який повинен використовувати додаток, коли жодного не вказано у +вхідному запиті. Маршрут може складатись із ідентифікатора модуля, ідентифікатора контролера і/або ідентифікатора дії. +Наприклад, `help`, `post/create` або `admin/post/create`. Якщо ідентифікатор дії не вказано, то буде використано значення +за замовчуванням, що вказане у [[yii\base\Controller::defaultAction]]. -Для [yii\web\Application|веб додатків] значення за замовчуванням даної властивості рівне `'site'`, що означає -контролер `SiteController` і його дія за замовчуванням. Таким чином, якщо ви спробуєте отримати доступ +Для [[yii\web\Application|веб-додатків]] значення за замовчуванням даної властивості рівне `'site'`, яке означає +контролер `SiteController` і буде використовуватись його дія за замовчуванням. Таким чином, якщо ви спробуєте отримати доступ до додатка, не вказавши маршрут - буде відображено результат дії `app\controllers\SiteController::actionIndex()`. -Для [yii\console\Application|консольних додатків] значення за замовчуванням рівне `'help'`, яке означає, +Для [[yii\console\Application|консольних додатків]] значення за замовчуванням рівне `'help'`, яке означає, що повинна використовуватись вбудована команда [[yii\console\controllers\HelpController::actionIndex()]]. -Таким чином, якщо ви виконаєте команду `yii` без аргументів, вам будет зображена довідкова інформація. +Таким чином, якщо ви виконаєте команду `yii` без аргументів, вам буде відображена довідкова інформація. #### [[yii\base\Application::extensions|extensions]] <span id="extensions"></span> -Дана властивість описує перелік [розширень](structure-extensions.md), які встановлені і використовуються додатком. -За замовчуванням, значенням даної властивості буде масив, отриманий із файла `@vendor/yiisoft/extensions.php`. +Дана властивість визначає перелік [розширень](structure-extensions.md), які встановлені і використовуються додатком. +За замовчуванням значенням даної властивості буде масив, отриманий із файлу `@vendor/yiisoft/extensions.php`. Файл `extensions.php` генерується і підтримується автоматично, коли ви використовуєте <<<<<<< HEAD +<<<<<<< HEAD [Composer](http://getcomposer.org) для встановлення розширень. ======= [Composer](https://getcomposer.org) для встановлення розширень. >>>>>>> yiichina/master +======= +[Composer](https://getcomposer.org) для встановлення розширень. +>>>>>>> master Таким чином, у більшості випадків, вам не потрібно налаштовувати дану властивість. В особливих випадках, коли ви хочете керувати розширеннями власноруч, ви можете вказати дану властивість наступним чином: @@ -418,8 +427,8 @@ $width = \Yii::$app->params['thumbnail.size'][0]; [ 'name' => 'extension name', 'version' => 'version number', - 'bootstrap' => 'BootstrapClassName', // опціонально, може бути також масив конфігурації - 'alias' => [ // опціонально + 'bootstrap' => 'BootstrapClassName', // не обов'язково; може бути також масив конфігурації + 'alias' => [ // не обов'язково '@alias1' => 'to/path1', '@alias2' => 'to/path2', ], @@ -433,62 +442,66 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Як бачите, властивість є масивом специфікацій розширень. Кожне розширення вказано масивом, який складається з елементів `name` і `version`. Якщо розширення має бути виконано в процесі [початкового завантаження](runtime-bootstrapping.md), -то слід вказати елемент у `bootstrap` властивості, який може бути іменем класа або масивом [конфігурації](concept-configurations.md). +то слід вказати елемент `bootstrap`, який може містити ім’я класу початкового завантаження або масив [конфігурації](concept-configurations.md). Розширення також може визначати декілька [псевдонімів](concept-aliases.md). #### [[yii\base\Application::layout|layout]] <span id="layout"></span> -Дана властивість визначає ім’я шаблону за замовчуванням, який мусить бути використаний при формувані -[представлення](structure-views.md). Значення за замовчуванням рівне `'main'`, яке означає, що має бути використаний -шаблон `main.php` в [директорії шаблонів](#layoutPath). Якщо обидві властивості, [директорія шаблонів](#layoutPath) -та [директорія представлень](#viewPath), приймають значення за замовчуванням, то файл шаблону за замовчуванням може -бути представлений псевдонімом шляху як `@app/views/layouts/main.php`. +Дана властивість визначає ім’я макету за замовчуванням, який мусить бути використаний при формуванні [представлення](structure-views.md). +Значення за замовчуванням рівне `'main'`, яке означає, що має бути використаний файл макету `main.php` з [директорії макетів](#layoutPath). +Якщо обидві властивості [директорія макетів](#layoutPath) та [директорія представлень](#viewPath) мають значення за замовчуванням, +то файл макету за замовчуванням може бути представлений псевдонімом шляху як `@app/views/layouts/main.php`. -Для відключення використання шаблону, ви можете вказати дану властивість як `false`, однак це є дуже рідкісним випадком. +Для заборони використання макету, ви можете вказати дану властивість як `false`, однак це є дуже рідкісним випадком. #### [[yii\base\Application::layoutPath|layoutPath]] <span id="layoutPath"></span> -Дана властивість визначає шлях, по якому слід шукати шаблони. Значення за замовчуванням рівне `layouts`, -що означає підпапку у [директорії представлень](#viewPath). Якщо значення [директорії представлень](#viewPath) +Дана властивість визначає шлях, де слід шукати макети. Значення за замовчуванням рівне `layouts`, +що означає під-директорію у [директорії представлень](#viewPath). Якщо значення [директорії представлень](#viewPath) є значенням за замовчуванням, то директорія шаблонів за замовчуванням може бути представлена псевдонімом шляху як `@app/views/layouts`. -Ви можете налаштувати дану властивість як директорію, так і як [псевдонім шляху](concept-aliases.md). +Ви можете налаштувати дану властивість як директорію або як [псевдонім шляху](concept-aliases.md). #### [[yii\base\Application::runtimePath|runtimePath]] <span id="runtimePath"></span> -Дана властивість визначає шлях, по якому зберігаються тимчасові файли, такі як: лог файли, кеш файли. -За замовчуванням це значення рівне директорії, яка преставлена псевдонімом шляху `@app/runtime`. +Дана властивість визначає шлях, де зберігаються тимчасові файли, такі як: файли журналів, кеш-файли. +За замовчуванням це значення рівне директорії, яка представлена псевдонімом шляху `@app/runtime`. -Ви можете налаштувати дану властивість як директорію або як [псевдонім](concept-aliases.md) шляху. Зверніть увагу, +Ви можете налаштувати дану властивість як директорію або як [псевдонім шляху](concept-aliases.md). Зверніть увагу, що дана директорія має бути доступна для запису процесом, який виконує додаток. Також директорія має -бути захищена від доступу кінцевим користувачам, оскільки файли, які зберігаються в ній,, можуть містити важливу інформацію. +бути захищена від доступу для кінцевих користувачів, оскільки файли, які зберігаються в ній, можуть містити важливу інформацію. -Для спрощення роботи з даною директорією, Yii надає зумовлений псевдонім шляху `@runtime`. +Для спрощення роботи з даною директорією, Yii надає попередньо визначений псевдонім шляху `@runtime`. #### [[yii\base\Application::viewPath|viewPath]] <span id="viewPath"></span> Дана властивість визначає базову директорію, де містяться всі файли представлень. Значення за замовчуванням являє -собою псевдонім `@app/views`. Ви можете налаштувати дану властивість як директорі або [псевдонім шляху](concept-aliases.md). +собою псевдонім `@app/views`. Ви можете налаштувати дану властивість як директорію або як [псевдонім шляху](concept-aliases.md). #### [[yii\base\Application::vendorPath|vendorPath]] <span id="vendorPath"></span> Дана властивість визначає директорію сторонніх бібліотек, які використовуються і керуються за допомогою <<<<<<< HEAD +<<<<<<< HEAD [Composer](http://getcomposer.org). Вона містить всі сторонні бібліотеки, які використовуються додатком, ======= [Composer](https://getcomposer.org). Вона містить всі сторонні бібліотеки, які використовуються додатком, >>>>>>> yiichina/master включаючи сам Yii фреймворк. Значеня за замовчуванням являє собою псевдонім `@app/vendor`. +======= +[Composer](https://getcomposer.org). Вона містить всі сторонні бібліотеки, які використовуються додатком, +включаючи сам фреймворк Yii. Значення за замовчуванням являє собою псевдонім `@app/vendor`. +>>>>>>> master -Ви можете налаштувати дану властивість як директорію або [псевдонім шляху](concept-aliases.md). +Ви можете налаштувати дану властивість як директорію або як [псевдонім шляху](concept-aliases.md). При зміні даної властивості, переконайтесь, що ви також змінили відповідним чином налаштування Composer. -Для спрощення роботи з даною директорією, Yii надає зумовлений псевдонім шляху `@vendor`. +Для спрощення роботи з даною директорією, Yii надає попередньо визначений псевдонім шляху `@vendor`. #### [[yii\console\Application::enableCoreCommands|enableCoreCommands]] <span id="enableCoreCommands"></span> @@ -499,7 +512,7 @@ $width = \Yii::$app->params['thumbnail.size'][0]; ## Події додатка <span id="application-events"></span> -Додаток викликає декілька подій під час свого життєвого циклу обробки запиту. +Додаток викликає декілька подій під час життєвого циклу обробки запиту. Ви можете приєднати обробники подій в конфігурації додатка наступним чином: ```php @@ -513,8 +526,8 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Використання синтаксису `on eventName` детально описано у розділі [Конфігурації](concept-configurations.md#configuration-format). -Іншим методом приэднання обробників подій у процесі [початкового завантаження додатку](runtime-bootstrapping.md), -одразу після того, як буде створено додаток, є описаний нижче приклад: +Іншим методом є приєднання обробників подій у процесі [початкового завантаження додатку](runtime-bootstrapping.md), +одразу після того, як буде створено екземпляр додатку. Наприклад: ```php \Yii::$app->on(\yii\base\Application::EVENT_BEFORE_REQUEST, function ($event) { @@ -526,21 +539,20 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Дана подія виникає *до* того, як додаток починає обробляти вхідний запит. Справжнє ім’я події - `beforeRequest`. -Коли виникає ця подія, об’єкт додатка вже створений і проініційований. Таким чином, це є -коректним місцем для додавання вашого коду за допомогою подій для перехвату управління обробки запиту. -Наприклад,в обробник події ви може динамічно призначати мову додатка [[yii\base\Application::language]] -в залежності від деяких параметрів. +Коли виникає ця подія, екземпляр додатка вже сконфігурований і проініціалізований. Таким чином, це є коректним місцем +для додавання вашого коду за допомогою подій для перехоплення управління обробки запиту. Наприклад, +в обробнику події ви можете динамічно визначати властивість [[yii\base\Application::language]] в залежності від деяких параметрів. ### [[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]] <span id="afterRequest"></span> -Дана подія виникає *після* закінчення обробки запиту додатком, але *до* відправки відповіді. +Дана подія виникає *після* закінчення обробки запиту додатком, але *до* відправлення відповіді. Справжнє ім’я події - `afterRequest`. -На момент виникнення даної події, обробка запиту завершена і ви можете використати це для побудови постобробки запиту, +На момент виникнення даної події обробка запиту завершена і ви можете використати це для після-обробки запиту, з метою налаштування відповіді. -Зверніть увагу, що у компоненті [[yii\web\Response|response]] також виникають події в процесі відправки даних кінцевому +Зверніть увагу, що компонент [[yii\web\Response|response]] також викликає події в процесі відправлення даних кінцевому користувачу. Ці події виникають *після* поточної події. @@ -549,10 +561,9 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Подія виникає *до* виконання кожної [дії контролера](structure-controllers.md). Справжнє ім’я події - `beforeAction`. -Параметр події є об’єктом [[yii\base\ActionEvent]]. Обробник події може встановлювати властивість +Параметр події є екземпляром [[yii\base\ActionEvent]]. Обробник події може встановлювати значення властивості [[yii\base\ActionEvent::isValid]] рівним `false` для зупинки виконання дії. - -Наприклад, +Наприклад: ```php [ @@ -565,33 +576,35 @@ $width = \Yii::$app->params['thumbnail.size'][0]; ] ``` -Зверніть увагу на те, що така ж подія `beforeAction` виникає також у [модулях](structure-modules.md) та -[контролерах](structure-controllers.md). Об’єкти додатку є першими, хто ініціює дані події, -за якими ініціюють модулі (якщо такі є), і в кінці - контролери. Якщо обробник події встановлює властивість -[[yii\base\ActionEvent::isValid]] рівним `false`, то всі наступні події не будуть викликані. +Зверніть увагу на те, що така ж подія `beforeAction` викликається також [модулями](structure-modules.md) та +[контролерами](structure-controllers.md). Об’єкти додатку є першими, хто ініціює дані події, +за ними модулі (якщо такі є), і в кінці - контролери. Якщо обробник події встановлює значення властивості +[[yii\base\ActionEvent::isValid]] рівним `false`, то всі наступні події НЕ будуть викликані. ### [[yii\base\Application::EVENT_AFTER_ACTION|EVENT_AFTER_ACTION]] <span id="afterAction"></span> -Ця подія виникає *після* виконання [дії контролера](structure-controllers.md). Справжнє ім’я події - `afterAction`. +Ця подія виникає *після* виконання кожної [дії контролера](structure-controllers.md). +Справжнє ім’я події - `afterAction`. -Параметр події є об’єктом [[yii\base\ActionEvent]]. Через властивість [[yii\base\ActionEvent::result]] -обробник події може отримати доступ або змінити результат дії контролера. Наприклад, +Параметр події є екземпляром [[yii\base\ActionEvent]]. Через властивість [[yii\base\ActionEvent::result]] +обробник події може отримати доступ або змінити результат дії контролера. +Наприклад: ```php [ 'on afterAction' => function ($event) { if (деяка умова) { - // modify $event->result + // змінення $event->result } else { } }, ] ``` -Зверніть увагу на те, що така ж подія `afterAction` виникає також у [модулях](structure-modules.md) та -[контролерах](structure-controllers.md). Ці об’єкти ініціюють події у зворотньому порядку, порівнюючи із `beforeAction`. -Таким чином, контролери є першими, хто ініціює дану подію, далі йдуть модулі (якщо такі є), і врешті - у додатках. +Зверніть увагу на те, що така ж подія `afterAction` викликається також [модулями](structure-modules.md) та +[контролерами](structure-controllers.md). Ці об’єкти ініціюють події у зворотньому порядку, порівнюючи з `beforeAction`. +Таким чином, контролери є першими, хто ініціює дану подію, далі йдуть модулі (якщо такі є), і врешті - об’єкти додатків. ## Життєвий цикл додатка <span id="application-lifecycle"></span> @@ -601,8 +614,8 @@ $width = \Yii::$app->params['thumbnail.size'][0]; Коли [вхідний скрипт](structure-entry-scripts.md) виконується для обробки запиту, додаток пройде наступний життєвий цикл: -1. Вхідний скрипт завантажує конфігурацію додатка у якості масива. -2. Вхідний скрипт створює новий об’єкт додатка: +1. Вхідний скрипт завантажує конфігурацію додатка у якості масиву. +2. Вхідний скрипт створює новий екземпляр додатка: * Викликається метод [[yii\base\Application::preInit()|preInit()]], який налаштовує деякі життєво важливі властивості додатка, як наприклад [[yii\base\Application::basePath|basePath]]. * Реєструється [[yii\base\Application::errorHandler|обробник помилок]]. @@ -611,8 +624,8 @@ $width = \Yii::$app->params['thumbnail.size'][0]; [[yii\base\Application::bootstrap()|bootstrap()]] для початкового завантаження компонентів. 3. Вхідний скрипт викликає метод [[yii\base\Application::run()]] для запуску додатка: * Ініціюється подія [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]]. - * Обробка запиту: розбір інформації запиту на [маршрут](runtime-routing.md) та відповідні параметри; - створення об’єктів модуля, контролера та дії, згідно вказаного маршруту; ініціювання подій. + * Обробляється запит: розбір інформації запиту на [маршрут](runtime-routing.md) та відповідні параметри; + створення об’єктів модуля, контролера та дії, згідно вказаного маршруту; виконання дії контролера. * Ініціюється подія [[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]]. - * Відповідь надсилається кінцевому користувачу. + * Надсилається відповідь кінцевому користувачу. 4. Вхідний скрипт отримує значення статусу виходу із додатка та завершує обробку запиту. diff --git a/docs/guide-uk/structure-controllers.md b/docs/guide-uk/structure-controllers.md index c0be1093a0..f995843e35 100644 --- a/docs/guide-uk/structure-controllers.md +++ b/docs/guide-uk/structure-controllers.md @@ -1,11 +1,11 @@ Контролери ========== -Контролери є частиною [MVC](http://uk.wikipedia.org/wiki/Модель-вид-контролер) архітектури. Це об’єкти класів, -успадкованих від [[yii\base\Controller]] та відповідають за обработку запитів і генерацію відповідей. +Контролери є частиною архітектури [MVC](http://uk.wikipedia.org/wiki/Модель-вид-контролер). Це об’єкти класів, +успадкованих від [[yii\base\Controller]] та відповідають за обробку запитів і генерування відповідей. Зокрема, після отримання контролю від [додатків](structure-applications.md), контролери проаналізують вхідні дані, передадуть їх у [моделі](structure-models.md), додадуть результати моделі у -[представлення](structure-views.md), і в кінцевому підсумку згенерують вихідні відповіді. +[представлення](structure-views.md), і на сам кінець згенерують вихідні відповіді. ## Дії <span id="actions"></span> @@ -13,7 +13,7 @@ Контролери складаються з *дій*, які є основними блоками, до яких може звертатись кінцевий користувач і запитувати виконання того або іншого функціоналу. В контролері може бути одна або декілька дій. -Наступний приклад показує `post` контролер з двома діями: `view` та `create`: +Наступний приклад показує контролер `post` з двома діями: `view` та `create`: ```php namespace app\controllers; @@ -53,24 +53,23 @@ class PostController extends Controller ``` У дії `view` (визначеній методом `actionView()`) код спочатку завантажує [модель](structure-models.md), -відповідно запитуваної ID моделі; Якщо модель успішно завантажена, то код відобразить її за допомогою -[представлення](structure-views.md), під назвою `view`. В іншому випадку - буде визване виключення. +відповідно до запитуваного ідентифікатора моделі; Якщо модель успішно завантажена, то код відобразить її за допомогою +[представлення](structure-views.md), під назвою `view`. В іншому випадку - буде отримане виключення. -У дії `create` (визначеній методом `actionCreate()`), код аналогічний. Він спочатку намагається завантажити +У дії `create` (визначеній методом `actionCreate()`) подібний код. Він спочатку намагається завантажити [модель](structure-models.md) за допомогою даних із запиту і зберегти модель. Якщо все пройшло успішно, -то код перенаправить браузер на дію `view` із ID щойно створеної моделі. В іншому випадку - він відобразить -предаставлення `create`, через яке користувач зможе вказати необхідні дані. +то код перенаправить браузер на дію `view` із ідентифікатором щойно створеної моделі. В іншому випадку - він відобразить +представлення `create`, через яке користувач зможе вказати необхідні дані. ## Маршрути <span id="routes"></span> -Кінцеві користувачі звертаються до дій з допомогою так названих *маршрутів*. Маршрут це рядок, -який складається з наступних частин: +Кінцеві користувачі звертаються до дій з допомогою так названих *маршрутів*. Маршрут це текстовий рядок, який складається з наступних частин: -* ID модуля: він існує, тільки якщо контролер належить не додатку, а [модулю](structure-modules.md); -* ID контролера: рядок, який унікально ідентифікує контролер серед всіх інших контролерів одного і того ж додатка +* ідентифікатор модуля: він існує, тільки якщо контролер належить не додатку, а [модулю](structure-modules.md); +* [ідентифікатор контролера](#controller-ids): текстовий рядок, який унікально ідентифікує контролер серед всіх інших контролерів одного і того ж додатка (або одного й того ж модуля, якщо контролер належить модулю); -* ID дії: рядок, який унікально ідентифікує дію серед всіх інших дій одного й того ж конторолера. +* [ідентифікатор дії](#action-ids): текстовий рядок, який унікально ідентифікує дію серед всіх інших дій одного й того ж контролера. Маршрути можуть мати наступний формат: @@ -80,18 +79,18 @@ ControllerID/ActionID або наступний формат, якщо контролер належить модулю: -```php +``` ModuleID/ControllerID/ActionID ``` Таким чином, якщо користувач звертається до URL `http://hostname/index.php?r=site/index`, то буде викликано дію `index` у контролері `site`. Розділ [Маршрутизація та створення URL](runtime-routing.md) містить більш детальну інформацію -про те, як маршрути співставляються із діями. +про те, як маршрути співвідносяться із діями. ## Створення контролерів <span id="creating-controllers"></span> -У [[yii\web\Application|веб додатках]] контролери повинні бути успадкованими від класу [[yii\web\Controller]] +У [[yii\web\Application|веб-додатках]] контролери повинні бути успадкованими від класу [[yii\web\Controller]] або його нащадків. Аналогічно для [[yii\console\Application|консольних додатків]], контролери повинні бути успадкованими від класу [[yii\console\Controller]] або його нащадків. Наступний код визначає контролер `site`: @@ -106,32 +105,31 @@ class SiteController extends Controller ``` -### ID контролерів <span id="controller-ids"></span> +### Ідентифікатори контролерів <span id="controller-ids"></span> -За звичай контролер зроблений таким чином, що він повинен обробляти запити, які пов’язані з певним ресурсом. -Саме з цієї причини, ID контролерів за завичай є іменниками, які посилаються на ресурс, який вони обробляють. -Наприклад, ви можете використовувати `article` в якості ID контролера, який відповідає за обробку даних публікацій. +Зазвичай контролер зроблений таким чином, що він повинен обробляти запити, які пов’язані з певним ресурсом. +Саме з цієї причини, ідентифікатори контролерів зазвичай є іменниками, які посилаються на ресурс, який вони обробляють. +Наприклад, ви можете використовувати `article` в якості ідентифікатора контролера, який відповідає за обробку даних статей. -За замовчуванням, ID контролерів мають містити тільки наступні символи: англійські букви в нижньому регістрі, цифри, -підкреслення, тире і слеш. Наприклад, обидва `article` та `post-comment` є прийнятними ID контролерів, в той час, +За замовчуванням, ідентифікатори контролерів мають містити тільки наступні символи: англійські букви в нижньому регістрі, цифри, +підкреслення, тире і слеш. Наприклад, обидва ідентифікатори контролера `article` та `post-comment` є прийнятними, в той час, як `article?`, `PostComment`, `admin\post` не є такими. -ID контролера також може містити префікс субдиректорії. Наприклад, у `admin/article` - частина `article` відповідає -контролеру в субдиректорії `admin` [[yii\base\Application::controllerNamespace|простору імен контролера]]. -Допустимими символами для префіксів субдиректорій є: англійські букви в нижньому і верхньому регістрах, цифри, -символи підкреслення і слеш, де слеш - використовується в якості роздільника для багаторівневих субдиректорій -(наприклад `panels/admin`). +Ідентифікатор контролера також може містити префікс під-директорії. Наприклад, у `admin/article` частина `article` відповідає +контролеру в під-директорії `admin` [[yii\base\Application::controllerNamespace|простору імен контролера]]. +Допустимими символами для префіксів під-директорій є: англійські букви в нижньому і верхньому регістрах, цифри, символи підкреслення і +слеш, де слеш використовується в якості роздільника для багаторівневих під-директорій (наприклад, `panels/admin`). ### Іменування класів контролерів <span id="controller-class-naming"></span> -Назви класів контролерів можуть бути отримані із ID контролерів наступними правилами: +Назви класів контролерів можуть бути отримані із ідентифікаторів контролерів наступним чином: -* Привести у верхній регістр перший символ в кожному слові, розділеному дефісами. Зверніть увагу, що, якщо - ID контролера містить слеш, то дане правило поширюється тільки на частину після останнього слеша в ID контролера. -* Прибрати дефіси і замінити будь-який прямий слеш на зворотний. -* Додати суфікс `Controller`. -* Додати на початок [[yii\base\Application::controllerNamespace|простір імен контролера]]. +1. Привести у верхній регістр перший символ в кожному слові, розділеному дефісами. Зверніть увагу, що, якщо ідентифікатор + контролера містить слеш, то дане правило поширюється тільки на частину після останнього слеша в ідентифікаторі контролера. +2. Прибрати дефіси і замінити будь-який прямий слеш на зворотний. +3. Додати суфікс `Controller`. +4. Додати на початок [[yii\base\Application::controllerNamespace|простір імен контролера]]. Нижче наведено декілька прикладів, з урахуванням того, що [[yii\base\Application::controllerNamespace|простір імен контролера]] має значення за замовчуванням `app\controllers`: @@ -141,12 +139,12 @@ ID контролера також може містити префікс суб * `admin/post-comment` відповідає `app\controllers\admin\PostCommentController`; * `adminPanels/post-comment` відповідає `app\controllers\adminPanels\PostCommentController`. -Класи контролерів мають бути [автозавантаженими](concept-autoloading.md). Саме з цієї причини у вищенаведених прикладах +Класи контролерів мають бути [автоматично завантаженими](concept-autoloading.md). Саме з цієї причини у вищенаведених прикладах контролер `article` має бути збереженим у файл, [псевдонім шляху](concept-aliases.md) якого є -`@app/controllers/ArticleController.php`; в той час, як контролер `admin/post2-comment` має знаходитись у файлі -`@app/controllers/admin/Post2CommentController.php`. +`@app/controllers/ArticleController.php`; в той час, як контролер `admin/post-comment` має знаходитись у файлі +`@app/controllers/admin/PostCommentController.php`. -> Інформація: Останній приклад `admin/post2-comment` показує яким чином ви можете розташувати контролер в директорії +> Info: Останній приклад `admin/post-comment` показує яким чином ви можете розташувати контролер в під-директорії [[yii\base\Application::controllerNamespace|простору імен контролера]]. Це дуже зручно, коли ви хочете організувати свої контролери у декілька категорій і не хочете використовувати [модулі](structure-modules.md). @@ -154,7 +152,7 @@ ID контролера також може містити префікс суб ### Мапа контролерів <span id="controller-map"></span> Ви можете налаштувати [[yii\base\Application::controllerMap|мапу контролерів]] для того, щоб подолати -описані вище обмеження іменування ID контролерів і назв класів. В основному, це дуже зручно, коли ви використовуєте +описані вище обмеження іменування ідентифікаторів контролерів і назв класів. В основному, це дуже зручно, коли ви використовуєте сторонні контролери, іменування яких ви не можете контролювати. Ви можете налаштувати [[yii\base\Application::controllerMap|мапу контролерів]] в @@ -179,16 +177,21 @@ ID контролера також може містити префікс суб Кожний додаток має контролер за замовчуванням, вказаний через властивість [[yii\base\Application::defaultRoute]]. <<<<<<< HEAD +<<<<<<< HEAD Коли в запиті не вказано [маршрут](#ids-routes), то буде використано маршрут із даної властивості. ======= Коли в запиті не вказано [маршрут](#routes), то буде використано маршрут із даної властивості. >>>>>>> yiichina/master Для [[yii\web\Application|веб додатків]], це значення рівне `'site'`, у той час, як для [[yii\console\Application|консольних додатків]], це `'help'`. Таким чином, якщо вказаний URL +======= +Коли в запиті не вказано [маршрут](#routes), то буде використано маршрут із зазначеної властивості. +Для [[yii\web\Application|веб-додатків]] це значення рівне `'site'`, у той час, як для +[[yii\console\Application|консольних додатків]], це - `'help'`. Таким чином, якщо вказаний URL +>>>>>>> master `http://hostname/index.php`, це значить, що контролер `site` виконає обробку запиту. -Ви можете змінити контролер за замовчуванням наступним чином в -[налаштуваннях додатку](structure-applications.md#application-configurations): +Ви можете змінити контролер за замовчуванням наступним чином в [налаштуваннях додатку](structure-applications.md#application-configurations): ```php [ @@ -199,9 +202,9 @@ ID контролера також може містити префікс суб ## Створення дій <span id="creating-actions"></span> -Створення дій може бути таким же простим, як і оголошення так званих *методов дій* у класі контролера. -Метод дії це *public* метод, ім’я якого починається зі слова `action`. Значення методі дії, що повертається, є даними -відповіді, які будуть вислані кінцевому користувачу. Наведений нижче код визначає дві дії - `index` і `hello-world`: +Створення дій може бути настільки ж простим, як і оголошення так званих *методів дій* у класі контролера. Метод дії це +*публічний* метод, ім’я якого починається зі слова `action`. Значення, яке повертається методом дії, представляє дані +відповіді, які будуть відправлені кінцевому користувачу. Наведений нижче код визначає дві дії `index` і `hello-world`: ```php namespace app\controllers; @@ -223,51 +226,53 @@ class SiteController extends Controller ``` -### ID дій <span id="action-ids"></span> +### Ідентифікатори дій <span id="action-ids"></span> -Частіше за все, дія розробляється для певної конкретної обробки ресурса. З цієї причини ID дій, в основному, -є дієсловами, такими як `view`, `update`, і т.д. +Частіше за все, дія розробляється для певної маніпуляції над ресурсом. З цієї причини ідентифікатори дій, в основному, +є дієсловами, такими як `view`, `update`, і т. д. -За замовчуванням, ID дій повинні містити тільки такі символи: англійські букви в нижньому регістрі, цифри, -підкреслення і дефіси. Дефіси в ID дій використовуються для поділу слів. Наприклад, `view`, `update2`, -`comment-post` є допустимими ID дій, в той час, як `view?`, `Update` не є такими. +За замовчуванням, ідентифікатори дій повинні містити тільки такі символи: англійські букви в нижньому регістрі, цифри, +підкреслення і дефіси. (Дефіси можуть використовуються для поділу слів.) Наприклад, `view`, `update2` і +`comment-post` є допустимими ідентифікаторами дій, в той час, як `view?` та `Update` не є такими. -Ви можете створювати дії двома способами: вбудовані дії і окремі дії. Вбудована дія є методом, визначеним -в класі контролера, тоді як окрема дія є екземпляром класу, успадкованого від [[yii\base\Action]] або його нащадків. +Ви можете створювати дії двома способами: вбудовані дії і автономні дії. Вбудована дія є методом, визначеним +в класі контролера, тоді як автономна дія є класом, успадкованим від [[yii\base\Action]] або його нащадків. Вбудовані дії вимагають менше зусиль для створення і, в основному, використовуються якщо у вас немає потреби -у повторному використанні цих дій. Окремі дії, з іншого боку, в основному створюються для використання в різних -контролерах або при використанні у [розширеннях](structure-extensions.md). +у повторному використанні цих дій. Автономні дії, з іншого боку, в основному створюються для використання в різних +контролерах або для розподілення у вигляді [розширень](structure-extensions.md). ### Вбудовані дії <span id="inline-actions"></span> -Вбудовані дії це ті дії, які визначені у рамках методів контролера, як ми це вже обговорили. +Вбудовані дії це ті дії, які визначені у рамках методів контролера, як це було щойно описано. -Назви методів дій можуть бути отримані із ID дій наступним чином: +Назви методів дій можуть бути отримані із ідентифікаторів дій наступним чином: -* Привести перший символ кожного слова в ID дії у верхній регістр; -* Прибрати дефіси; -* Додати префікс `action`. +1. Привести перший символ кожного слова в ідентифікаторі дії у верхній регістр. +2. Прибрати дефіси. +3. Додати префікс `action`. Наприклад, `index` перетвориться у `actionIndex`, а `hello-world` перетвориться у `actionHelloWorld`. -> Примітка: Назви імен дій є *регістрозалежними*. Якщо у вас є метод `ActionIndex`, то його не буде враховано +> Note: Назви імен методів дій є *регістр-залежними*. Якщо у вас є метод `ActionIndex`, то його не буде враховано як метод дії, і в результаті, запит до дії `index` призведе до отримання виключення. Також слід врахувати, що методи дій повинні бути публічними ("public"). Приватні ("private") або захищені ("protected") методи НЕ визначають вбудованих дій. + В основному використовуються вбудовані дії, оскільки для їх створення не потрібного багато зусиль. Тим не менше, якщо ви плануєте повторно використовувати деякі дії у різних місцях або якщо ви хочете -перерозподілити дії, ви повинні визначити їх як *окремими діями*. +перерозподілити дії, ви повинні визначити їх як *автономні дії*. -### Окремі дії <span id="standalone-actions"></span> +### Автономні дії <span id="standalone-actions"></span> -Окремі дії визначаються в якості класів, успадкованих від [[yii\base\Action]] або його нащадків. -Наприклад, в релізах Yii присутні [[yii\web\ViewAction]] та [[yii\web\ErrorAction]], обидва класи є окремими діями. +Автономні дії визначаються в якості класів, успадкованих від [[yii\base\Action]] або його нащадків. +Наприклад, в релізах Yii присутні [[yii\web\ViewAction]] та [[yii\web\ErrorAction]], обидва класи +є окремими діями. -Для використання окремої дії, ви маєте вказати її у *мапі дій* за допомогою перевизначення методу -[[yii\base\Controller::actions()]] у вашому класі контролера, наступним чином: +Для використання автономної дії, ви маєте вказати її у *мапі дій* за допомогою перевизначення методу +[[yii\base\Controller::actions()|actions()]] у вашому класі контролера, наступним чином: ```php public function actions() @@ -285,12 +290,13 @@ public function actions() } ``` -Як ви можете бачити, метод `actions()` повинен повернути масив, ключами якого є ID дій, а значеннями - відповідні -назви класів дій або [конфігурацій](concept-configurations.md). На відміну від вбудованих дій, ID окремих дій +Як ви можете бачити, метод `actions()` повинен повернути масив, ключами якого є ідентифікатори дій, а значеннями - відповідні +назви класів дій або [конфігурації](concept-configurations.md). На відміну від вбудованих дій, ідентифікатори автономних дій можуть містити довільні символи, доки вони визначені у методі `actions()`. -Для створення окремої дії, ви повинні успадкуватись від класу [[yii\base\Action]] або його нащадків, і реалізувати -публічний ("public") метод `run()`. Роль метода `run()` аналогічна іншим методам дій. Наприклад, +Для створення класу автономної дії, ви повинні успадкуватись від класу [[yii\base\Action]] або його нащадків, і реалізувати +публічний ("public") метод [[yii\base\Action::run()|run()]]. Роль метода [[yii\base\Action::run()|run()]] аналогічна +іншим методам дій. Наприклад, ```php <?php @@ -310,20 +316,20 @@ class HelloWorldAction extends Action ### Результати дій <span id="action-results"></span> -Значення, що повертається від метода дії або метода `run()` окремої дії дуже важливе. +Значення, що повертається від методу дії або методу [[yii\base\Action::run()|run()]] автономної дії дуже важливе. Воно є результатом виконання відповідної дії. -Значення, що повертається, може бути об’єктом [відповіді](runtime-responses.md), яке буде відправлено -кінцевому користувачу. +Значення, що повертається, може бути об’єктом [відповіді](runtime-responses.md), яке буде відправлено кінцевому користувачу. -* Для [[yii\web\Application|веб додатків]], значення, що повертається, також може бути довільними даними, - яке буде призначене до [[yii\web\Response::data]], а потім конвертоване у рядок, що представляє тіло відповіді. +* Для [[yii\web\Application|веб-додатків]], значення, що повертається, також може бути довільними даними, + яке буде призначене до [[yii\web\Response::data]], а потім конвертоване у текстовий рядок, що представляє тіло відповіді. * Для [[yii\console\Application|консольних додатків]], значення, що повертається, також може бути числом, що представляє [[yii\console\Response::exitStatus|статус виходу]] виконання команди. -В вищенаведених прикладах всі результати дій є рядками, які будуть використані у якості тіла відповіді користувачу. -Наступний приклад показує як дія може перенаправити браузер користувача на новий URL за допомогою -повернення об’єкта відповіді (оскільки метод [[yii\web\Controller::redirect()|redirect()]] повертає об’єкт response): +У вищенаведених прикладах всі результати дій є текстовими рядками, які будуть використані у якості тіла відповіді для +відправлення кінцевому користувачу. Наступний приклад показує як дія може перенаправити браузер користувача на новий URL +за допомогою повернення об’єкта відповіді (оскільки метод [[yii\web\Controller::redirect()|redirect()]] повертає +об’єкт response): ```php public function actionForward() @@ -336,12 +342,12 @@ public function actionForward() ### Параметри дій <span id="action-parameters"></span> -Методи дій для вбудованих дій і методи `run()` для окремих дій можуть приймати, так звані, *параметри дії*. -Їх значення беруться із запитів. Для [[yii\web\Application|веб додатків]], значення кожного з параметрів дії -береться із `$_GET`, використовуючи назву параметра у якості ключа; -для [[yii\console\Application|консольних додатків]] - вони відповідають аргументам командної строки. +Методи дій для вбудованих дій і методи [[yii\base\Action::run()|run()]] для автономних дій можуть приймати, так звані, +*параметри дії*. Їх значення беруться із запитів. Для [[yii\web\Application|веб-додатків]], значення кожного з +параметрів дії береться із `$_GET`, використовуючи назву параметра у якості ключа; +для [[yii\console\Application|консольних додатків]] - вони відповідають аргументам командного рядка. -В наступному прикладі, дія `view` (вбудовона дія) визначає два параметри: `$id` і `$version`. +В наступному прикладі, дія `view` (вбудована дія) оголошує два параметри: `$id` і `$version`. ```php namespace app\controllers; @@ -360,7 +366,7 @@ class PostController extends Controller Параметри дії будуть заповнені для різних запитів наступним чином: * `http://hostname/index.php?r=post/view&id=123`: параметру `$id` буде присвоєне значення `'123'`, у той час, - як `$version` буде мати значення null, так як рядок запиту не містить параметра `version`. + як `$version` буде мати значення null, бо рядок запиту не містить параметра `version`. * `http://hostname/index.php?r=post/view&id=123&version=2`: параметрам `$id` і `$version` будуть присвоєні значення `'123'` і `'2'` відповідно. * `http://hostname/index.php?r=post/view`: буде отримане виключення [[yii\web\BadRequestHttpException]], оскільки @@ -368,8 +374,7 @@ class PostController extends Controller * `http://hostname/index.php?r=post/view&id[]=123`: буде отримане виключення [[yii\web\BadRequestHttpException]], оскільки параметр `$id` отримав невірне значення `['123']`. -Якщо ви хочете, щоб параметр дії приймав масив значень, ви повинні використати type-hint `array` параметра метода, -як зображено нажче: +Якщо ви хочете, щоб параметр дії приймав масив значень, ви повинні вказати тип `array` для параметра метода, як наведено нижче: ```php public function actionView(array $id, $version = null) @@ -380,23 +385,27 @@ public function actionView(array $id, $version = null) Тепер, якщо запит буде містити URL `http://hostname/index.php?r=post/view&id[]=123`, то параметр `$id` отримає значення `['123']`. Якщо запит буде містити URL `http://hostname/index.php?r=post/view&id=123`, то параметр -`$id` все рівно отримає масив, оскільки скалярне значення `'123'` буде автоматично сконвертовано у масив. +`$id` все одно отримає масив, оскільки скалярне значення `'123'` буде автоматично перетворено у масив. -Вищенаведені приклади в основному показують як параметри дій працюють для веб додатків. Більше інформації -про параметри консольних додатків наведено в секції [Консольні команди](tutorial-console.md). +Вищенаведені приклади в основному показують як параметри дій працюють для веб-додатків. Більше інформації +про параметри консольних додатків наведено в розділі [Консольні команди](tutorial-console.md). ### Дія за замовчуванням <span id="default-action"></span> Кожний контролер містить дію за замовчуванням, визначену через властивість [[yii\base\Controller::defaultAction]]. <<<<<<< HEAD +<<<<<<< HEAD Коли [маршрут](#ids-routes) містить тільки ID контролера, то розуміється, що було запитана дія контролера ======= Коли [маршрут](#routes) містить тільки ID контролера, то розуміється, що було запитана дія контролера >>>>>>> yiichina/master +======= +Коли [маршрут](#routes) містить тільки ідентифікатор контролера, то розуміється, що було запитана дія контролера +>>>>>>> master за замовчуванням. -За замовчуванням, ця дія має значення `index`. Якщо ви хочете змінити це значення - просто перевизначте дану +За замовчуванням, ця дія має значення `index`. Для зміни цього значення необхідно просто перевизначити дану властивість у класі контролера наступним чином: ```php @@ -422,29 +431,27 @@ class SiteController extends Controller який було запитано. Для виконання запиту, контролер пройде через наступні етапи життєвого циклу: 1. Метод [[yii\base\Controller::init()]] буде викликаний після того, як контролер був створений і сконфігурований. -2. Контролер створить об’єкт дії, базуючись на ID дії, яку було запитано: - * Якщо ID дії не вказано, то буде використано [[yii\base\Controller::defaultAction|ID дії за замовчуванням]]; - * Якщо ID дії знайдено у [[yii\base\Controller::actions()|мапі дій]], то буде створено окрему дію; - * Якщо ID дії відповідає методу дії, то буде створено вбудовану дію; +2. Контролер створить об’єкт дії, базуючись на ідентифікаторі дії, яку було запитано: + * Якщо ідентифікатор дії не вказано, то буде використано [[yii\base\Controller::defaultAction|ідентифікатор дії за замовчуванням]]; + * Якщо ідентифікатор дії знайдено у [[yii\base\Controller::actions()|мапі дій]], то буде створено автономну дію; + * Якщо ідентифікатор дії відповідає методу дії, то буде створено вбудовану дію; * В іншому випадку, буде отримане виключення [[yii\base\InvalidRouteException]]. 3. Контролер послідовно викликає метод `beforeAction()` додатка, модуля (якщо контролер належить модулю) і - самого контролера. + самого контролера: * Якщо один із методів повернув `false`, то решта невикликаних методів `beforeAction` будуть пропущені, - а виконання дії буде відмінено; - * За замовчуванням, кожний виклик метода `beforeAction()` викликає подію `beforeAction`, на яку ви можете - призначити обробники. + а виконання дії буде скасовано; + * За замовчуванням, кожний виклик метода `beforeAction()` викликає подію `beforeAction`, на яку ви можете призначити обробник. 4. Контролер виконує дію: - * Параметри дії будуть проаналізовані і заповнені із даних запиту. -5. Контролер послідовно викликає методи `afterAction` контролера, модуля (якщо контролер належить модулю) і додатка. - * За замовчуванням, кожний виклик метода `afterAction()` викликає подію `afterAction`, на яку ви можете - призначити обробники. + * Параметри дії будуть проаналізовані та заповнені із даних запиту. +5. Контролер послідовно викликає методи `afterAction` контролера, модуля (якщо контролер належить модулю) і додатка: + * За замовчуванням, кожний виклик метода `afterAction()` викликає подію `afterAction`, на яку ви можете призначити обробник. 6. Додаток, отримавши результат виконання дії, привласнює його об’єкту [response](runtime-responses.md). ## Кращі практики <span id="best-practices"></span> -В добре організованих додатках, контролери за звичай дуже тонкі, і містять лише декілька рядків коду. -Якщо ваш контролер дуже складний, це за звичай означає, що вам потрібно провести його рефакторинг і +В добре організованому додатку контролери, зазвичай дуже малі, з діями, що містять лише декілька рядків коду. +Якщо ваш контролер дуже складний, це зазвичай означає, що вам потрібно провести його рефакторинг і перенести деякий код в інші класи. В цілому, контролери @@ -453,8 +460,12 @@ class SiteController extends Controller * можуть викликати методи [моделей](structure-models.md) та інших компонентів системи із даними запиту; * можуть використовувати [представлення](structure-views.md) для формування відповіді; <<<<<<< HEAD +<<<<<<< HEAD * не повинні займатись обробкою даних - це має відбуватися у [моделях](structure-models.md); ======= * НЕ повинні займатись обробкою даних - це має відбуватися на [рівні моделей](structure-models.md); >>>>>>> yiichina/master +======= +* НЕ повинні займатись обробкою даних - це має відбуватися на [рівні моделей](structure-models.md); +>>>>>>> master * мають уникати використання HTML або іншої розмітки - краще це робити у [представленнях](structure-views.md). diff --git a/docs/guide-uk/structure-entry-scripts.md b/docs/guide-uk/structure-entry-scripts.md index dcb5d5648c..cef3c1b325 100644 --- a/docs/guide-uk/structure-entry-scripts.md +++ b/docs/guide-uk/structure-entry-scripts.md @@ -1,35 +1,40 @@ Вхідні скрипти ============== -Вхідні скрипти це перша ланка в процесі початкового завантаження додатку. Додаток (веб додаток або консольний додаток) -має єдиний вхідний скрипт. Кінцеві користувачі роблять запити до вхідного скрипта, який створює об’єкти додатка та +Вхідні скрипти це перша ланка в процесі початкового завантаження додатку. Додаток (веб-додаток або консольний додаток) +має єдиний вхідний скрипт. Кінцеві користувачі роблять запити до вхідного скрипту, який створює екземпляри додатка та перенаправляє запит до них. -Вхідні скрипти для веб додатків повинні бути збережені в директоріях, доступних із веб, таким чином, вони можуть бути -доступними кінцевим користувачам. Зазвичай вони називаються `index.php`, але також можут використовуватись і інші +Вхідні скрипти для веб-додатків повинні бути збережені в директоріях, доступних через веб, таким чином, вони можуть бути +доступними кінцевим користувачам. Зазвичай вони називаються `index.php`, але також можуть використовуватись й інші імена, які можуть бути розпізнані веб-серверами. -Вхідні скрипти для консольних додатків зазвичай розміщенні у [кореневій директорії](structure-applications.md) +Вхідні скрипти для консольних додатків зазвичай розміщенні у [базовій директорії](structure-applications.md) додатку і мають назву `yii` (з суфіксом `.php`). Вони повинні мати права на виконання, щоб користувачі мали змогу запускати консольні додатки через команду `./yii <маршрут> [аргументи] [опції]`. Вхідні скрипти в основному виконують наступну роботу: +<<<<<<< HEAD * Оголошують глобальні константи; <<<<<<< HEAD * Реєструють автозавантажувач класів [Composer](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * Реєструють автозавантажувач класів [Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* Визначають глобальні константи; +* Реєструють автозавантажувач класів [Composer](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * Підключають файл класу [[Yii]]; * Завантажують конфігурацію додатка; -* Створюють і конфігурують об’єкт [додатка](structure-applications.md); +* Створюють і налаштовують екземпляр [додатка](structure-applications.md); * Викликають метод [[yii\base\Application::run()]] додатка для обробки вхідного запиту. -## Веб додатки <span id="web-applications"></span> +## Веб-додатки <span id="web-applications"></span> -Нижче наведений код вхідного скрипта для [базового шаблону додатка](start-installation.md). +Нижче наведений код вхідного скрипту для [базового шаблону проекту](start-installation.md). ```php <?php @@ -40,7 +45,7 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); // реєстрація автозавантажувача класів Composer require(__DIR__ . '/../vendor/autoload.php'); -// підключення файла класу Yii +// підключення файлу класу Yii require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); // завантаження конфігурації додатка @@ -53,7 +58,7 @@ $config = require(__DIR__ . '/../config/web.php'); ## Консольні додатки <span id="console-applications"></span> -Нижче наведений аналогічний код вхідного скрипта консольного додатка: +Нижче наведений аналогічний код вхідного скрипту консольного додатка: ```php #!/usr/bin/env php @@ -68,14 +73,10 @@ $config = require(__DIR__ . '/../config/web.php'); defined('YII_DEBUG') or define('YII_DEBUG', true); -// fcgi не має констант STDIN та STDOUT за замовчуванням -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - // реєстрація автозавантажувача класів Composer require(__DIR__ . '/vendor/autoload.php'); -// підключення файла класу Yii +// підключення файлу класу Yii require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php'); // завантаження конфігурації додатка @@ -87,21 +88,20 @@ exit($exitCode); ``` -## Оголошення констант <span id="defining-constants"></span> +## Визначення констант <span id="defining-constants"></span> Вхідні скрипти є найкращим місцем для оголошення глобальних констант. Yii підтримує наступні три константи: -* `YII_DEBUG`: вказує чи працює додаткок у режимі відлагодження ("debug mode"), перебуваючи у якому, - додаток буде збирати більше інформації у логи та покаже більш детальний стек викликів при отриманні виключення. - З цієї причини, режим відлагодження повинен бути використаний тільки в процесі розробки. - За замовчуванням значення `YII_DEBUG` дорівнює `false`. +* `YII_DEBUG`: вказує чи працює додаток у режимі налагодження ("debug mode"), перебуваючи у якому, додаток + буде зберігати більше інформації в журналі та покаже більш детальний стек викликів при отриманні виключення. З цієї причини, + режим налагодження повинен використовуватись здебільшого в процесі розробки. За замовчуванням значення `YII_DEBUG` дорівнює `false`. * `YII_ENV`: вказує в якому середовищі працює додаток. Дана тема детально розглянута у розділі [Конфігурації](concept-configurations.md#environment-constants). За замовчуванням значення `YII_ENV` дорівнює - `'prod'`, яке означає, що додаток працює у робочому ("production") режимі. + `'prod'`, яке означає, що додаток працює у робочому ("production") середовищі. * `YII_ENABLE_ERROR_HANDLER`: вказує чи потрібно увімкнути наявний у Yii обробник помилок. За замовчуванням значення даної константи дорівнює `true`. -При визначенні константи, ми зазвичай використовуєм наступний код: +При визначенні константи, розробники фреймворку зазвичай використовують код подібний до наступного: ```php defined('YII_DEBUG') or define('YII_DEBUG', true); @@ -117,5 +117,5 @@ if (!defined('YII_DEBUG')) { Перший варіант є більш коротким і зрозумілим. -Константи мають бути визначені якомога раніше, на самому початку вхідного скрипта, щоб вони могли вплинути на решту -PHP файлів, які будуть підключатись. +Константи мають бути визначені якомога раніше, на самому початку вхідного скрипту, щоб вони могли вплинути на решту +PHP-файлів, які будуть підключатись. diff --git a/docs/guide-uk/structure-models.md b/docs/guide-uk/structure-models.md new file mode 100644 index 0000000000..35d63fcf8a --- /dev/null +++ b/docs/guide-uk/structure-models.md @@ -0,0 +1,511 @@ +Моделі +====== + +Моделі є частиною архітектури [MVC](http://uk.wikipedia.org/wiki/Модель-вид-контролер). +Це об’єкти, які представляють бізнес-дані, бізнес-правила та бізнес-логіку. + +Ви можете створювати класи моделей шляхом розширення класу [[yii\base\Model]] або його нащадків. Базовий клас +[[yii\base\Model]] підтримує багато корисних можливостей: + +* [Атрибути](#attributes): представляють бізнес-дані та можуть бути доступними як звичайні властивості об’єкту + або як елементи масиву; +* [Мітки атрибутів](#attribute-labels): визначають мітки, які використовуються при відображенні атрибутів; +* [Масове призначення](#massive-assignment): підтримується заповнення декількох атрибутів одним кроком; +* [Правила перевірки](#validation-rules): забезпечують ввід даних на основі оголошених правил перевірки; +* [Експортування даних](#data-exporting): дозволяє даним моделі бути експортованими у масиви з налаштуванням форматів. + +Клас `Model` також є базовим класом для більш розширених моделей, таких як [Active Record](db-active-record.md). +Будь ласка, зверніться до відповідної документації для більш детальної інформації про ці розширені моделі. + +> Info: Необов’язково створювати класи моделей на базі класу [[yii\base\Model]]. Однак, оскільки багато компонентів Yii +побудовані так, щоб підтримувати [[yii\base\Model]], краще використовувати його як базовий клас для моделей. + + +## Атрибути <span id="attributes"></span> + +Моделі представляють бізнес-дані у вигляді *атрибутів*. Кожний атрибут є публічно доступною властивістю +моделі. Метод [[yii\base\Model::attributes()]] визначає, які атрибути має клас моделі. + +Ви можете отримати доступ до атрибута як до звичайної властивості об’єкту: + +```php +$model = new \app\models\ContactForm; + +// "name" - це атрибут моделі ContactForm +$model->name = 'example'; +echo $model->name; +``` + +Ви також можете отримувати доступ до атрибутів як до елементів масиву, завдяки підтримці +[ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) та [ArrayIterator](http://php.net/manual/en/class.arrayiterator.php) +у класі [[yii\base\Model]]: + +```php +$model = new \app\models\ContactForm; + +// доступ до атрибутів як до елементів масиву +$model['name'] = 'example'; +echo $model['name']; + +// перебір атрибутів +foreach ($model as $name => $value) { + echo "$name: $value\n"; +} +``` + + +### Визначення атрибутів <span id="defining-attributes"></span> + +За замовчуванням, якщо ваш клас моделі успадкований безпосередньо від [[yii\base\Model]], усі його *не статичні публічні* +змінні є атрибутами. Наприклад, клас моделі `ContactForm`, наведений нижче, має чотири атрибути: `name`, `email`, +`subject` і `body`. Модель `ContactForm` використовується для репрезентації вхідних даних, отриманих з HTML-форми. + +```php +namespace app\models; + +use yii\base\Model; + +class ContactForm extends Model +{ + public $name; + public $email; + public $subject; + public $body; +} +``` + + +Ви можете перевизначити метод [[yii\base\Model::attributes()]] для визначення атрибутів в інший спосіб. Метод повинен +повертати імена атрибутів у моделі. Наприклад, [[yii\db\ActiveRecord]] повертає +імена колонок відповідної таблиці бази даних як імена атрибутів. Також можливо знадобиться перевизначити +магічні методи, такі як `__get()` і `__set()`, щоб атрибути могли бути доступними як +звичайні властивості об’єкту. + + +### Мітки атрибутів <span id="attribute-labels"></span> + +При відображенні значень чи при отриманні вводу для атрибутів, часто необхідно відобразити деякі написи, пов’язані з +атрибутами. Наприклад, якщо атрибут має ім’я `firstName`, ви можете відобразити мітку `First Name`, яка є більш зручною +для користувача при відображенні кінцевим користувачам у таких місцях як поля форми чи повідомлення про помилки. + +Ви можете отримати мітку атрибуту викликом методу [[yii\base\Model::getAttributeLabel()|getAttributeLabel()]]. Наприклад, + +```php +$model = new \app\models\ContactForm; + +// відобразить "Name" +echo $model->getAttributeLabel('name'); +``` + +За замовчуванням мітки атрибутів генеруються автоматично з імен атрибутів. Генерування виконується +методом [[yii\base\Model::generateAttributeLabel()|generateAttributeLabel()]]. Він перетворить імена змінних зі стилю +"camel-case" у декілька слів з першою літерою у кожному слові у верхньому регістрі. Наприклад, `username` стане +`Username`, а `firstName` стане `First Name`. + +Якщо ви не хочете використовувати автоматично згенеровані мітки, ви можете перевизначити +[[yii\base\Model::attributeLabels()|generateAttributeLabel()]] для точного визначення міток атрибутів. Наприклад, + +```php +namespace app\models; + +use yii\base\Model; + +class ContactForm extends Model +{ + public $name; + public $email; + public $subject; + public $body; + + public function attributeLabels() + { + return [ + 'name' => 'Your name', + 'email' => 'Your email address', + 'subject' => 'Subject', + 'body' => 'Content', + ]; + } +} +``` + +Для додатків, що підтримують декілька мов, ви можливо захочете перекласти мітки атрибутів. Це можливо зробити +у методі [[yii\base\Model::attributeLabels()|attributeLabels()]] також, як показано нижче: + +```php +public function attributeLabels() +{ + return [ + 'name' => \Yii::t('app', 'Your name'), + 'email' => \Yii::t('app', 'Your email address'), + 'subject' => \Yii::t('app', 'Subject'), + 'body' => \Yii::t('app', 'Content'), + ]; +} +``` + +Ви можете навіть умовно визначати мітки атрибутів. Наприклад, на основі [сценарію](#scenarios), в якому модель +використовується, ви можете повертати різні мітки для одного й того ж атрибуту. + +> Info: Строго кажучи, мітки атрибутів є частиною [представлень](structure-views.md). Але оголошення міток +у моделях є часто дуже зручним та призводить до чистоти коду та можливості його повторного використання. + + +## Сценарії <span id="scenarios"></span> + +Модель може бути використана у різних *сценаріях*. Наприклад, модель `User` може бути використана для збору вводу при вході користувача, +але також вона може бути використана з метою реєстрації користувача. У різних сценаріях модель може використовувати різні +бізнес-правила та бізнес-логіку. Наприклад, атрибут `email` може бути обов’язковим у процесі реєстрації користувача, +але не потрібним у процесі входу користувача. + +Модель використовує властивість [[yii\base\Model::scenario]] для того, щоб відслідковувати сценарій, в якому вона використовується. +За замовчуванням модель підтримує тільки один сценарій іменований `default`. Наступний код показує два шляхи +призначення сценарію моделі: + +```php +// сценарій призначено як властивість +$model = new User; +$model->scenario = 'login'; + +// сценарій призначено через конфігурацію +$model = new User(['scenario' => 'login']); +``` + +За замовчуванням сценарії, які підтримуються моделлю, обумовлюються [правилами перевірки](#validation-rules) оголошеними +у моделі. Однак, ви можете змінити цю поведінку, перевизначивши метод [[yii\base\Model::scenarios()|scenarios()]] +як показано нижче: + +```php +namespace app\models; + +use yii\db\ActiveRecord; + +class User extends ActiveRecord +{ + public function scenarios() + { + return [ + 'login' => ['username', 'password'], + 'register' => ['username', 'email', 'password'], + ]; + } +} +``` + +> Info: У вищенаведеному та у наступних прикладах класи моделей успадковані від [[yii\db\ActiveRecord]], +тому що використання декількох сценаріїв зазвичай відбувається з класами [Active Record](db-active-record.md). + +Метод `scenarios()` повертає масив, ключами якого є імена сценаріїв, а значеннями - відповідні +*активні атрибути*. Активні атрибути можуть бути [масово призначеними](#massive-assignment) та підлягають +[перевірці](#validation-rules). У вищенаведеному прикладі, атрибути `username` і `password` є активними +у сценарії `login`; в той час як у сценарії `register`, також активним є атрибут `email` разом з `username` і `password`. + +Стандартна реалізація методу `scenarios()` повертає усі сценарії, знайдені у правилах перевірки, оголошених +методом [[yii\base\Model::rules()||rules()]]. При перевизначенні методу [[yii\base\Model::scenarios()||scenarios()]], +якщо ви хочете ввести нові сценарії на додачу до тих сценаріїв, необхідно написати код подібний до наступного: + +```php +namespace app\models; + +use yii\db\ActiveRecord; + +class User extends ActiveRecord +{ + public function scenarios() + { + $scenarios = parent::scenarios(); + $scenarios['login'] = ['username', 'password']; + $scenarios['register'] = ['username', 'email', 'password']; + return $scenarios; + } +} +``` + +Можливості сценаріїв в основному використовуються при [перевірці](#validation-rules) та [масовому призначенні](#massive-assignment) атрибутів. +Однак, ви можете використовувати їх для інших цілей. Наприклад, ви можете оголошувати [мітки атрибутів](#attribute-labels) +по-різному, в залежності від поточного сценарію. + + +## Правила перевірки <span id="validation-rules"></span> + +Дані для моделі, які отримуються від кінцевих користувачів, повинні пройти перевірку, щоб пересвідчитись, що вони задовольняють +певні правила (названі *правилами перевірки*, також відомі як *бізнес-правила*). Наприклад, дано модель `ContactForm`, +ви, можливо, хочете пересвідчитись, що всі атрибути заповнені та атрибут `email` містить коректну адресу електронної пошти. +Якщо значення для деяких атрибутів не задовольняють відповідні бізнес-правила, то будуть відображенні належні +повідомлення про помилки, щоб допомогти користувачу виправити їх. + +Ви можете викликати [[yii\base\Model::validate()|validate()]] для перевірки отриманих даних. Даний метод використає +правила перевірки, оголошені у [[yii\base\Model::rules()|rules()]], для перевірки кожного необхідного атрибуту. При відсутності +помилок він поверне true. В іншому випадку, він збереже помилки у властивості [[yii\base\Model::errors]] +та поверне false. Наприклад: + +```php +$model = new \app\models\ContactForm; + +// заповнення атрибутів моделі даними від користувача +$model->attributes = \Yii::$app->request->post('ContactForm'); + +if ($model->validate()) { + // усі дані є коректними +} else { + // невдала перевірка: $errors - масив, що містить повідомлення про помилки + $errors = $model->errors; +} +``` + + +Для оголошення правил перевірки, пов’язаних з моделлю, необхідно перевизначити метод [[yii\base\Model::rules()|rules()]], +повернувши правила, які атрибути моделі повинні задовольнити. Наступний приклад показує правила перевірки, оголошені +для моделі `ContactForm`: + +```php +public function rules() +{ + return [ + // атрибути name, email, subject і body є обов’язковими + [['name', 'email', 'subject', 'body'], 'required'], + + // атрибут email повинен бути коректною адресою електронної пошти + ['email', 'email'], + ]; +} +``` + +Правило може використовуватись для перевірки одного або кількох атрибутів, також атрибут може перевірятись одним або кількома правилами. +Будь ласка, зверніться до розділу [Перевірка вводу](input-validation.md) для більш детальної інформації про те, як оголошувати +правила перевірки. + +Іноді необхідно, щоб правила застосовувались лише у певних [сценаріях](#scenarios). Для цього ви можете +визначати властивість `on` для правила, як наведено нижче: + +```php +public function rules() +{ + return [ + // username, email і password є обов’язковими у сценарії "register" + [['username', 'email', 'password'], 'required', 'on' => 'register'], + + // username і password є обов’язковими у сценарії "login" + [['username', 'password'], 'required', 'on' => 'login'], + ]; +} +``` + +Якщо ви не визначите властивість `on`, то правило буде застосоване у всіх сценаріях. Правило називається +*активним правилом*, якщо воно може бути застосоване у поточному [[yii\base\Model::scenario|сценарії]]. + +Атрибут буде перевірятись тоді й лише тоді, якщо він є активним атрибутом, оголошеним у `scenarios()` і +пов’язаний з одним або кількома активними правилами, оголошеними у `rules()`. + + +## Масове призначення <span id="massive-assignment"></span> + +Масове призначення - це зручний спосіб заповнення моделі даними, введеними користувачем, використовуючи один рядок коду. +Атрибути моделі заповнюються шляхом призначення вхідних даних безпосередньо властивості [[yii\base\Model::$attributes]]. +Наступні два приклади коду є еквівалентними, обидва намагаються призначити дані з форми, представлені кінцевими користувачами, +атрибутам моделі `ContactForm`. Явно, що перший приклад, який використовує масове призначення, є набагато чистішим +й менш схильним до помилок, ніж другий: + +```php +$model = new \app\models\ContactForm; +$model->attributes = \Yii::$app->request->post('ContactForm'); +``` + +```php +$model = new \app\models\ContactForm; +$data = \Yii::$app->request->post('ContactForm', []); +$model->name = isset($data['name']) ? $data['name'] : null; +$model->email = isset($data['email']) ? $data['email'] : null; +$model->subject = isset($data['subject']) ? $data['subject'] : null; +$model->body = isset($data['body']) ? $data['body'] : null; +``` + + +### Безпечні атрибути <span id="safe-attributes"></span> + +Масові призначення застосовуються лише до, так званих *безпечних атрибутів*, які є атрибутами, що перелічені у +[[yii\base\Model::scenarios()]] для поточного [[yii\base\Model::scenario|сценарію]] моделі. +Наприклад, якщо модель `User` має нижченаведене оголошення сценарію, потім, коли поточним сценарієм +буде `login`, лише `username` і `password` можуть бути масово призначеними. Усі інші атрибути +будуть проігноровані. + +```php +public function scenarios() +{ + return [ + 'login' => ['username', 'password'], + 'register' => ['username', 'email', 'password'], + ]; +} +``` + +> Info: Причиною того, що масове призначення застосовується лише для безпечних атрибутів є необхідність + контролювати те, які атрибути можуть змінюватись даними від кінцевого користувача. Наприклад, якщо модель `User` + має атрибут `permission`, який визначає дозволи призначені користувачу, то необхідно бути впевненим, + що цей атрибут може змінюватись лише адміністраторами через back-end інтерфейс. + +Стандартна реалізація методу [[yii\base\Model::scenarios()|scenarios()]] повертає усі сценарії та атрибути, +знайдені у [[yii\base\Model::rules()|rules()]], якщо ви не перевизначили цей метод. Це означає, що атрибут є безпечним, +поки знаходиться в одному із активних правил перевірки. + +З цієї причини, надається спеціальний валідатор з псевдонімом `safe`, що дозволяє оголошувати атрибут +безпечним без фактичної його перевірки. Наприклад, наступне правило оголошує обидва атрибути `title` +і `description` безпечними: + +```php +public function rules() +{ + return [ + [['title', 'description'], 'safe'], + ]; +} +``` + + +### Небезпечні атрибути <span id="unsafe-attributes"></span> + +Як описано вище, метод [[yii\base\Model::scenarios()]] слугує двом цілям: вирішенню, які атрибути +повинні бути перевірені, і вирішенню, які атрибути є безпечними. У деяких рідких випадках, існує необхідність перевірити +атрибут, але не позначати його безпечним. Цього можна досягнути за допомогою префіксу знак оклику `!` у імені +атрибуту при його оголошені в `scenarios()`, як у атрибута `secret` в наведеному прикладі: + +```php +public function scenarios() +{ + return [ + 'login' => ['username', 'password', '!secret'], + ]; +} +``` + +Коли модель буде присутня у сценарії `login`, усі три атрибути будуть перевірені. Однак, лише атрибути +`username` і `password` можуть бути масово призначеними. Для призначення атрибуту `secret` вхідного значення +необхідно зробити це явно, як наведено нижче: + +```php +$model->secret = $secret; +``` + + +## Експортування даних <span id="data-exporting"></span> + +Часто існує потреба експортувати моделі у різні формати. Наприклад, може виникнути необхідність в перетворенні набору +моделей у формат JSON або Excel. Процес експортування може бути розділений на два незалежні кроки. +Першим кроком, моделі перетворюються у масиви; другим кроком, масиви перетворюються у +відповідні формати. Ви можете зосередитись лише на першому кроці, оскільки другий крок можна виконати за допомогою +загальних форматтерів даних, одним з яких є [[yii\web\JsonResponseFormatter]]. + +Найпростіший спосіб перетворення моделі у масив - це використання властивості [[yii\base\Model::$attributes]]. +Наприклад: + +```php +$post = \app\models\Post::findOne(100); +$array = $post->attributes; +``` + +За замовчуванням властивість [[yii\base\Model::$attributes]] поверне значення *усіх* атрибутів, +оголошених у [[yii\base\Model::attributes()]]. + +Більш гнучкий та потужний спосіб перетворення моделі в масив - використання методу [[yii\base\Model::toArray()]]. +Його типова поведінка така ж як у [[yii\base\Model::$attributes]]. Однак, він дозволяє обирати, +які елементи даних, що називаються *полями*, включати до масиву та як вони повинні бути форматовані. +Насправді, це стандартний спосіб експортування моделей при розробці веб-сервісів RESTful, як описано у +розділі [Форматування відповіді](rest-response-formatting.md). + + +### Поля <span id="fields"></span> + +Поле - це просто іменований елемент у масиві, отриманому через виклик методу [[yii\base\Model::toArray()]] +моделі. + +За замовчуванням імена полів є еквівалентними іменам атрибутів. Однак, можливо змінити цю поведінку за допомогою перевизначення +методів [[yii\base\Model::fields()|fields()]] і/або [[yii\base\Model::extraFields()|extraFields()]]. Обидва методи +повинні повертати перелік визначень полів. Поля, визначені у `fields()` є полями за замовчуванням, це означає, що +`toArray()` повертатиме ці поля за замовчуванням. Метод `extraFields()` визначає додатково доступні поля, +які також можуть бути повернутими через `toArray()`, якщо будуть зазначені у параметрі `$expand`. Наприклад, +наступний код буде повертати усі поля, визначені у `fields()`, а також поля `prettyName` і `fullAddress`, +якщо вони визначені у `extraFields()`: + +```php +$array = $model->toArray([], ['prettyName', 'fullAddress']); +``` + +Ви можете перевизначити `fields()`, щоб додати, видалити, перейменувати або перевизначити поля. Значенням, яке повертатиме `fields()`, +повинен бути масив. Ключами масиву є імена полів, а значеннями масиву - відповідні +визначення полів, які можуть бути або іменами властивостей/атрибутів, або анонімними функціями, що повертають +відповідні значення полів. В окремому випадку, коли ім’я поля збігається з ім’ям його атрибуту, +ви можете пропустити ключ масиву. Наприклад: + +```php +// точний перелік кожного поля найкраще використовувати тоді, коли ви хочете бути впевненим, що зміни +// у вашій таблиці БД або у атрибутах моделі не викликають змін ваших полів (для збереження зворотної сумісності API) +public function fields() +{ + return [ + // ім’я поля збігається з ім’ям атрибуту + 'id', + + // ім’я поля - "email", ім’я відповідного атрибуту - "email_address" + 'email' => 'email_address', + + // ім’я поля - "name", його значення визначене за допомогою анонімної PHP-функції + 'name' => function () { + return $this->first_name . ' ' . $this->last_name; + }, + ]; +} + +// відфільтровування деяких полів найкраще використовувати тоді, коли ви хочете +// успадкувати батьківську реалізацію та виключити деякі "чутливі" поля +public function fields() +{ + $fields = parent::fields(); + + // вилучити поля, які містять конфіденційну інформацію + unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); + + return $fields; +} +``` + +> Warning: Оскільки за замовчуванням усі атрибути моделі будуть включені до масиву, що експортується, ви повинні +перевірити ваші дані, щоб бути впевненим, що вони не містять конфіденційної інформації. Якщо ж така інформація присутня, +вам потрібно перевизначити метод `fields()` та відфільтрувати її. У вищенаведеному прикладі були +відфільтровані поля `auth_key`, `password_hash` і `password_reset_token`. + + +## Кращі практики <span id="best-practices"></span> + +Моделі є центральним місцем репрезентації бізнес-даних, бізнес-правил і бізнес-логіки. Вони часто потребують повторного +використання у різних місцях. В добре організованому додатку моделі зазвичай набагато більші, ніж +[контролери](structure-controllers.md). + +В цілому, моделі + +* можуть містити атрибути для репрезентації бізнес-даних; +* можуть містити правила перевірки для забезпечення коректності та цілісності даних; +* можуть містити методи, які реалізують бізнес-логіку; +* НЕ повинні безпосередньо отримувати доступ до запиту, сесії або будь-яких інших даних середовища. Ці дані повинні бути введені + у моделі за допомогою [контролерів](structure-controllers.md); +* повинні уникати вкладеного HTML- або іншого презентаційного коду - це краще робити у [представленнях](structure-views.md); +* мають уникати завеликої кількості [сценаріїв](#scenarios) в одній моделі. + +Як правило, ви можете враховувати останню рекомендацію, з наведених вище, тоді, коли розробляєте великі складні системи. +У цих системах моделі можуть бути дуже великими, оскільки вони використовуються у багатьох місцях й тому можуть містити +багато наборів правил і бізнес-логіки. Це часто перетворюється у кошмар під час супроводу коду моделі, +через те, що невелика зміна коду може зачіпати декілька різних місць. Щоб полегшити супровід коду моделі, +вам необхідно дотримуватися наступної стратегії: + +* Визначити набір базових класів моделей, які є спільними для різних [додатків](structure-applications.md) або + [модулів](structure-modules.md). Ці класи моделей повинні містити мінімальний набір правил та логіки, які + є загальними для усіх додатків та модулів, які їх використовують. +* У кожному [додатку](structure-applications.md) або [модулі](structure-modules.md), який використовує модель, + визначити конкретний клас моделі, що розширює відповідний базовий клас моделі. Конкретні класи моделей + повинні містити правила та логіку, які є специфічними для додатка чи модуля. + +Наприклад, у [розширеному шаблоні проекту](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-uk/README.md), ви можете визначати базовий клас +моделі `common\models\Post`. Потім для front-end додатку ви визначаєте та використовуєте конкретний клас моделі +`frontend\models\Post`, який розширює клас `common\models\Post`. Аналогічно для back-end додатку, +ви визначаєте `backend\models\Post`. Подібна стратегія дає вам впевненість в тому, що код у `frontend\models\Post` +є специфічним тільки для front-end додатку, та якщо ви внесете будь-які зміни до нього, то не має потреби турбуватись, що +зміни можуть порушити back-end додаток. diff --git a/docs/guide-uk/structure-overview.md b/docs/guide-uk/structure-overview.md index bcfd7deee6..b61630eacf 100644 --- a/docs/guide-uk/structure-overview.md +++ b/docs/guide-uk/structure-overview.md @@ -1,26 +1,25 @@ Огляд ===== -Додатки Yii організовані згідно шаблону проектування -[модель-представлення-подія (MVC)](http://uk.wikipedia.org/wiki/Модель-вид-контролер). -[Моделі](structure-models.md) являють собою дані, бізнес логіку та бізнес правила; +Додатки Yii організовані згідно архітектурного шаблону [Модель-Представлення-Контролер (MVC)](http://uk.wikipedia.org/wiki/Модель-вид-контролер). +[Моделі](structure-models.md) являють собою дані, бізнес-логіку та бізнес-правила; [представлення](structure-views.md) відповідають за відображення даних моделей; [контролери](structure-controllers.md) приймають вхідні дані від користувача і перетворюють їх у команди для [моделей](structure-models.md) та [представлень](structure-views.md). Окрім MVC, Yii додаток також має наступні сутності: -* [вхідні скрипти](structure-entry-scripts.md): це PHP скрипти, які доступні напряму кінцевому користувачу додатка. +* [вхідні скрипти](structure-entry-scripts.md): це PHP-скрипти, які доступні напряму кінцевому користувачу додатка. Вони відповідають за запуск циклу обробки запиту. * [додатки](structure-applications.md): це глобально доступні об’єкти, які відповідають за коректну роботу різних компонентів додатка і їх координацію для обробки запиту. * [компоненти додатку](structure-application-components.md): це об’єкти, зареєстровані в додатку і які надають різноманітні можливості для обробки запитів. -* [модулі](structure-modules.md): це самодостатні пакети, що включають в себе повністю всі ресурси для MVC. +* [модулі](structure-modules.md): це самодостатні пакунки, що включають в себе повністю всі ресурси для MVC. Додаток може бути організовано за допомогою декількох модулів. * [фільтри](structure-filters.md): це код, який повинен бути виконаний до і після обробки запиту контролерами. * [віджети](structure-widgets.md): це об’єкти, які можуть бути вбудованими у [представлення](structure-views.md). - Вони можуть містити різноманітну логіку і можуть бути повто використаними у різних представленнях. + Вони можуть містити логіку контролера і можуть бути повторно використаними у різних представленнях. На наступній діаграмі наведена структурна схема додатку: diff --git a/docs/guide-uk/structure-views.md b/docs/guide-uk/structure-views.md new file mode 100644 index 0000000000..272c5c98ba --- /dev/null +++ b/docs/guide-uk/structure-views.md @@ -0,0 +1,723 @@ +Представлення +============= + +Представлення є частиною архітектури [MVC](http://uk.wikipedia.org/wiki/Модель-вид-контролер). +Це код, який відповідає за відображення даних кінцевим користувачам. У веб-додатку представлення зазвичай створені +у вигляді *шаблонів представлень*, які є файлами скриптів PHP, що містять переважно HTML-код та презентаційний PHP-код. +Управління ними здійснюється за допомогою [компонента додатку](structure-application-components.md) [[yii\web\View|view]], який надає часто використовувані методи +для полегшення компонування та формування представлення. Для простоти, часто шаблони представлень або файли шаблонів представлень називаються +представленнями. + + +## Створення представлень <span id="creating-views"></span> + +Як зазначено вище, представлення є простим скриптом PHP, який містить HTML- та PHP-код. Далі наведено представлення, +що представляє форму входу користувача. Як можна побачити, PHP-код використовується для генерування динамічного вмісту, такого як +заголовку сторінки та форми, тоді як HTML-код організовує їх у презентабельну HTML-сторінку. + +```php +<?php +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $form yii\widgets\ActiveForm */ +/* @var $model app\models\LoginForm */ + +$this->title = 'Вхід'; +?> +<h1><?= Html::encode($this->title) ?></h1> + +<p>Будь ласка, заповніть наступні поля для входу:</p> + +<?php $form = ActiveForm::begin(); ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'password')->passwordInput() ?> + <?= Html::submitButton('Увійти') ?> +<?php ActiveForm::end(); ?> +``` + +Всередині представлення ви можете використовувати `$this`, що відноситься до [[yii\web\View|компоненту представлення]], який забезпечує керування +цим шаблоном та його формування. + +Окрім `$this`, у представленні можуть бути інші попередньо визначені змінні, такі як `$model` у прикладі +вище. Ці змінні представляють дані, які були *передані* у представлення через [контролери](structure-controllers.md) +або інші об’єкти, які спричинили [формування представлення](#rendering-views). + +> Tip: Попередньо визначені змінні перелічені в блоці коментаря у початку представлення, так що вони можуть + бути розпізнані в інтегрованих середовищах розробки (IDE). Це також є хорошим способом документування ваших представлень. + + +### Безпека <span id="security"></span> + +При створенні представлень, які генерують HTML-сторінки, важливо кодувати і/або фільтрувати дані, що надходять +від кінцевих користувачів, перед їх відображенням. В протилежному випадку ваш додаток може стати предметом +атак типу [міжсайтовий скриптінг (XSS)](https://uk.wikipedia.org/wiki/Міжсайтовий_скриптінг). + +Для відображення звичайного тексту спершу закодуйте його за допомогою [[yii\helpers\Html::encode()]]. Наприклад, в наступному коді +перед відображенням кодується ім’я користувача: + +```php +<?php +use yii\helpers\Html; +?> + +<div class="username"> + <?= Html::encode($user->name) ?> +</div> +``` + +Для відображення HTML-вмісту використовуйте [[yii\helpers\HtmlPurifier]], щоб відфільтрувати потенційно небезпечний вміст. Наприклад, в наступному +коді фільтрується вміст публікації перед відображенням: + +```php +<?php +use yii\helpers\HtmlPurifier; +?> + +<div class="post"> + <?= HtmlPurifier::process($post->text) ?> +</div> +``` + +> Tip: В той час як HTMLPurifier бездоганно виконує роботу, щоб зробити вивід безпечним, він не є швидким. Вам потрібно розглянути + можливість [кешування](caching-overview.md) результатів фільтрування, якщо ваш додаток потребує високої швидкодії. + + +### Впорядкування представлень <span id="organizing-views"></span> + +Подібно до [контролерів](structure-controllers.md) та [моделей](structure-models.md), існують домовленості щодо впорядкування представлень. + +* Представлення, які формуються контролером, повинні бути розміщені в директорії `@app/views/ControllerID` за замовчуванням, + де `ControllerID` відповідає [ідентифікатору контролера](structure-controllers.md#routes). Наприклад, для + класу контролера `PostController` директорія повинна бути `@app/views/post`; для класу `PostCommentController` + директорія повинна бути `@app/views/post-comment`. У випадку, коли контролер належить модулю, директорія + повинна бути `views/ControllerID` у [[yii\base\Module::basePath|директорії модуля]]. +* Представлення, які формуються у [віджетах](structure-widgets.md), повинні бути розміщені в директорії `WidgetPath/views` за + замовчуванням, де `WidgetPath` означає шлях до директорії, в якій знаходиться файл класу віджету. +* Для представлень, які формуються іншими об’єктами, рекомендується виконувати домовленості подібно до віджетів. + +Ви можете налаштувати ці типові директорії представлень, перевизначивши метод [[yii\base\ViewContextInterface::getViewPath()]] +контролерів або віджетів. + + +## Формування представлень <span id="rendering-views"></span> + +Ви можете формувати представлення в [контролерах](structure-controllers.md), [віджетах](structure-widgets.md) чи в будь-яких +інших місцях за допомогою виклику методів формування представлення. Усі ці методи мають подібну сигнатуру, що наведена нижче: + +``` +/** + * @param string $view ім’я представлення або шлях до файлу, в залежності від того, який метод формування використовується + * @param array $params дані, які передаються представленню + * @return string результат формування + */ +methodName($view, $params = []) +``` + + +### Формування в контролерах <span id="rendering-in-controllers"></span> + +Всередині [контролерів](structure-controllers.md), ви можете викликати наступні методи контролера для формування представлень: + +* [[yii\base\Controller::render()|render()]]: формує [іменоване представлення](#named-views) та застосовує [макет](#layouts) + до результату формування. +* [[yii\base\Controller::renderPartial()|renderPartial()]]: формує [іменоване представлення](#named-views) без будь-якого макету. +* [[yii\web\Controller::renderAjax()|renderAjax()]]: формує [іменоване представлення](#named-views) без будь-якого макету + та включає усі зареєстровані скрипти і файли JS/CSS. Це зазвичай використовується у відповіді на веб-запити AJAX. +* [[yii\base\Controller::renderFile()|renderFile()]]: формує представлення визначене шляхом файлу представлення або + [псевдонімом](concept-aliases.md). +* [[yii\base\Controller::renderContent()|renderContent()]]: формує статичний текстовий рядок, включаючи його в + поточний застосовний [макет](#layouts). Цей метод доступний починаючи з версії 2.0.1. + +Наприклад: + +```php +namespace app\controllers; + +use Yii; +use app\models\Post; +use yii\web\Controller; +use yii\web\NotFoundHttpException; + +class PostController extends Controller +{ + public function actionView($id) + { + $model = Post::findOne($id); + if ($model === null) { + throw new NotFoundHttpException; + } + + // формує представлення з ім’ям "view" та застосовує макет до нього + return $this->render('view', [ + 'model' => $model, + ]); + } +} +``` + + +### Формування у віджетах <span id="rendering-in-widgets"></span> + +Всередині [віджетів](structure-widgets.md), ви можете викликати наступні методи віджету для формування представлень. + +* [[yii\base\Widget::render()|render()]]: формує [іменоване представлення](#named-views). +* [[yii\base\Widget::renderFile()|renderFile()]]: формує представлення визначене шляхом файлу представлення або + [псевдонімом](concept-aliases.md). + +Наприклад: + +```php +namespace app\components; + +use yii\base\Widget; +use yii\helpers\Html; + +class ListWidget extends Widget +{ + public $items = []; + + public function run() + { + // формує представлення з ім’ям "list" + return $this->render('list', [ + 'items' => $this->items, + ]); + } +} +``` + + +### Формування у представленнях <span id="rendering-in-views"></span> + +Ви можете формувати представлення всередині іншого представлення за допомогою виклику одного з наступних методів наданих [[yii\base\View|компонентом представлення]]: + +* [[yii\base\View::render()|render()]]: формує [іменоване представлення](#named-views). +* [[yii\web\View::renderAjax()|renderAjax()]]: формує [іменоване представлення](#named-views) та включає усі зареєстровані + скрипти і файли JS/CSS. Це зазвичай використовується у відповіді на веб-запити AJAX. +* [[yii\base\View::renderFile()|renderFile()]]: формує представлення визначене шляхом файлу представлення або + [псевдонімом](concept-aliases.md). + +Наприклад, наступний код у представленні формує файл представлення `_overview.php`, який знаходиться в тій самій директорії, +що й представлення, яке зараз формується. Запам’ятайте, що `$this` у представленні відноситься до компонента [[yii\base\View|представлення]]: + +```php +<?= $this->render('_overview') ?> +``` + + +### Формування в інших місцях <span id="rendering-in-other-places"></span> + +В будь-якому місці ви можете отримати доступ до компонента додатка [[yii\base\View|представлення]] за допомогою виразу +`Yii::$app->view` та потім викликати його вищезгадані методи для формування представлення. Наприклад: + +```php +// відображає файл представлення "@app/views/site/license.php" +echo \Yii::$app->view->renderFile('@app/views/site/license.php'); +``` + + +### Іменовані представлення <span id="named-views"></span> + +При формуванні представлення ви можете визначити його, використовуючи ім’я представлення або шлях/псевдонім до файлу представлення. У більшості випадків +ви будете використовувати перший варіант, тому що він більш лаконічний та гнучкий. Представлення, визначені за допомогою імен називаються *іменованими представленнями*. + +Ім’я представлення перетворюється у відповідний шлях до файлу представлення за наступними правилами: + +* Ім’я представлення можна вказувати без розширення імені файлу. У цьому випадку в якості розширення буде використовуватись `.php`. Наприклад, + ім’я представлення `about` відповідає імені файлу `about.php`. +* Якщо ім’я представлення починається з двох слешів `//`, то відповідним шляхом до файлу представлення буде `@app/views/ViewName`. + Це означає, що представлення шукається в [[yii\base\Application::viewPath|директорії представлень додатку]]. + Наприклад, `//site/about` буде перетворено в `@app/views/site/about.php`. +* Якщо ім’я представлення починається з одного слешу `/`, то шлях до файлу представлення складатиметься з імені представлення та префіксу + у вигляді [[yii\base\Module::viewPath|директорії представлень]] поточного активного [модулю](structure-modules.md). + Якщо немає активного модулю, то буде використовуватись `@app/views/ViewName`. Наприклад, `/user/create` буде перетворено в + `@app/modules/user/views/user/create.php`, якщо поточним активним модулем є `user`. Якщо немає активного модулю, + то шляхом до файлу представлення буде `@app/views/user/create.php`. +* Якщо представлення формується з [[yii\base\View::context|контекстом]] і контекст має реалізований інтерфейс [[yii\base\ViewContextInterface]], + то шлях до файлу представлення складатиметься з префікса у вигляді [[yii\base\ViewContextInterface::getViewPath()|директорії представлень]] + контексту та з імені представлення. Це частіше застосовується до представлень, які формуються всередині контролерів або віджетів. Наприклад, + `about` буде перетворено в `@app/views/site/about.php`, якщо контекстом є контролер `SiteController`. +* Якщо представлення формується в середині іншого представлення, директорія, що містить інше представлення буде додана перед + іменем представлення, що формується, для створення актуального шляху до файлу. Наприклад, `item` перетвориться в `@app/views/post/item.php`, + якщо буде формуватись із представлення `@app/views/post/index.php`. + +Згідно з наведеними правилами, виклик `$this->render('view')` у контролері `app\controllers\PostController` буде +формувати представлення з файлу `@app/views/post/view.php`, в той час як виклик `$this->render('_overview')` у тому представленні +буде формувати представлення з файлу `@app/views/post/_overview.php`. + + +### Доступ до даних у представленнях <span id="accessing-data-in-views"></span> + +Є два підходи при доступові до даних в середині представлення: "вштовхування" та "витягування". + +Передаючи дані другим параметром у методи формування представлення, ви використовуєте підхід "вштовхування". +Дані повинні бути надані у вигляді масиву пар ключ-значення. Під час формування представлення буде викликана функція PHP +`extract()` на цьому масиві, видобувши таким чином змінні у представлення. +Наприклад, наступний код формування представлення у контролері "вштовхне" дві змінні до представлення `report`: +`$foo = 1` та `$bar = 2`. + +```php +echo $this->render('report', [ + 'foo' => 1, + 'bar' => 2, +]); +``` + +Підхід "витягування" активно здобуває дані з [[yii\base\View|компоненту представлення]] або інших об’єктів, доступних +у представленнях (наприклад, `Yii::$app`). Використовуючи нижче наведений код як приклад, всередині представлення можна отримати об’єкт +за допомогою виразу `$this->context`. У результаті, стає можливим доступ до усіх властивостей та методів +контролера у представленні `report`, як, наприклад, ідентифікатору контролера, що наведений у прикладі: + +```php +Ідентифікатор контролера: <?= $this->context->id ?> +``` + +Підхід "вштовхування" є бажанішим шляхом для доступу до даних у представленнях, тому що він робить представлення менш залежними +від об’єктів контексту, але його недоліком є необхідність весь час будувати масив даних вручну, що може +стати стомливим та збільшує вірогідність допустити помилку, якщо представлення використовується та формується у різних місцях. + + +### Обмін даними між представленнями <span id="sharing-data-among-views"></span> + +[[yii\base\View|Компонент представлення]] надає властивість [[yii\base\View::params|params]], яку можна використовувати +для обміну даними між представленнями. + +Наприклад, в представленні `about`, ви можете мати наступний код, який визначає поточну частину +"хлібних крихт". + +```php +$this->params['breadcrumbs'][] = 'Про нас'; +``` + +Потім у файлі [макету](#layouts), який також є представленням, ви можете відобразити "хлібні крихти", використовуючи дані +передані через [[yii\base\View::params|params]]: + +```php +<?= yii\widgets\Breadcrumbs::widget([ + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], +]) ?> +``` + + +## Макети <span id="layouts"></span> + +Макети - особливий тип представлень, які представляють спільні частини для інших представлень. Наприклад, сторінки +більшості веб-додатків мають однакові шапку та футер. Ви можете повторювати однакові шапку та футер сторінки +у кожному представленні, але краще зробити це один раз у макеті та розмістити результат формування вкладеного представлення +у відповідному місці макету. + + +### Створення макетів <span id="creating-layouts"></span> + +Оскільки макети є представленнями, вони можуть бути створенні тим самим шляхом, як і звичайні представлення. За замовчуванням, макети +зберігаються в директорії `@app/views/layouts`. Макети, які використовуються у [модулі](structure-modules.md), +повинні зберігатись в директорії `views/layouts` під [[yii\base\Module::basePath|директорією модуля]]. +Ви можете налаштувати типову директорію для макетів сконфігурувавши властивість [[yii\base\Module::layoutPath]] +додатку або модулів. + +Наступний приклад показує як виглядає макет. Майте на увазі, що для кращого сприйняття код у макеті дуже спрощений. +На практиці, ви можливо захочете додати більше вмісту до нього, такого як теги секції `<head>`, головне меню та інше. + +```php +<?php +use yii\helpers\Html; + +/* @var $this yii\web\View */ +/* @var $content string */ +?> +<?php $this->beginPage() ?> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"/> + <?= Html::csrfMetaTags() ?> + <title><?= Html::encode($this->title) ?> + head() ?> + + +beginBody() ?> +

Моя компанія
+ +
Моя компанія © 2014
+endBody() ?> + + +endPage() ?> +``` + +Як ви можете бачити, макет генерує HTML-теги, які є спільними для всіх сторінок. У секції `` +макет виводить змінну `$content`, яка містить результат формування вкладених представлень та "вштовхується" +у макет під час виклику методу [[yii\base\Controller::render()]]. + +В більшості макетів наступні методи будуть викликатись як показано у вищенаведеному коді. Ці методи здебільшого породжують події, +пов’язані з процесом формування, щоб скрипти та теги, які зареєстровані в інших місцях могли бути правильно включенні в +місцях, де ці методи викликаються. + +- [[yii\base\View::beginPage()|beginPage()]]: Цей метод повинен викликатись на самому початку макету. + Він породжує подію [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], яка слугує ознакою початку сторінки. +- [[yii\base\View::endPage()|endPage()]]: Цей метод повинен викликатись в кінці макету. + Він породжує подію [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]], яка слугує ознакою кінця сторінки. +- [[yii\web\View::head()|head()]]: Цей метод повинен викликатись всередині секції `` HTML-сторінки. + Він генерує заповнювач, який буде замінено зареєстрованим HTML-кодом (наприклад, теги link і meta), + коли сторінка буде повністю сформована. +- [[yii\web\View::beginBody()|beginBody()]]: Цей метод повинен викликатись на початку секції ``. + Він породжує подію [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] та генерує заповнювач, який + буде замінено зареєстрованим HTML-кодом (наприклад, JavaScript) на початку секції ``. +- [[yii\web\View::endBody()|endBody()]]: Цей метод повинен викликатись у кінці секції ``. + Він породжує подію [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]] та генерує заповнювач, який + буде замінено зареєстрованим HTML-кодом (наприклад, JavaScript) у кінці секції ``. + + +### Доступ до даних у макетах + +Всередині макету ви маєте доступ до двох попередньо визначених змінних: `$this` та `$content`. Перша посилається на +компонент [[yii\base\View|представлення]], як і у звичайних представленнях, в той час як друга містить результат формування вкладеного +представлення, який формується викликом методу [[yii\base\Controller::render()|render()]] у контролерах. + +Якщо ви бажаєте мати доступ до інших даних у макетах, то вам потрібно використовувати підхід "витягування", описаний в +підрозділі [Доступ до даних у представленнях](#accessing-data-in-views). Якщо ви бажаєте отримати дані з вкладеного представлення +у макеті, то можете використати підхід описаний у підрозділі [Обмін даними між представленнями](#sharing-data-among-views). + + +### Використання макетів + +Як описано в підрозділі [Формування в контролерах](#rendering-in-controllers), під час формування представлення +через виклик методу [[yii\base\Controller::render()|render()]] у контролері, буде застосовний макет +до результату формування. За замовчуванням, буде використовуватись макет `@app/views/layouts/main.php`. + +Ви можете використовувати інший макет сконфігурувавши властивість [[yii\base\Application::layout]] або [[yii\base\Controller::layout]]. +Перша відповідає за макет, який використовується усіма контролерами, друга ж перекриває першу для окремих контролерів. +Наприклад, наступний код налаштовує контролер `post` на використання макету `@app/views/layouts/post.php` +під час формування його представлень. Інші контролери, за умови, що їх властивість `layout` не встановлена, будуть надалі +використовувати типовий макет `@app/views/layouts/main.php`. + +```php +namespace app\controllers; + +use yii\web\Controller; + +class PostController extends Controller +{ + public $layout = 'post'; + + // ... +} +``` + +Для контролерів, що належать модулю, ви можете також сконфігурувати властивість модуля [[yii\base\Module::layout|layout]], щоб +використовувати окремий макет для цих контролерів. + +Оскільки властивість `layout` може бути сконфігурована на різних рівнях (контролери, моделі, додаток), +"за лаштунками" Yii виконує два кроки, щоб визначити, який файл макету є актуальним для окремого контролера. + +На першому кроці, визначається значення `layout` та контекстний модуль: + +- Якщо властивість контролера [[yii\base\Controller::layout]] відмінна від `null`, використовується вона і + [[yii\base\Controller::module|модуль]] контролера як контекстний модуль. +- Якщо [[yii\base\Controller::layout|layout]] не визначено (дорівнює `null`), здійснюється пошук у всіх батьківських модулях (включаючи сам додаток) контролера та + знаходиться перший модуль, властивість [[yii\base\Module::layout|layout]] якого не дорівнює `null`. Використовується значення цього модуля + [[yii\base\Module::layout|layout]] та сам модуль як контекстний модуль. + Якщо такого модуля не знайдено, це означає, що макет не буде застосовано. + +На другому кроці, визначається актуальний файл макету відповідно до значення `layout` та контекстного модулю, +визначених на першому кроці. Значенням `layout` може бути: + +- псевдонім шляху (наприклад, `@app/views/layouts/main`); +- абсолютний шлях (наприклад, `/main`): значення `layout` починається зі слешу. Актуальний файл макету буде + шукатись під [[yii\base\Application::layoutPath|директорією макетів]] додатка, яка типово дорівнює + `@app/views/layouts`; +- відносний шлях (наприклад, `main`): актуальний файл макету буде шукатись під + [[yii\base\Module::layoutPath|директорією макетів]] контекстного модуля, яка типово є директорією `views/layouts` під + [[yii\base\Module::basePath|директорією модуля]]; +- логічне значення `false`: макет не буде застосовано. + +Якщо значення `layout` не містить розширення файлу, то буде використане типове розширення `.php`. + + +### Вкладені макети + +Іноді потрібно вкласти один макет в інший. Наприклад, у різних розділах веб-сайту ви +захочете використовувати різні макети, які мають однаковий базовий макет, що генерує загальну +структуру HTML5-сторінки. Це можна зробити за допомогою викликів [[yii\base\View::beginContent()|beginContent()]] та +[[yii\base\View::endContent()|endContent()]] в дочірніх макетах як наведено нижче: + +```php +beginContent('@app/views/layouts/base.php'); ?> + +...вміст дочірнього макету... + +endContent(); ?> +``` + +Як показано вище, вміст дочірнього макету повинен бути замкнений між [[yii\base\View::beginContent()|beginContent()]] та +[[yii\base\View::endContent()|endContent()]]. Параметр, який передається в [[yii\base\View::beginContent()|beginContent()]] +визначає батьківський макет. Цей може бути файл макету чи псевдонім шляху. + +Використовуючи цей підхід, можна вкладати макети більше, ніж на один рівень. + + +### Використання блоків + +Блоки дозволяють визначати вміст представлення в одному місці, а відображати в іншому. Вони часто використовуються разом +з макетами. Наприклад, ви можете визначити блок у вкладеному представленні та відобразити його у макеті. + +Виклики [[yii\base\View::beginBlock()|beginBlock()]] та [[yii\base\View::endBlock()|endBlock()]] визначають блок. +Потім блок може бути доступним через `$view->blocks[$blockID]`, де `$blockID` означає унікальний ідентифікатор, який ви призначаєте +блоку під час його визначення. + +Нижченаведений приклад показує як ви можете використовувати блоки у вкладеному представленні для налаштовування окремих частин макету. + +Спершу, у вкладеному представленні, визначається один чи більше блоків: + +```php +... + +beginBlock('block1'); ?> + +...вміст блоку 1... + +endBlock(); ?> + +... + +beginBlock('block3'); ?> + +...вміст блоку 3... + +endBlock(); ?> +``` + +Потім, у макеті, формуються блоки, якщо вони присутні, або відображається деякий типовий вміст, якщо блок +не визначено. + +```php +... +blocks['block1'])): ?> + blocks['block1'] ?> + + ... типовий вміст для блоку 1 ... + + +... + +blocks['block2'])): ?> + blocks['block2'] ?> + + ... типовий вміст для блоку 2 ... + + +... + +blocks['block3'])): ?> + blocks['block3'] ?> + + ... типовий вміст для блоку 3 ... + +... +``` + + +## Використання компонентів представлення + +[[yii\base\View|Компоненти представлення]] надають багато можливостей, пов’язаних із представленням. Ви можете отримувати компоненти представлення +за допомогою створення індивідуальних екземплярів [[yii\base\View]] або його дочірніх класів, але у більшості випадків ви переважно будете використовувати +компонент `view` додатку. Ви можете сконфігурувати цей компонент у [конфігураціях додатку](structure-applications.md#application-configurations) +як наведено нижче: + +```php +[ + // ... + 'components' => [ + 'view' => [ + 'class' => 'app\components\View', + ], + // ... + ], +] +``` + +Компоненти представлення надають наступні корисні можливості, пов’язані з представленням, які описані більш детально в окремих розділах: + +* [темізація](output-theming.md): дозволяє проектувати та змінювати тему для веб-сайту; +* [кешування фрагментів](caching-fragment.md): дозволяє кешувати фрагменти веб-сторінки; +* [опрацювання клієнтських скриптів](output-client-scripts.md): підтримує реєстрацію та формування CSS та JavaScript; +* [опрацювання колекції ресурсів](structure-assets.md): підтримує реєстрацію та формування [колекцій ресурсів](structure-assets.md); +* [альтернативні шаблонізатори](tutorial-template-engines.md): дозволяє використовувати інші шаблонізатори, такі як + [Twig](http://twig.sensiolabs.org/), [Smarty](http://www.smarty.net/). + +Ви також можете часто використовувати наступні другорядні, але корисні, можливості в процесі розробки веб-сторінок. + + +### Встановлення заголовків сторінок + +Кожна веб-сторінка повинна мати заголовок. Звичайно тег заголовку виводиться в [макеті](#layouts). Однак, на практиці +заголовок часто визначається у вкладених представленнях, а не в макетах. Для вирішення цієї проблеми, компонент [[yii\web\View]] надає +властивість [[yii\web\View::title|title]], за допомогою якої можна передавати з вкладеного представлення до макетів інформацію заголовку. + +Для використання цієї можливості, в кожному вкладеному представленні ви можете задати заголовок як наведено нижче: + +```php +title = 'Мій заголовок сторінки'; +?> +``` + +Потім переконайтесь, що маєте наступний код в секції `` у макеті: + +```php +<?= Html::encode($this->title) ?> +``` + + +### Реєстрація мета-тегів + +Для веб-сторінок зазвичай потрібно генерувати різноманітні мета-теги, які мають різне цільове призначення. Подібно до заголовків сторінок, мета-теги +фігурують в секції `` та зазвичай генеруються в макетах. + +Якщо ви бажаєте визначити, які мета-теги генерувати у вкладених представленнях, ви можете викликати [[yii\web\View::registerMetaTag()]] +у вкладеному представленні, подібно до наведеного: + +```php +registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']); +?> +``` + +В коді вище реєструється мета-тег "keywords" у компоненті представлення. Зареєстрований мета-тег +формується після закінчення формування макету. Наступний HTML-код буде згенеровано та вставлено +в місці, де ви викличете [[yii\web\View::head()]] у макеті: + +```php + +``` + +Зауважте, якщо викликати [[yii\web\View::registerMetaTag()]] декілька разів, в результаті цього зареєструється кілька мета-тегів, +не зважаючи на те, чи мета-теги однакові чи ні. + +Щоб мати лише один екземпляр специфічного типу мета-тегу, потрібно визначати ключ в другому параметрі під час виклику методу. +Наприклад, наступний код реєструє два мета-теги "description". Однак, лише другий буде сформовано. + +```html +$this->registerMetaTag(['name' => 'description', 'content' => 'Це мій класний веб-сайт, який створено за допомогою Yii!'], 'description'); +$this->registerMetaTag(['name' => 'description', 'content' => 'Цей веб-сайт про смішних єнотів.'], 'description'); +``` + + +### Реєстрація тегів link + +Так само як і [мета-теги](#registering-meta-tags), теги link є корисними у багатьох випадках, як, наприклад, налаштування favicon, посилання на +стрічку новин (RSS) або делегування OpenID іншому серверу. Ви можете працювати з тегами link подібним шляхом як і з мета-тегами, +використовуючи [[yii\web\View::registerLinkTag()]]. Наприклад, у вкладеному представленні, ви можете зареєструвати тег link наступним чином, + +```php +$this->registerLinkTag([ + 'title' => 'Свіжі новини про Yii', + 'rel' => 'alternate', + 'type' => 'application/rss+xml', + 'href' => 'http://www.yiiframework.com/rss.xml/', +]); +``` + +Результатом вищенаведеного коду буде + +```html + +``` + +Подібно до [[yii\web\View::registerMetaTag()|registerMetaTags()]], ви можете визначати ключ під час виклику +[[yii\web\View::registerLinkTag()|registerLinkTag()]] для запобігання генерування повторних тегів link одного типу. + + +## Події у представленнях + +[[yii\base\View|Компоненти представлення]] породжують кілька подій в процесі формування представлення. Ви можете призначити обробники +на ці події для вставлення вмісту в представлення або для опрацювання сформованих результатів перед їх відправленням кінцевим користувачам. + +- Подія [[yii\base\View::EVENT_BEFORE_RENDER|EVENT_BEFORE_RENDER]]: породжується на початку формування файлу + в контролері. Обробники цієї події можуть встановлювати значення властивості [[yii\base\ViewEvent::isValid]] рівним `false` для скасування процесу формування. +- Подія [[yii\base\View::EVENT_AFTER_RENDER|EVENT_AFTER_RENDER]]: породжується після формування файлу викликом [[yii\base\View::afterRender()]]. + Обробники цієї події можуть отримати результат формування через властивість [[yii\base\ViewEvent::output]] та можуть модифікувати + її для зміни результату формування. +- Подія [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]]: породжується викликом [[yii\base\View::beginPage()]] у макетах. +- Подія [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]]: породжується викликом [[yii\base\View::endPage()]] у макетах. +- Подія [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]]: породжується викликом [[yii\web\View::beginBody()]] у макетах. +- Подія [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]]: породжується викликом [[yii\web\View::endBody()]] у макетах. + +Наприклад, наступний код вставить поточну дату в кінці секції `` сторінки: + +```php +\Yii::$app->view->on(View::EVENT_END_BODY, function () { + echo date('Y-m-d'); +}); +``` + + +## Формування статичних сторінок + +До статичних сторінок відносяться ті веб-сторінки, в яких основний вміст здебільшого статичний та не потребує доступу +до динамічних даних, що передаються з контролерів. + +Можна виводити статичні сторінки, розмістивши їх код у представленні, а потім використовуючи код подібний до наведеного у контролері: + +```php +public function actionAbout() +{ + return $this->render('about'); +} +``` + +Якщо веб-сайт містить багато статичних сторінок, було б дуже стомливим повторювати схожий код багато разів. +Для вирішення цієї проблеми можна впровадити [автономну дію](structure-controllers.md#standalone-actions) +[[yii\web\ViewAction]] у контролері. Наприклад, + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function actions() + { + return [ + 'page' => [ + 'class' => 'yii\web\ViewAction', + ], + ]; + } +} +``` + +Тепер, якщо створити представлення з ім’ям `about` у директорії `@app/views/site/pages`, стане можливим +відображення цього представлення за наступною URL-адресою: + +``` +http://localhost/index.php?r=site%2Fpage&view=about +``` + +Параметр `view` із запиту `GET` вказує автономній дії [[yii\web\ViewAction]], яке представлення запитане. Потім дія здійснює пошук +цього представлення в директорії `@app/views/site/pages`. Ви можете сконфігурувати [[yii\web\ViewAction::viewPrefix]], +щоб змінити директорію для пошуку цих представлень. + + +## Кращі практики + +Представлення відповідають за репрезентацію моделей у форматі зрозумілому кінцевим користувачам. В цілому, представлення + +* повинні здебільшого містити презентаційний код, такий як HTML та простий PHP-код для подання, форматування та формування даних; +* не повинні містити код, який виконує запити до БД. Такий код повинен бути в моделях; +* повинні уникати безпосереднього доступу до даних запиту, таких як `$_GET`, `$_POST`. Це обов’язок контролерів. + У випадку необхідності дані запиту повинні бути передані в представлення через контролери; +* можуть читати властивості моделі, але не повинні змінювати їх. + +Щоб зробити представлення більш контрольованими, уникайте створення представлень, які є дуже складними або містять забагато надлишкового коду. +Використовуйте наступні техніки, щоб досягнути цього: + +* використовуйте [макети](#layouts) для відображення спільних презентаційних секцій (наприклад, шапка та футер сторінки); +* розділяйте складне представлення на декілька менших. Менші представлення можуть бути сформовані та складені у більше + за допомогою методів формування описаних раніше у цьому розділі; +* створюйте та використовуйте [віджети](structure-widgets.md) як будівельні блоки представлень; +* створюйте та використовуйте класи-хелпери для перетворення та форматування даних у представленнях. + diff --git a/docs/guide-uk/tutorial-console.md b/docs/guide-uk/tutorial-console.md new file mode 100644 index 0000000000..a155c09744 --- /dev/null +++ b/docs/guide-uk/tutorial-console.md @@ -0,0 +1,243 @@ +Консольні додатки +================= + +Окрім багатьох можливостей для побудови веб-додатків, Yii також має повноцінну підтримку консольних додатків, +які в основному використовуються для реалізації фонових або супроводжувальних задач, які необхідно виконати для веб-сайту. + +Структура консольного додатка дуже подібна до структури веб-додатка Yii. Він складається з одного +або декількох класів [[yii\console\Controller]], які у середовищі консолі часто називають "командами". +Кожний контролер може також мати одну або декілька дій, так само як і веб-контролери. + +Обидва шаблони проекту вже мають консольний додаток у комплекті. +Ви можете запустити його, викликавши скрипт `yii`, який розміщений у базовій директорії додатка. +Це дасть вам перелік доступних команд, коли ви запускаєте його без будь-яких додаткових параметрів: + +![Виконання команди ./yii для відображення довідки](images/tutorial-console-help.png) + +Як видно на знімку екрану, Yii має вже визначений набір команд, які є доступними "з коробки": + +- [[yii\console\controllers\AssetController|AssetController]] - Дозволяє вам комбінувати та стискати ваші файли JavaScript і CSS. + Ви можете дізнатись більше про цю команду у розділі [Ресурси](structure-assets.md#using-the-asset-command). +- [[yii\console\controllers\CacheController|CacheController]] - Дозволяє вам оновити кеш додатка. +- [[yii\console\controllers\FixtureController|FixtureController]] - Керує завантаженням та вивантаженням даних фікстур для цілей тестування. + Ця команда описана більш детально у [розділі тестування про фікстури](test-fixtures.md#managing-fixtures). +- [[yii\console\controllers\HelpController|HelpController]] - Надає довідкову інформацію про консольні команди, ця команда використовується за замовчуванням + та виводить те, що ви побачили у вищенаведеному виводі. +- [[yii\console\controllers\MessageController|MessageController]] - Здобуває повідомлення для перекладу з файлів коду. + Щоб дізнатись більше про цю команду, будь ласка, зверніться до розділу [Інтернаціоналізація](tutorial-i18n.md#message-command). +- [[yii\console\controllers\MigrateController|MigrateController]] - Управляє міграціями додатка. + Міграції баз даних описані більш детально у розділі про [міграції баз даних](db-migrations.md). + + +Використання +------------ + +Для виконання дії консольного контролера використовуйте наступний синтаксис: + +``` +yii [--option1=value1 --option2=value2 ... argument1 argument2 ...] +``` + +У вищенаведеному прикладі, `` означає маршрут до дії контролера. Опції будуть заповнювати +властивості класу, а аргументи є параметрами для методу дії. + +Наприклад, дія [[yii\console\controllers\MigrateController::actionUp()|MigrateController::actionUp()]] +з властивістю [[yii\console\controllers\MigrateController::$migrationTable|MigrateController::$migrationTable]], що має значення `migrations`, +та обмеженням у 5 міграцій може бути викликана так: + +``` +yii migrate/up 5 --migrationTable=migrations +``` + +> Note: Коли використовуєте символ `*` в консолі, не забувайте замикати його в лапки, як `"*"`, для запобігання його інтерпретування як +> спецсимволу shell-оболонки, який заміщується іменами файлів поточної директорії. + + +Вхідний скрипт +-------------- + +Вхідний скрипт консольного додатку є еквівалентним до файлу початкового завантаження `index.php` для веб-додатків. +Консольний вхідний скрипт, як правило, називається `yii`, та розміщений у базовій директорії вашого додатку. +Він містить код подібний до наступного: + +```php +#!/usr/bin/env php +run(); +exit($exitCode); +``` + +Цей скрипт буде створено як частину вашого додатку; ви можете вільно змінювати його для відповідності вашим потребам. Значення константи `YII_DEBUG` може бути встановлено у `false`, +якщо ви не хочете бачити стек трасування при помилці, і/або якщо ви хочете підвищити загальну швидкодію. І в базовому, і в розширеному шаблонах +проекту у вхідному скрипті консольного додатку за замовчуванням включено режим налагодження для забезпечення більш дружнього до розробника середовища. + +Конфігурація +------------ + +Як видно у вищенаведеному коді, консольний додаток використовує свій власний файл конфігурації з іменем `console.php`. У цьому файлі +ви можете налаштовувати різні [компоненти додатка](structure-application-components.md) та властивості для консольного додатка зокрема. + +Якщо ваш веб-додаток та консольний додаток мають багато спільних параметрів конфігурації з однаковими значеннями, ви можете відокремити +спільні частини в окремий файл та включити цей файл до обох конфігурацій додатків (веб- та консольного). +Ви можете побачити приклад цього у "розширеному" шаблоні проекту. + +> Tip: Іноді, існує необхідність виконувати консольну команду, використовуючи конфігурацію додатка, яка +відрізняється від заданої у вхідному скрипті. Наприклад, якщо ви хочете використати команду `yii migrate` для +оновлення ваших тестових баз даних, які налаштовані кожна в окремому наборі тестів. Для динамічної зміни +конфігурації просто вкажіть потрібний файл конфігурації додатка +за допомогою опції `appconfig`, коли виконуєте команду: +```bash +yii --appconfig=path/to/config.php ... +``` + + +Створення власних консольних команд +----------------------------------- + +### Консольні контролер та дія + +Консольна команда визначена класом контролера успадкованого від [[yii\console\Controller]]. У класі контролера +визначаються одна або більше дій, які відповідають під-командам контролера. Всередині кожної дії міститься код, який реалізує відповідні завдання для окремої під-команди. + +При виконанні команди необхідно вказати маршрут до дії контролера. Наприклад, +маршрут `migrate/create` викликає під-команду, яка відповідає методу дії +[[yii\console\controllers\MigrateController::actionCreate()|MigrateController::actionCreate()]]. +Якщо маршрут, запропонований при виконанні, не містить ідентифікатора дії, то буде виконана стандартна дія (так само як у веб-контролері). + +### Опції + +Через перевизначення методу [[yii\console\Controller::options()]] ви можете визначити опції, які будуть доступними +для консольної команди (controller/actionID). Метод повинен повертати перелік публічних властивостей класу контролера. +При виконанні команди можна задати значення опції, використовуючи синтаксис `--OptionName=OptionValue`. +Це призначить значення `OptionValue` властивості `OptionName` класу контролера. + +Якщо значення за замовчуванням для опції є масивом і ви задаєте цю опцію під час виконання команди, +то значення опції буде перетворене у масив розділенням вхідного текстового рядка за комами. + +### Псевдоніми опцій + +Починаючи із версії 2.0.8, консольна команда надає метод [[yii\console\Controller::optionAliases()]] +для створення псевдонімів для опцій. + +Щоб визначити псевдонім, потрібно перевизначити метод [[yii\console\Controller::optionAliases()]] +у вашому контролері, наприклад: + +```php +namespace app\commands; + +use yii\console\Controller; + +class HelloController extends Controller +{ + public $message; + + public function options() + { + return ['message']; + } + + public function optionAliases() + { + return ['m' => 'message']; + } + + public function actionIndex() + { + echo $message . "\n"; + } +} +``` + +Тепер ви зможете використовувати наступний синтакс для запуску команди: + +``` +./yii hello -m=hola +``` + +### Аргументи + +Окрім опцій, команда також може приймати аргументи. Аргументи будуть передані як параметри до методу дії +відповідно до запитуваної під-команди. Перший аргумент відповідає першому параметру, другий +відповідає другому і т. д. Якщо при виклику команди надано не достатньо аргументів, то відповідним параметрам +будуть призначені типові значення, якщо попередньо визначені. Якщо типове значення не визначено і не передано значення під час виконання, то команда буде завершена з помилкою. + +Ви можете використовувати вказівку типу `array` для позначення аргументу, з яким потрібно обходитись як з масивом. +Масив буде згенерований розділенням вхідного текстового рядку за комами. + +Наступний приклад показує як оголошувати аргументи: + +```php +class ExampleController extends \yii\console\Controller +{ + // Команда "yii example/create test" викличе "actionCreate('test')" + public function actionCreate($name) { ... } + + // Команда "yii example/index city" викличе "actionIndex('city', 'name')" + // Команда "yii example/index city id" викличе "actionIndex('city', 'id')" + public function actionIndex($category, $order = 'name') { ... } + + // Команда "yii example/add test" викличе "actionAdd(['test'])" + // Команда "yii example/add test1,test2" викличе "actionAdd(['test1', 'test2'])" + public function actionAdd(array $name) { ... } +} +``` + + +### Код виходу + +Використання кодів виходу є найкращою практикою для розробки консольного додатку. Прийнято, якщо команда повертає `0`, це означає, що +все добре. Якщо команда повертає число більше за нуль, це вважається показником помилки. Повернуте число буде кодом +помилки, яке потенційно може використовуватись для пошуку деталей про помилку. +Наприклад, число `1`, як правило, може означати невідому помилку, а усі коди вище можуть бути зарезервовані для специфічних випадків: помилки вводу, відсутні файли і так далі. + +Для того, щоб ваша консольна команда повертала код виходу, просто поверніть ціле число з методу дії +контролера: + +```php +public function actionIndex() +{ + if (/* деяка проблема */) { + echo "A problem occurred!\n"; + return 1; + } + // щось виконується + return 0; +} +``` + +Є декілька попередньо визначених констант, які ви можете використовувати: + +- `Controller::EXIT_CODE_NORMAL` зі значенням `0`; +- `Controller::EXIT_CODE_ERROR` зі значенням `1`. + +Хорошою практикою є визначення значущих констант для вашого контролера у випадку, якщо ви маєте більше типів помилок. + +### Форматування та кольори + +Консоль Yii підтримує форматований вивід, який автоматично стане не форматованим, якщо він не підтримується +терміналом, в якому виконується команда. + +Виводити форматовані текстові рядки просто. Ось як вивести деякий жирний текст: + +```php +$this->stdout("Hello?\n", Console::BOLD); +``` + +Якщо необхідно побудувати текстовий рядок, динамічно комбінуючи декілька стилів, то найкраще використовувати `ansiFormat`: + +```php +$name = $this->ansiFormat('Alex', Console::FG_YELLOW); +echo "Hello, my name is $name."; +``` diff --git a/docs/guide-uk/tutorial-start-from-scratch.md b/docs/guide-uk/tutorial-start-from-scratch.md new file mode 100644 index 0000000000..58dd42664e --- /dev/null +++ b/docs/guide-uk/tutorial-start-from-scratch.md @@ -0,0 +1,62 @@ +Створення своєї власної структури додатку +========================================= + +> Note: Цей розділ знаходиться в розробці. + +У той час, як [базовий](https://github.com/yiisoft/yii2-app-basic) і [розширений](https://github.com/yiisoft/yii2-app-advanced) +шаблони додатків прекрасно підходять для більшості ваших потреб, ви також можете сворити свій власний шаблон додатку, з яким +працюватиме ваш проект. + +Шаблони додатку в Yii це простий репозиторій, що міститься у файлі `composer.json` і зареєстрований у якості пакету Composer. +Будь-який репозиторій може бути ідентифікований як Composer пакет, що робить можливим його встановлення +за допомогою команди Composer `create-project`. + +Оскільки це потребує дещо більше початкової роботи для створення власного шаблону з нуля, краще використовувати один із +вбудованих шаблонів в якості основи. Давайте використаємо тут базовий шаблон. + +Клонування базового шаблону +--------------------------- + +Першим кроком буде клонування базового шаблону Yii з Git репозиторія: + +```bash +git clone git@github.com:yiisoft/yii2-app-basic.git +``` + +Тоді, почекайте поки репозиторій завантажеться на ваш комп’ютер. Щоб зміни, які внесені в шаблон не були перезаписані, +вам необхідно видалити каталог `.git` і весь її зміст після завантаження. + +Заміна файлів +------------- + +Далі вам потрібно змінити файл `composer.json`, щоб показати ваш шаблон. +Змініть значення `name`, `description`, `keywords`, `homepage`, `license` і `support`, щоб описати ваш новий шаблон. +Також налаштуйте `require`, `require-dev`, `suggest` та інші параметри відповідно до вимог вашого шаблону. + +> Note: В файлі `composer.json` використовуйте параметр `writable` в розділі `extra`, щоб +> вказати права доступу до файлів, які необхідно встановити після створення додатку з використанням вашого шаблону. + +Далі внесіть зміни у структуру та зміст додатку на той, який ви би хотіли бачити за замовчуванням. +В кінці, оновіть файл інструкції README для застосування вашого шаблону. + +Створення пакету +---------------- + +З визначенням шаблону, створіть Git репозиторій та завантажте туди свої файли. Якщо ви, збираєтесь використовувати свій шаблон, +як *open source*, то [Github](http://github.com) є кращим місцем для його розташування. +Якщо ви не бажаєте публічно розміщувати свій шаблон, то підійде будь-який сайт сервісу Git. + +Далі, вам необхідно зареєструвати свій Composer пакет. Для публічних шаблонів, ваш пакет необхідно зареєструвати +в [Packagist](https://packagist.org/). Для приватних шаблонів, зареєструвати пакет трішки складніше. +Для цього слідуйте інструкціями у [Документації Composer](https://getcomposer.org/doc/05-repositories.md#hosting-your-own). + +Використання шаблону +-------------------- + +Це все, що потрібно для створення власного шаблону для Yii додатку. +Тепер ви можете створювати проекти, використовуючи свій шаблон: + +``` +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project +``` \ No newline at end of file diff --git a/docs/guide-uk/tutorial-template-engines.md b/docs/guide-uk/tutorial-template-engines.md new file mode 100644 index 0000000000..79810a476e --- /dev/null +++ b/docs/guide-uk/tutorial-template-engines.md @@ -0,0 +1,51 @@ +Використання шаблонізаторів +=========================== + +За замовчуванням, Yii використовує PHP в якості мови шаблонів, але ви можете налаштувати Yii для підтримки інших шаблонізаторів, +таких як [Twig](http://twig.sensiolabs.org/) або [Smarty](http://www.smarty.net/), які доступні в якості розширеннь. + +Компонент `View` відповідає за рендиренг видів. Ви можете додати користувальницький шаблон, шляхом зміни конфігурації поведінки +цього компонента: + +```php +[ + 'components' => [ + 'view' => [ + 'class' => 'yii\web\View', + 'renderers' => [ + 'tpl' => [ + 'class' => 'yii\smarty\ViewRenderer', + //'cachePath' => '@runtime/Smarty/cache', + ], + 'twig' => [ + 'class' => 'yii\twig\ViewRenderer', + 'cachePath' => '@runtime/Twig/cache', + // Array of twig options: + 'options' => [ + 'auto_reload' => true, + ], + 'globals' => ['html' => '\yii\helpers\Html'], + 'uses' => ['yii\bootstrap'], + ], + // ... + ], + ], + ], +] +``` + +У наведеному вище коді, як Smarty так і Twig налаштовані так, щоб використовуватись файлами представлень. +Але для того, щоб підключити ці розширення у ваш проект, вам також необхідно змінити файл `composer.json`, щоб включити їх: + +``` +"yiisoft/yii2-smarty": "*", +"yiisoft/yii2-twig": "*", +``` + +Цей код потрібно додати у розділ `require` файлу `composer.json`. Після внесення цих змін і збереження файлу, +ви можете встановити розширення, виконавши команду `composer update --prefer-dist` через командний рядок. + +Для отримання детальної інформації про використання конкретного шаблонізатора, будь ласка, зверніться до його документації: + +- [Документація Twig](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) +- [Документація Smarty](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) diff --git a/docs/guide-uk/tutorial-yii-integration.md b/docs/guide-uk/tutorial-yii-integration.md index 7ba86b0010..cb319ce5f8 100644 --- a/docs/guide-uk/tutorial-yii-integration.md +++ b/docs/guide-uk/tutorial-yii-integration.md @@ -12,6 +12,7 @@ Для використання сторонньої бібліотеки в Yii додатку, ви, в основному, повинні переконатися, що класи в бібліотеці правильно підключені або можуть завантажуватися автоматично. +<<<<<<< HEAD ### Використання пакетів Composer Багато сторонніх бібліотек випущені у вигляді пакетів [Composer](https://getcomposer.org/). @@ -21,6 +22,17 @@ 2. виконати команду `composer install` для встановлення зазначених пакетів. Класи встановлених пакетів Composer можуть бути автоматично завантажені, використовуючи автозавантажувача Composer. +======= +### Використання пакунків Composer + +Багато сторонніх бібліотек випущені у вигляді пакунків [Composer](https://getcomposer.org/). +Ви можете встановити такі бібліотеки, виконавши два прості кроки: + +1. змінити файл `composer.json` вашого додатку і вказати, які пакунки Composer необхідно встановити. +2. виконати команду `composer install` для встановлення зазначених пакунків. + +Класи встановлених пакунків Composer можуть бути автоматично завантажені, використовуючи автозавантажувача Composer. +>>>>>>> master Переконайтися, що [вхідний скрипт](structure-entry-scripts.md) вашого додатку містить наступні рядки для встановлення автозавантажувача Composer: @@ -34,7 +46,11 @@ require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); ### Використовуйте завантажені бібліотеки +<<<<<<< HEAD Якщо бібліотека не випущена в якості пакету Composer, ви повинні слідувати згідно її інструкції по встановленню. +======= +Якщо бібліотека не випущена в якості пакунку Composer, ви повинні слідувати згідно її інструкції по встановленню. +>>>>>>> master У більшості випадків, вам потрібно буде завантажити випущений файл вручну і розархівувати його в каталог `BasePath/vendor`, де `BasePath` представляє собою [основний шлях](structure-applications.md#basePath) вашого додатку. @@ -77,12 +93,17 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; ------------------------------------- Оскільки в Yii організовано безліч корисних функцій, іноді вони можуть знадобитися при розробці або розширенні сторонніх систем, +<<<<<<< HEAD таких як WordPress, Joomla, або додатки, розроблені з використанням іншого PHP фреймворку. +======= +таких як WordPress, Joomla, або додатки, розроблені з використанням іншого PHP-фреймворку. +>>>>>>> master Наприклад, ви можете використовувати клас [[yii\helpers\ArrayHelper]] або можливості [Active Record](db-active-record.md) в сторонніх системах. Для цього необхідно виконати два кроки: встановити Yii та bootstrap Yii. Якщо стороння система використовує управління залежностями Composer, ви можете встановити Yii за допомогою наступних команд: +<<<<<<< HEAD composer global require "fxp/composer-asset-plugin:~1.0.0" composer require yiisoft/yii2 composer install @@ -90,6 +111,33 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; Перша команда встановлює [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), який дозволяє керувати залежностями пакетів bower і npm через Composer. Навіть якщо ви захочете використовувати тільки прошарки бази даних або інші, не повʼязані ресурсами, можливості Yii, вам всерівно необхідно встановити даний пакет composer. +======= +```bash + composer global require "fxp/composer-asset-plugin:~1.1.1" + composer require yiisoft/yii2 + composer install +``` + +Перша команда встановлює [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), +який дозволяє керувати залежностями пакунків Bower і NPM через Composer. Навіть якщо ви захочете використовувати тільки +прошарки бази даних або інші, не повʼязані ресурсами, можливості Yii, вам все-одно необхідно встановити даний пакунок composer. + +Якщо ви бажаєте використовувати [можливість публікації ресурсів Yii](structure-assets.md), +вам також слід додати наступну конфігурацію до розділу `extra` вашого файлу `composer.json`: + +```json +{ + ... + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } +} +``` + +>>>>>>> master Дивіться також загальний [розділ про встановлення Yii](start-installation.md#installing-via-composer), для отримання додаткової інформації про Composer та проблеми, які можуть виникнути під час встановлення. @@ -108,14 +156,22 @@ new yii\web\Application($yiiConfig); // НЕ ВИКЛИКАЙТЕ run() в ць Як ви бачите, цей код дуже схожий на код [вхідного скрипта](structure-entry-scripts.md) типового додатку Yii. Єдина відмінність заключається в тому, що після створення екземпляру додатку, метод `run()` не викликається. Це звʼязано з тим, що при виклику `run()`, Yii захоплює контроль над процесом обробки запиту, що в даному випадку +<<<<<<< HEAD не є потрібним, так як цю задачу виконує вже існуючий додаток. +======= +не є потрібним, оскільки цю задачу виконує вже наявний додаток. +>>>>>>> master Як і у випадку з Yii додатком, вам необхідно налаштувати екземпляр додатку, виходячи із середовища запущеної сторонньої системи. Наприклад, щоб скористатися можливостями [Active Record](db-active-record.md), необхідно налаштувати [компонент додатку](structure-application-components.md) `db` з налаштування підключення бази данних, яка використовується сторонньою системою. +<<<<<<< HEAD Тепер ви можете використовувати більшість функцій Yii фреймворку. Наприклад, ви можете створювати класи Active Record і +======= +Тепер ви можете використовувати більшість функцій фреймворку Yii. Наприклад, ви можете створювати класи Active Record і +>>>>>>> master використовувати їх для роботи з базами даних. @@ -126,7 +182,11 @@ new yii\web\Application($yiiConfig); // НЕ ВИКЛИКАЙТЕ run() в ць Замість того, щоб переписувати цілий додаток під Yii 2, ви можете просто його покращити, використовуючи деякі функції, що доступні тільки в Yii 2. Для цього потрібно виконати наступні дії. +<<<<<<< HEAD > Примітка: Yii 2 вимагає версію PHP 5.4 або вищу. Переконайтися, що і сервер і існуючий додаток підтримують її. +======= +> Note: Yii 2 вимагає версію PHP 5.4 або вищу. Переконайтися, що і сервер і наявний додаток підтримують її. +>>>>>>> master По-перше, встановіть Yii 2 до вашого поточного додатку, виконавши дії, описані в [попередньому підрозділі](#using-yii-in-others). @@ -145,7 +205,11 @@ $yii1Config = require(__DIR__ . '/../config/yii1/main.php'); Yii::createWebApplication($yii1Config)->run(); ``` +<<<<<<< HEAD Так як Yii 1 і Yii 2 використовують клас `Yii`, вам необхідно створити модифіковану версію, щоб обʼєднати їх. +======= +Оскільки Yii 1 та Yii 2 використовують клас `Yii`, вам необхідно створити модифіковану версію, щоб обʼєднати їх. +>>>>>>> master Наведений нижче код підключить модифікований файл класу `Yii`, який може бути створений наступним чином. ```php diff --git a/docs/guide-uz/caching-page.md b/docs/guide-uz/caching-page.md new file mode 100644 index 0000000000..d00b056f3b --- /dev/null +++ b/docs/guide-uz/caching-page.md @@ -0,0 +1,36 @@ +Sahifalarni keshlash +================= + +Sahifalarni keshlash — bu sahifadagi butun ma'lumotni server tomonida keshda saqlashga aytiladi. Keginchalik sahifani serverdan +talab qilsak bizga sahifadagi ma'lumotni keshdan qaytaradi. + +Sahifalarni keshlash [harakat filtrlari](structure-filters.md) [[yii\filters\PageCache]] yordamida amalga oshiriladi va +kontroller sinfida quyidagi shaklda ishlatilishi mumkun: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\PageCache', + 'only' => ['index'], + 'duration' => 60, + 'variations' => [ + \Yii::$app->language, + ], + 'dependency' => [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT COUNT(*) FROM post', + ], + ], + ]; +} +``` + +Keltirilgan kodda kesh faqat indeks harakati uchun amalga oshiriladi. Nazarda tutulgan sahifa 60 sekundga +kesh qilinadi va dasturning joriy tiliga ko'ra o'zgarib turadi. Kesh qilingan sahifa muddati o'z o'zidan +o'zgaradi. Agarki shahrlarning umumiy soni o'zgarsa. + +Sahifalarni keshlash [fragmentlarni keshlashga](caching-fragment.md) juda o'hshaydi. Ikki holatda ham `duration` `dependencies` +`variations` va `enabled` parametrlari qo'llaniladi. Asosiy farqi shundaki sahifalarni keshlash [harakat filtri](structure-filters.md) shaklida +amalga oshiriladi. Fragmentlarni keshlash esa [vidjet shaklida](structure-widgets.md) amalga oshiriladi. diff --git a/docs/guide-uz/intro-upgrade-from-v1.md b/docs/guide-uz/intro-upgrade-from-v1.md index 5834dfe0cb..9eb58b7d64 100644 --- a/docs/guide-uz/intro-upgrade-from-v1.md +++ b/docs/guide-uz/intro-upgrade-from-v1.md @@ -83,7 +83,7 @@ class MyClass extends \yii\base\Object Yuqoridagi misolda oxirgi parametr obekt xususiyatlarini qiymatlovchi sozlashlar massivi ya'ni kalit-qiymat formatidagi juftlikdan iborat bo'lishi kerak. Siz sozlashlar qo'llanilganidan keyin initsializatsiya ishini amalga oshirish uchun oldindan [[yii\base\Object::init()|init()]] metod yaratib qo'yishingiz mumkin. -Ushbu kelishuvga asoslanib siz sozlash massivi yordamida yangi obektlarni yaratish va sozlashingiz mumkin: +Ushbu kelishuvga asoslanib siz sozlash massivi yordamida yangi obektlarni yaratishingiz va sozlashingiz mumkin: ```php $object = Yii::createObject([ diff --git a/docs/guide-uz/intro-yii.md b/docs/guide-uz/intro-yii.md index e7b603cc97..a0004d2d37 100644 --- a/docs/guide-uz/intro-yii.md +++ b/docs/guide-uz/intro-yii.md @@ -19,6 +19,7 @@ Yii ni boshqa freymvorklar bilan solishtirish - Yii juda yaxhsi kengayishi mumkin. Siz asosiy kodni ixtiyoriy qismini almashtirishingiz yoki sozlashingiz mumkin. Kengaytirish arxitekturasiga bo'ysunib kodni boshqalar bilan ulashish yoki jamoatning kodidan foydalanish mumkin. - Yii ning asosiy maqsadlaridan biri - ishlash tezligi. +<<<<<<< HEAD <<<<<<< HEAD Yii — bir odamning loyihasi emas. U unga yordam berayotgan ishlab chiquvchilar [katta jamoa][]si tomonidan qo'llab quvvatlanadi va rivojlantiriladi. Freymvork ishlab chiquvchilari web ishlab chiqish va boshqa ilovalarni maromini kuzatishadi. Ko'proq mos keluvchi imkoniyatlar va eng yaxshi sinalgan amaliyotlar freymvork sodda va elegantli interfeysi tarzida qo'llaniladi. @@ -28,6 +29,11 @@ Yii — bir odamning loyihasi emas. U unga yordam berayotgan ishlab chiquvchilar [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +Yii — bir odamning loyihasi emas. U unga yordam berayotgan ishlab chiquvchilar [katta jamoa][about_yii]si tomonidan qo'llab quvvatlanadi va rivojlantiriladi. Freymvork ishlab chiquvchilari web ishlab chiqish va boshqa ilovalarni maromini kuzatishadi. Ko'proq mos keluvchi imkoniyatlar va eng yaxshi sinalgan amaliyotlar freymvork sodda va elegantli interfeysi tarzida qo'llaniladi. + +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Yii talqinlari -------------- @@ -40,8 +46,12 @@ DT va bilimlarga talablar Yii 2.0 PHP 5.4.0 va undan yuqorisini talab qiladi. Boshqa imkoniyatlar uchun talablarni bilish uchun har bir alohida yo'lga qo'yilgan freymvork bilan birga mos o'rnatilgan talablar tekshiruv skriptini ishga tushirishingiz mumkin. +<<<<<<< HEAD <<<<<<< HEAD Freymvork to'liq obektga mo'ljallangan dasturlashga (OMD) asoslanganligi bois Yii da ishlash uchun OMD ni umumiy tushunish talab etiladi. Shuningdek, PHP ning zamonaviy imkoniyatlari bo'lmish [nomlar soxasi](http://www.php.net/manual/ru/language.namespaces.php) va [treytlar](http://www.php.net/manual/ru/language.oop5.traits.php) ni o'rganish talab etiladi. ======= Freymvork to'liq obektga mo'ljallangan dasturlashga (OMD) asoslanganligi bois Yii da ishlash uchun OMD ni umumiy tushunish talab etiladi. Shuningdek, PHP ning zamonaviy imkoniyatlari bo'lmish [nomlar soxasi](http://www.php.net/manual/ru/language.namespaces.php) va [treytlar](http://www.php.net/manual/ru/language.oop5.traits.php) ni o'rganish talab etiladi. >>>>>>> yiichina/master +======= +Freymvork to'liq obektga mo'ljallangan dasturlashga (OMD) asoslanganligi bois Yii da ishlash uchun OMD ni umumiy tushunish talab etiladi. Shuningdek, PHP ning zamonaviy imkoniyatlari bo'lmish [nomlar soxasi](http://www.php.net/manual/ru/language.namespaces.php) va [treytlar](http://www.php.net/manual/ru/language.oop5.traits.php) ni o'rganish talab etiladi. +>>>>>>> master diff --git a/docs/guide-uz/structure-controllers.md b/docs/guide-uz/structure-controllers.md new file mode 100644 index 0000000000..3a10ba3776 --- /dev/null +++ b/docs/guide-uz/structure-controllers.md @@ -0,0 +1,436 @@ +Kontrolyor +=========== + +Kontrolyor [MVC](https://ru.wikipedia.org/wiki/Model-View-Controller) arxitekturasining bir qismi hisoblanadi. Bu [[yii\base\Controller]] klassidan voris qilib yaratilgan klass obyektlaridir, ushbu obyektlar so'rovlarni (request) qayta ishlash va javoblarni (response) tayyorlash uchun mo'ljallangan. [Ilova (application)](structure-applications.md) so'rovlarni qayta ishlagandan so'ng, so'rovlarga mos kontrolyor obyektlarini yaratadi va bu obyektlar kiruvchi ma'lumotlarni tahlil qiladi hamda ushbu ma'lumotlarni [model](structure-models.md)ga jo'natadi so'ng modeldan olingan natijalarni [namoyish (view)](structure-views.md)ga joylashtiradi va oxirgi natijani hosil qiladi. + +## Amallar + +Kontrolyorlar *amallar (action)*dan tashkil topadi, *amallar* asosiy bloklar hisoblanib, foydalanuvchilar amallarga murojaat qilishi mumkin va ma'lum buyruqlar bajarilib natija hosil qilinadi hamda natija foydalanuvchiga qaytariladi. Bitta kontrolyorda bitta va undan ortiq amal bo'lishi mumkin. + +Quyidagi misolda ikkita amaldan (`view` va `create`) tashkil topgan `post` kontrolyori keltirilgan: + +```php +namespace app\controllers; + +use Yii; +use app\models\Post; +use yii\web\Controller; +use yii\web\NotFoundHttpException; + +class PostController extends Controller +{ + public function actionView($id) + { + $model = Post::findOne($id); + if ($model === null) { + throw new NotFoundHttpException; + } + + return $this->render('view', [ + 'model' => $model, + ]); + } + + public function actionCreate() + { + $model = new Post; + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } +} +``` + + +В действии `view` (определенном методом `actionView()`), код сначала загружает [модель](structure-models.md) +согласно запрошенному ID модели; Если модель успешно загружена, то код отобразит ее с помощью [представления](structure-views.md) +под названием `view`. В противном случае будет брошено исключение. + +В действии `create` (определенном методом `actionCreate()`), код аналогичен. Он сначала пытается загрузить [модель](structure-models.md) +с помощью данных из запроса и сохранить модель. Если все прошло успешно, то код перенаправляет браузер на действие `view` с ID только +что созданной модели. В противном случае он отобразит представление `create`, через которое пользователь может заполнить нужные данные. + + +## Маршруты + +Конечные пользователи обращаются к действиям через так называемые *маршруты*. Маршрут это строка, состоящая из следующих частей: + +* ID модуля: он существует, только если контроллер принадлежит не приложению, а [модулю](structure-modules.md); +* ID контроллера: строка, которая уникально идентифицирует контроллер среди всех других контроллеров одного и того же приложения + (или одного и того же модуля, если контроллер принадлежит модулю); +* ID действия: строка, которая уникально идентифицирует действие среди всех других действия одного и того же контроллера. + +Маршруты могут иметь следующий формат: + +``` +ControllerID/ActionID +``` + +или следующий формат, если контроллер принадлежит модулю: + +```php +ModuleID/ControllerID/ActionID +``` + +Таким образом, если пользователь запрашивает URL `http://hostname/index.php?r=site/index`, то `index` действие в `site` контроллере будет вызвано. +Секция [Маршрутизация](runtime-routing.md) содержит более подробную информацию о том как маршруты сопоставляются с действиями. + + +## Создание контроллеров + +В [[yii\web\Application|Веб приложениях]], контроллеры должны быть унаследованы от [[yii\web\Controller]] или его потомков. +Аналогично для [[yii\console\Application|консольных приложений]], контроллеры должны быть унаследованы от [[yii\console\Controller]] или +его потомков. Следующий код определяет `site` контроллер: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ +} +``` + + +### ID контроллеров + +Обычно контроллер сделан таким образом, что он должен обрабатывать запросы, связанные с определенным ресурсом. +Именно по этим причинам, ID контроллеров обычно являются существительные, ссылающиеся на ресурс, который они обрабатывают. +Например, вы можете использовать `article` в качестве ID контроллера, которые отвечает за обработку данных статей. + +По-умолчанию, ID контроллеров должны содержать только следующие символы: Английские буквы в нижнем регистре, цифры, подчеркивания, +тире и слэш. Например, оба `article` и `post-comment` являются допустимыми ID контроллеров, в то время как `article?`, `PostComment`, +`admin\post` не являются таковыми. + +ID контроллеров также могут содержать префикс подпапки. Например, в `admin/article` часть `article` является контроллером в +подпапке `admin` в [[yii\base\Application::controllerNamespace|пространстве имен контроллеров]]. +Допустимыми символами для префиксов подпапок являются: Английские буквы в нижнем и верхнем регистре, символы подчеркивания и +слэш, где слэш используется в качестве разграничителя для многовложенных подпапок (например `panels/admin`). + + + +### Правила наименования классов контроллеров + +Названия классов контроллеров могут быть получены из ID контроллеров следующими способами: + +* Привести в верхний регистр первый символ в каждом слове, разделенном дефисами. Обратите внимание что, если ID контроллера + содержит слэш, то данное правило распространяется только на часть после последнего слэша в ID контроллера; +* Убрать дефисы и заменить любой прямой слэш на обратный; +* Добавить суффикс `Controller`; +* Добавить в начало [[yii\base\Application::controllerNamespace|пространство имен контроллеров]]. + +Ниже приведены несколько примеров, с учетом того, что [[yii\base\Application::controllerNamespace|пространство имен контроллеров]] +имеет значение по умолчанию равное `app\controllers`: + +* `article` соответствует `app\controllers\ArticleController`; +* `post-comment` соответствует `app\controllers\PostCommentController`; +* `admin/post-comment` соответствует `app\controllers\admin\PostCommentController`; +* `adminPanels/post-comment` соответствует `app\controllers\adminPanels\PostCommentController`. + +Классы контроллеров должны быть [автозагружаемыми](concept-autoloading.md). Именно по этой причине, в вышеприведенном примере, +контроллер `article` должен быть сохранен в файл, [псевдоним](concept-aliases.md) которого `@app/controllers/ArticleController.php`; +в то время как контроллер `admin/post-comment` должен находиться в файле `@app/controllers/admin/PostCommentController.php`. + +> Информация: Последний пример `admin/post-comment` показывает каким образом вы можете расположить контроллер в подпапке + [[yii\base\Application::controllerNamespace|пространства имен контроллеров]]. Это очень удобно, когда вы хотите организовать свои контроллеры + в несколько категорий и не хотите использовать [модули](structure-modules.md). + + +### Карта контроллеров + +Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] для того, чтобы преодолеть +описанные выше ограничения именования ID контроллеров и названий классов. В основном это очень удобно, когда вы используете +сторонние контроллеры, именование которых вы не можете контролировать. + +Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] в [настройках приложения](structure-applications.md#application-configurations) +следующим образом: + +```php +[ + 'controllerMap' => [ + [ + // объявляет "account" контроллер, используя название класса + 'account' => 'app\controllers\UserController', + + // объявляет "article" контроллер, используя массив конфигурации + 'article' => [ + 'class' => 'app\controllers\PostController', + 'enableCsrfValidation' => false, + ], + ], + ], +] +``` + +### Контроллер по умолчанию + +Каждое приложение имеет контроллер по умолчанию, указанный через свойство [[yii\base\Application::defaultRoute]]. +Когда в запросе не указан [маршрут](#routes), тогда будет использован маршрут указанный в данном свойстве. +Для [[yii\web\Application|Веб приложений]], это значение `'site'`, в то время как для [[yii\console\Application|консольных приложений]], +это `'help'`. Таким образом, если задан URL `http://hostname/index.php`, это означает, что контроллер `site` выполнит обработку запроса. + +Вы можете изменить контроллер по умолчанию следующим образом в [настройках приложения](structure-applications.md#application-configurations): + +```php +[ + 'defaultRoute' => 'main', +] +``` + + +## Создание действий + +Создание действий не представляет сложностей также как и объявление так называемых *методов действий* в классе контроллера. Метод действия +это *public* метод, имя которого начинается со слова `action`. Возвращаемое значение метода действия представляет собой ответные данные, +которые будут высланы конечному пользователю. Приведенный ниже код определяет два действия `index` и `hello-world`: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function actionIndex() + { + return $this->render('index'); + } + + public function actionHelloWorld() + { + return 'Hello World'; + } +} +``` + + +### ID действий + +В основном действие разрабатывается для какой-либо конкретной обработки ресурса. По этой причине, ID действий в основном +являются глаголами, такими как `view`, `update`, и т. д. + +По-умолчанию, ID действия должен содержать только следующие символы: Английские буквы в нижнем регистре, цифры, +подчеркивания и дефисы. Дефисы в ID действий используются для разделения слов. Например, `view`, `update2`, `comment-post` являются +допустимыми ID действий, в то время как `view?`, `Update` не являются таковыми. + +Вы можете создавать действия двумя способами: встроенные действия и отдельные действия. Встроенное действие является методом, определенным +в классе контроллера, в то время как отдельное действие является экземпляром класса, унаследованного от [[yii\base\Action]] или его потомков. +Встроенные действия требуют меньше усилий для создания и в основном используются если у вас нет надобности в повторном использовании действий. +Отдельные действия, с другой стороны, в основном создаются для использования в различных контроллерах или при использовании в [расширениях](structure-extensions.md). + + +### Встроенные действия + +Встроенные действия это те действия, которые определены в рамках методов контроллера, как мы это уже обсудили. + +Названия методов действий могут быть получены из ID действий следующим образом: + +* Привести первый символ каждого слова в ID действия в верхний регистр; +* Убрать дефисы; +* Добавить префикс `action`. + +Например, `index` соответствует `actionIndex`, а `hello-world` соответствует `actionHelloWorld`. + +> Примечание: Названия имен действий являются *регистрозависимыми*. Если у вас есть метод `ActionIndex`, он не будет + учтен как метод действия, таким образом, запрос к действию `index` приведет к выбросу исключению. Также следует учесть, что методы действий + должны иметь область видимости public. Методы имеющие область видимости private или protected НЕ определяют методы встроенных действий. + + +Встроенные действия в основном используются, потому что для их создания не нужного много усилий. Тем не менее, если вы планируете повторно +использовать некоторые действия в различных местах, или если вы хотите перераспределить действия, вы должны определить его как *отдельной действие*. + + +### Отдельные действия + +Отдельные действия определяются в качестве классов, унаследованных от [[yii\base\Action]] или его потомков. +Например, в Yii релизах, присутствуют [[yii\web\ViewAction]] и [[yii\web\ErrorAction]], оба из которых являются +отдельными действиями. + +Для использования отдельного действия, вы должны указать его в *карте действий*, с помощью переопределения метода +[[yii\base\Controller::actions()]] в вашем классе контроллера, следующим образом: + +```php +public function actions() +{ + return [ + // объявляет "error" действие с помощью названия класса + 'error' => 'yii\web\ErrorAction', + + // объявляет "view" действие с помощью конфигурационного массива + 'view' => [ + 'class' => 'yii\web\ViewAction', + 'viewPrefix' => '', + ], + ]; +} +``` + +Как вы можете видеть, метод `actions()` должен вернуть массив, ключами которого являются ID действий, а значениями - соответствующие +названия класса действия или [конфигурация](concept-configurations.md). В отличие от встроенных действий, ID отдельных действий могут +содержать произвольные символы, до тех пор пока они определены в методе `actions()`. + +Для создания отдельного действия, вы должны наследоваться от класса [[yii\base\Action]] или его потомков, и реализовать +метод `run()` с областью видимости public. Роль метода `run()` аналогична другим методам действий. Например, + +```php + + +Возвращаемое значение методов действий или метода `run()` отдельного действия очень важно. Оно является результатом +выполнения соответствующего действия. + +Возвращаемое значение может быть объектом [response](runtime-responses.md), который будет отослан конечному пользователю +в качестве ответа. + +* Для [[yii\web\Application|Веб приложений]], возвращаемое значение также может быть произвольными данными, которые будут + присвоены [[yii\web\Response::data]], а затем сконвертированы в строку, представляющую тело ответа. +* Для [[yii\console\Application|Консольных приложений]], возвращаемое значение также может быть числом, представляющим + [[yii\console\Response::exitStatus|статус выхода]] исполнения команды. + +В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа, +высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый URL, с помощью +возврата response объекта (т. к. [[yii\web\Controller::redirect()|redirect()]] метод возвращает response объект): + +```php +public function actionForward() +{ + // перенаправляем браузер пользователя на http://example.com + return $this->redirect('http://example.com'); +} +``` + + +### Параметры действий + +Методы действий для встроенных действий и методы `run()` для отдельных действий могут принимать параметры, +называемые *параметры действий*. Их значения берутся из запросов. Для [[yii\web\Application|Веб приложений]], +значение каждого из параметров действия берется из `$_GET`, используя название параметра в качестве ключа; +для [[yii\console\Application|консольных приложений]], они соответствуют аргументам командной строки. + +В приведенном ниже примере, действие `view` (встроенное действие) определяет два параметра: `$id` и `$version`. + +```php +namespace app\controllers; + +use yii\web\Controller; + +class PostController extends Controller +{ + public function actionView($id, $version = null) + { + // ... + } +} +``` + +Для разных запросов параметры действий будут определены следующим образом: + +* `http://hostname/index.php?r=post/view&id=123`: параметр `$id` будет присвоено значение `'123'`, в то время + как `$version` будет иметь значение null, т. к. строка запроса не содержит параметра `version`; +* `http://hostname/index.php?r=post/view&id=123&version=2`: параметрам `$id` и `$version` будут присвоены + значения `'123'` и `'2'` соответственно; +* `http://hostname/index.php?r=post/view`: будет брошено исключение [[yii\web\BadRequestHttpException]], т. к. + обязательный параметр `$id` не был указан в запросе; +* `http://hostname/index.php?r=post/view&id[]=123`: будет брошено исключение [[yii\web\BadRequestHttpException]], т. к. + параметр `$id` получил неверное значение `['123']`. + +Если вы хотите, чтобы параметр действия принимал массив значений, вы должны использовать type-hint значение `array`, как показано ниже: + +```php +public function actionView(array $id, $version = null) +{ + // ... +} +``` + +Теперь, если запрос будет содержать URL `http://hostname/index.php?r=post/view&id[]=123`, то параметр `$id` примет значение +`['123']`. Если запрос будет содержать URL `http://hostname/index.php?r=post/view&id=123`, то параметр `$id` все равно будет +содержать массив, т. к. скалярное значение `'123'` будет автоматически сконвертировано в массив. + +Вышеприведенные примеры в основном показывают как параметры действий работают для Веб приложений. Больше информации +о параметрах консольных приложений представлено в секции [Консольные команды](tutorial-console.md). + + +### Действие по умолчанию + +Каждый контроллер имеет действие, указанное через свойство [[yii\base\Controller::defaultAction]]. +Когда [маршрут](#routes) содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию +было запрошено. + +По-умолчанию, это действие имеет значение `index`. Если вы хотите изменить это значение, просто переопределите данное +свойство в классе контроллера следующим образом: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $defaultAction = 'home'; + + public function actionHome() + { + return $this->render('home'); + } +} +``` + + +## Жизненный цикл контроллера + +При обработке запроса, [приложение](structure-applications.md) создаст контроллер, основываясь на +запрошенном [маршруте](#routes). Для выполнения запроса, контроллер пройдет через следующие этапы +жизненного цикла: + +1. Метод [[yii\base\Controller::init()]] будет вызван после того как контроллер будет создан и сконфигурирован; +2. Контроллер создает объект действия, основываясь на запрошенном ID действия: + * Если ID действия не указан, то будет использовано [[yii\base\Controller::defaultAction|ID действия по умолчанию]]; + * Если ID действия найдено в [[yii\base\Controller::actions()|карте действий]], то отдельное действие будет создано; + * Если ID действия соответствует методу действия, то встроенное действие будет создано; + * В противном случае, будет выброшено исключение [[yii\base\InvalidRouteException]]. +3. Контроллер последовательно вызывает метод `beforeAction()` приложения, модуля (если контроллер принадлежит модулю) и + самого контроллера. + * Если один из методов вернул `false`, то остальные, не вызванные методы `beforeAction` будут пропущены, а выполнение + действия будет отменено; + * По-умолчанию, каждый вызов метода `beforeAction()` вызовет событие `beforeAction`, на которое вы можете назначить обработчики. +4. Контроллер запускает действие: + * Параметры действия будут проанализированы и заполнены из данных запроса. +5. Контроллер последовательно вызывает методы `afterAction` контроллера, модуля (если контроллер принадлежит модулю) и приложения. + * По-умолчанию, каждый вызов метода `afterAction()` вызовет событие `afterAction`, на которое вы можете назначить обработчики. +6. Приложение, получив результат выполнения действия, присвоит его объекту [response](runtime-responses.md). + + +## Лучшие практики + +В хорошо организованных приложениях контроллеры обычно очень тонкие и содержат лишь несколько строк кода. +Если ваш контроллер слишком сложный, то обычно это означает, что вам надо провести его рефакторинг и перенести часть кода +в другие места. + +В целом, контроллеры + +* могут иметь доступ к данным [запроса](runtime-requests.md); +* могут вызывать методы [моделей](structure-models.md) и других компонентов системы с данными запроса; +* могут использовать [представления](structure-views.md) для формирования ответа; +* не должны заниматься обработкой данных, это должно происходить в [слое моделей](structure-models.md); +* должны избегать использования HTML или другой разметки, лучше это делать в [представлениях](structure-views.md). diff --git a/docs/guide-vi/README.md b/docs/guide-vi/README.md new file mode 100644 index 0000000000..9ebf4c8245 --- /dev/null +++ b/docs/guide-vi/README.md @@ -0,0 +1,199 @@ +The Definitive Guide to Yii 2.0 +=============================== + +Các hướng dẫn được phát hành theo [Các điều khoản về tài liệu Yii](http://www.yiiframework.com/doc/terms/). + +All Rights Reserved. + +2014 (c) Yii Software LLC. + + +Giới thiệu +------------ + +* [Về Yii](intro-yii.md) +* [Nâng cấp lên từ phiên bản 1.1](intro-upgrade-from-v1.md) + + +Bắt đầu +--------------- + +* [Cài đặt Yii](start-installation.md) +* [Thực hiện chạy ứng dụng](start-workflow.md) +* [Viết lời chào đầu tiên](start-hello.md) +* [Làm việc với Forms](start-forms.md) +* [Làm việc với Databases](start-databases.md) +* [Sử dụng Gii để sinh code](start-gii.md) +* [Mức cao hơn](start-looking-ahead.md) + + +Kiến trúc ứng dụng (Application Structure) +--------------------- + +* [Tổng quan về kiến trúc ứng dụng](structure-overview.md) +* [Mục Scripts](structure-entry-scripts.md) +* [Ứng dụng (Applications)](structure-applications.md) +* [Thành phần ứng dụng](structure-application-components.md) +* [Bộ điều khiển (Controllers)](structure-controllers.md) +* [Models](structure-models.md) +* [Views](structure-views.md) +* [Modules](structure-modules.md) +* [Bộ lọc (Filters)](structure-filters.md) +* [Widgets](structure-widgets.md) +* [Assets](structure-assets.md) +* [Phần mở rộng (Extensions)](structure-extensions.md) + + +Yêu cầu xử lý (Handling Requests) +----------------- + +* [Tổng quan](runtime-overview.md) +* [Bootstrapping](runtime-bootstrapping.md) +* [Routing và URL Creation](runtime-routing.md) +* [Yêu cầu (Requests)](runtime-requests.md) +* [Responses](runtime-responses.md) +* [Sessions và Cookies](runtime-sessions-cookies.md) +* [Xử lý lỗi (Handling Error)](runtime-handling-errors.md) +* [Logging](runtime-logging.md) + + +Các khái niệm chính +------------ + +* [Thành phần (Components)](concept-components.md) +* [Thuộc tính (Properties)](concept-properties.md) +* [Sự kiện (Events)](concept-events.md) +* [Hành vi (Behaviors)](concept-behaviors.md) +* [Cấu hình (Configurations)](concept-configurations.md) +* [Bí danh (Aliases)](concept-aliases.md) +* [Lớp tự động nạp (Autoloading)](concept-autoloading.md) +* [Service Locator](concept-service-locator.md) +* [Dependency Injection Container](concept-di-container.md) + + +Làm việc với Databases +---------------------- + +* [Data Access Objects](db-dao.md): Kết nối cơ sở dữ liệu, truy vấn cơ bản, giao dịch và phương thức hoạt động +* [Query Builder](db-query-builder.md): Sử dụng một truy vấn đơn giản, các lớp cơ sở dữ liệu trừu tượng +* [Active Record](db-active-record.md): The Active Record ORM, truy vấn và thao tác với dữ liệu, định nghĩa các mối quan hệ giữa các bảng +* [Migrations](db-migrations.md): Cung cấp cho đội dự án dễ dàng trong việc quản lý những schema CSDL trong ứng dụng +* **TBD** [Sphinx](db-sphinx.md) +* **TBD** [Redis](db-redis.md) +* **TBD** [MongoDB](db-mongodb.md) +* **TBD** [ElasticSearch](db-elasticsearch.md) + + +Nhận dữ liệu từ user +----------------------- + +* [Tạo mới Forms](input-forms.md) +* [Kiểm tra dữ liệu đầu vào (Validating Input)](input-validation.md) +* [File Upload](input-file-upload.md) +* [Thu thập dữ liệu từ danh sách đầu vào (Đang phát triển)](input-tabular-input.md) +* [Lấy dữ liệu cho nhiều Models (Chưa giải quyết)](input-multiple-models.md) + + +Hiển thị dữ liệu +--------------- + +* [Định dạng dữ liệu (Data Formatting)](output-formatter.md) +* [Phân trang (Pagination)](output-pagination.md) +* [Sắp xếp (Sorting)](output-sorting.md) +* [Cung cấp dữ liệu ra (Data Providers)](output-data-providers.md) +* [Dữ liệu Widgets](output-data-widgets.md) +* [làm việc với Client Scripts](output-client-scripts.md) +* [Giao diện (Theming)](output-theming.md) + + +Bảo mật (Security) +-------- + +* [Xác thực (Authentication)](security-authentication.md) +* [Quyền (Authorization)](security-authorization.md) +* [Các thao tác xử lý với Passwords (Đang phát triển)](security-passwords.md) +* [Auth Clients](security-auth-clients.md) +* [Best Practices](security-best-practices.md) + + +Bộ nhớ Cache +------- + +* [Tổng quan](caching-overview.md) +* [Cache dữ liệu](caching-data.md) +* [Fragment Caching](caching-fragment.md) +* [Page Caching](caching-page.md) +* [HTTP Caching](caching-http.md) + + +RESTful Web Services +-------------------- + +* [Bắt đầu](rest-quick-start.md) +* [Tài nguyên (Resources)](rest-resources.md) +* [Bộ điều khiển (Controllers)](rest-controllers.md) +* [Routing](rest-routing.md) +* [Định dạng thông điệp gửi đi (Response Formatting)](rest-response-formatting.md) +* [Xác thực (Authentication)](rest-authentication.md) +* [Rate Limiting](rest-rate-limiting.md) +* [Phiên bản (Version)](rest-versioning.md) +* [Error Handling](rest-error-handling.md) + + +Công cụ phát triển (Development Tools) +----------------- + +* [Thanh công cụ gỡ lỗi và sửa lỗi (Debug Toolbar và Debugger)](tool-debugger.md) +* [Sử dụng Gii để tạo code](tool-gii.md) +* **TBD** [Tạo tài liệu về API ](tool-api-doc.md) + + +Testing +------- + +* [Tổng quan](test-overview.md) +* [Thiết lập môi trường](test-environment-setup.md) +* [Unit Tests](test-unit.md) +* [Kiểm tra chức năng (Functional Tests)](test-functional.md) +* [Acceptance Tests](test-acceptance.md) +* [Fixtures](test-fixtures.md) + + +Chủ đề năng cao +-------------- + +* [Advanced Application Template](tutorial-advanced-app.md) +* [Building Application from Scratch](tutorial-start-from-scratch.md) +* [Giao diện dòng lệnh (Console Commands)](tutorial-console.md) +* [Core Validators](tutorial-core-validators.md) +* [Quốc tế hóa (Internationalization)](tutorial-i18n.md) +* [Thư (Mailing)](tutorial-mailing.md) +* [Tối ưu hiệu năng ứng dụng (Performance Tuning)](tutorial-performance-tuning.md) +* [Shared Hosting Environment](tutorial-shared-hosting.md) +* [Template Engines](tutorial-template-engines.md) +* [Working with Third-Party Code](tutorial-yii-integration.md) + + +Widgets +------- + +* GridView: **TBD** liên kết tới trang demo +* ListView: **TBD** liên kết tới trang demo +* DetailView: **TBD** liên kết tới trang demo +* ActiveForm: **TBD** liên kết tới trang demo +* Pjax: **TBD** liên kết tới trang demo +* Menu: **TBD** liên kết tới trang demo +* LinkPager: **TBD** liên kết tới trang demo +* LinkSorter: **TBD** liên kết tới trang demo +* [Bootstrap Widgets](widget-bootstrap.md) +* [jQuery UI Widgets](widget-jui.md) + + +Helpers +------- + +* [Tổng quan](helper-overview.md) +* [ArrayHelper](helper-array.md) +* [Html](helper-html.md) +* [Url](helper-url.md) + diff --git a/docs/guide/images/advanced-app-configs.graphml b/docs/guide-vi/images/advanced-app-configs.graphml similarity index 100% rename from docs/guide/images/advanced-app-configs.graphml rename to docs/guide-vi/images/advanced-app-configs.graphml diff --git a/docs/guide-ru/images/advanced-app-configs.png b/docs/guide-vi/images/advanced-app-configs.png similarity index 100% rename from docs/guide-ru/images/advanced-app-configs.png rename to docs/guide-vi/images/advanced-app-configs.png diff --git a/docs/guide-vi/images/application-lifecycle.graphml b/docs/guide-vi/images/application-lifecycle.graphml new file mode 100644 index 0000000000..850863ab26 --- /dev/null +++ b/docs/guide-vi/images/application-lifecycle.graphml @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Entry script (index.php or yii) + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Load application config + + + + + + + + + + + + + + + + + + + + + Create application instance + + + + + + + + + + Folder 5 + + + + + + + + + + + + + + + + preInit() + + + + + + + + + + + + + + + + + Register error handler + + + + + + + + + + + + + + + + + Configure application properties + + + + + + + + + + + + + + + + + init() + + + + + + + + + + + + + + + + + bootstrap() + + + + + + + + + + + + + + + + + + + + + + + Run application + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + EVENT_BEFORE_REQUEST + + + + + + + + + + + + + + + + + + + + + Handle request + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Resolve request into route and parameters + + + + + + + + + + + + + + + + + Create module, controller and action + + + + + + + + + + + + + + + + + Run action + + + + + + + + + + + + + + + + + + + EVENT_AFTER_REQUEST + + + + + + + + + + + + + + + + + Send response to end user + + + + + + + + + + + + + + + + + + + Complete request processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration array + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exit status + + + + + + + + + + + + + + + + diff --git a/docs/guide-vi/images/application-lifecycle.png b/docs/guide-vi/images/application-lifecycle.png new file mode 100644 index 0000000000..6a505ccefb Binary files /dev/null and b/docs/guide-vi/images/application-lifecycle.png differ diff --git a/docs/guide-vi/images/application-structure.graphml b/docs/guide-vi/images/application-structure.graphml new file mode 100644 index 0000000000..f6fce488be --- /dev/null +++ b/docs/guide-vi/images/application-structure.graphml @@ -0,0 +1,430 @@ + + + + + + + + + + + + + + + + + + + + + + + application +component + + + + + + + + + + + + + + + + + entry script + + + + + + + + + + + + + + + + + application + + + + + + + + + + + + + + + + + controller + + + + + + + + + + + + + + + + + filter + + + + + + + + + + + + + + + + + module + + + + + + + + + + + + + + + + + view + + + + + + + + + + + + + + + + + model + + + + + + + + + + + + + + + + + widget + + + + + + + + + + + + + + + + + asset bundle + + + + + + + + + + + + + + + + + 1:1 + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 1..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + diff --git a/docs/guide-vi/images/application-structure.png b/docs/guide-vi/images/application-structure.png new file mode 100644 index 0000000000..d3a5549888 Binary files /dev/null and b/docs/guide-vi/images/application-structure.png differ diff --git a/docs/guide/images/gii-entry.png b/docs/guide-vi/images/gii-entry.png similarity index 100% rename from docs/guide/images/gii-entry.png rename to docs/guide-vi/images/gii-entry.png diff --git a/docs/guide/images/gii-preview.png b/docs/guide-vi/images/gii-preview.png similarity index 100% rename from docs/guide/images/gii-preview.png rename to docs/guide-vi/images/gii-preview.png diff --git a/docs/guide-vi/images/rbac-access-check-1.graphml b/docs/guide-vi/images/rbac-access-check-1.graphml new file mode 100644 index 0000000000..44078515cf --- /dev/null +++ b/docs/guide-vi/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-vi/images/rbac-access-check-1.png b/docs/guide-vi/images/rbac-access-check-1.png new file mode 100644 index 0000000000..77ad551c26 Binary files /dev/null and b/docs/guide-vi/images/rbac-access-check-1.png differ diff --git a/docs/guide-vi/images/rbac-access-check-2.graphml b/docs/guide-vi/images/rbac-access-check-2.graphml new file mode 100644 index 0000000000..c521d429ea --- /dev/null +++ b/docs/guide-vi/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-vi/images/rbac-access-check-2.png b/docs/guide-vi/images/rbac-access-check-2.png new file mode 100644 index 0000000000..254f307a89 Binary files /dev/null and b/docs/guide-vi/images/rbac-access-check-2.png differ diff --git a/docs/guide-vi/images/rbac-access-check-3.graphml b/docs/guide-vi/images/rbac-access-check-3.graphml new file mode 100644 index 0000000000..8747cee0da --- /dev/null +++ b/docs/guide-vi/images/rbac-access-check-3.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-vi/images/rbac-access-check-3.png b/docs/guide-vi/images/rbac-access-check-3.png new file mode 100644 index 0000000000..1fdc0d935a Binary files /dev/null and b/docs/guide-vi/images/rbac-access-check-3.png differ diff --git a/docs/guide-vi/images/rbac-hierarchy-1.graphml b/docs/guide-vi/images/rbac-hierarchy-1.graphml new file mode 100644 index 0000000000..927b416d61 --- /dev/null +++ b/docs/guide-vi/images/rbac-hierarchy-1.graphml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-vi/images/rbac-hierarchy-1.png b/docs/guide-vi/images/rbac-hierarchy-1.png new file mode 100644 index 0000000000..7443fc7e71 Binary files /dev/null and b/docs/guide-vi/images/rbac-hierarchy-1.png differ diff --git a/docs/guide-vi/images/rbac-hierarchy-2.graphml b/docs/guide-vi/images/rbac-hierarchy-2.graphml new file mode 100644 index 0000000000..b81887b0e0 --- /dev/null +++ b/docs/guide-vi/images/rbac-hierarchy-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-vi/images/rbac-hierarchy-2.png b/docs/guide-vi/images/rbac-hierarchy-2.png new file mode 100644 index 0000000000..e77c5647c1 Binary files /dev/null and b/docs/guide-vi/images/rbac-hierarchy-2.png differ diff --git a/docs/guide-vi/images/request-lifecycle.graphml b/docs/guide-vi/images/request-lifecycle.graphml new file mode 100644 index 0000000000..aed5293b17 --- /dev/null +++ b/docs/guide-vi/images/request-lifecycle.graphml @@ -0,0 +1,834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + user + + + + + + + + + + + + + + + + + + + + model + + + + + + + + + + + + + + + + + database + + + + + + + + + + + + + + + + + + + + + + + + + + + + view + + + + + + + + + + + + + + + + + + + + controller + + + + + + + + + + Folder 1 + + + + + + + + + + + + + + + + create action + + + + + + + + + + + + + + + + + perform filters + + + + + + + + + + + + + + + + + + + + action + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + load model + + + + + + + + + + + + + + + + + render view + + + + + + + + + + + + + + + + + + + + + response component + + + + + + + + + + + + + + + + + request component + + + + + + + + + + + + + + + + + + + + application + + + + + + + + + + Folder 2 + + + + + + + + + + + + + + + + resolve route + + + + + + + + + + + + + + + + + create controller + + + + + + + + + + + + + + + + + + + + + + entry script + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + load app config + + + + + + + + + + + + + + + + + run application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11 + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + 4 + + + + + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + 8 + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="66px" viewBox="0 0 57 66" enable-background="new 0 0 57 66" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3799" y1="-2276.8809" x2="27.6209" y2="-2306.6792" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_13_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + <path fill="#2068A3" stroke="#2068A3" d="M28.106,33.487c-8.112,0-12.688,4.312-12.688,10.437c0,7.422,12.688,10.438,12.688,10.438 + s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.487,28.106,33.487z M26.288,53.051c0,0-7.135-2.093-8.805-7.201 + c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_2_" cx="14.2417" cy="9.1006" r="53.247" gradientTransform="matrix(1 0 0 -1 0.04 65.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#74AEEE"/> + <stop offset="1" style="stop-color:#2068A3"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#2068A3" stroke-miterlimit="10" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.022,7.807-14.022,7.807s-10.472-2.484-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path fill="#5491CF" stroke="#2068A3" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397c-0.514,1.027-1.669,4.084-1.669,5.148 + c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.335-2.36c-3.601-1.419-4.071-3.063-5.89-4.854 + C12.523,47.135,12.878,45,13.404,44.173z"/> + <path fill="#5491CF" stroke="#2068A3" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617c0.516,1.025,3.617,3.693,3.617,6.617 + c0,5.186-10.27,8.576-16.698,9.145c1.429,4.938,11.372,1.293,13.804-0.313c3.563-2.354,4.563-5.133,7.854-3.705 + C47.754,49.045,48.006,46.574,45.777,43.924z"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.623" cy="-2278.646" r="23.425" fx="23.0534" fy="-2281.1357" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="5761.7578" y1="11330.6484" x2="5785.3872" y2="11424.0977" gradientTransform="matrix(0.275 0 0 0.2733 -1558.9874 -3088.4209)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M27.958,6.333c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.083,13.952,36.271,6.268,27.958,6.333z"/> + <path id="Hair_Young_Brown_1_" fill="#CC9869" stroke="#99724F" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> + <path fill="#4B4B4B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M28.105,2 + C22.464,2,20.2,4.246,18.13,5.533C29.753,2.865,41.152,10.375,44.46,20.5C44.459,16.875,44.459,2,28.105,2z"/> + <path fill="#9B9B9B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M11.151,17.751 + C12.878,8.25,18.686,6.309,25.273,7.127C31.295,7.875,36.93,10.491,44.459,20.5C37.777,7.125,20.278-3.375,9.903,3.921 + C5.569,6.97,4.903,13.375,11.151,17.751z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + x="0px" y="0px" width="41px" height="48px" viewBox="-0.875 -0.887 41 48" enable-background="new -0.875 -0.887 41 48" + xml:space="preserve"> +<defs> +</defs> +<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-979.1445" x2="682.0508" y2="-979.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_1_)" d="M19.625,36.763C8.787,36.763,0,34.888,0,32.575v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,34.888,30.464,36.763,19.625,36.763z"/> +<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-973.1445" x2="682.0508" y2="-973.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_2_)" d="M19.625,36.763c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.927-18.396,3.927 + c-9.481,0-17.396-1.959-18.396-3.927l-1.229,2C0,34.888,8.787,36.763,19.625,36.763z"/> +<path fill="#3C89C9" d="M19.625,26.468c10.16,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.554,5.438 + c-12.125,0-18.467-2.484-19.541-4.918C-0.127,29.125,9.465,26.468,19.625,26.468z"/> +<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-965.6948" x2="682.0508" y2="-965.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_3_)" d="M19.625,23.313C8.787,23.313,0,21.438,0,19.125v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,21.438,30.464,23.313,19.625,23.313z"/> +<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-959.6948" x2="682.0508" y2="-959.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_4_)" d="M19.625,23.313c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926l-1.229,2C0,21.438,8.787,23.313,19.625,23.313z"/> +<path fill="#3C89C9" d="M19.476,13.019c10.161,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.555,5.438 + c-12.125,0-18.467-2.485-19.541-4.918C-0.277,15.674,9.316,13.019,19.476,13.019z"/> +<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-952.4946" x2="682.0508" y2="-952.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_5_)" d="M19.625,10.113C8.787,10.113,0,8.238,0,5.925v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,8.238,30.464,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-946.4946" x2="682.0508" y2="-946.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_6_)" d="M19.625,10.113c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926L0,5.925C0,8.238,8.787,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.0293" y1="-943.4014" x2="680.8223" y2="-943.4014" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<ellipse fill="url(#SVGID_7_)" cx="19.625" cy="3.926" rx="18.396" ry="3.926"/> +<path opacity="0.24" fill="#FFFFFF" enable-background="new " d="M31.04,45.982c0,0-4.354,0.664-7.29,0.781 + c-3.125,0.125-8.952,0-8.952,0l-2.384-10.292l0.044-2.108l-1.251-1.154L9.789,23.024l-0.082-0.119L9.5,20.529l-1.65-1.254 + L5.329,8.793c0,0,4.213,0.903,7.234,1.07s8.375,0.25,8.375,0.25l3,9.875l-0.25,1.313l1.063,2.168l2.312,9.645l-0.521,1.416 + l1.46,1.834L31.04,45.982z"/> +</svg> + + + + diff --git a/docs/guide-vi/images/request-lifecycle.png b/docs/guide-vi/images/request-lifecycle.png new file mode 100644 index 0000000000..f9ed032cec Binary files /dev/null and b/docs/guide-vi/images/request-lifecycle.png differ diff --git a/docs/guide-vi/images/start-app-installed.png b/docs/guide-vi/images/start-app-installed.png new file mode 100644 index 0000000000..a70f3e687d Binary files /dev/null and b/docs/guide-vi/images/start-app-installed.png differ diff --git a/docs/guide-vi/images/start-country-list.png b/docs/guide-vi/images/start-country-list.png new file mode 100644 index 0000000000..375419414d Binary files /dev/null and b/docs/guide-vi/images/start-country-list.png differ diff --git a/docs/guide-vi/images/start-entry-confirmation.png b/docs/guide-vi/images/start-entry-confirmation.png new file mode 100644 index 0000000000..ed442a4c6e Binary files /dev/null and b/docs/guide-vi/images/start-entry-confirmation.png differ diff --git a/docs/guide-vi/images/start-form-validation.png b/docs/guide-vi/images/start-form-validation.png new file mode 100644 index 0000000000..f38652871d Binary files /dev/null and b/docs/guide-vi/images/start-form-validation.png differ diff --git a/docs/guide-vi/images/start-gii-country-grid.png b/docs/guide-vi/images/start-gii-country-grid.png new file mode 100644 index 0000000000..746663c8f9 Binary files /dev/null and b/docs/guide-vi/images/start-gii-country-grid.png differ diff --git a/docs/guide-vi/images/start-gii-country-update.png b/docs/guide-vi/images/start-gii-country-update.png new file mode 100644 index 0000000000..51f2d38a32 Binary files /dev/null and b/docs/guide-vi/images/start-gii-country-update.png differ diff --git a/docs/guide-vi/images/start-gii-crud-preview.png b/docs/guide-vi/images/start-gii-crud-preview.png new file mode 100644 index 0000000000..85c2355f2e Binary files /dev/null and b/docs/guide-vi/images/start-gii-crud-preview.png differ diff --git a/docs/guide-vi/images/start-gii-crud.png b/docs/guide-vi/images/start-gii-crud.png new file mode 100644 index 0000000000..77c1ada18a Binary files /dev/null and b/docs/guide-vi/images/start-gii-crud.png differ diff --git a/docs/guide-vi/images/start-gii-model-preview.png b/docs/guide-vi/images/start-gii-model-preview.png new file mode 100644 index 0000000000..080be64b1f Binary files /dev/null and b/docs/guide-vi/images/start-gii-model-preview.png differ diff --git a/docs/guide-vi/images/start-gii-model.png b/docs/guide-vi/images/start-gii-model.png new file mode 100644 index 0000000000..59c16a477e Binary files /dev/null and b/docs/guide-vi/images/start-gii-model.png differ diff --git a/docs/guide-vi/images/start-gii.png b/docs/guide-vi/images/start-gii.png new file mode 100644 index 0000000000..28c75b6b79 Binary files /dev/null and b/docs/guide-vi/images/start-gii.png differ diff --git a/docs/guide-vi/images/start-hello-world.png b/docs/guide-vi/images/start-hello-world.png new file mode 100644 index 0000000000..30d1acad38 Binary files /dev/null and b/docs/guide-vi/images/start-hello-world.png differ diff --git a/docs/guide-vi/intro-yii.md b/docs/guide-vi/intro-yii.md new file mode 100644 index 0000000000..2dece4a9d2 --- /dev/null +++ b/docs/guide-vi/intro-yii.md @@ -0,0 +1,55 @@ +Yii là gì +=========== + +Yii có hiệu suất cao, được dựa trên các thành phần PHP framework cho việc phát triển các ứng dụng Web hiện đại. +Tên Yii (được phát âm là `Yee` hoặc `[ji:]`) ở Trung Quốc có nghĩa là "thật đơn giản và luôn phát triển". Nghĩa thứ hai có thể đọc ngắn gọn là **Yes It Is**! + + +Yii thích hợp nhất để làm gì? +--------------------- + +Yii là một framework lập trình Web chung chung mà có thể được sử dụng để phát triển một loạt các ứng dụng Web +được xây dựng với PHP. Bởi vì dựa trên kiến trúc thành phần và có bộ nhớ đệm hoàn hảo, +nó là đặc biệt thích hợp cho việc phát triển các ứng dụng quy mô lớn, chẳng hạn như các cổng thông tin, +diễn đàn, hệ thống quản lý nội dung (CMS), các dự án thương mại điện tử và các dịch vụ Web RESTful. + + +So sánh Yii Với các Frameworks khác? +------------------------------------------- + +Nếu bạn có kinh nghiệm với các framework khác, bạn sẽ rất vui mừng khi thấy những nỗ lực của Yii: + +- Giống như những PHP frameworks khác, Yii sử dụng mô hình MVC (Model-View-Controller) tổ chức code một cách hợp lý và có hệ thống hơn . +- Yii tạo ra code đơn giản và thanh lịch, đây là triết lý trong chương trình. Yii sẽ không bao giờ + cố gắng tạo ra những mấu thiết kế quá an toàn và ít có sự thay đổi. +- Yii là framework hoàn chình, cung cấp nhiều tính năng và được xác minh như: query builders, thao tác dữ liệu với + ActiveRecord được dùng cho CSDL quan hệ và NoSQL; hỗ trợ phát triển RESTful API; sự hỗ trợ đa bộ nhớ cache; và nhiều hơn. +- Yii rất dễ mở rộng. Bạn có thể tùy chình hoặc thay thế bất kỳ một trong những bộ code chuẩn. Bạn cũng có thể + tận dụng lợi thế của kiến trúc mở rộng chuẩn Yii để sử dụng hoặc phát triển mở rộng phân phối.. +- Hiệu suất cao luôn luôn là một trong những mục tiêu chính của Yii. + +Yii không chỉ được phát triển từ một người, nó được hỗ trợ bởi [đội ngũ phát triển cốt lõi mạnh mẽ][about_yii], cũng như một cộng đồng lớn, trong đó các chuyên gia liên tục +đóng góp cho sự phát triển của Yii. Nhóm nghiên cứu phát triển Yii giữ một mắt đóng trên các xu hướng phát triển Web mới nhất và trên thực hành tốt nhất và +các tính năng được tìm thấy trong các khuôn khổ và các dự án khác. Các thực hành tốt nhất và các tính năng được tìm thấy ở những nơi khác có liên quan nhất thường xuyên +được đưa vào khuôn khổ lõi và tiếp xúc thông qua giao diện đơn giản và thanh lịch. + +[about_yii]: http://www.yiiframework.com/about/ + +Các phiên bản Yii +------------ + +Yii Hiện nay có hai phiên bản chính: 1.1 và 2.0. Phiên bản 1.1 là phiên bản cũ và bây giờ là trong chế độ bảo trì. Tiếp đến, phiên bản 2.0 là phiên bản đuọc viết lại hoàn toàn Yii, sử dụng các +công nghệ mới và giao thức, bao gồm trình quản lý gói Composer, các tiêu chuẩn code PHP PSR, namespaces, traits, và như vậy. Phiên bản 2.0 đại diện cho sự hình thành của framework +và sẽ nhận được những nỗ lực phát triển chính trong vài năm tới. +Hướng dẫn này chủ yếu là về phiên bản 2.0. + + +Yêu cầu hệ thống và các điều kiện cần thiết +------------------------------ + +Yii 2.0 đòi hỏi phiên bản PHP 5.4.0 hoặc cao hơn. Bạn có thể chạy bất kỳ gói Yii đi kèm với các yêu cầu hệ thống. +kiểm tra xem những gì các đặc điểm cụ thể của từng cấu hình PHP. + +Để tìm hiểu Yii, bạn cần có kiến thức cơ bản về lập trình hướng đối tượng (OOP), vì Yii là một framework hướng đối tượng +thuần túy. Yii2 2.0 cũng sử dụng các tính năng PHP mới nhất, chẳng hạn như [namespaces](http://www.php.net/manual/en/language.namespaces.php) và [traits](http://www.php.net/manual/en/language.oop5.traits.php). +Hiểu được những khái niệm này sẽ giúp bạn nhanh chóng nắm bắt Yii 2.0. diff --git a/docs/guide-vi/start-databases.md b/docs/guide-vi/start-databases.md new file mode 100644 index 0000000000..5798e811e5 --- /dev/null +++ b/docs/guide-vi/start-databases.md @@ -0,0 +1,251 @@ +Làm việc với CSDL +====================== + +Phần này sẽ hướng dẫn làm thế nào để tạo mới trang có chức năng hiển thị dữ liệu các thành phố (country) và được lấy +từ bảng `country` nằm trong cơ sở dữ liệu. Để thực hành tốt bài hướng dẫn, bạn cần cấu hình các kết nối tới CSDL, +tạo class [Active Record](db-active-record.md), taọ một [action](structure-controllers.md), +và tạo mới [view](structure-views.md). + +Tóm tắt những nội dung chính: + +* Cấu hình kết nối tới CSDL +* Đinh nghĩa lớp Active Record +* Sử dụng lớp Active Record để truy vấn dữ liệu +* Hiển thị và phân trang dữ liệu trên view + +Lưu ý để thực hiện được bài hướng dẫn này, bạn cần có kiến thức về CSDL. +Riêng ở phần này, bạn cần có kiến thức về tạo mới CSDL, và làm thế nào để thực thi các câu lệnh SQL sử dụng công cụ ở phía client. + + +Chuẩn bị +---------------------- + +Đầu tiên, bạn cần tạo mới CSDL tên là `yii2basic`, từ bây giờ bạn sẽ dùng CSDL này để lấy dữ liệu. +Yii hỗ trợ nhiều CSDL trong ứng dụng, bạn có thể dùng những CSDL như SQLite, MySQL, PostgreSQL, MSSQL hoặc Oracle. Để cho đơn giản, +MySQL sẽ được dùng trong bài hướng dẫn này. + +Tiếp đến, tạo mới bảng vào CSDL tên là `country` , đồng thời chèn thêm dữ liệu. You may run the following SQL statements to do so: + +```sql +CREATE TABLE `country` ( + `code` CHAR(2) NOT NULL PRIMARY KEY, + `name` CHAR(52) NOT NULL, + `population` INT(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); +``` + +Đến đây, bạn có CSDL là `yii2basic`, có chứa bảng `country` có 3 cột và 10 trường dữ liệu. + +Cấu hình kết nối tới CSDL +--------------------------- + +Trước tiên, hãy chắc chắn rằng bạn đã cài 2 gói PHP [PDO](http://www.php.net/manual/en/book.pdo.php) và +PDO driver dành cho các CSDL mà đang sử dụng(ví dụ `pdo_mysql` cho MySQL). Đối với các CSDL quan hệ thì những gói này +cần phải có. + +Sau khi những yêu cầu trên được cài đặt, mở file `config/db.php` và thay đổi các tham số chính xác tới CSDL. Mặc định, +file sẽ có những đoạn code sau: + +```php + 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=yii2basic', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; +``` + +File `config/db.php` là file điển hình dành cho cấu hình ứng dụng [configuration](concept-configurations.md). Các tham số được mô tả trong file +cần thiết để tạo mới và khởi tạo các thể hiện [[yii\db\Connection]] và thực hiện các câu lệnh truy vấn + +Các thông tin cấu hình về CSDL ở trên được truy cập qua ứng dụng qua câu lệnh `Yii::$app->db`. + +> Lưu ý: File `config/db.php` sẽ chứa các thông tin chính trong việc cấu hình ứng dụng `config/web.php`, + Những thông tin làm thế nào để [ứng dụng](structure-applications.md) cần được khởi tạo. + Bạn có thể tham khảo thêm trong phần [cấu hình ứng dụng](concept-configurations.md) . + + +Tạo mới class Active Record +------------------------- + +Để thể hiện và thao tác với bảng dữ liệu `country`, ta tạo mới class[Active Record](db-active-record.md)- +tên là `Country`, và lưu vào file `models/Country.php`. + +```php + Info: Nếu tên lớp không trùng với tên trong bảng dữ liệu, bạn có thể +ghi đè phương thức [[yii\db\ActiveRecord::tableName()]] để miêu tả rõ ràng về tên bảng dữ liệu. + +Dùng lớp `Country`, bạn sẽ dễ dàng hơn trong việc thao tác với bảng `country`, ví dụ: + +```php +use app\models\Country; + +// lấy danh sách tử bảng country và sắp xếp theo thuộc tính "name" +$countries = Country::find()->orderBy('name')->all(); + +// lấy dữ liệu có khóa là "US" +$country = Country::findOne('US'); + +// in kết quả "United States" +echo $country->name; + +// thay đổi tên country thành "U.S.A." và lưu vào csdl +$country->name = 'U.S.A.'; +$country->save(); +``` + +> Lưu ý: Active Record khá là mạnh cho việc truy cập csdl theo hướng lập trình hướng đối tượng. +Bạn có thể xem thêm ở mục [Active Record](db-active-record.md). Cách khác, bạn có thể thao tác với csdl ở mức độ đơn giản hơn bằng việc truy cập qua đối tượng [Data Access Objects](db-dao.md). + + +Tạo hành động (action) +------------------ + +Để hiển thị dữ liệu country tới người dùng, bạn cần tạo mới hành động. Thay vì đặt các hành động ở `site` +controller, giống như đã làm ở phần trước, thì tạo controller mới có ý nghĩa hơn +đặc biệt liên quan tới dữ liệu về coutry. Tên controller là `CountryController`, và tạo mới +hành động `index` ở trong đó, bạn có thể tham khảo ở phần dưới. + +```php + 5, + 'totalCount' => $query->count(), + ]); + + $countries = $query->orderBy('name') + ->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); + + return $this->render('index', [ + 'countries' => $countries, + 'pagination' => $pagination, + ]); + } +} +``` + +Lưu nội dung đoạn mã trên vào file `controllers/CountryController.php`. + +Hành động `index` sẽ gọi các phương thức `Country::find()`. Đây là phương thức nằm ở Active Record nhằm xây dựng câu lệnh truy vấn và lấy tất cả dữ liệu trong bảng `country`. +Để hạn chế số lượng dữ liệu mỗi khi gửi yêu cầu, đối tượng [[yii\data\Pagination]] sẽ phân trang dữ liệu. + Mục đích khi dùng đối tượng `Pagination` là: + +* Thiết lập điều kiện `offset` và `limit` cho câu lệnh mỗi khi lấy liệu ra (mỗi lần chỉ hiển thị 5 kết quả). +* Dữ liệu được nhúng vào view để hiển thị số trang và bao gồm danh sách các button, sẽ được giải thích ở phần sau. + +Xem những dòng cuối, hành động `index` sẽ đổ ra view có tên là `index`, đồng thời gửi dữ liệu về country và thông tin về phân trang. + + +Tạo View +--------------- + +Trong thư mục `views`, bước một tạo thư mục con là `country`. Thư mực này được dùng để giữ những view được đổ ra +từ controller `country`. Trong thư mục `views/country`, tạo mới file tên là `index.php` +và chứa đoạn mã sau: + +```php + +

Countries

+
    + +
  • + name} ({$country->code})") ?>: + population ?> +
  • + +
+ + $pagination]) ?> +``` + +View trên có 2 phần liên quan tới hiển thị dữ liệu về country. Phần đầu tiên, cung cấp danh sách country và in ra dưới dạng danh sách . +Phần tiếp, một widget [[yii\widgets\LinkPager]] được sinh ra và dùng các thông tin truyển từ action xuống để phân trang. +Đối tượng `LinkPager` là một widget có chức năng hiển thị danh sách các button. Mỗi khi click vào mỗi button này sẻ cập nhật lại dữ liệu country +ở mỗi trang tương ứng. + + +Xem demo +------------- + +Truy cập vào Url sau và xem kết quả: + +``` +http://hostname/index.php?r=country/index +``` + +![Danh sách Country](images/start-country-list.png) + +Hãy xem, bạn sẽ thấy một trang hiển thị 4 country. Phần dưới danh sách các country, bạn sẽ thấy các nút phân trang. +Nếu bạn click vào button "2", bạn sẽ thấy 5 country khác trong CSDL: trang thứ 2 trong CSDL country. +Bạn để ý rằng URL trong trình duyệt cũng thay đổi. + +``` +http://hostname/index.php?r=country/index&page=2 +``` + +Trong luồng xử lý này, đối tượng [[yii\data\Pagination|Pagination]] sẽ cung cấp tất cả những hàm cần thiết cho việc phân trang: + +* Khởi tạo, đối tượng [[yii\data\Pagination|Pagination]] hiển thị ở trang đầu tiên , điều này được thực hiện câu lệnh truy vấn SELECT từ country + với mệnh đề `LIMIT 5 OFFSET 0`. Như kết quả trên, 5 country đầu tiên sẽ lấy ra và hiển thị. +* Widget [[yii\widgets\LinkPager|LinkPager]] sẽ tạo các buttons cùng với các URL( liên kết) + được tạo bởi phương thức [[yii\data\Pagination::createUrl()|Pagination]]. Các URL sẽ chứa tham số `page`, page sẽtương ứng với số trang khác nhau trong CSDL country. +* Nếu bạn click vào button "2", route `country/index` được gọi và đồng thời route sẽ được gắn và giữ. + Đối tượng [[yii\data\Pagination|Pagination]] đọc số trang từ tham số `page` từ Url và thiết lập trang trang hiện tại là 2. + Việc truy vấn dữ liệu country tương tự như mệnh đề `LIMIT 5 OFFSET 5` và trả về tiếp 5 country để hiển thị + + +Tổng kết +------- + +Bài hướng dẫn này, giúp bạn tìm hiểu và làm việc với CSDL. Bạn cũng được tìm hiều đối tượng [[yii\data\Pagination]] và [[yii\widgets\LinkPager]]. giúp cho việc +lấy và hiển thì dữ liệu trên trang. + +Trong phần tới, bạn sẽ được tìm hiểu về công cụ generate code khá mạnh, được gọi là [Gii](tool-gii.md), +giúp bạn nhanh chóng thực hiện một số tính năng tương tự, những hoạt động thao tác với các bảng trong CSDL như Tạo-Xem-Cập nhật-Xóa (CRUD) +. Trong thực tế, những mã nguồn được viết có thể tự động generate trong Yii sử dụng công cụ Gii. diff --git a/docs/guide-vi/start-forms.md b/docs/guide-vi/start-forms.md new file mode 100644 index 0000000000..00046d98e0 --- /dev/null +++ b/docs/guide-vi/start-forms.md @@ -0,0 +1,238 @@ +Làm việc với Forms +================== + +Ở phần này sẽ hướng dẫn làm thế nào để tạo mới trang Web cho phép ứng dụng lấy các thông tin về user từ form. +Trang này sẽ có chức năng hiển thị form cho user cùng với các input như name (tên người dùng) và email. +Sau khi nhận hai thông tin về user, trang web sẽ hiển thị thông tin tới user. + +Để làm được trang web này, bên cạnh tạo ra [action](structure-controllers.md) và +hai giao diện [views](structure-views.md), bạn cần phải tạo ra đối tượng [model](structure-models.md) để xử lý các nghiệp vụ logic truy xuất CSDL. + +Trong phần này, bạn sẽ được tìm hiểu về: + +* Tạo đối tượng [model](structure-models.md) nhận thông tin từ user được nhập từ form +* Khai báo rules để xách minh dữ liệu nhập vào +* Xây dựng form HTML ở [view](structure-views.md) + + +Tạo Model +---------------- + +Dữ liệu của user cần xử lý sẽ đại diện bởi lớp model `EntryForm` sau đây và +được lưu ở file `models/EntryForm.php`. Tham khảo thêm về phần [Class Autoloading](concept-autoloading.md) +để biết thêm chi tiết về quy tắc đặt tên cho các lớp. + +```php + Lưu ý: [[yii\base\Model]] là lớp cơ sở cho việc tương tác với các lớp dữ liệu và nó *không* liên quan tới các bảng trong CSDL. +[[yii\db\ActiveRecord]] là lớp thường được dùng với CSDL mỗi lớp này sẽ tương xứng với các bảng trong CSDL. + +Lớp `EntryForm` chứa hai biến ở phạm vi toàn cục (public), `name` và `email`, Các biến này sẽ được dùng để lưu trữ dữ liệu +khi người dùng nhập và gửi lên. Lớp này đồng thời chứa phương thức là `rules()`, phương thức này trả về tập quy tắc để xác thực +dữ liệu. Các quy tắc chứng thực được khai báo ở phần trên với ý nghĩa rằng. + +* cả hai giá trị `name` và `email` cần phải có +* giá trị `email` phải đúng cú pháp là địa chỉ email + +Nếu đã có đối tượng `EntryForm` cùng với dữ liệu user đã nhập, bạn có thể sử dụng phương thức +[[yii\base\Model::validate()|validate()]] để xác thực dữ liệu mỗi khi user gửi lên. Việc xác thực dữ liệu sai sẽ +thiết lập thuộc tính [[yii\base\Model::hasErrors|hasErrors]] thành "true", và bạn có thể xem thông tin về việc xác thực lỗi +từ phương thức [[yii\base\Model::getErrors|errors]]. + +```php +name = 'Qiang'; +$model->email = 'bad'; +if ($model->validate()) { + // Xác thực thành công! +} else { + // Xác thực lỗi! + // Use $model->getErrors() +} +``` + + +Tạo Action +------------------ + +Tiếp theo, trong controller `site` bạn sẽ tạo action là `entry` action này cần dùng tới model. Quy trình và cách tạo mới action +đã được hướng dẫn ở mục [Saying Hello](start-hello.md). + +```php +load(Yii::$app->request->post()) && $model->validate()) { + // valid data received in $model + + // do something meaningful here about $model ... + + return $this->render('entry-confirm', ['model' => $model]); + } else { + // either the page is initially displayed or there is some validation error + return $this->render('entry', ['model' => $model]); + } + } +} +``` + +Action này sẽ tạo đối tượng `EntryForm`. Sau khi được khởi tạo, nó sẽ lấy các thông tin thông qua biến + `$_POST`, biến này được Yii cung cấp [[yii\web\Request::post()]]. +Nếu dữ liệu gửi đến cho model thành công(chẳng hạn., khi user gửi thông tin từ HTML form), action sẽ gọi phương thức +[[yii\base\Model::validate()|validate()]] để chắc chắn rằng những giá trị được nhập vào là hợp lý. + +> Thông tin thêm: Thành phần `Yii::$app` được mô tả ở mục [application](structure-applications.md), + Thành phần này là một mẫu thiết kế singleton cho phép truy cập ở toàn cục. Được hoạt động như một [service locator](concept-service-locator.md) that + để cung cấp các thành phần như `request`, `response`, `db`, vv. nhằm để hỗ trợ thêm các chức năng đặc biệt. + Ở đoạn code trên, component `request` được khởi tạo bỏi ứng dụng dùng để truy cập dữ liệu từ `$_POST`. + +Nếu không có lỗi gì, action sẽ trả về (render) view tên là `entry-confirm` để xác nhận dữ liệu được gửi lên. +. Nếu dữ liệu trống hoặc gặp lỗi, dữ liệu sẽ được gửi về view `entry`, chứa form HTML, cùng với các thông điệp ở việc xác thực bị lỗi. + +> Lưu ý: ở bài hướng dẫn này, chúng ta chỉ xác nhận trang khi có dũ liệu hợp lệ. Bài thực hành này, + bạn cần lưu ý việc sử dụng các phương thức [[yii\web\Controller::refresh()|refresh()]] hoặc [[yii\web\Controller::redirect()|redirect()]] + nhằm để tránh [form resubmission problems](http://en.wikipedia.org/wiki/Post/Redirect/Get). + + +Tạo Views +-------------- + +Cuối cùng, chúng ta tạo mới 2 tập tin view có tên là `entry-confirm` và `entry`. Những view này sẽ được trả về như được mô tả ở trên từ action `entry`. + +View `entry-confirm` đơn giản chỉ hiển thị dữ liệu cho 2 thuộc tính name và email . View này được lưu trữ ở tập tin `views/site/entry-confirm.php`. + +```php + +

Bạn đã nhập với những thông tin như sau:

+ +
    +
  • : name) ?>
  • +
  • : email) ?>
  • +
+``` + +View `entry` sẽ hiển thị một form chứa các mã HTML. View này được lưu trữ ở tập tin `views/site/entry.php`. + +```php + + + + field($model, 'name') ?> + + field($model, 'email') ?> + +
+ 'btn btn-primary']) ?> +
+ + +``` + +Những [widget](structure-widgets.md) gọi là [[yii\widgets\ActiveForm|ActiveForm]] to +thường được dùng để xây dựng các Form. Các phương thức `begin()` và `end()` dùng để mở và đóng các tag tương ứng +. Giữa hai phương thức này, phương thức [[yii\widgets\ActiveForm::field()|field()]] sẽ tạo mới các input của form . Input đầu tiên sẽ dùng cho trường dữ liệu "name", +và input thức hai sẽ dược dùng cho trường "email". Sau cùng của các input, phương thức [[yii\helpers\Html::submitButton()]] +sẽ được gọi và tạo ra nút submit dùng để gửi dữ liệu. + + +Thử xem kết quả +------------- + +Truy cập vào URL sau để xem kết quả: + +``` +http://hostname/index.php?r=site/entry +``` + +Bạn sẽ thấy trang Web cùng với việc hiển thị form chứa 2 trường để nhập dữ liệu . Trước mỗi trường nhập liệu, có nhãn được chỉ định những dữ liệu nhập vào . +Nếu bạn không nhập dữ liệu gì vào và nhấn nút submit, hoặc nếu bạn cung cấp địa chỉ email sai, bạn sẽ thấy thông điệp thông báo lỗi ở mỗi trường nhập liệu. + +![Form with Validation Errors](images/start-form-validation.png) + +Sau khi nhập đúng các trường name và địa chỉ email đồng thời click vào nút submit, bạn sẽ thấy trang web mới +cùng với dữ liệu bạn vừa nhập . + +![Confirmation of Data Entry](images/start-entry-confirmation.png) + + + +### Thông tin thêm + +Chúng ta sẽ thắc mắc rằng làm sao các form HTMl được xây dựng lên, bởi vì nó dường như là những thủ thuật có thể +hiển thị các nhãn (label) cho từng trường input và hiển thị thông báo lỗi nếu bạn không nhập dữ liệu chính xác mà không cần +tải lại trang. + +Đúng vậy, việc xác nhận dữ liệu được thực hiện ở máy client sử dụng JavaScript, và tiếp đế được thực hiện ở máy chủ PHP. +Đối tượng [[yii\widgets\ActiveForm]] rất hữu dụng cho việc xác nhận những quy tắc (rules) mà bạn đã khai báo ở model `EntryForm`, +và biến chúng thành những đoạn mã javaScript thực thi, và sử dụng javaScript để xác thực. Trường hợp bạn đã vô hiệu hóa +javaScript trên trình duyệt, việc xác thực sẽ thực hiện ở phía server, nằm ở phương thức +`actionEntry()`. Điều này đảm bảo tính hợp lệ dữ liệu trong mọi trường hợp. + +> Cảnh báo: Việc xác thực ở phía client thường cung cấp cho sự trải nghiệm của người dùng tốt hơn. Xác thực phía server + thì luôn luôn được thực thi, có thể có hoặc không việc xác thực ở phía client. + +Các nhãn (label) cho các input được tạo ra bởi phương thức `field()`, sử dụng tên của thuộc tính nằm trong model. +Chẳng hạn, tên nhãn `Name` sẽ được tạo bởi thuộc tính `name`. + +Bạn có thể sửa tên nhãn ở đoạn code sau: + +```php +field($model, 'name')->label('Tên của bạn Name') ?> +field($model, 'email')->label('Địa chỉ Email') ?> +```n + +> Thông tin thêm: Yii giúp bạn xây dụng nhanh chóng đối với các view phức tạp bằng việc cung cấp các widget. + Bạn sẽ được học ở phần sau, cách đơn giản nhất để viết một widget. Bạn nên chuyển những code ở view của bạn + sang dạng widget để đơn giản hơn sự phát triển ứng dụng và tái sử dụng nó. + + +Tóm lược +------- + +Trong phần hướng dẫn này, bạn đã làm việc với tất cả các thành phần trong mô hình MVC. Bạn đã học cách tạo mới model và xác thực dữ liệu. + +Bạn đã tìm hiểu cách lấy dữ liệu từ user và hiển thị dữ liệu ra trình duyệt. Chức năng này có thể dành nhiều thời gian khi +xây dựng ứng dụng, tuy nhiên Yii hỗ trợ các chức năng thật đơn giản bằng việc cung cấp những widget. + +Trong phần tiếp theo, bạn sẽ tìm hiều làm thể nào để làm việc với CSDL, điều cần thiết với những ứng dụng. diff --git a/docs/guide-vi/start-gii.md b/docs/guide-vi/start-gii.md new file mode 100644 index 0000000000..c8cd304952 --- /dev/null +++ b/docs/guide-vi/start-gii.md @@ -0,0 +1,137 @@ +Sử dụng Gii để sinh code +======================== + +Trong phần này sẽ hướng dẫn sử dụng [Gii](tool-gii.md) để tự động sinh code, những mã nguồn tương tự với Web site +. Sử dụng Gii để tự động tạo mã nguồn thật đơn giản, bạn chỉ việc nhập thông tin đúng theo các hướng dẫn hiển thị trên các trang Web Gii và mã +nguỗn sẽ được sinh tự động. + +Nội dung chính trong phần này: + +* Nhúng Gii vào ứng dụng +* Dùng Gii để sinh Active Record +* Dùng Gii để sinh các mã nguồn CRUD cho các bảng CSDL +* Thay đổi các đoạn mã được sinh ra bởi Gii + + +Bắt đầu với Gii +------------ + +Yii cung cấp [Gii](tool-gii.md) như một [module](structure-modules.md). Bạn có thể nhúng Gii +bằng việc cấu hình các thuộc tính của ứng dụng ở phần [[yii\base\Application::modules|modules]] . Tùy thuộc vào ứng dụng của bạn, bạn có thể nhìn thấy được những đoạn mã sau được cung cấp trong file cấu hình `config/web.php`: + +```php +$config = [ ... ]; + +if (YII_ENV_DEV) { + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; +} +``` + +Phần cấu hình trên được đề cập ở mục [Môi trường phát triển](concept-configurations.md#environment-constants), +ứng dụng bao gồm module tên là `gii`, nằm ở lớp [[yii\gii\Module]]. + +Nếu bạn xem qua file [entry script](structure-entry-scripts.md) `web/index.php` trong ứng dụng của bạn, bạn sẽ thấy dòng sau, +Điều này chủ yếu thiết lập tham số `YII_ENV_DEV` có giá trị true. + +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +Dựa vào dòng này, ứng dụng sẽ được thiết lập ở chế độ phát triển, và sẵn sàng nhúng Gii vào ứng dụng, ở mỗi cấu hình trên. +Bây giờ bạn có thể truy cập Gii qua đường dẫn: + +``` +http://hostname/index.php?r=gii +``` + +> Lưu ý: Nếu bạn muốn truy cập Gii không chỉ từ localhost mà còn từ các máy khác, mặc định các truy cập sẽ bị từ chối +> để đảm bảo sự an toàn hơn. Bạn có thể cấu hình Gii bằng việc thêm địa chỉ IP được phép gọi như sau, +> +```php +'gii' => [ + 'class' => 'yii\gii\Module', + 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'] // thêm những địa chỉ ip +], +``` + +![Gii](images/start-gii.png) + + +Sinh các lớp Active Record +--------------------------------- + +Để dùng gii cho việc sinh các lớp Active Record, chọn "Model Generator" (bằng việc click vào link trên trang chính của Gii). Và điền các thông tin vào form như sau: + +* Tên bảng: `country` +* Tên lớp: `Country` + +![Sinh Model](images/start-gii-model.png) + +Tiếp đến, nhấn vào nút "Preview". Bạn sẽ thấy class `models/Country.php` ở danh sách các class được tạo ra. Bạn phải chọn vào tên class để xem nội dung. + +Khi sử dụng Gii, nếu bạn đã tạo file tương tự và cần được ghi đè lên,nhấp vào nút diff bên cạnh tên tập tin để thấy sự khác biệt giữa các mã được tạo ra và các phiên bản hiện tại. + +![Model Generator Preview](images/start-gii-model-preview.png) + +Khi bạn muốn ghi đè vào file đã có, kiểm tra tiếp ở ô "overwrite" và click vào nút "Generate". Nếu bạn tạo mới file, bạn chỉ việc click vào nút "Generate". + +Tiếp theo, bạn sẽ thấy +một trang xác nhận cho thấy các mã đã được tạo thành công. Nếu đã tồn tại file, bạn cũng sẽ thấy một thông báo rằng nó đã được ghi đè bằng các mã mới được tạo ra. + + +Sinh các mã nguồn CRUD +-------------------- + +CRUD là chuẩn cho việc Tạo mới (Create), Xem (Read), Cập nhật (Update), và Xóa (Delete), representing the four common tasks taken with data on most Web sites. To create CRUD functionality using Gii, select the "CRUD Generator" (bằng việc nhấn vào). Ví dụ với bảng "country", điền các thông tin vào form như sau: + +* Lớp Model: `app\models\Country` +* Lớp Search Model: `app\models\CountrySearch` +* Lớp Controller: `app\controllers\CountryController` + +![CRUD Generator](images/start-gii-crud.png) + +Tiếp đến, chọn vào nút "Preview". Bạn sẽ thấy một danh sách các tập tin được tạo ra, như hình dưới. + +![Xem CRUD Generator](images/start-gii-crud-preview.png) + +Nếu bạn đã tạo các file `controllers/CountryController.php` và +`views/country/index.php` trước đó (trong phần hướng dẫn về CSDL), kiểm tra nút "overwrite" để thay thế file đó. (Các phiên bản trước không hỗ trợ để sinh CRUD.) + + +Xem kết quả +------------- + +Xem kết quả, dùng trình duyệt truy cập vào đường dẫn sau: + +``` +http://hostname/index.php?r=country/index +``` + +Bạn sẽ thấy dữ liệu bảng được hiển thị chứa các thông tin trong CSDL country. Bạn có thể sắp xếp các bảng, +hoặc lọc nội dụng bằng việc nhập các điều kiện cần lọc ở phần đầu bảng. + +Mỗi dữ liệu country được hiển thị trên bảng, bạn có thể chọn để xem chi tiết, cập nhật, hoặc xóa. +Bạn cũng có thể "tạo mới Country", click vào button ở phần trên cùng của bảng. + +![Bảng Countries](images/start-gii-country-grid.png) + +![Cập nhật Country](images/start-gii-country-update.png) + +Danh sách các file sau được sinh bởi Gii, bạn có thể dùng trong ứng dụng, hoặc chỉnh sửa chúng: + +* Controller: `controllers/CountryController.php` +* Models: `models/Country.php` and `models/CountrySearch.php` +* Views: `views/country/*.php` + +> Thông tin: Công cụ Gii được xây dụng lên với việc phát triển ứng dụng nhanh và dễ mở rộng. Sử dụng nó một cách thích hợp +rất có thể đẩy nhanh tốc độ phát triển ứng dụng của bạn. Biết thêm thông tin, xem thêm ở phần [Gii](tool-gii.md). + + +Tổng kết +------- + +Ở phần này, bạn đã học được cách sử dụng Gii để tạo ra mã thực hiện hoàn chỉnh +chức năng CRUD cho nội dung được lưu trữ trong một bảng cơ sở dữ liệu. diff --git a/docs/guide-vi/start-hello.md b/docs/guide-vi/start-hello.md new file mode 100644 index 0000000000..eb44e55cf4 --- /dev/null +++ b/docs/guide-vi/start-hello.md @@ -0,0 +1,139 @@ +Bắt đầu ứng dụng với lời chào Hello +============ + +Phần này sẽ mô tả làm thế nào để tạo ra một trang Web mới trong ứng dụng của bạn cùng với lời chào "Hello". +Để đạt được mục tiêu này. bạn sẽ cần tạo mới một [action](structure-controllers.md#creating-actions) và +một [view](structure-views.md): + +* Ứng dụng sẽ gửi đi các request từ trang Web để tới các action +* và action sẽ tạo mới View để hiển thị lời chào "Hello" tới user. + +Thông qua bài hướng dẫn này, bạn sẽ nắm vững ba điều: + +1. Làm thế nào để tạo ra một [action](structure-controllers.md) để đáp ứng các requests, +2. Làm thế nào để tạo ra [view](structure-views.md) để xây dựng nội dung các thông điệp, và +3. Cách ứng dụng gửi đi các request tới các [actions](structure-controllers.md#creating-actions). + + +Tạo Action +------------------ + +Với nhiệm vụ tạo ra thông điệp "Hello", bạn sẽ tạo một [action](structure-controllers.md#creating-actions) `say`, action này +sẽ lấy các tham số `message` từ request và hiển thị thông điệp trở lại user. Nếu request không cung cấp tham số `message`, +action sẽ mặc định hiển thị thông điệp "Hello". + +> Lưu ý: [Hành động (Actions)](structure-controllers.md#creating-actions) là người dùng cuối có thể truy cập các đối tượng và thực hiện trực tiếp. + Các Actions được nằm trong [bộ điều khiển (controllers)](structure-controllers.md). + Các kết quả của một action là người sử dụng cuối cùng nhận được các thông điệp. + +Các Actions cần phải được khai báo ở [controllers](structure-controllers.md). Để cho đơn giản, bạn có thể khai báo +action `say` ở controller `SiteController`. Controller này được khai báo ở trong +lớp `controllers/SiteController.php`. Action mới cần tạo nằm ở đoạn code sau: + +```php +render('say', ['message' => $message]); + } +} +``` + +Trong đoạn code trên, action `say` đinh nghĩa phương thức có tên là `actionSay` nằm trong lớp `SiteController`. +Yii sử dụng tiền tố `action` để phân biệt các phương thức thuộc action từ các phương thức không phải là action trong một lớp điều khiển. +Tên nằm sau `action` là tiền tố ánh xạ tới các action's ID. + +Để hiểu được quy tắc đặt tên cho actions, Bạn nên hiểu cách hoạt động Yii xử lý với các action IDs. Mỗi Action IDs luôn luôn là những ký tự +thường. Nếu action ID đòi hỏi nhiều từ, chúng ta sẽ nối những từ đó bằng dấu gạch ngang (ví dụ, `create-comment`). Tên phương thức của action +sẽ được ánh xa tới action IDs bởi loại bỏ bất kỳ dấu gạch ngang từ IDs, dấu gạch ngang được thêm vào từ chữ cái in hoa đầu tiên trong mỗi từ, và từ đứng trước `action`. Ví dụ, +với action ID `create-comment` tương ứng tới action có phương thức tên là `actionCreateComment`. + +Trong ví dụ này, phương thức của action nhận tham số `$message`, mặc đinh giá trị là `"Hello"` (Như việc bạn có thể thiết +lập các giá trị mặc định cho bất kỳ tham số cho các hàm hoặc phương thức trong PHP). Mỗi khi ứng dụng +nhận request và xác đinh là action chịu trách nhiệm cho xử lý các yêu cầu là action `say` , ứng dung +sẽ lưu trữ tham số này cùng với tên tham số được tìm thấy trong request. Nói cách khác, nếu request bao gồm +tham số `message` theo cùng với giá trị `"Goodbye"`, biến `$message` tương ứng trong action sẽ được gán giá trị. + +Phương thức [[yii\web\Controller::render()|render()]] nằm trong mỗi action được gọi để trả về một [view](structure-views.md) +có tên là `say`. Tham số `message` luôn luôn được gửi qua view để xem nó có được dùng hay không. Kết quả việc render được +thực hiện trong mỗi action. Ứng dụng sẽ nhận kết quả này và hiển thị tới user trên trình duyệt (như là một trang HTML đầy đủ). + + +Tạo mới View +--------------- + +[Views](structure-views.md) đảm nhận việc hiển thị thông tin và tương tác với người dùng. Để thực hiện yêu câu hiển thị +lời chào "Hello", bạn cần phải tạo một view `say` có chức năng hiển thị tham số `message`, tham số này được nhận từ action gửi đến: + +```php + + +``` + +Bạn cần lưu trữ view `say` nằm ở đường dẫn `views/site/say.php`. Mỗi khi phương thức [[yii\web\Controller::render()|render()]] +được gọi ở action, nó sẽ tìm kiếm tập tin PHP nằm ở đường dẫn `views/ControllerID/ViewName.php`. + +Lưu ý rằng, đoạn code trên, biến `message` đã được phương thức [[yii\helpers\Html::encode()|HTML-encoded]] +mã hóa trước khi được in ra. Việc mã hóa là cần thiết khi gửi các tham số tới user, các tham số này có thể bị tấn công qua +[XSS (cross-site scripting)](http://en.wikipedia.org/wiki/Cross-site_scripting) đây là kỹ thuật tấn công bằng cách chèn chèn các +thẻ HTML hoặc đoạn mã JavaScript độc hại. + +Tất nhiên, bạn có thể thêm các nội dung ở view `say`.Nội dung bao gồm các thẻ HTML, dữ liệu văn bản, và cũng có thể là các câu lệnh PHP. +Trên thực tế, view `say` chỉ là các đoạn mã PHP được thực thi bởi phương thức [[yii\web\Controller::render()|render()]]. +Nội dung được gửi ra từ view sẽ được gửi tới ứng dụng (application) như những phản hồi kết quả. +Sau đó ứng dụng sẽ gửi kết quả tới user. + + +Trying it Out +------------- + +Sau khi đã tạo action và view, bạn có thể truy cập vào trang bởi việc truy cập vào URL sau: + +``` +http://hostname/index.php?r=site/say&message=Hello+World +``` + +![Hello World](images/start-hello-world.png) + +URL này sẽ trả về một trang và hiển thị lời chào "Hello World". Trang này có cùng phần header và footer như những trang khác trong ứng dụng. + +Nếu bạn không nhập tham số `message` vào URL, bạn chỉ xem thấy mỗi dòng "Hello" được hiển thị. Bởi vì tham số `message` được thông qua phương thức `actionSay()`, và mỗi khi tham số này không được nhập, +thì giá trị mặc đinh `"Hello"` sẽ được thay thế. + +> Lưu ý: Trang này có cùng phần header và footer như những trang khác là bởi vì phương thức [[yii\web\Controller::render()|render()]] + sẽ tự động nhúng nội dung của view `say` vào một [layout](structure-views.md#layouts) layout này nằm ở `views/layouts/main.php`. + +Tham số `r` ở trên URL sẽ được giải thích thêm. Nó là chuẩn cho bộ định tuyến [route](runtime-routing.md), mỗi ứng dụng sẽ cung cấp ID +tương ứng với từng action. Với các đinh dạng router `ControllerID/ActionID`. Khi ứng dụng nhận request, ứng dụng sẽ kiểm tra các tham số +theo cùng request đó, sử dụng `ControllerID` để xác định lớp điều khiển để xử các request. Sau đó, bộ điều khiển sẽ +xác dịnh `ActionID` cần được khởi tạo để xử lý công việc. Trong ví dụ này, route `site/say` +sẽ gán (ám chỉ tới) bộ điều khiển `SiteController` và action `say`. Điều này sẽ có kết quả là, phương thức `SiteController::actionSay()` sẽ được gọi để xử lý các request. + +> Lưu ý: Giống như actions, ứng dụng sử dụng các định danh ID để nhận diện các controller. Các Controller ID + có quy tắc đặt tên giống với các action IDs. Tên của controller được chuyển đổi từ các controller IDs + bằng việc loại bỏ dấu gạch ngang từ đinh danh ID, tận dụng các chữ cái đầu tiên trong mỗi từ, + và từ đứng trước `Controller`. Ví dụ, bộ điều khiển controller ID có tên là `post-comment` sẽ tương ứng + với controller là `PostCommentController`. + + +Tổng kết +------- + +Qua phần này, bạn đã thao tác với phần controller và view nằm trong mẫu thiết kế MVC. +Bạn đã tạo một action thuộc phần của controller để xử lý các request . Và bạn cũng đã tạo được view cho việc +hoàn thành nội dung trong thông điệp trả về . Trong ví dụ đơn giản này, không có model được sử dụng để thao tác dữ liệu mà chỉ sử dụng tham số `message`. + +Bạn cũng đã học được router trong Yii, cái mà có vai trò quan trọng trong việc thiết lập kết nối giữa user và các controller actions. + +Trong phần tiếp , bạn sẽ tìm hiểu cách tạo một model, và thêm mới các trang có chứa HTML form. diff --git a/docs/guide-vi/start-installation.md b/docs/guide-vi/start-installation.md new file mode 100644 index 0000000000..a641fc0a79 --- /dev/null +++ b/docs/guide-vi/start-installation.md @@ -0,0 +1,213 @@ +Cài đặt Yii +============== + +Bạn có thể cài đặt Yii theo hai cách, dùng trình quản lý gói [Composer](http://getcomposer.org/) hoặc tải về một tập tin lưu trữ. +Cách thứ nhất thường được hay dùng hơn, vì nó cho phép bạn cài đặt các [Gói mở rộng (extensions)](structure-extensions.md) mới hoặc cập nhật Yii đơn giản chỉ mới một câu lệnh. + +Ứng dụng Yii cần được tải về và cài đặt, điều này có kết quả giống nhau khi thực hiện cài đặt theo chuẩn. +Những ứng dụng Yii khi đã cài đặt đều được triển khai một số tính năng cơ bản, như đăng nhập (login), form liên hệ (contact form), vv. +Những tính năng trên đều được khuyến khích. Vì thế, nó có thể hữu ích như là một điểm bắt đầu tốt cho các dự án của bạn. + +Trong bài hướng dẫn này và các phần tiếp theo, chúng ta sẽ tìm hiều cách cài ứng dụng Yii với tên *Basic Application Template* và +làm thế nào để thực hiện các tính năng mới trên mẫu ứng dụng này. Yii đồng thời cũng cung cấp mẫu ứng dụng tên là [Advanced Application Template](tutorial-advanced-app.md) +mẫu này được dùng tốt hơn cho những đội dự án cần phát triển ứng dụng có nhiều tầng. + +> Lưu ý: Các mẫu ứng dụng *Basic Application Template* là thích hợp cho việc phát triển 90% của các ứng dụng Web. nó khác +với các mẫu ứng dụng [Advanced Application Template](tutorial-advanced-app.md). Nếu bạn là người mới tìm hiều về Yii, chúng tôi khuyến khích +bạn bắt đầu với các ứng dụng *Basic Application Template* , ứng dụng này đơn giản và ít chức năng thích hợp hơn cho việc tìm hiểu về Yii. + + + +Cài đặt qua trinh quản lý gói Composer +----------------------- + +Nếu bạn chưa cài Composer, bạn có thể cài đặt theo đường link sau +[getcomposer.org](https://getcomposer.org/download/). Trong hệ điều hành Linux và Mac OS X, bạn có thể chạy các lệnh sau đây: + + curl -s http://getcomposer.org/installer | php + mv composer.phar /usr/local/bin/composer + +Còn trên HDH Windows, bạn có thể tải về và chạy [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). + +Nếu bạn có bất kỳ thắc mắc hoặc muốn biết thêm nghiên cứu chuyên sâu Composer, vui lòng tham khảo [Tài liệu Composer](https://getcomposer.org/doc/) + +Nếu bạn đã cài Composer rồi, hãy chắc chắn rằng bạn đang sử dụng phiên bản mới nhất. Bạn có thể cập nhật Composer bằng cách thực hiện lệnh + `composer self-update`. + +Sau khi cài đặt Composer, bạn có thể cài đặt Yii bằng cách chạy lệnh sau ở thư mục Web mà ứng dụng cần chạy: + + composer global require "fxp/composer-asset-plugin:~1.1.1" + composer create-project --prefer-dist yiisoft/yii2-app-basic basic + +Câu lệnh đầu tiên sẽ cài đặt [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) +Nó là thông qua Composer để quản lý bower và npm dùng cho việc quản lý các gói cần thiết. Bạn cần chạy câu lệnh này để có hiệu lực toàn hệ thống. +Câu lệnh thứ hai sẽ cài đặt phiên bản Yii có tên là `basic`. Bạn có thể chọn một tên thư mục khác nếu bạn muốn. + +> Chú ý: Trong quá trình cài đặt Composer có thể yêu cầu thông tin đăng nhập từ tài khoản Github của bạn. điều này là bình thường bởi vì Composer +> cần đầy đủ thông tin API rate-limit để lấy các thông tin gói phụ thuộc từ Github. Để biết thêm chi tiết, +> xin vui lòng tham khảo [Composer documentation](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +> Thủ thuật: Nếu bạn muốn cài đặt phiên bản phát triển mới nhất của Yii, bạn có thể sử dụng lệnh sau để thay thế, +> điều này chỉ cần thêm [stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> +> Chú ý. phiên bản phát triển của Yii(dev version) không nên sử dụng cho mô trường ứng dụng bởi vì nó có thể phá vỡ các hoạt động trong code. + + +Cài đặt từ tập tin lưu trữ +------------------------------- + +Việc cài đặt Yii từ một tập tin lưu trữ bao gồm ba bước: + +1. Tải gói cài đặt từ [yiiframework.com](http://www.yiiframework.com/download/). +2. Giải nén file tải về vào một thư mục Web của ứng dụng cần chạy. +3. Sửa đồi file `config/web.php` bởi nhập thông tin secret key (khóa bí mật) `cookieValidationKey` ở mục cấu hình + (này được thực hiện tự động nếu bạn đang cài đặt Yii sử dụng Composer): + + ```php + // !!! chèn một khóa bí mật trong phần sau (nếu rỗng) - điều này được yêu cầu cho cookie validation + 'cookieValidationKey' => 'Nhập khóa bí mật vào đây', + ``` + + +Các thiết lập cài đặt khác +-------------------------- + +Yii giới thiệu hai phương pháp cài đặt ở trên, những phương pháp này sẽ tạo ứng dụng Web. +.Đối với các dự án nhỏ hoặc cho việc học để sử dụng, đây là một điểm khởi đầu tốt. + +Nhưng cũng có những phương pháp cài đặt khác: + +* Nếu bạn chỉ muốn cài đặt các khung cốt lõi và muốn xây dựng toàn bộ một ứng dụng từ đầu, + bạn có thể làm theo hướng dẫn như đã hướng dẫn ở bài viết [Building Application from Scratch](tutorial-start-from-scratch.md). +* Nếu bạn muốn bắt đầu với một ứng dụng phức tạp hơn, phù hợp hơn với môi trường phát triển trong team bạn, + bạn có thể xem xét việc cài đặt mẫu ứng dụng [Advanced Application Template](tutorial-advanced-app.md). + + +Kết quả cài đặt +-------------------------- + +Sau khi cài đặt, bạn có thể sử dụng trình duyệt để truy cập các ứng dụng Yii được cài đặt với các URL sau đây: + +``` +http://localhost/basic/web/index.php +``` + +URL này giả sử bạn đã cài đặt Yii trong một thư mục có tên `basic`, trực tiếp dưới thư mục gốc tài liệu máy chủ Web của bạn, +và rằng các máy chủ Web đang chạy trên máy tính cục bộ của bạn (`localhost`). Bạn có thể cần phải điều chỉnh nó trong môi trường cài đặt. + +![Successful Installation of Yii](images/start-app-installed.png) + +Bạn sẽ có thể thấy trang hiển thị "Congratulations!" ở trình duyệt của ban. Còn không, xin vui lòng kiểm tra xem PHP đáp ứng cài đặt của bạn +Các yêu cầu Yii. Bạn có thể kiểm tra xem các yêu cầu tối thiểu được đáp ứng bằng một trong những phương pháp sau đây: + +* Sử dụng trình duyệt để truy cập vào URL `http://localhost/basic/requirements.php` +* Chay câu lệnh như sau: + + ``` + cd basic + php requirements.php + ``` + +Bạn nên cấu hình cài đặt PHP của bạn để nó đáp ứng các yêu cầu tối thiểu của Yii. Diều quan trọng nhất, bạn nên có PHP 5.4 hoặc hơn. Bạn cũng nên cài đặt +các gói [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php) và một trình điều khiển cơ sở dữ liệu tương ứng +(như là `pdo_mysql` cho CSDL MySQL), nếu ứng dụng của bạn cần thao tác với CSLD. + + +Cấu hình máy chủ Web +----------------------- + +> Lưu ý: Lưu ý: Nếu bạn chỉ là chạy thử ứng dụng Yii thay vì được triển khai(deploying) trong một môi trường sản xuất, + bạn có thể bỏ qua phần này. + +Các ứng dụng được cài đặt theo phương pháp trên, được chạy trong Windows, Max OS X, Linux hoặc máy chủ [Apache HTTP](http://httpd.apache.org/) +hoặc [Nginx HTTP server](http://nginx.org/) và PHP phiên bản 5.4 hoặc cao hơn đều có thể được chạy trực tiếp. Yii 2.0 cũng tương thích với HHVM, +do [HHVM](http://hhvm.com/)của Facebook và PHP tiêu chuẩn trên các khía cạnh trong một vài nơi một với trường hợp hơi khác nhau, +khi sử dụng HHVM đòi hỏi ít thay đổi. + +Trong môi trường máy chủ sản xuất, bạn có thể cấu hình máy chủ để ứng dụng có thể truy cập thông qua URL http://www.example.com/index.php +thay vì http://www.example.com/basic/web/index.php. Cấu hình này đòi hỏi các thư mục gốc tài liệu của máy chủ Web vào thư mục basic/web. Bạn cũng có thể ẩn index.php trên URL, +chi tiết trên URL phân tích và tạo ra một chương trình chiếu, bạn sẽ tìm hiểu làm thế nào để cấu hình Apache hoặc Nginx máy chủ để đạt được những mục tiêu này. + +> Lưu ý: Thiết lập `basic/web` như thư mục gốc, bạn có thể ngăn chặn người dùng truy cập vào các dữ liệu cá nhân và các thông tin nhạy cảm được lưu trữ + ở các thư mục con nằm trong `basic/web`. Từ chối truy cập vào các thư mục khác là một cải tiến bảo mật. + +> Lưu ý: Bạn nên điều chính cấu trúc ứng dụng của bạn để bảo mật tốt hơn, điều này cần thiếu nếu khi ứng dụng của ban chạy trên những hosting miễn phí, ở môi trường mà bạn +không có quyền thay đổi các thiết lập ở server Web. Tham khảo thêm ở phần sau để biết thêm chi tiết [Shared Hosting Environment](tutorial-shared-hosting.md). + + +### Các khuyến nghị khi cấu hình máy chủ Apache + +Sử dụng các cấu hình sau đây trong file `httpd.conf` của Apache hoặc trong một cấu hình máy chủ ảo. Lưu ý rằng bạn nên +thay thế đường dẫn đường dẫn thực tế `path/to/basic/web` cho `basic/web`. + +``` +# Thiết lập document root tới đường dẫn "basic/web" +DocumentRoot "path/to/basic/web" + + + # use mod_rewrite for pretty URL support + RewriteEngine on + # If a directory or a file exists, use the request directly + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Otherwise forward the request to index.php + RewriteRule . index.php + + # ...other settings... + +``` + + +### Các khuyến nghị khi cấu hình Nginx + +Để sử dụng [Nginx](http://wiki.nginx.org/), bạn cần phải cài đặt [FPM SAPI](http://php.net/install.fpm). +Bạn có thể cấu hình Nginx như sau, thay thế đường dẫn `path/to/basic/web` với đường dẫn thực tế ở +`basic/web` và `mysite.local` thay thế bằng tên máy chủ thực tế để cung cấp dịch vụ. + +``` +server { + charset utf-8; + client_max_body_size 128M; + + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 + + server_name mysite.local; + root /path/to/basic/web; + index index.php; + + access_log /path/to/basic/log/access.log; + error_log /path/to/basic/log/error.log; + + location / { + # Redirect everything that isn't a real file to index.php + try_files $uri $uri/ /index.php?$args; + } + + # uncomment to avoid processing of calls to non-existing static files by Yii + #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + # try_files $uri =404; + #} + #error_page 404 /404.html; + + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; + #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; + } + + location ~ /\.(ht|svn|git) { + deny all; + } +} +``` + +Khi sử dụng cấu hình này, bạn cũng nên thiết lập `cgi.fix_pathinfo=0` ở file `php.ini` +để tránh nhiều hệ thống không cần thiết `stat()` khi gọi hệ thống. + +Cũng lưu ý rằng khi bạn chạy một máy chủ HTTPS, bạn cần phải thêm dòng `fastcgi_param HTTPS on;` vào file cấu hình +để Yii có thể hiểu ra những kết nối là an toàn. diff --git a/docs/guide-vi/start-looking-ahead.md b/docs/guide-vi/start-looking-ahead.md new file mode 100644 index 0000000000..1c0a86c8f5 --- /dev/null +++ b/docs/guide-vi/start-looking-ahead.md @@ -0,0 +1,32 @@ +Looking Ahead +============= + +Nếu bạn đã đọc và thực hành các bài viết trong chuyên mục "Getting Started", thì bạn có thể xây dựng ứng dụng Yii hoàn chỉnh. Trong những phần trước, bạn đã được tìm hiều để thực hiện một số chức năng cơ bản +, như lấy thông tin từ user qua form, thu thập thông tin từ CSDL, và hiển thị dữ liệu cùng với phân trang +. Bạn cũng được tìm hiểu sử dụng [Gii](tool-gii.md) đểsinh code tự động. Sử dụng Gii thật đơn giản chỉ việc điển các thông tin vào các form và gii sẽ sinh code tự động, và giảm tải số lượng lơn +các chức năng tương tự. + +Trong phần này sẽ tổng hợp những tài nguyên về tìm hiểu Yii framework. + +* Tài liệu tham khảo + - [Giới thiệu tổng quan](http://www.yiiframework.com/doc-2.0/guide-README.html): + Như tiêu đề, các hướng dẫn sẽ cung cấp tổng quan về sử dụng Yii và cách thức Yii hoạt động + . Đây là các hướng dẫn quan trọng để bắt đầu với Yii, vì vậy bạn cần tìm hiểu trước khi đi sâu và viết code về Yii. + - [Thông tin chi tiết Class](http://www.yiiframework.com/doc-2.0/index.html): + Phần này sẽ mô tả và cách dùng các class được cung cấp bởi Yii. Phần này là quan trọng khi bạn viết code và muốn hiểu về cách dùng từng phần trong lớp, phương thức, + các thuộc tính. Tham khảo cách dùng các lớp sẽ giúp bạn hiểu sâu về từng phần trong framework. + - [Các bài hướng dẫn khác](http://www.yiiframework.com/wiki/?tag=yii2): + Các bài hướng dẫn được viết bởi những người có kinh nghiệm về lập trình Yii. Hầu hết trong số đó, được viết theo những kinh nghiệm lập trình lâu năm. + , và đưa ra các giải pháp về từng phần, vấn đề khi lập trình Yii. Các bài viết có thể không chi tiết và dễ hiểu như phần giới thiệu, tuy nhiên các bài viết có + những chủ đề rộng hơn và thường có những ví dụ và giải pháp với các vấn đề lập trình. + - [Sách](http://www.yiiframework.com/doc/) +* [Extensions](http://www.yiiframework.com/extensions/): + Yii tự hào có một thư viện của hàng ngàn các phần mở rộng với sự đóng góp lớn của cộng động, bạn có thể dễ dàng tích hợp vào các ứng dụng của bạn, do đó làm cho phát triển ứng dụng của bạn nhanh hơn và dễ dàng hơn. +* Cộng dồng + - Diễn đàn: + - IRC chat: The #yii channel on the freenode network () + - GitHub: + - Facebook: + - Twitter: + - LinkedIn: + - Stackoverflow: diff --git a/docs/guide-vi/start-workflow.md b/docs/guide-vi/start-workflow.md new file mode 100644 index 0000000000..7768b06d71 --- /dev/null +++ b/docs/guide-vi/start-workflow.md @@ -0,0 +1,97 @@ +Chạy ứng dụng +==================== + +Sau khi cài đặt Yii, ứng dụng Yii của bạn đã được chạy, tùy thuộc vào cấu hình bạn có thể truy cập qua URL `http://hostname/basic/web/index.php` +hoặc `http://hostname/index.php`. Bài hướng dẫn này sẽ mô tả chức năng của ứng dụng và cách tổ chức code trong ứng dụng, +và làm thế nào để xử lý các yêu cầu của ứng dụng. + +> Lưu ý: Để đơn giản, xuyên suốt các bài hướng dẫn "Getting Started" này, giả sử rằng chúng ta đã thiết lập `basic/web` + như thư mục gốc trong máy chủ Web, và cấu hình URL dể truy cập vào ứng dụng của ban thành + `http://hostname/index.php` hoặc điều tương tự. Tùy theo yêu cầu của bạn, bạn hãy điều chình + URLs sao cho phù hợp với ứng dụng. + + +Chức năng +------------- + +Mẫu ứng dụng *Basic Application* bao gồm 4 trang cơ bản: + +* Trang chủ (homepage), được hiển thị khi bạn truy cập vào URL `http://hostname/index.php`, +* Trang "About", +* Trang "Contact", trang hiển thị form contact cho phép user liên hệ với bạn qua email, +* Trang "Login", trang hiển thị form login cho phép bạn có thể xác thực user. Hãy thử đăng nhập với + thông tin "admin/admin", và bạn sẽ thấy tên "Login" ở menu chính thay đổi thành "Logout". + +Những trang trên đều có cùng phần header và footer. Phần header chứa main menu bar cho phép điều hướng giữa các trang. + +Ở dưới cùng của trình duyệt, bạn có thể thấy một thanh công cụ. Đây là một công cụ gỡ lỗi [debugger tool](tool-debugger.md) rất hữu ích +được cung cấp bởi Yii, bạn có thể ghi lại và hiển thị một lượng lớn các thông tin gỡ lỗi, +chẳng hạn như thông tin đăng nhập, tình trạng phản ứng, các câu lệnh truy vấn cơ sở dữ liệu, và như vậy. + +Ngoài ra, các ứng dụng web có các script từ dòng lệnh (console) tên là *yii*, được nằm ở ứng dụng cơ sở. +Những script này có thể dùng chạy nền và bảo trì chức năng ứng dụng, thông tin mô tả thêm [Console Application Section](tutorial-console.md). + + +Cấu trúc ứng dụng (Application Structure) +--------------------- + +Những thư mục và tập tin quan trọng nhất của ứng dụng (giả sử thư mục gốc của ứng dụng tên là `basic`): + +``` +basic/ Thư mục gốc ứng dụng + composer.json Tập tin cấu hình Composer, mô tả thông tin gói + config/ Chứa các cấu hình ứng dụng và cấu hình khác + console.php thông tin cấu hình ứng dụng giao diện console + web.php thông tin cấu hình ứng dụng Web + commands/ chứa các lớp lệnh console + controllers/ Chứa các lớp điều khiển (controller) + models/ Chứa các lớp model + runtime/ chứa các file được sinh ra bởi Yii trong quá trình chạy, chẳng hạn như đăng nhập và file của bộ nhớ cache + vendor/ chứa các gói cài đặt Composer Package, bao gồm cả Yii framework + views/ chứa các file về view + web/ Thư mục gốc ứng dụng Web, chứa các file truy cập Web + assets/ chứa tập tin tài nguyên Yii (javascript và css) + index.php tập tin thực thi ứng dụng (hoặc bootstrap) + yii Giao diện điều khiển lệnh script (Yii console) +``` + +Nói chung, những tập tin trong ứng dụng có thể chia thành hai loại: tập tin nằm trong thư mục `basic/web` và những tập +tin nằm ở thư mục khác. Trước dây có thể truy cập trực tiếp qua HTTP (chẳng hạn như một trình duyệt), tuy nhiên sau này không thể truy cập và không nên có. + +Yii thực thi theo mẫu thiết kế [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller), +Điều này được phản ánh trong cấu trúc đường dẫn ở trên. Thư mục `models` chứa tất cả [lớp dữ liệu (model)](structure-models.md), +còn thư mục `views` sẽ chứa tất cả [view scripts](structure-views.md), và thư mục `controllers` chứa tất cả +[lớp điều khiển (controller classes)](structure-controllers.md). + +Biểu đồ sau đây cho thấy cấu trúc tĩnh của một ứng dụng: + +![Static Structure of Application](images/application-structure.png) + +Mỗi ứng dụng sẽ có một mục đầu vào (Entry Script) `web/index.php` như vậy việc truy cập vào ứng dụng Web chỉ được phép truy cập qua mục này. +Entry script tiếp nhận các request và tạo mới [ứng dụng (application)](structure-applications.md) để xử lý. +Các [ứng dụng](structure-applications.md) giải quyết các request cùng với các [thành phần (components)](concept-components.md), +và gửi các request tới các phần tử trong mô hình MVC. Các [Widgets](structure-widgets.md) sẽ được sử dụng ở [views](structure-views.md) +để đơn giản hơn việc xây dựng các giao diện phức tạp. + + +Chu trình xứ lý yêu cầu +----------------- + +Biểu đồ dưới đây cho thấy làm thế nào một ứng dụng để xử lý các yêu cầu: + +![Request Lifecycle](images/request-lifecycle.png) + +1. User tạo yêu cầu (request) tới [mục script](structure-entry-scripts.md) `web/index.php`. +2. Entry script tải các [cấu hình (configuration)](concept-configurations.md) ứng dụng và tạo mới + [ứng dụng](structure-applications.md) để khởi tạo để xử lý yêu cầu. +3. Ứng dụng lấy thông tin [route](runtime-routing.md) được yêu cầu cùng với những thành phần (component) + cần xử lý các [request](runtime-requests.md). +4. Ứng dụng tạo mới [controller](structure-controllers.md) khởi tạo để xử lý yêu cầu. +5. Bộ điều khiển (controller) tạo mới các [action (hành động)](structure-controllers.md) khởi tạo và thực hiện các bộ lọc cho các hành động. +6. Nếu bất kỳ bộ lọc nào bị lỗi, action sẽ bị hủy. +7. Nếu bất kỳ bộ đạt, action sẽ được thực thi. +8. Action sẽ tải dữ liệu từ data model, có thể từ CSDL. +9. Action sẽ tạo mới View, đồng thời cung cấp dữ liệu cho nó . +10. Kết quả việc tạo mới view sẽ trả vê một thành phần ứng dụng [response](runtime-responses.md) . +11. Thành phần response gửi kết quả đến trình duyệt của người dùng và hiển thị kết quả. + diff --git a/docs/guide-vi/structure-overview.md b/docs/guide-vi/structure-overview.md new file mode 100644 index 0000000000..5e1f52f9ff --- /dev/null +++ b/docs/guide-vi/structure-overview.md @@ -0,0 +1,26 @@ +Tổng quan về kiến trúc ứng dụng +======== + +Các ứng dụng Yii được tổ chức dựa theo mẫu thiết kế [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) +. [Models](structure-models.md) chứa nghiệp vụ logic, truy xuất database và định nghĩa các quy tắc xác thực dữ liệu; [views](structure-views.md) +đảm nhận việc hiển thị thôn tin của model; và [controllers](structure-controllers.md) có nhiệm vụ điều hướng các yêu cầu và chuyển các tương tác giữa +[models](structure-models.md) và [views](structure-views.md). + +Ngoài mô hình MVC, ứng dụng Yii có những phần phần sau đây: + +* [entry scripts](structure-entry-scripts.md): là file đầu tiên chứa các mã nguồn để tiếp nhận các request của người dùng. + Thành phần này có trách nhiệm bắt đầu về chu trình xử lý các yêu cầu trong ứng dụng. +* [ứng dụng](structure-applications.md): là đối tượng có phạm vi truy cập toàn cục giúp quản lý các thành phần trong ứng dụng + và điều hướng chúng để thực hiện các yêu cầu. +* [thành phần](structure-application-components.md): là đối tượng được đăng ký với ứng dụng + và cung cấp những dịch vụ cho các yêu cầu xử lý . +* [modules](structure-modules.md): là những gói có chứa mô hình MVC hoàn chỉnh. + Một ứng dụng có thể được tổ chức dưới dạng nhiều module. +* [filters](structure-filters.md): chứa những mã nguồn cần được gọi trước và sau việc xử lý của từng yêu cầu của bộ điều khiển + handling of each request by controllers. +* [widgets](structure-widgets.md): các đối tượng được nhúng vào [views](structure-views.md). Các widget có thể chứa các nghiệp vụ logic + và có thể tái sử dụng ở những view khác. + +Mô hình sau mô tả cấu trúc ứng dụng ở dạng tĩnh: + +![Static Structure of Application](images/application-structure.png) diff --git a/docs/guide-zh-CN/README.md b/docs/guide-zh-CN/README.md index d40e6028b7..e77608b927 100644 --- a/docs/guide-zh-CN/README.md +++ b/docs/guide-zh-CN/README.md @@ -91,7 +91,7 @@ Yii 2.0 权威指南 显示数据 -------- -* **编撰中** [格式化输出数据](output-formatter.md) +* **编撰中** [格式化输出数据](output-formatting.md) * **编撰中** [分页(Pagination)](output-pagination.md) * **编撰中** [排序(Sorting)](output-sorting.md) * **编撰中** [数据提供器](output-data-providers.md) diff --git a/docs/guide-zh-CN/blocktypes.json b/docs/guide-zh-CN/blocktypes.json new file mode 100644 index 0000000000..67d0c6e51d --- /dev/null +++ b/docs/guide-zh-CN/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "警告:", + "Note:": "注意:", + "Info:": "补充:", + "Tip:": "提示:" +} \ No newline at end of file diff --git a/docs/guide-zh-CN/caching-http.md b/docs/guide-zh-CN/caching-http.md index a27032fbfa..2284197fd1 100644 --- a/docs/guide-zh-CN/caching-http.md +++ b/docs/guide-zh-CN/caching-http.md @@ -86,7 +86,11 @@ ETag 相比 `Last-Modified` 能实现更复杂和更精确的缓存策略。例 复杂的 Etag 生成种子可能会违背使用 `HttpCache` 的初衷而引起不必要的性能开销,因为响应每一次请求都需要重新计算 Etag。请试着找出一个最简单的表达式去触发 Etag 失效。 +<<<<<<< .merge_file_a06996 > 注意:为了遵循 [RFC 7232(HTTP 1.1 协议)](http://tools.ietf.org/html/rfc7232#section-2.4),如果同时配置了 `ETag` 和 `Last-Modified` 头,`HttpCache` 将会同时发送它们。并且如果客户端同时发送 `If-None-Match` 头和 `If-Modified-Since` 头,则只有前者会被接受。 +======= +> Note: 为了遵循 [RFC 7232(HTTP 1.1 协议)](http://tools.ietf.org/html/rfc7232#section-2.4),如果同时配置了 `ETag` 和 `Last-Modified` 头,`HttpCache` 将会同时发送它们。并且如果客户端同时发送 `If-None-Match` 头和 `If-Modified-Since` 头,则只有前者会被接受。 +>>>>>>> .merge_file_a01948 diff --git a/docs/guide-zh-CN/caching-overview.md b/docs/guide-zh-CN/caching-overview.md index 26f6c581a6..b9345cd1d5 100644 --- a/docs/guide-zh-CN/caching-overview.md +++ b/docs/guide-zh-CN/caching-overview.md @@ -5,7 +5,11 @@ 缓存可以应用在 Web 应用程序的任何层级任何位置。在服务器端,在较的低层面,缓存可能用于存储基础数据,例如从数据库中取出的最新文章列表;在较高的层面,缓存可能用于存储一段或整个 Web 页面,例如最新文章的渲染结果。在客户端,HTTP 缓存可能用于将最近访问的页面内容存储到浏览器缓存中。 +<<<<<<< .merge_file_a06272 Yii 支持如上所有缓存机制: +======= +Yii 支持如下所有缓存机制: +>>>>>>> .merge_file_a04468 * [数据缓存](caching-data.md) * [片段缓存](caching-fragment.md) diff --git a/docs/guide-zh-CN/concept-aliases.md b/docs/guide-zh-CN/concept-aliases.md index 658b7227cd..f2f05ba7c1 100644 --- a/docs/guide-zh-CN/concept-aliases.md +++ b/docs/guide-zh-CN/concept-aliases.md @@ -17,7 +17,11 @@ Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@bar', 'http://www.example.com'); ``` +<<<<<<< .merge_file_a06852 > 注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。 +======= +> Note: 别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。 +>>>>>>> .merge_file_a04208 可以通过在一个别名后面加斜杠 `/` 和一至多个路径分段生成新别名(无需调用 [[Yii::setAlias()]])。我们把通过 [[Yii::setAlias()]] 定义的别名称为**根别名**,而用他们衍生出去的别名成为**衍生别名**。例如,`@foo` 就是根别名,而 `@foo/bar/file.php` 是一个衍生别名。 @@ -53,7 +57,11 @@ echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path/to/foo/bar/file.php 由衍生别名所解析出的文件路径和 URL 是通过替换掉衍生别名中的根别名部分得到的。 +<<<<<<< .merge_file_a06852 > 注意:[[Yii::getAlias()]] 并不检查结果路径/URL 所指向的资源是否真实存在。 +======= +> Note: [[Yii::getAlias()]] 并不检查结果路径/URL 所指向的资源是否真实存在。 +>>>>>>> .merge_file_a04208 根别名可能也会包含斜杠 `/`。[[Yii::getAlias()]] 足够智能到判断一个别名中的哪部分是根别名,因此能正确解析文件路径/URL。例如: diff --git a/docs/guide-zh-CN/concept-autoloading.md b/docs/guide-zh-CN/concept-autoloading.md index 4b9697a8e7..6d88e41a1b 100644 --- a/docs/guide-zh-CN/concept-autoloading.md +++ b/docs/guide-zh-CN/concept-autoloading.md @@ -3,7 +3,11 @@ Yii 依靠[类自动加载机制](http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)([中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。 +<<<<<<< .merge_file_a06860 > 注意:为了简化叙述,本篇文档中我们只会提及类的自动加载。不过,要记得文中的描述同样也适用于接口和Trait(特质)的自动加载哦。 +======= +> Note: 为了简化叙述,本篇文档中我们只会提及类的自动加载。不过,要记得文中的描述同样也适用于接口和Trait(特质)的自动加载哦。 +>>>>>>> .merge_file_a06468 使用 Yii 自动加载器 @@ -55,7 +59,11 @@ require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); 你也可以只使用 Composer 的自动加载,而不用 Yii 的自动加载。不过这样做的话,类的加载效率会下降,且你必须遵循 Composer 所设定的规则,从而让你的类满足可以被自动加载的要求。 +<<<<<<< .merge_file_a06860 > 补充:若你不想要使用 Yii 的自动加载器,你必须创建一个你自己版本的 `Yii.php` 文件,并把它包含进你的[入口脚本](structure-entry-scripts.md)里。 +======= +> Info: 若你不想要使用 Yii 的自动加载器,你必须创建一个你自己版本的 `Yii.php` 文件,并把它包含进你的[入口脚本](structure-entry-scripts.md)里。 +>>>>>>> .merge_file_a06468 自动加载扩展类 diff --git a/docs/guide-zh-CN/concept-behaviors.md b/docs/guide-zh-CN/concept-behaviors.md index 833aa688f3..5b4ed005a0 100644 --- a/docs/guide-zh-CN/concept-behaviors.md +++ b/docs/guide-zh-CN/concept-behaviors.md @@ -39,7 +39,11 @@ class MyBehavior extends Behavior 以上代码定义了行为类 `app\components\MyBehavior` 并为要附加行为的组件提供了两个属性 `prop1` 、 `prop2` 和一个方法 `foo()` 。注意属性 `prop2` 是通过 getter `getProp2()` 和 setter `setProp2()` 定义的。能这样用是因为 [[yii\base\Object]] 是 [[yii\base\Behavior]] 的祖先类,此祖先类支持用 getter 和 setter 方法定义[属性](basic-properties.md) +<<<<<<< .merge_file_a05016 > 提示:在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件。 +======= +> Tip: 在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件。 +>>>>>>> .merge_file_a00824 处理事件 diff --git a/docs/guide-zh-CN/concept-components.md b/docs/guide-zh-CN/concept-components.md index 6b6664df21..b9feaa3bc8 100644 --- a/docs/guide-zh-CN/concept-components.md +++ b/docs/guide-zh-CN/concept-components.md @@ -71,7 +71,11 @@ $component = \Yii::createObject([ ], [1, 2]); ``` +<<<<<<< .merge_file_a01348 > 补充:尽管调用 [[Yii::createObject()]] 的方法看起来更加复杂,但这主要因为它更加灵活强大,它是基于[依赖注入容器](concept-di-container.md)实现的。 +======= +> Info: 尽管调用 [[Yii::createObject()]] 的方法看起来更加复杂,但这主要因为它更加灵活强大,它是基于[依赖注入容器](concept-di-container.md)实现的。 +>>>>>>> .merge_file_a06700 [[yii\base\Object]] 类执行时的生命周期如下: diff --git a/docs/guide-zh-CN/concept-events.md b/docs/guide-zh-CN/concept-events.md index 1824fbdb15..3fa77c7419 100644 --- a/docs/guide-zh-CN/concept-events.md +++ b/docs/guide-zh-CN/concept-events.md @@ -68,7 +68,11 @@ $foo->on(Foo::EVENT_HELLO, function ($event) { 事件处理器顺序 ----------------- +<<<<<<< .merge_file_a01132 可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 `$event` 参数的 [yii\base\Event::handled]] 属性为真,如下: +======= +可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 `$event` 参数的 [[yii\base\Event::handled]] 属性为真,如下: +>>>>>>> .merge_file_a06216 ```php $foo->on(Foo::EVENT_HELLO, function ($event) { @@ -109,7 +113,11 @@ class Foo extends Component 以上代码当调用 `bar()` ,它将触发名为 `hello` 的事件。 +<<<<<<< .merge_file_a01132 > 提示:推荐使用类常量来表示事件名。上例中,常量 `EVENT_HELLO` 用来表示 `hello` 。这有两个好处。第一,它可以防止拼写错误并支持 IDE 的自动完成。第二,只要简单检查常量声明就能了解一个类支持哪些事件。 +======= +> Tip: 推荐使用类常量来表示事件名。上例中,常量 `EVENT_HELLO` 用来表示 `hello` 。这有两个好处。第一,它可以防止拼写错误并支持 IDE 的自动完成。第二,只要简单检查常量声明就能了解一个类支持哪些事件。 +>>>>>>> .merge_file_a06216 有时想要在触发事件时同时传递一些额外信息到事件处理器。例如,邮件程序要传递消息信息到 `messageSent` 事件的处理器以便处理器了解哪些消息被发送了。为此,可以提供一个事件对象作为 [[yii\base\Component::trigger()]] 方法的第二个参数。这个事件对象必须是 [[yii\base\Event]] 类或其子类的实例。如: @@ -204,7 +212,11 @@ Event::trigger(Foo::className(), Foo::EVENT_HELLO); 注意这种情况下 `$event->sender` 指向触发事件的类名而不是对象实例。 +<<<<<<< .merge_file_a01132 > 注意:因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 [[yii\base\Object]]。 +======= +> Note: 因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 [[yii\base\Object]]。 +>>>>>>> .merge_file_a06216 移除类级别的事件处理器只需调用[[yii\base\Event::off()]],如: diff --git a/docs/guide-zh-CN/concept-properties.md b/docs/guide-zh-CN/concept-properties.md index c0cba42c75..eab95c2898 100644 --- a/docs/guide-zh-CN/concept-properties.md +++ b/docs/guide-zh-CN/concept-properties.md @@ -11,7 +11,11 @@ $object->label = trim($label); 为解决该问题,Yii 引入了一个名为 [[yii\base\Object]] 的基类,它支持基于类内的 **getter** 和 **setter**(读取器和设定器)方法来定义属性。如果某类需要支持这个特性,只需要继承 [[yii\base\Object]] 或其子类即可。 +<<<<<<< .merge_file_a06884 > 补充:几乎每个 Yii 框架的核心类都继承自 [[yii\base\Object]] 或其子类。这意味着只要在核心类中见到 getter 或 setter 方法,就可以像调用属性一样调用它。 +======= +> Info: 几乎每个 Yii 框架的核心类都继承自 [[yii\base\Object]] 或其子类。这意味着只要在核心类中见到 getter 或 setter 方法,就可以像调用属性一样调用它。 +>>>>>>> .merge_file_a04208 getter 方法是名称以 `get` 开头的方法,而 setter 方法名以 `set` 开头。方法名中 `get` 或 `set` 后面的部分就定义了该属性的名字。如下面代码所示,getter 方法 `getLabel()` 和 setter 方法 `setLabel()` 操作的是 `label` 属性,: diff --git a/docs/guide-zh-CN/db-active-record.md b/docs/guide-zh-CN/db-active-record.md index 14a6e40253..dc5fe43541 100644 --- a/docs/guide-zh-CN/db-active-record.md +++ b/docs/guide-zh-CN/db-active-record.md @@ -1,7 +1,7 @@ Active Record ============= -> 注意:该章节还在开发中。 +> Note: 该章节还在开发中。 [Active Record](http://zh.wikipedia.org/wiki/Active_Record) (活动记录,以下简称AR)提供了一个面向对象的接口, 用以访问数据库中的数据。一个 AR 类关联一张数据表, @@ -178,7 +178,7 @@ $sql = 'SELECT * FROM customer'; $customers = Customer::findBySql($sql)->all(); ``` -> 小技巧:在上面的代码中,`Customer::STATUS_ACTIVE` 是一个在 `Customer` 类里定义的常量。(译注:这种常量的值一般都是tinyint)相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。 +> Tip: 在上面的代码中,`Customer::STATUS_ACTIVE` 是一个在 `Customer` 类里定义的常量。(译注:这种常量的值一般都是tinyint)相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。 有两个快捷方法:`findOne` 和 `findAll()` 用来返回一个或者一组`ActiveRecord`实例。前者返回第一个匹配到的实例,后者返回所有。 例如: @@ -417,7 +417,7 @@ SELECT * FROM customer WHERE id=1; SELECT * FROM order WHERE customer_id=1; ``` -> 提示:再次用表达式 `$customer->orders`将不会执行第二次 SQL 查询, +> Tip: 再次用表达式 `$customer->orders`将不会执行第二次 SQL 查询, SQL 查询只在该表达式第一次使用时执行。 数据库访问只返回缓存在内部前一次取回的结果集,如果你想查询新的 关联数据,先要注销现有结果集:`unset($customer->orders);`。 @@ -452,7 +452,7 @@ class Customer extends \yii\db\ActiveRecord $orders = $customer->getBigOrders(200)->all(); ``` ->注意:关联查询返回的是 [[yii\db\ActiveQuery]] 的实例,如果像特性(如类属性)那样连接关联数据, +> Note: 关联查询返回的是 [[yii\db\ActiveQuery]] 的实例,如果像特性(如类属性)那样连接关联数据, 返回的结果是关联查询的结果,即 [[yii\db\ActiveRecord]] 的实例, 或者是数组,或者是 null ,取决于关联关系的多样性。如,`$customer->getOrders()` 返回 `ActiveQuery` 实例,而 `$customer->orders` 返回`Order` 对象数组 @@ -646,7 +646,7 @@ if ($customers[0]->orders[0]->customer === $customers[0]) { } ``` ->注意:相对关系不能在包含中间表的关联关系中定义。 即是,如果你的关系是通过[[yii\db\ActiveQuery::via()|via()]] 或 [[yii\db\ActiveQuery::viaTable()|viaTable()]]方法定义的, 就不能调用[[yii\db\ActiveQuery::inverseOf()]]方法了。 +> Note: 相对关系不能在包含中间表的关联关系中定义。 即是,如果你的关系是通过[[yii\db\ActiveQuery::via()|via()]] 或 [[yii\db\ActiveQuery::viaTable()|viaTable()]]方法定义的, 就不能调用[[yii\db\ActiveQuery::inverseOf()]]方法了。 JOIN 类型关联查询 @@ -1013,6 +1013,7 @@ TODO 被污染属性 ------------------- +<<<<<<< .merge_file_a04472 <<<<<<< HEAD TODO @@ -1023,6 +1024,13 @@ TODO 如果你对最近一次修改前的属性值感兴趣,你可以调用[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] 或 [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]。 >>>>>>> yiichina/master +======= +当你调用[[yii\db\ActiveRecord::save()|save()]]用于保存活动记录(Active Record)实例时,只有被污染的属性才会被保存。一个属性是否认定为被污染取决于它的值自从最后一次从数据库加载或者最近一次保存到数据库后到现在是否被修改过。注意:无论活动记录(Active Record)是否有被污染属性,数据验证始终会执行。 + +活动记录(Active Record)会自动维护一个污染数据列表。它的工作方式是通过维护一个较旧属性值版本,并且将它们与最新的进行比较。你可以通过调用[[yii\db\ActiveRecord::getDirtyAttributes()]]来获取当前的污染属性。你也可以调用[[yii\db\ActiveRecord::markAttributeDirty()]]来显示的标记一个属性为污染属性。 + +如果你对最近一次修改前的属性值感兴趣,你可以调用[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] 或 [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]。 +>>>>>>> .merge_file_a07132 另见 ------------------- diff --git a/docs/guide-zh-CN/db-dao.md b/docs/guide-zh-CN/db-dao.md index dd8322856f..d5277a0b99 100644 --- a/docs/guide-zh-CN/db-dao.md +++ b/docs/guide-zh-CN/db-dao.md @@ -64,7 +64,7 @@ return [ ], ``` -注意:如果需要同时使用多个数据库可以定义 多个 连接组件: +注意:如果需要同时使用多个数据库可以定义多个连接组件: ```php return [ @@ -307,11 +307,11 @@ Yii 提供了以下常量作为常用的隔离级别 你可以使用以上常量或者使用一个string字符串命令,在对应数据库中执行该命令用以设置隔离级别,比如对于`postgres`有效的命令为`SERIALIZABLE READ ONLY DEFERRABLE`. ->注意:某些数据库只能针对连接来设置事务隔离级别,所以你必须要为连接明确制定隔离级别.目前受影响的数据库:`MSSQL SQLite` +> Note: 某些数据库只能针对连接来设置事务隔离级别,所以你必须要为连接明确制定隔离级别.目前受影响的数据库:`MSSQL SQLite` ->注意:SQLite 只支持两种事务隔离级别,所以你只能设置`READ UNCOMMITTED` 和 `SERIALIZABLE`.使用其他隔离级别会抛出异常. +> Note: SQLite 只支持两种事务隔离级别,所以你只能设置`READ UNCOMMITTED` 和 `SERIALIZABLE`.使用其他隔离级别会抛出异常. ->注意:PostgreSQL 不允许在事务开始前设置隔离级别,所以你不能在事务开始时指定隔离级别.你可以在事务开始之后调用[[yii\db\Transaction::setIsolationLevel()]] 来设置. +> Note: PostgreSQL 不允许在事务开始前设置隔离级别,所以你不能在事务开始时指定隔离级别.你可以在事务开始之后调用[[yii\db\Transaction::setIsolationLevel()]] 来设置. 关于隔离级别[isolation levels]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels @@ -361,11 +361,11 @@ $rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); // 通过主服务器执行更新操作 $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); ``` ->注意:通过[[yii\db\Command::execute()]] 执行的查询被认为是写操作,所有使用[[yii\db\Command]]来执行的其他查询方法被认为是读操作.你可以通过`$db->slave`得到当前正在使用能够的从服务器. +> Note: 通过[[yii\db\Command::execute()]] 执行的查询被认为是写操作,所有使用[[yii\db\Command]]来执行的其他查询方法被认为是读操作.你可以通过`$db->slave`得到当前正在使用能够的从服务器. -`Connection`组件支持从服务器的负载均衡和故障转移,当第一次执行读查询时,会随即选择一个从服务器进行连接,如果连接失败则又选择另一个,如果所有从服务器都不可用,则会连接主服务器。你可以配置[[yii\db\Connection::serverStatusCache|server status cache]]来记住那些不能连接的从服务器,使Yii 在一段时间[[yii\db\Connection::serverRetryInterval].内不会重复尝试连接那些根本不可用的从服务器. +`Connection`组件支持从服务器的负载均衡和故障转移,当第一次执行读查询时,会随即选择一个从服务器进行连接,如果连接失败则又选择另一个,如果所有从服务器都不可用,则会连接主服务器。你可以配置[[yii\db\Connection::serverStatusCache|server status cache]]来记住那些不能连接的从服务器,使Yii 在一段时间[[yii\db\Connection::serverRetryInterval]].内不会重复尝试连接那些根本不可用的从服务器. ->注意:在上述配置中,每个从服务器连接超时时间被指定为10s. 如果在10s内不能连接,则被认为该服务器已经挂掉.你也可以自定义超时参数. +> Note: 在上述配置中,每个从服务器连接超时时间被指定为10s. 如果在10s内不能连接,则被认为该服务器已经挂掉.你也可以自定义超时参数. 你也可以配置多主多从的结构,例如: @@ -410,7 +410,7 @@ $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); ``` 上述配置制定了2个主服务器和4个从服务器.`Connection`组件也支持主服务器的负载均衡和故障转移,与从服务器不同的是,如果所有主服务器都不可用,则会抛出异常. ->注意:当你使用[[yii\db\Connection::masters|masters]]来配置一个或多个主服务器时,`Connection`中关于数据库连接的其他属性(例如:`dsn`, `username`, `password`)都会被忽略. +> Note: 当你使用[[yii\db\Connection::masters|masters]]来配置一个或多个主服务器时,`Connection`中关于数据库连接的其他属性(例如:`dsn`, `username`, `password`)都会被忽略. 事务默认使用主服务器的连接,并且在事务执行中的所有操作都会使用主服务器的连接,例如: diff --git a/docs/guide-zh-CN/helper-overview.md b/docs/guide-zh-CN/helper-overview.md index eccb14c268..5098128910 100644 --- a/docs/guide-zh-CN/helper-overview.md +++ b/docs/guide-zh-CN/helper-overview.md @@ -1,7 +1,11 @@ 助手类 ======= +<<<<<<< .merge_file_a01044 > 注意:这部分正在开发中。 +======= +> Note: 这部分正在开发中。 +>>>>>>> .merge_file_a06768 Yii 提供许多类来简化常见编码,如对字条串或数组的操作, HTML 代码生成,等等。这些助手类被编写在命名空间 `yii\helpers` 下,并且 @@ -15,7 +19,11 @@ use yii\helpers\Html; echo Html::encode('Test > test'); ``` +<<<<<<< .merge_file_a01044 > 注意:为了支持 [自定义助手类](#customizing-helper-classes),Yii 将每一个助手类 +======= +> Note: 为了支持 [自定义助手类](#customizing-helper-classes),Yii 将每一个助手类 +>>>>>>> .merge_file_a06768 分隔成两个类:一个基类 (例如 `BaseArrayHelper`) 和一个具体的类 (例如 `ArrayHelper`). 当使用助手类时,应该仅使用具体的类版本而不使用基类。 diff --git a/docs/guide-zh-CN/images/start-gii.png b/docs/guide-zh-CN/images/start-gii.png new file mode 100644 index 0000000000..28c75b6b79 Binary files /dev/null and b/docs/guide-zh-CN/images/start-gii.png differ diff --git a/docs/guide-zh-CN/input-validation.md b/docs/guide-zh-CN/input-validation.md index a12af4f51e..58bf6af33b 100644 --- a/docs/guide-zh-CN/input-validation.md +++ b/docs/guide-zh-CN/input-validation.md @@ -115,11 +115,9 @@ public function rules() [[yii\validators\Validator::when|when]] 属性来定义相关条件。举例而言, ```php -[ ['state', 'required', 'when' => function($model) { return $model->country == 'USA'; - }], -] + }] ``` [[yii\validators\Validator::when|when]] 属性会读入一个如下所示结构的 PHP callable 函数对象: @@ -137,13 +135,11 @@ function ($model, $attribute) [[yii\validators\Validator::whenClient|whenClient]] 属性,它会读入一条包含有 JavaScript 函数的字符串。这个函数将被用于确定该客户端验证规则是否被启用。比如, ```php -[ ['state', 'required', 'when' => function ($model) { return $model->country == 'USA'; }, 'whenClient' => "function (attribute, value) { return $('#country').value == 'USA'; - }"], -] + }"] ``` @@ -154,10 +150,10 @@ function ($model, $attribute) 下面的例子展示了如何去掉输入信息的首尾空格,并将空输入返回为 null。具体方法为通过调用 [trim](tutorial-core-validators.md#trim) 和 [default](tutorial-core-validators.md#default) 核心验证器: ```php -[ +return [ [['username', 'email'], 'trim'], [['username', 'email'], 'default'], -] +]; ``` 也还可以用更加通用的 [filter(滤镜)](tutorial-core-validators.md#filter) 核心验证器来执行更加复杂的数据过滤。 @@ -171,27 +167,25 @@ function ($model, $attribute) [default](tutorial-core-validators.md#default) 验证器来实现这一点。举例来说, ```php -[ +return [ // 若 "username" 和 "email" 为空,则设为 null [['username', 'email'], 'default'], // 若 "level" 为空,则设其为 1 ['level', 'default', 'value' => 1], -] +]; ``` 默认情况下,当输入项为空字符串,空数组,或 null 时,会被视为“空值”。你也可以通过配置 [[yii\validators\Validator::isEmpty]] 属性来自定义空值的判定规则。比如, ```php -[ ['agree', 'required', 'isEmpty' => function ($value) { return empty($value); - }], -] + }] ``` -> 注意:对于绝大多数验证器而言,若其 [[yii\base\Validator::skipOnEmpty]] 属性为默认值 +> Note: 对于绝大多数验证器而言,若其 [[yii\base\Validator::skipOnEmpty]] 属性为默认值 true,则它们不会对空值进行任何处理。也就是当他们的关联特性接收到空值时,相关验证会被直接略过。在 [核心验证器](tutorial-core-validators.md) 之中,只有 `captcha`(验证码),`default`(默认值),`filter`(滤镜),`required`(必填),以及 `trim`(去首尾空格),这几个验证器会处理空输入。 @@ -214,7 +208,7 @@ if ($validator->validate($email, $error)) { } ``` -> 注意:不是所有的验证器都支持这种形式的验证。比如 [unique(唯一性)](tutorial-core-validators.md#unique)核心验证器就就是一个例子,它的设计初衷就是只作用于模型类内部的。 +> Note: 不是所有的验证器都支持这种形式的验证。比如 [unique(唯一性)](tutorial-core-validators.md#unique)核心验证器就就是一个例子,它的设计初衷就是只作用于模型类内部的。 若你需要针对一系列值执行多项验证,你可以使用 [[yii\base\DynamicModel]] 。它支持即时添加特性和验证规则的定义。它的使用规则是这样的: @@ -324,7 +318,7 @@ class MyForm extends Model } ``` -> 注意:缺省状态下,行内验证器不会在关联特性的输入值为空或该特性已经在其他验证中失败的情况下起效。若你想要确保该验证器始终启用的话,你可以在定义规则时,酌情将 [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] 以及 [[yii\validators\Validator::skipOnError|skipOnError]] +> Note: 缺省状态下,行内验证器不会在关联特性的输入值为空或该特性已经在其他验证中失败的情况下起效。若你想要确保该验证器始终启用的话,你可以在定义规则时,酌情将 [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] 以及 [[yii\validators\Validator::skipOnError|skipOnError]] 属性设为 false,比如, > ```php [ @@ -371,7 +365,7 @@ class CountryValidator extends Validator 当终端用户通过 HTML 表单提供相关输入信息时,我们可能会需要用到基于 JavaScript 的客户端验证。因为,它可以让用户更快速的得到错误信息,也因此可以提供更好的用户体验。你可以使用或自己实现除服务器端验证之外,**还能额外**客户端验证功能的验证器。 -> 补充:尽管客户端验证为加分项,但它不是必须项。它存在的主要意义在于给用户提供更好的客户体验。正如“永远不要相信来自终端用户的输入信息”,也同样永远不要相信客户端验证。基于这个理由,你应该始终如前文所描述的那样,通过调用 [[yii\base\Model::validate()]] 方法执行服务器端验证。 +> Info: 尽管客户端验证为加分项,但它不是必须项。它存在的主要意义在于给用户提供更好的客户体验。正如“永远不要相信来自终端用户的输入信息”,也同样永远不要相信客户端验证。基于这个理由,你应该始终如前文所描述的那样,通过调用 [[yii\base\Model::validate()]] 方法执行服务器端验证。 ### 使用客户端验证 @@ -464,7 +458,7 @@ class StatusValidator extends Validator $statuses = json_encode(Status::find()->select('id')->asArray()->column()); $message = json_encode($this->message); return << 技巧:上述代码主要是演示了如何支持客户端验证。在具体实践中,你可以使用 [in](tutorial-core-validators.md#in) 核心验证器来达到同样的目的。比如这样的验证规则: +> Tip: 上述代码主要是演示了如何支持客户端验证。在具体实践中,你可以使用 [in](tutorial-core-validators.md#in) 核心验证器来达到同样的目的。比如这样的验证规则: > ```php [ ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], diff --git a/docs/guide-zh-CN/output-formatting.md b/docs/guide-zh-CN/output-formatting.md index 677abc4593..b0d907e864 100644 --- a/docs/guide-zh-CN/output-formatting.md +++ b/docs/guide-zh-CN/output-formatting.md @@ -120,7 +120,11 @@ echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00 echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 ``` +<<<<<<< .merge_file_a06508 > 注意:时区从属于全世界各国政府定的规则,可能会频繁的变更,因此你的系统的时区数据库可能不是最新的信息, +======= +> Note: 时区从属于全世界各国政府定的规则,可能会频繁的变更,因此你的系统的时区数据库可能不是最新的信息, +>>>>>>> .merge_file_a06028 > 可参考 [ICU manual](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) > 关于更新时区数据库的详情, > 也可参考:[为国际化设置PHP环境](tutorial-i18n.md#setup-environment). diff --git a/docs/guide-zh-CN/output-formatter.md b/docs/guide-zh-CN/output-formatting.md~HEAD similarity index 100% rename from docs/guide-zh-CN/output-formatter.md rename to docs/guide-zh-CN/output-formatting.md~HEAD diff --git a/docs/guide-zh-CN/rest-authentication.md b/docs/guide-zh-CN/rest-authentication.md index d2a9aaa9ce..c0d1af4e00 100644 --- a/docs/guide-zh-CN/rest-authentication.md +++ b/docs/guide-zh-CN/rest-authentication.md @@ -29,7 +29,11 @@ Yii 支持上述的认证方式,你也可很方便的创建新的认证方式 步骤1不是必要的,但是推荐配置,因为RESTful APIs应为无状态的,当[[yii\web\User::enableSession|enableSession]]为false, 请求中的用户认证状态就不能通过session来保持,每个请求的认证通过步骤2和3来实现。 +<<<<<<< .merge_file_a06500 > 提示: 如果你将RESTful APIs作为应用开发,可以设置应用配置中 `user` 组件的[[yii\web\User::enableSession|enableSession]], +======= +> Tip: 如果你将RESTful APIs作为应用开发,可以设置应用配置中 `user` 组件的[[yii\web\User::enableSession|enableSession]], +>>>>>>> .merge_file_a05220 如果将RESTful APIs作为模块开发,可以在模块的 `init()` 方法中增加如下代码,如下所示: ```php @@ -107,7 +111,11 @@ class User extends ActiveRecord implements IdentityInterface 如果认证失败,会发送一个HTTP状态码为401的响应,并带有其他相关信息头(如HTTP 基本认证会有`WWW-Authenticate` 头信息). +<<<<<<< .merge_file_a06500 ## 授权
+======= +## 授权 +>>>>>>> .merge_file_a05220 在用户认证成功后,你可能想要检查他是否有权限执行对应的操作来获取资源,这个过程称为 *authorization* , 详情请参考 [Authorization section](security-authorization.md). diff --git a/docs/guide-zh-CN/rest-controllers.md b/docs/guide-zh-CN/rest-controllers.md index cb50f047e8..6d298e7d6a 100644 --- a/docs/guide-zh-CN/rest-controllers.md +++ b/docs/guide-zh-CN/rest-controllers.md @@ -21,7 +21,11 @@ Yii 提供两个控制器基类来简化创建RESTful 操作的工作:[[yii\rest * 对操作和资源进行用户认证. +<<<<<<< .merge_file_a00416 ## 创建控制器类 +======= +## 创建控制器类 +>>>>>>> .merge_file_a02328 当创建一个新的控制器类,控制器类的命名最好使用资源名称的单数格式,例如,提供用户信息的控制器 可命名为`UserController`. @@ -38,7 +42,11 @@ public function actionView($id) ``` +<<<<<<< .merge_file_a00416 ## 过滤器 +======= +## 过滤器 +>>>>>>> .merge_file_a02328 [[yii\rest\Controller]]提供的大多数RESTful API功能通过[过滤器](structure-filters.md)实现. 特别是以下过滤器会按顺序执行: @@ -46,7 +54,11 @@ public function actionView($id) * [[yii\filters\ContentNegotiator|contentNegotiator]]: 支持内容协商,在 [响应格式化](rest-response-formatting.md) 一节描述; * [[yii\filters\VerbFilter|verbFilter]]: 支持HTTP 方法验证; the [Authentication](rest-authentication.md) section; +<<<<<<< .merge_file_a00416 * [[yii\filters\AuthMethod|authenticator]]: 支持用户认证,在[认证](rest-authentication.md)一节描述; +======= +* [[yii\filters\auth\AuthMethod|authenticator]]: 支持用户认证,在[认证](rest-authentication.md)一节描述; +>>>>>>> .merge_file_a02328 * [[yii\filters\RateLimiter|rateLimiter]]: 支持频率限制,在[频率限制](rest-rate-limiting.md) 一节描述. 这些过滤器都在[[yii\rest\Controller::behaviors()|behaviors()]]方法中声明, @@ -67,6 +79,7 @@ public function behaviors() ``` +<<<<<<< .merge_file_a00416 ## 继承 `ActiveController` 如果你的控制器继承[[yii\rest\ActiveController]],应设置[[yii\rest\ActiveController::modelClass||modelClass]] 属性 @@ -74,6 +87,15 @@ public function behaviors() ### 自定义操作 +======= +## 继承 `ActiveController` + +如果你的控制器继承[[yii\rest\ActiveController]],应设置[[yii\rest\ActiveController::modelClass|modelClass]] 属性 +为通过该控制器返回给用户的资源类名,该类必须继承[[yii\db\ActiveRecord]]. + + +### 自定义操作 +>>>>>>> .merge_file_a02328 [[yii\rest\ActiveController]] 默认提供一下操作: @@ -110,7 +132,11 @@ public function prepareDataProvider() 请参考独立操作类的参考文档学习哪些配置项有用。 +<<<<<<< .merge_file_a00416 ### 执行访问检查 +======= +### 执行访问检查 +>>>>>>> .merge_file_a02328 通过RESTful APIs显示数据时,经常需要检查当前用户是否有权限访问和操作所请求的资源, 在[[yii\rest\ActiveController]]中,可覆盖[[yii\rest\ActiveController::checkAccess()|checkAccess()]]方法来完成权限检查。 @@ -140,4 +166,8 @@ public function checkAccess($action, $model = null, $params = []) `checkAccess()` 方法默认会被[[yii\rest\ActiveController]]默认操作所调用,如果创建新的操作并想执行权限检查, 应在新的操作中明确调用该方法。 +<<<<<<< .merge_file_a00416 > 提示: 可使用[Role-Based Access Control (RBAC) 基于角色权限控制组件](security-authorization.md)实现`checkAccess()`。 +======= +> Tip: 可使用[Role-Based Access Control (RBAC) 基于角色权限控制组件](security-authorization.md)实现`checkAccess()`。 +>>>>>>> .merge_file_a02328 diff --git a/docs/guide-zh-CN/rest-error-handling.md b/docs/guide-zh-CN/rest-error-handling.md index 11f2b4c820..d2cb70837a 100644 --- a/docs/guide-zh-CN/rest-error-handling.md +++ b/docs/guide-zh-CN/rest-error-handling.md @@ -41,3 +41,57 @@ Content-Type: application/json; charset=UTF-8 * `422`: 数据验证失败 (例如,响应一个 `POST` 请求)。 请检查响应体内详细的错误消息。 * `429`: 请求过多。 由于限速请求被拒绝。 * `500`: 内部服务器错误。 这可能是由于内部程序错误引起的。 +<<<<<<< .merge_file_a05756 +======= + + +## 自定义错误响应 + +有时你可能想自定义默认的错误响应格式。例如,你想一直使用HTTP状态码200, +而不是依赖于使用不同的HTTP状态来表示不同的错误, +并附上实际的HTTP状态代码为JSON结构的一部分的响应,就像以下所示, + +``` +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "success": false, + "data": { + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 + } +} +``` + +为了实现这一目的,你可以响应该应用程序配置的 `response` 组件的 `beforeSend` 事件: + +```php +return [ + // ... + 'components' => [ + 'response' => [ + 'class' => 'yii\web\Response', + 'on beforeSend' => function ($event) { + $response = $event->sender; + if ($response->data !== null && !empty(Yii::$app->request->get('suppress_response_code'))) { + $response->data = [ + 'success' => $response->isSuccessful, + 'data' => $response->data, + ]; + $response->statusCode = 200; + } + }, + ], + ], +]; +``` + +当 `suppress_response_code` 作为 `GET` 参数传递时,上面的代码 +将重新按照自己定义的格式响应(无论失败还是成功)。 +>>>>>>> .merge_file_a06412 diff --git a/docs/guide-zh-CN/rest-quick-start.md b/docs/guide-zh-CN/rest-quick-start.md index 1f8ed5fa9a..133b7d496d 100644 --- a/docs/guide-zh-CN/rest-quick-start.md +++ b/docs/guide-zh-CN/rest-quick-start.md @@ -60,6 +60,26 @@ class UserController extends ActiveController 用户的数据就能通过美化的 URL 和有意义的 http 动词进行访问和操作。 +<<<<<<< .merge_file_a05804 +======= +## 启用 JSON 输入 + +为了使 API 接收 JSON 格式的输入数据,配置 `request` 应用程序组件的 [[yii\web\Request::$parsers|parsers]] +属性使用 [[yii\web\JsonParser]] 用于JSON输入: + +```php +'request' => [ + 'parsers' => [ + 'application/json' => 'yii\web\JsonParser', + ] +] +``` + +> Info: 上述配置是可选的。若未按上述配置,API 将仅可以分辨 + `application/x-www-form-urlencoded` 和 `multipart/form-data` 输入格式。 + + +>>>>>>> .merge_file_a05620 ## 尝试 随着以上所做的最小的努力,你已经完成了创建用于访问用户数据 @@ -75,17 +95,28 @@ class UserController extends ActiveController * `OPTIONS /users`: 显示关于末端 `/users` 支持的动词 * `OPTIONS /users/123`: 显示有关末端 `/users/123` 支持的动词 +<<<<<<< .merge_file_a05804 > 补充:Yii 将在末端使用的控制器的名称自动变为复数。(译注:个人感觉这里应该变为注意) 你可以访问你的API用`curl`命令如下, +======= +> Info: Yii 将在末端使用的控制器的名称自动变为复数。(译注:个人感觉这里应该变为注意) +> 你可以用 [[yii\rest\UrlRule::$pluralize]]-属性来配置此项。 + +你可以访问你的API用 `curl` 命令如下, +>>>>>>> .merge_file_a05620 ``` $ curl -i -H "Accept:application/json" "http://localhost/users" HTTP/1.1 200 OK +<<<<<<< .merge_file_a05804 Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 +======= +... +>>>>>>> .merge_file_a05620 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 @@ -109,15 +140,24 @@ Content-Type: application/json; charset=UTF-8 ] ``` +<<<<<<< .merge_file_a05804 试着改变可接受的内容类型为`application/xml`,你会看到结果以 XML 格式返回: +======= +试着改变可接受的内容类型为`application/xml`, +你会看到结果以 XML 格式返回: +>>>>>>> .merge_file_a05620 ``` $ curl -i -H "Accept:application/xml" "http://localhost/users" HTTP/1.1 200 OK +<<<<<<< .merge_file_a05804 Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 +======= +... +>>>>>>> .merge_file_a05620 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 @@ -142,7 +182,25 @@ Content-Type: application/xml ``` +<<<<<<< .merge_file_a05804 > 技巧:你还可以通过 Web 浏览器中输入 URL `http://localhost/users` 来访问你的 API。 +======= +以下命令将创建一个新的用户通过发送JSON格式的用户数据的 POST 请求: + +``` +$ curl -i -H "Accept:application/json" -H "Content-Type:application/json" -XPOST "http://localhost/users" -d '{"username": "example", "email": "user@example.com"}' + +HTTP/1.1 201 Created +... +Location: http://localhost/users/1 +Content-Length: 99 +Content-Type: application/json; charset=UTF-8 + +{"id":1,"username":"example","email":"user@example.com","created_at":1414674789,"updated_at":1414674789} +``` + +> Tip: 你还可以通过 Web 浏览器中输入 URL `http://localhost/users` 来访问你的 API。 +>>>>>>> .merge_file_a05620 尽管如此,你可能需要一些浏览器插件来发送特定的 headers 请求。 如你所见,在 headers 响应,有关于总数,页数的信息,等等。 @@ -153,7 +211,11 @@ Content-Type: application/xml 例如:URL `http://localhost/users?fields=id,email` 将只返回 `id` 和 `email` 字段。 +<<<<<<< .merge_file_a05804 > 补充:你可能已经注意到了 `http://localhost/users` 的结果包括一些敏感字段, +======= +> Info: 你可能已经注意到了 `http://localhost/users` 的结果包括一些敏感字段, +>>>>>>> .merge_file_a05620 > 例如 `password_hash`, `auth_key` 你肯定不希望这些出现在你的 API 结果中。 > 你应该在 [响应格式](rest-response-formatting.md) 部分中过滤掉这些字段。 @@ -169,5 +231,10 @@ Content-Type: application/xml 你可以使用 [[yii\rest\UrlRule]] 简化路由到你的 API 末端。 +<<<<<<< .merge_file_a05804 为了方便维护你的WEB前端和后端,建议你开发接口作为一个单独的应用程序,虽然这不是必须的。 +======= +为了方便维护你的WEB前端和后端,建议你开发接口作为一个单独的应用程序, +虽然这不是必须的。 +>>>>>>> .merge_file_a05620 diff --git a/docs/guide-zh-CN/rest-rate-limiting.md b/docs/guide-zh-CN/rest-rate-limiting.md index de29f39d88..ef81929370 100644 --- a/docs/guide-zh-CN/rest-rate-limiting.md +++ b/docs/guide-zh-CN/rest-rate-limiting.md @@ -1,13 +1,20 @@ 速率限制 ============= +<<<<<<< .merge_file_a04232 为防止滥用,你应该考虑增加速率限制到您的API。 例如,您可以限制每个用户的API的使用是在10分钟内最多100次的API调用。 如果一个用户同一个时间段内太多的请求被接收, 将返回响应状态代码 429 (这意味着过多的请求)。 +======= +为防止滥用,你应该考虑增加速率限制到您的 API。 +例如,您可以限制每个用户的 API 的使用是在 10 分钟内最多 100 次的 API 调用。 +如果一个用户在规定的时间内太多的请求被接收,将返回响应状态代码 429 (这意味着过多的请求)。 +>>>>>>> .merge_file_a03968 要启用速率限制, [[yii\web\User::identityClass|user identity class]] 应该实现 [[yii\filters\RateLimitInterface]]. 这个接口需要实现以下三个方法: +<<<<<<< .merge_file_a04232 * `getRateLimit()`: 返回允许的请求的最大数目及时间,例如,`[100, 600]` 表示在600秒内最多100次的API调用。 * `loadAllowance()`: 返回剩余的允许的请求和相应的UNIX时间戳数 当最后一次速率限制检查时。 @@ -22,6 +29,18 @@ 为 [[yii\rest\Controller]] 配置一个行为过滤器来执行速率限制检查。 如果速度超出限制 该速率限制器将抛出一个 [[yii\web\TooManyRequestsHttpException]]。 你可以在你的 REST 控制器类里配置速率限制, +======= +* `getRateLimit()`: 返回允许的请求的最大数目及时间,例如,`[100, 600]` 表示在 600 秒内最多 100 次的 API 调用。 +* `loadAllowance()`: 返回剩余的允许的请求和最后一次速率限制检查时相应的 UNIX 时间戳数。 +* `saveAllowance()`: 保存剩余的允许请求数和当前的 UNIX 时间戳。 + +你可以在 user 表中使用两列来记录容差和时间戳信息。 +`loadAllowance()` 和 `saveAllowance()` 可以通过实现对符合当前身份验证的用户的这两列值的读和保存。为了提高性能,你也可以 +考虑使用缓存或 NoSQL 存储这些信息。 + +一旦 identity 实现所需的接口, Yii 会自动使用 [[yii\filters\RateLimiter]] +为 [[yii\rest\Controller]] 配置一个行为过滤器来执行速率限制检查。如果速度超出限制,该速率限制器将抛出一个 [[yii\web\TooManyRequestsHttpException]]。你可以参考以下代码在你的 REST 控制器类里配置速率限制: +>>>>>>> .merge_file_a03968 ```php public function behaviors() @@ -32,12 +51,20 @@ public function behaviors() } ``` +<<<<<<< .merge_file_a04232 当速率限制被激活,默认情况下每个响应将包含以下HTTP头发送 目前的速率限制信息: +======= +当速率限制被激活,默认情况下每个响应将包含以下 HTTP 头发送目前的速率限制信息: +>>>>>>> .merge_file_a03968 * `X-Rate-Limit-Limit`: 同一个时间段所允许的请求的最大数目; * `X-Rate-Limit-Remaining`: 在当前时间段内剩余的请求的数量; * `X-Rate-Limit-Reset`: 为了得到最大请求数所等待的秒数。 +<<<<<<< .merge_file_a04232 你可以禁用这些头信息通过配置 [[yii\filters\RateLimiter::enableRateLimitHeaders]] 为false, +======= +你可以禁用这些头信息通过配置 [[yii\filters\RateLimiter::enableRateLimitHeaders]] 为 false, +>>>>>>> .merge_file_a03968 就像在上面的代码示例所示。 diff --git a/docs/guide-zh-CN/rest-resources.md b/docs/guide-zh-CN/rest-resources.md index d5a7cd88ba..d887a7dfdf 100644 --- a/docs/guide-zh-CN/rest-resources.md +++ b/docs/guide-zh-CN/rest-resources.md @@ -15,7 +15,11 @@ RESTful 的 API 都是关于访问和操作 *资源*,可将资源看成MVC模 继承 [[yii\base\Model]] 会将它所有的公开成员变量返回。 +<<<<<<< .merge_file_a01056 ## 字段 +======= +## 字段 +>>>>>>> .merge_file_a06160 当RESTful API响应中包含一个资源时,该资源需要序列化成一个字符串。 Yii将这个过程分成两步,首先,资源会被[[yii\rest\Serializer]]转换成数组, @@ -42,7 +46,11 @@ http://localhost/users?fields=id,email&expand=profile ``` +<<<<<<< .merge_file_a01056 ### 覆盖 `fields()` 方法 +======= +### 覆盖 `fields()` 方法 +>>>>>>> .merge_file_a06160 [[yii\base\Model::fields()]] 默认返回模型的所有属性作为字段, [[yii\db\ActiveRecord::fields()]] 只返回和数据表关联的属性作为字段。 @@ -79,11 +87,19 @@ public function fields() } ``` +<<<<<<< .merge_file_a01056 > 警告: 模型的所有属性默认会被包含到API结果中,应检查数据确保没包含敏感数据,如果有敏感数据, > 应覆盖`fields()`过滤掉,在上述例子中,我们选择过滤掉 `auth_key`, `password_hash` 和 `password_reset_token`. ### 覆盖 `extraFields()` 方法 +======= +> Warning: 模型的所有属性默认会被包含到API结果中,应检查数据确保没包含敏感数据,如果有敏感数据, +> 应覆盖`fields()`过滤掉,在上述例子中,我们选择过滤掉 `auth_key`, `password_hash` 和 `password_reset_token`. + + +### 覆盖 `extraFields()` 方法 +>>>>>>> .merge_file_a06160 [[yii\base\Model::extraFields()]] 默认返回空值,[[yii\db\ActiveRecord::extraFields()]] 返回和数据表关联的属性。 @@ -119,7 +135,11 @@ public function extraFields() ``` +<<<<<<< .merge_file_a01056 ## 链接 +======= +## 链接 +>>>>>>> .merge_file_a06160 [HATEOAS](http://en.wikipedia.org/wiki/HATEOAS), 是Hypermedia as the Engine of Application State的缩写, 提升RESTful API 应返回允许终端用户访问的资源操作的信息,HATEOAS 的目的是在API中返回包含相关链接信息的资源数据。 @@ -160,7 +180,11 @@ class User extends ActiveRecord implements Linkable ``` +<<<<<<< .merge_file_a01056 ## 集合 +======= +## 集合 +>>>>>>> .merge_file_a06160 资源对象可以组成 *集合*,每个集合包含一组相同类型的资源对象。 diff --git a/docs/guide-zh-CN/rest-response-formatting.md b/docs/guide-zh-CN/rest-response-formatting.md index ba174b64f8..7b2bd62c7e 100644 --- a/docs/guide-zh-CN/rest-response-formatting.md +++ b/docs/guide-zh-CN/rest-response-formatting.md @@ -9,8 +9,13 @@ 2. 资源对象转换为数组, 如在 [Resources](rest-resources.md) 部分中所描述的。 通过 [[yii\rest\Serializer]] 来完成。 3. 通过内容协商步骤将数组转换成字符串。 +<<<<<<< .merge_file_a03016 [yii\web\ResponseFormatterInterface|response formatters]] 通过 [yii\web\Response::formatters|response]] 应用程序组件来注册完成。 +======= + [[yii\web\ResponseFormatterInterface|response formatters]] 通过 + [[yii\web\Response::formatters|response]] 应用程序组件来注册完成。 +>>>>>>> .merge_file_a05144 ## 内容协商 diff --git a/docs/guide-zh-CN/runtime-handling-errors.md b/docs/guide-zh-CN/runtime-handling-errors.md index 9b80e7cfeb..6cefbd8083 100644 --- a/docs/guide-zh-CN/runtime-handling-errors.md +++ b/docs/guide-zh-CN/runtime-handling-errors.md @@ -61,7 +61,11 @@ throw new NotFoundHttpException(); 当`YII_DEBUG` 为 true (表示在调试模式),错误处理器会显示异常以及详细的函数调用栈和源代码行数来帮助调试, 当`YII_DEBUG` 为 false,只有错误信息会被显示以防止应用的敏感信息泄漏。 +<<<<<<< .merge_file_a02672 > 补充: 如果异常是继承 [[yii\base\UserException]],不管`YII_DEBUG`为何值,函数调用栈信息都不会显示, +======= +> Info: 如果异常是继承 [[yii\base\UserException]],不管`YII_DEBUG`为何值,函数调用栈信息都不会显示, +>>>>>>> .merge_file_a06884 这是因为这种错误会被认为是用户产生的错误,开发人员不需要去修正。 [[yii\web\ErrorHandler|error handler]] 错误处理器默认使用两个[视图](structure-views.md)显示错误: @@ -134,7 +138,11 @@ public function actionError() * `message`: 错误信息 * `exception`: 更多详细信息的异常对象,如HTTP 状态码,错误码,错误调用栈等。 +<<<<<<< .merge_file_a02672 > 补充: 如果你使用 [基础应用模板](start-installation.md) 或 [高级应用模板](tutorial-advanced-app.md), +======= +> Info: 如果你使用 [基础应用模板](start-installation.md) 或 [高级应用模板](tutorial-advanced-app.md), +>>>>>>> .merge_file_a06884 错误操作和错误视图已经定义好了。 diff --git a/docs/guide-zh-CN/runtime-responses.md b/docs/guide-zh-CN/runtime-responses.md index a9034b1bcc..06d65ddd23 100644 --- a/docs/guide-zh-CN/runtime-responses.md +++ b/docs/guide-zh-CN/runtime-responses.md @@ -71,7 +71,7 @@ $headers->set('Pragma', 'no-cache'); $values = $headers->remove('Pragma'); ``` -> 补充: 头名称是大小写敏感的,在[[yii\web\Response::send()]]方法调用前新注册的头信息并不会发送给用户。 +> Info: 头名称是大小写敏感的,在[[yii\web\Response::send()]]方法调用前新注册的头信息并不会发送给用户。 ## 响应主体 @@ -143,7 +143,7 @@ public function actionInfo() } ``` -> 注意: 如果创建你自己的响应对象,将不能在应用配置中设置 `response` 组件,尽管如此, +> Note: 如果创建你自己的响应对象,将不能在应用配置中设置 `response` 组件,尽管如此, 可使用 [依赖注入](concept-di-container.md) 应用通用配置到你新的响应对象。 @@ -170,14 +170,14 @@ public function actionOld() \Yii::$app->response->redirect('http://example.com/new', 301)->send(); ``` -> 补充: [[yii\web\Response::redirect()]] 方法默认会设置响应状态码为302,该状态码会告诉浏览器请求的资源 +> Info: [[yii\web\Response::redirect()]] 方法默认会设置响应状态码为302,该状态码会告诉浏览器请求的资源 *临时* 放在另一个URI地址上,可传递一个301状态码告知浏览器请求的资源已经 *永久* 重定向到新的URId地址。 如果当前请求为AJAX 请求,发送一个 `Location` 头不会自动使浏览器跳转,为解决这个问题, [[yii\web\Response::redirect()]] 方法设置一个值为要跳转的URL的`X-Redirect` 头, 在客户端可编写JavaScript 代码读取该头部值然后让浏览器跳转对应的URL。 -> 补充: Yii 配备了一个`yii.js` JavaScript 文件提供常用JavaScript功能,包括基于`X-Redirect`头的浏览器跳转, +> Info: Yii 配备了一个`yii.js` JavaScript 文件提供常用JavaScript功能,包括基于`X-Redirect`头的浏览器跳转, 因此,如果你使用该JavaScript 文件(通过[[yii\web\YiiAsset]] 资源包注册),就不需要编写AJAX跳转的代码。 diff --git a/docs/guide-zh-CN/runtime-sessions-cookies.md b/docs/guide-zh-CN/runtime-sessions-cookies.md index db00d92286..9db42f6879 100644 --- a/docs/guide-zh-CN/runtime-sessions-cookies.md +++ b/docs/guide-zh-CN/runtime-sessions-cookies.md @@ -69,7 +69,11 @@ foreach ($session as $name => $value) ... foreach ($_SESSION as $name => $value) ... ``` +<<<<<<< .merge_file_a02856 > 补充: 当使用`session`组件访问session数据时候,如果session没有开启会自动开启, +======= +> Info: 当使用`session`组件访问session数据时候,如果session没有开启会自动开启, +>>>>>>> .merge_file_a01508 这和通过`$_SESSION`不同,`$_SESSION`要求先执行`session_start()`。 当session数据为数组时,`session`组件会限制你直接修改数据中的单元项,例如: @@ -132,7 +136,11 @@ $session['captcha.lifetime'] = 3600; 所有这些session类支持相同的API方法集,因此,切换到不同的session存储介质不需要修改项目使用session的代码。 +<<<<<<< .merge_file_a02856 > 注意: 如果通过`$_SESSION`访问使用自定义存储介质的session,需要确保session已经用[[yii\web\Session::open()]] 开启, +======= +> Note: 如果通过`$_SESSION`访问使用自定义存储介质的session,需要确保session已经用[[yii\web\Session::open()]] 开启, +>>>>>>> .merge_file_a01508 这是因为在该方法中注册自定义session存储处理器。 学习如何配置和使用这些组件类请参考它们的API文档,如下为一个示例 @@ -167,7 +175,11 @@ CREATE TABLE session - PostgreSQL: BYTEA - MSSQL: BLOB +<<<<<<< .merge_file_a02856 > 注意: 根据php.ini 设置的 `session.hash_function`,你需要调整`id`列的长度, +======= +> Note: 根据 php.ini 设置的 `session.hash_function`,你需要调整`id`列的长度, +>>>>>>> .merge_file_a01508 例如,如果 `session.hash_function=sha256` ,应使用长度为64而不是40的char类型。 @@ -214,7 +226,11 @@ $session->addFlash('alerts', 'You are promoted.'); $alerts = $session->getFlash('alerts'); ``` +<<<<<<< .merge_file_a02856 > 注意: 不要在相同名称的flash数据中使用[[yii\web\Session::setFlash()]] 的同时也使用[[yii\web\Session::addFlash()]], +======= +> Note: 不要在相同名称的flash数据中使用[[yii\web\Session::setFlash()]] 的同时也使用[[yii\web\Session::addFlash()]], +>>>>>>> .merge_file_a01508 因为后一个防范会自动将flash信息转换为数组以使新的flash数据可追加进来,因此, 当你调用[[yii\web\Session::getFlash()]]时,会发现有时获取到一个数组,有时获取到一个字符串, 取决于你调用这两个方法的顺序。 @@ -280,7 +296,11 @@ unset($cookies['language']); [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]] 可配置这些属性到cookie中并添加到响应的cookie集合中。 +<<<<<<< .merge_file_a02856 > 注意: 为安全起见[[yii\web\Cookie::httpOnly]] 被设置为true,这可减少客户端脚本访问受保护cookie(如果浏览器支持)的风险, +======= +> Note: 为安全起见[[yii\web\Cookie::httpOnly]] 被设置为true,这可减少客户端脚本访问受保护cookie(如果浏览器支持)的风险, +>>>>>>> .merge_file_a01508 更多详情可阅读 [httpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) for more details. @@ -290,12 +310,20 @@ unset($cookies['language']); 使cookie不被客户端修改。该功能通过给每个cookie签发一个哈希字符串来告知服务端cookie是否在客户端被修改, 如果被修改,通过`request`组件的[[yii\web\Request::cookies|cookie collection]]cookie集合访问不到该cookie。 +<<<<<<< .merge_file_a02856 > 注意: Cookie验证只保护cookie值被修改,如果一个cookie验证失败,仍然可以通过`$_COOKIE`来访问该cookie, +======= +> Note: Cookie验证只保护cookie值被修改,如果一个cookie验证失败,仍然可以通过`$_COOKIE`来访问该cookie, +>>>>>>> .merge_file_a01508 因为这是第三方库对未通过cookie验证自定义的操作方式。 Cookie验证默认启用,可以设置[[yii\web\Request::enableCookieValidation]]属性为false来禁用它,尽管如此,我们强烈建议启用它。 +<<<<<<< .merge_file_a02856 > 注意: 直接通过`$_COOKIE` 和 `setcookie()` 读取和发送的Cookie不会被验证。 +======= +> Note: 直接通过`$_COOKIE` 和 `setcookie()` 读取和发送的Cookie不会被验证。 +>>>>>>> .merge_file_a01508 当使用cookie验证,必须指定[[yii\web\Request::cookieValidationKey]],它是用来生成s上述的哈希值, 可通过在应用配置中配置`request` 组件。 @@ -310,5 +338,9 @@ return [ ]; ``` +<<<<<<< .merge_file_a02856 > 补充: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] 对你的应用安全很重要, +======= +> Info: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] 对你的应用安全很重要, +>>>>>>> .merge_file_a01508 应只被你信任的人知晓,请不要将它放入版本控制中。 diff --git a/docs/guide-zh-CN/start-databases.md b/docs/guide-zh-CN/start-databases.md index 13e75a351b..8a81508686 100644 --- a/docs/guide-zh-CN/start-databases.md +++ b/docs/guide-zh-CN/start-databases.md @@ -27,6 +27,7 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +<<<<<<< .merge_file_a07132 INSERT INTO `country` VALUES ('AU','Australia',18886000); INSERT INTO `country` VALUES ('BR','Brazil',170115000); INSERT INTO `country` VALUES ('CA','Canada',1147000); @@ -37,6 +38,18 @@ INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); INSERT INTO `country` VALUES ('IN','India',1013662000); INSERT INTO `country` VALUES ('RU','Russia',146934000); INSERT INTO `country` VALUES ('US','United States',278357000); +======= +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); +>>>>>>> .merge_file_a06196 ``` 此时便有了一个名为 `yii2basic` 的数据库,在这个数据库中有一个包含三个字段的数据表 `country`,表中有十行数据。 @@ -64,7 +77,11 @@ return [ 上面配置的数据库连接可以在应用中通过 `Yii::$app->db` 表达式访问。 +<<<<<<< .merge_file_a07132 > 补充:`config/db.php` 将被包含在应用配置文件 `config/web.php` 中,后者指定了整个[应用](structure-applications.md)如何初始化。请参考[配置](concept-configurations.md)章节了解更多信息。 +======= +> Info: `config/db.php` 将被包含在应用配置文件 `config/web.php` 中,后者指定了整个[应用](structure-applications.md)如何初始化。请参考[配置](concept-configurations.md)章节了解更多信息。 +>>>>>>> .merge_file_a06196 创建活动记录 @@ -86,7 +103,11 @@ class Country extends ActiveRecord 这个 `Country` 类继承自 [[yii\db\ActiveRecord]]。你不用在里面写任何代码。只需要像现在这样,Yii 就能根据类名去猜测对应的数据表名。 +<<<<<<< .merge_file_a07132 > 补充:如果类名和数据表名不能直接对应,可以覆写 [[yii\db\ActiveRecord::tableName()|tableName()]] 方法去显式指定相关表名。 +======= +> Info: 如果类名和数据表名不能直接对应,可以覆写 [[yii\db\ActiveRecord::tableName()|tableName()]] 方法去显式指定相关表名。 +>>>>>>> .merge_file_a06196 使用 `Country` 类可以很容易地操作 `country` 表数据,就像这段代码: @@ -107,7 +128,11 @@ $country->name = 'U.S.A.'; $country->save(); ``` +<<<<<<< .merge_file_a07132 > 补充:活动记录是面向对象、功能强大的访问和操作数据库数据的方式。你可以在[活动记录](db-active-record.md)章节了解更多信息。除此之外你还可以使用另一种更原生的被称做[数据访问对象](db-dao)的方法操作数据库数据。 +======= +> Info: 活动记录是面向对象、功能强大的访问和操作数据库数据的方式。你可以在[活动记录](db-active-record.md)章节了解更多信息。除此之外你还可以使用另一种更原生的被称做[数据访问对象](db-dao)的方法操作数据库数据。 +>>>>>>> .merge_file_a06196 创建操作 @@ -212,4 +237,8 @@ http://hostname/index.php?r=country/index&page=2 本章节中你学到了如何使用数据库。你还学到了如何取出并使用 [[yii\data\Pagination]] 和 [[yii\widgets\LinkPager]] 显示数据。 -下一章中你会学到如何使用 Yii 中强大的代码生成器 [Gii](tool-gii.md),去帮助你实现一些常用的功能需求,例如增查改删(CRUD)数据表中的数据。事实上你之前所写的代码全部都可以由 Gii 自动生成。 \ No newline at end of file +<<<<<<< .merge_file_a07132 +下一章中你会学到如何使用 Yii 中强大的代码生成器 [Gii](tool-gii.md),去帮助你实现一些常用的功能需求,例如增查改删(CRUD)数据表中的数据。事实上你之前所写的代码全部都可以由 Gii 自动生成。 +======= +下一章中你会学到如何使用 Yii 中强大的代码生成器 [Gii](tool-gii.md),去帮助你实现一些常用的功能需求,例如增查改删(CRUD)数据表中的数据。事实上你之前所写的代码全部都可以由 Gii 自动生成。 +>>>>>>> .merge_file_a06196 diff --git a/docs/guide-zh-CN/start-forms.md b/docs/guide-zh-CN/start-forms.md index ffc2a1dd20..ad32514d39 100644 --- a/docs/guide-zh-CN/start-forms.md +++ b/docs/guide-zh-CN/start-forms.md @@ -41,7 +41,11 @@ class EntryForm extends Model 该类继承自Yii 提供的一个基类 [[yii\base\Model]],该基类通常用来表示数据。 +<<<<<<< .merge_file_a03416 > 补充:[[yii\base\Model]] 被用于普通模型类的父类并与数据表**无关**。[[yii\db\ActiveRecord]] 通常是普通模型类的父类但与数据表有关联(译注:[[yii\db\ActiveRecord]] 类其实也是继承自 [[yii\base\Model]],增加了数据库处理)。 +======= +> Info: [[yii\base\Model]] 被用于普通模型类的父类并与数据表**无关**。[[yii\db\ActiveRecord]] 通常是普通模型类的父类但与数据表有关联(译注:[[yii\db\ActiveRecord]] 类其实也是继承自 [[yii\base\Model]],增加了数据库处理)。 +>>>>>>> .merge_file_a05016 `EntryForm` 类包含 `name` 和 `email` 两个公共成员,用来储存用户输入的数据。它还包含一个名为 `rules()` 的方法,用来返回数据验证规则的集合。上面声明的验证规则表示: @@ -102,11 +106,19 @@ class SiteController extends Controller 该操作首先创建了一个 `EntryForm` 对象。然后尝试从 `$_POST` 搜集用户提交的数据,由 Yii 的 [[yii\web\Request::post()]] 方法负责搜集。如果模型被成功填充数据(也就是说用户已经提交了 HTML 表单),操作将调用 [[yii\base\Model::validate()|validate()]] 去确保用户提交的是有效数据。 +<<<<<<< .merge_file_a03416 > 补充:表达式 `Yii::$app` 代表[应用](structure-applications.md)实例,它是一个全局可访问的单例。同时它也是一个[服务定位器](concept-service-locator.md),能提供 `request`,`response`,`db` 等等特定功能的组件。在上面的代码里就是使用 `request` 组件来访问应用实例收到的 `$_POST` 数据。 用户提交表单后,操作将会渲染一个名为 `entry-confirm` 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误(译者:如 email 格式不对),`entry` 视图将会渲染输出,连同表单一起输出的还有验证错误的详细信息。 > 注意:在这个简单例子里我们只是呈现了有效数据的确认页面。实践中你应该考虑使用 [[yii\web\Controller::refresh()|refresh()]] 或 [[yii\web\Controller::redirect()|redirect()]] 去避免[表单重复提交问题](http://en.wikipedia.org/wiki/Post/Redirect/Get)。 +======= +> Info: 表达式 `Yii::$app` 代表[应用](structure-applications.md)实例,它是一个全局可访问的单例。同时它也是一个[服务定位器](concept-service-locator.md),能提供 `request`,`response`,`db` 等等特定功能的组件。在上面的代码里就是使用 `request` 组件来访问应用实例收到的 `$_POST` 数据。 + +用户提交表单后,操作将会渲染一个名为 `entry-confirm` 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误(译者:如 email 格式不对),`entry` 视图将会渲染输出,连同表单一起输出的还有验证错误的详细信息。 + +> Note: 在这个简单例子里我们只是呈现了有效数据的确认页面。实践中你应该考虑使用 [[yii\web\Controller::refresh()|refresh()]] 或 [[yii\web\Controller::redirect()|redirect()]] 去避免[表单重复提交问题](http://en.wikipedia.org/wiki/Post/Redirect/Get)。 +>>>>>>> .merge_file_a05016 创建视图 @@ -176,7 +188,11 @@ http://hostname/index.php?r=site/entry 是的,其实数据首先由客户端 JavaScript 脚本验证,然后才会提交给服务器通过 PHP 验证。[[yii\widgets\ActiveForm]] 足够智能到把你在 `EntryForm` 模型中声明的验证规则转化成客户端 JavaScript 脚本去执行验证。如果用户浏览器禁用了 JavaScript, 服务器端仍然会像 `actionEntry()` 方法里这样验证一遍数据。这保证了任何情况下用户提交的数据都是有效的。 +<<<<<<< .merge_file_a03416 > 警告:客户端验证是提高用户体验的手段。无论它是否正常启用,服务端验证则都是必须的,请不要忽略它。 +======= +> Warning: 客户端验证是提高用户体验的手段。无论它是否正常启用,服务端验证则都是必须的,请不要忽略它。 +>>>>>>> .merge_file_a05016 输入框的文字标签是 `field()` 方法生成的,内容就是模型中该数据的属性名。例如模型中的 `name` 属性生成的标签就是 `Name`。 @@ -187,7 +203,11 @@ http://hostname/index.php?r=site/entry field($model, 'email')->label('自定义 Email') ?> ``` +<<<<<<< .merge_file_a03416 > 补充:Yii 提供了相当多类似的小部件去帮你生成复杂且动态的视图。在后面你还会了解到自己写小部件是多么简单。你可能会把自己的很多视图代码转化成小部件以提高重用,加快开发效率。 +======= +> Info: Yii 提供了相当多类似的小部件去帮你生成复杂且动态的视图。在后面你还会了解到自己写小部件是多么简单。你可能会把自己的很多视图代码转化成小部件以提高重用,加快开发效率。 +>>>>>>> .merge_file_a05016 总结 diff --git a/docs/guide-zh-CN/start-gii.md b/docs/guide-zh-CN/start-gii.md index ae591159cb..bedf72013b 100644 --- a/docs/guide-zh-CN/start-gii.md +++ b/docs/guide-zh-CN/start-gii.md @@ -39,7 +39,11 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); http://hostname/index.php?r=gii ``` +<<<<<<< .merge_file_a06868 > 补充: 如果你通过本机以外的机器访问 Gii,请求会被出于安全原因拒绝。你可以配置 Gii 为其添加允许访问的 IP 地址: +======= +> Info: 如果你通过本机以外的机器访问 Gii,请求会被出于安全原因拒绝。你可以配置 Gii 为其添加允许访问的 IP 地址: +>>>>>>> .merge_file_a02624 > ```php 'gii' => [ @@ -113,7 +117,11 @@ http://hostname/index.php?r=country/index * 模型:`models/Country.php` 和 `models/CountrySearch.php` * 视图:`views/country/*.php` +<<<<<<< .merge_file_a06868 > 补充:Gii 被设计成高度可定制和可扩展的代码生成工具。使用它可以大幅提高应用开发速度。请参考 [Gii](tool-gii.md) 章节了解更多内容。 +======= +> Info: Gii 被设计成高度可定制和可扩展的代码生成工具。使用它可以大幅提高应用开发速度。请参考 [Gii](tool-gii.md) 章节了解更多内容。 +>>>>>>> .merge_file_a02624 总结 diff --git a/docs/guide-zh-CN/start-hello.md b/docs/guide-zh-CN/start-hello.md index 62642483d9..5bc9f9ef9c 100644 --- a/docs/guide-zh-CN/start-hello.md +++ b/docs/guide-zh-CN/start-hello.md @@ -18,7 +18,11 @@ 为了 “Hello”,需要创建一个 `say` [操作](structure-controllers.md#creating-actions),从请求中接收 `message` 参数并显示给最终用户。如果请求没有提供 `message` 参数,操作将显示默认参数 “Hello”。 +<<<<<<< .merge_file_a05440 > 补充:[操作](structure-controllers.md#creating-actions)是最终用户可以直接访问并执行的对象。操作被组织在[控制器](structure-controllers.md)中。一个操作的执行结果就是最终用户收到的响应内容。 +======= +> Info: [操作](structure-controllers.md#creating-actions)是最终用户可以直接访问并执行的对象。操作被组织在[控制器](structure-controllers.md)中。一个操作的执行结果就是最终用户收到的响应内容。 +>>>>>>> .merge_file_a02836 操作必须声明在[控制器](structure-controllers.md)中。为了简单起见,你可以直接在 `SiteController` 控制器里声明 `say` 操作。这个控制器是由文件 `controllers/SiteController.php` 定义的。以下是一个操作的声明: @@ -83,11 +87,19 @@ http://hostname/index.php?r=site/say&message=Hello+World 如果你省略 URL 中的 `message` 参数,将会看到页面只显示 “Hello”。这是因为 `message` 被作为一个参数传给 `actionSay()` 方法,当省略它时,参数将使用默认的 `“Hello”` 代替。 +<<<<<<< .merge_file_a05440 > 补充:新页面和其它页面使用同样的头部和尾部是因为 [[yii\web\Controller::render()|render()]] 方法会自动把 `say` 视图执行的结果嵌入称为[布局](structure-views.md#layouts)的文件中,本例中是 `views/layouts/main.php`。 上面 URL 中的参数 `r` 需要更多解释。它代表[路由](runtime-routing.md),是整个应用级的,指向特定操作的独立 ID。路由格式是 `控制器 ID/操作 ID`。应用接受请求的时候会检查参数,使用控制器 ID 去确定哪个控制器应该被用来处理请求。然后相应控制器将使用操作 ID 去确定哪个操作方法将被用来做具体工作。上述例子中,路由 `site/say` 将被解析至 `SiteController` 控制器和其中的 `say` 操作。因此 `SiteController::actionSay()` 方法将被调用处理请求。 > 补充:与操作一样,一个应用中控制器同样有唯一的 ID。控制器 ID 和操作 ID 使用同样的命名规则。控制器的类名源自于控制器 ID,移除了连字符,每个单词首字母大写,并加上 `Controller` 后缀。例子:控制器 ID `post-comment` 相当于控制器类名 `PostCommentController`。 +======= +> Info: 新页面和其它页面使用同样的头部和尾部是因为 [[yii\web\Controller::render()|render()]] 方法会自动把 `say` 视图执行的结果嵌入称为[布局](structure-views.md#layouts)的文件中,本例中是 `views/layouts/main.php`。 + +上面 URL 中的参数 `r` 需要更多解释。它代表[路由](runtime-routing.md),是整个应用级的,指向特定操作的独立 ID。路由格式是 `控制器 ID/操作 ID`。应用接受请求的时候会检查参数,使用控制器 ID 去确定哪个控制器应该被用来处理请求。然后相应控制器将使用操作 ID 去确定哪个操作方法将被用来做具体工作。上述例子中,路由 `site/say` 将被解析至 `SiteController` 控制器和其中的 `say` 操作。因此 `SiteController::actionSay()` 方法将被调用处理请求。 + +> Info: 与操作一样,一个应用中控制器同样有唯一的 ID。控制器 ID 和操作 ID 使用同样的命名规则。控制器的类名源自于控制器 ID,移除了连字符,每个单词首字母大写,并加上 `Controller` 后缀。例子:控制器 ID `post-comment` 相当于控制器类名 `PostCommentController`。 +>>>>>>> .merge_file_a02836 总结 diff --git a/docs/guide-zh-CN/start-installation.md b/docs/guide-zh-CN/start-installation.md index 882350bb95..24b8920902 100644 --- a/docs/guide-zh-CN/start-installation.md +++ b/docs/guide-zh-CN/start-installation.md @@ -1,13 +1,17 @@ 安装 Yii ============== +<<<<<<< .merge_file_a03504 <<<<<<< HEAD 你可以通过两种方式安装 Yii:使用 [Composer](http://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](structure-extensions.md)或更新 Yii 了。 ======= 你可以通过两种方式安装 Yii:使用 [Composer](https://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](structure-extensions.md)或更新 Yii 了。 >>>>>>> yiichina/master +======= +你可以通过两种方式安装 Yii:使用 [Composer](https://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](structure-extensions.md)或更新 Yii 了。 +>>>>>>> .merge_file_a06364 -> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。 +> Note: 和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。 通过 Composer 安装 @@ -15,11 +19,15 @@ 如果还没有安装 Composer,你可以按 [getcomposer.org](https://getcomposer.org/download/) 中的方法安装。在 Linux 和 Mac OS X 中可以运行如下命令: +<<<<<<< .merge_file_a03504 <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sS https://getcomposer.org/installer | php >>>>>>> yiichina/master +======= + curl -sS https://getcomposer.org/installer | php +>>>>>>> .merge_file_a06364 mv composer.phar /usr/local/bin/composer 在 Windows 中,你需要下载并运行 [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe)。 @@ -30,24 +38,28 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下命令即可安装 Yii : +<<<<<<< .merge_file_a03504 <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= + composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> .merge_file_a06364 composer create-project --prefer-dist yiisoft/yii2-app-basic basic 第一条命令安装 [Composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/),它是通过 Composer 管理 bower 和 npm 包所必须的,此命令全局生效,一劳永逸。 第二条命令会将 Yii 安装在名为 `basic` 的目录中,你也可以随便选择其他名称。 -> 注意:在安装过程中 Composer 可能会询问你 GitHub 账户的登录信息,因为可能在使用中超过了 GitHub API +> Note: 在安装过程中 Composer 可能会询问你 GitHub 账户的登录信息,因为可能在使用中超过了 GitHub API (对匿名用户的)使用限制。因为 Composer 需要为所有扩展包从 GitHub 中获取大量信息,所以超限非常正常。(译注:也意味着作为程序猿没有 GitHub 账号,就真不能愉快地玩耍了)登陆 GitHub 之后可以得到更高的 API 限额,这样 Composer 才能正常运行。更多细节请参考 [Composer 文档](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens)(该段 Composer 中文文档[期待您的参与](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens))。 -> 技巧:如果你想安装 Yii 的最新开发版本,可以使用以下命令代替,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability)([中文版](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/04-schema.md#minimum-stability)): +> Tip: 如果你想安装 Yii 的最新开发版本,可以使用以下命令代替,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability)([中文版](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/04-schema.md#minimum-stability)): > > composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic > @@ -109,15 +121,15 @@ http://localhost/basic/web/index.php 配置 Web 服务器 ----------------------- ->补充:如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。 +> Info: 如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。 通过上述方法安装的应用程序在 Windows,Max OS X,Linux 中的 [Apache HTTP 服务器](http://httpd.apache.org/)或 [Nginx HTTP 服务器](http://nginx.org/)且PHP版本为5.4或更高都可以直接运行。Yii 2.0 也兼容 Facebook 公司的 [HHVM](http://hhvm.com/),由于 HHVM 和标准 PHP 在边界案例上有些地方略有不同,在使用 HHVM 时需稍作处理。 在生产环境的服务器上,你可能会想配置服务器让应用程序可以通过 URL `http://www.example.com/index.php` 访问而不是 `http://www.example.com/basic/web/index.php`。这种配置需要将 Web 服务器的文档根目录指向 `basic/web` 目录。可能你还会想隐藏掉 URL 中的 `index.php`,具体细节在 [URL 解析和生成](runtime-url-handling.md)一章中有介绍,你将学到如何配置 Apache 或 Nginx 服务器实现这些目标。 ->补充:将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。 +> Info: 将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。 ->补充:如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。 +> Info: 如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。 ### 推荐使用的 Apache 配置 diff --git a/docs/guide-zh-CN/start-workflow.md b/docs/guide-zh-CN/start-workflow.md index b27c0a3c1a..4f789f8641 100644 --- a/docs/guide-zh-CN/start-workflow.md +++ b/docs/guide-zh-CN/start-workflow.md @@ -3,7 +3,11 @@ 安装 Yii 后,就有了一个可运行的 Yii 应用,根据配置的不同,可以通过 `http://hostname/basic/web/index.php` 或 `http://hostname/index.php` 访问。本章节将介绍应用的内建功能,如何组织代码,以及一般情况下应用如何处理请求。 +<<<<<<< .merge_file_a07004 > 补充:为简单起见,在整个“入门”板块都假定你已经把 `basic/web` 设为 Web 服务器根目录并配置完毕,你访问应用的地址会是 `http://lostname/index.php` 或类似的。请按需调整 URL。 +======= +> Info: 为简单起见,在整个“入门”板块都假定你已经把 `basic/web` 设为 Web 服务器根目录并配置完毕,你访问应用的地址会是 `http://lostname/index.php` 或类似的。请按需调整 URL。 +>>>>>>> .merge_file_a04932 功能 ------------- diff --git a/docs/guide-zh-CN/structure-application-components.md b/docs/guide-zh-CN/structure-application-components.md index 211c545e07..6726e0bc50 100644 --- a/docs/guide-zh-CN/structure-application-components.md +++ b/docs/guide-zh-CN/structure-application-components.md @@ -40,7 +40,7 @@ ] ``` -> 补充:请谨慎注册太多应用组件,应用组件就像全局变量,使用太多可能加大测试和维护的难度。 +> Info: 请谨慎注册太多应用组件,应用组件就像全局变量,使用太多可能加大测试和维护的难度。 一般情况下可以在需要时再创建本地组件。 diff --git a/docs/guide-zh-CN/structure-applications.md b/docs/guide-zh-CN/structure-applications.md index 2fd025d2ab..be053b07e4 100644 --- a/docs/guide-zh-CN/structure-applications.md +++ b/docs/guide-zh-CN/structure-applications.md @@ -4,7 +4,7 @@ 应用主体是管理 Yii 应用系统整体结构和生命周期的对象。 每个Yii应用系统只能包含一个应用主体,应用主体在 [入口脚本](structure-entry-scripts.md) 中创建并能通过表达式 `\Yii::$app` 全局范围内访问。 -> 补充: 当我们说"一个应用",它可能是一个应用主体对象,也可能是一个应用系统,是根据上下文来决定[译:中文为避免歧义,Application翻译为应用主体]。 +> Info: 当我们说"一个应用",它可能是一个应用主体对象,也可能是一个应用系统,是根据上下文来决定[译:中文为避免歧义,Application翻译为应用主体]。 Yii有两种应用主体: [[yii\web\Application|网页应用主体]] and [[yii\console\Application|控制台应用主体]], 如名称所示,前者主要处理网页请求,后者处理控制台请求。 @@ -117,7 +117,7 @@ $config = require(__DIR__ . '/../config/web.php'); ] ``` -> 补充: 如果模块ID和应用组件ID同名,优先使用应用组件ID,如果你想用模块ID,可以使用如下无名称函数返回模块ID。 +> Info: 如果模块ID和应用组件ID同名,优先使用应用组件ID,如果你想用模块ID,可以使用如下无名称函数返回模块ID。 > ```php [ function () { @@ -347,10 +347,14 @@ $width = \Yii::$app->params['thumbnail.size'][0]; 该属性用数组列表指定应用安装和使用的 [扩展](structure-extensions.md),默认使用`@vendor/yiisoft/extensions.php`文件返回的数组。 <<<<<<< HEAD +<<<<<<< HEAD 当你使用 [Composer](http://getcomposer.org) 安装扩展,`extensions.php` 会被自动生成和维护更新。 ======= 当你使用 [Composer](https://getcomposer.org) 安装扩展,`extensions.php` 会被自动生成和维护更新。 >>>>>>> yiichina/master +======= +当你使用 [Composer](https://getcomposer.org) 安装扩展,`extensions.php` 会被自动生成和维护更新。 +>>>>>>> master 所以大多数情况下,不需要配置该属性。 特殊情况下你想自己手动维护扩展,可以参照如下配置该属性: @@ -412,11 +416,15 @@ $width = \Yii::$app->params['thumbnail.size'][0]; #### [[yii\base\Application::vendorPath|vendorPath]] +<<<<<<< HEAD <<<<<<< HEAD 该属性指定 [Composer](http://getcomposer.org) 管理的供应商路径,该路径包含应用使用的包括Yii框架在内的所有第三方库。 ======= 该属性指定 [Composer](https://getcomposer.org) 管理的供应商路径,该路径包含应用使用的包括Yii框架在内的所有第三方库。 >>>>>>> yiichina/master +======= +该属性指定 [Composer](https://getcomposer.org) 管理的供应商路径,该路径包含应用使用的包括Yii框架在内的所有第三方库。 +>>>>>>> master 默认值为带别名的 `@app/vendor` 。 可以配置它为一个目录或者路径 [别名](concept-aliases.md),当你修改时,务必修改对应的 Composer 配置。 diff --git a/docs/guide-zh-CN/structure-assets.md b/docs/guide-zh-CN/structure-assets.md index 5c089b3af4..7f37fcfa88 100644 --- a/docs/guide-zh-CN/structure-assets.md +++ b/docs/guide-zh-CN/structure-assets.md @@ -69,7 +69,7 @@ class AppAsset extends AssetBundle - 绝对URL地址表示为外部JavaScript文件,如 `http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js` 或 `//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js`. -* [[yii\web\AssetBundle::css|css]]: 一个包含该资源包JavaScript文件的数组,该数组格式和 [[yii\web\AssetBundle::js|js]] 相同。 +* [[yii\web\AssetBundle::css|css]]: 一个包含该资源包CSS文件的数组,该数组格式和 [[yii\web\AssetBundle::js|js]] 相同。 * [[yii\web\AssetBundle::depends|depends]]: 一个列出该资源包依赖的其他资源包(后两节有详细介绍)。 * [[yii\web\AssetBundle::jsOptions|jsOptions]]: 当调用[[yii\web\View::registerJsFile()]]注册该包 *每个* JavaScript文件时, 指定传递到该方法的选项。 @@ -86,19 +86,19 @@ class AppAsset extends AssetBundle * 源资源: 资源文件和PHP源代码放在一起,不能被Web直接访问,为了使用这些源资源,它们要拷贝到一个可Web访问的Web目录中 成为发布的资源,这个过程称为*发布资源*,随后会详细介绍。 * 发布资源: 资源文件放在可通过Web直接访问的Web目录中; -* 外部资源: 资源文件放在你的Web应用不同的Web服务器上; +* 外部资源: 资源文件放在与你的Web应用不同的Web服务器上; 当定义资源包类时候,如果你指定了[[yii\web\AssetBundle::sourcePath|sourcePath]] 属性,就表示任何使用相对路径的资源会被 当作源资源;如果没有指定该属性,就表示这些资源为发布资源(因此应指定[[yii\web\AssetBundle::basePath|basePath]] 和 [[yii\web\AssetBundle::baseUrl|baseUrl]] 让Yii知道它们的位置)。 -推荐将资源文件放到Web目录以避免不必要的发布资源过程,这就是之前的例子指定 +推荐将资源文件放到Web目录以避免不必要的发布资源过程,这就是之前的例子:指定 [[yii\web\AssetBundle::basePath|basePath]] 而不是 [[yii\web\AssetBundle::sourcePath|sourcePath]]. 对于 [扩展](structure-extensions.md)来说,由于它们的资源和源代码都在不能Web访问的目录下, 在定义资源包类时必须指定[[yii\web\AssetBundle::sourcePath|sourcePath]]属性。 -> 注意: [[yii\web\AssetBundle::sourcePath|source path]] 属性不要用`@webroot/assets`,该路径默认为 +> Note: [[yii\web\AssetBundle::sourcePath|source path]] 属性不要用`@webroot/assets`,该路径默认为 [[yii\web\AssetManager|asset manager]]资源管理器将源资源发布后存储资源的路径,该路径的所有内容会认为是临时文件, 可能会被删除。 @@ -113,11 +113,15 @@ class AppAsset extends AssetBundle 在`AppAsset` 示例中,资源包依赖其他两个资源包: [[yii\web\YiiAsset]] 和 [[yii\bootstrap\BootstrapAsset]] 也就是该资源包的CSS和JavaScript文件要在这两个依赖包的文件包含 *之后* 才包含。 +<<<<<<< HEAD <<<<<<< HEAD 资源依赖关系是可传递,也就是人说A依赖B,B依赖C,那么A也依赖C。 ======= 资源依赖关系是可传递,也就是说A依赖B,B依赖C,那么A也依赖C。 >>>>>>> yiichina/master +======= +资源依赖关系是可传递,也就是说A依赖B,B依赖C,那么A也依赖C。 +>>>>>>> master ### 资源选项 @@ -127,7 +131,7 @@ class AppAsset extends AssetBundle 这些属性值会分别传递给 [[yii\web\View::registerCssFile()]] 和 [[yii\web\View::registerJsFile()]] 方法, 在[视图](structure-views.md) 调用这些方法包含CSS和JavaScript文件时。 -> 注意: 在资源包类中设置的选项会应用到该包中 *每个* CSS/JavaScript 文件,如果想对每个文件使用不同的选项, +> Note: 在资源包类中设置的选项会应用到该包中 *每个* CSS/JavaScript 文件,如果想对每个文件使用不同的选项, 应创建不同的资源包并在每个包中使用一个选项集。 例如,只想IE9或更高的浏览器包含一个CSS文件,可以使用如下选项: @@ -172,7 +176,7 @@ public $jsOptions = ['position' => \yii\web\View::POS_HEAD]; 应设置 [[yii\web\AssetBundle::sourcePath|sourcePath]] 属性为`@bower/PackageName` 或 `@npm/PackageName`, 因为根据别名Composer会安装Bower或NPM包到对应的目录下。 -> 注意: 一些包会将它们分布式文件放到一个子目录中,对于这种情况,应指定子目录作为 +> Note: 一些包会将它们分布式文件放到一个子目录中,对于这种情况,应指定子目录作为 [[yii\web\AssetBundle::sourcePath|sourcePath]]属性值,例如,[[yii\web\JqueryAsset]]使用 `@bower/jquery/dist` 而不是 `@bower/jquery`。 @@ -196,7 +200,7 @@ AppAsset::register($this); // $this 代表视图对象 ### 自定义资源包 -Yii通过名为 `assetManager`的应用组件实现[[yii\web\AssetManager] 来管理应用组件, +Yii通过名为 `assetManager`的应用组件实现[[yii\web\AssetManager]] 来管理应用组件, 通过配置[[yii\web\AssetManager::bundles]] 属性,可以自定义资源包的行为, 例如,[[yii\web\JqueryAsset]] 资源包默认从jquery Bower包中使用`jquery.js` 文件, 为了提高可用性和性能,你可能需要从Google服务器上获取jquery文件,可以在应用配置中配置`assetManager`,如下所示: @@ -222,7 +226,7 @@ return [ 可通过类似[[yii\web\AssetManager::bundles]]配置多个资源包,数组的键应为资源包的类名(最开头不要反斜杠), 数组的值为对应的[配置数组](concept-configurations.md). -> 提示: 可以根据条件判断使用哪个资源,如下示例为如何在开发环境用`jquery.js`,否则用`jquery.min.js`: +> Tip: 可以根据条件判断使用哪个资源,如下示例为如何在开发环境用`jquery.js`,否则用`jquery.min.js`: > > ```php > 'yii\web\JqueryAsset' => [ @@ -279,7 +283,7 @@ return [ 如果数组任何键对比为资源文件的最后文件名(如果有的话前缀为 [[yii\web\AssetBundle::sourcePath]]),对应的值为替换原来的资源。 例如,资源文件`my/path/to/jquery.js` 匹配键 `jquery.js`. -> 注意: 只有相对相对路径指定的资源对应到资源部署,替换的资源路径可以为绝对路径,也可为和[[yii\web\AssetManager::basePath]]相关的路径。 +> Note: 只有相对相对路径指定的资源对应到资源部署,替换的资源路径可以为绝对路径,也可为和[[yii\web\AssetManager::basePath]]相关的路径。 ### 资源发布 @@ -387,7 +391,7 @@ return [ 数组的键为文件扩展名(前面不要.),数组的值为目标资源文件扩展名和执行资源转换的命令, 命令中的标记 `{from}` 和`{to}`会分别被源资源文件路径和目标资源文件路径替代。 -> 补充: 除了以上方式,也有其他的方式来处理扩展语法资源,例如,可使用编译工具如[grunt](http://gruntjs.com/) +> Info: 除了以上方式,也有其他的方式来处理扩展语法资源,例如,可使用编译工具如[grunt](http://gruntjs.com/) 来监控并自动转换扩展语法资源,此时,应使用资源包中编译后的CSS/Javascript文件而不是原始文件。 @@ -400,7 +404,7 @@ return [ >>>>>>> yiichina/master 通常的方式是在页面中合并并压缩多个CSS/JavaScript 文件为一个或很少的几个文件,并使用压缩后的文件而不是原始文件。 -> 补充: 合并和压缩资源通常在应用在产品上线模式,在开发模式下使用原始的CSS/JavaScript更方便调试。 +> Info: 合并和压缩资源通常在应用在产品上线模式,在开发模式下使用原始的CSS/JavaScript更方便调试。 接下来介绍一种合并和压缩资源文件而不需要修改已有代码的方式: @@ -422,11 +426,11 @@ return [ 鉴定你的应用有两个页面X 和 Y,页面X使用资源包A,B和C,页面Y使用资源包B,C和D。 -有两种方式划分这些资源包,一种使用一个组包含所有资源包,另一种是将(A,B,C)放在组X,(B,C,C)放在组Y, +有两种方式划分这些资源包,一种使用一个组包含所有资源包,另一种是将(A,B,C)放在组X,(B,C,D)放在组Y, 哪种方式更好?第一种方式优点是两个页面使用相同的已合并CSS和JavaScript文件使HTTP缓存更高效,另一方面,由于单个组包含所有文件, 已合并的CSS和Javascipt文件会更大,因此会增加文件传输时间,在这个示例中,我们使用第一种方式,也就是用一个组包含所有包。 -> 补充: 将资源包分组并不是无价值的,通常要求分析现实中不同页面各种资源的数据量,开始时为简便使用一个组。 +> Info: 将资源包分组并不是无价值的,通常要求分析现实中不同页面各种资源的数据量,开始时为简便使用一个组。 在所有包中使用工具(例如 [Closure Compiler](https://developers.google.com/closure/compiler/), [YUI Compressor](https://github.com/yui/yuicompressor/)) 来合并和压缩CSS和JavaScript文件, @@ -526,7 +530,7 @@ return [ 应修改该文件的`bundles`的选项指定哪些包你想要合并,在`targets`选项中应指定这些包如何分组,如前述的可以指定一个或多个组。 -> 注意: 由于在控制台应用别名 `@webroot` and `@web` 不可用,应在配置中明确指定它们。 +> Note: 由于在控制台应用别名 `@webroot` and `@web` 不可用,应在配置中明确指定它们。 JavaScript文件会被合并压缩后写入到`js/all-{hash}.js`文件,其中 {hash} 会被结果文件的哈希值替换。 @@ -545,4 +549,4 @@ yii asset assets.php config/assets-prod.php 生成的配置文件可以在应用配置中包含,如最后一小节所描述的。 -> 补充: 使用`asset` 命令并不是唯一一种自动合并和压缩过程的方法,可使用优秀的工具[grunt](http://gruntjs.com/)来完成这个过程。 +> Info: 使用`asset` 命令并不是唯一一种自动合并和压缩过程的方法,可使用优秀的工具[grunt](http://gruntjs.com/)来完成这个过程。 diff --git a/docs/guide-zh-CN/structure-controllers.md b/docs/guide-zh-CN/structure-controllers.md index 1d8efab652..3550a0e5fd 100644 --- a/docs/guide-zh-CN/structure-controllers.md +++ b/docs/guide-zh-CN/structure-controllers.md @@ -61,11 +61,15 @@ class PostController extends Controller 终端用户通过所谓的*路由*寻找到操作,路由是包含以下部分的字符串: +<<<<<<< HEAD <<<<<<< HEAD * 模型ID: 仅存在于控制器属于非应用的[模块](structure-modules.md); ======= * 模块ID: 仅存在于控制器属于非应用的[模块](structure-modules.md); >>>>>>> yiichina/master +======= +* 模块ID: 仅存在于控制器属于非应用的[模块](structure-modules.md); +>>>>>>> master * 控制器ID: 同应用(或同模块如果为模块下的控制器)下唯一标识控制器的字符串; * 操作ID: 同控制器下唯一标识操作的字符串。 @@ -133,9 +137,9 @@ class SiteController extends Controller 控制器类必须能被 [自动加载](concept-autoloading.md),所以在上面的例子中, 控制器`article` 类应在 [别名](concept-aliases.md) 为`@app/controllers/ArticleController.php`的文件中定义, -控制器`admin/post2-comment`应在`@app/controllers/admin/Post2CommentController.php`文件中。 +控制器`admin/post-comment`应在`@app/controllers/admin/PostCommentController.php`文件中。 -> 补充: 最后一个示例 `admin/post2-comment` 表示你可以将控制器放在 +> Info: 最后一个示例 `admin/post-comment` 表示你可以将控制器放在 [[yii\base\Application::controllerNamespace|controller namespace]]控制器命名空间下的子目录中, 在你不想用 [模块](structure-modules.md) 的情况下给控制器分类,这种方式很有用。 @@ -231,7 +235,7 @@ class SiteController extends Controller 例如`index` 转成 `actionIndex`, `hello-world` 转成 `actionHelloWorld`。 -> 注意: 操作方法的名字*大小写敏感*,如果方法名称为`ActionIndex`不会认为是操作方法, +> Note: 操作方法的名字*大小写敏感*,如果方法名称为`ActionIndex`不会认为是操作方法, 所以请求`index`操作会返回一个异常,也要注意操作方法必须是公有的,私有或者受保护的方法不能定义成内联操作。 diff --git a/docs/guide-zh-CN/structure-entry-scripts.md b/docs/guide-zh-CN/structure-entry-scripts.md index ab54adc374..08f3076e9d 100644 --- a/docs/guide-zh-CN/structure-entry-scripts.md +++ b/docs/guide-zh-CN/structure-entry-scripts.md @@ -11,10 +11,14 @@ Web 应用的入口脚本必须放在终端用户能够访问的目录下,通 * 定义全局常量; <<<<<<< HEAD +<<<<<<< HEAD * 注册 [Composer 自动加载器](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * 注册 [Composer 自动加载器](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* 注册 [Composer 自动加载器](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * 包含 [[Yii]] 类文件; * 加载应用配置; * 创建一个[应用](structure-applications.md)实例并配置; @@ -62,10 +66,6 @@ $config = require(__DIR__ . '/../config/web.php'); defined('YII_DEBUG') or define('YII_DEBUG', true); -// fcgi 默认没有定义 STDIN 和 STDOUT -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - // 注册 Composer 自动加载器 require(__DIR__ . '/vendor/autoload.php'); diff --git a/docs/guide-zh-CN/structure-extensions.md b/docs/guide-zh-CN/structure-extensions.md index 68f23bad38..3995624e48 100644 --- a/docs/guide-zh-CN/structure-extensions.md +++ b/docs/guide-zh-CN/structure-extensions.md @@ -555,7 +555,7 @@ If it is the first time you release an extension, you should register it on a Co on the VCS repository of your extension and notify the Composer repository about the new release. People will then be able to find the new release, and install or update the extension through the Composer repository. -In the releases of your extension, besides code files you should also consider including the followings to +In the releases of your extension, besides code files you should also consider including the following to help other people learn about and use your extension: * A readme file in the package root directory: it describes what your extension does and how to install and use it. diff --git a/docs/guide-zh-CN/structure-filters.md b/docs/guide-zh-CN/structure-filters.md index 5c327a1fe7..91023a0b6c 100644 --- a/docs/guide-zh-CN/structure-filters.md +++ b/docs/guide-zh-CN/structure-filters.md @@ -37,7 +37,7 @@ public function behaviors() 申明之后,过滤器会应用到所属该模块或应用主体的 *所有* 控制器动作, 除非像上述一样配置过滤器的 [[yii\base\ActionFilter::only|only]] 和 [[yii\base\ActionFilter::except|except]] 属性。 -> 补充: 在模块或应用主体中申明过滤器,在[[yii\base\ActionFilter::only|only]] 和 [[yii\base\ActionFilter::except|except]] +> Info: 在模块或应用主体中申明过滤器,在[[yii\base\ActionFilter::only|only]] 和 [[yii\base\ActionFilter::except|except]] 属性中使用[路由](structure-controllers.md#routes) 代替动作ID, 因为在模块或应用主体中只用动作ID并不能唯一指定到具体动作。. @@ -208,7 +208,7 @@ use yii\web\Response; ]; ``` -> 补充: 如果请求中没有检测到内容格式和语言,使用[[formats]]和[[languages]]第一个配置项。 +> Info: 如果请求中没有检测到内容格式和语言,使用[[formats]]和[[languages]]第一个配置项。 diff --git a/docs/guide-zh-CN/structure-models.md b/docs/guide-zh-CN/structure-models.md index 6fda72be2c..e76d0a0d45 100644 --- a/docs/guide-zh-CN/structure-models.md +++ b/docs/guide-zh-CN/structure-models.md @@ -15,7 +15,7 @@ `Model` 类也是更多高级模型如[Active Record 活动记录](db-active-record.md)的基类, 更多关于这些高级模型的详情请参考相关手册。 -> 补充:模型并不强制一定要继承[[yii\base\Model]],但是由于很多组件支持[[yii\base\Model]],最好使用它做为模型基类。 +> Info: 模型并不强制一定要继承[[yii\base\Model]],但是由于很多组件支持[[yii\base\Model]],最好使用它做为模型基类。 ## 属性 @@ -138,11 +138,15 @@ public function attributeLabels() 甚至可以根据条件定义标签,例如通过使用模型的 [scenario场景](#scenarios), 可对相同的属性返回不同的标签。 +<<<<<<< HEAD <<<<<<< HEAD > 补充:属性标签是 [视图](structure-views.md)一部分,但是在模型中申明标签通常非常方便,并可行程非常简洁可重用代码。 ======= > 补充:属性标签是 [视图](structure-views.md)一部分,但是在模型中申明标签通常非常方便,并可形成非常简洁可重用代码。 >>>>>>> yiichina/master +======= +> Info: 属性标签是 [视图](structure-views.md)一部分,但是在模型中申明标签通常非常方便,并可行程非常简洁可重用代码。 +>>>>>>> master ## 场景 @@ -182,7 +186,7 @@ class User extends ActiveRecord } ``` -> 补充:在上述和下述的例子中,模型类都是继承[[yii\db\ActiveRecord]], +> Info: 在上述和下述的例子中,模型类都是继承[[yii\db\ActiveRecord]], 因为多场景的使用通常发生在[Active Record](db-active-record.md) 类中. `scenarios()` 方法返回一个数组,数组的键为场景名,值为对应的 *active attributes活动属性*。 @@ -317,7 +321,7 @@ public function scenarios() } ``` -> 补充: 块赋值只应用在安全属性上,因为你想控制哪些属性会被终端用户输入数据所修改, +> Info: 块赋值只应用在安全属性上,因为你想控制哪些属性会被终端用户输入数据所修改, 例如,如果 `User` 模型有一个`permission`属性对应用户的权限, 你可能只想让这个属性在后台界面被管理员修改。 @@ -433,7 +437,7 @@ public function fields() } ``` -> 警告:由于模型的所有属性会被包含在导出数组,最好检查数据确保没包含敏感数据, +> Warning: 由于模型的所有属性会被包含在导出数组,最好检查数据确保没包含敏感数据, > 如果有敏感数据,应覆盖 `fields()` 方法过滤掉,在上述列子中,我们选择过滤掉 > `auth_key`, `password_hash` and `password_reset_token`。 diff --git a/docs/guide-zh-CN/structure-modules.md b/docs/guide-zh-CN/structure-modules.md index d050398527..5c65a1fbb4 100644 --- a/docs/guide-zh-CN/structure-modules.md +++ b/docs/guide-zh-CN/structure-modules.md @@ -152,7 +152,7 @@ $module = MyModuleClass::getInstance(); 其中 `MyModuleClass` 对应你想要的模块类,`getInstance()` 方法返回当前请求的模块类实例, 如果模块没有被请求,该方法会返回空,注意不需要手动创建一个模块类,因为手动创建的和Yii处理请求时自动创建的不同。 -> 补充: 当开发模块时,你不能假定模块使用固定的ID,因为在应用或其他没模块中,模块可能会对应到任意的ID, +> Info: 当开发模块时,你不能假定模块使用固定的ID,因为在应用或其他没模块中,模块可能会对应到任意的ID, 为了获取模块ID,应使用上述代码获取模块实例,然后通过`$module->id`获取模块ID。 也可以使用如下方式访问模块实例: diff --git a/docs/guide-zh-CN/structure-views.md b/docs/guide-zh-CN/structure-views.md index b61d446fb0..5bf4f1cf14 100644 --- a/docs/guide-zh-CN/structure-views.md +++ b/docs/guide-zh-CN/structure-views.md @@ -39,7 +39,7 @@ $this->title = 'Login'; 除了 `$this`之外,上述示例中的视图有其他预定义变量如 `$model`, 这些变量代表从[控制器](structure-controllers.md)或其他触发[视图渲染](#rendering-views)的对象 *传入* 到视图的数据。 -> 技巧: 将预定义变量列到视图文件头部注释处,这样可被IDE编辑器识别,也是生成视图文档的好方法。 +> Tip: 将预定义变量列到视图文件头部注释处,这样可被IDE编辑器识别,也是生成视图文档的好方法。 ### 安全 @@ -71,7 +71,7 @@ use yii\helpers\HtmlPurifier; ``` -> 技巧:HTMLPurifier在保证输出数据安全上做的不错,但性能不佳,如果你的应用需要高性能可考虑 +> Tip: HTMLPurifier在保证输出数据安全上做的不错,但性能不佳,如果你的应用需要高性能可考虑 [缓存](caching-overview.md) 过滤后的结果。 @@ -390,7 +390,7 @@ class PostController extends Controller 第二步,它决定第一步中布局的值和上下文模块对应到实际的布局文件,布局的值可为: - 路径别名 (如 `@app/views/layouts/main`). -- 绝对路径 (如 `/main`): 布局的值以斜杠开始,在应用的[[yii\base\Application::layoutPath|layout path] 布局路径 +- 绝对路径 (如 `/main`): 布局的值以斜杠开始,在应用的[[yii\base\Application::layoutPath|layout path]] 布局路径 中查找实际的布局文件,布局路径默认为 `@app/views/layouts`。 - 相对路径 (如 `main`): 在上下文模块的[[yii\base\Module::layoutPath|layout path]]布局路径中查找实际的布局文件, 布局路径默认为[[yii\base\Module::basePath|module directory]]模块目录下的`views/layouts` 目录。 diff --git a/docs/guide-zh-CN/structure-widgets.md b/docs/guide-zh-CN/structure-widgets.md index 0b4e9baee3..9aacd7d9b8 100644 --- a/docs/guide-zh-CN/structure-widgets.md +++ b/docs/guide-zh-CN/structure-widgets.md @@ -12,7 +12,7 @@ use yii\jui\DatePicker; 'date']) ?> ``` -Yii提供许多优秀的小部件,比如[[yii\widgets\ActiveForm|active form]], [yii\widgets\Menu|menu]], +Yii提供许多优秀的小部件,比如[[yii\widgets\ActiveForm|active form]], [[yii\widgets\Menu|menu]], [jQuery UI widgets](widget-jui.md), [Twitter Bootstrap widgets](widget-bootstrap.md)。 接下来介绍小部件的基本知识,如果你想了解某个小部件请参考对应的类API文档。 @@ -135,7 +135,7 @@ class HelloWidget extends Widget 如上所示,PHP输出缓冲在`init()`启动,所有在`init()` 和 `run()`方法之间的输出内容都会被获取,并在`run()`处理和返回。 -> 补充: 当你调用 [[yii\base\Widget::begin()]] 时会创建一个新的小部件实例并在构造结束时调用`init()`方法, +> Info: 当你调用 [[yii\base\Widget::begin()]] 时会创建一个新的小部件实例并在构造结束时调用`init()`方法, 在`end()`时会调用`run()`方法并输出返回结果。 如下代码显示如何使用这种 `HelloWidget`: diff --git a/docs/guide-zh-CN/tutorial-core-validators.md b/docs/guide-zh-CN/tutorial-core-validators.md index 3bd0fcb03c..1a2e4d0241 100644 --- a/docs/guide-zh-CN/tutorial-core-validators.md +++ b/docs/guide-zh-CN/tutorial-core-validators.md @@ -36,7 +36,7 @@ public function rules() - `strict`:是否要求待测输入必须严格匹配 `trueValue` 或 `falseValue`。默认为 `false`。 -> 注意:因为通过 HTML 表单传递的输入数据都是字符串类型,所以一般情况下你都需要保持 +> Note: 因为通过 HTML 表单传递的输入数据都是字符串类型,所以一般情况下你都需要保持 [[yii\validators\BooleanValidator::strict|strict]] 属性为假。 @@ -126,7 +126,7 @@ function foo($model, $attribute) { } ``` -> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 +> Info: 如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 ## [[yii\validators\NumberValidator|double(双精度浮点型)]] @@ -244,7 +244,7 @@ function foo($model, $attribute) { - `filter`:用于定义滤镜的 PHP 回调函数。可以为全局函数名,匿名函数,或其他。该函数的样式必须是 `function ($value) { return $newValue; }`。该属性不能省略,必须设置。 - `skipOnArray`:是否在输入值为数组时跳过滤镜。默认为 false。请注意如果滤镜不能处理数组输入,你就应该把该属性设为 true。否则可能会导致 PHP Error 的发生。 -> 技巧:如果你只是想要用 trim 处理下输入值,你可以直接用 [trim](#trim) 验证器的。 +> Tip: 如果你只是想要用 trim 处理下输入值,你可以直接用 [trim](#trim) 验证器的。 ## [[yii\validators\ImageValidator|image(图片)]] @@ -345,7 +345,7 @@ function foo($model, $attribute) { 当设置了 `requiredValue` 属性时,若该属性为 true,输入值与 `requiredValue` 的比对会同时检查数据类型。 -> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 +> Info: 如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 ## [[yii\validators\SafeValidator|safe(安全)]] diff --git a/docs/guide-zh-CN/tutorial-yii-integration.md b/docs/guide-zh-CN/tutorial-yii-integration.md index b53d6f0e54..717bfae57a 100644 --- a/docs/guide-zh-CN/tutorial-yii-integration.md +++ b/docs/guide-zh-CN/tutorial-yii-integration.md @@ -96,7 +96,7 @@ new yii\web\Application($yiiConfig); // 千万别在这调用 run() 方法。( 如果你之前使用 Yii 1,大概你也有正在运行的 Yii 1 应用吧。不必用 Yii 2 重写整个应用,你也可以通过增添对哪些 Yii 2 独占功能的支持来增强这个系统。下面我们就来详细描述一下具体的实现过程。 -> 注意:Yii 2 需要 PHP 5.4+ 的版本。你需要确保你的服务器以及现有应用都可以支持 PHP 5.4。 +> Note: Yii 2 需要 PHP 5.4+ 的版本。你需要确保你的服务器以及现有应用都可以支持 PHP 5.4。 首先,参考前文板块中给出的方法,在已有的应用中安装 Yii 2。 diff --git a/docs/guide/README.md b/docs/guide/README.md index 600d2c8acb..bdb6d363c2 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -79,16 +79,22 @@ Working with Databases * [Active Record](db-active-record.md): The Active Record ORM, retrieving and manipulating records, and defining relations * [Migrations](db-migrations.md): Apply version control to your databases in a team development environment <<<<<<< HEAD +<<<<<<< HEAD * **TBD** [Sphinx](db-sphinx.md) * **TBD** [Redis](db-redis.md) * **TBD** [MongoDB](db-mongodb.md) * **TBD** [ElasticSearch](db-elasticsearch.md) ======= +======= +>>>>>>> master * [Sphinx](https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide/README.md) * [Redis](https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/README.md) * [MongoDB](https://github.com/yiisoft/yii2-mongodb/blob/master/docs/guide/README.md) * [ElasticSearch](https://github.com/yiisoft/yii2-elasticsearch/blob/master/docs/guide/README.md) +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master Getting Data from Users @@ -97,18 +103,22 @@ Getting Data from Users * [Creating Forms](input-forms.md) * [Validating Input](input-validation.md) * [Uploading Files](input-file-upload.md) -* [Collecting tabular input](input-tabular-input.md) +* [Collecting Tabular Input](input-tabular-input.md) * [Getting Data for Multiple Models](input-multiple-models.md) Displaying Data --------------- +<<<<<<< HEAD <<<<<<< HEAD * [Data Formatting](output-formatter.md) ======= * [Data Formatting](output-formatting.md) >>>>>>> yiichina/master +======= +* [Data Formatting](output-formatting.md) +>>>>>>> master * [Pagination](output-pagination.md) * [Sorting](output-sorting.md) * [Data Providers](output-data-providers.md) @@ -120,14 +130,21 @@ Displaying Data Security -------- +* [Overview](security-overview.md) * [Authentication](security-authentication.md) * [Authorization](security-authorization.md) * [Working with Passwords](security-passwords.md) <<<<<<< HEAD +<<<<<<< HEAD * [Auth Clients](security-auth-clients.md) ======= * [Auth Clients](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) >>>>>>> yiichina/master +======= +* [Cryptography](security-cryptography.md) +* [Views security](structure-views.md#security) +* [Auth Clients](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) +>>>>>>> master * [Best Practices](security-best-practices.md) @@ -158,6 +175,7 @@ RESTful Web Services Development Tools ----------------- +<<<<<<< HEAD <<<<<<< HEAD * [Debug Toolbar and Debugger](tool-debugger.md) * [Generating Code using Gii](tool-gii.md) @@ -167,6 +185,11 @@ Development Tools * [Generating Code using Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) * **TBD** [Generating API Documentation](https://github.com/yiisoft/yii2-apidoc) >>>>>>> yiichina/master +======= +* [Debug Toolbar and Debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) +* [Generating Code using Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) +* **TBD** [Generating API Documentation](https://github.com/yiisoft/yii2-apidoc) +>>>>>>> master Testing @@ -183,11 +206,15 @@ Testing Special Topics -------------- +<<<<<<< HEAD <<<<<<< HEAD * [Advanced Application Template](tutorial-advanced-app.md) ======= * [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) >>>>>>> yiichina/master +======= +* [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) +>>>>>>> master * [Building Application from Scratch](tutorial-start-from-scratch.md) * [Console Commands](tutorial-console.md) * [Core Validators](tutorial-core-validators.md) @@ -211,12 +238,17 @@ Widgets * LinkPager: **TBD** link to demo page * LinkSorter: **TBD** link to demo page <<<<<<< HEAD +<<<<<<< HEAD * [Bootstrap Widgets](widget-bootstrap.md) * [jQuery UI Widgets](widget-jui.md) ======= * [Bootstrap Widgets](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/README.md) * [jQuery UI Widgets](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md) >>>>>>> yiichina/master +======= +* [Bootstrap Widgets](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/README.md) +* [jQuery UI Widgets](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md) +>>>>>>> master Helpers diff --git a/docs/guide/caching-data.md b/docs/guide/caching-data.md index f109adef23..5e2bd0fc42 100644 --- a/docs/guide/caching-data.md +++ b/docs/guide/caching-data.md @@ -1,7 +1,7 @@ Data Caching ============ -Data caching is about storing some PHP variable in cache and retrieving it later from cache. +Data caching is about storing some PHP variables in cache and retrieving it later from cache. It is also the foundation for more advanced caching features, such as [query caching](#query-caching) and [page caching](caching-page.md). @@ -118,9 +118,9 @@ All cache components have the same base class [[yii\caching\Cache]] and thus sup value will be returned if the data item is not found in the cache or is expired/invalidated. * [[yii\caching\Cache::set()|set()]]: stores a data item identified by a key in cache. * [[yii\caching\Cache::add()|add()]]: stores a data item identified by a key in cache if the key is not found in the cache. -* [[yii\caching\Cache::mget()|mget()]]: retrieves multiple data items from cache with the specified keys. -* [[yii\caching\Cache::mset()|mset()]]: stores multiple data items in cache. Each item is identified by a key. -* [[yii\caching\Cache::madd()|madd()]]: stores multiple data items in cache. Each item is identified by a key. +* [[yii\caching\Cache::multiGet()|multiGet()]]: retrieves multiple data items from cache with the specified keys. +* [[yii\caching\Cache::multiSet()|multiSet()]]: stores multiple data items in cache. Each item is identified by a key. +* [[yii\caching\Cache::multiAdd()|multiAdd()]]: stores multiple data items in cache. Each item is identified by a key. If a key already exists in the cache, the data item will be skipped. * [[yii\caching\Cache::exists()|exists()]]: returns a value indicating whether the specified key is found in the cache. * [[yii\caching\Cache::delete()|delete()]]: removes a data item identified by a key from the cache. @@ -131,15 +131,19 @@ All cache components have the same base class [[yii\caching\Cache]] and thus sup this array instead to avoid this problem. Some cache storage, such as MemCache, APC, support retrieving multiple cached values in a batch mode, -which may reduce the overhead involved in retrieving cached data. The APIs [[yii\caching\Cache::mget()|mget()]] -and [[yii\caching\Cache::madd()|madd()]] are provided to exploit this feature. In case the underlying cache storage +which may reduce the overhead involved in retrieving cached data. The APIs [[yii\caching\Cache::multiGet()|multiGet()]] +and [[yii\caching\Cache::multiAdd()|multiAdd()]] are provided to exploit this feature. In case the underlying cache storage does not support this feature, it will be simulated. +<<<<<<< HEAD <<<<<<< HEAD Because [[yii\caching\Cache]] implements `ArrayAccess`, a cache component can be used like an array. The followings ======= Because [[yii\caching\Cache]] implements `ArrayAccess`, a cache component can be used like an array. The following >>>>>>> yiichina/master +======= +Because [[yii\caching\Cache]] implements `ArrayAccess`, a cache component can be used like an array. The following +>>>>>>> master are some examples: ```php @@ -244,17 +248,25 @@ Below is a summary of the available cache dependencies: - [[yii\caching\TagDependency]]: associates a cached data item with one or multiple tags. You may invalidate the cached data items with the specified tag(s) by calling [[yii\caching\TagDependency::invalidate()]]. +> Note: Avoid using [[yii\caching\Cache::exists()|exists()]] method along with dependencies. It does not check whether + the dependency associated with the cached data, if there is any, has changed. So a call to + [[yii\caching\Cache::get()|get()]] may return `false` while [[yii\caching\Cache::exists()|exists()]] returns `true`. + ## Query Caching Query caching is a special caching feature built on top of data caching. It is provided to cache the result of database queries. +<<<<<<< HEAD <<<<<<< HEAD Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache` application component. ======= Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache` [application component](#cache-components). >>>>>>> yiichina/master +======= +Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache` [application component](#cache-components). +>>>>>>> master The basic usage of query caching is as follows, assuming `$db` is a [[yii\db\Connection]] instance: ```php @@ -281,6 +293,20 @@ $result = Customer::getDb()->cache(function ($db) { and are potentially more efficient. +### Cache Flushing + +When you need to invalidate all the stored cache data, you can call [[yii\caching\Cache::flush()]]. + +You can flush the cache from the console by calling `yii cache/flush` as well. + - `yii cache`: lists the available caches in application + - `yii cache/flush cache1 cache2`: flushes the cache components `cache1`, `cache2` (you can pass multiple component + names separated with space) + - `yii cache/flush-all`: flushes all cache components in the application + +> Info: Console application uses a separate configuration file by default. Ensure, that you have the same caching +components in your web and console application configs to reach the proper effect. + + ### Configurations Query caching has three global configurable options through [[yii\db\Connection]]: diff --git a/docs/guide/concept-aliases.md b/docs/guide/concept-aliases.md index 43cab4a21e..f584e1edce 100644 --- a/docs/guide/concept-aliases.md +++ b/docs/guide/concept-aliases.md @@ -1,10 +1,12 @@ Aliases ======= -Aliases are used to represent file paths or URLs so that you don't have to hard-code absolute paths or URLs in your project. An alias must start with the `@` character to be differentiated from normal file paths and URLs. Yii has many pre-defined aliases already available. -For example, the alias `@yii` represents the installation path of the Yii framework; `@web` represents -the base URL for the currently running Web application. +Aliases are used to represent file paths or URLs so that you don't have to hard-code absolute paths or URLs in your +project. An alias must start with the `@` character to be differentiated from normal file paths and URLs. Alias defined +without leading `@` will be prefixed with `@` character. +Yii has many pre-defined aliases already available. For example, the alias `@yii` represents the installation path of +the Yii framework; `@web` represents the base URL for the currently running Web application. Defining Aliases ---------------- diff --git a/docs/guide/concept-autoloading.md b/docs/guide/concept-autoloading.md index 669c347970..8297d12d28 100644 --- a/docs/guide/concept-autoloading.md +++ b/docs/guide/concept-autoloading.md @@ -27,20 +27,28 @@ For example, if a class name and namespace is `foo\bar\MyClass`, the [alias](con would be `@foo/bar/MyClass.php`. In order for this alias to be resolvable into a file path, either `@foo` or `@foo/bar` must be a [root alias](concept-aliases.md#defining-aliases). +<<<<<<< HEAD <<<<<<< HEAD When using the [Basic Application Template](start-installation.md), you may put your classes under the top-level ======= When using the [Basic Project Template](start-installation.md), you may put your classes under the top-level >>>>>>> yiichina/master +======= +When using the [Basic Project Template](start-installation.md), you may put your classes under the top-level +>>>>>>> master namespace `app` so that they can be autoloaded by Yii without the need of defining a new alias. This is because `@app` is a [predefined alias](concept-aliases.md#predefined-aliases), and a class name like `app\components\MyClass` can be resolved into the class file `AppBasePath/components/MyClass.php`, according to the algorithm just described. +<<<<<<< HEAD <<<<<<< HEAD In the [Advanced Application Template](tutorial-advanced-app.md), each tier has its own root alias. For example, ======= In the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), each tier has its own root alias. For example, >>>>>>> yiichina/master +======= +In the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), each tier has its own root alias. For example, +>>>>>>> master the front-end tier has a root alias `@frontend`, while the back-end tier root alias is `@backend`. As a result, you may put the front-end classes under the namespace `frontend` while the back-end classes are under `backend`. This will allow these classes to be autoloaded by the Yii autoloader. @@ -75,10 +83,14 @@ When using the Yii autoloader together with other autoloaders, you should includ *after* all other autoloaders are installed. This will make the Yii autoloader the first one responding to any class autoloading request. For example, the following code is extracted from <<<<<<< HEAD +<<<<<<< HEAD the [entry script](structure-entry-scripts.md) of the [Basic Application Template](start-installation.md). The first ======= the [entry script](structure-entry-scripts.md) of the [Basic Project Template](start-installation.md). The first >>>>>>> yiichina/master +======= +the [entry script](structure-entry-scripts.md) of the [Basic Project Template](start-installation.md). The first +>>>>>>> master line installs the Composer autoloader, while the second line installs the Yii autoloader: ```php diff --git a/docs/guide/concept-behaviors.md b/docs/guide/concept-behaviors.md index e763adb4e6..a6d824f695 100644 --- a/docs/guide/concept-behaviors.md +++ b/docs/guide/concept-behaviors.md @@ -46,10 +46,13 @@ The above code defines the behavior class `app\components\MyBehavior`, with two `prop1` and `prop2`--and one method `foo()`. Note that property `prop2` is defined via the getter `getProp2()` and the setter `setProp2()`. This is the case because [[yii\base\Behavior]] extends [[yii\base\Object]] and therefore supports defining [properties](concept-properties.md) via getters and setters. -Because this class is a behavior, when it is attached to a component, that component will then also have the the `prop1` and `prop2` properties and the `foo()` method. +Because this class is a behavior, when it is attached to a component, that component will then also have the `prop1` and `prop2` properties and the `foo()` method. > Tip: Within a behavior, you can access the component that the behavior is attached to through the [[yii\base\Behavior::owner]] property. +> Note: In case [[yii\base\Behavior::__get()]] and/or [[yii\base\Behavior::__set()]] method of behavior is overridden you +need to override [[yii\base\Behavior::canGetProperty()]] and/or [[yii\base\Behavior::canSetProperty()]] as well. + Handling Component Events ------------------ @@ -250,7 +253,8 @@ Using `TimestampBehavior` ------------------------- To wrap up, let's take a look at [[yii\behaviors\TimestampBehavior]]. This behavior supports automatically -updating the timestamp attributes of an [[yii\db\ActiveRecord|Active Record]] model anytime the model is saved (e.g., on insert or update). +updating the timestamp attributes of an [[yii\db\ActiveRecord|Active Record]] model anytime the model is saved via +`insert()`, `update()` or `save()` method. First, attach this behavior to the [[yii\db\ActiveRecord|Active Record]] class that you plan to use: @@ -273,6 +277,8 @@ class User extends ActiveRecord ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], ], + // if you're using datetime instead of UNIX timestamp: + // 'value' => new Expression('NOW()'), ], ]; } @@ -281,12 +287,14 @@ class User extends ActiveRecord The behavior configuration above specifies that when the record is being: -* inserted, the behavior should assign the current timestamp to +* inserted, the behavior should assign the current UNIX timestamp to the `created_at` and `updated_at` attributes -* updated, the behavior should assign the current timestamp to the `updated_at` attribute +* updated, the behavior should assign the current UNIX timestamp to the `updated_at` attribute + +> Note: For the above implementation to work with MySQL database, please declare the columns(`created_at`, `updated_at`) as int(11) for being UNIX timestamp. With that code in place, if you have a `User` object and try to save it, you will find its `created_at` and `updated_at` are automatically -filled with the current timestamp: +filled with the current UNIX timestamp: ```php $user = new User; @@ -303,6 +311,21 @@ to a specified attribute and save it to the database: $user->touch('login_time'); ``` +Other behaviors +--------------- + +There are several built-in and external behaviors available: + +- [[yii\behaviors\BlameableBehavior]] - automatically fills the specified attributes with the current user ID. +- [[yii\behaviors\SluggableBehavior]] - automatically fills the specified attribute with a value that can be used + as a slug in a URL. +- [[yii\behaviors\AttributeBehavior]] - automatically assigns a specified value to one or multiple attributes of + an ActiveRecord object when certain events happen. +- [yii2tech\ar\softdelete\SoftDeleteBehavior](https://github.com/yii2tech/ar-softdelete) - provides methods to soft-delete + and soft-restore ActiveRecord i.e. set flag or status which marks record as deleted. +- [yii2tech\ar\position\PositionBehavior](https://github.com/yii2tech/ar-position) - allows managing records order in an + integer field by providing reordering methods. + Comparing Behaviors with Traits ---------------------- diff --git a/docs/guide/concept-configurations.md b/docs/guide/concept-configurations.md index b1ba63b868..067e9ce3c8 100644 --- a/docs/guide/concept-configurations.md +++ b/docs/guide/concept-configurations.md @@ -92,10 +92,14 @@ This is because the [[yii\web\Application|application]] class has a lot of confi More importantly, its [[yii\web\Application::components|components]] property can receive an array of configurations for creating components that are registered through the application. The following is an abstract from the application <<<<<<< HEAD +<<<<<<< HEAD configuration file for the [basic application template](start-installation.md). ======= configuration file for the [Basic Project Template](start-installation.md). >>>>>>> yiichina/master +======= +configuration file for the [Basic Project Template](start-installation.md). +>>>>>>> master ```php $config = [ diff --git a/docs/guide/concept-di-container.md b/docs/guide/concept-di-container.md index 45059d4dbd..0ab5c2a224 100644 --- a/docs/guide/concept-di-container.md +++ b/docs/guide/concept-di-container.md @@ -2,7 +2,7 @@ Dependency Injection Container ============================== A dependency injection (DI) container is an object that knows how to instantiate and configure objects and -all their dependent objects. [Martin's article](http://martinfowler.com/articles/injection.html) has well +all their dependent objects. [Martin Fowler's article](http://martinfowler.com/articles/injection.html) has well explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii. @@ -14,7 +14,7 @@ dependency injection: * Constructor injection; * Setter and property injection; -* PHP callable injection. +* PHP callable injection; ### Constructor Injection @@ -95,29 +95,25 @@ $container->set('Foo', function () { $foo = $container->get('Foo'); ``` -To hide the complex logic for building a new object, you may use a static class method to return the PHP -callable. For example, +To hide the complex logic for building a new object, you may use a static class method as callable. For example, ```php class FooBuilder { public static function build() { - return function () { - $foo = new Foo(new Bar); - // ... other initializations ... - return $foo; - }; + $foo = new Foo(new Bar); + // ... other initializations ... + return $foo; } } -$container->set('Foo', FooBuilder::build()); +$container->set('Foo', ['app\helper\FooBuilder', 'build']); $foo = $container->get('Foo'); ``` -As you can see, the PHP callable is returned by the `FooBuilder::build()` method. By doing so, the person -who wants to configure the `Foo` class no longer needs to be aware of how it is built. +By doing so, the person who wants to configure the `Foo` class no longer needs to be aware of how it is built. Registering Dependencies @@ -207,8 +203,8 @@ For example, // "db" is a previously registered alias name $db = $container->get('db'); -// equivalent to: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); -$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); +// equivalent to: $engine = new \app\components\SearchEngine($apiKey, $apiSecret, ['type' => 1]); +$engine = $container->get('app\components\SearchEngine', [$apiKey, $apiSecret], ['type' => 1]); ``` Behind the scene, the DI container does much more work than just creating a new object. @@ -307,6 +303,8 @@ You can still override the value set via DI container, though: echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); ``` +> Tip: no matter which value type it is, it will be overwritten so be careful with option arrays. They won't be merged. + Another example is to take advantage of the automatic constructor injection of the DI container. Assume your controller class depends on some other objects, such as a hotel booking service. You can declare the dependency through a constructor parameter and let the DI container to resolve it for you. @@ -345,10 +343,14 @@ When to Register Dependencies Because dependencies are needed when new objects are being created, their registration should be done <<<<<<< HEAD +<<<<<<< HEAD as early as possible. The followings are the recommended practices: ======= as early as possible. The following are the recommended practices: >>>>>>> yiichina/master +======= +as early as possible. The following are the recommended practices: +>>>>>>> master * If you are the developer of an application, you can register dependencies in your application's [entry script](structure-entry-scripts.md) or in a script that is included by the entry script. diff --git a/docs/guide/concept-events.md b/docs/guide/concept-events.md index 0ba9ac3703..13e7fff457 100644 --- a/docs/guide/concept-events.md +++ b/docs/guide/concept-events.md @@ -252,6 +252,76 @@ Event::off(Foo::className(), Foo::EVENT_HELLO); ``` +Events using interfaces +------------- + +There is even more abstract way to deal with events. You can create a separated interface for the special event and +implement it in classes, where you need it. + +For example we can create the following interface: + +```php +interface DanceEventInterface +{ + const EVENT_DANCE = 'dance'; +} +``` + +And two classes, that implement it: + +```php +class Dog extends Component implements DanceEventInterface +{ + public function meetBuddy() + { + echo "Woof!"; + $this->trigger(DanceEventInterface::EVENT_DANCE); + } +} + +class Developer extends Component implements DanceEventInterface +{ + public function testsPassed() + { + echo "Yay!"; + $this->trigger(DanceEventInterface::EVENT_DANCE); + } +} +``` + +To handle the `EVENT_DANCE`, triggered by any of these classes, call [[yii\base\Event::on()|Event::on()]] and +pass the interface name as the first argument: + +```php +Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) { + Yii::trace($event->sender->className . ' just danced'); // Will log that Dog or Developer danced +}) +``` + +You can trigger the event of those classes: + +```php +Event::trigger(DanceEventInterface::className(), DanceEventInterface::EVENT_DANCE); +``` + +But please notice, that you can not trigger all the classes, that implement the interface: + +```php +// DOES NOT WORK +Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // error +``` + +To detach event handler, call [[yii\base\Event::off()|Event::off()]]. For example: + +```php +// detaches $handler +Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler); + +// detaches all handlers of DanceEventInterface::EVENT_DANCE +Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE); +``` + + Global Events ------------- @@ -278,4 +348,4 @@ which will be triggered by the object. Instead, the handler attachment and the e done through the Singleton (e.g. the application instance). However, because the namespace of the global events is shared by all parties, you should name the global events -wisely, such as introducing some sort of namespace (e.g. "frontend.mail.sent", "backend.mail.sent"). +wisely, such as introducing some sort of namespace (e.g. "frontend.mail.sent", "backend.mail.sent"). \ No newline at end of file diff --git a/docs/guide/concept-properties.md b/docs/guide/concept-properties.md index bdd689f7fa..fbd382cf2a 100644 --- a/docs/guide/concept-properties.md +++ b/docs/guide/concept-properties.md @@ -2,7 +2,9 @@ Properties ========== In PHP, class member variables are also called *properties*. These variables are part of the class definition, and are used -to represent the state of a class instance (i.e., to differentiate one instance of the class from another). In practice, you may often want to handle the reading or writing of properties in special ways. For example, you may want to always trim a string when it is being assigned +to represent the state of a class instance (i.e., to differentiate one instance of the class from another). +In practice, you may often want to handle the reading or writing of properties in special ways. For example, +you may want to always trim a string when it is being assigned to a `label` property. You *could* use the following code to achieve this task: ```php @@ -10,14 +12,16 @@ $object->label = trim($label); ``` The drawback of the above code is that you would have to call `trim()` everywhere in your code where you might set the `label` -property. If, in the future, the `label` property gets a new requirement, such as the first letter must be capitalized, you would again have to modify every bit of code that assigns a value to `label`. The repetition of code leads to bugs, and is a practice you want to avoid as much as possible. +property. If, in the future, the `label` property gets a new requirement, such as the first letter must be capitalized, +you would again have to modify every bit of code that assigns a value to `label`. +The repetition of code leads to bugs, and is a practice you want to avoid as much as possible. To solve this problem, Yii introduces a base class called [[yii\base\Object]] that supports defining properties based on *getter* and *setter* class methods. If a class needs that functionality, it should extend from [[yii\base\Object]], or from a child class. > Info: Nearly every core class in the Yii framework extends from [[yii\base\Object]] or a child class. - This means that whenever you see a getter or setter in a core class, you can use it like a property. + This means, that whenever you see a getter or setter in a core class, you can use it like a property. A getter method is a method whose name starts with the word `get`; a setter method starts with `set`. The name after the `get` or `set` prefix defines the name of a property. For example, a getter `getLabel()` and/or @@ -44,7 +48,8 @@ class Foo extends Object } ``` -(To be clear, the getter and setter methods create the property `label`, which in this case internally refers to a private attribute named `_label`.) +To be clear, the getter and setter methods create the property `label`, which in this case internally refers to a private +property named `_label`. Properties defined by getters and setters can be used like class member variables. The main difference is that when such property is being read, the corresponding getter method will be called; when the property is @@ -72,5 +77,10 @@ There are several special rules for, and limitations on, the properties defined will affect the *member variable* 'label'; that line would not call the `setLabel()` setter method. * These properties do not support visibility. It makes no difference to the defining getter or setter method if the property is public, protected or private. * The properties can only be defined by *non-static* getters and/or setters. Static methods will not be treated in the same manner. +* A normal call to `property_exists()` does not work to determine magic properties. You should call [[yii\base\Object::canGetProperty()|canGetProperty()]] + or [[yii\base\Object::canSetProperty()|canSetProperty()]] respectively. -Returning back to the problem described at the beginning of this guide, instead of calling `trim()` everywhere a `label` value is assigned, `trim()` now only needs to be invoked within the setter `setLabel()`. And if a new requirement makes it necesary that the label be initially capitalized, the `setLabel()` method can quickly be modified without touching any other code. The one change will universally affect every assignment to `label`. +Returning back to the problem described at the beginning of this guide, instead of calling `trim()` everywhere a `label` value is assigned, +`trim()` now only needs to be invoked within the setter `setLabel()`. +And if a new requirement makes it necessary that the label be initially capitalized, the `setLabel()` method can quickly +be modified without touching any other code. The one change will universally affect every assignment to `label`. diff --git a/docs/guide/db-active-record.md b/docs/guide/db-active-record.md index 628e16fa48..f33910abae 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -1,11 +1,14 @@ Active Record ============= +<<<<<<< HEAD <<<<<<< HEAD > Note: This section is under development. ======= >>>>>>> yiichina/master +======= +>>>>>>> master [Active Record](http://en.wikipedia.org/wiki/Active_record_pattern) provides an object-oriented interface for accessing and manipulating data stored in databases. An Active Record class is associated with a database table, an Active Record instance corresponds to a row of that table, and an *attribute* of an Active Record @@ -133,10 +136,14 @@ The process usually takes the following three steps: 1. Create a new query object by calling the [[yii\db\ActiveRecord::find()]] method; 2. Build the query object by calling [query building methods](db-query-builder.md#building-queries); <<<<<<< HEAD +<<<<<<< HEAD 3. Call a [query method](db-query-builder.md#query-methods) to retrieve data in terms ofActive Record instances. ======= 3. Call a [query method](db-query-builder.md#query-methods) to retrieve data in terms of Active Record instances. >>>>>>> yiichina/master +======= +3. Call a [query method](db-query-builder.md#query-methods) to retrieve data in terms of Active Record instances. +>>>>>>> master As you can see, this is very similar to the procedure with [query builder](db-query-builder.md). The only difference is that instead of using the `new` operator to create a query object, you call [[yii\db\ActiveRecord::find()]] @@ -164,11 +171,15 @@ $count = Customer::find() ->where(['status' => Customer::STATUS_ACTIVE]) ->count(); +<<<<<<< HEAD <<<<<<< HEAD // return all active customers in an array indexed by customer IDs ======= // return all customers in an array indexed by customer IDs >>>>>>> yiichina/master +======= +// return all customers in an array indexed by customer IDs +>>>>>>> master // SELECT * FROM `customer` $customers = Customer::find() ->indexBy('id') @@ -195,7 +206,7 @@ Both methods can take one of the following parameter formats: - an associative array: the keys are column names and the values are the corresponding desired column values to be looked for. Please refer to [Hash Format](db-query-builder.md#hash-format) for more details. -The following code shows how theses methods can be used: +The following code shows how these methods can be used: ```php // returns a single customer whose ID is 123 @@ -215,7 +226,7 @@ $customer = Customer::findOne([ // returns all inactive customers // SELECT * FROM `customer` WHERE `status` = 0 -$customer = Customer::findAll([ +$customers = Customer::findAll([ 'status' => Customer::STATUS_INACTIVE, ]); ``` @@ -226,10 +237,14 @@ $customer = Customer::findAll([ Besides using query building methods, you can also write raw SQLs to query data and populate the results into <<<<<<< HEAD +<<<<<<< HEAD Active Record objects. You can do so by calling the [[yii\db\ActiveRecord::queryBySql()]] method: ======= Active Record objects. You can do so by calling the [[yii\db\ActiveRecord::findBySql()]] method: >>>>>>> yiichina/master +======= +Active Record objects. You can do so by calling the [[yii\db\ActiveRecord::findBySql()]] method: +>>>>>>> master ```php // returns all inactive customers @@ -237,11 +252,15 @@ $sql = 'SELECT * FROM customer WHERE status=:status'; $customers = Customer::findBySql($sql, [':status' => Customer::STATUS_INACTIVE])->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD Do not call extra query building methods after calling [[yii\db\ActiveRecord::queryBySql()|queryBySql()]] as they ======= Do not call extra query building methods after calling [[yii\db\ActiveRecord::findBySql()|findBySql()]] as they >>>>>>> yiichina/master +======= +Do not call extra query building methods after calling [[yii\db\ActiveRecord::findBySql()|findBySql()]] as they +>>>>>>> master will be ignored. @@ -270,6 +289,7 @@ named in this way. If you are concerned about code style consistency, you should ### Data Transformation +<<<<<<< HEAD <<<<<<< HEAD It often happens that the data being entered and/or displayed are in a different format from the one used in storing the data in a database. For example, in the database you are storing customers' birthdays as UNIX timestamps @@ -281,6 +301,12 @@ storing the data in a database. For example, in the database you are storing cus (which is not a good design, though), while in most cases you would like to manipulate birthdays as strings in the format of `'YYYY/MM/DD'`. To achieve this goal, you can define *data transformation* methods in the `Customer` >>>>>>> yiichina/master +======= +It often happens that the data being entered and/or displayed are in a format which is different from the one used in +storing the data in a database. For example, in the database you are storing customers' birthdays as UNIX timestamps +(which is not a good design, though), while in most cases you would like to manipulate birthdays as strings in +the format of `'YYYY/MM/DD'`. To achieve this goal, you can define *data transformation* methods in the `Customer` +>>>>>>> master Active Record class like the following: ```php @@ -303,13 +329,19 @@ class Customer extends ActiveRecord Now in your PHP code, instead of accessing `$customer->birthday`, you would access `$customer->birthdayText`, which will allow you to input and display customer birthdays in the format of `'YYYY/MM/DD'`. +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> master > Tip: The above example shows a generic way of transforming data in different formats. If you are working with > date values, you may use [DateValidator](tutorial-core-validators.md#date) and [[yii\jui\DatePicker|DatePicker]], > which is easier to use and more powerful. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ### Retrieving Data in Arrays @@ -350,14 +382,14 @@ foreach (Customer::find()->each(10) as $customer) { // batch query with eager loading foreach (Customer::find()->with('orders')->each() as $customer) { - // $customer is a Customer object + // $customer is a Customer object with the 'orders' relation populated } ``` ## Saving Data -Using Active Record, you can easily save data to database by taking the following steps: +Using Active Record, you can easily save data to the database by taking the following steps: 1. Prepare an Active Record instance 2. Assign new values to Active Record attributes @@ -437,7 +469,7 @@ $customer->save(); ### Updating Counters -It is a common task to increment or decrement a column in a database table. We call such columns as counter columns. +It is a common task to increment or decrement a column in a database table. We call these columns "counter columns". You can use [[yii\db\ActiveRecord::updateCounters()|updateCounters()]] to update one or multiple counter columns. For example, @@ -467,6 +499,13 @@ to explicitly mark an attribute as dirty. If you are interested in the attribute values prior to their most recent modification, you may call [[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] or [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]. +> Note: The comparison of old and new values will be done using the `===` operator so a value will be considered dirty +> even if it has the same value but a different type. This is often the case when the model receives user input from +> HTML forms where every value is represented as a string. +> To ensure the correct type for e.g. integer values you may apply a [validation filter](input-validation.md#data-filtering): +> `['attributeName', 'filter', 'filter' => 'intval']`. This works with all the typecasting functions of PHP like +> [intval()](http://php.net/manual/en/function.intval.php), [floatval()](http://php.net/manual/en/function.floatval.php), +> [boolval](http://php.net/manual/en/function.boolval.php), etc... ### Default Attribute Values @@ -490,10 +529,14 @@ which is a static method. ```php <<<<<<< HEAD +<<<<<<< HEAD // UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com` ======= // UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com%` >>>>>>> yiichina/master +======= +// UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com%` +>>>>>>> master Customer::updateAll(['status' => Customer::STATUS_ACTIVE], ['like', 'email', '@example.com']); ``` @@ -534,7 +577,7 @@ to get a chance to customize the life cycle. You can also respond to certain Act during a life cycle to inject your custom code. These events are especially useful when you are developing Active Record [behaviors](concept-behaviors.md) which need to customize Active Record life cycles. -In the following, we will summarize various Active Record life cycles and the methods/events that are involved +In the following, we will summarize the various Active Record life cycles and the methods/events that are involved in the life cycles. @@ -542,7 +585,7 @@ in the life cycles. When creating a new Active Record instance via the `new` operator, the following life cycle will happen: -1. class constructor; +1. Class constructor. 2. [[yii\db\ActiveRecord::init()|init()]]: triggers an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event. @@ -551,7 +594,7 @@ When creating a new Active Record instance via the `new` operator, the following When querying data through one of the [querying methods](#querying-data), each newly populated Active Record will undergo the following life cycle: -1. class constructor. +1. Class constructor. 2. [[yii\db\ActiveRecord::init()|init()]]: triggers an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event. 3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: triggers an [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] event. @@ -571,7 +614,7 @@ life cycle will happen: an [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] or [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] event. If the method returns false or [[yii\base\ModelEvent::isValid]] is false, the rest of the steps will be skipped. -5. Performs the actual data insertion or updating; +5. Performs the actual data insertion or updating. 6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: triggers an [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] or [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] event. @@ -585,18 +628,24 @@ life cycle will happen: 1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: triggers an [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] event. If the method returns false or [[yii\base\ModelEvent::isValid]] is false, the rest of the steps will be skipped. -2. perform the actual data deletion +2. Performs the actual data deletion. 3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: triggers an [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] event. -> Note: Calling any of the following methods will NOT initiate any of the above life cycles: +> Note: Calling any of the following methods will NOT initiate any of the above life cycles because they work on the +> database directly and not on a record basis: > > - [[yii\db\ActiveRecord::updateAll()]] > - [[yii\db\ActiveRecord::deleteAll()]] > - [[yii\db\ActiveRecord::updateCounters()]] > - [[yii\db\ActiveRecord::updateAllCounters()]] +### Refreshing Data Life Cycle + +When calling [[yii\db\ActiveRecord::refresh()|refresh()]] to refresh an Active Record instance, the +[[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] event is triggered if refresh is successful and the method returns `true`. + ## Working with Transactions @@ -646,16 +695,18 @@ class Customer extends ActiveRecord ``` The [[yii\db\ActiveRecord::transactions()]] method should return an array whose keys are [scenario](structure-models.md#scenarios) -names and values the corresponding operations that should be enclosed within transactions. You should use the following +names and values are the corresponding operations that should be enclosed within transactions. You should use the following constants to refer to different DB operations: * [[yii\db\ActiveRecord::OP_INSERT|OP_INSERT]]: insertion operation performed by [[yii\db\ActiveRecord::insert()|insert()]]; * [[yii\db\ActiveRecord::OP_UPDATE|OP_UPDATE]]: update operation performed by [[yii\db\ActiveRecord::update()|update()]]; * [[yii\db\ActiveRecord::OP_DELETE|OP_DELETE]]: deletion operation performed by [[yii\db\ActiveRecord::delete()|delete()]]. -Use `|` operators to concatenate the above constants to indicate multiple operations. You may also use the shortcut +Use the `|` operators to concatenate the above constants to indicate multiple operations. You may also use the shortcut constant [[yii\db\ActiveRecord::OP_ALL|OP_ALL]] to refer to all three operations above. +Transactions that are created using this method will be started before calling [[yii\db\ActiveRecord::beforeSave()|beforeSave()]] +and will be committed after [[yii\db\ActiveRecord::afterSave()|afterSave()]] has run. ## Optimistic Locks @@ -678,9 +729,13 @@ To use optimistic locking, 2. Override the [[yii\db\ActiveRecord::optimisticLock()]] method to return the name of this column. <<<<<<< HEAD 3. In the Web form that takes user inputs, add a hidden field to store the current version number of the row being updated. +<<<<<<< HEAD ======= 3. In the Web form that takes user inputs, add a hidden field to store the current version number of the row being updated. Be sure your version attribute has input validation rules and validates successfully. >>>>>>> yiichina/master +======= + Be sure your version attribute has input validation rules and validates successfully. +>>>>>>> master 4. In the controller action that updates the row using Active Record, try and catch the [[yii\db\StaleObjectException]] exception. Implement necessary business logic (e.g. merging the changes, prompting staled data) to resolve the conflict. @@ -694,10 +749,14 @@ use yii\helpers\Html; // ...other input fields <<<<<<< HEAD +<<<<<<< HEAD echo Html::activeHiddenField($model, 'version'); ======= echo Html::activeHiddenInput($model, 'version'); >>>>>>> yiichina/master +======= +echo Html::activeHiddenInput($model, 'version'); +>>>>>>> master // ------ controller code ------- @@ -710,11 +769,15 @@ public function actionUpdate($id) try { if ($model->load(Yii::$app->request->post()) && $model->save()) { +<<<<<<< HEAD <<<<<<< HEAD return $this->redirect(['view', ]); ======= return $this->redirect(['view', 'id' => $model->id]); >>>>>>> yiichina/master +======= + return $this->redirect(['view', 'id' => $model->id]); +>>>>>>> master } else { return $this->render('update', [ 'model' => $model, @@ -732,7 +795,7 @@ public function actionUpdate($id) Besides working with individual database tables, Active Record is also capable of bringing together related data, making them readily accessible through the primary data. For example, the customer data is related with the order data because one customer may have placed one or multiple orders. With appropriate declaration of this relation, -you may be able to access a customer's order information using the expression `$customer->orders` which gives +you'll be able to access a customer's order information using the expression `$customer->orders` which gives back the customer's order information in terms of an array of `Order` Active Record instances. @@ -744,6 +807,8 @@ The task is as simple as declaring a *relation method* for every interested rela ```php class Customer extends ActiveRecord { + // ... + public function getOrders() { return $this->hasMany(Order::className(), ['customer_id' => 'id']); @@ -752,6 +817,8 @@ class Customer extends ActiveRecord class Order extends ActiveRecord { + // ... + public function getCustomer() { return $this->hasOne(Customer::className(), ['id' => 'customer_id']); @@ -778,12 +845,18 @@ While declaring a relation, you should specify the following information: The array values are the columns of the primary data (represented by the Active Record class that you are declaring relations), while the array keys are the columns of the related data. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master An easy rule to remember this is, as you see in the example above, you write the column that belongs to the related Active Record directly next to it. You see there that `customer_id` is a property of `Order` and `id` is a property of `Customer`. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ### Accessing Relational Data @@ -802,11 +875,15 @@ $orders = $customer->orders; ``` > Info: When you declare a relation named `xyz` via a getter method `getXyz()`, you will be able to access +<<<<<<< HEAD <<<<<<< HEAD `xyz` like an object [property](concept-properties.md). Note that the name is case sensitive. ======= `xyz` like an [object property](concept-properties.md). Note that the name is case sensitive. >>>>>>> yiichina/master +======= + `xyz` like an [object property](concept-properties.md). Note that the name is case sensitive. +>>>>>>> master If a relation is declared with [[yii\db\ActiveRecord::hasMany()|hasMany()]], accessing this relation property will return an array of the related Active Record instances; if a relation is declared with @@ -815,6 +892,7 @@ Active Record instance or null if no related data is found. When you access a relation property for the first time, a SQL statement will be executed, like shown in the <<<<<<< HEAD +<<<<<<< HEAD above example. If the same property is access again, the previous result will be returned and no extra SQL statement will be executed. To enforce a re-execution of the SQL statement, you should unset the relation property first: `unset($customer->orders)`. @@ -824,6 +902,12 @@ above example. If the same property is accessed again, the previous result will the SQL statement. To force re-executing the SQL statement, you should unset the relation property first: `unset($customer->orders)`. +======= +above example. If the same property is accessed again, the previous result will be returned without re-executing +the SQL statement. To force re-executing the SQL statement, you should unset the relation property +first: `unset($customer->orders)`. + +>>>>>>> master > Note: While this concept looks similar to the [object property](concept-properties.md) feature, there is an > important difference. For normal object properties the property value is of the same type as the defining getter method. > A relation method however returns an [[yii\db\ActiveQuery]] instance, while accessing a relation property will either @@ -836,7 +920,10 @@ first: `unset($customer->orders)`. > > This is useful for creating customized queries, which is described in the next section. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ### Dynamic Relational Query @@ -846,21 +933,27 @@ using query building methods before performing DB query. For example, ```php $customer = Customer::findOne(123); -// SELECT * FROM `order` WHERE `subtotal` > 200 ORDER BY `id` +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id` $orders = $customer->getOrders() ->where(['>', 'subtotal', 200]) ->orderBy('id') ->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD Sometimes you may even want to parameterize a relation declaration so that you can more easily perform ======= +======= +>>>>>>> master Unlike accessing a relation property, each time you perform a dynamic relational query via a relation method, a SQL statement will be executed, even if the same dynamic relational query was performed before. Sometimes you may even want to parametrize a relation declaration so that you can more easily perform +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master dynamic relational query. For example, you may declare a `bigOrders` relation as follows, ```php @@ -878,13 +971,14 @@ class Customer extends ActiveRecord Then you will be able to perform the following relational queries: ```php -// SELECT * FROM `order` WHERE `subtotal` > 200 ORDER BY `id` +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id` $orders = $customer->getBigOrders(200)->all(); -// SELECT * FROM `order` WHERE `subtotal` > 100 ORDER BY `id` +// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 100 ORDER BY `id` $orders = $customer->bigOrders; ``` +<<<<<<< HEAD <<<<<<< HEAD > Note: While a relation method returns a [[yii\db\ActiveQuery]] instance, accessing a relation property will either return a [[yii\db\ActiveRecord]] instance or an array of that. This is different from a normal object @@ -895,6 +989,8 @@ a SQL statement will be executed, even if the same dynamic relational query is p ======= >>>>>>> yiichina/master +======= +>>>>>>> master ### Relations via a Junction Table @@ -905,7 +1001,7 @@ to multiple order items, while one product item will also correspond to multiple When declaring such relations, you would call either [[yii\db\ActiveQuery::via()|via()]] or [[yii\db\ActiveQuery::viaTable()|viaTable()]] to specify the junction table. The difference between [[yii\db\ActiveQuery::via()|via()]] and [[yii\db\ActiveQuery::viaTable()|viaTable()]] -is that the former specifies the junction table in terms of an existing relation name while the latter directly +is that the former specifies the junction table in terms of an existing relation name while the latter directly uses the junction table. For example, ```php @@ -937,11 +1033,15 @@ class Order extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD The usage of relations declared with a junction table is the same as normal relations. For example, ======= The usage of relations declared with a junction table is the same as that of normal relations. For example, >>>>>>> yiichina/master +======= +The usage of relations declared with a junction table is the same as that of normal relations. For example, +>>>>>>> master ```php // SELECT * FROM `order` WHERE `id` = 100 @@ -956,16 +1056,25 @@ $items = $order->items; ### Lazy Loading and Eager Loading +<<<<<<< HEAD <<<<<<< HEAD As described earlier, when you access the related objects for the first time, ActiveRecord will perform a DB query to retrieve the corresponding data and populate it into the related objects. No query will be performed if you access the same related objects again. We call this *lazy loading*. For example, +======= +In [Accessing Relational Data](#accessing-relational-data), we explained that you can access a relation property +of an Active Record instance like accessing a normal object property. A SQL statement will be executed only when +you access the relation property the first time. We call such relational data accessing method *lazy loading*. +For example, +>>>>>>> master ```php -// SQL executed: SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// SQL executed: SELECT * FROM order WHERE customer_id=1 +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 $orders = $customer->orders; +<<<<<<< HEAD ======= In [Accessing Relational Data](#accessing-relational-data), we explained that you can access a relation property of an Active Record instance like accessing a normal object property. A SQL statement will be executed only when @@ -980,31 +1089,40 @@ $customer = Customer::findOne(123); $orders = $customer->orders; >>>>>>> yiichina/master +======= + +>>>>>>> master // no SQL executed $orders2 = $customer->orders; ``` +<<<<<<< HEAD <<<<<<< HEAD Lazy loading is very convenient to use. However, it may suffer from a performance issue in the following scenario: +======= +Lazy loading is very convenient to use. However, it may suffer from a performance issue when you need to access +the same relation property of multiple Active Record instances. Consider the following code example. How many +SQL statements will be executed? +>>>>>>> master ```php -// SQL executed: SELECT * FROM customer LIMIT 100 +// SELECT * FROM `customer` LIMIT 100 $customers = Customer::find()->limit(100)->all(); foreach ($customers as $customer) { - // SQL executed: SELECT * FROM order WHERE customer_id=... + // SELECT * FROM `order` WHERE `customer_id` = ... $orders = $customer->orders; - // ...handle $orders... } ``` -How many SQL queries will be performed in the above code, assuming there are more than 100 customers in -the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query -is performed to bring back the orders of that customer. +As you can see from the code comment above, there are 101 SQL statements being executed! This is because each +time you access the `orders` relation property of a different `Customer` object in the for-loop, a SQL statement +will be executed. -To solve the above performance problem, you can use the so-called *eager loading* approach by calling [[yii\db\ActiveQuery::with()]]: +To solve this performance problem, you can use the so-called *eager loading* approach as shown below, ```php +<<<<<<< HEAD // SQL executed: SELECT * FROM customer LIMIT 100; // SELECT * FROM orders WHERE customer_id IN (1,2,...) $customers = Customer::find()->limit(100) @@ -1031,41 +1149,71 @@ will be executed. To solve this performance problem, you can use the so-called *eager loading* approach as shown below, ```php +======= +>>>>>>> master // SELECT * FROM `customer` LIMIT 100; // SELECT * FROM `orders` WHERE `customer_id` IN (...) $customers = Customer::find() ->with('orders') ->limit(100) ->all(); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master foreach ($customers as $customer) { // no SQL executed $orders = $customer->orders; +<<<<<<< HEAD <<<<<<< HEAD // ...handle $orders... +======= +>>>>>>> master } ``` -As you can see, only two SQL queries are needed for the same task! +By calling [[yii\db\ActiveQuery::with()]], you instruct Active Record to bring back the orders for the first 100 +customers in one single SQL statement. As a result, you reduce the number of the executed SQL statements from 101 to 2! -> Info: In general, if you are eager loading `N` relations among which `M` relations are defined with `via()` or `viaTable()`, -> a total number of `1+M+N` SQL queries will be performed: one query to bring back the rows for the primary table, one for -> each of the `M` junction tables corresponding to the `via()` or `viaTable()` calls, and one for each of the `N` related tables. +You can eagerly load one or multiple relations. You can even eagerly load *nested relations*. A nested relation is a relation +that is declared within a related Active Record class. For example, `Customer` is related with `Order` through the `orders` +relation, and `Order` is related with `Item` through the `items` relation. When querying for `Customer`, you can eagerly +load `items` using the nested relation notation `orders.items`. -> Note: When you are customizing `select()` with eager loading, make sure you include the columns that link -> the related models. Otherwise, the related models will not be loaded. For example, +The following code shows different usage of [[yii\db\ActiveQuery::with()|with()]]. We assume the `Customer` class +has two relations `orders` and `country`, while the `Order` class has one relation `items`. ```php -$orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); -// $orders[0]->customer is always null. To fix the problem, you should do the following: -$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +// eager loading both "orders" and "country" +$customers = Customer::find()->with('orders', 'country')->all(); +// equivalent to the array syntax below +$customers = Customer::find()->with(['orders', 'country'])->all(); +// no SQL executed +$orders= $customers[0]->orders; +// no SQL executed +$country = $customers[0]->country; + +// eager loading "orders" and the nested relation "orders.items" +$customers = Customer::find()->with('orders.items')->all(); +// access the items of the first order of the first customer +// no SQL executed +$items = $customers[0]->orders[0]->items; ``` -Sometimes, you may want to customize the relational queries on the fly. This can be -done for both lazy loading and eager loading. For example, +You can eagerly load deeply nested relations, such as `a.b.c.d`. All parent relations will be eagerly loaded. +That is, when you call [[yii\db\ActiveQuery::with()|with()]] using `a.b.c.d`, you will eagerly load +`a`, `a.b`, `a.b.c` and `a.b.c.d`. + +> Info: In general, when eagerly loading `N` relations among which `M` relations are defined with a + [junction table](#junction-table), a total number of `N+M+1` SQL statements will be executed. + Note that a nested relation `a.b.c.d` counts as 4 relations. + +When eagerly loading a relation, you can customize the corresponding relational query using an anonymous function. +For example, ```php +<<<<<<< HEAD $customer = Customer::findOne(1); // lazy loading: SELECT * FROM order WHERE customer_id=1 AND subtotal>100 $orders = $customer->getOrders()->where('subtotal>100')->all(); @@ -1119,6 +1267,8 @@ When eagerly loading a relation, you can customize the corresponding relational For example, ```php +======= +>>>>>>> master // find customers and bring back together their country and active orders // SELECT * FROM `customer` // SELECT * FROM `country` WHERE `id` IN (...) @@ -1127,15 +1277,34 @@ $customers = Customer::find()->with([ 'country', 'orders' => function ($query) { $query->andWhere(['status' => Order::STATUS_ACTIVE]); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master }, ])->all(); ``` <<<<<<< HEAD +<<<<<<< HEAD +======= +When customizing the relational query for a relation, you should specify the relation name as an array key +and use an anonymous function as the corresponding array value. The anonymous function will receive a `$query` parameter +which represents the [[yii\db\ActiveQuery]] object used to perform the relational query for the relation. +In the code example above, we are modifying the relational query by appending an additional condition about order status. +>>>>>>> master -### Inverse Relations +> Note: If you call [[yii\db\Query::select()|select()]] while eagerly loading relations, you have to make sure +> the columns referenced in the relation declarations are being selected. Otherwise, the related models may not +> be loaded properly. For example, +> +> ```php +> $orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); +> // $orders[0]->customer is always null. To fix the problem, you should do the following: +> $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +> ``` +<<<<<<< HEAD Relations can often be defined in pairs. For example, `Customer` may have a relation named `orders` while `Order` may have a relation named `customer`: ======= @@ -1154,6 +1323,8 @@ In the code example above, we are modifying the relational query by appending an > $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); > ``` +======= +>>>>>>> master ### Joining with Relations @@ -1179,7 +1350,11 @@ $customers = Customer::find() ->all(); ``` +<<<<<<< HEAD > Note: It is important to disambiguate column names when building relational queries involving JOIN SQL statements. +======= +> Note: It is important to disambiguate column names when building relational queries involving JOIN SQL statements. +>>>>>>> master A common practice is to prefix column names with their corresponding table names. However, a better approach is to exploit the existing relation declarations by calling [[yii\db\ActiveQuery::joinWith()]]: @@ -1234,21 +1409,52 @@ Note that this differs from our earlier example which only brings back customers > Info: When [[yii\db\ActiveQuery]] is specified with a condition via [[yii\db\ActiveQuery::onCondition()|onCondition()]], the condition will be put in the `ON` part if the query involves a JOIN query. If the query does not involve JOIN, the on-condition will be automatically appended to the `WHERE` part of the query. +<<<<<<< HEAD +======= + Thus it may only contain conditions including columns of the related table. + +#### Relation table aliases + +As noted before, when using JOIN in a query, we need to disambiguate column names. Therefor often an alias is +defined for a table. Setting an alias for the relational query would be possible by customizing the relation query in the following way: + +```php +$query->joinWith([ + 'orders' => function ($q) { + $q->from(['o' => Order::tableName()]); + }, +]) +``` + +This however looks very complicated and involves either hardcoding the related objects table name or calling `Order::tableName()`. +Since version 2.0.7, Yii provides a shortcut for this. You may now define and use the alias for the relation table like the following: + +```php +// join the orders relation and sort the result by orders.id +$query->joinWith(['orders o'])->orderBy('o.id'); +``` +>>>>>>> master ### Inverse Relations Relation declarations are often reciprocal between two Active Record classes. For example, `Customer` is related to `Order` via the `orders` relation, and `Order` is related back to `Customer` via the `customer` relation. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ```php class Customer extends ActiveRecord { +<<<<<<< HEAD <<<<<<< HEAD .... ======= >>>>>>> yiichina/master +======= +>>>>>>> master public function getOrders() { return $this->hasMany(Order::className(), ['customer_id' => 'id']); @@ -1257,10 +1463,13 @@ class Customer extends ActiveRecord class Order extends ActiveRecord { +<<<<<<< HEAD <<<<<<< HEAD .... ======= >>>>>>> yiichina/master +======= +>>>>>>> master public function getCustomer() { return $this->hasOne(Customer::className(), ['id' => 'customer_id']); @@ -1268,24 +1477,30 @@ class Order extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD If we perform the following query, we would find that the `customer` of an order is not the same customer object that finds those orders, and accessing `customer->orders` will trigger one SQL execution while accessing the `customer` of an order will trigger another SQL execution: +======= +Now consider the following piece of code: +>>>>>>> master ```php -// SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// echoes "not equal" -// SELECT * FROM order WHERE customer_id=1 -// SELECT * FROM customer WHERE id=1 -if ($customer->orders[0]->customer === $customer) { - echo 'equal'; -} else { - echo 'not equal'; -} +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$order = $customer->orders[0]; + +// SELECT * FROM `customer` WHERE `id` = 123 +$customer2 = $order->customer; + +// displays "not the same" +echo $customer2 === $customer ? 'same' : 'not the same'; ``` +<<<<<<< HEAD To avoid the redundant execution of the last SQL statement, we could declare the inverse relations for the `customer` and the `orders` relations by calling the [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] method, like the following: ======= @@ -1305,6 +1520,8 @@ $customer2 = $order->customer; echo $customer2 === $customer ? 'same' : 'not the same'; ``` +======= +>>>>>>> master We would think `$customer` and `$customer2` are the same, but they are not! Actually they do contain the same customer data, but they are different objects. When accessing `$order->customer`, an extra SQL statement is executed to populate a new object `$customer2`. @@ -1312,15 +1529,21 @@ is executed to populate a new object `$customer2`. To avoid the redundant execution of the last SQL statement in the above example, we should tell Yii that `customer` is an *inverse relation* of `orders` by calling the [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] method like shown below: +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ```php class Customer extends ActiveRecord { +<<<<<<< HEAD <<<<<<< HEAD .... ======= >>>>>>> yiichina/master +======= +>>>>>>> master public function getOrders() { return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer'); @@ -1328,173 +1551,107 @@ class Customer extends ActiveRecord } ``` +<<<<<<< HEAD <<<<<<< HEAD Now if we execute the same query as shown above, we would get: +======= +With this modified relation declaration, we will have: +>>>>>>> master ```php -// SELECT * FROM customer WHERE id=1 -$customer = Customer::findOne(1); -// echoes "equal" -// SELECT * FROM order WHERE customer_id=1 -if ($customer->orders[0]->customer === $customer) { - echo 'equal'; -} else { - echo 'not equal'; -} +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$order = $customer->orders[0]; + +// No SQL will be executed +$customer2 = $order->customer; + +// displays "same" +echo $customer2 === $customer ? 'same' : 'not the same'; ``` -In the above, we have shown how to use inverse relations in lazy loading. Inverse relations also apply in -eager loading: +> Note: Inverse relations cannot be defined for relations involving a [junction table](#junction-table). + That is, if a relation is defined with [[yii\db\ActiveQuery::via()|via()]] or [[yii\db\ActiveQuery::viaTable()|viaTable()]], + you should not call [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] further. + + +## Saving Relations + +When working with relational data, you often need to establish relationships between different data or destroy +existing relationships. This requires setting proper values for the columns that define the relations. Using Active Record, +you may end up writing the code like the following: ```php -// SELECT * FROM customer -// SELECT * FROM order WHERE customer_id IN (1, 2, ...) -$customers = Customer::find()->with('orders')->all(); -// echoes "equal" -if ($customers[0]->orders[0]->customer === $customers[0]) { - echo 'equal'; -} else { - echo 'not equal'; -} -``` - -> Note: Inverse relation cannot be defined with a relation that involves pivoting tables. -> That is, if your relation is defined with [[yii\db\ActiveQuery::via()|via()]] or [[yii\db\ActiveQuery::viaTable()|viaTable()]], -> you cannot call [[yii\db\ActiveQuery::inverseOf()]] further. - - -### Joining with Relations - -When working with relational databases, a common task is to join multiple tables and apply various -query conditions and parameters to the JOIN SQL statement. Instead of calling [[yii\db\ActiveQuery::join()]] -explicitly to build up the JOIN query, you may reuse the existing relation definitions and call -[[yii\db\ActiveQuery::joinWith()]] to achieve this goal. For example, - -```php -// find all orders and sort the orders by the customer id and the order id. also eager loading "customer" -$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all(); -// find all orders that contain books, and eager loading "books" -$orders = Order::find()->innerJoinWith('books')->all(); -``` - -In the above, the method [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] is a shortcut to [[yii\db\ActiveQuery::joinWith()|joinWith()]] -with the join type set as `INNER JOIN`. - -You may join with one or multiple relations; you may apply query conditions to the relations on-the-fly; -and you may also join with sub-relations. For example, - -```php -// join with multiple relations -// find the orders that contain books and were placed by customers who registered within the past 24 hours -$orders = Order::find()->innerJoinWith([ - 'books', - 'customer' => function ($query) { - $query->where('customer.created_at > ' . (time() - 24 * 3600)); - } -])->all(); -// join with sub-relations: join with books and books' authors -$orders = Order::find()->joinWith('books.author')->all(); -``` - -Behind the scenes, Yii will first execute a JOIN SQL statement to bring back the primary models -satisfying the conditions applied to the JOIN SQL. It will then execute a query for each relation -and populate the corresponding related records. - -The difference between [[yii\db\ActiveQuery::joinWith()|joinWith()]] and [[yii\db\ActiveQuery::with()|with()]] is that -the former joins the tables for the primary model class and the related model classes to retrieve -the primary models, while the latter just queries against the table for the primary model class to -retrieve the primary models. - -Because of this difference, you may apply query conditions that are only available to a JOIN SQL statement. -For example, you may filter the primary models by the conditions on the related models, like the example -above. You may also sort the primary models using columns from the related tables. - -When using [[yii\db\ActiveQuery::joinWith()|joinWith()]], you are responsible to disambiguate column names. -In the above examples, we use `item.id` and `order.id` to disambiguate the `id` column references -because both of the order table and the item table contain a column named `id`. - -By default, when you join with a relation, the relation will also be eagerly loaded. You may change this behavior -by passing the `$eagerLoading` parameter which specifies whether to eager load the specified relations. - -And also by default, [[yii\db\ActiveQuery::joinWith()|joinWith()]] uses `LEFT JOIN` to join the related tables. -You may pass it with the `$joinType` parameter to customize the join type. As a shortcut to the `INNER JOIN` type, -you may use [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]]. - -Below are some more examples, - -```php -// find all orders that contain books, but do not eager load "books". -$orders = Order::find()->innerJoinWith('books', false)->all(); -// which is equivalent to the above -$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all(); -``` - -Sometimes when joining two tables, you may need to specify some extra condition in the ON part of the JOIN query. -This can be done by calling the [[yii\db\ActiveQuery::onCondition()]] method like the following: - -```php -class User extends ActiveRecord -{ - public function getBooks() - { - return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]); - } -} -``` - -In the above, the [[yii\db\ActiveRecord::hasMany()|hasMany()]] method returns an [[yii\db\ActiveQuery]] instance, -upon which [[yii\db\ActiveQuery::onCondition()|onCondition()]] is called -to specify that only items whose `category_id` is 1 should be returned. - -When you perform a query using [[yii\db\ActiveQuery::joinWith()|joinWith()]], the ON condition will be put in the ON part -of the corresponding JOIN query. For example, - -```php -// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 -// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 -$users = User::find()->joinWith('books')->all(); -``` - -Note that if you use eager loading via [[yii\db\ActiveQuery::with()]] or lazy loading, the on-condition will be put -in the WHERE part of the corresponding SQL statement, because there is no JOIN query involved. For example, - -```php -// SELECT * FROM user WHERE id=10 -$user = User::findOne(10); -// SELECT * FROM item WHERE owner_id=10 AND category_id=1 -$books = $user->books; -``` - - -Working with Relationships --------------------------- - -ActiveRecord provides the following two methods for establishing and breaking a -relationship between two ActiveRecord objects: - -- [[yii\db\ActiveRecord::link()|link()]] -- [[yii\db\ActiveRecord::unlink()|unlink()]] - -For example, given a customer and a new order, we can use the following code to make the -order owned by the customer: - -```php -$customer = Customer::findOne(1); +$customer = Customer::findOne(123); $order = new Order(); $order->subtotal = 100; -$customer->link('orders', $order); +// ... + +// setting the attribute that defines the "customer" relation in Order +$order->customer_id = $customer->id; +$order->save(); ``` -The [[yii\db\ActiveRecord::link()|link()]] call above will set the `customer_id` of the order to be the primary key -value of `$customer` and then call [[yii\db\ActiveRecord::save()|save()]] to save the order into the database. - - -Cross-DBMS Relations --------------------- - -ActiveRecord allows you to establish relationships between entities from different DBMS. For example: between a relational database table and MongoDB collection. Such a relation does not require any special code: +Active Record provides the [[yii\db\ActiveRecord::link()|link()]] method that allows you to accomplish this task more nicely: ```php +$customer = Customer::findOne(123); +$order = new Order(); +$order->subtotal = 100; +// ... + +$order->link('customer', $customer); +``` + +The [[yii\db\ActiveRecord::link()|link()]] method requires you to specify the relation name and the target Active Record +instance that the relationship should be established with. The method will modify the values of the attributes that +link two Active Record instances and save them to the database. In the above example, it will set the `customer_id` +attribute of the `Order` instance to be the value of the `id` attribute of the `Customer` instance and then save it +to the database. + +> Note: You cannot link two newly created Active Record instances. + +The benefit of using [[yii\db\ActiveRecord::link()|link()]] is even more obvious when a relation is defined via +a [junction table](#junction-table). For example, you may use the following code to link an `Order` instance +with an `Item` instance: + +```php +$order->link('items', $item); +``` + +The above code will automatically insert a row in the `order_item` junction table to relate the order with the item. + +> Info: The [[yii\db\ActiveRecord::link()|link()]] method will NOT perform any data validation while + saving the affected Active Record instance. It is your responsibility to validate any input data before + calling this method. + +The opposite operation to [[yii\db\ActiveRecord::link()|link()]] is [[yii\db\ActiveRecord::unlink()|unlink()]] +which breaks an existing relationship between two Active Record instances. For example, + +```php +$customer = Customer::find()->with('orders')->where(['id' => 123])->one(); +$customer->unlink('orders', $customer->orders[0]); +``` + +By default, the [[yii\db\ActiveRecord::unlink()|unlink()]] method will set the foreign key value(s) that specify +the existing relationship to be null. You may, however, choose to delete the table row that contains the foreign key value +by passing the `$delete` parameter as true to the method. + +When a junction table is involved in a relation, calling [[yii\db\ActiveRecord::unlink()|unlink()]] will cause +the foreign keys in the junction table to be cleared, or the deletion of the corresponding row in the junction table +if `$delete` is true. + + +## Cross-Database Relations + +Active Record allows you to declare relations between Active Record classes that are powered by different databases. +The databases can be of different types (e.g. MySQL and PostgreSQL, or MS SQL and MongoDB), and they can run on +different servers. You can use the same syntax to perform relational queries. For example, + +```php +<<<<<<< HEAD // Relational database Active Record ======= With this modified relation declaration, we will have: @@ -1594,6 +1751,9 @@ different servers. You can use the same syntax to perform relational queries. Fo ```php // Customer is associated with the "customer" table in a relational database (e.g. MySQL) >>>>>>> yiichina/master +======= +// Customer is associated with the "customer" table in a relational database (e.g. MySQL) +>>>>>>> master class Customer extends \yii\db\ActiveRecord { public static function tableName() @@ -1603,20 +1763,28 @@ class Customer extends \yii\db\ActiveRecord public function getComments() { +<<<<<<< HEAD <<<<<<< HEAD // Customer, stored in relational database, has many Comments, stored in MongoDB collection: ======= // a customer has many comments >>>>>>> yiichina/master +======= + // a customer has many comments +>>>>>>> master return $this->hasMany(Comment::className(), ['customer_id' => 'id']); } } +<<<<<<< HEAD <<<<<<< HEAD // MongoDb Active Record ======= // Comment is associated with the "comment" collection in a MongoDB database >>>>>>> yiichina/master +======= +// Comment is associated with the "comment" collection in a MongoDB database +>>>>>>> master class Comment extends \yii\mongodb\ActiveRecord { public static function collectionName() @@ -1626,74 +1794,67 @@ class Comment extends \yii\mongodb\ActiveRecord public function getCustomer() { +<<<<<<< HEAD <<<<<<< HEAD // Comment, stored in MongoDB collection, has one Customer, stored in relational database: +======= + // a comment has one customer +>>>>>>> master return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } + +$customers = Customer::find()->with('comments')->all(); ``` -All Active Record features like eager and lazy loading, establishing and breaking a relationship and so on, are -available for cross-DBMS relations. - -> Note: do not forget Active Record solutions for different DBMS may have specific methods and features, which may not be - applied for cross-DBMS relations. For example: usage of [[yii\db\ActiveQuery::joinWith()]] will obviously not work with - relation to the MongoDB collection. +You can use most of the relational query features that have been described in this section. + +> Note: Usage of [[yii\db\ActiveQuery::joinWith()|joinWith()]] is limited to databases that allow cross-database JOIN queries. + For this reason, you cannot use this method in the above example because MongoDB does not support JOIN. -Scopes ------- - -When you call [[yii\db\ActiveRecord::find()|find()]] or [[yii\db\ActiveRecord::findBySql()|findBySql()]], it returns an -[[yii\db\ActiveQuery|ActiveQuery]] instance. -You may call additional query methods, such as [[yii\db\ActiveQuery::where()|where()]], [[yii\db\ActiveQuery::orderBy()|orderBy()]], -to further specify the query conditions. - -It is possible that you may want to call the same set of query methods in different places. If this is the case, -you should consider defining the so-called *scopes*. A scope is essentially a method defined in a custom query class that calls a set of query methods to modify the query object. You can then use a scope instead of calling a normal query method. - -Two steps are required to define a scope. First, create a custom query class for your model and define the needed scope -methods in this class. For example, create a `CommentQuery` class for the `Comment` model and define the `active()` -scope method like the following: - -```php -namespace app\models; - -use yii\db\ActiveQuery; - -class CommentQuery extends ActiveQuery -{ - public function active($state = true) - { - $this->andWhere(['active' => $state]); - return $this; - } -} -``` - -Important points are: - -1. Class should extend from `yii\db\ActiveQuery` (or another `ActiveQuery` such as `yii\mongodb\ActiveQuery`). -2. A method should be `public` and should return `$this` in order to allow method chaining. It may accept parameters. -3. Check [[yii\db\ActiveQuery]] methods that are very useful for modifying query conditions. - -Second, override [[yii\db\ActiveRecord::find()]] to use the custom query class instead of the regular [[yii\db\ActiveQuery|ActiveQuery]]. -For the example above, you need to write the following code: +## Customizing Query Classes +By default, all Active Record queries are supported by [[yii\db\ActiveQuery]]. To use a customized query class +in an Active Record class, you should override the [[yii\db\ActiveRecord::find()]] method and return an instance +of your customized query class. For example, + ```php namespace app\models; use yii\db\ActiveRecord; +use yii\db\ActiveQuery; class Comment extends ActiveRecord { - /** - * @inheritdoc - * @return CommentQuery - */ public static function find() { return new CommentQuery(get_called_class()); + } +} + +class CommentQuery extends ActiveQuery +{ + // ... +} +``` + +Now whenever you are performing a query (e.g. `find()`, `findOne()`) or defining a relation (e.g. `hasOne()`) +with `Comment`, you will be working with an instance of `CommentQuery` instead of `ActiveQuery`. + +> Tip: In big projects, it is recommended that you use customized query classes to hold most query-related code + so that the Active Record classes can be kept clean. + +You can customize a query class in many creative ways to improve your query building experience. For example, +you can define new query building methods in a customized query class: + +```php +class CommentQuery extends ActiveQuery +{ + public function active($state = true) + { +<<<<<<< HEAD + return new CommentQuery(get_called_class()); ======= // a comment has one customer return $this->hasOne(Customer::className(), ['id' => 'customer_id']); @@ -1751,10 +1912,14 @@ class CommentQuery extends ActiveQuery { return $this->andWhere(['active' => $state]); >>>>>>> yiichina/master +======= + return $this->andWhere(['active' => $state]); +>>>>>>> master } } ``` +<<<<<<< HEAD <<<<<<< HEAD That's it. Now you can use your custom scope methods: @@ -1766,27 +1931,38 @@ That's it. Now you can use your custom scope methods: This allows you to write query building code like the following: >>>>>>> yiichina/master +======= +> Note: Instead of calling [[yii\db\ActiveQuery::where()|where()]], you usually should call + [[yii\db\ActiveQuery::andWhere()|andWhere()]] or [[yii\db\ActiveQuery::orWhere()|orWhere()]] to append additional + conditions when defining new query building methods so that any existing conditions are not overwritten. + +This allows you to write query building code like the following: + +>>>>>>> master ```php $comments = Comment::find()->active()->all(); $inactiveComments = Comment::find()->active(false)->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD You can also use scopes when defining relations. For example, +======= +You can also use the new query building methods when defining relations about `Comment` or performing relational query: +>>>>>>> master ```php -class Post extends \yii\db\ActiveRecord +class Customer extends \yii\db\ActiveRecord { public function getActiveComments() { - return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active(); - + return $this->hasMany(Comment::className(), ['customer_id' => 'id'])->active(); } } -``` -Or use the scopes on-the-fly when performing a relational query: +$customers = Customer::find()->with('activeComments')->all(); +<<<<<<< HEAD ```php $posts = Post::find()->with([ ======= @@ -1807,29 +1983,69 @@ $customers = Customer::find()->with('activeComments')->all(); $customers = Customer::find()->with([ >>>>>>> yiichina/master +======= +// or alternatively + +$customers = Customer::find()->with([ +>>>>>>> master 'comments' => function($q) { $q->active(); } ])->all(); ``` +<<<<<<< HEAD <<<<<<< HEAD ### Default Scope +======= +> Info: In Yii 1.1, there is a concept called *scope*. Scope is no longer directly supported in Yii 2.0, + and you should use customized query classes and query methods to achieve the same goal. -If you used Yii 1.1 before, you may know a concept called *default scope*. A default scope is a scope that -applies to ALL queries. You can define a default scope easily by overriding [[yii\db\ActiveRecord::find()]]. For example, + +## Selecting extra fields + +When Active Record instance is populated from query results, its attributes are filled up by corresponding column +values from received data set. +>>>>>>> master + +You are able to fetch additional columns or values from query and store it inside the Active Record. +For example, assume we have a table named 'room', which contains information about rooms available in the hotel. +Each room stores information about its geometrical size using fields 'length', 'width', 'height'. +Imagine we need to retrieve list of all available rooms with their volume in descendant order. +So you can not calculate volume using PHP, because we need to sort the records by its value, but you also want 'volume' +to be displayed in the list. +To achieve the goal, you need to declare an extra field in your 'Room' Active Record class, which will store 'volume' value: ```php -public static function find() +class Room extends \yii\db\ActiveRecord { - return parent::find()->where(['deleted' => false]); + public $volume; + + // ... } ``` -Note that all your queries should then not use [[yii\db\ActiveQuery::where()|where()]] but -[[yii\db\ActiveQuery::andWhere()|andWhere()]] and [[yii\db\ActiveQuery::orWhere()|orWhere()]] -to not override the default condition. +Then you need to compose a query, which calculates volume of the room and performs the sort: +```php +$rooms = Room::find() + ->select([ + '{{room}}.*', // select all columns + '([[length]] * [[width]].* [[height]]) AS volume', // calculate a volume + ]) + ->orderBy('volume DESC') // apply sort + ->all(); + +foreach ($rooms as $room) { + echo $room->volume; // contains value calculated by SQL +} +``` + +Ability to select extra fields can be exceptionally useful for aggregation queries. +Assume you need to display a list of customers with the count of orders they have made. +First of all, you need to declare a `Customer` class with 'orders' relation and extra field for count storage: + +<<<<<<< HEAD ======= > Info: In Yii 1.1, there is a concept called *scope*. Scope is no longer directly supported in Yii 2.0, and you should use customized query classes and query methods to achieve the same goal. @@ -1876,7 +2092,7 @@ foreach ($rooms as $room) { Ability to select extra fields can be exceptionally useful for aggregation queries. Assume you need to display a list of customers with the count of orders they have made. First of all, you need to declare a `Customer` class with 'orders' relation and extra field for count storage: - +======= ```php class Customer extends \yii\db\ActiveRecord { @@ -1893,6 +2109,106 @@ class Customer extends \yii\db\ActiveRecord Then you can compose a query, which joins the orders and calculates their count: +```php +$customers = Customer::find() + ->select([ + '{{customer}}.*', // select all customer fields + 'COUNT({{order}}.id) AS ordersCount' // calculate orders count + ]) + ->joinWith('orders') // ensure table junction + ->groupBy('{{customer}}.id') // group the result to ensure aggregation function works + ->all(); +``` + +A disadvantage of using this method would be that if the information isn't loaded on the SQL query it has to be calculated +separately, which also means that newly saved records won't contain the information from any extra field. + +```php +$room = new Room(); +$room->length = 100; +$room->width = 50; +$room->height = 2; + +$room->volume; // this value will be null since it was not declared yet. +``` + +Using the [[yii\db\BaseActiveRecord::__get()|__get()]] and [[yii\db\BaseActiveRecord::__set()|__set()]] magic methods +we can emulate the behavior of a property + +```php +class Room extends \yii\db\ActiveRecord +{ + private $_volume; + + public function setVolume($volume) + { + $this->_volume = (float) $volume; + } + + public function getVolume() + { + if (empty($this->length) || empty($this->width) || empty($this->height)) { + return null; + } + + if ($this->_volume === null) { + $this->setVolume( + $this->length * $this->width * $this->height + ); + } + + return $this->_volume; + } + + // ... +} +``` + +When the select query doesn't provide the volume, the model will be able to calculate it automatically using +the attributes of the model. + +Similary it can be used on extra fields depending on relational data +>>>>>>> master + +```php +class Customer extends \yii\db\ActiveRecord +{ +<<<<<<< HEAD + public $ordersCount; +======= + private $_ordersCount; + + public function setOrdersCount($count) + { + $this->_ordersCount = (int) $count; + } + + public function getOrdersCount() + { + if ($this->isNewRecord) { + return null; // This avoid calling a query searching for null primary keys. + } + + if ($this->_ordersCount === null) { + $this->setOrdersCount(count($this->orders)); + } + + return $this->_ordersCount; + } +>>>>>>> master + + // ... + + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} +``` +<<<<<<< HEAD + +Then you can compose a query, which joins the orders and calculates their count: + ```php $customers = Customer::find() ->select([ @@ -1904,3 +2220,5 @@ $customers = Customer::find() ->all(); ``` >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide/db-dao.md b/docs/guide/db-dao.md index 402ffc4802..3acd5fa841 100644 --- a/docs/guide/db-dao.md +++ b/docs/guide/db-dao.md @@ -89,80 +89,72 @@ and [[yii\db\Connection::password|password]]. Please refer to [[yii\db\Connectio > Info: When you create a DB connection instance, the actual connection to the database is not established until you execute the first SQL or you call the [[yii\db\Connection::open()|open()]] method explicitly. +<<<<<<< HEAD <<<<<<< HEAD ======= > Tip: Sometimes you may want to execute some queries right after the database connection is established to initialize > some environment variables. You can register an event handler for the [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] event +======= +> Tip: Sometimes you may want to execute some queries right after the database connection is established to initialize +> some environment variables (e.g., to set the timezone or character set). You can do so by registering an event handler +> for the [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] event +>>>>>>> master > of the database connection. You may register the handler directly in the application configuration like so: > > ```php > 'db' => [ > // ... > 'on afterOpen' => function($event) { +<<<<<<< HEAD > $event->sender->createCommand("YOUR SQL HERE")->execute(); > } > ] > ``` >>>>>>> yiichina/master +======= +> // $event->sender refers to the DB connection +> $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); +> } +> ], +> ``` + +>>>>>>> master ## Executing SQL Queries Once you have a database connection instance, you can execute a SQL query by taking the following steps: -1. Create a [[yii\db\Command]] with a plain SQL; +1. Create a [[yii\db\Command]] with a plain SQL query; 2. Bind parameters (optional); 3. Call one of the SQL execution methods in [[yii\db\Command]]. The following example shows various ways of fetching data from a database: ```php -$db = new yii\db\Connection(...); - // return a set of rows. each row is an associative array of column names and values. -// an empty array is returned if no results -$posts = $db->createCommand('SELECT * FROM post') +// an empty array is returned if the query returned no results +$posts = Yii::$app->db->createCommand('SELECT * FROM post') ->queryAll(); // return a single row (the first row) -// false is returned if no results -$post = $db->createCommand('SELECT * FROM post WHERE id=1') +// false is returned if the query has no result +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1') ->queryOne(); // return a single column (the first column) -// an empty array is returned if no results -$titles = $db->createCommand('SELECT title FROM post') +// an empty array is returned if the query returned no results +$titles = Yii::$app->db->createCommand('SELECT title FROM post') ->queryColumn(); -// return a scalar -// false is returned if no results -$count = $db->createCommand('SELECT COUNT(*) FROM post') +// return a scalar value +// false is returned if the query has no result +$count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post') ->queryScalar(); ``` > Note: To preserve precision, the data fetched from databases are all represented as strings, even if the corresponding database column types are numerical. -> Tip: If you need to execute a SQL query right after establishing a connection (e.g., to set the timezone or character set), -> you can do so in the [[yii\db\Connection::EVENT_AFTER_OPEN]] event handler. For example, -> -```php -return [ - // ... - 'components' => [ - // ... - 'db' => [ - 'class' => 'yii\db\Connection', - // ... - 'on afterOpen' => function($event) { - // $event->sender refers to the DB connection - $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); - } - ], - ], - // ... -]; -``` - ### Binding Parameters @@ -170,7 +162,7 @@ When creating a DB command from a SQL with parameters, you should almost always to prevent SQL injection attacks. For example, ```php -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') ->bindValue(':id', $_GET['id']) ->bindValue(':status', 1) ->queryOne(); @@ -190,11 +182,11 @@ The following example shows alternative ways of binding parameters: ```php $params = [':id' => $_GET['id'], ':status' => 1]; -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') ->bindValues($params) ->queryOne(); -$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) ->queryOne(); ``` @@ -203,17 +195,18 @@ Besides preventing SQL injection attacks, it may also improve performance by pre executing it multiple times with different parameters. For example, ```php -$command = $db->createCommand('SELECT * FROM post WHERE id=:id'); +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id'); $post1 = $command->bindValue(':id', 1)->queryOne(); $post2 = $command->bindValue(':id', 2)->queryOne(); +// ... ``` Because [[yii\db\Command::bindParam()|bindParam()]] supports binding parameters by references, the above code can also be written like the following: ```php -$command = $db->createCommand('SELECT * FROM post WHERE id=:id') +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id') ->bindParam(':id', $id); $id = 1; @@ -221,6 +214,7 @@ $post1 = $command->queryOne(); $id = 2; $post2 = $command->queryOne(); +// ... ``` Notice that you bind the placeholder to the `$id` variable before the execution, and then change the value of that variable @@ -234,7 +228,7 @@ The `queryXyz()` methods introduced in the previous sections all deal with SELEC For queries that do not bring back data, you should call the [[yii\db\Command::execute()]] method instead. For example, ```php -$db->createCommand('UPDATE post SET status=1 WHERE id=1') +Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') ->execute(); ``` @@ -246,16 +240,16 @@ SQLs. These methods will properly quote table and column names and bind paramete ```php // INSERT (table name, column values) -$db->createCommand()->insert('user', [ +Yii::$app->db->createCommand()->insert('user', [ 'name' => 'Sam', 'age' => 30, ])->execute(); // UPDATE (table name, column values, condition) -$db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); +Yii::$app->db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); // DELETE (table name, condition) -$db->createCommand()->delete('user', 'status = 0')->execute(); +Yii::$app->db->createCommand()->delete('user', 'status = 0')->execute(); ``` You may also call [[yii\db\Command::batchInsert()|batchInsert()]] to insert multiple rows in one shot, which is much @@ -263,39 +257,43 @@ more efficient than inserting one row at a time: ```php // table name, column names, column values -$db->createCommand()->batchInsert('user', ['name', 'age'], [ +Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ ['Tom', 30], ['Jane', 20], ['Linda', 25], ])->execute(); ``` +Note that the aforementioned methods only create the query and you always have to call [[yii\db\Command::execute()|execute()]] +to actually run them. + ## Quoting Table and Column Names -When writing database-agnostic code, properly quote table and column names is often a headache because +When writing database-agnostic code, properly quoting table and column names is often a headache because different databases have different name quoting rules. To overcome this problem, you may use the following quoting syntax introduced by Yii: * `[[column name]]`: enclose a column name to be quoted in double square brackets; * `{{table name}}`: enclose a table name to be quoted in double curly brackets. -Yii DAO will automatically turn such constructs in a SQL into the corresponding quoted column or table names. +Yii DAO will automatically convert such constructs into the corresponding quoted column or table names using the +DBMS specific syntax. For example, ```php // executes this SQL for MySQL: SELECT COUNT(`id`) FROM `employee` -$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") ->queryScalar(); ``` ### Using Table Prefix -If most of your DB tables use some common prefix in their tables, you may use the table prefix feature supported +If most of your DB tables names share a common prefix, you may use the table prefix feature provided by Yii DAO. -First, specify the table prefix via the [[yii\db\Connection::tablePrefix]] property: +First, specify the table prefix via the [[yii\db\Connection::tablePrefix]] property in the application config: ```php return [ @@ -311,12 +309,12 @@ return [ ``` Then in your code, whenever you need to refer to a table whose name contains such a prefix, use the syntax -`{{%table name}}`. The percentage character will be automatically replaced with the table prefix that you have specified +`{{%table_name}}`. The percentage character will be automatically replaced with the table prefix that you have specified when configuring the DB connection. For example, ```php // executes this SQL for MySQL: SELECT COUNT(`id`) FROM `tbl_employee` -$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") ->queryScalar(); ``` @@ -325,21 +323,22 @@ $count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") When running multiple related queries in a sequence, you may need to wrap them in a transaction to ensure the integrity and consistency of your database. If any of the queries fails, the database will be rolled back to the state as if -none of these queries is executed. +none of these queries were executed. The following code shows a typical way of using transactions: ```php -$db->transaction(function($db) { +Yii::$app->db->transaction(function($db) { $db->createCommand($sql1)->execute(); $db->createCommand($sql2)->execute(); // ... executing other SQL statements ... }); ``` -The above code is equivalent to the following: +The above code is equivalent to the following, which gives you more control about the error handling code: ```php +$db = Yii::$app->db; $transaction = $db->beginTransaction(); try { @@ -360,26 +359,27 @@ try { By calling the [[yii\db\Connection::beginTransaction()|beginTransaction()]] method, a new transaction is started. The transaction is represented as a [[yii\db\Transaction]] object stored in the `$transaction` variable. Then, the queries being executed are enclosed in a `try...catch...` block. If all queries are executed successfully, -the [[yii\db\Transaction::commit()|commit()]] method is called to commit the transaction. Otherwise, an exception -will be triggered and caught, and the [[yii\db\Transaction::rollBack()|rollBack()]] method is called to roll back -the changes made by the queries prior to that failed query in the transaction. +the [[yii\db\Transaction::commit()|commit()]] method is called to commit the transaction. Otherwise, if an exception +will be triggered and caught, the [[yii\db\Transaction::rollBack()|rollBack()]] method is called to roll back +the changes made by the queries prior to that failed query in the transaction. `throw $e` will then re-throw the +exception as if we had not caught it, so the normal error handling process will take care of it. ### Specifying Isolation Levels Yii also supports setting [isolation levels] for your transactions. By default, when starting a new transaction, -it will use the isolation level set by your database system. You can override the default isolation level as follows, +it will use the default isolation level set by your database system. You can override the default isolation level as follows, ```php $isolationLevel = \yii\db\Transaction::REPEATABLE_READ; -$db->transaction(function ($db) { +Yii::$app->db->transaction(function ($db) { .... }, $isolationLevel); // or alternatively -$transaction = $db->beginTransaction($isolationLevel); +$transaction = Yii::$app->db->beginTransaction($isolationLevel); ``` Yii provides four constants for the most common isolation levels: @@ -395,7 +395,7 @@ by the DBMS that you are using. For example, in PostgreSQL, you may use `SERIALI Note that some DBMS allow setting the isolation level only for the whole connection. Any subsequent transactions will get the same isolation level even if you do not specify any. When using this feature you may need to set the isolation level for all transactions explicitly to avoid conflicting settings. -At the time of this writing, only MSSQL and SQLite are affected. +At the time of this writing, only MSSQL and SQLite are affected by this limitation. > Note: SQLite only supports two isolation levels, so you can only use `READ UNCOMMITTED` and `SERIALIZABLE`. Usage of other levels will result in an exception being thrown. @@ -412,7 +412,7 @@ You have to call [[yii\db\Transaction::setIsolationLevel()]] in this case after If your DBMS supports Savepoint, you may nest multiple transactions like the following: ```php -$db->transaction(function ($db) { +Yii::$app->db->transaction(function ($db) { // outer transaction $db->transaction(function ($db) { @@ -424,6 +424,7 @@ $db->transaction(function ($db) { Or alternatively, ```php +$db = Yii::$app->db; $outerTransaction = $db->beginTransaction(); try { $db->createCommand($sql1)->execute(); @@ -432,13 +433,15 @@ try { try { $db->createCommand($sql2)->execute(); $innerTransaction->commit(); - } catch (Exception $e) { + } catch (\Exception $e) { $innerTransaction->rollBack(); + throw $e; } $outerTransaction->commit(); -} catch (Exception $e) { +} catch (\Exception $e) { $outerTransaction->rollBack(); + throw $e; } ``` @@ -448,7 +451,7 @@ try { Many DBMS support [database replication](http://en.wikipedia.org/wiki/Replication_(computing)#Database_replication) to get better database availability and faster server response time. With database replication, data are replicated from the so-called *master servers* to *slave servers*. All writes and updates must take place on the master servers, -while reads may take place on the slave servers. +while reads may also take place on the slave servers. To take advantage of database replication and achieve read-write splitting, you can configure a [[yii\db\Connection]] component like the following: @@ -488,18 +491,18 @@ Such read-write splitting is accomplished automatically with this configuration. ```php // create a Connection instance using the above configuration -$db = Yii::createObject($config); +Yii::$app->db = Yii::createObject($config); // query against one of the slaves -$rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); +$rows = Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); // query against the master -$db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); +Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); ``` > Info: Queries performed by calling [[yii\db\Command::execute()]] are considered as write queries, while all other queries done through one of the "query" methods of [[yii\db\Command]] are read queries. - You can get the currently active slave connection via `$db->slave`. + You can get the currently active slave connection via `Yii::$app->db->slave`. The `Connection` component supports load balancing and failover between slaves. When performing a read query for the first time, the `Connection` component will randomly pick a slave and @@ -569,6 +572,7 @@ By default, transactions use the master connection. And within a transaction, al the master connection. For example, ```php +$db = Yii::$app->db; // the transaction is started on the master connection $transaction = $db->beginTransaction(); @@ -587,24 +591,24 @@ try { If you want to start a transaction with the slave connection, you should explicitly do so, like the following: ```php -$transaction = $db->slave->beginTransaction(); +$transaction = Yii::$app->db->slave->beginTransaction(); ``` Sometimes, you may want to force using the master connection to perform a read query. This can be achieved with the `useMaster()` method: ```php -$rows = $db->useMaster(function ($db) { +$rows = Yii::$app->db->useMaster(function ($db) { return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); }); ``` -You may also directly set `$db->enableSlaves` to be false to direct all queries to the master connection. +You may also directly set `Yii::$app->db->enableSlaves` to be false to direct all queries to the master connection. ## Working with Database Schema -Yii DAO provides a whole set of methods to let you manipulate database schema, such as creating new tables, +Yii DAO provides a whole set of methods to let you manipulate the database schema, such as creating new tables, dropping a column from a table, etc. These methods are listed as follows: * [[yii\db\Command::createTable()|createTable()]]: creating a table @@ -626,18 +630,23 @@ These methods can be used like the following: ```php // CREATE TABLE -$db->createCommand()->createTable('post', [ +Yii::$app->db->createCommand()->createTable('post', [ 'id' => 'pk', 'title' => 'string', 'text' => 'text', ]); ``` -You can also retrieve the definition information about a table through +The above array describes the name and types of the columns to be created. For the column types, Yii provides +a set of abstract data types, that allow you to define a database agnostic schema. These are converted to +DBMS specific type definitions dependent on the database, the table is created in. +Please refer to the API documentation of the [[yii\db\Command::createTable()|createTable()]]-method for more information. + +Besides changing the database schema, you can also retrieve the definition information about a table through the [[yii\db\Connection::getTableSchema()|getTableSchema()]] method of a DB connection. For example, ```php -$table = $db->getTableSchema('post'); +$table = Yii::$app->db->getTableSchema('post'); ``` The method returns a [[yii\db\TableSchema]] object which contains the information about the table's columns, diff --git a/docs/guide/db-elasticsearch.md b/docs/guide/db-elasticsearch.md deleted file mode 100644 index a83d6a0c36..0000000000 --- a/docs/guide/db-elasticsearch.md +++ /dev/null @@ -1,6 +0,0 @@ -Elasticsearch -============= - -> Note: This section is under development. -> -> It has no content yet. diff --git a/docs/guide/db-migrations.md b/docs/guide/db-migrations.md index 753975eb72..894bf7a2d7 100644 --- a/docs/guide/db-migrations.md +++ b/docs/guide/db-migrations.md @@ -1,6 +1,7 @@ Database Migration ================== +<<<<<<< HEAD <<<<<<< HEAD > Note: This section is under development. @@ -19,19 +20,48 @@ with the source code. The following steps show how database migration can be used by a team during development: >>>>>>> yiichina/master +======= +During the course of developing and maintaining a database-driven application, the structure of the database +being used evolves just like the source code does. For example, during the development of an application, +a new table may be found necessary; after the application is deployed to production, it may be discovered +that an index should be created to improve the query performance; and so on. Because a database structure change +often requires some source code changes, Yii supports the so-called *database migration* feature that allows +you to keep track of database changes in terms of *database migrations* which are version-controlled together +with the source code. + +The following steps show how database migration can be used by a team during development: +>>>>>>> master 1. Tim creates a new migration (e.g. creates a new table, changes a column definition, etc.). 2. Tim commits the new migration into the source control system (e.g. Git, Mercurial). 3. Doug updates his repository from the source control system and receives the new migration. <<<<<<< HEAD +<<<<<<< HEAD 4. Doug applies the migration to his local development database, thereby syncing his database to reflect the changes Tim made. +======= +4. Doug applies the migration to his local development database, thereby synchronizing his database + to reflect the changes that Tim has made. -Yii supports database migration via the `yii migrate` command line tool. This tool supports: +And the following steps show how to deploy a new release with database migrations to production: -* Creating new migrations -* Applying, reverting, and redoing migrations -* Showing migration history and new migrations +1. Scott creates a release tag for the project repository that contains some new database migrations. +2. Scott updates the source code on the production server to the release tag. +3. Scott applies any accumulated database migrations to the production database. +Yii provides a set of migration command line tools that allow you to: +>>>>>>> master + +* create new migrations; +* apply migrations; +* revert migrations; +* re-apply migrations; +* show migration history and status. + +All these tools are accessible through the command `yii migrate`. In this section we will describe in detail +how to accomplish various tasks using these tools. You may also get the usage of each tool via the help +command `yii help migrate`. + +<<<<<<< HEAD Creating Migrations ------------------- ======= @@ -62,6 +92,13 @@ command `yii help migrate`. ## Creating Migrations >>>>>>> yiichina/master +======= +> Tip: migrations could affect not only database schema but adjust existing data to fit new schema, create RBAC + hierarchy or clean up cache. + + +## Creating Migrations +>>>>>>> master To create a new migration, run the following command: @@ -69,6 +106,7 @@ To create a new migration, run the following command: yii migrate/create ``` +<<<<<<< HEAD <<<<<<< HEAD The required `name` parameter specifies a very brief description of the migration. For example, if the migration creates a new table named *news*, you'd use the command: ======= @@ -76,20 +114,32 @@ The required `name` argument gives a brief description about the new migration. the migration is about creating a new table named *news*, you may use the name `create_news_table` and run the following command: >>>>>>> yiichina/master +======= +The required `name` argument gives a brief description about the new migration. For example, if +the migration is about creating a new table named *news*, you may use the name `create_news_table` +and run the following command: +>>>>>>> master ``` yii migrate/create create_news_table ``` +<<<<<<< HEAD <<<<<<< HEAD As you'll shortly see, the `name` parameter is used as part of a PHP class name in the migration. Therefore, it should only contain letters, digits and/or underscore characters. +======= +> Note: Because the `name` argument will be used as part of the generated migration class name, + it should only contain letters, digits, and/or underscore characters. +>>>>>>> master -The above command will create a new -file named `m101129_185401_create_news_table.php`. This file will be created within the `@app/migrations` directory. Initially, the migration file will be generated with the following code: +The above command will create a new PHP class file named `m150101_185401_create_news_table.php` +in the `@app/migrations` directory. The file contains the following code which mainly declares +a migration class `m150101_185401_create_news_table` with the skeleton code: ```php +<<<<<<< HEAD class m101129_185401_create_news_table extends \yii\db\Migration ======= > Note: Because the `name` argument will be used as part of the generated migration class name, @@ -107,36 +157,52 @@ use yii\db\Migration; class m150101_185401_create_news_table extends Migration >>>>>>> yiichina/master +======= +>>>>>> master { public function up() { + } public function down() { echo "m101129_185401_create_news_table cannot be reverted.\n"; + return false; } -} -``` +<<<<<<< HEAD <<<<<<< HEAD Notice that the class name is the same as the file name, and follows the pattern `m_`, where: +======= + /* + // Use safeUp/safeDown to run migration code within a transaction + public function safeUp() + { + } +>>>>>>> master -* `` refers to the UTC timestamp (in the -format of `yymmdd_hhmmss`) when the migration is created, -* `` is taken from the command's `name` parameter. + public function safeDown() + { + } + */ +} +``` -In the class, the `up()` method should contain the code implementing the actual database -migration. In other words, the `up()` method executes code that actually changes the database. The `down()` method may contain code that reverts the changes made by `up()`. +Each database migration is defined as a PHP class extending from [[yii\db\Migration]]. The migration +class name is automatically generated in the format of `m_`, where -Sometimes, it is impossible for the `down()` to undo the database migration. For example, if the migration deletes -table rows or an entire table, that data cannot be recovered in the `down()` method. In such -cases, the migration is called irreversible, meaning the database cannot be rolled back to -a previous state. When a migration is irreversible, as in the above generated code, the `down()` -method returns `false` to indicate that the migration cannot be reverted. +* `` refers to the UTC datetime at which the migration creation command is executed. +* `` is the same as the value of the `name` argument that you provide to the command. +<<<<<<< HEAD As an example, let's show the migration for creating a news table. ======= Each database migration is defined as a PHP class extending from [[yii\db\Migration]]. The migration @@ -150,11 +216,19 @@ You may also want to write code in the `down()` method to revert the changes mad when you upgrade the database with this migration, while the `down()` method is invoked when you downgrade the database. The following code shows how you may implement the migration class to create a `news` table: >>>>>>> yiichina/master +======= +In the migration class, you are expected to write code in the `up()` method that makes changes to the database structure. +You may also want to write code in the `down()` method to revert the changes made by `up()`. The `up()` method is invoked +when you upgrade the database with this migration, while the `down()` method is invoked when you downgrade the database. +The following code shows how you may implement the migration class to create a `news` table: +>>>>>>> master ```php +>>>>>> yiichina/master +======= +use yii\db\Migration; + +class m150101_185401_create_news_table extends Migration +>>>>>>> master { public function up() { $this->createTable('news', [ +<<<<<<< HEAD <<<<<<< HEAD 'id' => 'pk', ======= 'id' => Schema::TYPE_PK, >>>>>>> yiichina/master +======= + 'id' => Schema::TYPE_PK, +>>>>>>> master 'title' => Schema::TYPE_STRING . ' NOT NULL', 'content' => Schema::TYPE_TEXT, ]); @@ -180,31 +263,39 @@ class m150101_185401_create_news_table extends \yii\db\Migration { $this->dropTable('news'); } - } ``` +<<<<<<< HEAD <<<<<<< HEAD The base class [[\yii\db\Migration]] exposes a database connection via the `db` property. You can use it for manipulating data and the schema of a database. +======= +> Info: Not all migrations are reversible. For example, if the `up()` method deletes a row of a table, you may + not be able to recover this row in the `down()` method. Sometimes, you may be just too lazy to implement + the `down()`, because it is not very common to revert database migrations. In this case, you should return + `false` in the `down()` method to indicate that the migration is not reversible. +>>>>>>> master -The column types used in this example are abstract types that will be replaced -by Yii with the corresponding types depending on your database management system. -You can use them to write database independent migrations. -For example `pk` will be replaced by `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY` -for MySQL and `integer PRIMARY KEY AUTOINCREMENT NOT NULL` for sqlite. -See documentation of [[yii\db\QueryBuilder::getColumnType()]] for more details and a list -of available types. You may also use the constants defined in [[yii\db\Schema]] to -define column types. +The base migration class [[yii\db\Migration]] exposes a database connection via the [[yii\db\Migration::db|db]] +property. You can use it to manipulate the database schema using the methods as described in +[Working with Database Schema](db-dao.md#working-with-database-schema-). -> Note: You can add constraints and other custom table options at the end of the table description by -> specifying them as a simple string. For example, in the above migration, after the `content` attribute definition -> you can write `'CONSTRAINT ...'` or other custom options. +Rather than using physical types, when creating a table or column you should use *abstract types* +so that your migrations are independent of specific DBMS. The [[yii\db\Schema]] class defines +a set of constants to represent the supported abstract types. These constants are named in the format +of `TYPE_`. For example, `TYPE_PK` refers to auto-incremental primary key type; `TYPE_STRING` +refers to a string type. When a migration is applied to a particular database, the abstract types +will be translated into the corresponding physical types. In the case of MySQL, `TYPE_PK` will be turned +into `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, while `TYPE_STRING` becomes `varchar(255)`. +You can append additional constraints when using abstract types. In the above example, ` NOT NULL` is appended +to `Schema::TYPE_STRING` to specify that the column cannot be null. -Transactional Migrations ------------------------- +> Info: The mapping between abstract types and physical types is specified by + the [[yii\db\QueryBuilder::$typeMap|$typeMap]] property in each concrete `QueryBuilder` class. +<<<<<<< HEAD While performing complex DB migrations, we usually want to make sure that each migration succeeds or fail as a whole so that the database maintains its consistency and integrity. In order to achieve this goal, we can exploit @@ -246,9 +337,15 @@ As a result, if any operation in these methods fails, all prior operations will In the following example, besides creating the `news` table we also insert an initial row into this table. >>>>>>> yiichina/master +======= +Since version 2.0.6, you can make use of the newly introduced schema builder which provides more convenient way of defining column schema. +So the migration above could be written like the following: +>>>>>>> master ```php +>>>>>> yiichina/master +======= +use yii\db\Migration; + +class m150101_185401_create_news_table extends Migration +>>>>>>> master { - public function safeUp() + public function up() { $this->createTable('news', [ - 'id' => 'pk', - 'title' => Schema::TYPE_STRING . ' NOT NULL', - 'content' => Schema::TYPE_TEXT, + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), ]); +<<<<<<< HEAD <<<<<<< HEAD $this->createTable('user', [ @@ -278,30 +381,147 @@ class m150101_185401_create_news_table extends Migration 'title' => 'test 1', 'content' => 'content 1', >>>>>>> yiichina/master - ]); +======= } - public function safeDown() + public function down() { -<<<<<<< HEAD $this->dropTable('news'); - $this->dropTable('user'); } - } ``` -When your code uses more then one query it is recommended to use `safeUp` and `safeDown`. - -> Note: Not all DBMS support transactions. And some DB queries cannot be put -> into a transaction. In this case, you will have to implement `up()` and -> `down()`, instead. In the case of MySQL, some SQL statements may cause -> [implicit commit](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html). +A list of all available methods for defining the column types is available in the API documentation of [[yii\db\SchemaBuilderTrait]]. -Applying Migrations -------------------- +## Generating Migrations +Since version 2.0.7 migration console provides a convenient way to create migrations. + +If the migration name is of a special form, for example `create_xxx` or `drop_xxx` then the generated migration +file will contain extra code, in this case for creating/dropping tables. +In the following all variants of this feature are described. + +### Create Table + +```php +yii migrate/create create_post +``` + +generates + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() +>>>>>>> master + ]); + } + + /** + * @inheritdoc + */ + public function down() + { +<<<<<<< HEAD +<<<<<<< HEAD + $this->dropTable('news'); + $this->dropTable('user'); +======= + $this->dropTable('post'); + } +} +``` + +To create table fields right away, specify them via `--fields` option. + +```php +yii migrate/create create_post --fields="title:string,body:text" +``` + +generates + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(), + 'body' => $this->text(), + ]); +>>>>>>> master + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} + +``` + +You can specify more field parameters. + +```php +yii migrate/create create_post --fields="title:string(12):notNull:unique,body:text" +``` + +generates + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +> Note: primary key is added automatically and is named `id` by default. If you want to use another name you may +> specify it explicitly like `--fields="name:primaryKey"`. + +#### Foreign keys + +<<<<<<< HEAD To apply all available new migrations (i.e., make the local database up-to-date), run the following command: ======= @@ -362,148 +582,516 @@ Below is the list of all these database accessing methods: To upgrade a database to its latest structure, you should apply all available new migrations using the following command: >>>>>>> yiichina/master +======= +Since 2.0.8 the generator supports foreign keys using the `foreignKey` keyword. +>>>>>>> master + +```php +yii migrate/create create_post --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" +``` + +generates + +```php +/** + * Handles the creation for table `post`. + * Has foreign keys to the tables: + * + * - `user` + * - `category` + */ +class m160328_040430_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'author_id' => $this->integer()->notNull(), + 'category_id' => $this->integer()->defaultValue(1), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + + // creates index for column `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); + + // add foreign key for table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); + + // add foreign key for table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // drops foreign key for table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // drops index for column `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} +``` + +<<<<<<< HEAD +<<<<<<< HEAD +The command will show the list of all new migrations. If you confirm you want to apply +the migrations, it will run the `up()` method in every new migration class, one +after another, in the order of the timestamp value in the class name. +======= +The position of the `foreignKey` keyword in the column description doesn't +change the generated code. That means: + +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +All generate the same code. + +The `foreignKey` keyword can take a parameter between parenthesis which will be +the name of the related table for the generated foreign key. If no parameter +is passed then the table name will be deduced from the column name. + +In the example above `author_id:integer:notNull:foreignKey(user)` will generate a +column named `author_id` with a foreign key to the `user` table while +`category_id:integer:defaultValue(1):foreignKey` will generate a column +`category_id` with a foreign key to the `category` table. + +### Drop Table + +```php +yii migrate/create drop_post --fields="title:string(12):notNull:unique,body:text" +``` +>>>>>>> master + +generates + +```php +class m150811_220037_drop_post extends Migration +{ + public function up() + { + $this->dropTable('post'); + } + + public function down() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } +} +``` + +### Add Column + +If the migration name is of the form `add_xxx_to_yyy` then the file content would contain `addColumn` and `dropColumn` +statements necessary. + +To add column: + +```php +yii migrate/create add_position_to_post --fields="position:integer" +``` + +generates + +```php +class m150811_220037_add_position_to_post extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### Drop Column + +If the migration name is of the form `drop_xxx_from_yyy` then the file content would contain `addColumn` and `dropColumn` +statements necessary. + +```php +yii migrate/create drop_position_from_post --fields="position:integer" +``` + +generates + +```php +class m150811_220037_drop_position_from_post extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +### Add Junction Table + +If the migration name is in if the form of `create_junction_xxx_and_yyy` then code necessary to create junction table +will be generated. + +```php +yii migrate/create create_junction_post_and_tag --fields="created_at:dateTime" +``` + +generates + +```php +/** + * Handles the creation for table `post_tag`. + * Has foreign keys to the tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_post_and_tag extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post_tag', [ + 'post_id' => $this->integer(), + 'tag_id' => $this->integer(), + 'created_at' => $this->dateTime(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +``` + +### Transactional Migrations + +While performing complex DB migrations, it is important to ensure each migration to either succeed or fail as a whole +so that the database can maintain integrity and consistency. To achieve this goal, it is recommended that you +enclose the DB operations of each migration in a [transaction](db-dao.md#performing-transactions). + +An even easier way of implementing transactional migrations is to put migration code in the `safeUp()` and `safeDown()` +methods. These two methods differ from `up()` and `down()` in that they are enclosed implicitly in a transaction. +As a result, if any operation in these methods fails, all prior operations will be rolled back automatically. + +In the following example, besides creating the `news` table we also insert an initial row into this table. + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + + $this->insert('news', [ + 'title' => 'test 1', + 'content' => 'content 1', + ]); + } + + public function safeDown() + { + $this->delete('news', ['id' => 1]); + $this->dropTable('news'); + } +} +``` + +Note that usually when you perform multiple DB operations in `safeUp()`, you should reverse their execution order +in `safeDown()`. In the above example we first create the table and then insert a row in `safeUp()`; while +in `safeDown()` we first delete the row and then drop the table. + +> Note: Not all DBMS support transactions. And some DB queries cannot be put into a transaction. For some examples, + please refer to [implicit commit](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). If this is the case, + you should still implement `up()` and `down()`, instead. + + +### Database Accessing Methods + +The base migration class [[yii\db\Migration]] provides a set of methods to let you access and manipulate databases. +You may find these methods are named similarly as the [DAO methods](db-dao.md) provided by the [[yii\db\Command]] class. +For example, the [[yii\db\Migration::createTable()]] method allows you to create a new table, +just like [[yii\db\Command::createTable()]] does. + +The benefit of using the methods provided by [[yii\db\Migration]] is that you do not need to explicitly +create [[yii\db\Command]] instances and the execution of each method will automatically display useful messages +telling you what database operations are done and how long they take. + +Below is the list of all these database accessing methods: + +* [[yii\db\Migration::execute()|execute()]]: executing a SQL statement +* [[yii\db\Migration::insert()|insert()]]: inserting a single row +* [[yii\db\Migration::batchInsert()|batchInsert()]]: inserting multiple rows +* [[yii\db\Migration::update()|update()]]: updating rows +* [[yii\db\Migration::delete()|delete()]]: deleting rows +* [[yii\db\Migration::createTable()|createTable()]]: creating a table +* [[yii\db\Migration::renameTable()|renameTable()]]: renaming a table +* [[yii\db\Migration::dropTable()|dropTable()]]: removing a table +* [[yii\db\Migration::truncateTable()|truncateTable()]]: removing all rows in a table +* [[yii\db\Migration::addColumn()|addColumn()]]: adding a column +* [[yii\db\Migration::renameColumn()|renameColumn()]]: renaming a column +* [[yii\db\Migration::dropColumn()|dropColumn()]]: removing a column +* [[yii\db\Migration::alterColumn()|alterColumn()]]: altering a column +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: adding a primary key +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: removing a primary key +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: adding a foreign key +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: removing a foreign key +* [[yii\db\Migration::createIndex()|createIndex()]]: creating an index +* [[yii\db\Migration::dropIndex()|dropIndex()]]: removing an index +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: adding comment to column +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: dropping comment from column +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: adding comment to table +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: dropping comment from table + +> Info: [[yii\db\Migration]] does not provide a database query method. This is because you normally do not need + to display extra message about retrieving data from a database. It is also because you can use the powerful + [Query Builder](db-query-builder.md) to build and run complex queries. + +> Note: When manipulating data using a migration you may find that using your [Active Record](db-active-record.md) classes +> for this might be useful because some of the logic is already implemented there. Keep in mind however, that in contrast +> to code written in the migrations, who's nature is to stay constant forever, application logic is subject to change. +> So when using Active Record in migration code, changes to the logic in the Active Record layer may accidentally break +> existing migrations. For this reason migration code should be kept independent from other application logic such +> as Active Record classes. + + +## Applying Migrations + +To upgrade a database to its latest structure, you should apply all available new migrations using the following command: ``` yii migrate ``` -<<<<<<< HEAD -The command will show the list of all new migrations. If you confirm you want to apply -the migrations, it will run the `up()` method in every new migration class, one -after another, in the order of the timestamp value in the class name. +This command will list all migrations that have not been applied so far. If you confirm that you want to apply +these migrations, it will run the `up()` or `safeUp()` method in every new migration class, one after another, +in the order of their timestamp values. If any of the migrations fails, the command will quit without applying +the rest of the migrations. -After applying a migration, the migration tool will keep a record in a database -table named `migration`. This allows the tool to identify which migrations -have been applied and which have not. If the `migration` table does not exist, -the tool will automatically create it in the database specified by the `db` -[application component](structure-application-components.md). +> Tip: In case you don't have command line at your server you may try [web shell](https://github.com/samdark/yii2-webshell) +> extension. -Sometimes, we may only want to apply one or a few new migrations. We can use the -following command: +For each migration that has been successfully applied, the command will insert a row into a database table named +`migration` to record the successful application of the migration. This will allow the migration tool to identify +which migrations have been applied and which have not. + +> Info: The migration tool will automatically create the `migration` table in the database specified by + the [[yii\console\controllers\MigrateController::db|db]] option of the command. By default, the database + is specified by the `db` [application component](structure-application-components.md). + +Sometimes, you may only want to apply one or a few new migrations, instead of all available migrations. +You can do so by specifying the number of migrations that you want to apply when running the command. +For example, the following command will try to apply the next three available migrations: ``` -yii migrate/up 3 +yii migrate 3 ``` -This command will apply the next 3 new migrations. Changing the value 3 will allow -us to change the number of migrations to be applied. - -We can also migrate the database to a specific version with the following command: +You can also explicitly specify a particular migration to which the database should be migrated +by using the `migrate/to` command in one of the following formats: ``` -yii migrate/to 101129_185401 +yii migrate/to 150101_185401 # using timestamp to specify the migration +yii migrate/to "2015-01-01 18:54:01" # using a string that can be parsed by strtotime() +yii migrate/to m150101_185401_create_news_table # using full name +yii migrate/to 1392853618 # using UNIX timestamp ``` -That is, we use the timestamp part of a migration name to specify the version -that we want to migrate the database to. If there are multiple migrations between -the last applied migration and the specified migration, all these migrations -will be applied. If the specified migration has been applied before, then all -migrations applied after it will be reverted (to be described in the next section). +If there are any unapplied migrations earlier than the specified one, they will all be applied before the specified +migration is applied. + +If the specified migration has already been applied before, any later applied migrations will be reverted. -Reverting Migrations --------------------- +## Reverting Migrations -To revert the last migration step or several applied migrations, we can use the following -command: +To revert (undo) one or multiple migrations that have been applied before, you can run the following command: ``` -yii migrate/down [step] +yii migrate/down # revert the most recently applied migration +yii migrate/down 3 # revert the most 3 recently applied migrations ``` -where the optional `step` parameter specifies how many migrations to be reverted -back. It defaults to 1, meaning only the last applied migration will be reverted back. - -As we described before, not all migrations can be reverted. Trying to revert -such migrations will throw an exception and stop the entire reverting process. +> Note: Not all migrations are reversible. Trying to revert such migrations will cause an error and stop the + entire reverting process. -Redoing Migrations ------------------- +## Redoing Migrations -Redoing migrations means first reverting and then applying the specified migrations. -This can be done with the following command: +Redoing migrations means first reverting the specified migrations and then applying again. This can be done +as follows: ``` -yii migrate/redo [step] +yii migrate/redo # redo the last applied migration +yii migrate/redo 3 # redo the last 3 applied migrations ``` -where the optional `step` parameter specifies how many migrations to be redone. -It defaults to 1, which means only the last migration will be redone. +> Note: If a migration is not reversible, you will not be able to redo it. -Showing Migration Information ------------------------------ +## Listing Migrations -Besides applying and reverting migrations, the migration tool can also display -the migration history and the new migrations to be applied. +To list which migrations have been applied and which are not, you may use the following commands: ``` -yii migrate/history [limit] -yii migrate/new [limit] +yii migrate/history # showing the last 10 applied migrations +yii migrate/history 5 # showing the last 5 applied migrations +yii migrate/history all # showing all applied migrations + +yii migrate/new # showing the first 10 new migrations +yii migrate/new 5 # showing the first 5 new migrations +yii migrate/new all # showing all new migrations ``` -where the optional parameter `limit` specifies the number of migrations to be -displayed. If `limit` is not specified, all available migrations will be displayed. -The first command shows the migrations that have been applied, while the second -command shows the migrations that have not been applied. +## Modifying Migration History - -Modifying Migration History ---------------------------- - -Sometimes, we may want to modify the migration history to a specific migration -version without actually applying or reverting the relevant migrations. This -often happens when developing a new migration. We can use the following command -to achieve this goal. +Instead of actually applying or reverting migrations, sometimes you may simply want to mark that your database +has been upgraded to a particular migration. This often happens when you manually change the database to a particular +state and you do not want the migration(s) for that change to be re-applied later. You can achieve this goal with +the following command: ``` -yii migrate/mark 101129_185401 -``` - -This command is very similar to `yii migrate/to` command, except that it only -modifies the migration history table to the specified version without applying -or reverting the migrations. - - -Customizing Migration Command ------------------------------ - -There are several ways to customize the migration command. - -### Use Command Line Options - -The migration command comes with a few options that can be specified on the command -line: - -* `interactive`: boolean, specifies whether to perform migrations in an - interactive mode. Defaults to true, meaning the user will be prompted when - performing a specific migration. You may set this to false so the - migrations are performed as a background process. - -* `migrationPath`: string, specifies the directory storing all migration class - files. This must be specified in terms of a path alias, and the corresponding - directory must exist. If not specified, it will use the `migrations` - sub-directory under the application base path. - -* `migrationTable`: string, specifies the name of the database table for storing - migration history information. It defaults to `migration`. The table - structure is `version varchar(255) primary key, apply_time integer`. - -* `db`: string, specifies the ID of the database [application component](structure-application-components.md). - Defaults to 'db'. - -* `templateFile`: string, specifies the path of the file to be served as the code - template for generating the migration classes. This must be specified in terms - of a path alias (e.g. `application.migrations.template`). If not set, an - internal template will be used. Inside the template, the token `{ClassName}` - will be replaced with the actual migration class name. - -To specify these options, execute the migrate command using the following format: - -``` -yii migrate/up --option1=value1 --option2=value2 ... +yii migrate/mark 150101_185401 # using timestamp to specify the migration +yii migrate/mark "2015-01-01 18:54:01" # using a string that can be parsed by strtotime() +yii migrate/mark m150101_185401_create_news_table # using full name +yii migrate/mark 1392853618 # using UNIX timestamp ``` ======= This command will list all migrations that have not been applied so far. If you confirm that you want to apply @@ -634,40 +1222,93 @@ The migration command comes with a few command-line options that can be used to The following example shows how you can use these options. >>>>>>> yiichina/master +The command will modify the `migration` table by adding or deleting certain rows to indicate that the database +has been applied migrations to the specified one. No migrations will be applied or reverted by this command. + + +## Customizing Migrations + +There are several ways to customize the migration command. + + +### Using Command Line Options + +The migration command comes with a few command-line options that can be used to customize its behaviors: + +* `interactive`: boolean (defaults to true), specifies whether to perform migrations in an interactive mode. + When this is true, the user will be prompted before the command performs certain actions. + You may want to set this to false if the command is being used in a background process. + +* `migrationPath`: string (defaults to `@app/migrations`), specifies the directory storing all migration + class files. This can be specified as either a directory path or a path [alias](concept-aliases.md). + Note that the directory must exist, or the command may trigger an error. + +* `migrationTable`: string (defaults to `migration`), specifies the name of the database table for storing + migration history information. The table will be automatically created by the command if it does not exist. + You may also manually create it using the structure `version varchar(255) primary key, apply_time integer`. + +* `db`: string (defaults to `db`), specifies the ID of the database [application component](structure-application-components.md). + It represents the database that will be migrated using this command. + +* `templateFile`: string (defaults to `@yii/views/migration.php`), specifies the path of the template file + that is used for generating skeleton migration class files. This can be specified as either a file path + or a path [alias](concept-aliases.md). The template file is a PHP script in which you can use a predefined variable + named `$className` to get the migration class name. + +* `generatorTemplateFiles`: array (defaults to `[ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createJunctionMigration.php' + ]`), specifies template files for generating migration code. See "[Generating Migrations](#generating-migrations)" + for more details. + +* `fields`: array of column definition strings used for creating migration code. Defaults to `[]`. The format of each + definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. For example, `--fields=name:string(12):notNull` produces + a string column of size 12 which is not null. + +The following example shows how you can use these options. + For example, if we want to migrate a `forum` module whose migration files are located within the module's `migrations` directory, we can use the following command: ``` <<<<<<< HEAD +<<<<<<< HEAD yii migrate/up --migrationPath=@app/modules/forum/migrations +======= +# migrate the migrations in a forum module non-interactively +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 +>>>>>>> master ``` -### Configure Command Globally +### Configuring Command Globally -While command line options allow us to configure the migration command -on-the-fly, sometimes we may want to configure the command once for all. -For example, we may want to use a different table to store the migration history, -or we may want to use a customized migration template. We can do so by modifying -the console application's configuration file like the following, +Instead of entering the same option values every time you run the migration command, you may configure it +once for all in the application configuration like shown below: ```php -'controllerMap' => [ - 'migrate' => [ - 'class' => 'yii\console\controllers\MigrateController', - 'migrationTable' => 'my_custom_migrate_table', +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], ], -] +]; ``` -Now if we run the `migrate` command, the above configurations will take effect -without requiring us to enter the command line options every time. Other command options -can be also configured this way. +With the above configuration, each time you run the migration command, the `backend_migration` table +will be used to record the migration history. You no longer need to specify it via the `migrationTable` +command-line option. -### Migrating with Multiple Databases +## Migrating Multiple Databases +<<<<<<< HEAD By default, migrations will be applied to the database specified by the `db` [application component](structure-application-components.md). You may change it by specifying the `--db` option, for example, ======= @@ -702,36 +1343,52 @@ command-line option. By default, migrations are applied to the same database specified by the `db` [application component](structure-application-components.md). If you want them to be applied to a different database, you may specify the `db` command-line option like shown below, >>>>>>> yiichina/master +======= +By default, migrations are applied to the same database specified by the `db` [application component](structure-application-components.md). +If you want them to be applied to a different database, you may specify the `db` command-line option like shown below, +>>>>>>> master ``` yii migrate --db=db2 ``` +<<<<<<< HEAD <<<<<<< HEAD The above command will apply *all* migrations found in the default migration path to the `db2` database. +======= +The above command will apply migrations to the `db2` database. +>>>>>>> master -If your application works with multiple databases, it is possible that some migrations should be applied -to one database while some others should be applied to another database. In this case, it is recommended that -you create a base migration class for each different database and override the [[yii\db\Migration::init()]] -method like the following, +Sometimes it may happen that you want to apply *some* of the migrations to one database, while some others to another +database. To achieve this goal, when implementing a migration class you should explicitly specify the DB component +ID that the migration would use, like the following: ```php -public function init() +db = 'db2'; - parent::init(); + public function init() + { + $this->db = 'db2'; + parent::init(); + } } ``` -To create a migration that should be applied to a particular database, simply extend from the corresponding -base migration class. Now if you run the `yii migrate` command, each migration will be applied to its corresponding database. +The above migration will be applied to `db2`, even if you specify a different database through the `db` command-line +option. Note that the migration history will still be recorded in the database specified by the `db` command-line option. -> Info: Because each migration uses a hardcoded DB connection, the `--db` option of the `migrate` command will - have no effect. Also note that the migration history will be stored in the default `db` database. +If you have multiple migrations that use the same database, it is recommended that you create a base migration class +with the above `init()` code. Then each migration class can extend from this base class. -If you want to support changing the DB connection via the `--db` option, you may take the following alternative -approach to work with multiple databases. +> Tip: Besides setting the [[yii\db\Migration::db|db]] property, you can also operate on different databases + by creating new database connections to them in your migration classes. You then use the [DAO methods](db-dao.md) + with these connections to manipulate different databases. +<<<<<<< HEAD For each database, create a migration path and save all corresponding migration classes there. To apply migrations, run the command as follows, ======= @@ -768,6 +1425,10 @@ with the above `init()` code. Then each migration class can extend from this bas Another strategy that you can take to migrate multiple databases is to keep migrations for different databases in different migration paths. Then you can migrate these databases in separate commands like the following: >>>>>>> yiichina/master +======= +Another strategy that you can take to migrate multiple databases is to keep migrations for different databases in +different migration paths. Then you can migrate these databases in separate commands like the following: +>>>>>>> master ``` yii migrate --migrationPath=@app/migrations/db1 --db=db1 @@ -775,9 +1436,14 @@ yii migrate --migrationPath=@app/migrations/db2 --db=db2 ... ``` +<<<<<<< HEAD <<<<<<< HEAD > Info: The above approach stores the migration history in different databases specified via the `--db` option. ======= The first command will apply migrations in `@app/migrations/db1` to the `db1` database, the second command will apply migrations in `@app/migrations/db2` to `db2`, and so on. >>>>>>> yiichina/master +======= +The first command will apply migrations in `@app/migrations/db1` to the `db1` database, the second command +will apply migrations in `@app/migrations/db2` to `db2`, and so on. +>>>>>>> master diff --git a/docs/guide/db-mongodb.md b/docs/guide/db-mongodb.md deleted file mode 100644 index 12e18229a1..0000000000 --- a/docs/guide/db-mongodb.md +++ /dev/null @@ -1,6 +0,0 @@ -Mongo DB -======== - -> Note: This section is under development. -> -> It has no content yet. diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md index ceb916f6d9..964a404866 100644 --- a/docs/guide/db-query-builder.md +++ b/docs/guide/db-query-builder.md @@ -1,8 +1,8 @@ Query Builder ============= -Built on top of [Database Access Objects](db-dao.md), query builder allows you to construct a SQL statement -in a programmatic and DBMS-agnostic way. Compared to writing raw SQLs, using query builder will help you write +Built on top of [Database Access Objects](db-dao.md), query builder allows you to construct a SQL query +in a programmatic and DBMS-agnostic way. Compared to writing raw SQL statements, using query builder will help you write more readable SQL-related code and generate more secure SQL statements. Using query builder usually involves two steps: @@ -21,7 +21,7 @@ $rows = (new \yii\db\Query()) ->all(); ``` -The above code generates and executes the following SQL statement, where the `:last_name` parameter is bound with the +The above code generates and executes the following SQL query, where the `:last_name` parameter is bound with the string `'Smith'`. ```sql @@ -40,8 +40,8 @@ LIMIT 10 ## Building Queries To build a [[yii\db\Query]] object, you call different query building methods to specify different parts of -a SQL statement. The names of these methods resemble the SQL keywords used in the corresponding parts of the SQL -statement. For example, to specify the `FROM` part of a SQL statement, you would call the `from()` method. +a SQL query. The names of these methods resemble the SQL keywords used in the corresponding parts of the SQL +statement. For example, to specify the `FROM` part of a SQL query, you would call the [[yii\db\Query::from()|from()]] method. All the query building methods return the query object itself, which allows you to chain multiple calls together. In the following, we will describe the usage of each query building method. @@ -61,7 +61,7 @@ $query->select(['id', 'email']); $query->select('id, email'); ``` -The column names being selected may include table prefixes and/or column aliases, like you do when writing raw SQLs. +The column names being selected may include table prefixes and/or column aliases, like you do when writing raw SQL queries. For example, ```php @@ -87,12 +87,19 @@ that contains commas to avoid incorrect automatic name quoting. For example, ```php <<<<<<< HEAD +<<<<<<< HEAD $query->select(["CONCAT(first_name, ' ', last_name]) AS full_name", 'email']); ======= $query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); >>>>>>> yiichina/master +======= +$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); +>>>>>>> master ``` +As with all places where raw SQL is involved, you may use the [DBMS agnostic quoting syntax](db-dao.md#quoting-table-and-column-names) +for table and column names when writing DB expressions in select. + Starting from version 2.0.1, you may also select sub-queries. You should specify each sub-query in terms of a [[yii\db\Query]] object. For example, @@ -128,7 +135,7 @@ $query->from('user'); ``` You can specify the table(s) being selected from in either a string or an array. The table names may contain -schema prefixes and/or table aliases, like you do when writing raw SQLs. For example, +schema prefixes and/or table aliases, like you do when writing raw SQL statements. For example, ```php $query->from(['public.user u', 'public.post p']); @@ -157,7 +164,7 @@ $query->from(['u' => $subQuery]); ### [[yii\db\Query::where()|where()]] -The [[yii\db\Query::where()|where()]] method specifies the `WHERE` fragment of a SQL statement. You can use one of +The [[yii\db\Query::where()|where()]] method specifies the `WHERE` fragment of a SQL query. You can use one of the three formats to specify a `WHERE` condition: - string format, e.g., `'status=1'` @@ -167,13 +174,17 @@ the three formats to specify a `WHERE` condition: #### String Format -String format is best used to specify very simple conditions. It works as if you are writing a raw SQL. For example, +String format is best used to specify very simple conditions or if you need to use builtin functions of the DBMS. +It works as if you are writing a raw SQL. For example, ```php $query->where('status=1'); // or use parameter binding to bind dynamic parameter values $query->where('status=:status', [':status' => $status]); + +// raw SQL using MySQL YEAR() function on a date field +$query->where('YEAR(somedate) = 2015'); ``` Do NOT embed variables directly in the condition like the following, especially if the variable values come from @@ -192,6 +203,8 @@ $query->where('status=:status') ->addParams([':status' => $status]); ``` +As with all places where raw SQL is involved, you may use the [DBMS agnostic quoting syntax](db-dao.md#quoting-table-and-column-names) +for table and column names when writing conditions in string format. #### Hash Format @@ -219,6 +232,9 @@ $userQuery = (new Query())->select('id')->from('user'); $query->where(['id' => $userQuery]); ``` +Using the Hash Format, Yii internally uses parameter binding so in contrast to the [string format](#string-format), here +you do not have to add parameters manually. + #### Operator Format @@ -230,10 +246,14 @@ Operator format allows you to specify arbitrary conditions in a programmatic way where the operands can each be specified in string format, hash format or operator format recursively, while <<<<<<< HEAD +<<<<<<< HEAD the operator can be one of the followings: ======= the operator can be one of the following: >>>>>>> yiichina/master +======= +the operator can be one of the following: +>>>>>>> master - `and`: the operands should be concatenated together using `AND`. For example, `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, @@ -294,6 +314,9 @@ the operator can be one of the following: - `>`, `<=`, or any other valid DB operator that takes two operands: the first operand must be a column name while the second operand a value. For example, `['>', 'age', 10]` will generate `age>10`. +Using the Operator Format, Yii internally uses parameter binding so in contrast to the [string format](#string-format), here +you do not have to add parameters manually. + #### Appending Conditions @@ -312,16 +335,16 @@ if (!empty($search)) { } ``` -If `$search` is not empty, the following SQL statement will be generated: +If `$search` is not empty, the following WHERE condition will be generated: ```sql -... WHERE (`status` = 10) AND (`title` LIKE '%yii%') +WHERE (`status` = 10) AND (`title` LIKE '%yii%') ``` #### Filter Conditions -When building `WHERE` conditions based on input from end users, you usually want to ignore those empty input values. +When building `WHERE` conditions based on input from end users, you usually want to ignore those input values, that are empty. For example, in a search form that allows you to search by username and email, you would like to ignore the username/email condition if the user does not enter anything in the username/email input field. You can achieve this goal by using the [[yii\db\Query::filterWhere()|filterWhere()]] method: @@ -336,7 +359,7 @@ $query->filterWhere([ The only difference between [[yii\db\Query::filterWhere()|filterWhere()]] and [[yii\db\Query::where()|where()]] is that the former will ignore empty values provided in the condition in [hash format](#hash-format). So if `$email` -is empty while `$username` is not, the above code will result in the SQL `...WHERE username=:username`. +is empty while `$username` is not, the above code will result in the SQL condition `WHERE username=:username`. > Info: A value is considered empty if it is null, an empty array, an empty string or a string consisting of whitespaces only. @@ -344,11 +367,25 @@ Like [[yii\db\Query::andWhere()|andWhere()]] and [[yii\db\Query::orWhere()|orWhe [[yii\db\Query::andFilterWhere()|andFilterWhere()]] and [[yii\db\Query::orFilterWhere()|orFilterWhere()]] to append additional filter conditions to the existing one. +Additionally, there is [[yii\db\Query::andFilterCompare()]] that can intelligently determine operator based on what's +in the value: + +```php +$query->andFilterCompare('name', 'John Doe'); +$query->andFilterCompare('rating', '>9'); +$query->andFilterCompare('value', '<=100'); +``` + +You can also specify operator explicitly: + +```php +$query->andFilterCompare('name', 'Doe', 'like'); +``` ### [[yii\db\Query::orderBy()|orderBy()]] -The [[yii\db\Query::orderBy()|orderBy()]] method specifies the `ORDER BY` fragment of a SQL statement. For example, - +The [[yii\db\Query::orderBy()|orderBy()]] method specifies the `ORDER BY` fragment of a SQL query. For example, + ```php // ... ORDER BY `id` ASC, `name` DESC $query->orderBy([ @@ -361,7 +398,7 @@ In the above code, the array keys are column names while the array values are th The PHP constant `SORT_ASC` specifies ascending sort and `SORT_DESC` descending sort. If `ORDER BY` only involves simple column names, you can specify it using a string, just like you do when writing -raw SQLs. For example, +raw SQL statements. For example, ```php $query->orderBy('id ASC, name DESC'); @@ -380,7 +417,7 @@ $query->orderBy('id ASC') ### [[yii\db\Query::groupBy()|groupBy()]] -The [[yii\db\Query::groupBy()|groupBy()]] method specifies the `GROUP BY` fragment of a SQL statement. For example, +The [[yii\db\Query::groupBy()|groupBy()]] method specifies the `GROUP BY` fragment of a SQL query. For example, ```php // ... GROUP BY `id`, `status` @@ -388,14 +425,18 @@ $query->groupBy(['id', 'status']); ``` If `GROUP BY` only involves simple column names, you can specify it using a string, just like you do when writing -raw SQLs. For example, +raw SQL statements. For example, ```php <<<<<<< HEAD +<<<<<<< HEAD $query->groupBy('id, status']); ======= $query->groupBy('id, status'); >>>>>>> yiichina/master +======= +$query->groupBy('id, status'); +>>>>>>> master ``` > Note: You should use the array format if `GROUP BY` involves some DB expression. @@ -411,7 +452,7 @@ $query->groupBy(['id', 'status']) ### [[yii\db\Query::having()|having()]] -The [[yii\db\Query::having()|having()]] method specifies the `HAVING` fragment of a SQL statement. It takes +The [[yii\db\Query::having()|having()]] method specifies the `HAVING` fragment of a SQL query. It takes a condition which can be specified in the same way as that for [where()](#where). For example, ```php @@ -434,8 +475,8 @@ $query->having(['status' => 1]) ### [[yii\db\Query::limit()|limit()]] and [[yii\db\Query::offset()|offset()]] The [[yii\db\Query::limit()|limit()]] and [[yii\db\Query::offset()|offset()]] methods specify the `LIMIT` -and `OFFSET` fragments of a SQL statement. For example, - +and `OFFSET` fragments of a SQL query. For example, + ```php // ... LIMIT 10 OFFSET 20 $query->limit(10)->offset(20); @@ -449,19 +490,22 @@ If you specify an invalid limit or offset (e.g. a negative value), it will be ig ### [[yii\db\Query::join()|join()]] -The [[yii\db\Query::join()|join()]] method specifies the `JOIN` fragment of a SQL statement. For example, - +The [[yii\db\Query::join()|join()]] method specifies the `JOIN` fragment of a SQL query. For example, + ```php // ... LEFT JOIN `post` ON `post`.`user_id` = `user`.`id` $query->join('LEFT JOIN', 'post', 'post.user_id = user.id'); ``` The [[yii\db\Query::join()|join()]] method takes four parameters: - + - `$type`: join type, e.g., `'INNER JOIN'`, `'LEFT JOIN'`. - `$table`: the name of the table to be joined. - `$on`: optional, the join condition, i.e., the `ON` fragment. Please refer to [where()](#where) for details - about specifying a condition. + about specifying a condition. Note, that the array syntax does **not** work for specifying a column based + condition, e.g. `['user.id' => 'comment.userId']` will result in a condition where the user id must be equal + to the string `'comment.userId'`. You should use the string syntax instead and specify the condition as + `'user.id = comment.userId'`. - `$params`: optional, the parameters to be bound to the join condition. You can use the following shortcut methods to specify `INNER JOIN`, `LEFT JOIN` and `RIGHT JOIN`, respectively. @@ -491,7 +535,7 @@ In this case, you should put the sub-query in an array and use the array key to ### [[yii\db\Query::union()|union()]] -The [[yii\db\Query::union()|union()]] method specifies the `UNION` fragment of a SQL statement. For example, +The [[yii\db\Query::union()|union()]] method specifies the `UNION` fragment of a SQL query. For example, ```php $query1 = (new \yii\db\Query()) @@ -548,11 +592,16 @@ $row = (new \yii\db\Query()) All these query methods take an optional `$db` parameter representing the [[yii\db\Connection|DB connection]] that <<<<<<< HEAD +<<<<<<< HEAD should be used to perform a DB query. If you omit this parameter, the `db` application component will be used ======= should be used to perform a DB query. If you omit this parameter, the `db` [application component](structure-application-components.md) will be used >>>>>>> yiichina/master as the DB connection. Below is another example using the `count()` query method: +======= +should be used to perform a DB query. If you omit this parameter, the `db` [application component](structure-application-components.md) will be used +as the DB connection. Below is another example using the [[yii\db\Query::count()|count()]] query method: +>>>>>>> master ```php // executes SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name @@ -566,7 +615,7 @@ When you call a query method of [[yii\db\Query]], it actually does the following * Call [[yii\db\QueryBuilder]] to generate a SQL statement based on the current construct of [[yii\db\Query]]; * Create a [[yii\db\Command]] object with the generated SQL statement; -* Call a query method (e.g. `queryAll()`) of [[yii\db\Command]] to execute the SQL statement and retrieve the data. +* Call a query method (e.g. [[yii\db\Command::queryAll()|queryAll()]]) of [[yii\db\Command]] to execute the SQL statement and retrieve the data. Sometimes, you may want to examine or use the SQL statement built from a [[yii\db\Query]] object. You can achieve this goal with the following code: @@ -618,12 +667,18 @@ $query = (new \yii\db\Query()) The anonymous function takes a parameter `$row` which contains the current row data and should return a scalar value which will be used as the index value for the current row. +> Note: In contrast to query methods like [[yii\db\Query::groupBy()|groupBy()]] or [[yii\db\Query::orderBy()|orderBy()]] +> which are converted to SQL and are part of the query, this method works after the data has been fetched from the database. +> That means that only those column names can be used that have been part of SELECT in your query. +> Also if you selected a column with table prefix, e.g. `customer.id`, the result set will only contain `id` so you have to call +> `->indexBy('id')` without table prefix. + ### Batch Query When working with large amounts of data, methods such as [[yii\db\Query::all()]] are not suitable because they require loading all data into the memory. To keep the memory requirement low, Yii -provides the so-called batch query support. A batch query makes uses of the data cursor and fetches +provides the so-called batch query support. A batch query makes use of the data cursor and fetches data in batches. Batch query can be used like the following: @@ -667,5 +722,6 @@ foreach ($query->batch() as $users) { } foreach ($query->each() as $username => $user) { + // ... } ``` diff --git a/docs/guide/db-redis.md b/docs/guide/db-redis.md deleted file mode 100644 index 0232ff69c4..0000000000 --- a/docs/guide/db-redis.md +++ /dev/null @@ -1,6 +0,0 @@ -Redis -===== - -> Note: This section is under development. -> -> It has no content yet. diff --git a/docs/guide/db-sphinx.md b/docs/guide/db-sphinx.md deleted file mode 100644 index 87a2afc946..0000000000 --- a/docs/guide/db-sphinx.md +++ /dev/null @@ -1,6 +0,0 @@ -Sphinx Search -============= - -> Note: This section is under development. -> -> It has no content yet. diff --git a/docs/guide/glossary.md b/docs/guide/glossary.md index 60be5e4dd9..43623e3c1b 100644 --- a/docs/guide/glossary.md +++ b/docs/guide/glossary.md @@ -52,7 +52,7 @@ Module is a sub-application which contains MVC elements by itself, such as model ## namespace -Namespace refers to a [PHP language feature](http://php.net/manual/en/language.namespaces.php) which is actively used in Yii2. +Namespace refers to a [PHP language feature](http://php.net/manual/en/language.namespaces.php) which is actively used in Yii 2. # P diff --git a/docs/guide/helper-array.md b/docs/guide/helper-array.md index fd544c9e33..8a9697828e 100644 --- a/docs/guide/helper-array.md +++ b/docs/guide/helper-array.md @@ -1,11 +1,15 @@ ArrayHelper =========== +<<<<<<< HEAD <<<<<<< HEAD Additionally to [rich set of PHP array functions](http://php.net/manual/en/book.array.php) Yii array helper provides ======= Additionally to the [rich set of PHP array functions](http://php.net/manual/en/book.array.php), the Yii array helper provides >>>>>>> yiichina/master +======= +Additionally to the [rich set of PHP array functions](http://php.net/manual/en/book.array.php), the Yii array helper provides +>>>>>>> master extra static methods allowing you to deal with arrays more efficiently. @@ -113,30 +117,94 @@ $result = ArrayHelper::getColumn($array, function ($element) { ## Re-indexing Arrays -In order to index an array according to a specified key, the `index` method can be used. The input array should be -multidimensional or an array of objects. The key can be a key name of the sub-array, a property name of object, or -an anonymous function which returns the key value given an array element. +In order to index an array according to a specified key, the `index` method can be used. The input should be either +multidimensional array or an array of objects. The `$key` can be either a key name of the sub-array, a property name of +object, or an anonymous function that must return the value that will be used as a key. -If a key value is null, the corresponding array element will be discarded and not put in the result. For example, +The `$groups` attribute is an array of keys, that will be used to group the input array into one or more sub-arrays +based on keys specified. + +If the `$key` attribute or its value for the particular element is null and `$groups` is not defined, the array +element will be discarded. Otherwise, if `$groups` is specified, array element will be added to the result array +without any key. + +For example: ```php $array = [ - ['id' => '123', 'data' => 'abc'], - ['id' => '345', 'data' => 'def'], + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], ]; $result = ArrayHelper::index($array, 'id'); -// the result is: -// [ -// '123' => ['id' => '123', 'data' => 'abc'], -// '345' => ['id' => '345', 'data' => 'def'], -// ] +``` -// using anonymous function +The result will be an associative array, where the key is the value of `id` attribute + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // The second element of an original array is overwritten by the last element because of the same id +] +``` + +Anonymous function, passed as a `$key`, gives the same result. + +```php $result = ArrayHelper::index($array, function ($element) { return $element['id']; }); ``` +Passing `id` as a third argument will group `$array` by `id`: + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +The result will be a multidimensional array grouped by `id` on the first level and not indexed on the second level: + +```php +[ + '123' => [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ], + '345' => [ // all elements with this index are present in the result array + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ] +] +``` + +An anonymous function can be used in the grouping array as well: + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level and +indexed by `data` on the third level: + +```php +[ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ] + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + ] + ] +] +``` ## Building Maps @@ -268,7 +336,7 @@ models in order to serve data arrays via REST API or use it otherwise. The follo ```php $posts = Post::find()->limit(10)->all(); -$data = ArrayHelper::toArray($post, [ +$data = ArrayHelper::toArray($posts, [ 'app\models\Post' => [ 'id', 'title', @@ -305,3 +373,22 @@ The result of conversion above will be: It is possible to provide default way of converting object to array for a specific class by implementing [[yii\base\Arrayable|Arrayable]] interface in that class. + +## Testing against Arrays + +Often you need to check if an element is in an array or a set of elements is a subset of another. +While PHP offers `in_array()`, this does not support subsets or `\Traversable` objects. + +To aid these kinds of tests, [[yii\base\ArrayHelper]] provides [[yii\base\ArrayHelper::isIn()|isIn()]] +and [[yii\base\ArrayHelper::isSubset()|isSubset()]] with the same signature as [[in_array()]]. + +```php +// true +ArrayHelper::isIn('a', ['a']); +// true +ArrayHelper::isIn('a', new(ArrayObject['a'])); + +// true +ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) + +``` diff --git a/docs/guide/helper-html.md b/docs/guide/helper-html.md index 4838d3aa8f..5382755095 100644 --- a/docs/guide/helper-html.md +++ b/docs/guide/helper-html.md @@ -75,7 +75,57 @@ echo Html::tag('div', 'Pwede na', $options); //
Pwede na
``` -In order to do the same with styles for the `style` attribute: +You may specify multiple CSS classes using the array style as well: + +```php +$options = ['class' => ['btn', 'btn-default']]; + +echo Html::tag('div', 'Save', $options); +// renders '
Save
' +``` + +While adding or removing classes you may use the array format as well: + +```php +$options = ['class' => 'btn']; + +if ($type === 'success') { + Html::addCssClass($options, ['btn-success', 'btn-lg']); +} + +echo Html::tag('div', 'Save', $options); +// renders '
Save
' +``` + +`Html::addCssClass()` prevents duplicating classes, so you don't need to worry that the same class may appear twice: + +```php +$options = ['class' => 'btn btn-default']; + +Html::addCssClass($options, 'btn-default'); // class 'btn-default' is already present + +echo Html::tag('div', 'Save', $options); +// renders '
Save
' +``` + +If the CSS class option is specified via the array format, you may use a named key to mark the logical purpose of the class. +In this case, a class with the same key in the array format will be ignored in `Html::addCssClass()`: + +```php +$options = [ + 'class' => [ + 'btn', + 'theme' => 'btn-default', + ] +]; + +Html::addCssClass($options, ['theme' => 'btn-success']); // 'theme' key is already taken + +echo Html::tag('div', 'Save', $options); +// renders '
Save
' +``` + +CSS styles can be setup in similar way using `style` attribute: ```php $options = ['style' => ['width' => '100px', 'height' => '100px']]; diff --git a/docs/guide/helper-overview.md b/docs/guide/helper-overview.md index 56b31f85f7..ed6563c672 100644 --- a/docs/guide/helper-overview.md +++ b/docs/guide/helper-overview.md @@ -28,13 +28,13 @@ The following core helper classes are provided in the Yii releases: - [ArrayHelper](helper-array.md) - Console - FileHelper +- FormatConverter - [Html](helper-html.md) - HtmlPurifier -- Image +- Imagine (provided by yii2-imagine extension) - Inflector - Json - Markdown -- Security - StringHelper - [Url](helper-url.md) - VarDumper diff --git a/docs/guide/helper-url.md b/docs/guide/helper-url.md index 31de5898cf..f53a4b1878 100644 --- a/docs/guide/helper-url.md +++ b/docs/guide/helper-url.md @@ -8,10 +8,14 @@ Url helper provides a set of static methods for managing URLs. There are two methods you can use to get common URLs: home URL and base URL of the current request. In order to get <<<<<<< HEAD +<<<<<<< HEAD home URL use the following: ======= home URL, use the following: >>>>>>> yiichina/master +======= +home URL, use the following: +>>>>>>> master ```php $relativeHomeUrl = Url::home(); @@ -19,6 +23,7 @@ $absoluteHomeUrl = Url::home(true); $httpsAbsoluteHomeUrl = Url::home('https'); ``` +<<<<<<< HEAD <<<<<<< HEAD If no parameter is passed, URL generated is relative. You can either pass `true` to get absolute URL for the current schema or specify schema explicitly (`https`, `http`). @@ -30,6 +35,12 @@ schema or specify a schema explicitly (`https`, `http`). To get the base URL of the current request use the following: >>>>>>> yiichina/master +======= +If no parameter is passed, the generated URL is relative. You can either pass `true` to get an absolute URL for the current +schema or specify a schema explicitly (`https`, `http`). + +To get the base URL of the current request use the following: +>>>>>>> master ```php $relativeBaseUrl = Url::base(); @@ -42,11 +53,15 @@ The only parameter of the method works exactly the same as for `Url::home()`. ## Creating URLs +<<<<<<< HEAD <<<<<<< HEAD In order to create URL to a given route use `Url::toRoute()` method. The method uses [[\yii\web\UrlManager]] to create ======= In order to create a URL to a given route use the `Url::toRoute()` method. The method uses [[\yii\web\UrlManager]] to create >>>>>>> yiichina/master +======= +In order to create a URL to a given route use the `Url::toRoute()` method. The method uses [[\yii\web\UrlManager]] to create +>>>>>>> master a URL: ```php @@ -57,22 +72,26 @@ You may specify the route as a string, e.g., `site/index`. You may also use an a query parameters for the URL being created. The array format must be: ```php -// generates: /index.php?r=site/index¶m1=value1¶m2=value2 +// generates: /index.php?r=site%2Findex¶m1=value1¶m2=value2 ['site/index', 'param1' => 'value1', 'param2' => 'value2'] ``` If you want to create a URL with an anchor, you can use the array format with a `#` parameter. For example, ```php -// generates: /index.php?r=site/index¶m1=value1#name +// generates: /index.php?r=site%2Findex¶m1=value1#name ['site/index', 'param1' => 'value1', '#' => 'name'] ``` +<<<<<<< HEAD <<<<<<< HEAD A route may be either absolute or relative. An absolute route has a leading slash (e.g. `/site/index`), while a relative ======= A route may be either absolute or relative. An absolute route has a leading slash (e.g. `/site/index`) while a relative >>>>>>> yiichina/master +======= +A route may be either absolute or relative. An absolute route has a leading slash (e.g. `/site/index`) while a relative +>>>>>>> master route has none (e.g. `site/index` or `index`). A relative route will be converted into an absolute one by the following rules: - If the route is an empty string, the current [[\yii\web\Controller::route|route]] will be used; @@ -88,19 +107,19 @@ to the above rules. Below are some examples of using this method: ```php -// /index.php?r=site/index +// /index.php?r=site%2Findex echo Url::toRoute('site/index'); -// /index.php?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); -// /index.php?r=post/edit&id=100 assume the alias "@postEdit" is defined as "post/edit" +// /index.php?r=post%2Fedit&id=100 assume the alias "@postEdit" is defined as "post/edit" echo Url::toRoute(['@postEdit', 'id' => 100]); -// http://www.example.com/index.php?r=site/index +// http://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', true); -// https://www.example.com/index.php?r=site/index +// https://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', 'https'); ``` @@ -124,13 +143,13 @@ will be replaced with the specified one. Below are some usage examples: ```php -// /index.php?r=site/index +// /index.php?r=site%2Findex echo Url::to(['site/index']); -// /index.php?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); -// /index.php?r=post/edit&id=100 assume the alias "@postEdit" is defined as "post/edit" +// /index.php?r=post%2Fedit&id=100 assume the alias "@postEdit" is defined as "post/edit" echo Url::to(['@postEdit', 'id' => 100]); // the currently requested URL @@ -156,12 +175,12 @@ passing a `$params` parameter to the method. For example, ```php // assume $_GET = ['id' => 123, 'src' => 'google'], current route is "post/view" -// /index.php?r=post/view&id=123&src=google +// /index.php?r=post%2Fview&id=123&src=google echo Url::current(); -// /index.php?r=post/view&id=123 +// /index.php?r=post%2Fview&id=123 echo Url::current(['src' => null]); -// /index.php?r=post/view&id=100&src=google +// /index.php?r=post%2Fview&id=100&src=google echo Url::current(['id' => 100]); ``` diff --git a/docs/guide/images/advanced-app-configs.png b/docs/guide/images/advanced-app-configs.png deleted file mode 100644 index daa9a6f9e8..0000000000 Binary files a/docs/guide/images/advanced-app-configs.png and /dev/null differ diff --git a/docs/guide/images/start-country-list.png b/docs/guide/images/start-country-list.png index 6994da2103..375419414d 100644 Binary files a/docs/guide/images/start-country-list.png and b/docs/guide/images/start-country-list.png differ diff --git a/docs/guide/images/tutorial-console-help.png b/docs/guide/images/tutorial-console-help.png index 34812a6d90..15b8b66a03 100644 Binary files a/docs/guide/images/tutorial-console-help.png and b/docs/guide/images/tutorial-console-help.png differ diff --git a/docs/guide/input-file-upload.md b/docs/guide/input-file-upload.md index f3c819111f..fa755f4f81 100644 --- a/docs/guide/input-file-upload.md +++ b/docs/guide/input-file-upload.md @@ -1,15 +1,16 @@ Uploading Files =============== -Uploading files in Yii is done via a form model, its validation rules and some controller code. Let's review what's -required to handle uploads properly. +Uploading files in Yii is usually done with the help of [[yii\web\UploadedFile]] which encapsulates each uploaded +file as an `UploadedFile` object. Combined with [[yii\widgets\ActiveForm]] and [models](structure-models.md), +you can easily implement a secure file uploading mechanism. -Uploading single file ---------------------- +## Creating Models -First of all, you need to create a model that will handle file uploads. Create `models/UploadForm.php` with the following -content: +Like working with plain text inputs, to upload a single file you would create a model class and use an attribute +of the model to keep the uploaded file instance. You should also declare a validation rule to validate the file upload. +For example, ```php namespace app\models; @@ -17,34 +18,47 @@ namespace app\models; use yii\base\Model; use yii\web\UploadedFile; -/** - * UploadForm is the model behind the upload form. - */ class UploadForm extends Model { /** - * @var UploadedFile file attribute + * @var UploadedFile */ - public $file; + public $imageFile; - /** - * @return array the validation rules. - */ public function rules() { return [ - [['file'], 'file'], + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], ]; } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } } ``` -In the code above, we've created a model `UploadForm` with an attribute `file` that will become `` in -the HTML form. The attribute has the validation rule named `file` that uses [[yii\validators\FileValidator|FileValidator]]. +In the code above, the `imageFile` attribute is used to keep the uploaded file instance. It is associated with +a `file` validation rule which uses [[yii\validators\FileValidator]] to ensure a file with extension name `png` or `jpg` +is uploaded. The `upload()` method will perform the validation and save the uploaded file on the server. -### Form view +The `file` validator allows you to check file extensions, size, MIME type, etc. Please refer to +the [Core Validators](tutorial-core-validators.md#file) section for more details. -Next, create a view that will render the form: +> Tip: If you are uploading an image, you may consider using the `image` validator instead. The `image` validator is + implemented via [[yii\validators\ImageValidator]] which verifies if an attribute has received a valid image + that can be then either saved or processed using the [Imagine Extension](https://github.com/yiisoft/yii2-imagine). + + +## Rendering File Input + +Next, create a file input in a view: ```php ['enctype' => 'multipart/form-data']]) ?> -field($model, 'file')->fileInput() ?> + field($model, 'imageFile')->fileInput() ?> - + ``` -The `'enctype' => 'multipart/form-data'` is necessary because it allows file uploads. `fileInput()` represents a form -input field. +It is important to remember that you add the `enctype` option to the form so that the file can be properly uploaded. +The `fileInput()` call will render a `` tag which will allow users to select a file to upload. -### Controller +> Tip: since version 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] adds `enctype` option to the form + automatically when file input field is used. -Now create the controller that connects the form and the model together: +## Wiring Up + +Now in a controller action, write the code to wire up the model and the view to implement file uploading: ```php namespace app\controllers; @@ -82,10 +99,10 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { - $model->file = UploadedFile::getInstance($model, 'file'); - - if ($model->file && $model->validate()) { - $model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // file is uploaded successfully + return; } } @@ -94,54 +111,31 @@ class SiteController extends Controller } ``` -Instead of `model->load(...)`, we are using `UploadedFile::getInstance(...)`. [[\yii\web\UploadedFile|UploadedFile]] -does not run the model validation, rather it only provides information about the uploaded file. Therefore, you need to run the validation manually via `$model->validate()` to trigger the [[yii\validators\FileValidator|FileValidator]]. The validator expects that -the attribute is an uploaded file, as you see in the core framework code: +In the above code, when the form is submitted, the [[yii\web\UploadedFile::getInstance()]] method is called +to represent the uploaded file as an `UploadedFile` instance. We then rely on the model validation to make sure +the uploaded file is valid and save the file on the server. -```php -if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { - return [$this->uploadRequired, []]; -} -``` -If the validation is successful, then we're saving the file: - -```php -$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); -``` +## Uploading Multiple Files +<<<<<<< HEAD <<<<<<< HEAD If you're using the "basic" application template, then folder `uploads` should be created under `web`. ======= If you're using the "basic" project template, then folder `uploads` should be created under `web`. >>>>>>> yiichina/master +======= +You can also upload multiple files at once, with some adjustments to the code listed in the previous subsections. +>>>>>>> master -That's it. Load the page and try uploading. Uploads should end up in `basic/web/uploads`. - -Validation ----------- - -It's often required to adjust validation rules to accept certain files only or require uploading. Below we'll review -some common rule configurations. - -### Required - -If you need to make the file upload mandatory, use `skipOnEmpty` like the following: - -```php -public function rules() -{ - return [ - [['file'], 'file', 'skipOnEmpty' => false], - ]; -} -``` - -### MIME type - -It is wise to validate the type of file uploaded. FileValidator has the property `$extensions` for this purpose: +First you should adjust the model class by adding the `maxFiles` option in the `file` validation rule to limit +the maximum number of files allowed to upload. Setting `maxFiles` to `0` means there is no limit on the number of files +that can be uploaded simultaneously. The maximum number of files allowed to be uploaded simultaneously is also limited +with PHP directive [`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads), +which defaults to 20. The `upload()` method should also be updated to save the uploaded files one by one. ```php +<<<<<<< HEAD public function rules() { return [ @@ -180,54 +174,60 @@ received a valid image that can be then either saved or processed using the [Ima Uploading multiple files ------------------------ +======= +namespace app\models; +>>>>>>> master -If you need to upload multiple files at once, some adjustments are required. - -Model: +use yii\base\Model; +use yii\web\UploadedFile; -```php class UploadForm extends Model { /** - * @var UploadedFile|Null file attribute + * @var UploadedFile[] */ - public $file; + public $imageFiles; - /** - * @return array the validation rules. - */ public function rules() { return [ - [['file'], 'file', 'maxFiles' => 10], // <--- here! + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], ]; } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } } ``` -View: - +In the view file, you should add the `multiple` option to the `fileInput()` call so that the file upload field +can receive multiple files: + ```php ['enctype' => 'multipart/form-data']]); ?> -field($model, 'file[]')->fileInput(['multiple' => true]) ?> + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> - + ``` -The difference is the following line: - -```php -field($model, 'file[]')->fileInput(['multiple' => true]) ?> -``` - -Controller: +And finally in the controller action, you should call `UploadedFile::getInstances()` instead of +`UploadedFile::getInstance()` to assign an array of `UploadedFile` instances to `UploadForm::imageFiles`. ```php namespace app\controllers; @@ -244,12 +244,10 @@ class SiteController extends Controller $model = new UploadForm(); if (Yii::$app->request->isPost) { - $model->file = UploadedFile::getInstances($model, 'file'); - - if ($model->file && $model->validate()) { - foreach ($model->file as $file) { - $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); - } + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // file is uploaded successfully + return; } } @@ -257,7 +255,3 @@ class SiteController extends Controller } } ``` - -There are two differences from single file upload. First is that `UploadedFile::getInstances($model, 'file');` is used -instead of `UploadedFile::getInstance($model, 'file');`. The former returns instances for **all** uploaded files while -the latter gives you only a single instance. The second difference is that we're doing `foreach` and saving each file. diff --git a/docs/guide/input-forms.md b/docs/guide/input-forms.md index eb951cf381..9b8cc8def0 100644 --- a/docs/guide/input-forms.md +++ b/docs/guide/input-forms.md @@ -10,7 +10,7 @@ to validate its input on the server side (Check the [Validating Input](input-val When creating model-based forms, the first step is to define the model itself. The model can be either based upon an [Active Record](db-active-record.md) class, representing some data from the database, or a generic Model class (extending from [[yii\base\Model]]) to capture arbitrary input, for example a login form. -In the following example we show, how a generic Model is used for a login form: +In the following example, we show how a generic model can be used for a login form: ```php `, `` and other tags according to the [[yii\widgets\ActiveField::$template|template]] defined by the form field. <<<<<<< HEAD +<<<<<<< HEAD The name of the input field is determined automatically from the model's [[yii\base\Model::formName()|form name] and the attribute's name. ======= The name of the input field is determined automatically from the model's [[yii\base\Model::formName()|form name]] and the attribute's name. >>>>>>> yiichina/master +======= +The name of the input field is determined automatically from the model's [[yii\base\Model::formName()|form name]] and the attribute name. +>>>>>>> master For example, the name for the input field for the `username` attribute in the above example will be `LoginForm[username]`. This naming rule will result in an array of all attributes for the login form to be available in `$_POST['LoginForm']` on the server side. +> Tip: If you have only one model in a form and want to simplify the input names you may skip the array part by +> overriding the [[yii\base\Model::formName()|formName()]] method of the model to return an empty string. +> This can be useful for filter models used in the [GridView](output-data-widgets.md#grid-view) to create nicer URLs. + Specifying the attribute of the model can be done in more sophisticated ways. For example when an attribute may take an array value when uploading multiple files or selecting multiple items you may specify it by appending `[]` to the attribute name: @@ -94,29 +102,106 @@ echo $form->field($model, 'uploadFile[]')->fileInput(['multiple'=>'multiple']); echo $form->field($model, 'items[]')->checkboxList(['a' => 'Item A', 'b' => 'Item B', 'c' => 'Item C']); ``` +Be careful when naming form elements such as submit buttons. According to the [jQuery documentation](https://api.jquery.com/submit/) there +are some reserved names that can cause conflicts: + +> Forms and their child elements should not use input names or ids that conflict with properties of a form, +> such as `submit`, `length`, or `method`. Name conflicts can cause confusing failures. +> For a complete list of rules and to check your markup for these problems, see [DOMLint](http://kangax.github.io/domlint/). + Additional HTML tags can be added to the form using plain HTML or using the methods from the [[yii\helpers\Html|Html]]-helper class like it is done in the above example with [[yii\helpers\Html::submitButton()|Html::submitButton()]]. > Tip: If you are using Twitter Bootstrap CSS in your application you may want to use -> [[yii\bootstrap\ActiveForm]] instead of [[yii\widgets\ActiveForm]], which is an extension of the -> ActiveForm class that adds some additional styling that works well with the bootstrap CSS framework. +> [[yii\bootstrap\ActiveForm]] instead of [[yii\widgets\ActiveForm]]. The former extends from the latter and +> uses Bootstrap-specific styles when generating form input fields. -> Tip: in order to style required fields with asterisk you can use the following CSS: +> Tip: In order to style required fields with asterisks, you can use the following CSS: > > ```css -> div.required label:after { +> div.required label.control-label:after { > content: " *"; > color: red; > } > ``` +Creating Drop-down List +----------------------- + +We can use ActiveForm [dropDownList()](http://www.yiiframework.com/doc-2.0/yii-widgets-activefield.html#dropDownList()-detail) +method to create a drop-down list: + +```php +use app\models\ProductCategory; + +/* @var $this yii\web\View */ +/* @var $form yii\widgets\ActiveForm */ +/* @var $model app\models\Product */ + +echo $form->field($model, 'product_category')->dropdownList( + ProductCategory::find()->select(['category_name', 'id'])->indexBy('id')->column(), + ['prompt'=>'Select Category'] +); +``` + +The value of your model field will be automatically pre-selected. + +Working with Pjax +----------------------- + +The [[yii\widgets\Pjax|Pjax]] widget allows you to update a certain section of a +page instead of reloading the entire page. You can use it to update only the form +and replace its contents after the submission. + +You can configure [[yii\widgets\Pjax::$formSelector|$formSelector]] to specify +which form submission may trigger pjax. If not set, all forms with `data-pjax` +attribute within the enclosed content of Pjax will trigger pjax requests. + +```php +use yii\widgets\Pjax; +use yii\widgets\ActiveForm; + +Pjax::begin([ + // Pjax options +]); + $form = ActiveForm::begin([ + 'options' => ['data' => ['pjax' => true]], + // more ActiveForm options + ]); + + // ActiveForm content + + ActiveForm::end(); +Pjax::end(); +``` +> Tip: Be careful with the links inside the [[yii\widgets\Pjax|Pjax]] widget since +> the response will also be rendered inside the widget. To prevent this, use the +> `data-pjax="0"` HTML attribute. + +#### Values in Submit Buttons and File Upload + +There are known issues using `jQuery.serializeArray()` when dealing with +[[https://github.com/jquery/jquery/issues/2321|files]] and +[[https://github.com/jquery/jquery/issues/2321|submit button values]] which +won't be solved and are instead deprecated in favor of the `FormData` class +introduced in HTML5. + +That means the only official support for files and submit button values with +ajax or using the [[yii\widgets\Pjax|Pjax]] widget depends on the +[[https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility|browser support]] +for the `FormData` class. + +Further Reading +--------------- + The next section [Validating Input](input-validation.md) handles the validation of the submitted form data on the server side as well as ajax- and client side validation. To read about more complex usage of forms, you may want to check out the following sections: +<<<<<<< HEAD - [Collecting tabular input](input-tabular-input.md) for collecting data for multiple models of the same kind. - [Complex Forms with Multiple Models](input-multiple-models.md) for handling multiple different models in the same form. <<<<<<< HEAD @@ -124,3 +209,8 @@ To read about more complex usage of forms, you may want to check out the followi ======= - [Uploading Files](input-file-upload.md) on how to use forms for uploading files. >>>>>>> yiichina/master +======= +- [Collecting Tabular Input](input-tabular-input.md) for collecting data for multiple models of the same kind. +- [Getting Data for Multiple Models](input-multiple-models.md) for handling multiple different models in the same form. +- [Uploading Files](input-file-upload.md) on how to use forms for uploading files. +>>>>>>> master diff --git a/docs/guide/input-multiple-models.md b/docs/guide/input-multiple-models.md index fc69beae0c..4810c3374e 100644 --- a/docs/guide/input-multiple-models.md +++ b/docs/guide/input-multiple-models.md @@ -1,35 +1,80 @@ -Complex Forms with Multiple Models -================================== +Getting Data for Multiple Models +================================ -In complex user interfaces it can happen that a user has to fill in data in one form that -has to be saved in different tables in the database. The concept of Yii forms allows you to -build these forms with nearly no more complexity compared to single model forms. +When dealing with some complex data, it is possible that you may need to use multiple different models to collect +the user input. For example, assuming the user login information is stored in the `user` table while the user profile +information is stored in the `profile` table, you may want to collect the input data about a user through a `User` model +and a `Profile` model. With the Yii model and form support, you can solve this problem in a way that is not much +different from handling a single model. -Same as with one model you follow the following schema for validation on the server side: +In the following, we will show how you can create a form that would allow you to collect data for both `User` and `Profile` +models. -1. instantiate model classes -2. populate the models attributes with input data -3. validate all models -4. If validation passes for all models, save them -5. If validation fails or no data has been submitted, display the form by passing all model instances to the view +First, the controller action for collecting the user and profile data can be written as follows, -In the following we show an example for using multiple models in a form... TBD +```php +namespace app\controllers; -Multiple models example ---------------- +use Yii; +use yii\base\Model; +use yii\web\Controller; +use yii\web\NotFoundHttpException; +use app\models\User; +use app\models\Profile; -> Note: This section is under development. -> -> It has no content yet. +class UserController extends Controller +{ + public function actionUpdate($id) + { + $user = User::findOne($id); + $profile = Profile::findOne($id); + + if (!isset($user, $profile)) { + throw new NotFoundHttpException("The user was not found."); + } + + $user->scenario = 'update'; + $profile->scenario = 'update'; + + if ($user->load(Yii::$app->request->post()) && $profile->load(Yii::$app->request->post())) { + $isValid = $user->validate(); + $isValid = $profile->validate() && $isValid; + if ($isValid) { + $user->save(false); + $profile->save(false); + return $this->redirect(['user/view', 'id' => $id]); + } + } + + return $this->render('update', [ + 'user' => $user, + 'profile' => $profile, + ]); + } +} +``` -TBD +In the `update` action, we first load the `$user` and `$profile` models to be updated from the database. We then call +[[yii\base\Model::load()]] to populate these two models with the user input. If successful we will validate +the two models and save them. Otherwise we will render the `update` view which has the following content: -Dependend models ----------------- +```php + 'user-update-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($user, 'username') ?> -> Note: This section is under development. -> -> It has no content yet. + ...other input fields... + + field($profile, 'website') ?> -TBD + 'btn btn-primary']) ?> + +``` + +As you can see, in the `update` view you would render input fields using two models `$user` and `$profile`. diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index cee6bc8fd1..31c3efced8 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -10,6 +10,7 @@ succeeded or not. If not, you may get the error messages from the [[yii\base\Mod ```php <<<<<<< HEAD +<<<<<<< HEAD $model = new \app\models\ContactForm; // populate model attributes with user inputs @@ -22,6 +23,14 @@ $model->load(\Yii::$app->request->post()); // which is equivalent to the following: // $model->attributes = \Yii::$app->request->post('ContactForm'); >>>>>>> yiichina/master +======= +$model = new \app\models\ContactForm(); + +// populate model attributes with user inputs +$model->load(\Yii::$app->request->post()); +// which is equivalent to the following: +// $model->attributes = \Yii::$app->request->post('ContactForm'); +>>>>>>> master if ($model->validate()) { // all inputs are valid @@ -99,14 +108,39 @@ When the `validate()` method is called, it does the following steps to perform v 3. Use each active rule to validate each active attribute which is associated with the rule. The validation rules are evaluated in the order they are listed. <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> yiichina/master +======= + +>>>>>>> master According to the above validation steps, an attribute will be validated if and only if it is an active attribute declared in `scenarios()` and is associated with one or multiple active rules declared in `rules()`. +> Note: It is handy to give names to rules i.e. +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> You can use it in a child model: +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + ### Customizing Error Messages @@ -154,11 +188,9 @@ on the value of another attribute you can use the [[yii\validators\Validator::wh to define such conditions. For example, ```php -[ ['state', 'required', 'when' => function($model) { return $model->country == 'USA'; - }], -] + }] ``` The [[yii\validators\Validator::when|when]] property takes a PHP callable with the following signature: @@ -177,13 +209,11 @@ the [[yii\validators\Validator::whenClient|whenClient]] property which takes a s function whose return value determines whether to apply the rule or not. For example, ```php -[ ['state', 'required', 'when' => function ($model) { return $model->country == 'USA'; }, 'whenClient' => "function (attribute, value) { return $('#country').val() == 'USA'; - }"], -] + }"] ``` @@ -196,10 +226,10 @@ The following examples shows how to trim the spaces in the inputs and turn empty the [trim](tutorial-core-validators.md#trim) and [default](tutorial-core-validators.md#default) core validators: ```php -[ +return [ [['username', 'email'], 'trim'], [['username', 'email'], 'default'], -] +]; ``` You may also use the more general [filter](tutorial-core-validators.md#filter) validator to perform more complex @@ -215,28 +245,26 @@ When input data are submitted from HTML forms, you often need to assign some def if they are empty. You can do so by using the [default](tutorial-core-validators.md#default) validator. For example, ```php -[ +return [ // set "username" and "email" as null if they are empty [['username', 'email'], 'default'], // set "level" to be 1 if it is empty ['level', 'default', 'value' => 1], -] +]; ``` By default, an input is considered empty if its value is an empty string, an empty array or a null. -You may customize the default empty detection logic by configuring the the [[yii\validators\Validator::isEmpty]] property +You may customize the default empty detection logic by configuring the [[yii\validators\Validator::isEmpty]] property with a PHP callable. For example, ```php -[ ['agree', 'required', 'isEmpty' => function ($value) { return empty($value); - }], -] + }] ``` -> Note: Most validators do not handle empty inputs if their [[yii\base\Validator::skipOnEmpty]] property takes +> Note: Most validators do not handle empty inputs if their [[yii\validators\Validator::skipOnEmpty]] property takes the default value true. They will simply be skipped during validation if their associated attributes receive empty inputs. Among the [core validators](tutorial-core-validators.md), only the `captcha`, `default`, `filter`, `required`, and `trim` validators will handle empty inputs. @@ -384,7 +412,10 @@ class MyForm extends Model A standalone validator is a class extending [[yii\validators\Validator]] or its child class. You may implement its validation logic by overriding the [[yii\validators\Validator::validateAttribute()]] method. If an attribute fails the validation, call [[yii\base\Model::addError()]] to save the error message in the model, like you do -with [inline validators](#inline-validators). For example, +with [inline validators](#inline-validators). + + +For example the inline validator above could be moved into new [[components/validators/CountryValidator]] class. ```php namespace app\components; @@ -407,6 +438,32 @@ If you want your validator to support validating a value without a model, you sh instead of `validateAttribute()` and `validate()` because by default the latter two methods are implemented by calling `validateValue()`. +Below is an example of how you could use the above validator class within your model. + +```php +namespace app\models; + +use Yii; +use yii\base\Model; +use app\components\validators\CountryValidator; + +class EntryForm extends Model +{ + public $name; + public $email; + public $country; + + public function rules() + { + return [ + [['name', 'email'], 'required'], + ['country', CountryValidator::className()], + ['email', 'email'], + ]; + } +} +``` + ## Client-Side Validation @@ -481,11 +538,16 @@ If you want to turn off client-side validation completely, you may configure the [[yii\widgets\ActiveForm::enableClientValidation]] property to be false. You may also turn off client-side validation of individual input fields by configuring their [[yii\widgets\ActiveField::enableClientValidation]] <<<<<<< HEAD +<<<<<<< HEAD property to be false. ======= property to be false. When `enableClientValidation` is configured at both the input field level and the form level, the former will take precedence. >>>>>>> yiichina/master +======= +property to be false. When `enableClientValidation` is configured at both the input field level and the form level, +the former will take precedence. +>>>>>>> master ### Implementing Client-Side Validation @@ -530,7 +592,7 @@ class StatusValidator extends Validator $statuses = json_encode(Status::find()->select('id')->asArray()->column()); $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return << ] > ``` +> Tip: If you need to work with client validation manually i.e. dynamically add fields or do some custom UI logic, refer +> to [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) +> in Yii 2.0 Cookbook. + ### Deferred Validation If you need to perform asynchronous client-side validation, you can create [Deferred objects](http://api.jquery.com/category/deferred-object/). @@ -631,17 +697,40 @@ For example, to validate if a username is unique or not, it is necessary to chec You can use AJAX-based validation in this case. It will trigger an AJAX request in the background to validate the input while keeping the same user experience as the regular client-side validation. +<<<<<<< HEAD <<<<<<< HEAD To enable AJAX validation for the whole form, you have to set the [[yii\widgets\ActiveForm::enableAjaxValidation]] property to be `true` and specify `id` to be a unique form identifier: +======= +To enable AJAX validation for a single input field, configure the [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] +property of that field to be true and specify a unique form `id`: +>>>>>>> master ```php - 'contact-form', - 'enableAjaxValidation' => true, -]); ?> +use yii\widgets\ActiveForm; + +$form = ActiveForm::begin([ + 'id' => 'registration-form', +]); + +echo $form->field($model, 'username', ['enableAjaxValidation' => true]); + +// ... + +ActiveForm::end(); ``` +To enable AJAX validation for the whole form, configure [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] +to be true at the form level: + +```php +$form = ActiveForm::begin([ + 'id' => 'contact-form', + 'enableAjaxValidation' => true, +]); +``` + +<<<<<<< HEAD You may also turn AJAX validation on or off for individual input fields by configuring their [[yii\widgets\ActiveField::enableAjaxValidation]] property. ======= @@ -675,6 +764,10 @@ $form = ActiveForm::begin([ > Note: When the `enableAjaxValidation` property is configured at both the input field level and the form level, the former will take precedence. >>>>>>> yiichina/master +======= +> Note: When the `enableAjaxValidation` property is configured at both the input field level and the form level, + the former will take precedence. +>>>>>>> master You also need to prepare the server so that it can handle the AJAX validation requests. This can be achieved by a code snippet like the following in the controller actions: @@ -691,3 +784,6 @@ this request by running the validation and returning the errors in JSON format. > Info: You can also use [Deferred Validation](#deferred-validation) to perform AJAX validation. However, the AJAX validation feature described here is more systematic and requires less coding effort. + +When both `enableClientValidation` and `enableAjaxValidation` are set to true, AJAX validation request will be triggered +only after the successful client validation. diff --git a/docs/guide/intro-upgrade-from-v1.md b/docs/guide/intro-upgrade-from-v1.md index 9e2b6e15b5..4e3b04a182 100644 --- a/docs/guide/intro-upgrade-from-v1.md +++ b/docs/guide/intro-upgrade-from-v1.md @@ -108,11 +108,15 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` +<<<<<<< HEAD <<<<<<< HEAD More details about configurations can be found in the [Object Configurations](concept-configurations.md) section. ======= More details about configurations can be found in the [Configurations](concept-configurations.md) section. >>>>>>> yiichina/master +======= +More details about configurations can be found in the [Configurations](concept-configurations.md) section. +>>>>>>> master Events @@ -508,6 +512,7 @@ User and IdentityInterface The `CWebUser` class in 1.1 is now replaced by [[yii\web\User]], and there is no more `CUserIdentity` class. Instead, you should implement the [[yii\web\IdentityInterface]] which <<<<<<< HEAD +<<<<<<< HEAD is much more straightforward to use. The advanced application template provides such an example. Please refer to the [Authentication](security-authentication.md), [Authorization](security-authorization.md), and [Advanced Application Template](tutorial-advanced-app.md) sections for more details. @@ -516,6 +521,11 @@ is much more straightforward to use. The advanced project template provides such Please refer to the [Authentication](security-authentication.md), [Authorization](security-authorization.md), and [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) sections for more details. >>>>>>> yiichina/master +======= +is much more straightforward to use. The advanced project template provides such an example. + +Please refer to the [Authentication](security-authentication.md), [Authorization](security-authorization.md), and [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) sections for more details. +>>>>>>> master URL Management @@ -536,6 +546,12 @@ the same goal. Please refer to the [Url manager docs](runtime-routing.md) section for more details. +An important change in the naming convention for routes is that camel case names of controllers +and actions are now converted to lower case where each word is separated by a hypen, e.g. the controller +id for the `CamelCaseController` will be `camel-case`. +See the section about [controller IDs](structure-controllers.md#controller-ids) and [action IDs](structure-controllers.md#action-ids) for more details. + + Using Yii 1.1 and 2.x together ------------------------------ diff --git a/docs/guide/intro-yii.md b/docs/guide/intro-yii.md index 52c218c589..dad09234db 100644 --- a/docs/guide/intro-yii.md +++ b/docs/guide/intro-yii.md @@ -20,7 +20,7 @@ How does Yii Compare with Other Frameworks? If you're already familiar with another framework, you may appreciate knowing how Yii compares: -- Like most PHP frameworks, Yii implements the MVC (Model-View-Controller) design pattern and promotes code +- Like most PHP frameworks, Yii implements the MVC (Model-View-Controller) architectural pattern and promotes code organization based on that pattern. - Yii takes the philosophy that code should be written in a simple yet elegant way. Yii will never try to over-design things mainly for the purpose of strictly following some design pattern. @@ -31,21 +31,29 @@ If you're already familiar with another framework, you may appreciate knowing ho take advantage of Yii's solid extension architecture to use or develop redistributable extensions. - High performance is always a primary goal of Yii. +<<<<<<< HEAD <<<<<<< HEAD Yii is not a one-man show, it is backed up by a [strong core developer team][], as well as a large community ======= Yii is not a one-man show, it is backed up by a [strong core developer team][about_yii], as well as a large community >>>>>>> yiichina/master +======= +Yii is not a one-man show, it is backed up by a [strong core developer team][about_yii], as well as a large community +>>>>>>> master of professionals constantly contributing to Yii's development. The Yii developer team keeps a close eye on the latest Web development trends and on the best practices and features found in other frameworks and projects. The most relevant best practices and features found elsewhere are regularly incorporated into the core framework and exposed via simple and elegant interfaces. +<<<<<<< HEAD <<<<<<< HEAD [strong core developer team]: http://www.yiiframework.com/about/ ======= [about_yii]: http://www.yiiframework.com/about/ >>>>>>> yiichina/master +======= +[about_yii]: http://www.yiiframework.com/about/ +>>>>>>> master Yii Versions ------------ diff --git a/docs/guide/output-data-providers.md b/docs/guide/output-data-providers.md index 45567151cf..35f1ecf4b1 100644 --- a/docs/guide/output-data-providers.md +++ b/docs/guide/output-data-providers.md @@ -1,159 +1,257 @@ <<<<<<< HEAD +<<<<<<< HEAD Data providers +======= +Data Providers +>>>>>>> master ============== -> Note: This section is under development. +In the [Pagination](output-pagination.md) and [Sorting](output-sorting.md) sections, we have described how to +allow end users to choose a particular page of data to display and sort them by some columns. Because the task +of paginating and sorting data is very common, Yii provides a set of *data provider* classes to encapsulate it. -Data provider abstracts data set via [[yii\data\DataProviderInterface]] and handles pagination and sorting. -It can be used by [grids, lists and other data widgets](output-data-widgets.md). +A data provider is a class implementing [[yii\data\DataProviderInterface]]. It mainly supports retrieving paginated +and sorted data. It is usually used to work with [data widgets](output-data-widgets.md) so that end users can +interactively paginate and sort data. -In Yii there are three built-in data providers: [[yii\data\ActiveDataProvider]], [[yii\data\ArrayDataProvider]] and -[[yii\data\SqlDataProvider]]. +The following data provider classes are included in the Yii releases: -Active data provider --------------------- +* [[yii\data\ActiveDataProvider]]: uses [[yii\db\Query]] or [[yii\db\ActiveQuery]] to query data from databases + and return them in terms of arrays or [Active Record](db-active-record.md) instances. +* [[yii\data\SqlDataProvider]]: executes a SQL statement and returns database data as arrays. +* [[yii\data\ArrayDataProvider]]: takes a big array and returns a slice of it based on the paginating and sorting + specifications. -`ActiveDataProvider` provides data by performing DB queries using [[yii\db\Query]] and [[yii\db\ActiveQuery]]. - -The following is an example of using it to provide ActiveRecord instances: +The usage of all these data providers share the following common pattern: ```php -$provider = new ActiveDataProvider([ - 'query' => Post::find(), - 'pagination' => [ - 'pageSize' => 20, - ], +// create the data provider by configuring its pagination and sort properties +$provider = new XyzDataProvider([ + 'pagination' => [...], + 'sort' => [...], ]); -// get the posts in the current page -$posts = $provider->getModels(); +// retrieves paginated and sorted data +$models = $provider->getModels(); + +// get the number of data items in the current page +$count = $provider->getCount(); + +// get the total number of data items across all pages +$totalCount = $provider->getTotalCount(); ``` -And the following example shows how to use ActiveDataProvider without ActiveRecord: +You specify the pagination and sorting behaviors of a data provider by configuring its +[[yii\data\BaseDataProvider::pagination|pagination]] and [[yii\data\BaseDataProvider::sort|sort]] properties +which correspond to the configurations for [[yii\data\Pagination]] and [[yii\data\Sort]], respectively. +You may also configure them to be false to disable pagination and/or sorting features. + +[Data widgets](output-data-widgets.md), such as [[yii\grid\GridView]], have a property named `dataProvider` which +can take a data provider instance and display the data it provides. For example, ```php -$query = new Query(); -$provider = new ActiveDataProvider([ - 'query' => $query->from('post'), - 'sort' => [ - // Set the default sort by name ASC and created_at DESC. - 'defaultOrder' => [ - 'name' => SORT_ASC, - 'created_at' => SORT_DESC - ] - ], - 'pagination' => [ - 'pageSize' => 20, - ], +echo yii\grid\GridView::widget([ + 'dataProvider' => $dataProvider, ]); - -// get the posts in the current page -$posts = $provider->getModels(); ``` -Array data provider -------------------- +These data providers mainly vary in the way how the data source is specified. In the following subsections, +we will explain the detailed usage of each of these data providers. -ArrayDataProvider implements a data provider based on a data array. -The [[yii\data\ArrayDataProvider::$allModels]] property contains all data models that may be sorted and/or paginated. -ArrayDataProvider will provide the data after sorting and/or pagination. -You may configure the [[yii\data\ArrayDataProvider::$sort]] and [[yii\data\ArrayDataProvider::$pagination]] properties to -customize the sorting and pagination behaviors. +## Active Data Provider -Elements in the [[yii\data\ArrayDataProvider::$allModels]] array may be either objects (e.g. model objects) -or associative arrays (e.g. query results of DAO). -Make sure to set the [[yii\data\ArrayDataProvider::$key]] property to the name of the field that uniquely -identifies a data record or false if you do not have such a field. - -Compared to `ActiveDataProvider`, `ArrayDataProvider` could be less efficient -because it needs to have [[yii\data\ArrayDataProvider::$allModels]] ready. - -ArrayDataProvider may be used in the following way: +To use [[yii\data\ActiveDataProvider]], you should configure its [[yii\data\ActiveDataProvider::query|query]] property. +It can take either a [[yii\db\Query]] or [[yii\db\ActiveQuery]] object. If the former, the data returned will be arrays; +if the latter, the data returned can be either arrays or [Active Record](db-active-record.md) instances. +For example, ```php -$query = new Query(); -$provider = new ArrayDataProvider([ - 'allModels' => $query->from('post')->all(), - 'sort' => [ - 'attributes' => ['id', 'username', 'email'], - ], +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['status' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, 'pagination' => [ 'pageSize' => 10, ], + 'sort' => [ + 'defaultOrder' => [ + 'created_at' => SORT_DESC, + 'title' => SORT_ASC, + ] + ], ]); -// get the posts in the current page + +// returns an array of Post objects $posts = $provider->getModels(); ``` -> Note: if you want to use the sorting feature, you must configure the [[sort]] property -so that the provider knows which columns can be sorted. - -SQL data provider ------------------ - -SqlDataProvider implements a data provider based on a plain SQL statement. It provides data in terms of arrays, each -representing a row of query result. - -Like other data providers, SqlDataProvider also supports sorting and pagination. It does so by modifying the given -[[yii\data\SqlDataProvider::$sql]] statement with "ORDER BY" and "LIMIT" clauses. You may configure the -[[yii\data\SqlDataProvider::$sort]] and [[yii\data\SqlDataProvider::$pagination]] properties to customize sorting -and pagination behaviors. - -`SqlDataProvider` may be used in the following way: +If `$query` in the above example is created using the following code, then the data provider will return raw arrays. ```php +use yii\db\Query; + +$query = (new Query())->from('post')->where(['status' => 1]); +``` + +> Note: If a query already specifies the `orderBy` clause, the new ordering instructions given by end users + (through the `sort` configuration) will be appended to the existing `orderBy` clause. Any existing `limit` + and `offset` clauses will be overwritten by the pagination request from end users (through the `pagination` configuration). + +By default, [[yii\data\ActiveDataProvider]] uses the `db` application component as the database connection. You may +use a different database connection by configuring the [[yii\data\ActiveDataProvider::db]] property. + + +## SQL Data Provider + +[[yii\data\SqlDataProvider]] works with a raw SQL statement which is used to fetch the needed +data. Based on the specifications of [[yii\data\SqlDataProvider::sort|sort]] and +[[yii\data\SqlDataProvider::pagination|pagination]], the provider will adjust the `ORDER BY` and `LIMIT` +clauses of the SQL statement accordingly to fetch only the requested page of data in the desired order. + +To use [[yii\data\SqlDataProvider]], you should specify the [[yii\data\SqlDataProvider::sql|sql]] property as well +as the [[yii\data\SqlDataProvider::totalCount|totalCount]] property. For example, + +```php +use yii\data\SqlDataProvider; + $count = Yii::$app->db->createCommand(' - SELECT COUNT(*) FROM user WHERE status=:status + SELECT COUNT(*) FROM post WHERE status=:status ', [':status' => 1])->queryScalar(); -$dataProvider = new SqlDataProvider([ - 'sql' => 'SELECT * FROM user WHERE status=:status', +$provider = new SqlDataProvider([ + 'sql' => 'SELECT * FROM post WHERE status=:status', 'params' => [':status' => 1], 'totalCount' => $count, + 'pagination' => [ + 'pageSize' => 10, + ], 'sort' => [ 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - 'default' => SORT_DESC, - 'label' => 'Name', - ], + 'title', + 'view_count', + 'created_at', ], ], - 'pagination' => [ - 'pageSize' => 20, - ], ]); -// get the user records in the current page -$models = $dataProvider->getModels(); +// returns an array of data rows +$models = $provider->getModels(); ``` -> Note: if you want to use the pagination feature, you must configure the [[yii\data\SqlDataProvider::$totalCount]] -property to be the total number of rows (without pagination). And if you want to use the sorting feature, -you must configure the [[yii\data\SqlDataProvider::$sort]] property so that the provider knows which columns can -be sorted. +> Info: The [[yii\data\SqlDataProvider::totalCount|totalCount]] property is required only if you need to + paginate the data. This is because the SQL statement specified via [[yii\data\SqlDataProvider::sql|sql]] + will be modified by the provider to return only the currently requested page of data. The provider still + needs to know the total number of data items in order to correctly calculate the number of pages available. -Implementing your own custom data provider ------------------------------------------- +## Array Data Provider -Yii allows you to introduce your own custom data providers. In order to do it you need to implement the following -`protected` methods: +[[yii\data\ArrayDataProvider]] is best used when working with a big array. The provider allows you to return +a page of the array data sorted by one or multiple columns. To use [[yii\data\ArrayDataProvider]], you should +specify the [[yii\data\ArrayDataProvider::allModels|allModels]] property as the big array. +Elements in the big array can be either associative arrays +(e.g. query results of [DAO](db-dao.md)) or objects (e.g. [Active Record](db-active-record.md) instances). +For example, + +```php +use yii\data\ArrayDataProvider; + +$data = [ + ['id' => 1, 'name' => 'name 1', ...], + ['id' => 2, 'name' => 'name 2', ...], + ... + ['id' => 100, 'name' => 'name 100', ...], +]; + +$provider = new ArrayDataProvider([ + 'allModels' => $data, + 'pagination' => [ + 'pageSize' => 10, + ], + 'sort' => [ + 'attributes' => ['id', 'name'], + ], +]); + +// get the rows in the currently requested page +$rows = $provider->getModels(); +``` + +> Note: Compared to [Active Data Provider](#active-data-provider) and [SQL Data Provider](#sql-data-provider), + array data provider is less efficient because it requires loading *all* data into the memory. + + +## Working with Data Keys + +When using the data items returned by a data provider, you often need to identify each data item with a unique key. +For example, if the data items represent customer information, you may want to use the customer ID as the key +for each customer data. Data providers can return a list of such keys corresponding with the data items returned +by [[yii\data\DataProviderInterface::getModels()]]. For example, + +```php +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['status' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, +]); + +// returns an array of Post objects +$posts = $provider->getModels(); + +// returns the primary key values corresponding to $posts +$ids = $provider->getKeys(); +``` + +In the above example, because you provide to [[yii\data\ActiveDataProvider]] an [[yii\db\ActiveQuery]] object, +it is intelligent enough to return primary key values as the keys. You may also explicitly specify how the key +values should be calculated by configuring [[yii\data\ActiveDataProvider::key]] with a column name or +a callable calculating key values. For example, + +```php +// use "slug" column as key values +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => 'slug', +]); + +// use the result of md5(id) as key values +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => function ($model) { + return md5($model->id); + } +]); +``` + + +## Creating Custom Data Provider + +To create your own custom data provider classes, you should implement [[yii\data\DataProviderInterface]]. +An easier way is to extend from [[yii\data\BaseDataProvider]] which allows you to focus on the core data provider +logic. In particular, you mainly need to implement the following methods: -- `prepareModels` that prepares the data models that will be made available in the current page and returns them as an array. -- `prepareKeys` that accepts an array of currently available data models and returns keys associated with them. -- `prepareTotalCount` that returns a value indicating the total number of data models in the data provider. +- [[yii\data\BaseDataProvider::prepareModels()|prepareModels()]]: prepares the data models that will be made + available in the current page and returns them as an array. +- [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: accepts an array of currently available data models + and returns keys associated with them. +- [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: returns a value indicating the total number + of data models in the data provider. -Below is an example of a data provider that reads CSV efficiently: +Below is an example of a data provider that reads CSV data efficiently: ```php >>>>>> yiichina/master +======= + * @var string name of the CSV file to read +>>>>>>> master */ public $filename; @@ -499,6 +600,9 @@ class CsvDataProvider extends BaseDataProvider } ``` <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index cc109513dd..fd4a80d595 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -17,13 +17,17 @@ The model can be either an instance or subclass of [[\yii\base\Model]] such as a DetailView uses the [[yii\widgets\DetailView::$attributes|$attributes]] property to determine which model attributes should be displayed and how they <<<<<<< HEAD +<<<<<<< HEAD should be formatted. See the [formatter section](output-formatter.md) for available formatting options. ======= should be formatted. See the [formatter section](output-formatting.md) for available formatting options. >>>>>>> yiichina/master +======= +should be formatted. See the [formatter section](output-formatting.md) for available formatting options. +>>>>>>> master A typical usage of DetailView is as follows: - + ```php echo DetailView::widget([ 'model' => $model, @@ -75,7 +79,7 @@ use yii\helpers\HtmlPurifier; ?>

title) ?>

- + text) ?>
``` @@ -107,9 +111,9 @@ These are then also available as variables in the view. GridView -------- -Data grid or GridView is one of the most powerful Yii widgets. It is extremely useful if you need to quickly build the admin +Data grid or [[yii\grid\GridView|GridView]] is one of the most powerful Yii widgets. It is extremely useful if you need to quickly build the admin section of the system. It takes data from a [data provider](output-data-providers.md) and renders each row using a set of [[yii\grid\GridView::columns|columns]] -presenting data in the form of a table. +presenting data in the form of a table. Each row of the table represents the data of a single data item, and a column usually represents an attribute of the item (some columns may correspond to complex expressions of attributes or static text). @@ -224,20 +228,24 @@ echo GridView::widget([ 'format' => ['date', 'php:Y-m-d'] ], ], -]); +]); ``` In the above, `text` corresponds to [[\yii\i18n\Formatter::asText()]]. The value of the column is passed as the first argument. In the second column definition, `date` corresponds to [[\yii\i18n\Formatter::asDate()]]. The value of the column is, again, passed as the first argument while 'php:Y-m-d' is used as the second argument value. +<<<<<<< HEAD <<<<<<< HEAD For a list of available formatters see the [section about Data Formatting](output-formatter.md). ======= For a list of available formatters see the [section about Data Formatting](output-formatting.md). >>>>>>> yiichina/master +======= +For a list of available formatters see the [section about Data Formatting](output-formatting.md). +>>>>>>> master -For configuring data columns there is also a shortcut format which is described in the +For configuring data columns there is also a shortcut format which is described in the API documentation for [[yii\grid\GridView::columns|columns]]. @@ -279,7 +287,24 @@ Available properties you can configure are: - [[yii\grid\ActionColumn::urlCreator|urlCreator]] is a callback that creates a button URL using the specified model information. The signature of the callback should be the same as that of [[yii\grid\ActionColumn::createUrl()]]. If this property is not set, button URLs will be created using [[yii\grid\ActionColumn::createUrl()]]. +- [[yii\grid\ActionColumn::visibleButtons|visibleButtons]] is an array of visibility conditions for each button. + The array keys are the button names (without curly brackets), and the values are the boolean true/false or the + anonymous function. When the button name is not specified in this array it will be shown by default. + The callbacks must use the following signature: + ```php + function ($model, $key, $index) { + return $model->status === 'editable'; + } + ``` + + Or you can pass a boolean value: + + ```php + [ + 'update' => \Yii::$app->user->can('update') + ] + ``` #### Checkbox column @@ -309,7 +334,7 @@ var keys = $('#grid').yiiGridView('getSelectedRows'); #### Serial column -Serial column renders row numbers starting with `1` and going forward. +[[yii\grid\SerialColumn|Serial column]] renders row numbers starting with `1` and going forward. Usage is as simple as the following: @@ -330,13 +355,14 @@ echo GridView::widget([ ### Filtering data -For filtering data the GridView needs a [model](structure-models.md) that takes the input from, the filtering -form and adjusts the query of the dataProvider to respect the search criteria. +For filtering data, the GridView needs a [model](structure-models.md) that represents the search criteria which is +usually taken from the filter fields in the GridView table. A common practice when using [active records](db-active-record.md) is to create a search Model class -that provides needed functionality (it can be generated for you by Gii). This class defines the validation -rules for the search and provides a `search()` method that will return the data provider. +that provides needed functionality (it can be generated for you by [Gii](start-gii.md)). This class defines the validation +rules for the search and provides a `search()` method that will return the data provider with an +adjusted query that respects the search criteria. -To add the search capability for the `Post` model, we can create `PostSearch` like the following example: +To add the search capability for the `Post` model, we can create a `PostSearch` model like the following example: ```php $query, ]); - // load the seach form data and validate + // load the search form data and validate if (!($this->load($params) && $this->validate())) { return $dataProvider; } @@ -385,9 +411,11 @@ class PostSearch extends Post return $dataProvider; } } - ``` +> Tip: See [Query Builder](db-query-builder.md) and especially [Filter Conditions](db-query-builder.md#filter-conditions) +> to learn how to build filtering query. + You can use this function in the controller to get the dataProvider for the GridView: ```php @@ -412,6 +440,83 @@ echo GridView::widget([ ]); ``` +### Separate filter form + +Most of the time using GridView header filters is enough, but in case you need a separate filter form, +you can easily add it as well. You can create partial view `_search.php` with the following contents: + +```php + + +
+ ['index'], + 'method' => 'get', + ]); ?> + + field($model, 'title') ?> + + field($model, 'creation_date') ?> + +
+ 'btn btn-primary']) ?> + 'btn btn-default']) ?> +
+ + +
+``` + +and include it in `index.php` view like so: + +```php +render('_search', ['model' => $searchModel]) ?> +``` + +> Note: if you use Gii to generate CRUD code, the separate filter form (`_search.php`) is generated by default, +but is commented in `index.php` view. Uncomment it and it's ready to use! + +Separate filter form is useful when you need to filter by fields, that are not displayed in GridView +or for special filtering conditions, like date range. For filtering by date range we can add non DB attributes +`createdFrom` and `createdTo` to the search model: + +```php +class PostSearch extends Post +{ + /** + * @var string + */ + public $createdFrom; + + /** + * @var string + */ + public $createdTo; +} +``` + +Extend query conditions in the `search()` method like so: + +```php +$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom]) + ->andFilterWhere(['<=', 'creation_date', $this->createdTo]); +``` + +And add the representative fields to the filter form: + +```php +field($model, 'creationFrom') ?> + +field($model, 'creationTo') ?> +``` ### Working with model relations @@ -434,6 +539,7 @@ $dataProvider = new ActiveDataProvider([ // join with relation `author` that is a relation to the table `users` // and set the table alias to be `author` $query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]); +// since version 2.0.7, the above line can be simplified to $query->joinWith('author AS author'); // enable sorting for the related column $dataProvider->sort->attributes['author.name'] = [ 'asc' => ['author.name' => SORT_ASC], @@ -475,8 +581,9 @@ $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name' > For example, if you use the alias `au` for the author relation table, the joinWith statement looks like the following: > > ```php -> $query->joinWith(['author' => function($query) { $query->from(['au' => 'users']); }]); +> $query->joinWith(['author au']); > ``` +> > It is also possible to just call `$query->joinWith(['author']);` when the alias is defined in the relation definition. > > The alias has to be used in the filter condition but the attribute name stays the same: @@ -506,7 +613,7 @@ $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name' #### Using SQL views for filtering, sorting and displaying data -There is also another approach that can be faster and more useful - SQL views. For example, if we need to show the gridview +There is also another approach that can be faster and more useful - SQL views. For example, if we need to show the gridview with users and their profiles, we can do so in this way: ```sql @@ -608,6 +715,44 @@ echo GridView::widget([ ### Using GridView with Pjax -> Note: This section is under development. +The [[yii\widgets\Pjax|Pjax]] widget allows you to update a certain section of a +page instead of reloading the entire page. You can use it to to update only the +[[yii\grid\GridView|GridView]] content when using filters. -TBD +```php +use yii\widgets\Pjax; +use yii\grid\GridView; + +Pjax::begin([ + // PJax options +]); + Gridview::widget([ + // GridView options + ]); +Pjax::end(); +``` + +Pjax also works for the links inside the [[yii\widgets\Pjax|Pjax]] widget and +for the links specified by [[yii\widgets\Pjax::$linkSelector|Pjax::$linkSelector]]. +But this might be a problem for the links of an [[yii\grid\ActionColumn|ActionColumn]]. +To prevent this, add the HTML attribute `data-pjax="0"` to the links when you edit +the [[yii\grid\ActionColumn::$buttons|ActionColumn::$buttons]] property. + +#### GridView/ListView with Pjax in Gii + +Since 2.0.5, the CRUD generator of [Gii](start-gii.md) has an option called +`$enablePjax` that can be used via either web interface or command line. + +```php +yii gii/crud --controllerClass="backend\\controllers\PostController" \ + --modelClass="common\\models\\Post" \ + --enablePjax=1 +``` + +Which generates a [[yii\widgets\Pjax|Pjax]] widget wrapping the +[[yii\grid\GridView|GridView]] or [[yii\widgets\ListView|ListView]] widgets. + +Further reading +--------------- + +- [Rendering Data in Yii 2 with GridView and ListView](http://www.sitepoint.com/rendering-data-in-yii-2-with-gridview-and-listview/) by Arno Slatius. diff --git a/docs/guide/output-formatter.md b/docs/guide/output-formatter.md deleted file mode 100644 index fd4af44ab5..0000000000 --- a/docs/guide/output-formatter.md +++ /dev/null @@ -1,200 +0,0 @@ -Data Formatter -============== - -For formatting of outputs Yii provides a formatter class to make data more readable for users. -[[yii\i18n\Formatter]] is a helper class that is registered as an [application component](structure-application-components.md) named `formatter` by default. - -It provides a set of methods for data formatting purpose such as date/time values, numbers and other commonly used formats in a localized way. -The formatter can be used in two different ways. - -1. Using the formatting methods (all formatter methods prefixed with `as`) directly: - - ```php - echo Yii::$app->formatter->asDate('2014-01-01', 'long'); // output: January 1, 2014 - echo Yii::$app->formatter->asPercent(0.125, 2); // output: 12.50% - echo Yii::$app->formatter->asEmail('cebe@example.com'); // output: cebe@example.com - echo Yii::$app->formatter->asBoolean(true); // output: Yes - // it also handles display of null values: - echo Yii::$app->formatter->asDate(null); // output: (Not set) - ``` - -2. Using the [[yii\i18n\Formatter::format()|format()]] method and the format name. - This method is also used by widgets like [[yii\grid\GridView]] and [[yii\widgets\DetailView]] where you can specify - the data format of a column in the widget configuration. - - ```php - echo Yii::$app->formatter->format('2014-01-01', 'date'); // output: January 1, 2014 - // you can also use an array to specify parameters for the format method: - // `2` is the value for the $decimals parameter of the asPercent()-method. - echo Yii::$app->formatter->format(0.125, ['percent', 2]); // output: 12.50% - ``` - -All output of the formatter is localized when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. -You can configure the [[yii\i18n\Formatter::locale|locale]] property of the formatter for this. If not configured, the -application [[yii\base\Application::language|language]] is used as the locale. See the [section on internationalization](tutorial-i18n.md) for more details. -The Formatter will then choose the correct format for dates and numbers according to the locale including names of month and -weekdays translated to the current language. Date formats are also affected by the [[yii\i18n\Formatter::timeZone|timeZone]] -which will also be taken from the application [[yii\base\Application::timeZone|timeZone]] if not configured explicitly. - -For example the date format call will output different results for different locales: - -```php -Yii::$app->formatter->locale = 'en-US'; -echo Yii::$app->formatter->asDate('2014-01-01'); // output: January 1, 2014 -Yii::$app->formatter->locale = 'de-DE'; -echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1. Januar 2014 -Yii::$app->formatter->locale = 'ru-RU'; -echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1 января 2014 г. -``` - -> Note that formatting may differ between different versions of the ICU library compiled with PHP and also based on the fact whether the -> [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed or not. So to ensure your website works with the same output -> in all environments it is recommended to install the PHP intl extension in all environments and verify that the version of the ICU library -> is the same. See also: [Setting up your PHP environment for internationalization](tutorial-i18n.md#setup-environment). -> -> Note also that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901 -> on 32bit systems will fall back to the PHP implementation, which does not provide localized month and day names, -> because intl uses a 32bit UNIX timestamp internally. On a 64bit system the intl formatter is used in all cases if installed. - - -Configuring the formatter -------------------------- - -The default formats used by the formatter methods can be adjusted using the properties of the [[yii\i18n\Formatter|formatter class]]. -You can adjust these values application wide by configuring the `formatter` component in your [application config](concept-configurations.md#application-configurations). -An example configuration is shown in the following. -For more details about the available properties check out the [[yii\i18n\Formatter|API documentation of the Formatter class]] and the following subsections. - -```php -'components' => [ - 'formatter' => [ - 'dateFormat' => 'dd.MM.yyyy', - 'decimalSeparator' => ',', - 'thousandSeparator' => ' ', - 'currencyCode' => 'EUR', - ], -], -``` - -Formatting Date and Time values -------------------------------- - -The formatter class provides different methods for formatting date and time values. These are: - -- [[yii\i18n\Formatter::asDate()|date]] - the value is formatted as a date e.g. `January 01, 2014`. -- [[yii\i18n\Formatter::asTime()|time]] - the value is formatted as a time e.g. `14:23`. -- [[yii\i18n\Formatter::asDatetime()|datetime]] - the value is formatted as date and time e.g. `January 01, 2014 14:23`. -- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - the value is formatted as a [unix timestamp](http://en.wikipedia.org/wiki/Unix_time) e.g. `1412609982`. -- [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - the value is formatted as the time interval between a date - and now in human readable form e.g. `1 hour ago`. - -The date and time format for the [[yii\i18n\Formatter::asDate()|date]], [[yii\i18n\Formatter::asTime()|time]], and -[[yii\i18n\Formatter::asDatetime()|datetime]] methods can be specified globally by configuring the formatters -properties [[yii\i18n\Formatter::$dateFormat|$dateFormat]], [[yii\i18n\Formatter::$timeFormat|$timeFormat]], and -[[yii\i18n\Formatter::$datetimeFormat|$datetimeFormat]]. - -By default the formatter uses a shortcut format that is interpreted differently according to the currently active locale -so that dates and times are formatted in a way that is common for the users country and language. -There are four different shortcut formats available: - -- `short` in `en_GB` locale will print for example `06/10/2014` for date and `15:58` for time, while -- `medium` will print `6 Oct 2014` and `15:58:42`, -- `long` will print `6 October 2014` and `15:58:42 GMT`, -- and `full` will print `Monday, 6 October 2014` and `15:58:42 GMT`. - -Additionally you can specify custom formats using the syntax defined by the -[ICU Project](http://site.icu-project.org/) which is described in the ICU manual under the following URL: -. Alternatively you can use the syntax that can be recognized by the -PHP [date()](http://php.net/manual/en/function.date.php) function using a string that is prefixed with `php:`. - -```php -// ICU format -echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06 -// PHP date()-format -echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06 -``` - -### Time zones - -When formatting date and time values, Yii will convert them to the [[yii\i18n\Formatter::timeZone|configured time zone]]. -Therefore the input value is assumed to be in UTC unless a time zone is explicitly given. For this reason -it is recommended to store all date and time values in UTC, preferably as a UNIX timestamp, which is always UTC by definition. -If the input value is in a time zone different from UTC, the time zone has to be stated explicitly like in the following example: - -```php -// assuming Yii::$app->timeZone = 'Europe/Berlin'; -echo Yii::$app->formatter->asTime(1412599260); // 14:41:00 -echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00 -echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 -``` - -Since version 2.0.1 it is also possible to configure the time zone that is assumed for timestamps that do not include a time zone -identifier like the second example in the code above. You can set [[yii\i18n\Formatter::defaultTimeZone]] to the time zone you use for data storage. - -> Note: As time zones are subject to rules made by the governments around the world and may change frequently, it is -> likely that you do not have the latest information in the time zone database installed on your system. -> You may refer to the [ICU manual](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) -> for details on updating the time zone database. -> See also: [Setting up your PHP environment for internationalization](tutorial-i18n.md#setup-environment). - - -Formatting Numbers ------------------- - -For formatting numeric values the formatter class provides the following methods: - -- [[yii\i18n\Formatter::asInteger()|integer]] - the value is formatted as an integer e.g. `42`. -- [[yii\i18n\Formatter::asDecimal()|decimal]] - the value is formatted as a decimal number considering decimal and thousand - separators e.g. `2,542.123` or `2.542,123`. -- [[yii\i18n\Formatter::asPercent()|percent]] - the value is formatted as a percent number e.g. `42%`. -- [[yii\i18n\Formatter::asScientific()|scientific]] - the value is formatted as a number in scientific format e.g. `4.2E4`. -- [[yii\i18n\Formatter::asCurrency()|currency]] - the value is formatted as a currency value e.g. `£420.00`. - Note that for this function to work properly, the locale needs to include a country part e.g. `en_GB` or `en_US` because language only - would be ambiguous in this case. -- [[yii\i18n\Formatter::asSize()|size]] - the value that is a number of bytes is formatted as a human readable size e.g. `410 kibibytes`. -- [[yii\i18n\Formatter::asShortSize()|shortSize]] - is the short version of [[yii\i18n\Formatter::asSize()|size]], e.g. `410 KiB`. - -The format for number formatting can be adjusted using the [[yii\i18n\Formatter::decimalSeparator|decimalSeparator]] and -[[yii\i18n\Formatter::thousandSeparator|thousandSeparator]] which are set by default according to the locale. - -For more advanced configuration, [[yii\i18n\Formatter::numberFormatterOptions]] and [[yii\i18n\Formatter::numberFormatterTextOptions]] -can be used to configure the internally used [NumberFormatter class](http://php.net/manual/en/class.numberformatter.php) - -For example, to adjust the maximum and minimum value of fraction digits, you can configure [[yii\i18n\Formatter::numberFormatterOptions]] property like the following: - -```php -'numberFormatterOptions' => [ - NumberFormatter::MIN_FRACTION_DIGITS => 0, - NumberFormatter::MAX_FRACTION_DIGITS => 2, -] -``` - -Other formatters ----------------- - -In addition to date, time and number formatting, Yii provides a set of other useful formatters for different situations: - -- [[yii\i18n\Formatter::asRaw()|raw]] - the value is outputted as is, this is a pseudo-formatter that has no effect except that - `null` values will be formatted using [[nullDisplay]]. -- [[yii\i18n\Formatter::asText()|text]] - the value is HTML-encoded. - This is the default format used by the [GridView DataColumn](output-data-widgets.md#data-column). -- [[yii\i18n\Formatter::asNtext()|ntext]] - the value is formatted as an HTML-encoded plain text with newlines converted - into line breaks. -- [[yii\i18n\Formatter::asParagraphs()|paragraphs]] - the value is formatted as HTML-encoded text paragraphs wrapped - into `

` tags. -- [[yii\i18n\Formatter::asHtml()|html]] - the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can - pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`. -- [[yii\i18n\Formatter::asEmail()|email]] - the value is formatted as a `mailto`-link. -- [[yii\i18n\Formatter::asImage()|image]] - the value is formatted as an image tag. -- [[yii\i18n\Formatter::asUrl()|url]] - the value is formatted as a hyperlink. -- [[yii\i18n\Formatter::asBoolean()|boolean]] - the value is formatted as a boolean. By default `true` is rendered - as `Yes` and `false` as `No`, translated to the current application language. You can adjust this by configuring - the [[yii\i18n\Formatter::booleanFormat]] property. - -`null`-values -------------- - -For values that are `null` in PHP, the formatter class will print a placeholder instead of an empty string which -defaults to `(not set)` translated to the current application language. You can configure the -[[yii\i18n\Formatter::nullDisplay|nullDisplay]] property to set a custom placeholder. -If you do not want special handling for `null` values, you can set [[yii\i18n\Formatter::nullDisplay|nullDisplay]] to `null`. diff --git a/docs/guide/output-formatting.md b/docs/guide/output-formatting.md index fd4af44ab5..92e73489db 100644 --- a/docs/guide/output-formatting.md +++ b/docs/guide/output-formatting.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Data Formatter ============== @@ -106,14 +107,111 @@ Additionally you can specify custom formats using the syntax defined by the [ICU Project](http://site.icu-project.org/) which is described in the ICU manual under the following URL: . Alternatively you can use the syntax that can be recognized by the PHP [date()](http://php.net/manual/en/function.date.php) function using a string that is prefixed with `php:`. +======= +Data Formatting +=============== + +To display data in a more readable format for users, you may format them using the `formatter` [application component](structure-application-components.md). +By default the formatter is implemented by [[yii\i18n\Formatter]] which provides a set of methods to format data as +date/time, numbers, currencies, and other commonly used formats. You can use the formatter like the following, + +```php +$formatter = \Yii::$app->formatter; + +// output: January 1, 2014 +echo $formatter->asDate('2014-01-01', 'long'); + +// output: 12.50% +echo $formatter->asPercent(0.125, 2); + +// output: cebe@example.com +echo $formatter->asEmail('cebe@example.com'); + +// output: Yes +echo $formatter->asBoolean(true); +// it also handles display of null values: + +// output: (Not set) +echo $formatter->asDate(null); +``` + +As you can see, all these methods are named as `asXyz()`, where `Xyz` stands for a supported format. Alternatively, +you may format data using the generic method [[yii\i18n\Formatter::format()|format()]], which allows you to control +the desired format programmatically and is commonly used by widgets like [[yii\grid\GridView]] and [[yii\widgets\DetailView]]. +For example, + +```php +// output: January 1, 2014 +echo Yii::$app->formatter->format('2014-01-01', 'date'); + +// you can also use an array to specify parameters for the format method: +// `2` is the value for the $decimals parameter of the asPercent()-method. +// output: 12.50% +echo Yii::$app->formatter->format(0.125, ['percent', 2]); +``` + +> Note: The formatter component is designed to format values to be displayed for the end user. If you want +> to convert user input into machine readable format, or just format a date in a machine readable format, +> the formatter is not the right tool for that. +> To convert user input for date and number values you may use [[yii\validators\DateValidator]] and [[yii\validators\NumberValidator]] +> respectively. For simple conversion between machine readable date and time formats, +> the PHP [date()](http://php.net/manual/en/function.date.php)-function is enough. + +## Configuring Formatter + +You may customize the formatting rules by configuring the `formatter` component in the [application configuration](concept-configurations.md#application-configurations). +For example, + +```php +return [ + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'dd.MM.yyyy', + 'decimalSeparator' => ',', + 'thousandSeparator' => ' ', + 'currencyCode' => 'EUR', + ], + ], +]; +``` + +Please refer to [[yii\i18n\Formatter]] for the properties that may be configured. + + +## Formatting Date and Time Values + +The formatter supports the following output formats that are related with date and time: + +- [[yii\i18n\Formatter::asDate()|date]]: the value is formatted as a date, e.g. `January 01, 2014`. +- [[yii\i18n\Formatter::asTime()|time]]: the value is formatted as a time, e.g. `14:23`. +- [[yii\i18n\Formatter::asDatetime()|datetime]]: the value is formatted as date and time, e.g. `January 01, 2014 14:23`. +- [[yii\i18n\Formatter::asTimestamp()|timestamp]]: the value is formatted as a [unix timestamp](http://en.wikipedia.org/wiki/Unix_time), e.g. `1412609982`. +- [[yii\i18n\Formatter::asRelativeTime()|relativeTime]]: the value is formatted as the time interval between a date + and now in human readable form e.g. `1 hour ago`. +- [[yii\i18n\Formatter::asDuration()|duration]]: the value is formatted as a duration in human readable format. e.g. `1 day, 2 minutes`. + +The default date and time formats used for the [[yii\i18n\Formatter::asDate()|date]], [[yii\i18n\Formatter::asTime()|time]], +and [[yii\i18n\Formatter::asDatetime()|datetime]] methods can be customized globally by configuring +[[yii\i18n\Formatter::dateFormat|dateFormat]], [[yii\i18n\Formatter::timeFormat|timeFormat]], and +[[yii\i18n\Formatter::datetimeFormat|datetimeFormat]]. + +You can specify date and time formats using the [ICU syntax](http://userguide.icu-project.org/formatparse/datetime). +You can also use the [PHP date() syntax](http://php.net/manual/en/function.date.php) with a prefix `php:` to differentiate +it from ICU syntax. For example, +>>>>>>> master ```php // ICU format echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06 +<<<<<<< HEAD +======= + +>>>>>>> master // PHP date()-format echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06 ``` +<<<<<<< HEAD ### Time zones When formatting date and time values, Yii will convert them to the [[yii\i18n\Formatter::timeZone|configured time zone]]. @@ -161,6 +259,71 @@ For more advanced configuration, [[yii\i18n\Formatter::numberFormatterOptions]] can be used to configure the internally used [NumberFormatter class](http://php.net/manual/en/class.numberformatter.php) For example, to adjust the maximum and minimum value of fraction digits, you can configure [[yii\i18n\Formatter::numberFormatterOptions]] property like the following: +======= +When working with applications that need to support multiple languages, you often need to specify different date +and time formats for different locales. To simplify this task, you may use format shortcuts (e.g. `long`, `short`), instead. +The formatter will turn a format shortcut into an appropriate format according to the currently active [[yii\i18n\Formatter::locale|locale]]. +The following format shortcuts are supported (the examples assume `en_GB` is the active locale): + +- `short`: will output `06/10/2014` for date and `15:58` for time; +- `medium`: will output `6 Oct 2014` and `15:58:42`; +- `long`: will output `6 October 2014` and `15:58:42 GMT`; +- `full`: will output `Monday, 6 October 2014` and `15:58:42 GMT`. + +Since version 2.0.7 it is also possible to format dates in different calendar systems. +Please refer to the API documentation of the formatters [[yii\i18n\Formatter::$calendar|$calendar]]-property on how to set a different calendar. + + +### Time Zones + +When formatting date and time values, Yii will convert them to the target [[yii\i18n\Formatter::timeZone|time zone]]. +The value being formatted is assumed to be in UTC, unless a time zone is explicitly given or you have configured +[[yii\i18n\Formatter::defaultTimeZone]]. + +In the following examples, we assume the target [[yii\i18n\Formatter::timeZone|time zone]] is set as `Europe/Berlin`. + +```php +// formatting a UNIX timestamp as a time +echo Yii::$app->formatter->asTime(1412599260); // 14:41:00 + +// formatting a datetime string (in UTC) as a time +echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00 + +// formatting a datetime string (in CEST) as a time +echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00 +``` + +> Note: As time zones are subject to rules made by the governments around the world and may change frequently, it is +> likely that you do not have the latest information in the time zone database installed on your system. +> You may refer to the [ICU manual](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) +> for details on updating the time zone database. Please also read +> [Setting up your PHP environment for internationalization](tutorial-i18n.md#setup-environment). + + +## Formatting Numbers + +The formatter supports the following output formats that are related with numbers: + +- [[yii\i18n\Formatter::asInteger()|integer]]: the value is formatted as an integer e.g. `42`. +- [[yii\i18n\Formatter::asDecimal()|decimal]]: the value is formatted as a decimal number considering decimal and thousand + separators e.g. `2,542.123` or `2.542,123`. +- [[yii\i18n\Formatter::asPercent()|percent]]: the value is formatted as a percent number e.g. `42%`. +- [[yii\i18n\Formatter::asScientific()|scientific]]: the value is formatted as a number in scientific format e.g. `4.2E4`. +- [[yii\i18n\Formatter::asCurrency()|currency]]: the value is formatted as a currency value e.g. `£420.00`. + Note that for this function to work properly, the locale needs to include a country part e.g. `en_GB` or `en_US` because language only + would be ambiguous in this case. +- [[yii\i18n\Formatter::asSize()|size]]: the value that is a number of bytes is formatted as a human readable size e.g. `410 kibibytes`. +- [[yii\i18n\Formatter::asShortSize()|shortSize]]: is the short version of [[yii\i18n\Formatter::asSize()|size]], e.g. `410 KiB`. + +The format for number formatting can be adjusted using the [[yii\i18n\Formatter::decimalSeparator|decimalSeparator]] and +[[yii\i18n\Formatter::thousandSeparator|thousandSeparator]], both of which take default values according to the +active [[yii\i18n\Formatter::locale|locale]]. + +For more advanced configuration, [[yii\i18n\Formatter::numberFormatterOptions]] and [[yii\i18n\Formatter::numberFormatterTextOptions]] +can be used to configure the [NumberFormatter class](http://php.net/manual/en/class.numberformatter.php) used internally +to implement the formatter. For example, to adjust the maximum and minimum value of fraction digits, you can configure +the [[yii\i18n\Formatter::numberFormatterOptions]] property like the following: +>>>>>>> master ```php 'numberFormatterOptions' => [ @@ -169,6 +332,7 @@ For example, to adjust the maximum and minimum value of fraction digits, you can ] ``` +<<<<<<< HEAD Other formatters ---------------- @@ -198,3 +362,64 @@ For values that are `null` in PHP, the formatter class will print a placeholder defaults to `(not set)` translated to the current application language. You can configure the [[yii\i18n\Formatter::nullDisplay|nullDisplay]] property to set a custom placeholder. If you do not want special handling for `null` values, you can set [[yii\i18n\Formatter::nullDisplay|nullDisplay]] to `null`. +======= + +## Other Formats + +Besides date/time and number formats, Yii also supports other commonly used formats, including + +- [[yii\i18n\Formatter::asRaw()|raw]]: the value is outputted as is, this is a pseudo-formatter that has no effect except that + `null` values will be formatted using [[nullDisplay]]. +- [[yii\i18n\Formatter::asText()|text]]: the value is HTML-encoded. + This is the default format used by the [GridView DataColumn](output-data-widgets.md#data-column). +- [[yii\i18n\Formatter::asNtext()|ntext]]: the value is formatted as an HTML-encoded plain text with newlines converted + into line breaks. +- [[yii\i18n\Formatter::asParagraphs()|paragraphs]]: the value is formatted as HTML-encoded text paragraphs wrapped + into `

` tags. +- [[yii\i18n\Formatter::asHtml()|html]]: the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can + pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`. +- [[yii\i18n\Formatter::asEmail()|email]]: the value is formatted as a `mailto`-link. +- [[yii\i18n\Formatter::asImage()|image]]: the value is formatted as an image tag. +- [[yii\i18n\Formatter::asUrl()|url]]: the value is formatted as a hyperlink. +- [[yii\i18n\Formatter::asBoolean()|boolean]]: the value is formatted as a boolean. By default `true` is rendered + as `Yes` and `false` as `No`, translated to the current application language. You can adjust this by configuring + the [[yii\i18n\Formatter::booleanFormat]] property. + + +## Null Values + +Null values are specially formatted. Instead of displaying an empty string, the formatter will convert it into a +preset string which defaults to `(not set)` translated into the current application language. You can configure the +[[yii\i18n\Formatter::nullDisplay|nullDisplay]] property to customize this string. + + +## Localizing Data Format + +As aforementioned, the formatter may use the currently active [[yii\i18n\Formatter::locale|locale]] to determine how +to format a value that is suitable in the target country/region. For example, the same date value may be formatted +differently for different locales: + +```php +Yii::$app->formatter->locale = 'en-US'; +echo Yii::$app->formatter->asDate('2014-01-01'); // output: January 1, 2014 + +Yii::$app->formatter->locale = 'de-DE'; +echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1. Januar 2014 + +Yii::$app->formatter->locale = 'ru-RU'; +echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1 января 2014 г. +``` + +By default, the currently active [[yii\i18n\Formatter::locale|locale]] is determined by the value of +[[yii\base\Application::language]]. You may override it by setting the [[yii\i18n\Formatter::locale]] property explicitly. + +> Note: The Yii formatter relies on the [PHP intl extension](http://php.net/manual/en/book.intl.php) to support +> localized data formatting. Because different versions of the ICU library compiled with PHP may cause different +> formatting results, it is recommended that you use the same ICU version for all your environments. For more details, +> please refer to [Setting up your PHP environment for internationalization](tutorial-i18n.md#setup-environment). +> +> If the intl extension is not installed, the data will not be localized. +> +> Note that for date values that are before year 1901 or after 2038, they will not be localized on 32-bit systems, even +> if the intl extension is installed. This is because in this case ICU is using 32-bit UNIX timestamps to date values. +>>>>>>> master diff --git a/docs/guide/output-pagination.md b/docs/guide/output-pagination.md index f4cf209953..8670a63800 100644 --- a/docs/guide/output-pagination.md +++ b/docs/guide/output-pagination.md @@ -1,6 +1,7 @@ Pagination ========== +<<<<<<< HEAD <<<<<<< HEAD When there's too much data to be displayed on a single page at once it's often divided into parts each containing some data items and displayed one part at a time. Such parts are called @@ -11,39 +12,59 @@ pagination is already sorted out for you automatically. If not, you need to crea object, fill it with data such as [[\yii\data\Pagination::$totalCount|total item count]], [[\yii\data\Pagination::$pageSize|page size]] and [[\yii\data\Pagination::$page|current page]], apply it to the query and then feed it to [[\yii\widgets\LinkPager|link pager]]. +======= +When there are too much data to be displayed on a single page, a common strategy is to display them in multiple +pages and on each page only display a small portion of the data. This strategy is known as *pagination*. +>>>>>>> master +Yii uses a [[yii\data\Pagination]] object to represent the information about a pagination scheme. In particular, -First of all in controller action we're creating pagination object and filling it with data: +* [[yii\data\Pagination::$totalCount|total count]] specifies the total number of data items. Note that this + is usually much more than the number of data items needed to display on a single page. +* [[yii\data\Pagination::$pageSize|page size]] specifies how many data items each page contains. The default + value is 20. +* [[yii\data\Pagination::$page|current page]] gives the current page number (zero-based). The default value + value is 0, meaning the first page. + +With a fully specified [[yii\data\Pagination]] object, you can retrieve and display data partially. For example, +if you are fetching data from a database, you can specify the `OFFSET` and `LIMIT` clause of the DB query with +the corresponding values provided by the pagination. Below is an example, ```php -function actionIndex() -{ - $query = Article::find()->where(['status' => 1]); - $countQuery = clone $query; - $pages = new Pagination(['totalCount' => $countQuery->count()]); - $models = $query->offset($pages->offset) - ->limit($pages->limit) - ->all(); +use yii\data\Pagination; - return $this->render('index', [ - 'models' => $models, - 'pages' => $pages, - ]); -} +// build a DB query to get all articles with status = 1 +$query = Article::find()->where(['status' => 1]); + +// get the total number of articles (but do not fetch the article data yet) +$count = $query->count(); + +// create a pagination object with the total count +$pagination = new Pagination(['totalCount' => $count]); + +// limit the query using the pagination and retrieve the articles +$articles = $query->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); ``` -Then in a view we're outputting models for the current page and passing pagination object to the link pager: +Which page of articles will be returned in the above example? It depends on whether a query parameter named `page` +is given. By default, the pagination will attempt to set the [[yii\data\Pagination::$page|current page]] to be +the value of the `page` parameter. If the parameter is not provided, then it will default to 0. + +To facilitate building the UI element that supports pagination, Yii provides the [[yii\widgets\LinkPager]] widget +that displays a list of page buttons upon which users can click to indicate which page of data should be displayed. +The widget takes a pagination object so that it knows what is the current page and how many page buttons should +be displayed. For example, ```php -foreach ($models as $model) { - // display $model here -} +use yii\widgets\LinkPager; -// display pagination echo LinkPager::widget([ - 'pagination' => $pages, + 'pagination' => $pagination, ]); ``` +<<<<<<< HEAD ======= When there are too much data to be displayed on a single page, a common strategy is to display them in multiple pages and on each page only display a small portion of the data. This strategy is known as *pagination*. @@ -95,6 +116,8 @@ echo LinkPager::widget([ 'pagination' => $pagination, ]); ``` +======= +>>>>>>> master If you want to build UI element manually, you may use [[yii\data\Pagination::createUrl()]] to create URLs that would lead to different pages. The method requires a page parameter and will create a properly formatted URL @@ -105,13 +128,23 @@ containing the page parameter. For example, // If you do not specify this, the currently requested route will be used $pagination->route = 'article/index'; +<<<<<<< HEAD // displays: /index.php?r=article/index&page=100 echo $pagination->createUrl(100); // displays: /index.php?r=article/index&page=101 +======= +// displays: /index.php?r=article%2Findex&page=100 +echo $pagination->createUrl(100); + +// displays: /index.php?r=article%2Findex&page=101 +>>>>>>> master echo $pagination->createUrl(101); ``` > Tip: You can customize the name of the `page` query parameter by configuring the [[yii\data\Pagination::pageParam|pageParam]] property when creating the pagination object. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide/output-sorting.md b/docs/guide/output-sorting.md index 6bf4fa142b..ad072e666d 100644 --- a/docs/guide/output-sorting.md +++ b/docs/guide/output-sorting.md @@ -1,52 +1,98 @@ Sorting ======= +<<<<<<< HEAD <<<<<<< HEAD Sometimes the data that is to be displayed should be sorted according to one or several attributes. If you are using a [data provider](output-data-providers.md) with one of the [data widgets](output-data-widgets.md), sorting is handled for you automatically. If not, you should create a [[yii\data\Sort]] instance, configure it and apply it to the query. It can also be passed to the view, where it can be used to create links to sort by certain attributes. +======= +When displaying multiple rows of data, it is often needed that the data be sorted according to some columns +specified by end users. Yii uses a [[yii\data\Sort]] object to represent the information about a sorting schema. +In particular, +>>>>>>> master -A typical usage example is as follows, +* [[yii\data\Sort::$attributes|attributes]] specifies the *attributes* by which the data can be sorted. + An attribute can be as simple as a [model attribute](structure-models.md#attributes). It can also be a composite + one by combining multiple model attributes or DB columns. More details will be given in the following. +* [[yii\data\Sort::$attributeOrders|attributeOrders]] gives the currently requested ordering directions for + each attribute. +* [[yii\data\Sort::$orders|orders]] gives the ordering directions in terms of the low-level columns. + +To use [[yii\data\Sort]], first declare which attributes can be sorted. Then retrieve the currently requested +ordering information from [[yii\data\Sort::$attributeOrders|attributeOrders]] or [[yii\data\Sort::$orders|orders]] +and use them to customize the data query. For example, ```php -function actionIndex() -{ - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - 'default' => SORT_DESC, - 'label' => 'Name', - ], +use yii\data\Sort; + +$sort = new Sort([ + 'attributes' => [ + 'age', + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + 'default' => SORT_DESC, + 'label' => 'Name', ], - ]); + ], +]); - $models = Article::find() - ->where(['status' => 1]) - ->orderBy($sort->orders) - ->all(); - - return $this->render('index', [ - 'models' => $models, - 'sort' => $sort, - ]); -} +$articles = Article::find() + ->where(['status' => 1]) + ->orderBy($sort->orders) + ->all(); ``` -In the view: +In the above example, two attributes are declared for the [[yii\data\Sort|Sort]] object: `age` and `name`. + +The `age` attribute is a *simple* attribute corresponding to the `age` attribute of the `Article` Active Record class. +It is equivalent to the following declaration: ```php -// display links leading to sort actions +'age' => [ + 'asc' => ['age' => SORT_ASC], + 'desc' => ['age' => SORT_DESC], + 'default' => SORT_ASC, + 'label' => Inflector::camel2words('age'), +] +``` + +The `name` attribute is a *composite* attribute defined by `first_name` and `last_name` of `Article`. It is declared +using the following array structure: + +- The `asc` and `desc` elements specify how to sort by the attribute in ascending and descending directions, respectively. + Their values represent the actual columns and the directions by which the data should be sorted by. You can specify + one or multiple columns to indicate simple ordering or composite ordering. +- The `default` element specifies the direction by which the attribute should be sorted when initially requested. + It defaults to ascending order, meaning if it is not sorted before and you request to sort by this attribute, + the data will be sorted by this attribute in ascending order. +- The `label` element specifies what label should be used when calling [[yii\data\Sort::link()]] to create a sort link. + If not set, [[yii\helpers\Inflector::camel2words()]] will be called to generate a label from the attribute name. + Note that it will not be HTML-encoded. + +> Info: You can directly feed the value of [[yii\data\Sort::$orders|orders]] to the database query to build + its `ORDER BY` clause. Do not use [[yii\data\Sort::$attributeOrders|attributeOrders]] because some of the + attributes may be composite and cannot be recognized by the database query. + +You can call [[yii\data\Sort::link()]] to generate a hyperlink upon which end users can click to request sorting +the data by the specified attribute. You may also call [[yii\data\Sort::createUrl()]] to create a sortable URL. +For example, + +```php +// specifies the route that the URL to be created should use +// If you do not specify this, the currently requested route will be used +$sort->route = 'article/index'; + +// display links leading to sort by name and age, respectively echo $sort->link('name') . ' | ' . $sort->link('age'); -foreach ($models as $model) { - // display $model here -} +// displays: /index.php?r=article%2Findex&sort=age +echo $sort->createUrl('age'); ``` +<<<<<<< HEAD In the above, we declare two attributes that support sorting: `name` and `age`. We pass the sort information to the Article query so that the query results are sorted by the orders specified by the Sort object. In the view, we show two hyperlinks @@ -143,3 +189,8 @@ echo $sort->createUrl('age'); You may specify a default ordering via [[yii\data\Sort::defaultOrder]] when the query parameter is not present. You may also customize the name of the query parameter by configuring the [[yii\data\Sort::sortParam|sortParam]] property. >>>>>>> yiichina/master +======= +[[yii\data\Sort]] checks the `sort` query parameter to determine which attributes are being requested for sorting. +You may specify a default ordering via [[yii\data\Sort::defaultOrder]] when the query parameter is not present. +You may also customize the name of the query parameter by configuring the [[yii\data\Sort::sortParam|sortParam]] property. +>>>>>>> master diff --git a/docs/guide/output-theming.md b/docs/guide/output-theming.md index 675b40d100..701181888a 100644 --- a/docs/guide/output-theming.md +++ b/docs/guide/output-theming.md @@ -1,6 +1,7 @@ Theming ======= +<<<<<<< HEAD <<<<<<< HEAD > Note: This section is under development. @@ -16,80 +17,93 @@ Configuring a theme Theme configuration is specified via the `view` component of the application. In order to set up a theme to work with basic application views, the following should be in your application config file: +======= +Theming is a way to replace a set of [views](structure-views.md) with another without the need of touching +the original view rendering code. You can use theming to systematically change the look and feel of an application. + +To use theming, you should configure the [[yii\base\View::theme|theme]] property of the `view` application component. +The property configures a [[yii\base\Theme]] object which governs how view files are being replaced. You should +mainly specify the following properties of [[yii\base\Theme]]: + +- [[yii\base\Theme::basePath]]: specifies the base directory that contains the themed resources (CSS, JS, images, etc.) +- [[yii\base\Theme::baseUrl]]: specifies the base URL of the themed resources. +- [[yii\base\Theme::pathMap]]: specifies the replacement rules of view files. More details will be given in the following + subsections. + +For example, if you call `$this->render('about')` in `SiteController`, you will be rendering the view file +`@app/views/site/about.php`. However, if you enable theming in the following application configuration, +the view file `@app/themes/basic/site/about.php` will be rendered, instead. +>>>>>>> master ```php -'components' => [ - 'view' => [ - 'theme' => [ - 'pathMap' => ['@app/views' => '@app/themes/basic'], - 'baseUrl' => '@web/themes/basic', - ], - ], -], -``` - -In the above, `pathMap` defines a map of original paths to themed paths while `baseUrl` defines the base URL for -resources referenced by theme files. - -In our case `pathMap` is `['@app/views' => '@app/themes/basic']`. That means that every view in `@app/views` will be -first searched under `@app/themes/basic` and if a view exists in the theme directory it will be used instead of the -original view. - -For example, with a configuration above a themed version of a view file `@app/views/site/index.php` will be -`@app/themes/basic/site/index.php`. It basically replaces `@app/views` in `@app/views/site/index.php` with -`@app/themes/basic`. - -In order to configure theme runtime you can use the following code before rendering a view. Typically it will be -placed in controller: - -```php -$this->getView()->theme = Yii::createObject([ - 'class' => '\yii\base\Theme', - 'pathMap' => ['@app/views' => '@app/themes/basic'], - 'baseUrl' => '@web/themes/basic', -]); -``` - -### Theming modules - -In order to theme modules, `pathMap` may look like the following: - -```php -'components' => [ - 'view' => [ - 'theme' => [ - 'pathMap' => [ - '@app/views' => '@app/themes/basic', - '@app/modules' => '@app/themes/basic/modules', // <-- !!! +return [ + 'components' => [ + 'view' => [ + 'theme' => [ + 'basePath' => '@app/themes/basic', + 'baseUrl' => '@web/themes/basic', + 'pathMap' => [ + '@app/views' => '@app/themes/basic', + ], ], ], ], -], +]; ``` -It will allow you to theme `@app/modules/blog/views/comment/index.php` with `@app/themes/basic/modules/blog/views/comment/index.php`. +> Info: Path aliases are supported by themes. When doing view replacement, path aliases will be turned into + the actual file paths or URLs. -### Theming widgets - -In order to theme a widget view located at `@app/widgets/currency/views/index.php`, you need the following configuration for -the view component theme: +You can access the [[yii\base\Theme]] object through the [[yii\base\View::theme]] property. For example, +in a view file, you can write the following code because `$this` refers to the view object: ```php -'components' => [ - 'view' => [ - 'theme' => [ - 'pathMap' => ['@app/widgets' => '@app/themes/basic/widgets'], - ], - ], +$theme = $this->theme; + +// returns: $theme->baseUrl . '/img/logo.gif' +$url = $theme->getUrl('img/logo.gif'); + +// returns: $theme->basePath . '/img/logo.gif' +$file = $theme->getPath('img/logo.gif'); +``` + +The [[yii\base\Theme::pathMap]] property governs how view files should be replaced. It takes an array of +key-value pairs, where the keys are the original view paths to be replaced and the values are the corresponding +themed view paths. The replacement is based on partial match: if a view path starts with any key in +the [[yii\base\Theme::pathMap|pathMap]] array, that matching part will be replaced with the corresponding array value. +Using the above configuration example, because `@app/views/site/about.php` partially matches the key +`@app/views`, it will be replaced as `@app/themes/basic/site/about.php`. + + +## Theming Modules + +In order to theme modules, [[yii\base\Theme::pathMap]] can be configured like the following: + +```php +'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/modules' => '@app/themes/basic/modules', // <-- !!! ], ``` -With the configuration above you can create a themed version of the `@app/widgets/currency/index.php` view in -`@app/themes/basic/widgets/currency/index.php`. +It will allow you to theme `@app/modules/blog/views/comment/index.php` into `@app/themes/basic/modules/blog/views/comment/index.php`. -Using multiple paths --------------------- +## Theming Widgets + +In order to theme widgets, you can configure [[yii\base\Theme::pathMap]] in the following way: + +```php +'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/widgets' => '@app/themes/basic/widgets', // <-- !!! +], +``` + +This will allow you to theme `@app/widgets/currency/views/index.php` into `@app/themes/basic/widgets/currency/index.php`. + + +<<<<<<< HEAD It is possible to map a single path to multiple theme paths. For example, ======= Theming is a way to replace a set of [views](structure-views.md) with another without the need of touching @@ -176,12 +190,17 @@ In order to theme widgets, you can configure [[yii\base\Theme::pathMap]] in the This will allow you to theme `@app/widgets/currency/views/index.php` into `@app/themes/basic/widgets/currency/index.php`. +======= +>>>>>>> master ## Theme Inheritance Sometimes you may want to define a basic theme which contains a basic look and feel of the application, and then based on the current holiday, you may want to vary the look and feel slightly. You can achieve this goal using theme inheritance which is done by mapping a single view path to multiple targets. For example, +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ```php 'pathMap' => [ @@ -192,14 +211,20 @@ theme inheritance which is done by mapping a single view path to multiple target ] ``` +<<<<<<< HEAD <<<<<<< HEAD In this case, first the view will be searched for in `@app/themes/christmas/site/index.php` then if it's not found it will check `@app/themes/basic/site/index.php`. If there's no view there as well, then the application view will be used. This ability is especially useful if you want to temporarily or conditionally override some views. ======= +======= +>>>>>>> master In this case, the view `@app/views/site/index.php` would be themed as either `@app/themes/christmas/site/index.php` or `@app/themes/basic/site/index.php`, depending on which themed file exists. If both themed files exist, the first one will take precedence. In practice, you would keep most themed view files in `@app/themes/basic` and customize some of them in `@app/themes/christmas`. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide/rest-authentication.md b/docs/guide/rest-authentication.md index 6145f21748..4e6b083e83 100644 --- a/docs/guide/rest-authentication.md +++ b/docs/guide/rest-authentication.md @@ -25,11 +25,15 @@ Yii supports all of the above authentication methods. You can also easily create To enable authentication for your APIs, do the following steps: +<<<<<<< HEAD <<<<<<< HEAD 1. Configure the `user` application component: ======= 1. Configure the `user` [application component](structure-application-components.md): >>>>>>> yiichina/master +======= +1. Configure the `user` [application component](structure-application-components.md): +>>>>>>> master - Set the [[yii\web\User::enableSession|enableSession]] property to be `false`. - Set the [[yii\web\User::loginUrl|loginUrl]] property to be `null` to show a HTTP 403 error instead of redirecting to the login page. 2. Specify which authentication methods you plan to use by configuring the `authenticator` behavior @@ -41,15 +45,16 @@ is false, the user authentication status will NOT be persisted across requests u will be performed for every request, which is accomplished by Step 2 and 3. > Tip: You may configure [[yii\web\User::enableSession|enableSession]] of the `user` application component - in application configurations if you are developing RESTful APIs in terms of an application. If you develop - RESTful APIs as a module, you may put the following line in the module's `init()` method, like the following: +> in application configurations if you are developing RESTful APIs in terms of an application. If you develop +> RESTful APIs as a module, you may put the following line in the module's `init()` method, like the following: +> > ```php -public function init() -{ - parent::init(); - \Yii::$app->user->enableSession = false; -} -``` +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` For example, to use HTTP Basic Auth, you may configure the `authenticator` behavior as follows, @@ -126,5 +131,5 @@ action for the requested resource. This process is called *authorization* which the [Authorization section](security-authorization.md). If your controllers extend from [[yii\rest\ActiveController]], you may override -the [[yii\rest\Controller::checkAccess()|checkAccess()]] method to perform authorization check. The method +the [[yii\rest\ActiveController::checkAccess()|checkAccess()]] method to perform authorization check. The method will be called by the built-in actions provided by [[yii\rest\ActiveController]]. diff --git a/docs/guide/rest-controllers.md b/docs/guide/rest-controllers.md index 518e041931..52d1673d98 100644 --- a/docs/guide/rest-controllers.md +++ b/docs/guide/rest-controllers.md @@ -22,7 +22,7 @@ will be described in detail in the next few sections: [[yii\rest\ActiveController]] in addition provides the following features: * A set of commonly needed actions: `index`, `view`, `create`, `update`, `delete`, `options`; -* User authorization in regarding to the requested action and resource. +* User authorization in regard to the requested action and resource. ## Creating Controller Classes @@ -53,7 +53,7 @@ In particular, the following filters will be executed in the order they are list * [[yii\filters\ContentNegotiator|contentNegotiator]]: supports content negotiation, to be explained in the [Response Formatting](rest-response-formatting.md) section; * [[yii\filters\VerbFilter|verbFilter]]: supports HTTP method validation; -* [[yii\filters\AuthMethod|authenticator]]: supports user authentication, to be explained in +* [[yii\filters\auth\AuthMethod|authenticator]]: supports user authentication, to be explained in the [Authentication](rest-authentication.md) section; * [[yii\filters\RateLimiter|rateLimiter]]: supports rate limiting, to be explained in the [Rate Limiting](rest-rate-limiting.md) section. @@ -79,7 +79,7 @@ public function behaviors() ## Extending `ActiveController` If your controller class extends from [[yii\rest\ActiveController]], you should set -its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class +its [[yii\rest\ActiveController::modelClass|modelClass]] property to be the name of the resource class that you plan to serve through this controller. The class must extend from [[yii\db\ActiveRecord]]. diff --git a/docs/guide/rest-error-handling.md b/docs/guide/rest-error-handling.md index 9618c588e1..b984536dc2 100644 --- a/docs/guide/rest-error-handling.md +++ b/docs/guide/rest-error-handling.md @@ -77,11 +77,15 @@ return [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; +<<<<<<< HEAD <<<<<<< HEAD if ($response->data !== null && !empty(Yii::$app->request->get('suppress_response_code'))) { ======= if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { >>>>>>> yiichina/master +======= + if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { +>>>>>>> master $response->data = [ 'success' => $response->isSuccessful, 'data' => $response->data, diff --git a/docs/guide/rest-quick-start.md b/docs/guide/rest-quick-start.md index 5e1f835833..3387f71c61 100644 --- a/docs/guide/rest-quick-start.md +++ b/docs/guide/rest-quick-start.md @@ -20,19 +20,27 @@ In the following, we use an example to illustrate how you can build a set of RES Assume you want to expose the user data via RESTful APIs. The user data are stored in the `user` DB table, <<<<<<< HEAD +<<<<<<< HEAD and you have already created the [[yii\db\ActiveRecord|ActiveRecord]] class `app\models\User` to access the user data. ======= and you have already created the [active record](db-active-record.md) class `app\models\User` to access the user data. >>>>>>> yiichina/master +======= +and you have already created the [active record](db-active-record.md) class `app\models\User` to access the user data. +>>>>>>> master ## Creating a Controller +<<<<<<< HEAD <<<<<<< HEAD First, create a controller class `app\controllers\UserController` as follows, ======= First, create a [controller](structure-controllers.md) class `app\controllers\UserController` as follows, >>>>>>> yiichina/master +======= +First, create a [controller](structure-controllers.md) class `app\controllers\UserController` as follows, +>>>>>>> master ```php namespace app\controllers; @@ -45,6 +53,7 @@ class UserController extends ActiveController } ``` +<<<<<<< HEAD <<<<<<< HEAD The controller class extends from [[yii\rest\ActiveController]]. By specifying [[yii\rest\ActiveController::modelClass|modelClass]] as `app\models\User`, the controller knows what model can be used for fetching and manipulating data. @@ -53,6 +62,11 @@ The controller class extends from [[yii\rest\ActiveController]], which implement By specifying [[yii\rest\ActiveController::modelClass|modelClass]] as `app\models\User`, the controller knows which model can be used for fetching and manipulating data. >>>>>>> yiichina/master +======= +The controller class extends from [[yii\rest\ActiveController]], which implements a common set of RESTful actions. +By specifying [[yii\rest\ActiveController::modelClass|modelClass]] +as `app\models\User`, the controller knows which model can be used for fetching and manipulating data. +>>>>>>> master ## Configuring URL Rules @@ -78,10 +92,14 @@ can be accessed and manipulated with pretty URLs and meaningful HTTP verbs. To let the API accept input data in JSON format, configure the [[yii\web\Request::$parsers|parsers]] property of <<<<<<< HEAD +<<<<<<< HEAD the `request` application component to use the [[yii\web\JsonParser]] for JSON input: ======= the `request` [application component](structure-application-components.md) to use the [[yii\web\JsonParser]] for JSON input: >>>>>>> yiichina/master +======= +the `request` [application component](structure-application-components.md) to use the [[yii\web\JsonParser]] for JSON input: +>>>>>>> master ```php 'request' => [ @@ -202,7 +220,7 @@ For example, the URL `http://localhost/users?fields=id,email` will only return t > Info: You may have noticed that the result of `http://localhost/users` includes some sensitive fields, > such as `password_hash`, `auth_key`. You certainly do not want these to appear in your API result. -> You can and should filter out these fields as described in the [Response Formatting](rest-response-formatting.md) section. +> You can and should filter out these fields as described in the [Resources](rest-resources.md) section. ## Summary diff --git a/docs/guide/rest-rate-limiting.md b/docs/guide/rest-rate-limiting.md index b3f1671643..f076645a61 100644 --- a/docs/guide/rest-rate-limiting.md +++ b/docs/guide/rest-rate-limiting.md @@ -13,10 +13,32 @@ This interface requires implementation of three methods: when the rate limit was last checked. * `saveAllowance()`: saves both the number of remaining requests allowed and the current UNIX timestamp. -You may want to use two columns in the user table to record the allowance and timestamp information. With those defined, then `loadAllowance()` and `saveAllowance()` can be implemented to read and save the values +You may want to use two columns in the user table to record the allowance and timestamp information. With those defined, +then `loadAllowance()` and `saveAllowance()` can be implemented to read and save the values of the two columns corresponding to the current authenticated user. To improve performance, you may also consider storing these pieces of information in a cache or NoSQL storage. +Implementation in the `User` model could look like the following: + +```php +public function getRateLimit($request, $action) +{ + return [$this->rateLimit, 1]; // $rateLimit requests per second +} + +public function loadAllowance($request, $action) +{ + return [$this->allowance, $this->allowance_updated_at]; +} + +public function saveAllowance($request, $action, $allowance, $timestamp) +{ + $this->allowance = $allowance; + $this->allowance_updated_at = $timestamp; + $this->save(); +} +``` + Once the identity class implements the required interface, Yii will automatically use [[yii\filters\RateLimiter]] configured as an action filter for [[yii\rest\Controller]] to perform rate limiting check. The rate limiter will throw a [[yii\web\TooManyRequestsHttpException]] when the rate limit is exceeded. diff --git a/docs/guide/rest-response-formatting.md b/docs/guide/rest-response-formatting.md index db58b35511..b99f1bd085 100644 --- a/docs/guide/rest-response-formatting.md +++ b/docs/guide/rest-response-formatting.md @@ -10,12 +10,17 @@ with response formatting: This is done by [[yii\rest\Serializer]]. 3. Convert arrays into a string in the format as determined by the content negotiation step. This is done by [[yii\web\ResponseFormatterInterface|response formatters]] registered with +<<<<<<< HEAD <<<<<<< HEAD the [[yii\web\Response::formatters|response]] application component. ======= the [[yii\web\Response::formatters|formatters]] property of the `response` [application component](structure-application-components.md). >>>>>>> yiichina/master +======= + the [[yii\web\Response::formatters|formatters]] property of the + `response` [application component](structure-application-components.md). +>>>>>>> master ## Content Negotiation diff --git a/docs/guide/rest-routing.md b/docs/guide/rest-routing.md index fba6cd6971..c631864306 100644 --- a/docs/guide/rest-routing.md +++ b/docs/guide/rest-routing.md @@ -7,12 +7,17 @@ With resource and controller classes ready, you can access the resources using t In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs. For example, a request `POST /users` would mean accessing the `user/create` action. <<<<<<< HEAD +<<<<<<< HEAD This can be done easily by configuring the `urlManager` application component in the application configuration like the following: ======= This can be done easily by configuring the `urlManager` [application component](structure-application-components.md) in the application configuration like the following: >>>>>>> yiichina/master +======= +This can be done easily by configuring the `urlManager` [application component](structure-application-components.md) +in the application configuration like the following: +>>>>>>> master ```php 'urlManager' => [ diff --git a/docs/guide/runtime-handling-errors.md b/docs/guide/runtime-handling-errors.md index f8ca55f03a..7bd0bef6f1 100644 --- a/docs/guide/runtime-handling-errors.md +++ b/docs/guide/runtime-handling-errors.md @@ -3,10 +3,14 @@ Handling Errors Yii includes a built-in [[yii\web\ErrorHandler|error handler]] which makes error handling a much more pleasant <<<<<<< HEAD +<<<<<<< HEAD experience than before. In particular, the Yii error handler does the followings to improve error handling: ======= experience than before. In particular, the Yii error handler does the following to improve error handling: >>>>>>> yiichina/master +======= +experience than before. In particular, the Yii error handler does the following to improve error handling: +>>>>>>> master * All non-fatal PHP errors (e.g. warnings, notices) are converted into catchable exceptions. * Exceptions and fatal PHP errors are displayed with detailed call stack information and source code lines @@ -148,13 +152,23 @@ the following variables if the error action is defined as [[yii\web\ErrorAction] * `exception`: the exception object through which you can retrieve more useful information, such as HTTP status code, error code, error call stack, etc. +<<<<<<< HEAD <<<<<<< HEAD > Info: If you are using the [basic application template](start-installation.md) or the [advanced application template](tutorial-advanced-app.md), ======= > Info: If you are using the [basic project template](start-installation.md) or the [advanced project template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), >>>>>>> yiichina/master +======= +> Info: If you are using the [basic project template](start-installation.md) or the [advanced project template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), +>>>>>>> master the error action and the error view are already defined for you. +> Note: If you need to redirect in an error handler, do it the following way: +> ```php +> Yii::$app->getResponse()->redirect($url)->send(); +> return; +> ``` + ### Customizing Error Response Format diff --git a/docs/guide/runtime-overview.md b/docs/guide/runtime-overview.md index ca6d84964f..b886f140f1 100644 --- a/docs/guide/runtime-overview.md +++ b/docs/guide/runtime-overview.md @@ -10,10 +10,10 @@ Each time when a Yii application handles a request, it undergoes a similar workf the [request](runtime-requests.md) application component. 4. The application creates a [controller](structure-controllers.md) instance to handle the request. 5. The controller creates an [action](structure-controllers.md) instance and performs the filters for the action. -6. If any filter fails, the action is cancelled. +6. If any [filter](structure-filters.md) fails, the action is cancelled. 7. If all filters pass, the action is executed. -8. The action loads a data model, possibly from a database. -9. The action renders a view, providing it with the data model. +8. The action loads a data [model](structure-models.md), possibly from a database. +9. The action renders a [view](structure-views.md), providing it with the data model. 10. The rendered result is returned to the [response](runtime-responses.md) application component. 11. The response component sends the rendered result to the user's browser. diff --git a/docs/guide/runtime-requests.md b/docs/guide/runtime-requests.md index ef98c1f4fa..6a877c21b4 100644 --- a/docs/guide/runtime-requests.md +++ b/docs/guide/runtime-requests.md @@ -66,10 +66,10 @@ For example, ```php $request = Yii::$app->request; -if ($request->isAjax) { // the request is an AJAX request } -if ($request->isGet) { // the request method is GET } -if ($request->isPost) { // the request method is POST } -if ($request->isPut) { // the request method is PUT } +if ($request->isAjax) { /* the request is an AJAX request */ } +if ($request->isGet) { /* the request method is GET */ } +if ($request->isPost) { /* the request method is POST */ } +if ($request->isPut) { /* the request method is PUT */ } ``` ## Request URLs @@ -105,7 +105,7 @@ $headers = Yii::$app->request->headers; // returns the Accept header value $accept = $headers->get('Accept'); -if ($headers->has('User-Agent')) { // there is User-Agent header } +if ($headers->has('User-Agent')) { /* there is User-Agent header */ } ``` The `request` component also provides support for quickly accessing some commonly used headers, including: diff --git a/docs/guide/runtime-routing.md b/docs/guide/runtime-routing.md index 417b2b313e..785590994d 100644 --- a/docs/guide/runtime-routing.md +++ b/docs/guide/runtime-routing.md @@ -11,10 +11,14 @@ back into the original route and query parameters. The central piece responsible for routing and URL creation is the [[yii\web\UrlManager|URL manager]], <<<<<<< HEAD +<<<<<<< HEAD which is registered as the `urlManager` application component. The [[yii\web\UrlManager|URL manager]] ======= which is registered as the `urlManager` [application component](structure-application-components.md). The [[yii\web\UrlManager|URL manager]] >>>>>>> yiichina/master +======= +which is registered as the `urlManager` [application component](structure-application-components.md). The [[yii\web\UrlManager|URL manager]] +>>>>>>> master provides the [[yii\web\UrlManager::parseRequest()|parseRequest()]] method to parse an incoming request into a route and the associated query parameters and the [[yii\web\UrlManager::createUrl()|createUrl()]] method to create a URL from a given route and its associated query parameters. @@ -34,7 +38,7 @@ Depending on the `urlManager` configuration, the created URL may look like one o And if the created URL is requested later, it will still be parsed back into the original route and query parameter value. ``` -/index.php?r=post/view&id=100 +/index.php?r=post%2Fview&id=100 /index.php/post/100 /posts/100 ``` @@ -62,7 +66,7 @@ property of the [[yii\web\UrlManager|URL manager]] without changing any other ap ## Routing Routing involves two steps. In the first step, the incoming request is parsed into a route and the associated -query parameters. In the second step, a [controller action](structure-controllers.md) corresponding to the parsed route +query parameters. In the second step, a [controller action](structure-controllers.md#actions) corresponding to the parsed route is created to handle the request. When using the default URL format, parsing a request into a route is as simple as getting the value of a `GET` @@ -85,11 +89,11 @@ controller and action: 3. Check if the ID refers to a module listed in the [[yii\base\Module::modules|modules]] property of the current module. If so, a module is created according to the configuration found in the module list, and Step 2 will be taken to handle the next part of the route under the context of the newly created module. -4. Treat the ID as a controller ID and create a controller object. Do the next step with the rest part of +4. Treat the ID as a [controller ID](structure-controllers.md#controller-ids) and create a controller object. Do the next step with the rest part of the route. 5. The controller looks for the current ID in its [[yii\base\Controller::actions()|action map]]. If found, it creates an action according to the configuration found in the map. Otherwise, the controller will - attempt to create an inline action which is defined by an action method corresponding to the current ID. + attempt to create an inline action which is defined by an action method corresponding to the current [action ID](structure-controllers.md#action-ids). Among the above steps, if any error occurs, a [[yii\web\NotFoundHttpException]] will be thrown, indicating the failure of the routing process. @@ -128,6 +132,8 @@ With the above configuration, the `site/offline` action will be used to handle a The `catchAll` property should take an array whose first element specifies a route, and the rest of the elements (name-value pairs) specify the parameters to be [bound to the action](structure-controllers.md#action-parameters). +> Info: Debug panel on development environment will not work when this property is enabled + ## Creating URLs @@ -137,19 +143,19 @@ their associated query parameters. For example, ```php use yii\helpers\Url; -// creates a URL to a route: /index.php?r=post/index +// creates a URL to a route: /index.php?r=post%2Findex echo Url::to(['post/index']); -// creates a URL to a route with parameters: /index.php?r=post/view&id=100 +// creates a URL to a route with parameters: /index.php?r=post%2Fview&id=100 echo Url::to(['post/view', 'id' => 100]); -// creates an anchored URL: /index.php?r=post/view&id=100#content +// creates an anchored URL: /index.php?r=post%2Fview&id=100#content echo Url::to(['post/view', 'id' => 100, '#' => 'content']); -// creates an absolute URL: http://www.example.com/index.php?r=post/index +// creates an absolute URL: http://www.example.com/index.php?r=post%2Findex echo Url::to(['post/index'], true); -// creates an absolute URL using the https scheme: https://www.example.com/index.php?r=post/index +// creates an absolute URL using the https scheme: https://www.example.com/index.php?r=post%2Findex echo Url::to(['post/index'], 'https'); ``` @@ -174,19 +180,19 @@ For example, assume the current module is `admin` and the current controller is ```php use yii\helpers\Url; -// currently requested route: /index.php?r=admin/post/index +// currently requested route: /index.php?r=admin%2Fpost%2Findex echo Url::to(['']); -// a relative route with action ID only: /index.php?r=admin/post/index +// a relative route with action ID only: /index.php?r=admin%2Fpost%2Findex echo Url::to(['index']); -// a relative route: /index.php?r=admin/post/index +// a relative route: /index.php?r=admin%2Fpost%2Findex echo Url::to(['post/index']); -// an absolute route: /index.php?r=post/index +// an absolute route: /index.php?r=post%2Findex echo Url::to(['/post/index']); -// /index.php?r=post/index assume the alias "@posts" is defined as "/post/index" +// /index.php?r=post%2Findex assume the alias "@posts" is defined as "/post/index" echo Url::to(['@posts']); ``` @@ -201,7 +207,7 @@ Instead of passing an array as its first parameter, you should pass a string in ```php use yii\helpers\Url; -// currently requested URL: /index.php?r=admin/post/index +// currently requested URL: /index.php?r=admin%2Fpost%2Findex echo Url::to(); // an aliased URL: http://example.com @@ -218,7 +224,7 @@ methods. For example, ```php use yii\helpers\Url; -// home page URL: /index.php?r=site/index +// home page URL: /index.php?r=site%2Findex echo Url::home(); // the base URL, useful if the application is deployed in a sub-folder of the Web root diff --git a/docs/guide/runtime-sessions-cookies.md b/docs/guide/runtime-sessions-cookies.md index d5fab498f8..abaedfb83b 100644 --- a/docs/guide/runtime-sessions-cookies.md +++ b/docs/guide/runtime-sessions-cookies.md @@ -230,8 +230,11 @@ $alerts = $session->getFlash('alerts'); find sometimes you are getting an array while sometimes you are getting a string, depending on the order of the invocation of these two methods. +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> master > Tip: For displaying Flash messages you can use [[yii\bootstrap\Alert|bootstrap Alert]] widget in the following way: > > ```php @@ -241,7 +244,10 @@ $alerts = $session->getFlash('alerts'); > ]); > ``` +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ## Cookies @@ -250,6 +256,8 @@ maintain a collection of cookies via the property named `cookies`. The cookie co the cookies submitted in a request, while the cookie collection in the latter represents the cookies that are to be sent to the user. +The part of the application dealing with request and response directly is controller. Therefore, cookies should be +read and sent in controller. ### Reading Cookies diff --git a/docs/guide/security-auth-clients.md b/docs/guide/security-auth-clients.md deleted file mode 100644 index a1802cdcbd..0000000000 --- a/docs/guide/security-auth-clients.md +++ /dev/null @@ -1,384 +0,0 @@ -Auth Clients -============ - -Yii provides official extension that lets you authenticate and/or authorize using external services via consuming -[OpenID](http://openid.net/), [OAuth](http://oauth.net/) or [OAuth2](http://oauth.net/2/). - -Installing extension --------------------- - -In order to install extension use Composer. Either run - -``` -composer require --prefer-dist yiisoft/yii2-authclient "*" -``` - -or add - -```json -"yiisoft/yii2-authclient": "*" -``` -to the `require` section of your composer.json. - -Configuring clients -------------------- - -After extension is installed you need to setup auth client collection application component: - -```php -'components' => [ - 'authClientCollection' => [ - 'class' => 'yii\authclient\Collection', - 'clients' => [ - 'google' => [ - 'class' => 'yii\authclient\clients\GoogleOpenId' - ], - 'facebook' => [ - 'class' => 'yii\authclient\clients\Facebook', - 'clientId' => 'facebook_client_id', - 'clientSecret' => 'facebook_client_secret', - ], - // etc. - ], - ] - ... -] -``` - -Out of the box the following clients are provided: - -- [[\yii\authclient\clients\Facebook|Facebook]]. -- [[yii\authclient\clients\GitHub|GitHub]]. -- Google (via [[yii\authclient\clients\GoogleOpenId|OpenID]] and [[yii\authclient\clients\GoogleOAuth|OAuth]]). -- [[yii\authclient\clients\LinkedIn|LinkedIn]]. -- [[yii\authclient\clients\Live|Microsoft Live]]. -- [[yii\authclient\clients\Twitter|Twitter]]. -- [[yii\authclient\clients\VKontakte|VKontakte]]. -- Yandex (via [[yii\authclient\clients\YandexOpenId|OpenID]] and [[yii\authclient\clients\YandexOAuth|OAuth]]). - -Configuration for each client is a bit different. For OAuth it's required to get client ID and secret key from -the service you're going to use. For OpenID it works out of the box in most cases. - -Storing authorization data --------------------------- - -In order to recognize the user authenticated via external service we need to store ID provided on first authentication -and then check against it on subsequent authentications. It's not a good idea to limit login options to external -services only since these may fail and there won't be a way for the user to log in. Instead it's better to provide -both external authentication and good old login and password. - -If we're storing user information in a database the schema could be the following: - -```sql -CREATE TABLE user ( - id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, - username varchar(255) NOT NULL, - auth_key varchar(32) NOT NULL, - password_hash varchar(255) NOT NULL, - password_reset_token varchar(255), - email varchar(255) NOT NULL, - status smallint(6) NOT NULL DEFAULT 10, - created_at int(11) NOT NULL, - updated_at int(11) NOT NULL -); - -CREATE TABLE auth ( - id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id int(11) NOT NULL, - source string(255) NOT NULL, - source_id string(255) NOT NULL -); - -ALTER TABLE auth ADD CONSTRAINT fk-auth-user_id-user-id -FOREIGN KEY user_id REFERENCES user(id); -``` - -In the SQL above `user` is a standard table that is used in advanced application template to store user -info. Each user can authenticate using multiple external services therefore each `user` record can relate to -multiple `auth` records. In the `auth` table `source` is the name of the auth provider used and `source_id` is -unique user identificator that is provided by external service after successful login. - -Using tables created above we can generate `Auth` model. No further adjustments needed. - - -Adding action to controller ---------------------------- - -Next step is to add [[yii\authclient\AuthAction]] to a web controller. Typically `SiteController`: - -```php -class SiteController extends Controller -{ - public function actions() - { - return [ - 'auth' => [ - 'class' => 'yii\authclient\AuthAction', - 'successCallback' => [$this, 'onAuthSuccess'], - ], - ]; - } - - public function onAuthSuccess($client) - { - $attributes = $client->getUserAttributes(); - - /** @var Auth $auth */ - $auth = Auth::find()->where([ - 'source' => $client->getId(), - 'source_id' => $attributes['id'], - ])->one(); - - if (Yii::$app->user->isGuest) { - if ($auth) { // login - $user = $auth->user; - Yii::$app->user->login($user); - } else { // signup - if (isset($attributes['email']) && isset($attributes['username']) && User::find()->where(['email' => $attributes['email']])->exists()) { - Yii::$app->getSession()->setFlash('error', [ - Yii::t('app', "User with the same email as in {client} account already exists but isn't linked to it. Login using email first to link it.", ['client' => $client->getTitle()]), - ]); - } else { - $password = Yii::$app->security->generateRandomString(6); - $user = new User([ - 'username' => $attributes['login'], - 'email' => $attributes['email'], - 'password' => $password, - ]); - $user->generateAuthKey(); - $user->generatePasswordResetToken(); - $transaction = $user->getDb()->beginTransaction(); - if ($user->save()) { - $auth = new Auth([ - 'user_id' => $user->id, - 'source' => $client->getId(), - 'source_id' => (string)$attributes['id'], - ]); - if ($auth->save()) { - $transaction->commit(); - Yii::$app->user->login($user); - } else { - print_r($auth->getErrors()); - } - } else { - print_r($user->getErrors()); - } - } - } - } else { // user already logged in - if (!$auth) { // add auth provider - $auth = new Auth([ - 'user_id' => Yii::$app->user->id, - 'source' => $client->getId(), - 'source_id' => $attributes['id'], - ]); - $auth->save(); - } - } - } -} -``` - -`successCallback` method is called when user was successfully authenticated via external service. Via `$client` instance -we can retrieve information received. In our case we'd like to: - -- If user is guest and record found in auth then log this user in. -- If user is guest and record not found in auth then create new user and make a record in auth table. Then log in. -- If user is logged in and record not found in auth then try connecting additional account (save its data into auth table). - -Although, all clients are different they shares same basic interface [[yii\authclient\ClientInterface]], -which governs common API. - -Each client has some descriptive data, which can be used for different purposes: - -- `id` - unique client id, which separates it from other clients, it could be used in URLs, logs etc. -- `name` - external auth provider name, which this client is match too. Different auth clients - can share the same name, if they refer to the same external auth provider. - For example: clients for Google OpenID and Google OAuth have same name "google". - This attribute can be used inside the database, CSS styles and so on. -- `title` - user friendly name for the external auth provider, it is used to present auth client - at the view layer. - -Each auth client has different auth flow, but all of them supports `getUserAttributes()` method, -which can be invoked if authentication was successful. - -This method allows you to get information about external user account, such as ID, email address, -full name, preferred language etc. Note that for each provider fields available may vary in both existence and -names. - -Defining list of attributes, which external auth provider should return, depends on client type: - -- [[yii\authclient\OpenId]]: combination of `requiredAttributes` and `optionalAttributes`. -- [[yii\authclient\OAuth1]] and [[yii\authclient\OAuth2]]: field `scope`, note that different - providers use different formats for the scope. - -### Getting additional data via extra API calls - -Both [[yii\authclient\OAuth1]] and [[yii\authclient\OAuth2]] provide method `api()`, which -can be used to access external auth provider REST API. However this method is very basic and -it may be not enough to access full external API functionality. This method is mainly used to -fetch the external user account data. - -To use API calls, you need to setup [[yii\authclient\BaseOAuth::apiBaseUrl]] according to the -API specification. Then you can call [[yii\authclient\BaseOAuth::api()]] method: - -```php -use yii\authclient\OAuth2; - -$client = new OAuth2; - -// ... - -$client->apiBaseUrl = 'https://www.googleapis.com/oauth2/v1'; -$userInfo = $client->api('userinfo', 'GET'); -``` - -Adding widget to login view ---------------------------- - -There's ready to use [[yii\authclient\widgets\AuthChoice]] widget to use in views: - -```php - ['site/auth'], - 'popupMode' => false, -]) ?> -``` - -Creating your own auth clients ------------------------------- - -You may create your own auth client for any external auth provider, which supports -OpenId or OAuth protocol. To do so, first of all, you need to find out which protocol is -supported by the external auth provider, this will give you the name of the base class -for your extension: - - - For OAuth 2 use [[yii\authclient\OAuth2]]. - - For OAuth 1/1.0a use [[yii\authclient\OAuth1]]. - - For OpenID use [[yii\authclient\OpenId]]. - -At this stage you can determine auth client default name, title and view options, declaring -corresponding methods: - -```php -use yii\authclient\OAuth2; - -class MyAuthClient extends OAuth2 -{ - protected function defaultName() - { - return 'my_auth_client'; - } - - protected function defaultTitle() - { - return 'My Auth Client'; - } - - protected function defaultViewOptions() - { - return [ - 'popupWidth' => 800, - 'popupHeight' => 500, - ]; - } -} -``` - -Depending on actual base class, you will need to redeclare different fields and methods. - -### [[yii\authclient\OpenId]] - -All you need is to specify auth URL, by redeclaring `authUrl` field. -You may also setup default required and/or optional attributes. -For example: - -```php -use yii\authclient\OpenId; - -class MyAuthClient extends OpenId -{ - public $authUrl = 'https://www.my.com/openid/'; - - public $requiredAttributes = [ - 'contact/email', - ]; - - public $optionalAttributes = [ - 'namePerson/first', - 'namePerson/last', - ]; -} -``` - -### [[yii\authclient\OAuth2]] - -You will need to specify: - -- Auth URL by redeclaring `authUrl` field. -- Token request URL by redeclaring `tokenUrl` field. -- API base URL by redeclaring `apiBaseUrl` field. -- User attribute fetching strategy by redeclaring `initUserAttributes()` method. - -For example: - -```php -use yii\authclient\OAuth2; - -class MyAuthClient extends OAuth2 -{ - public $authUrl = 'https://www.my.com/oauth2/auth'; - - public $tokenUrl = 'https://www.my.com/oauth2/token'; - - public $apiBaseUrl = 'https://www.my.com/apis/oauth2/v1'; - - protected function initUserAttributes() - { - return $this->api('userinfo', 'GET'); - } -} -``` - -You may also specify default auth scopes. - -> Note: Some OAuth providers may not follow OAuth standards clearly, introducing - differences, and may require additional efforts to implement clients for. - -### [[yii\authclient\OAuth1]] - -You will need to specify: - -- Auth URL by redeclaring `authUrl` field. -- Request token URL by redeclaring `requestTokenUrl` field. -- Access token URL by redeclaring `accessTokenUrl` field. -- API base URL by redeclaring `apiBaseUrl` field. -- User attribute fetching strategy by redeclaring `initUserAttributes()` method. - -For example: - -```php -use yii\authclient\OAuth1; - -class MyAuthClient extends OAuth1 -{ - public $authUrl = 'https://www.my.com/oauth/auth'; - - public $requestTokenUrl = 'https://www.my.com/oauth/request_token'; - - public $accessTokenUrl = 'https://www.my.com/oauth/access_token'; - - public $apiBaseUrl = 'https://www.my.com/apis/oauth/v1'; - - protected function initUserAttributes() - { - return $this->api('userinfo', 'GET'); - } -} -``` - -You may also specify default auth scopes. - -> Note: Some OAuth providers may not follow OAuth standards clearly, introducing - differences, and may require additional efforts to implement clients for. - diff --git a/docs/guide/security-authentication.md b/docs/guide/security-authentication.md index 449d7a6415..8be7fc0b2e 100644 --- a/docs/guide/security-authentication.md +++ b/docs/guide/security-authentication.md @@ -1,19 +1,74 @@ Authentication ============== +<<<<<<< HEAD <<<<<<< HEAD > Note: This section is under development. +======= +Authentication is the process of verifying the identity of a user. It usually uses an identifier +(e.g. a username or an email address) and a secret token (e.g. a password or an access token) to judge +if the user is the one whom he claims as. Authentication is the basis of the login feature. +>>>>>>> master -Authentication is the act of verifying who a user is, and is the basis of the login process. Typically, authentication uses the combination of an identifier--a username or email address--and a password. The user submits these values through a form, and the application then compares the submitted information against that previously stored (e.g., upon registration). +Yii provides an authentication framework which wires up various components to support login. To use this framework, +you mainly need to do the following work: + +* Configure the [[yii\web\User|user]] application component; +* Create a class that implements the [[yii\web\IdentityInterface]] interface. -In Yii, this entire process is performed semi-automatically, leaving the developer to merely implement [[yii\web\IdentityInterface]], the most important class in the authentication system. Typically, implementation of `IdentityInterface` is accomplished using the `User` model. -You can find a fully featured example of authentication in the -[advanced application template](tutorial-advanced-app.md). Below, only the interface methods are listed: +## Configuring [[yii\web\User]] + +The [[yii\web\User|user]] application component manages the user authentication status. It requires you to +specify an [[yii\web\User::identityClass|identity class]] which contains the actual authentication logic. +In the following application configuration, the [[yii\web\User::identityClass|identity class]] for +[[yii\web\User|user]] is configured to be `app\models\User` whose implementation is explained in +the next subsection: + +```php +return [ + 'components' => [ + 'user' => [ + 'identityClass' => 'app\models\User', + ], + ], +]; +``` + + +## Implementing [[yii\web\IdentityInterface]] + +The [[yii\web\User::identityClass|identity class]] must implement the [[yii\web\IdentityInterface]] which contains +the following methods: + +* [[yii\web\IdentityInterface::findIdentity()|findIdentity()]]: it looks for an instance of the identity + class using the specified user ID. This method is used when you need to maintain the login status via session. +* [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]: it looks for + an instance of the identity class using the specified access token. This method is used when you need + to authenticate a user by a single secret token (e.g. in a stateless RESTful application). +* [[yii\web\IdentityInterface::getId()|getId()]]: it returns the ID of the user represented by this identity instance. +* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: it returns a key used to verify cookie-based login. + The key is stored in the login cookie and will be later compared with the server-side version to make + sure the login cookie is valid. +* [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: it implements the logic for verifying + the cookie-based login key. + +If a particular method is not needed, you may implement it with an empty body. For example, if your application +is a pure stateless RESTful application, you would only need to implement [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]] +and [[yii\web\IdentityInterface::getId()|getId()]] while leaving all other methods with an empty body. + +In the following example, an [[yii\web\User::identityClass|identity class]] is implemented as +an [Active Record](db-active-record.md) class associated with the `user` database table. ```php +>>>>>> master public static function tableName() { return 'user'; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master /** * Finds an identity by the given ID. @@ -133,26 +193,38 @@ class User extends ActiveRecord implements IdentityInterface } ``` +<<<<<<< HEAD <<<<<<< HEAD Two of the outlined methods are simple: `findIdentity` is provided with an ID value and returns a model instance associated with that ID. The `getId` method returns the ID itself. Two of the other methods – `getAuthKey` and `validateAuthKey` – are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should return a string that is unique for each user. You can reliably create a unique string using `Yii::$app->getSecurity()->generateRandomString()`. It's a good idea to also save this as part of the user's record: +======= +As explained previously, you only need to implement `getAuthKey()` and `validateAuthKey()` if your application +uses cookie-based login feature. In this case, you may use the following code to generate an auth key for each +user and store it in the `user` table: +>>>>>>> master ```php -public function beforeSave($insert) +class User extends ActiveRecord implements IdentityInterface { - if (parent::beforeSave($insert)) { - if ($this->isNewRecord) { - $this->auth_key = Yii::$app->getSecurity()->generateRandomString(); + ...... + + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + if ($this->isNewRecord) { + $this->auth_key = \Yii::$app->security->generateRandomString(); + } + return true; } - return true; + return false; } - return false; } ``` +<<<<<<< HEAD The `validateAuthKey` method just needs to compare the `$authKey` variable, passed as a parameter (itself retrieved from a cookie), with the value fetched from the database. ======= As explained previously, you only need to implement `getAuthKey()` and `validateAuthKey()` if your application @@ -177,6 +249,8 @@ class User extends ActiveRecord implements IdentityInterface } ``` +======= +>>>>>>> master > Note: Do not confuse the `User` identity class with [[yii\web\User]]. The former is the class implementing the authentication logic. It is often implemented as an [Active Record](db-active-record.md) class associated with some persistent storage for storing the user credential information. The latter is an application component @@ -251,4 +325,7 @@ The [[yii\web\User]] class raises a few events during the login and logout proce You may respond to these events to implement features such as login audit, online user statistics. For example, in the handler for [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]], you may record the login time and IP address in the `user` table. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master diff --git a/docs/guide/security-authorization.md b/docs/guide/security-authorization.md index e52f103b06..99aaeeb227 100644 --- a/docs/guide/security-authorization.md +++ b/docs/guide/security-authorization.md @@ -1,23 +1,22 @@ Authorization ============= -> Note: This section is under development. - Authorization is the process of verifying that a user has enough permission to do something. Yii provides two authorization methods: Access Control Filter (ACF) and Role-Based Access Control (RBAC). -Access Control Filter ---------------------- +## Access Control Filter -Access Control Filter (ACF) is a simple authorization method that is best used by applications that only need some -simple access control. As its name indicates, ACF is an action filter that can be attached to a controller or a module -as a behavior. ACF will check a set of [[yii\filters\AccessControl::rules|access rules]] to make sure the current user -can access the requested action. +Access Control Filter (ACF) is a simple authorization method implemented as [[yii\filters\AccessControl]] which +is best used by applications that only need some simple access control. As its name indicates, ACF is +an action [filter](structure-filters.md) that can be used in a controller or a module. While a user is requesting +to execute an action, ACF will check a list of [[yii\filters\AccessControl::rules|access rules]] +to determine if the user is allowed to access the requested action. -The code below shows how to use ACF which is implemented as [[yii\filters\AccessControl]]: +The code below shows how to use ACF in the `site` controller: ```php +use yii\web\Controller; use yii\filters\AccessControl; class SiteController extends Controller @@ -48,32 +47,39 @@ class SiteController extends Controller ``` In the code above ACF is attached to the `site` controller as a behavior. This is the typical way of using an action -filter. The `only` option specifies that the ACF should only be applied to `login`, `logout` and `signup` actions. -The `rules` option specifies the [[yii\filters\AccessRule|access rules]], which reads as follows: +filter. The `only` option specifies that the ACF should only be applied to the `login`, `logout` and `signup` actions. +All other actions in the `site` controller are not subject to the access control. The `rules` option lists +the [[yii\filters\AccessRule|access rules]], which reads as follows: -- Allow all guest (not yet authenticated) users to access 'login' and 'signup' actions. The `roles` option - contains a question mark `?` which is a special token recognized as "guests". -- Allow authenticated users to access 'logout' action. The `@` character is another special token recognized as - authenticated users. +- Allow all guest (not yet authenticated) users to access the `login` and `signup` actions. The `roles` option + contains a question mark `?` which is a special token representing "guest users". +- Allow authenticated users to access the `logout` action. The `@` character is another special token representing + "authenticated users". -When ACF performs authorization check, it will examine the rules one by one from top to bottom until it finds -a match. The `allow` value of the matching rule will then be used to judge if the user is authorized. If none -of the rules matches, it means the user is NOT authorized and ACF will stop further action execution. +ACF performs the authorization check by examining the access rules one by one from top to bottom until it finds +a rule that matches the current execution context. The `allow` value of the matching rule will then be used to +judge if the user is authorized or not. If none of the rules matches, it means the user is NOT authorized, +and ACF will stop further action execution. +<<<<<<< HEAD <<<<<<< HEAD By default, ACF does only of the followings when it determines a user is not authorized to access the current action: ======= By default, ACF does only the following when it determines a user is not authorized to access the current action: >>>>>>> yiichina/master +======= +When ACF determines a user is not authorized to access the current action, it takes the following measure by default: +>>>>>>> master -* If the user is a guest, it will call [[yii\web\User::loginRequired()]], which may redirect the browser to the login page. +* If the user is a guest, it will call [[yii\web\User::loginRequired()]] to redirect the user browser to the login page. * If the user is already authenticated, it will throw a [[yii\web\ForbiddenHttpException]]. -You may customize this behavior by configuring the [[yii\filters\AccessControl::denyCallback]] property: +You may customize this behavior by configuring the [[yii\filters\AccessControl::denyCallback]] property like the following: ```php [ 'class' => AccessControl::className(), + ... 'denyCallback' => function ($rule, $action) { throw new \Exception('You are not allowed to access this page'); } @@ -90,8 +96,8 @@ be an array of action IDs. The comparison is case-sensitive. If this option is e it means the rule applies to all actions. * [[yii\filters\AccessRule::controllers|controllers]]: specifies which controllers this rule -matches. This should be an array of controller IDs. The comparison is case-sensitive. If this option is -empty or not set, it means the rule applies to all controllers. +matches. This should be an array of controller IDs. Each controller ID is prefixed with the module ID (if any). +The comparison is case-sensitive. If this option is empty or not set, it means the rule applies to all controllers. * [[yii\filters\AccessRule::roles|roles]]: specifies which user roles that this rule matches. Two special roles are recognized, and they are checked via [[yii\web\User::isGuest]]: @@ -99,8 +105,8 @@ empty or not set, it means the rule applies to all controllers. - `?`: matches a guest user (not authenticated yet) - `@`: matches an authenticated user - Using other role names requires RBAC (to be described in the next section), and [[yii\web\User::can()]] will be called. - If this option is empty or not set, it means this rule applies to all roles. + Using other role names will trigger the invocation of [[yii\web\User::can()]], which requires enabling RBAC + (to be described in the next subsection). If this option is empty or not set, it means this rule applies to all roles. * [[yii\filters\AccessRule::ips|ips]]: specifies which [[yii\web\Request::userIP|client IP addresses]] this rule matches. An IP address can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix. @@ -152,8 +158,7 @@ class SiteController extends Controller ``` -Role based access control (RBAC) --------------------------------- +## Role Based Access Control (RBAC) Role-Based Access Control (RBAC) provides a simple yet powerful centralized access control. Please refer to the [Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control) for details about comparing RBAC @@ -168,7 +173,7 @@ part is to use the authorization data to perform access check in places where it To facilitate our description next, we will first introduce some basic RBAC concepts. -### Basic Concepts +### Basic Concepts A role represents a collection of *permissions* (e.g. creating posts, updating posts). A role may be assigned to one or multiple users. To check if a user has a specified permission, we may check if the user is assigned @@ -184,7 +189,7 @@ and a permission may consist of other permissions. Yii implements a *partial ord more special *tree* hierarchy. While a role can contain a permission, it is not true vice versa. -### Configuring RBAC Manager +### Configuring RBAC Before we set off to define authorization data and perform access checking, we need to configure the [[yii\base\Application::authManager|authManager]] application component. Yii provides two types of authorization managers: @@ -192,7 +197,8 @@ Before we set off to define authorization data and perform access checking, we n data, while the latter stores authorization data in a database. You may consider using the former if your application does not require very dynamic role and permission management. -#### configuring authManager with `PhpManager` + +#### Using `PhpManager` The following code shows how to configure the `authManager` in the application configuration using the [[yii\rbac\PhpManager]] class: @@ -210,10 +216,11 @@ return [ The `authManager` can now be accessed via `\Yii::$app->authManager`. -> Tip: By default, [[yii\rbac\PhpManager]] stores RBAC data in files under `@app/rbac/` directory. Make sure the directory - and all the files in it are writable by the Web server process if permissions hierarchy needs to be changed online. +By default, [[yii\rbac\PhpManager]] stores RBAC data in files under `@app/rbac` directory. Make sure the directory +and all the files in it are writable by the Web server process if permissions hierarchy needs to be changed online. -#### configuring authManager with `DbManager` + +#### Using `DbManager` The following code shows how to configure the `authManager` in the application configuration using the [[yii\rbac\DbManager]] class: @@ -228,6 +235,9 @@ return [ ], ]; ``` +> Note: If you are using yii2-basic-app template, there is a `config/console.php` configuration file where the + `authManager` needs to be declared additionally to `config/web.php`. +> In case of yii2-advanced-app the `authManager` should be declared only once in `common/config/main.php`. `DbManager` uses four database tables to store its data: @@ -242,7 +252,8 @@ Before you can go on you need to create those tables in the database. To do this The `authManager` can now be accessed via `\Yii::$app->authManager`. -### Building Authorization Data + +### Building Authorization Data Building authorization data is all about the following tasks: @@ -300,6 +311,9 @@ class RbacController extends Controller } ``` +> Note: If you are using advanced template, you need to put your `RbacController` inside `console/controllers` directory + and change namespace to `console/controllers`. + After executing the command with `yii rbac/init` we'll get the following hierarchy: ![Simple RBAC hierarchy](images/rbac-hierarchy-1.png "Simple RBAC hierarchy") @@ -308,10 +322,14 @@ Author can create post, admin can update post and do everything author can. If your application allows user signup you need to assign roles to these new users once. For example, in order for all <<<<<<< HEAD +<<<<<<< HEAD signed up users to become authors in your advanced application template you need to modify `frontend\models\SignupForm::signup()` ======= signed up users to become authors in your advanced project template you need to modify `frontend\models\SignupForm::signup()` >>>>>>> yiichina/master +======= +signed up users to become authors in your advanced project template you need to modify `frontend\models\SignupForm::signup()` +>>>>>>> master as follows: ```php @@ -341,7 +359,7 @@ For applications that require complex access control with dynamically updated au (i.e. admin panel) may need to be developed using APIs offered by `authManager`. -### Using Rules +### Using Rules As aforementioned, rules add additional constraint to roles and permissions. A rule is a class extending from [[yii\rbac\Rule]]. It must implement the [[yii\rbac\Rule::execute()|execute()]] method. In the hierarchy we've @@ -395,11 +413,12 @@ $auth->addChild($updateOwnPost, $updatePost); $auth->addChild($author, $updateOwnPost); ``` -Now we've got the following hierarchy: +Now we have got the following hierarchy: ![RBAC hierarchy with a rule](images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") -### Access Check + +### Access Check With the authorization data ready, access check is as simple as a call to the [[yii\rbac\ManagerInterface::checkAccess()]] method. Because most access check is about the current user, for convenience Yii provides a shortcut method @@ -411,11 +430,11 @@ if (\Yii::$app->user->can('createPost')) { } ``` -If the current user is Jane with ID=1 we're starting at `createPost` and trying to get to `Jane`: +If the current user is Jane with `ID=1` we are starting at `createPost` and trying to get to `Jane`: ![Access check](images/rbac-access-check-1.png "Access check") -In order to check if user can update post we need to pass an extra parameter that is required by the `AuthorRule` described before: +In order to check if a user can update a post, we need to pass an extra parameter that is required by `AuthorRule` described before: ```php if (\Yii::$app->user->can('updatePost', ['post' => $post])) { @@ -423,20 +442,21 @@ if (\Yii::$app->user->can('updatePost', ['post' => $post])) { } ``` -Here's what happens if current user is John: +Here is what happens if the current user is John: ![Access check](images/rbac-access-check-2.png "Access check") -We're starting with the `updatePost` and going through `updateOwnPost`. In order to pass it `AuthorRule` should return -`true` from its `execute` method. The method receives its `$params` from `can` method call so the value is -`['post' => $post]`. If everything is OK we're getting to `author` that is assigned to John. +We are starting with the `updatePost` and going through `updateOwnPost`. In order to pass the access check, `AuthorRule` +should return `true` from its `execute()` method. The method receives its `$params` from the `can()` method call so the value is +`['post' => $post]`. If everything is fine, we will get to `author` which is assigned to John. -In case of Jane it is a bit simpler since she's an admin: +In case of Jane it is a bit simpler since she is an admin: ![Access check](images/rbac-access-check-3.png "Access check") -### Using Default Roles + +### Using Default Roles A default role is a role that is *implicitly* assigned to *all* users. The call to [[yii\rbac\ManagerInterface::assign()]] is not needed, and the authorization data does not contain its assignment information. diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index 8081b5e617..87a3231272 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -139,16 +139,52 @@ from a user browser are made by the user himself. It could be false. For example, `an.example.com` website has `/logout` URL that, when accessed using a simple GET, logs user out. As long as it's requested by the user itself everything is OK but one day bad guys are somehow posting `` on a forum user visits frequently. Browser doesn't make any difference between -requesting an image or requesting a page so when user opens a page with such `img` tag he's being logged out from -`an.example.com`. +requesting an image or requesting a page so when user opens a page with such `img` tag, the browser will send the GET request to that URL, and the user will be logged out from `an.example.com`. -That's the basic idea. One can say that logging user out is nothing serious. Well, sending POST isn't much trickier. +That's the basic idea. One can say that logging user out is nothing serious, but bad guys can do much more, using this idea. Imagine that some website has an URL `http://an.example.com/purse/transfer?to=anotherUser&amout=2000`. Accessing it using GET request, causes transfer of $2000 from authorized user account to user `anotherUser`. We know, that browser will always send GET request to load an image, so we can modify code to accept only POST requests on that URL. Unfortunately, this will not save us, because an attacker can put some JavaScript code instead of `` tag, which allows to send POST requests on that URL. In order to avoid CSRF you should always: 1. Follow HTTP specification i.e. GET should not change application state. 2. Keep Yii CSRF protection enabled. +Sometimes you need to disable CSRF validation per controller and/or action. It could be achieved by setting its property: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionIndex() + { + // CSRF validation will not be applied to this and other actions + } + +} +``` + +To disable CSRF validation per custom actions you can do: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function beforeAction($action) + { + // ...set `$this->enableCsrfValidation` here based on some conditions... + // call parent method that will check CSRF if such property is true. + return parent::beforeAction($action); + } +} +``` + Avoiding file exposure ---------------------- @@ -171,3 +207,17 @@ simply rewrite code with what's generated by Gii. Debug toolbar should be avoided at production unless really necessary. It exposes all the application and config details possible. If you absolutely need it check twice that access is properly restricted to your IP only. + +Using secure connection over TLS +-------------------------------- + +Yii provides features that rely on cookies and/or PHP sessions. These can be vulnerable in case your connection is +compromised. The risk is reduced if the app uses secure connection via TLS. + +Please refer to your webserver documentation for instructions on how to configure it. You may also check example configs +provided by H5BP project: + +- [Nginx](https://github.com/h5bp/server-configs-nginx) +- [Apache](https://github.com/h5bp/server-configs-apache). +- [IIS](https://github.com/h5bp/server-configs-iis). +- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd). diff --git a/docs/guide/security-cryptography.md b/docs/guide/security-cryptography.md new file mode 100644 index 0000000000..b5818f9bfb --- /dev/null +++ b/docs/guide/security-cryptography.md @@ -0,0 +1,66 @@ +Cryptography +============ + +In this section we'll review the following security aspects: + +- Generating random data +- Encryption and Decryption +- Confirming Data Integrity + +Generating Pseudorandom Data +---------------------------- + +Pseudorandom data is useful in many situations. For example when resetting a password via email you need to generate a +token, save it to the database, and send it via email to end user which in turn will allow them to prove ownership of +that account. It is very important that this token be unique and hard to guess, else there is a possibility that attacker +can predict the token's value and reset the user's password. + +Yii security helper makes generating pseudorandom data simple: + + +```php +$key = Yii::$app->getSecurity()->generateRandomString(); +``` + +Encryption and Decryption +------------------------- + +Yii provides convenient helper functions that allow you to encrypt/decrypt data using a secret key. The data is passed through the encryption function so that only the person which has the secret key will be able to decrypt it. +For example, we need to store some information in our database but we need to make sure only the user who has the secret key can view it (even if the application database is compromised): + + +```php +// $data and $secretKey are obtained from the form +$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); +// store $encryptedData to database +``` + +Subsequently when user wants to read the data: + +```php +// $secretKey is obtained from user input, $encryptedData is from the database +$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); +``` + +It's also possible to use key instead of password via [[\yii\base\Security::encryptByKey()]] and +[[\yii\base\Security::decryptByKey()]]. + +Confirming Data Integrity +------------------------- + +There are situations in which you need to verify that your data hasn't been tampered with by a third party or even corrupted in some way. Yii provides an easy way to confirm data integrity in the form of two helper functions. + +Prefix the data with a hash generated from the secret key and data + + +```php +// $secretKey our application or user secret, $genuineData obtained from a reliable source +$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); +``` + +Checks if the data integrity has been compromised + +```php +// $secretKey our application or user secret, $data obtained from an unreliable source +$data = Yii::$app->getSecurity()->validateData($data, $secretKey); +``` diff --git a/docs/guide/security-overview.md b/docs/guide/security-overview.md new file mode 100644 index 0000000000..7903a10ac9 --- /dev/null +++ b/docs/guide/security-overview.md @@ -0,0 +1,14 @@ +Security +======== + +Good security is vital to the health and success of any application. Unfortunately, many developers cut corners when it +comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your +Yii powered application as secure as possible, Yii has included several excellent and easy to use security features. + +* [Authentication](security-authentication.md) +* [Authorization](security-authorization.md) +* [Working with Passwords](security-passwords.md) +* [Cryptography](security-cryptography.md) +* [Views security](structure-views.md#security) +* [Auth Clients](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) +* [Best Practices](security-best-practices.md) diff --git a/docs/guide/security-passwords.md b/docs/guide/security-passwords.md index 26b246a206..4bc424a6e5 100644 --- a/docs/guide/security-passwords.md +++ b/docs/guide/security-passwords.md @@ -1,12 +1,20 @@ <<<<<<< HEAD +<<<<<<< HEAD Security ======= Working with Passwords >>>>>>> yiichina/master ======== +======= +Working with Passwords +====================== +>>>>>>> master -> Note: This section is under development. +Most developers know that passwords cannot be stored in plain text, but many developers believe it's still safe to hash +passwords using `md5` or `sha1`. There was a time when using the aforementioned hashing algorithms was sufficient, +but modern hardware makes it possible to reverse such hashes and even stronger ones very quickly using brute force attacks. +<<<<<<< HEAD Good security is vital to the health and success of any application. Unfortunately, many developers cut corners when it comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your Yii powered application as secure as possible, Yii has included several excellent and easy to use security features. @@ -20,6 +28,12 @@ Hashing and Verifying Passwords Most developers know that passwords cannot be stored in plain text, but many developers believe it's still safe to hash passwords using `md5` or `sha1`. There was a time when using the aforementioned hashing algorithms was sufficient, but modern hardware makes it possible to reverse such hashes very quickly using brute force attacks. In order to provide increased security for user passwords, even in the worst case scenario (your application is breached), you need to use a hashing algorithm that is resilient against brute force attacks. The best current choice is `bcrypt`. In PHP, you can create a `bcrypt` hash using the [crypt function](http://php.net/manual/en/function.crypt.php). Yii provides two helper functions which make using `crypt` to securely generate and verify hashes easier. +======= +In order to provide increased security for user passwords, even in the worst case scenario (your application is breached), +you need to use a hashing algorithm that is resilient against brute force attacks. The best current choice is `bcrypt`. +In PHP, you can create a `bcrypt` hash using the [crypt function](http://php.net/manual/en/function.crypt.php). Yii provides +two helper functions which make using `crypt` to securely generate and verify hashes easier. +>>>>>>> master When a user provides a password for the first time (e.g., upon registration), the password needs to be hashed: @@ -40,6 +54,7 @@ if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // wrong password } ``` +<<<<<<< HEAD <<<<<<< HEAD Generating Pseudorandom data @@ -158,3 +173,5 @@ See also - [Views security](structure-views.md#security) +======= +>>>>>>> master diff --git a/docs/guide/start-databases.md b/docs/guide/start-databases.md index 57c301b2a7..7d74ff6202 100644 --- a/docs/guide/start-databases.md +++ b/docs/guide/start-databases.md @@ -8,10 +8,10 @@ and create a [view](structure-views.md). Through this tutorial, you will learn how to: -* Configure a DB connection -* Define an Active Record class -* Query data using the Active Record class -* Display data in a view in a paginated fashion +* configure a DB connection, +* define an Active Record class, +* query data using the Active Record class, +* display data in a view in a paginated fashion. Note that in order to finish this section, you should have basic knowledge and experience using databases. In particular, you should know how to create a database, and how to execute SQL statements using a DB client tool. @@ -32,16 +32,16 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `country` VALUES ('AU','Australia',18886000); -INSERT INTO `country` VALUES ('BR','Brazil',170115000); -INSERT INTO `country` VALUES ('CA','Canada',1147000); -INSERT INTO `country` VALUES ('CN','China',1277558000); -INSERT INTO `country` VALUES ('DE','Germany',82164700); -INSERT INTO `country` VALUES ('FR','France',59225700); -INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `country` VALUES ('IN','India',1013662000); -INSERT INTO `country` VALUES ('RU','Russia',146934000); -INSERT INTO `country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); ``` At this point, you have a database named `yii2basic`, and within it a `country` table with three columns, containing ten rows of data. @@ -78,6 +78,12 @@ The DB connection configured above can be accessed in the application code via t which specifies how the [application](structure-applications.md) instance should be initialized. For more information, please refer to the [Configurations](concept-configurations.md) section. +If you need to work with databases support for which isn't bundled with Yii, check the following extensions: + +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) + Creating an Active Record ------------------------- @@ -219,7 +225,7 @@ Trying it Out To see how all of the above code works, use your browser to access the following URL: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` ![Country List](images/start-country-list.png) @@ -229,7 +235,7 @@ If you click on the button "2", you will see the page display another five count Observe more carefully and you will find that the URL in the browser also changes to ``` -http://hostname/index.php?r=country/index&page=2 +http://hostname/index.php?r=country%2Findex&page=2 ``` Behind the scenes, [[yii\data\Pagination|Pagination]] is providing all of the necessary functionality to paginate a data set: @@ -251,7 +257,7 @@ Summary In this section, you learned how to work with a database. You also learned how to fetch and display data in pages with the help of [[yii\data\Pagination]] and [[yii\widgets\LinkPager]]. -In the next section, you will learn how to use the powerful code generation tool, called [Gii](tool-gii.md), +In the next section, you will learn how to use the powerful code generation tool, called [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md), to help you rapidly implement some commonly required features, such as the Create-Read-Update-Delete (CRUD) operations for working with the data in a database table. As a matter of fact, the code you have just written can all be automatically generated in Yii using the Gii tool. diff --git a/docs/guide/start-forms.md b/docs/guide/start-forms.md index 7b6e946007..bb56456320 100644 --- a/docs/guide/start-forms.md +++ b/docs/guide/start-forms.md @@ -10,9 +10,9 @@ two [views](structure-views.md), you will also create a [model](structure-models Through this tutorial, you will learn how to: -* Create a [model](structure-models.md) to represent the data entered by a user through a form -* Declare rules to validate the data entered -* Build an HTML form in a [view](structure-views.md) +* create a [model](structure-models.md) to represent the data entered by a user through a form, +* declare rules to validate the data entered, +* build an HTML form in a [view](structure-views.md). Creating a Model @@ -27,10 +27,14 @@ section for more details about the class file naming convention. namespace app\models; +<<<<<<< HEAD <<<<<<< HEAD ======= use Yii; >>>>>>> yiichina/master +======= +use Yii; +>>>>>>> master use yii\base\Model; class EntryForm extends Model @@ -190,7 +194,7 @@ Trying it Out To see how it works, use your browser to access the following URL: ``` -http://hostname/index.php?r=site/entry +http://hostname/index.php?r=site%2Fentry ``` You will see a page displaying a form with two input fields. In front of each input field, a label indicates what data is to be entered. If you click the submit button without @@ -239,7 +243,7 @@ the following code: Summary ------- -In this section of the guide, you have touched every part in the MVC design pattern. You have learned how +In this section of the guide, you have touched every part in the MVC architectural pattern. You have learned how to create a model class to represent the user data and validate said data. You have also learned how to get data from users and how to display data back in the browser. This is a task that diff --git a/docs/guide/start-gii.md b/docs/guide/start-gii.md index b65cd13ddf..81337c6614 100644 --- a/docs/guide/start-gii.md +++ b/docs/guide/start-gii.md @@ -1,25 +1,29 @@ Generating Code with Gii ======================== -This section will describe how to use [Gii](tool-gii.md) to automatically generate code +This section will describe how to use [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) to automatically generate code that implements some common Web site features. Using Gii to auto-generate code is simply a matter of entering the right information per the instructions shown on the Gii Web pages. Through this tutorial, you will learn how to: -* Enable Gii in your application -* Use Gii to generate an Active Record class -* Use Gii to generate the code implementing the CRUD operations for a DB table -* Customize the code generated by Gii +* enable Gii in your application, +* use Gii to generate an Active Record class, +* use Gii to generate the code implementing the CRUD operations for a DB table, +* customize the code generated by Gii. Starting Gii ------------ +<<<<<<< HEAD <<<<<<< HEAD [Gii](tool-gii.md) is provided in Yii as a [module](structure-modules.md). You can enable Gii ======= [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) is provided in Yii as a [module](structure-modules.md). You can enable Gii >>>>>>> yiichina/master +======= +[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) is provided in Yii as a [module](structure-modules.md). You can enable Gii +>>>>>>> master by configuring it in the [[yii\base\Application::modules|modules]] property of the application. Depending upon how you created your application, you may find the following code is already provided in the `config/web.php` configuration file: ```php @@ -27,7 +31,9 @@ $config = [ ... ]; if (YII_ENV_DEV) { $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } ``` @@ -109,7 +115,7 @@ Trying it Out To see how it works, use your browser to access the following URL: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` You will see a data grid showing the countries from the database table. You may sort the grid, @@ -131,7 +137,7 @@ or to customize them: > Info: Gii is designed to be a highly customizable and extensible code generation tool. Using it wisely can greatly accelerate your application development speed. For more details, please refer to - the [Gii](tool-gii.md) section. + the [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) section. Summary diff --git a/docs/guide/start-hello.md b/docs/guide/start-hello.md index 8767b6d0a1..c812001341 100644 --- a/docs/guide/start-hello.md +++ b/docs/guide/start-hello.md @@ -10,7 +10,7 @@ a [view](structure-views.md): Through this tutorial, you will learn three things: -1. How to create an [action](structure-controllers.md) to respond to requests, +1. how to create an [action](structure-controllers.md#creating-actions) to respond to requests, 2. how to create a [view](structure-views.md) to compose the response's content, and 3. how an application dispatches requests to [actions](structure-controllers.md#creating-actions). @@ -102,7 +102,7 @@ Trying it Out After creating the action and the view, you may access the new page by accessing the following URL: ``` -http://hostname/index.php?r=site/say&message=Hello+World +http://hostname/index.php?r=site%2Fsay&message=Hello+World ``` ![Hello World](images/start-hello-world.png) @@ -134,7 +134,7 @@ the `SiteController::actionSay()` method will be called to handle the request. Summary ------- -In this section, you have touched the controller and view parts of the MVC design pattern. +In this section, you have touched the controller and view parts of the MVC architectural pattern. You created an action as part of a controller to handle a specific request. And you also created a view to compose the response's content. In this simple example, no model was involved as the only data used was the `message` parameter. diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md index faad367ad7..1bd9928381 100644 --- a/docs/guide/start-installation.md +++ b/docs/guide/start-installation.md @@ -1,19 +1,24 @@ Installing Yii ============== +<<<<<<< HEAD <<<<<<< HEAD You can install Yii in two ways, using the [Composer](http://getcomposer.org/) package manager or by downloading an archive file. +======= +You can install Yii in two ways, using the [Composer](https://getcomposer.org/) package manager or by downloading an archive file. +>>>>>>> master The former is the preferred way, as it allows you to install new [extensions](structure-extensions.md) or update Yii by simply running a single command. -Standard installations of Yii result in both the framework and an application template being downloaded and installed. -An application template is a working Yii application implementing some basic features, such as login, contact form, etc. +Standard installations of Yii result in both the framework and a project template being downloaded and installed. +A project template is a working Yii project implementing some basic features, such as login, contact form, etc. Its code is organized in a recommended way. Therefore, it can serve as a good starting point for your projects. -In this and the next few sections, we will describe how to install Yii with the so-called *Basic Application Template* and +In this and the next few sections, we will describe how to install Yii with the so-called *Basic Project Template* and how to implement new features on top of this template. Yii also provides another template called -the [Advanced Application Template](tutorial-advanced-app.md) which is better used in a team development environment +the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) which is better used in a team development environment to develop applications with multiple tiers. +<<<<<<< HEAD > Info: The Basic Application Template is suitable for developing 90 percent of Web applications. It differs from the Advanced Application Template mainly in how their code is organized. If you are new to Yii, we strongly recommend you stick to the Basic Application Template for its simplicity yet sufficient functionalities. @@ -34,6 +39,11 @@ to develop applications with multiple tiers. from the Advanced Project Template mainly in how their code is organized. If you are new to Yii, we strongly recommend you stick to the Basic Project Template for its simplicity yet sufficient functionalities. >>>>>>> yiichina/master +======= +> Info: The Basic Project Template is suitable for developing 90 percent of Web applications. It differs + from the Advanced Project Template mainly in how their code is organized. If you are new to Yii, we strongly + recommend you stick to the Basic Project Template for its simplicity yet sufficient functionalities. +>>>>>>> master Installing via Composer @@ -42,12 +52,19 @@ Installing via Composer If you do not already have Composer installed, you may do so by following the instructions at [getcomposer.org](https://getcomposer.org/download/). On Linux and Mac OS X, you'll run the following commands: +<<<<<<< HEAD <<<<<<< HEAD curl -s http://getcomposer.org/installer | php ======= curl -sSS https://getcomposer.org/installer | php >>>>>>> yiichina/master mv composer.phar /usr/local/bin/composer +======= +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` +>>>>>>> master On Windows, you'll download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). @@ -59,12 +76,19 @@ by running `composer self-update`. With Composer installed, you can install Yii by running the following commands under a Web-accessible folder: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master composer create-project --prefer-dist yiisoft/yii2-app-basic basic +======= +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` +>>>>>>> master The first command installs the [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) which allows managing bower and npm package dependencies through Composer. You only need to run this command @@ -77,7 +101,9 @@ once for all. The second command installs Yii in a directory named `basic`. You > Tip: If you want to install the latest development version of Yii, you may use the following command instead, > which adds a [stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability): > -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` > > Note that the development version of Yii should not be used for production as it may break your running code. @@ -110,41 +136,57 @@ But there are other installation options available: * If you only want to install the core framework and would like to build an entire application from scratch, you may follow the instructions as explained in [Building Application from Scratch](tutorial-start-from-scratch.md). * If you want to start with a more sophisticated application, better suited to team development environments, +<<<<<<< HEAD <<<<<<< HEAD you may consider installing the [Advanced Application Template](tutorial-advanced-app.md). ======= you may consider installing the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). >>>>>>> yiichina/master +======= + you may consider installing the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). +>>>>>>> master Verifying the Installation -------------------------- -After installation, you can use your browser to access the installed Yii application with the following URL: - -``` -http://localhost/basic/web/index.php +After installation is done, either configure your web server (see next section) or use the +[built-in PHP web server](https://secure.php.net/manual/en/features.commandline.webserver.php) by running the following +console command while in the project `web` directory: + +```bash +php yii serve ``` -This URL assumes you have installed Yii in a directory named `basic`, directly under the Web server's document root directory, -and that the Web server is running on your local machine (`localhost`). You may need to adjust it to your installation environment. +> Note: By default the HTTP-server will listen to port 8080. However if that port is already in use or you wish to +serve multiple applications this way, you might want to specify what port to use. Just add the --port argument: + +```bash +php yii serve --port=8888 +``` + +You can use your browser to access the installed Yii application with the following URL: + +``` +http://localhost:8080/ +``` ![Successful Installation of Yii](images/start-app-installed.png) You should see the above "Congratulations!" page in your browser. If not, please check if your PHP installation satisfies Yii's requirements. You can check if the minimum requirements are met using one of the following approaches: -* Use a browser to access the URL `http://localhost/basic/requirements.php` +* Copy `/requirements.php` to `/web/requirements.php` and then use a browser to access it via `http://localhost/requirements.php` * Run the following commands: - ``` + ```bash cd basic php requirements.php ``` -You should configure your PHP installation so that it meets the minimum requirements of Yii. Most importantly, you should have PHP 5.4 or above. You should also install -the [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php) and a corresponding database driver -(such as `pdo_mysql` for MySQL databases), if your application needs a database. +You should configure your PHP installation so that it meets the minimum requirements of Yii. Most importantly, you +should have PHP 5.4 or above. You should also install the [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php) +and a corresponding database driver (such as `pdo_mysql` for MySQL databases), if your application needs a database. Configuring Web Servers @@ -179,7 +221,7 @@ the [Shared Hosting Environment](tutorial-shared-hosting.md) section for more de Use the following configuration in Apache's `httpd.conf` file or within a virtual host configuration. Note that you should replace `path/to/basic/web` with the actual path for `basic/web`. -``` +```apache # Set document root to be "basic/web" DocumentRoot "path/to/basic/web" @@ -203,7 +245,7 @@ To use [Nginx](http://wiki.nginx.org/), you should install PHP as an [FPM SAPI]( You may use the following Nginx configuration, replacing `path/to/basic/web` with the actual path for `basic/web` and `mysite.local` with the actual hostname to serve. -``` +```nginx server { charset utf-8; client_max_body_size 128M; @@ -220,7 +262,7 @@ server { location / { # Redirect everything that isn't a real file to index.php - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # uncomment to avoid processing of calls to non-existing static files by Yii @@ -231,7 +273,7 @@ server { location ~ \.php$ { include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; diff --git a/docs/guide/start-looking-ahead.md b/docs/guide/start-looking-ahead.md index 89adb7c639..78cbce422f 100644 --- a/docs/guide/start-looking-ahead.md +++ b/docs/guide/start-looking-ahead.md @@ -3,7 +3,7 @@ Looking Ahead If you've read through the entire "Getting Started" chapter, you have now created a complete Yii application. In the process, you have learned how to implement some commonly needed features, such as getting data from users via an HTML form, fetching data from a database, and -displaying data in a paginated fashion. You have also learned how to use [Gii](tool-gii.md) to generate +displaying data in a paginated fashion. You have also learned how to use [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) to generate code automatically. Using Gii for code generation turns the bulk of your Web development process into a task as simple as just filling out some forms. This section will summarize the Yii resources available to help you be more productive when using the framework. @@ -27,6 +27,7 @@ This section will summarize the Yii resources available to help you be more prod * Community - Forum: - IRC chat: The #yii channel on the freenode network () + - Gitter chat: - GitHub: - Facebook: - Twitter: diff --git a/docs/guide/start-workflow.md b/docs/guide/start-workflow.md index 00645a7905..335526b56d 100644 --- a/docs/guide/start-workflow.md +++ b/docs/guide/start-workflow.md @@ -11,11 +11,17 @@ how the code is organized, and how the application handles requests in general. your application to be `http://hostname/index.php` or something similar. For your needs, please adjust the URLs in our descriptions accordingly. <<<<<<< HEAD +<<<<<<< HEAD ======= Note that unlike framework itself, after project template is installed it's all yours. You're free to add or delete code and overall modify it as you need. >>>>>>> yiichina/master +======= + +Note that unlike framework itself, after project template is installed it's all yours. You're free to add or delete +code and overall modify it as you need. +>>>>>>> master Functionality @@ -23,7 +29,7 @@ Functionality The basic application installed contains four pages: -* The homepage, displayed when you access the URL `http://hostname/index.php`, +* the homepage, displayed when you access the URL `http://hostname/index.php`, * the "About" page, * the "Contact" page, which displays a contact form that allows end users to contact you via email, * and the "Login" page, which displays a login form that can be used to authenticate end users. Try logging in @@ -33,15 +39,20 @@ These pages share a common header and footer. The header contains a main menu ba among different pages. You should also see a toolbar at the bottom of the browser window. -This is a useful [debugger tool](tool-debugger.md) provided by Yii to record and display a lot of debugging information, such as log messages, response statuses, the database queries run, and so on. +This is a useful [debugger tool](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) provided by Yii to record and display a lot of debugging information, such as log messages, response statuses, the database queries run, and so on. Additionally to the web application, there is a console script called `yii`, which is located in the applications base directory. +<<<<<<< HEAD This script can be used to run background and maintainance tasks for the application, which are described <<<<<<< HEAD in the [Console Application Section](tutoral-console.md). ======= in the [Console Application Section](tutorial-console.md). >>>>>>> yiichina/master +======= +This script can be used to run background and maintenance tasks for the application, which are described +in the [Console Application Section](tutorial-console.md). +>>>>>>> master Application Structure @@ -70,7 +81,7 @@ basic/ application base path In general, the files in the application can be divided into two types: those under `basic/web` and those under other directories. The former can be directly accessed via HTTP (i.e., in a browser), while the latter can not and should not be. -Yii implements the [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) design pattern, +Yii implements the [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) architectural pattern, which is reflected in the above directory organization. The `models` directory contains all [model classes](structure-models.md), the `views` directory contains all [view scripts](structure-views.md), and the `controllers` directory contains all [controller classes](structure-controllers.md). diff --git a/docs/guide/structure-application-components.md b/docs/guide/structure-application-components.md index 103713b6ac..e45331e742 100644 --- a/docs/guide/structure-application-components.md +++ b/docs/guide/structure-application-components.md @@ -96,11 +96,15 @@ if you do not specify its class, the default one will be used. Please refer to the [Handling Errors](runtime-handling-errors.md) section for more details. * [[yii\i18n\Formatter|formatter]]: formats data when they are displayed to end users. For example, a number may be displayed with thousand separator, a date may be formatted in long format. +<<<<<<< HEAD <<<<<<< HEAD Please refer to the [Data Formatting](output-formatter.md) section for more details. ======= Please refer to the [Data Formatting](output-formatting.md) section for more details. >>>>>>> yiichina/master +======= + Please refer to the [Data Formatting](output-formatting.md) section for more details. +>>>>>>> master * [[yii\i18n\I18N|i18n]]: supports message translation and formatting. Please refer to the [Internationalization](tutorial-i18n.md) section for more details. * [[yii\log\Dispatcher|log]]: manages log targets. diff --git a/docs/guide/structure-applications.md b/docs/guide/structure-applications.md index 276f22af71..53f9ca3159 100644 --- a/docs/guide/structure-applications.md +++ b/docs/guide/structure-applications.md @@ -10,13 +10,13 @@ the [entry script](structure-entry-scripts.md) and is globally accessible throug There are two types of applications: [[yii\web\Application|Web applications]] and [[yii\console\Application|console applications]]. As the names indicate, the former mainly handles -Web requests while the latter console command requests. +Web requests, while the latter handles console command requests. ## Application Configurations When an [entry script](structure-entry-scripts.md) creates an application, it will load -a [configuration](concept-configurations.md) and apply it to the application, like the following: +a [configuration](concept-configurations.md) and apply it to the application, as follows: ```php require(__DIR__ . '/../vendor/autoload.php'); @@ -53,14 +53,14 @@ and [[yii\base\Application::basePath|basePath]]. The [[yii\base\Application::id|id]] property specifies a unique ID that differentiates an application from others. It is mainly used programmatically. Although not a requirement, for best interoperability -it is recommended that you use alphanumeric characters only when specifying an application ID. +it is recommended that you use only alphanumeric characters when specifying an application ID. #### [[yii\base\Application::basePath|basePath]] The [[yii\base\Application::basePath|basePath]] property specifies the root directory of an application. It is the directory that contains all protected source code of an application system. Under this directory, -you normally will see sub-directories such as `models`, `views`, `controllers`, which contain source code +you normally will see sub-directories such as `models`, `views`, and `controllers`, which contain source code corresponding to the MVC pattern. You may configure the [[yii\base\Application::basePath|basePath]] property using a directory path @@ -82,7 +82,7 @@ different applications. This property allows you to define a set of [aliases](concept-aliases.md) in terms of an array. The array keys are alias names, and the array values are the corresponding path definitions. -For example, +For example: ```php [ @@ -93,8 +93,8 @@ For example, ] ``` -This property is provided such that you can define aliases in terms of application configurations instead of -the method calls [[Yii::setAlias()]]. +This property is provided so that you can define aliases in terms of application configurations instead of +by calling the [[Yii::setAlias()]] method. #### [[yii\base\Application::bootstrap|bootstrap]] @@ -106,13 +106,13 @@ you may list its ID as an element in this property. Each component listed in this property may be specified in one of the following formats: -- an application component ID as specified via [components](#components). -- a module ID as specified via [modules](#modules). -- a class name. -- a configuration array. +- an application component ID as specified via [components](#components), +- a module ID as specified via [modules](#modules), +- a class name, +- a configuration array, - an anonymous function that creates and returns a component. -For example, +For example: ```php [ @@ -138,28 +138,33 @@ For example, ``` > Info: If a module ID is the same as an application component ID, the application component will be used during - the bootstrapping process. If you want to use the module instead, you may specify it using an anonymous function - like the following: +> the bootstrapping process. If you want to use the module instead, you may specify it using an anonymous function +> like the following: +> > ```php -[ - function () { - return Yii::$app->getModule('user'); - }, -] -``` +> [ +> function () { +> return Yii::$app->getModule('user'); +> }, +> ] +> ``` During the bootstrapping process, each component will be instantiated. If the component class implements [[yii\base\BootstrapInterface]], its [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]] method will also be called. +<<<<<<< HEAD <<<<<<< HEAD Another practical example is in the application configuration for the [Basic Application Template](start-installation.md), ======= Another practical example is in the application configuration for the [Basic Project Template](start-installation.md), >>>>>>> yiichina/master +======= +Another practical example is in the application configuration for the [Basic Project Template](start-installation.md), +>>>>>>> master where the `debug` and `gii` modules are configured as bootstrapping components when the application is running -in development environment, +in the development environment: ```php if (YII_ENV_DEV) { @@ -183,7 +188,7 @@ a [controller action](structure-controllers.md) which should handle all user req used when the application is in maintenance mode and needs to handle all incoming requests via a single action. The configuration is an array whose first element specifies the route of the action. -The rest of the array elements (key-value pairs) specify the parameters to be bound to the action. For example, +The rest of the array elements (key-value pairs) specify the parameters to be bound to the action. For example: ```php [ @@ -195,11 +200,12 @@ The rest of the array elements (key-value pairs) specify the parameters to be bo ] ``` +> Info: Debug panel on development environment will not work when this property is enabled #### [[yii\base\Application::components|components]] This is the single most important property. It allows you to register a list of named components -called [application components](structure-application-components.md) that you can use in other places. For example, +called [application components](structure-application-components.md) that you can use in other places. For example: ```php [ @@ -219,7 +225,7 @@ Each application component is specified as a key-value pair in the array. The ke while the value represents the component class name or [configuration](concept-configurations.md). You can register any component with an application, and the component can later be accessed globally -using the expression `\Yii::$app->ComponentID`. +using the expression `\Yii::$app->componentID`. Please read the [Application Components](structure-application-components.md) section for details. @@ -262,7 +268,7 @@ be `app\controllers\admin\PostController`. It is important that the fully qualified controller classes should be [autoloadable](concept-autoloading.md) and the actual namespace of your controller classes match the value of this property. Otherwise, -you will receive "Page Not Found" error when accessing the application. +you will receive a "Page Not Found" error when accessing the application. In case you want to break the convention as described above, you may configure the [controllerMap](#controllerMap) property. @@ -277,7 +283,7 @@ if your application needs to support multiple languages. The value of this property determines various [internationalization](tutorial-i18n.md) aspects, including message translation, date formatting, number formatting, etc. For example, the [[yii\jui\DatePicker]] widget will use this property value by default to determine in which language the calendar should be displayed and how -should the date be formatted. +the date should be formatted. It is recommended that you specify a language in terms of an [IETF language tag](http://en.wikipedia.org/wiki/IETF_language_tag). For example, `en` stands for English, while `en-US` stands for English (United States). @@ -290,7 +296,7 @@ More details about this property can be found in the [Internationalization](tuto This property specifies the [modules](structure-modules.md) that the application contains. The property takes an array of module classes or [configurations](concept-configurations.md) with the array keys -being the module IDs. For example, +being the module IDs. For example: ```php [ @@ -313,8 +319,8 @@ Please refer to the [Modules](structure-modules.md) section for more details. #### [[yii\base\Application::name|name]] This property specifies the application name that may be displayed to end users. Unlike the -[[yii\base\Application::id|id]] property which should take a unique value, the value of this property is mainly for -display purpose and does not need to be unique. +[[yii\base\Application::id|id]] property, which should take a unique value, the value of this property is mainly for +display purposes; it does not need to be unique. You do not always need to configure this property if none of your code is using it. @@ -334,15 +340,15 @@ image size as a parameter like the following: ] ``` -Then in your code where you need to use the size value, you can simply use the code like the following: +Then in your code where you need to use the size value, you can simply use code like the following: ```php $size = \Yii::$app->params['thumbnail.size']; $width = \Yii::$app->params['thumbnail.size'][0]; ``` -Later if you decide to change the thumbnail size, you only need to modify it in the application configuration -without touching any dependent code. +Later if you decide to change the thumbnail size, you only need to modify it in the application configuration; +you don't need to touch any dependent code. #### [[yii\base\Application::sourceLanguage|sourceLanguage]] @@ -359,9 +365,9 @@ More details about this property can be found in the [Internationalization](tuto #### [[yii\base\Application::timeZone|timeZone]] -This property is provided as an alternative way of setting the default time zone of PHP runtime. +This property is provided as an alternative way of setting the default time zone of the PHP runtime. By configuring this property, you are essentially calling the PHP function -[date_default_timezone_set()](http://php.net/manual/en/function.date-default-timezone-set.php). For example, +[date_default_timezone_set()](http://php.net/manual/en/function.date-default-timezone-set.php). For example: ```php [ @@ -372,28 +378,28 @@ By configuring this property, you are essentially calling the PHP function #### [[yii\base\Application::version|version]] -This property specifies the version of the application. It defaults to `'1.0'`. You do not always need to configure +This property specifies the version of the application. It defaults to `'1.0'`. You do not need to configure this property if none of your code is using it. ### Useful Properties The properties described in this subsection are not commonly configured because their default values -stipulate common conventions. However, you may still configure them in case you want to break the conventions. +derive from common conventions. However, you may still configure them in case you want to break the conventions. #### [[yii\base\Application::charset|charset]] -This property specifies the charset that the application uses. The default value is `'UTF-8'` which should -be kept as is for most applications unless you are working with some legacy systems that use a lot of non-unicode data. +This property specifies the charset that the application uses. The default value is `'UTF-8'`, which should +be kept as-is for most applications unless you are working with a legacy system that uses a lot of non-Unicode data. #### [[yii\base\Application::defaultRoute|defaultRoute]] This property specifies the [route](runtime-routing.md) that an application should use when a request -does not specify one. The route may consist of child module ID, controller ID, and/or action ID. -For example, `help`, `post/create`, `admin/post/create`. If action ID is not given, it will take the default -value as specified in [[yii\base\Controller::defaultAction]]. +does not specify one. The route may consist of a child module ID, a controller ID, and/or an action ID. +For example, `help`, `post/create`, or `admin/post/create`. If an action ID is not given, this property will take +the default value specified in [[yii\base\Controller::defaultAction]]. For [[yii\web\Application|Web applications]], the default value of this property is `'site'`, which means the `SiteController` controller and its default action should be used. As a result, if you access @@ -409,13 +415,17 @@ without providing any arguments, it will display the help information. This property specifies the list of [extensions](structure-extensions.md) that are installed and used by the application. By default, it will take the array returned by the file `@vendor/yiisoft/extensions.php`. The `extensions.php` file <<<<<<< HEAD +<<<<<<< HEAD is generated and maintained automatically when you use [Composer](http://getcomposer.org) to install extensions. ======= is generated and maintained automatically when you use [Composer](https://getcomposer.org) to install extensions. >>>>>>> yiichina/master +======= +is generated and maintained automatically when you use [Composer](https://getcomposer.org) to install extensions. +>>>>>>> master So in most cases, you do not need to configure this property. -In the special case when you want to maintain extensions manually, you may configure this property like the following: +In the special case when you want to maintain extensions manually, you may configure this property as follows: ```php [ @@ -463,14 +473,14 @@ You may configure it as a directory or a path [alias](concept-aliases.md). #### [[yii\base\Application::runtimePath|runtimePath]] -This property specifies the path where temporary files, such as log files, cache files, can be generated. +This property specifies the path where temporary files, such as log files and cache files, can be generated. The default value is the directory represented by the alias `@app/runtime`. You may configure it as a directory or a path [alias](concept-aliases.md). Note that the runtime path must be writable by the process running the application. And the path should be protected from being accessed -by end users because the temporary files under it may contain sensitive information. +by end users, because the temporary files under it may contain sensitive information. -To simplify accessing to this path, Yii has predefined a path alias named `@runtime` for it. +To simplify access to this path, Yii has predefined a path alias named `@runtime` for it. #### [[yii\base\Application::viewPath|viewPath]] @@ -481,18 +491,22 @@ represented by the alias `@app/views`. You may configure it as a directory or a #### [[yii\base\Application::vendorPath|vendorPath]] +<<<<<<< HEAD <<<<<<< HEAD This property specifies the vendor directory managed by [Composer](http://getcomposer.org). It contains ======= This property specifies the vendor directory managed by [Composer](https://getcomposer.org). It contains >>>>>>> yiichina/master +======= +This property specifies the vendor directory managed by [Composer](https://getcomposer.org). It contains +>>>>>>> master all third party libraries used by your application, including the Yii framework. The default value is the directory represented by the alias `@app/vendor`. You may configure this property as a directory or a path [alias](concept-aliases.md). When you modify this property, make sure you also adjust the Composer configuration accordingly. -To simplify accessing to this path, Yii has predefined a path alias named `@vendor` for it. +To simplify access to this path, Yii has predefined a path alias named `@vendor` for it. #### [[yii\console\Application::enableCoreCommands|enableCoreCommands]] @@ -503,8 +517,8 @@ whether the core commands included in the Yii release should be enabled. The def ## Application Events -An application triggers several events during the lifecycle of handling an request. You may attach event -handlers to these events in application configurations like the following, +An application triggers several events during the lifecycle of handling a request. You may attach event +handlers to these events in application configurations as follows: ```php [ @@ -518,7 +532,7 @@ The use of the `on eventName` syntax is described in the [Configurations](concep section. Alternatively, you may attach event handlers during the [bootstrapping process](runtime-bootstrapping.md) -after the application instance is created. For example, +after the application instance is created. For example: ```php \Yii::$app->on(\yii\base\Application::EVENT_BEFORE_REQUEST, function ($event) { @@ -554,7 +568,7 @@ The actual event name is `beforeAction`. The event parameter is an instance of [[yii\base\ActionEvent]]. An event handler may set the [[yii\base\ActionEvent::isValid]] property to be `false` to stop running the action. -For example, +For example: ```php [ @@ -570,7 +584,7 @@ For example, Note that the same `beforeAction` event is also triggered by [modules](structure-modules.md) and [controllers](structure-controllers.md). Application objects are the first ones triggering this event, followed by modules (if any), and finally controllers. If an event handler -sets [[yii\base\ActionEvent::isValid]] to be `false`, all the following events will NOT be triggered. +sets [[yii\base\ActionEvent::isValid]] to be `false`, all of the subsequent events will NOT be triggered. ### [[yii\base\Application::EVENT_AFTER_ACTION|EVENT_AFTER_ACTION]] @@ -580,7 +594,7 @@ The actual event name is `afterAction`. The event parameter is an instance of [[yii\base\ActionEvent]]. Through the [[yii\base\ActionEvent::result]] property, an event handler may access or modify the action result. -For example, +For example: ```php [ @@ -617,7 +631,7 @@ an application will undergo the following lifecycle: 3. The entry script calls [[yii\base\Application::run()]] to run the application: * Trigger the [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] event. * Handle the request: resolve the request into a [route](runtime-routing.md) and the associated parameters; - create the module, controller and action objects as specified by the route; and run the action. + create the module, controller, and action objects as specified by the route; and run the action. * Trigger the [[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]] event. * Send response to the end user. 4. The entry script receives the exit status from the application and completes the request processing. diff --git a/docs/guide/structure-assets.md b/docs/guide/structure-assets.md index 51ab8ca541..10b176b07d 100644 --- a/docs/guide/structure-assets.md +++ b/docs/guide/structure-assets.md @@ -25,11 +25,15 @@ its corresponding fully qualified PHP class name (without the leading backslash) be [autoloadable](concept-autoloading.md). It usually specifies where the assets are located, what CSS and JavaScript files the bundle contains, and how the bundle depends on other bundles. +<<<<<<< HEAD <<<<<<< HEAD The following code defines the main asset bundle used by [the basic application template](start-installation.md): ======= The following code defines the main asset bundle used by [the basic project template](start-installation.md): >>>>>>> yiichina/master +======= +The following code defines the main asset bundle used by [the basic project template](start-installation.md): +>>>>>>> master ```php publishOptions['beforeCopy'] = function ($from, $to) { - $dirname = basename(dirname($from)); - return $dirname === 'fonts' || $dirname === 'css'; - }; - } + ]; + public $publishOptions = [ + 'only' => [ + 'fonts/', + 'css/', + ] + ]; } ``` The above example defines an asset bundle for the ["fontawesome" package](http://fontawesome.io/). By specifying -the `beforeCopy` publishing option, only the `fonts` and `css` subdirectories will be published. +the `only` publishing option, only the `fonts` and `css` subdirectories will be published. ### Bower and NPM Assets diff --git a/docs/guide/structure-controllers.md b/docs/guide/structure-controllers.md index 4290493c03..67e9d58d81 100644 --- a/docs/guide/structure-controllers.md +++ b/docs/guide/structure-controllers.md @@ -67,9 +67,9 @@ the `create` view through which users can provide the needed input. End users address actions through the so-called *routes*. A route is a string that consists of the following parts: * a module ID: this exists only if the controller belongs to a non-application [module](structure-modules.md); -* a controller ID: a string that uniquely identifies the controller among all controllers within the same application +* a [controller ID](#controller-ids): a string that uniquely identifies the controller among all controllers within the same application (or the same module if the controller belongs to a module); -* an action ID: a string that uniquely identifies the action among all actions within the same controller. +* an [action ID](#action-ids): a string that uniquely identifies the action among all actions within the same controller. Routes take the following format: @@ -112,55 +112,59 @@ For this reason, controller IDs are often nouns referring to the types of the re For example, you may use `article` as the ID of a controller that handles article data. By default, controller IDs should contain these characters only: English letters in lower case, digits, -underscores, dashes and forward slashes. For example, `article` and `post-comment` are both valid controller IDs, +underscores, hyphens, and forward slashes. For example, `article` and `post-comment` are both valid controller IDs, while `article?`, `PostComment`, `admin\post` are not. A controller ID may also contain a subdirectory prefix. For example, `admin/article` stands for an `article` controller in the `admin` subdirectory under the [[yii\base\Application::controllerNamespace|controller namespace]]. -Valid characters for subdirectory prefixes include: English letters in lower and upper cases, digits, underscores and +Valid characters for subdirectory prefixes include: English letters in lower and upper cases, digits, underscores, and forward slashes, where forward slashes are used as separators for multi-level subdirectories (e.g. `panels/admin`). ### Controller Class Naming -Controller class names can be derived from controller IDs according to the following rules: +Controller class names can be derived from controller IDs according to the following procedure: -* Turn the first letter in each word separated by dashes into upper case. Note that if the controller ID +1. Turn the first letter in each word separated by hyphens into upper case. Note that if the controller ID contains slashes, this rule only applies to the part after the last slash in the ID. -* Remove dashes and replace any forward slashes with backward slashes. -* Append the suffix `Controller`. -* And prepend the [[yii\base\Application::controllerNamespace|controller namespace]]. +2. Remove hyphens and replace any forward slashes with backward slashes. +3. Append the suffix `Controller`. +4. Prepend the [[yii\base\Application::controllerNamespace|controller namespace]]. +<<<<<<< HEAD <<<<<<< HEAD The followings are some examples, assuming the [[yii\base\Application::controllerNamespace|controller namespace]] ======= The following are some examples, assuming the [[yii\base\Application::controllerNamespace|controller namespace]] >>>>>>> yiichina/master +======= +The following are some examples, assuming the [[yii\base\Application::controllerNamespace|controller namespace]] +>>>>>>> master takes the default value `app\controllers`: -* `article` derives `app\controllers\ArticleController`; -* `post-comment` derives `app\controllers\PostCommentController`; -* `admin/post-comment` derives `app\controllers\admin\PostCommentController`; -* `adminPanels/post-comment` derives `app\controllers\adminPanels\PostCommentController`. +* `article` becomes `app\controllers\ArticleController`; +* `post-comment` becomes `app\controllers\PostCommentController`; +* `admin/post-comment` becomes `app\controllers\admin\PostCommentController`; +* `adminPanels/post-comment` becomes `app\controllers\adminPanels\PostCommentController`. Controller classes must be [autoloadable](concept-autoloading.md). For this reason, in the above examples, the `article` controller class should be saved in the file whose [alias](concept-aliases.md) -is `@app/controllers/ArticleController.php`; while the `admin/post2-comment` controller should be -in `@app/controllers/admin/Post2CommentController.php`. +is `@app/controllers/ArticleController.php`; while the `admin/post-comment` controller should be +in `@app/controllers/admin/PostCommentController.php`. -> Info: The last example `admin/post2-comment` shows how you can put a controller under a sub-directory +> Info: The last example `admin/post-comment` shows how you can put a controller under a sub-directory of the [[yii\base\Application::controllerNamespace|controller namespace]]. This is useful when you want to organize your controllers into several categories and you do not want to use [modules](structure-modules.md). ### Controller Map -You can configure [[yii\base\Application::controllerMap|controller map]] to overcome the constraints -of the controller IDs and class names described above. This is mainly useful when you are using some -third-party controllers which you do not have control over their class names. +You can configure the [[yii\base\Application::controllerMap|controller map]] to overcome the constraints +of the controller IDs and class names described above. This is mainly useful when you are using +third-party controllers and you do not have control over their class names. -You may configure [[yii\base\Application::controllerMap|controller map]] in the -[application configuration](structure-applications.md#application-configurations) like the following: +You may configure the [[yii\base\Application::controllerMap|controller map]] in the +[application configuration](structure-applications.md#application-configurations). For example: ```php [ @@ -183,7 +187,7 @@ You may configure [[yii\base\Application::controllerMap|controller map]] in the Each application has a default controller specified via the [[yii\base\Application::defaultRoute]] property. When a request does not specify a [route](#routes), the route specified by this property will be used. For [[yii\web\Application|Web applications]], its value is `'site'`, while for [[yii\console\Application|console applications]], -it is `help`. Therefore, if a URL is `http://hostname/index.php`, it means the `site` controller will handle the request. +it is `help`. Therefore, if a URL is `http://hostname/index.php`, then the `site` controller will handle the request. You may change the default controller with the following [application configuration](structure-applications.md#application-configurations): @@ -198,7 +202,7 @@ You may change the default controller with the following [application configurat Creating actions can be as simple as defining the so-called *action methods* in a controller class. An action method is a *public* method whose name starts with the word `action`. The return value of an action method represents -the response data to be sent to end users. The following code defines two actions `index` and `hello-world`: +the response data to be sent to end users. The following code defines two actions, `index` and `hello-world`: ```php namespace app\controllers; @@ -222,16 +226,16 @@ class SiteController extends Controller ### Action IDs -An action is often designed to perform a particular manipulation about a resource. For this reason, +An action is often designed to perform a particular manipulation of a resource. For this reason, action IDs are usually verbs, such as `view`, `update`, etc. By default, action IDs should contain these characters only: English letters in lower case, digits, -underscores and dashes. The dashes in an actionID are used to separate words. For example, -`view`, `update2`, `comment-post` are all valid action IDs, while `view?`, `Update` are not. +underscores, and hyphens. (You can use hyphens to separate words.) For example, +`view`, `update2`, and `comment-post` are all valid action IDs, while `view?` and `Update` are not. You can create actions in two ways: inline actions and standalone actions. An inline action is defined as a method in the controller class, while a standalone action is a class extending -[[yii\base\Action]] or its child class. Inline actions take less effort to create and are often preferred +[[yii\base\Action]] or its child classes. Inline actions take less effort to create and are often preferred if you have no intention to reuse these actions. Standalone actions, on the other hand, are mainly created to be used in different controllers or be redistributed as [extensions](structure-extensions.md). @@ -240,11 +244,11 @@ created to be used in different controllers or be redistributed as [extensions]( Inline actions refer to the actions that are defined in terms of action methods as we just described. -The names of the action methods are derived from action IDs according to the following criteria: +The names of the action methods are derived from action IDs according to the following procedure: -* Turn the first letter in each word of the action ID into upper case; -* Remove dashes; -* Prepend the prefix `action`. +1. Turn the first letter in each word of the action ID into upper case. +2. Remove hyphens. +3. Prepend the prefix `action`. For example, `index` becomes `actionIndex`, and `hello-world` becomes `actionHelloWorld`. @@ -288,7 +292,7 @@ As you can see, the `actions()` method should return an array whose keys are act action class names or [configurations](concept-configurations.md). Unlike inline actions, action IDs for standalone actions can contain arbitrary characters, as long as they are declared in the `actions()` method. -To create a standalone action class, you should extend [[yii\base\Action]] or its child class, and implement +To create a standalone action class, you should extend [[yii\base\Action]] or a child class, and implement a public method named `run()`. The role of the `run()` method is similar to that of an action method. For example, ```php @@ -309,7 +313,7 @@ class HelloWorldAction extends Action ### Action Results -The return value of an action method or the `run()` method of a standalone action is significant. It stands +The return value of an action method or of the `run()` method of a standalone action is significant. It stands for the result of the corresponding action. The return value can be a [response](runtime-responses.md) object which will be sent to the end user as the response. @@ -424,25 +428,25 @@ to fulfill the request: * If the action ID is found to match an action method, an inline action will be created; * Otherwise an [[yii\base\InvalidRouteException]] exception will be thrown. 3. The controller sequentially calls the `beforeAction()` method of the application, the module (if the controller - belongs to a module) and the controller. - * If one of the calls returns false, the rest of the uncalled `beforeAction()` will be skipped and the + belongs to a module), and the controller. + * If one of the calls returns false, the rest of the uncalled `beforeAction()` methods will be skipped and the action execution will be cancelled. * By default, each `beforeAction()` method call will trigger a `beforeAction` event to which you can attach a handler. -4. The controller runs the action: - * The action parameters will be analyzed and populated from the request data; +4. The controller runs the action. + * The action parameters will be analyzed and populated from the request data. 5. The controller sequentially calls the `afterAction()` method of the controller, the module (if the controller - belongs to a module) and the application. + belongs to a module), and the application. * By default, each `afterAction()` method call will trigger an `afterAction` event to which you can attach a handler. 6. The application will take the action result and assign it to the [response](runtime-responses.md). ## Best Practices -In a well-designed application, controllers are often very thin with each action containing only a few lines of code. +In a well-designed application, controllers are often very thin, with each action containing only a few lines of code. If your controller is rather complicated, it usually indicates that you should refactor it and move some code to other classes. -In summary, controllers +Here are some specific best practices. Controllers * may access the [request](runtime-requests.md) data; * may call methods of [models](structure-models.md) and other service components with request data; diff --git a/docs/guide/structure-entry-scripts.md b/docs/guide/structure-entry-scripts.md index d667ea3ade..ca588ba925 100644 --- a/docs/guide/structure-entry-scripts.md +++ b/docs/guide/structure-entry-scripts.md @@ -1,7 +1,7 @@ Entry Scripts ============= -Entry scripts are the first chain in the application bootstrapping process. An application (either +Entry scripts are the first step in the application bootstrapping process. An application (either Web application or console application) has a single entry script. End users make requests to entry scripts which instantiate application instances and forward the requests to them. @@ -17,10 +17,14 @@ Entry scripts mainly do the following work: * Define global constants; <<<<<<< HEAD +<<<<<<< HEAD * Register [Composer autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading); ======= * Register [Composer autoloader](https://getcomposer.org/doc/01-basic-usage.md#autoloading); >>>>>>> yiichina/master +======= +* Register [Composer autoloader](https://getcomposer.org/doc/01-basic-usage.md#autoloading); +>>>>>>> master * Include the [[Yii]] class file; * Load application configuration; * Create and configure an [application](structure-applications.md) instance; @@ -29,11 +33,15 @@ Entry scripts mainly do the following work: ## Web Applications +<<<<<<< HEAD <<<<<<< HEAD The following is the code in the entry script for the [Basic Web Application Template](start-installation.md). ======= The following is the code in the entry script for the [Basic Web Project Template](start-installation.md). >>>>>>> yiichina/master +======= +The following is the code in the entry script for the [Basic Web Project Template](start-installation.md). +>>>>>>> master ```php -Cross-origin resource sharing [CORS](https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS) is a mechanism that allows many resources (e.g. fonts, JavaScript, etc.) +Cross-origin resource sharing [CORS](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS) is a mechanism that allows many resources (e.g. fonts, JavaScript, etc.) on a Web page to be requested from another domain outside the domain the resource originated from. In particular, JavaScript's AJAX calls can use the XMLHttpRequest mechanism. Such "cross-domain" requests would otherwise be forbidden by Web browsers, per the same origin security policy. diff --git a/docs/guide/structure-models.md b/docs/guide/structure-models.md index 5b943a488d..05328b4d69 100644 --- a/docs/guide/structure-models.md +++ b/docs/guide/structure-models.md @@ -165,10 +165,10 @@ setting the scenario of a model: ```php // scenario is set as a property $model = new User; -$model->scenario = 'login'; +$model->scenario = User::SCENARIO_LOGIN; // scenario is set through configuration -$model = new User(['scenario' => 'login']); +$model = new User(['scenario' => User::SCENARIO_LOGIN]); ``` By default, the scenarios supported by a model are determined by the [validation rules](#validation-rules) declared @@ -182,11 +182,14 @@ use yii\db\ActiveRecord; class User extends ActiveRecord { + const SCENARIO_LOGIN = 'login'; + const SCENARIO_REGISTER = 'register'; + public function scenarios() { return [ - 'login' => ['username', 'password'], - 'register' => ['username', 'email', 'password'], + self::SCENARIO_LOGIN => ['username', 'password'], + self::SCENARIO_REGISTER => ['username', 'email', 'password'], ]; } } @@ -211,11 +214,14 @@ use yii\db\ActiveRecord; class User extends ActiveRecord { + const SCENARIO_LOGIN = 'login'; + const SCENARIO_REGISTER = 'register'; + public function scenarios() { $scenarios = parent::scenarios(); - $scenarios['login'] = ['username', 'password']; - $scenarios['register'] = ['username', 'email', 'password']; + $scenarios[self::SCENARIO_LOGIN] = ['username', 'password']; + $scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password']; return $scenarios; } } @@ -283,10 +289,10 @@ public function rules() { return [ // username, email and password are all required in "register" scenario - [['username', 'email', 'password'], 'required', 'on' => 'register'], + [['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER], // username and password are required in "login" scenario - [['username', 'password'], 'required', 'on' => 'login'], + [['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN], ]; } ``` @@ -333,8 +339,8 @@ be kept untouched. public function scenarios() { return [ - 'login' => ['username', 'password'], - 'register' => ['username', 'email', 'password'], + self::SCENARIO_LOGIN => ['username', 'password'], + self::SCENARIO_REGISTER => ['username', 'email', 'password'], ]; } ``` @@ -373,7 +379,7 @@ name when declaring it in `scenarios()`, like the `secret` attribute in the foll public function scenarios() { return [ - 'login' => ['username', 'password', '!secret'], + self::SCENARIO_LOGIN => ['username', 'password', '!secret'], ]; } ``` @@ -386,6 +392,19 @@ have to do it explicitly as follows, $model->secret = $secret; ``` +The same can be done in `rules()` method: + +```php +public function rules() +{ + return [ + [['username', 'password', '!secret'], 'required', 'on' => 'login'] + ]; +} +``` + +In this case attributes `username`, `password` and `secret` are required, but `secret` must be assigned explicitly. + ## Data Exporting @@ -503,11 +522,15 @@ you may take the following strategy: define a concrete model class by extending from the corresponding base model class. The concrete model classes should contain rules and logic that are specific for that application or module. +<<<<<<< HEAD <<<<<<< HEAD For example, in the [Advanced Application Template](tutorial-advanced-app.md), you may define a base model ======= For example, in the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), you may define a base model >>>>>>> yiichina/master +======= +For example, in the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), you may define a base model +>>>>>>> master class `common\models\Post`. Then for the front end application, you define and use a concrete model class `frontend\models\Post` which extends from `common\models\Post`. And similarly for the back end application, you define `backend\models\Post`. With this strategy, you will be sure that the code in `frontend\models\Post` diff --git a/docs/guide/structure-modules.md b/docs/guide/structure-modules.md index 5cec96177a..198f038b20 100644 --- a/docs/guide/structure-modules.md +++ b/docs/guide/structure-modules.md @@ -118,6 +118,31 @@ the [[yii\base\Module::layout]] property to point to the layout name. If you do the application's layout will be used instead. +### Console commands in Modules + +Your module may also declare commands, that will be available through the [Console](tutorial-console.md) mode. + +In order for the command line utility to see your commands, you will need to change the [[yii\base\Module::controllerNamespace]] +property, when Yii is executed in the console mode, and point it to your commands namespace. + +One way to achieve that is to test the instance type of the Yii application in the module's `init` method: + +```php +public function init() +{ + parent::init(); + if (Yii::$app instanceof \yii\console\Application) { + $this->controllerNamespace = 'app\modules\forum\commands'; + } +} +``` + +Your commands will then be available from the command line using the following route: + +``` +yii // +``` + ## Using Modules To use a module in an application, simply configure the application by listing the module in @@ -144,7 +169,8 @@ array value is a [configuration](concept-configurations.md) for creating the mod Like accessing controllers in an application, [routes](structure-controllers.md#routes) are used to address controllers in a module. A route for a controller within a module must begin with the module ID followed by -the controller ID and action ID. For example, if an application uses a module named `forum`, then the route +the [controller ID](structure-controllers.md#controller-ids) and [action ID](structure-controllers.md#action-ids). +For example, if an application uses a module named `forum`, then the route `forum/post/index` would represent the `index` action of the `post` controller in the module. If the route only contains the module ID, then the [[yii\base\Module::defaultRoute]] property, which defaults to `default`, will determine which controller/action should be used. This means a route `forum` would represent the `default` diff --git a/docs/guide/structure-overview.md b/docs/guide/structure-overview.md index 2eaa91d908..3c535d50b3 100644 --- a/docs/guide/structure-overview.md +++ b/docs/guide/structure-overview.md @@ -2,7 +2,7 @@ Overview ======== Yii applications are organized according to the [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) -design pattern. [Models](structure-models.md) represent data, business logic and rules; [views](structure-views.md) +architectural pattern. [Models](structure-models.md) represent data, business logic and rules; [views](structure-views.md) are output representation of models; and [controllers](structure-controllers.md) take input and convert it to commands for [models](structure-models.md) and [views](structure-views.md). diff --git a/docs/guide/structure-views.md b/docs/guide/structure-views.md index 7e98a889af..acfd0aa922 100644 --- a/docs/guide/structure-views.md +++ b/docs/guide/structure-views.md @@ -599,7 +599,7 @@ regardless whether the meta tags are the same or not. To make sure there is only a single instance of a meta tag type, you can specify a key as a second parameter when calling the method. For example, the following code registers two "description" meta tags. However, only the second one will be rendered. -```html +```php $this->registerMetaTag(['name' => 'description', 'content' => 'This is my cool website made with Yii!'], 'description'); $this->registerMetaTag(['name' => 'description', 'content' => 'This website is about funny raccoons.'], 'description'); ``` @@ -694,7 +694,7 @@ Now if you create a view named `about` under the directory `@app/views/site/page display this view by the following URL: ``` -http://localhost/index.php?r=site/page&view=about +http://localhost/index.php?r=site%2Fpage&view=about ``` The `GET` parameter `view` tells [[yii\web\ViewAction]] which view is requested. The action will then look diff --git a/docs/guide/structure-widgets.md b/docs/guide/structure-widgets.md index 0529b278eb..27cde16ff7 100644 --- a/docs/guide/structure-widgets.md +++ b/docs/guide/structure-widgets.md @@ -69,6 +69,18 @@ Note that unlike [[yii\base\Widget::widget()]] which returns the rendering resul [[yii\base\Widget::begin()]] returns an instance of the widget which you can use to build the widget content. +### Configuring global defaults + +Global defaults for a widget type could be configured via DI container: + +```php +\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); +``` + +See ["Practical Usage" section in Dependency Injection Container guide](concept-di-container.md#practical-usage) for +details. + + ## Creating Widgets To create a widget, extend from [[yii\base\Widget]] and override the [[yii\base\Widget::init()]] and/or diff --git a/docs/guide/test-acceptance.md b/docs/guide/test-acceptance.md index c36d9a4fb1..02e249a780 100644 --- a/docs/guide/test-acceptance.md +++ b/docs/guide/test-acceptance.md @@ -3,7 +3,7 @@ Acceptance Tests > Note: This section is under development. -- http://codeception.com/docs/04-AcceptanceTests +- [Codeception Acceptance Tests](http://codeception.com/docs/03-AcceptanceTests) Running basic and advanced template acceptance tests ---------------------------------------------------- diff --git a/docs/guide/test-environment-setup.md b/docs/guide/test-environment-setup.md index 5b9862b721..6aca0ca18a 100644 --- a/docs/guide/test-environment-setup.md +++ b/docs/guide/test-environment-setup.md @@ -3,7 +3,7 @@ Testing environment setup > Note: This section is under development. -Yii2 has officially maintained integration with [`Codeception`](https://github.com/Codeception/Codeception) testing +Yii 2 has officially maintained integration with [`Codeception`](https://github.com/Codeception/Codeception) testing framework that allows you to create the following test types: - [Unit testing](test-unit.md) - verifies that a single unit of code is working as expected; @@ -11,18 +11,33 @@ framework that allows you to create the following test types: - [Acceptance testing](test-acceptance.md) - verifies scenarios from a user's perspective in a browser. Yii provides ready to use test sets for all three test types in both +<<<<<<< HEAD [`yii2-basic`](https://github.com/yiisoft/yii2/tree/master/apps/basic) and <<<<<<< HEAD [`yii2-advanced`](https://github.com/yiisoft/yii2/tree/master/apps/advanced) application templates. ======= [`yii2-advanced`](https://github.com/yiisoft/yii2/tree/master/apps/advanced) project templates. >>>>>>> yiichina/master +======= +[`yii2-basic`](https://github.com/yiisoft/yii2-app-basic) and +[`yii2-advanced`](https://github.com/yiisoft/yii2-app-advanced) project templates. +>>>>>>> master -In order to run tests you need to install [Codeception](https://github.com/Codeception/Codeception). A good way to -install it is the following: +In order to run tests you need to install [Codeception](https://github.com/Codeception/Codeception). +You can install it either locally - for particular project only, or globally - for your development machine. + +For the local installation use following commands: ``` -composer global require "codeception/codeception=2.0.*" +composer require "codeception/codeception=2.1.*" +composer require "codeception/specify=*" +composer require "codeception/verify=*" +``` + +For the global installation you will need to use `global` directive: + +``` +composer global require "codeception/codeception=2.1.*" composer global require "codeception/specify=*" composer global require "codeception/verify=*" ``` @@ -35,3 +50,9 @@ Changed current directory to Then add `/vendor/bin` to you `PATH` environment variable. Now we're able to use `codecept` from command line globally. + +> Note: global installation allows you use Codeception for all projects you are working on your development machine and + allows running `codecept` shell command globally without specifying path. However, such approach may be inappropriate, + for example, if 2 different projects require different versions of Codeception installed. + For the simplicity all shell commands related to the tests running around this guide are written assuming Codeception + has been installed globally. diff --git a/docs/guide/test-fixtures.md b/docs/guide/test-fixtures.md index 8311310769..b6041aba3f 100644 --- a/docs/guide/test-fixtures.md +++ b/docs/guide/test-fixtures.md @@ -108,7 +108,7 @@ In the above, we have shown how to define a fixture about a DB table. To define Using Fixtures -------------- -If you are using [CodeCeption](http://codeception.com/) to test your code, you should consider using +If you are using [Codeception](http://codeception.com/) to test your code, you should consider using the `yii2-codeception` extension which has built-in support for loading and accessing fixtures. If you are using other testing frameworks, you may use [[yii\test\FixtureTrait]] in your test cases to achieve the same goal. @@ -259,7 +259,7 @@ Yii supports fixtures via the `yii fixture` command line tool. This tool support Fixtures format --------------- -Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) on them. +Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md) on them. Lets assume we have fixtures data to load: ``` @@ -283,7 +283,7 @@ return [ ]; ``` If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb` -fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md). +fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md). Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). Fixture classes name should not be plural. @@ -377,7 +377,11 @@ Auto-generating fixtures Yii also can auto-generate fixtures for you based on some template. You can generate your fixtures with different data on different languages and formats. These feature is done by [Faker](https://github.com/fzaninotto/Faker) library and `yii2-faker` extension. <<<<<<< HEAD +<<<<<<< HEAD See extension [guide](https://github.com/yiisoft/yii2/tree/master/extensions/faker) for more docs. ======= See extension [guide](https://github.com/yiisoft/yii2-faker) for more docs. >>>>>>> yiichina/master +======= +See extension [guide](https://github.com/yiisoft/yii2-faker) for more docs. +>>>>>>> master diff --git a/docs/guide/test-functional.md b/docs/guide/test-functional.md index 6041bed707..19161a3808 100644 --- a/docs/guide/test-functional.md +++ b/docs/guide/test-functional.md @@ -3,9 +3,9 @@ Functional Tests > Note: This section is under development. -- http://codeception.com/docs/05-FunctionalTests +- [Codeception Functional Tests](http://codeception.com/docs/04-FunctionalTests) Running basic and advanced template functional tests ---------------------------------------------------- -Please refer to instructions provided in `apps/advanced/tests/README.md` and `apps/basic/tests/README.md`. \ No newline at end of file +Please refer to instructions provided in `apps/advanced/tests/README.md` and `apps/basic/tests/README.md`. diff --git a/docs/guide/test-overview.md b/docs/guide/test-overview.md index 2860ed2162..ab746d5546 100644 --- a/docs/guide/test-overview.md +++ b/docs/guide/test-overview.md @@ -30,7 +30,7 @@ The process of developing a feature is the following: After it's done the process is repeated again for another feature or improvement. If the existing feature is to be changed, tests should be changed as well. -> **Tip**: If you feel that you are losing time doing a lot of small and simple iterations, try covering more by your +> Tip: If you feel that you are losing time doing a lot of small and simple iterations, try covering more by your > test scenario so you do more before executing tests again. If you're debugging too much, try doing the opposite. The reason to create tests before doing any implemenation is that it allows us to focus on what we want to achieve @@ -44,7 +44,7 @@ So to sum up the pros of such an approach are the following: In the long term it usually gives you a good time-saving effect. -> **Tip**: If you want to know more about the principles for gathering software requirements and modeling the subject +> Tip: If you want to know more about the principles for gathering software requirements and modeling the subject > matter it's good to learn [Domain Driven Development (DDD)](https://en.wikipedia.org/wiki/Domain-driven_design). When and how to test diff --git a/docs/guide/test-unit.md b/docs/guide/test-unit.md index e2451a2b04..ca4a02b31b 100644 --- a/docs/guide/test-unit.md +++ b/docs/guide/test-unit.md @@ -11,7 +11,7 @@ Unit tests are usually developed by people who write the classes being tested. Unit testing in Yii is built on top of PHPUnit and, optionally, Codeception so it's recommended to go through their docs: - [PHPUnit docs starting from chapter 2](http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html). -- [Codeception Unit Tests](http://codeception.com/docs/06-UnitTests). +- [Codeception Unit Tests](http://codeception.com/docs/05-UnitTests). Running basic and advanced template unit tests ---------------------------------------------- @@ -22,4 +22,4 @@ Framework unit tests -------------------- If you want to run unit tests for Yii framework itself follow -"[Getting started with Yii2 development](https://github.com/yiisoft/yii2/blob/master/docs/internals/getting-started.md)". +"[Getting started with Yii 2 development](https://github.com/yiisoft/yii2/blob/master/docs/internals/getting-started.md)". diff --git a/docs/guide/tool-api-doc.md b/docs/guide/tool-api-doc.md deleted file mode 100644 index a9a892ed9f..0000000000 --- a/docs/guide/tool-api-doc.md +++ /dev/null @@ -1,6 +0,0 @@ -Generating Api Documentation -============================ - -> Note: This section is under development. -> -> It has no content yet. diff --git a/docs/guide/tool-debugger.md b/docs/guide/tool-debugger.md deleted file mode 100644 index 4a30134633..0000000000 --- a/docs/guide/tool-debugger.md +++ /dev/null @@ -1,193 +0,0 @@ -Debug toolbar and debugger -========================== - -> Note: This section is under development. - -Yii2 includes a handy toolbar, and built-in debugger, for faster development and debugging of your applications. The toolbar displays information -about the currently opened page, while the debugger can be used to analyze data you've previously collected (i.e., to confirm the values of variables). - -Out of the box these tools allow you to: - -- Quickly get the framework version, PHP version, response status, current controller and action, performance info and - more via toolbar -- Browse the application and PHP configuration -- View the request data, request and response headers, session data, and environment variables -- See, search, and filter the logs -- View any profiling results -- View the database queries executed by the page -- View the emails sent by the application - -All of this information will be available per request, allowing you to revisit the information for past requests as well. - - -Installing and configuring --------------------------- - -To enable these features, add these lines to your configuration file to enable the debug module: - -```php -'bootstrap' => ['debug'], -'modules' => [ - 'debug' => 'yii\debug\Module', -] -``` - -By default, the debug module only works when browsing the website from localhost. If you want to use it on a remote (staging) -server, add the parameter `allowedIPs` to the configuration to whitelist your IP: - -```php -'bootstrap' => ['debug'], -'modules' => [ - 'debug' => [ - 'class' => 'yii\debug\Module', - 'allowedIPs' => ['1.2.3.4', '127.0.0.1', '::1'] - ] -] -``` - -If you are using `enableStrictParsing` URL manager option, add the following to your `rules`: - -```php -'urlManager' => [ - 'enableStrictParsing' => true, - 'rules' => [ - // ... - 'debug//' => 'debug//', - ], -], -``` - -> Note: the debugger stores information about each request in the `@runtime/debug` directory. If you have problems using -> the debugger, such as weird error messages when using it, or the toolbar not showing up or not showing any requests, check -> whether the web server has enough permissions to access this directory and the files located inside. - - -### Extra configuration for logging and profiling - -Logging and profiling are simple but powerful tools that may help you to understand the execution flow of both the -framework and the application. These tools are useful for development and production environments alike. - -While in a production environment, you should log only significantly important messages manually, as described in -[logging guide section](logging.md). It hurts performance too much to continue to log all messages in production. - -In a development environment, the more logging the better, and it's especially useful to record the execution trace. - -In order to see the trace messages that will help you to understand what happens under the hood of the framework, you need to set the -trace level in the configuration file: - -```php -return [ - // ... - 'components' => [ - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, // <-- here -``` - -By default, the trace level is automatically set to `3` if Yii is running in debug mode, as determined by the presence of the following line in your `index.php` file: - -```php -defined('YII_DEBUG') or define('YII_DEBUG', true); -``` - -> Note: Make sure to disable debug mode in production environments since it may have a significant and adverse performance effect. Further, the debug mode may expose sensitive information to end users. - - -Creating your own panels ------------------------- - -Both the toolbar and debugger are highly configurable and customizable. To do so, you can create your own panels that collect -and display the specific data you want. Below we'll describe the process of creating a simple custom panel that: - -- Collects the views rendered during a request -- Shows the number of views rendered in the toolbar -- Allows you to check the view names in the debugger - -The assumption is that you're using the basic application template. - -First we need to implement the `Panel` class in `panels/ViewsPanel.php`: - -```php -_viewFiles[] = $event->sender->getViewFile(); - }); - } - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Views'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - $url = $this->getUrl(); - $count = count($this->data); - return "

"; - } - - /** - * @inheritdoc - */ - public function getDetail() - { - return '
  1. ' . implode('
  2. ', $this->data) . '
'; - } - - /** - * @inheritdoc - */ - public function save() - { - return $this->_viewFiles; - } -} -``` - -The workflow for the code above is: - -1. `init` is executed before any controller action is run. This method is the best place to attach handlers that will collect data during the controller action's execution. -2. `save` is called after controller action is executed. The data returned by this method will be stored in a data file. If nothing is returned by this method, the panel - won't be rendered. -3. The data from the data file is loaded into `$this->data`. For the toolbar, this will always represent the latest data, For the debugger, this property may be set to be read from any previous data file as well. -4. The toolbar takes its contents from `getSummary`. There, we're showing the number of view files rendered. The debugger uses - `getDetail` for the same purpose. - -Now it's time to tell the debugger to use the new panel. In `config/web.php`, the debug configuration is modified to: - -```php -if (YII_ENV_DEV) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = [ - 'class' => 'yii\debug\Module', - 'panels' => [ - 'views' => ['class' => 'app\panels\ViewsPanel'], - ], - ]; - -// ... -``` - -That's it. Now we have another useful panel without writing much code. diff --git a/docs/guide/tool-gii.md b/docs/guide/tool-gii.md deleted file mode 100644 index 608e42aa03..0000000000 --- a/docs/guide/tool-gii.md +++ /dev/null @@ -1,269 +0,0 @@ -The Gii code generation tool -============================ - -> Note: This section is under development. - -Yii includes a handy tool, named Gii, that provides rapid prototyping by generating commonly used code snippets -as well as complete CRUD controllers. - -Gii provides a Web-based interface for you to interactively generate the code you want. It also provides a -command line interface for people who prefer to work with their console windows most of the time. - - -Installing and configuring --------------------------- - -Gii is an official Yii extension. The preferred way to install this extension is through -[composer](http://getcomposer.org/download/). - -You can either run this command: - -``` -composer require "yiisoft/yii2-gii:*" -``` - -Or you can add this code to the require section of your `composer.json` file: - -``` -"yiisoft/yii2-gii": "*" -``` - -Once the Gii extension has been installed, you enable it by adding these lines to your application configuration file: - -```php -return [ - 'bootstrap' => ['gii'], - 'modules' => [ - 'gii' => 'yii\gii\Module', - // ... - ], - // ... -]; -``` - -You can then access Gii through the following URL: - -``` -http://localhost/path/to/index.php?r=gii -``` - -If you have enabled pretty URLs, you may use the following URL: - -``` -http://localhost/path/to/index.php/gii -``` - -> Note: if you are accessing gii from an IP address other than localhost, access will be denied by default. -> To circumvent that default, add the allowed IP addresses to the configuration: -> -```php -'gii' => [ - 'class' => 'yii\gii\Module', - 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'] // adjust this to your needs -], -``` - -If you have configured Gii similarly in your console application configuration, you may also access Gii through -command window like the following: - -``` -# change path to your application's base path -cd path/to/AppBasePath - -# show help information about Gii -yii help gii - -# show help information about the model generator in Gii -yii help gii/model - -# generate City model from city table -yii gii/model --tableName=city --modelClass=City -``` - - -### Basic application - -In basic application template configuration structure is a bit different so Gii should be configured in -`config/web.php`: - -```php -// ... -if (YII_ENV_DEV) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; // <--- here -} -``` - -So in order to adjust IP address you need to do it like the following: - -```php -if (YII_ENV_DEV) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = [ - 'class' => 'yii\gii\Module', - 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'], - ]; -} -``` - -How to use it -------------- - -When you open Gii you first see the entry page that lets you choose a generator. - -![Gii entry page](images/gii-entry.png) - -By default there are the following generators available: - -- **Model Generator** - This generator generates an ActiveRecord class for the specified database table. -- **CRUD Generator** - This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) - operations for the specified data model. -- **Controller Generator** - This generator helps you to quickly generate a new controller class, one or several - controller actions and their corresponding views. -- **Form Generator** - This generator generates a view script file that displays a form to collect input for the - specified model class. -- **Module Generator** - This generator helps you to generate the skeleton code needed by a Yii module. -- **Extension Generator** - This generator helps you to generate the files needed by a Yii extension. - -After choosing a generator by clicking on the "Start" button you will see a form that allows you to configure the -parameters of the generator. Fill out the form according to your needs and press the "Preview" button to get a -preview of the code that Gii is about to generate. Depending on the generator you chose and whether the files -already existed or not, you will get an output similar to what you see in the following picture: - -![Gii preview](images/gii-preview.png) - -Clicking on the file name you can view a preview of the code that will be generated for that file. -When the file already exists, Gii also provides a diff view that shows what is different between the code that exists -and the one that will be generated. In this case you can also choose which files should be overridden and which not. - -> Tip: When using the Model Generator to update models after database change, you can copy the code from Gii preview - and merge the changes with your own code. You can use IDE features like PHPStorms - [compare with clipboard](http://www.jetbrains.com/phpstorm/webhelp/comparing-files.html), [Aptana Studio](http://www.aptana.com/products/studio3/download) or [Eclipse](http://www.eclipse.org/pdt/) based editor also allows [compare with clipboard](http://andrei.gmxhome.de/anyedit/examples.html) by using [AnyEdit tools plugin](http://andrei.gmxhome.de/anyedit/) for this, which allows you to merge in relevant changes and leave out others that may revert your own code. - - -After you have reviewed the code and selected the files to be generated you can click the "Generate" button to create -the files. If all went fine you are done. When you see errors that Gii is not able to generate the files you have to -adjust directory permissions so that your webserver is able to write to the directories and create the files. - -> Note: The code generated by Gii is only a template that has to be adjusted to your needs. It is there - to help you create new things quickly but it is not something that creates ready to use code. - We often see people using the models generated by Gii without change and just extend them to adjust - some parts of it. This is not how it is meant to be used. Code generated by Gii may be incomplete or incorrect - and has to be changed to fit your needs before you can use it. - - -Creating your own templates ---------------------------- - -Every generator has a form field `Code Template` that lets you choose a template to use for code generation. -By default Gii only provides one template `default` but you can create your own templates that are adjusted to your needs. - -If you open the folder `@app\vendor\yiisoft\yii2-gii\generators`, you'll see six folders of generators. - -``` -+ controller -- crud - + default -+ extension -+ form -+ model -+ module -``` - -These names are the generator names. If you open any of these folders, you can see the folder `default`, which is the name of the template. - -Copy the folder `@app\vendor\yiisoft\yii2-gii\generators\crud\default` to another location, for example `@app\myTemplates\crud\`. -Now open this folder and modify any template to fit your desires, for example, add `errorSummary` in `views\_form.php`: - -```php -modelClass)) ?>-form"> - - $form = ActiveForm::begin(); ?> - $form->errorSummary($model) ?> - generateActiveField($attribute) . " ?>\n\n"; - } ?> -//... -``` - -Now you need to tell Gii about our template. The setting is made in the config file: - -```php -// config/web.php for basic app -// ... -if (YII_ENV_DEV) { - $config['modules']['gii'] = [ - 'class' => 'yii\gii\Module', - 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'], - 'generators' => [ //here - 'crud' => [ // generator name - 'class' => 'yii\gii\generators\crud\Generator', // generator class - 'templates' => [ //setting for out templates - 'myCrud' => '@app/myTemplates/crud/default', // template name => path to template - ] - ] - ], - ]; -} -``` -Open the CRUD generator and you will see that in the field `Code Template` of form appeared own template . - -Creating your own generators ----------------------------- - -Open the folder of any generator and you will see two files `form.php` and `Generator.php`. -One is the form, the second is the generator class. In order to create your own generator, you need to create or -override these classes in any folder. Again as in the previous paragraph customize the configuration: - -```php -//config/web.php for basic app -//.. -if (YII_ENV_DEV) { - $config['modules']['gii'] = [ - 'class' => 'yii\gii\Module', - 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'], - 'generators' => [ - 'myCrud' => [ - 'class' => 'app\myTemplates\crud\Generator', - 'templates' => [ - 'my' => '@app/myTemplates/crud/default', - ] - ] - ], - ]; -} -``` - -```php -// @app/myTemplates/crud/Generator.php - Note: This section is under development. - -This template is for large projects developed in teams where the backend is divided from the frontend, application is deployed -to multiple servers etc. This application template also goes a bit further regarding features and provides essential -database, signup and password restore out of the box. - -The following table compares the difference between the advanced and the basic application templates: - - -| Feature | Basic | Advanced | -|---|:---:|:---:| -| Project structure | ✓ | ✓ | -| Site controller | ✓ | ✓ | -| User login/logout | ✓ | ✓ | -| Forms | ✓ | ✓ | -| DB connection | ✓ | ✓ | -| Console command | ✓ | ✓ | -| Asset bundle | ✓ | ✓ | -| Codeception tests | ✓ | ✓ | -| Twitter Bootstrap | ✓ | ✓ | -| Front- and back-end apps | | ✓ | -| Ready to use User model | | ✓ | -| User signup and password restore | | ✓ | - - -Installation ------------- - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), follow the instructions in the -[Installing Yii](start-installation.md#installing-via-composer) section to install it. - -With Composer installed, you can then install the application using the following commands: - - composer global require "fxp/composer-asset-plugin:1.0.0" - composer create-project --prefer-dist yiisoft/yii2-app-advanced yii-application - -The first command installs the [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) -which allows managing bower and npm package dependencies through Composer. You only need to run this command -once for all. The second command installs the advanced application in a directory named `yii-application`. -You can choose a different directory name if you want. - -Getting started ---------------- - -After you install the application, you have to conduct the following steps to initialize -the installed application. You only need to do these once for all. - -1. Execute the `init` command and select `dev` as environment. - - ``` - php /path/to/yii-application/init - ``` - - Otherwise, in production execute `init` in non-interactive mode. - - ``` - php /path/to/yii-application/init --env=Production --overwrite=All - ``` - -2. Create a new database and adjust the `components['db']` configuration in `common/config/main-local.php` accordingly. -3. Apply migrations with console command `yii migrate`. -4. Set document roots of your web server: - -- for frontend `/path/to/yii-application/frontend/web/` and using the URL `http://frontend/` -- for backend `/path/to/yii-application/backend/web/` and using the URL `http://backend/` - -To login into the application, you need to first sign up, with any of your email address, username and password. Then, you can login into the application with same email address and password at any time. - -Directory structure -------------------- - -The root directory contains the following subdirectories: - -- `backend` - backend web application. -- `common` - files common to all applications. -- `console` - console application. -- `environments` - environment configs. -- `frontend` - frontend web application. - -Root directory contains a set of files. - -- `.gitignore` contains a list of directories ignored by git version system. If you need something never get to your source - code repository, add it there. -- `composer.json` - Composer config described in "Configuring Composer" below. -- `init` - initialization script described in "Configuration and environments" below. -- `init.bat` - same for Windows. -- `LICENSE.md` - license info. Put your project license there. Especially when opensourcing. -- `README.md` - basic info about installing template. Consider replacing it with information about your project and its - installation. -- `requirements.php` - Yii requirements checker. -- `yii` - console application bootstrap. -- `yii.bat` - same for Windows. - -Predefined path aliases ------------------------ - -- `@yii` - framework directory. -- `@app` - base path of currently running application. -- `@common` - common directory. -- `@frontend` - frontend web application directory. -- `@backend` - backend web application directory. -- `@console` - console directory. -- `@runtime` - runtime directory of currently running web application. -- `@vendor` - Composer vendor directory. -- `@bower` - vendor directory that contains the [bower packages](http://bower.io/). -- `@npm` - vendor directory that contains [npm packages](https://www.npmjs.org/). -- `@web` - base URL of currently running web application. -- `@webroot` - web root directory of currently running web application. - -The aliases specific to the directory structure of the advanced application -(`@common`, `@frontend`, `@backend`, and `@console`) are defined in `common/config/bootstrap.php`. - - -Applications ------------- - -There are three applications in advanced template: frontend, backend and console. Frontend is typically what is presented -to end user, the project itself. Backend is admin panel, analytics and such functionality. Console is typically used for -cron jobs and low-level server management. Also it's used during application deployment and handles migrations and assets. - -There's also a `common` directory that contains files used by more than one application. For example, `User` model. - -frontend and backend are both web applications and both contain the `web` directory. That's the webroot you should point your -web server to. - -Each application has its own namespace and alias corresponding to its name. Same applies to common directory. - -Configuration and environments ------------------------------- - -There are multiple problems with a typical approach to configuration: - -- Each team member has its own configuration options. Committing such config will affect other team members. -- Production database password and API keys should not end up in the repository. -- There are multiple server environments: development, testing, production. Each should have its own configuration. -- Defining all configuration options for each case is very repetitive and takes too much time to maintain. - -In order to solve these issues Yii introduces a simple environments concept. Each environment is represented -by a set of files under the `environments` directory. The `init` command is used to switch between these. What it really does is -copy everything from the environment directory over to the root directory where all applications are. - -By default there are two environments: `dev` and `prod`. First is for development. It has all the developer tools -and debug turned on. Second is for server deployments. It has debug and developer tools turned off. - -Typically environment contains application bootstrap files such as `index.php` and config files suffixed with -`-local.php`. These are added to `.gitignore` and never added to source code repository. - -In order to avoid duplication configurations are overriding each other. For example, the frontend reads configuration in the -following order: - -- `common/config/main.php` -- `common/config/main-local.php` -- `frontend/config/main.php` -- `frontend/config/main-local.php` - -Parameters are read in the following order: - -- `common/config/params.php` -- `common/config/params-local.php` -- `frontend/config/params.php` -- `frontend/config/params-local.php` - -The later config file overrides the former. - -Here's the full scheme: - -![Advanced application configs](images/advanced-app-configs.png) - -Configuring Composer --------------------- - -After the application template is installed it's a good idea to adjust default `composer.json` that can be found in the root -directory: - -```json -{ - "name": "yiisoft/yii2-app-advanced", - "description": "Yii 2 Advanced Application Template", - "keywords": ["yii2", "framework", "advanced", "application template"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "*", - "yiisoft/yii2-bootstrap": "*", - "yiisoft/yii2-swiftmailer": "*" - }, - "require-dev": { - "yiisoft/yii2-codeception": "*", - "yiisoft/yii2-debug": "*", - "yiisoft/yii2-gii": "*", - "yiisoft/yii2-faker": "*" - }, - "config": { - "process-timeout": 1800 - }, - "extra": { - "asset-installer-paths": { - "npm-asset-library": "vendor/npm", - "bower-asset-library": "vendor/bower" - } - } -} -``` - -First we're updating basic information. Change `name`, `description`, `keywords`, `homepage` and `support` to match -your project. - -Now the interesting part. You can add more packages your application needs to the `require` section. -All these packages are coming from [packagist.org](https://packagist.org/) so feel free to browse the website for useful code. - -After your `composer.json` is changed you can run `composer update --prefer-dist`, wait till packages are downloaded and -installed and then just use them. Autoloading of classes will be handled automatically. - -Creating links from backend to frontend ---------------------------------------- - -Often it's required to create links from the backend application to the frontend application. Since the frontend application may -contain its own URL manager rules you need to duplicate that for the backend application by naming it differently: - -```php -return [ - 'components' => [ - 'urlManager' => [ - // here is your normal backend url manager config - ], - 'urlManagerFrontend' => [ - // here is your frontend URL manager config - ], - - ], -]; -``` - -After it is done, you can get an URL pointing to frontend like the following: - -```php -echo Yii::$app->urlManagerFrontend->createUrl(...); -``` diff --git a/docs/guide/tutorial-console.md b/docs/guide/tutorial-console.md index c3b1b59719..c6a5ea61ce 100644 --- a/docs/guide/tutorial-console.md +++ b/docs/guide/tutorial-console.md @@ -2,17 +2,21 @@ Console applications ==================== Besides the rich features for building web applications, Yii also has full featured support for console applications -which are mainly used to create background and maintainance tasks that need to be performed for a website. +which are mainly used to create background and maintenance tasks that need to be performed for a website. The structure of console applications is very similar to a Yii web application. It consists of one or more [[yii\console\Controller]] classes, which are often referred to as "commands" in the console environment. Each controller can also have one or more actions, just like web controllers. +<<<<<<< HEAD <<<<<<< HEAD Both Application templates already have a console application with them. ======= Both project templates already have a console application with them. >>>>>>> yiichina/master +======= +Both project templates already have a console application with them. +>>>>>>> master You can run it by calling the `yii` script, which is located in the base directory of the repository. This will give you a list of available commands when you run it without any further parameters: @@ -20,16 +24,21 @@ This will give you a list of available commands when you run it without any furt As you can see in the screenshot, Yii has already defined a set of commands that are available by default: +<<<<<<< HEAD <<<<<<< HEAD - [yii\console\controllers\AssetController|AssetController] - Allows you to combine and compress your JavaScript and CSS files. +======= +- [[yii\console\controllers\AssetController|AssetController]] - Allows you to combine and compress your JavaScript and CSS files. +>>>>>>> master You can learn more about this command in the [Assets Section](structure-assets.md#using-the-asset-command). -- [yii\console\controllers\CacheController|CacheController] - Allows you to flush application caches. -- [yii\console\controllers\FixtureController|FixtureController] - Manages fixture data loading and unloading for testing purposes. +- [[yii\console\controllers\CacheController|CacheController]] - Allows you to flush application caches. +- [[yii\console\controllers\FixtureController|FixtureController]] - Manages fixture data loading and unloading for testing purposes. This command is described in more detail in the [Testing Section about Fixtures](test-fixtures.md#managing-fixtures). -- [yii\console\controllers\HelpController|HelpController] - Provides help information about console commands, this is the default command +- [[yii\console\controllers\HelpController|HelpController]] - Provides help information about console commands, this is the default command and prints what you have seen in the above output. -- [yii\console\controllers\MessageController|MessageController] - Extracts messages to be translated from source files. +- [[yii\console\controllers\MessageController|MessageController]] - Extracts messages to be translated from source files. To learn more about this command, please refer to the [I18N Section](tutorial-i18n.md#message-command). +<<<<<<< HEAD - [yii\console\controllers\MigrateController|MigrateController] - Manages application migrations. ======= - [[yii\console\controllers\AssetController|AssetController]] - Allows you to combine and compress your JavaScript and CSS files. @@ -43,7 +52,11 @@ As you can see in the screenshot, Yii has already defined a set of commands that To learn more about this command, please refer to the [I18N Section](tutorial-i18n.md#message-command). - [[yii\console\controllers\MigrateController|MigrateController]] - Manages application migrations. >>>>>>> yiichina/master +======= +- [[yii\console\controllers\MigrateController|MigrateController]] - Manages application migrations. +>>>>>>> master Database migrations are described in more detail in the [Database Migration Section](db-migrations.md). +- [[yii\console\controllers\ServeController|ServeController]] - Allows you run PHP built-in web server. Usage @@ -66,7 +79,7 @@ and a limit of 5 migrations can be called like so: yii migrate/up 5 --migrationTable=migrations ``` -> **Note**: When using `*` in console, don't forget to quote it as `"*"` in order to avoid executing it as a shell +> Note: When using `*` in console, don't forget to quote it as `"*"` in order to avoid executing it as a shell > glob that will be replaced by all file names of the current directory. @@ -86,10 +99,6 @@ It contains code like the following: defined('YII_DEBUG') or define('YII_DEBUG', true); -// fcgi doesn't have STDIN and STDOUT defined by default -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); - require(__DIR__ . '/vendor/autoload.php'); require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php'); @@ -113,11 +122,16 @@ you should configure various [application components](structure-application-comp If your web application and console application share a lot of configuration parameters and values, you may consider moving the common <<<<<<< HEAD +<<<<<<< HEAD parts into a separate file, and including this file in both of the application configurations (web and console). You can see an example of this in the "advanced" application template. ======= parts into a separate file, and including this file in both of the application configurations (web and console). You can see an example of this in the "advanced" project template. >>>>>>> yiichina/master +======= +parts into a separate file, and including this file in both of the application configurations (web and console). +You can see an example of this in the "advanced" project template. +>>>>>>> master > Tip: Sometimes, you may want to run a console command using an application configuration that is different > from the one specified in the entry script. For example, you may want to use the `yii migrate` command to @@ -153,6 +167,45 @@ This will assign `OptionValue` to the `OptionName` property of the controller cl If the default value of an option is of an array type and you set this option while running the command, the option value will be converted into an array by splitting the input string on any commas. +### Options Aliases + +Since version 2.0.8 console command provides [[yii\console\Controller::optionAliases()]] method to add +aliases for options. + +To define an alias, override [[yii\console\Controller::optionAliases()]] in your controller, for example: + +```php +namespace app\commands; + +use yii\console\Controller; + +class HelloController extends Controller +{ + public $message; + + public function options() + { + return ['message']; + } + + public function optionAliases() + { + return ['m' => 'message']; + } + + public function actionIndex() + { + echo $message . "\n"; + } +} +``` + +Now, you can use the following syntax to run the command: + +``` +./yii hello -m=hello +``` + ### Arguments Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action @@ -196,7 +249,7 @@ method: public function actionIndex() { if (/* some problem */) { - echo "A problem occured!\n"; + echo "A problem occurred!\n"; return 1; } // do something @@ -206,8 +259,8 @@ public function actionIndex() There are some predefined constants you can use: -- `Controller::EXIT_CODE_NORMAL` with value of `0`; -- `Controller::EXIT_CODE_ERROR` with value of `1`. +- [[yii\console\Controller::EXIT_CODE_NORMAL|Controller::EXIT_CODE_NORMAL]] with value of `0`; +- [[yii\console\Controller::EXIT_CODE_ERROR|Controller::EXIT_CODE_ERROR]] with value of `1`. It's a good practice to define meaningful constants for your controller in case you have more error code types. @@ -222,7 +275,7 @@ Outputting formatted strings is simple. Here's how to output some bold text: $this->stdout("Hello?\n", Console::BOLD); ``` -If you need to build string dynamically combining multiple styles it's better to use `ansiFormat`: +If you need to build string dynamically combining multiple styles it's better to use [[yii\helpers\Console::ansiFormat()|ansiFormat()]]: ```php $name = $this->ansiFormat('Alex', Console::FG_YELLOW); diff --git a/docs/guide/tutorial-core-validators.md b/docs/guide/tutorial-core-validators.md index 3da35b8027..cf3adbe59d 100644 --- a/docs/guide/tutorial-core-validators.md +++ b/docs/guide/tutorial-core-validators.md @@ -58,7 +58,7 @@ to make sure an input is the same as the verification code displayed by [[yii\ca [[yii\captcha\CaptchaAction|CAPTCHA action]] that renders the CAPTCHA image. Defaults to `'site/captcha'`. - `skipOnEmpty`: whether the validation can be skipped if the input is empty. Defaults to false, which means the input is required. - + ## [[yii\validators\CompareValidator|compare]] @@ -79,7 +79,7 @@ is as specified by the `operator` property. is being used to validate an attribute, the default value of this property would be the name of the attribute suffixed with `_repeat`. For example, if the attribute being validated is `password`, then this property will default to `password_repeat`. -- `compareValue`: a constant value that the input value should be compared with. When both +- `compareValue`: a constant value that the input value should be compared with. When both of this property and `compareAttribute` are specified, this property will take precedence. - `operator`: the comparison operator. Defaults to `==`, meaning checking if the input value is equal to that of `compareAttribute` or `compareValue`. The following operators are supported: @@ -103,21 +103,40 @@ is as specified by the `operator` property. This validator checks if the input value is a date, time or datetime in a proper format. <<<<<<< HEAD +<<<<<<< HEAD Optionally, it can convert the input value into a UNIX timestamp and store it in an attribute ======= Optionally, it can convert the input value into a UNIX timestamp or other machine readable format and store it in an attribute >>>>>>> yiichina/master +======= +Optionally, it can convert the input value into a UNIX timestamp or other machine readable format and store it in an attribute +>>>>>>> master specified via [[yii\validators\DateValidator::timestampAttribute|timestampAttribute]]. -- `format`: the date/time format that the value being validated should be in. +- `format`: the date/time format that the value being validated should be in. This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). - Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP + Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP `Datetime` class. Please refer to on supported formats. If this is not set, it will take the value of `Yii::$app->formatter->dateFormat`. <<<<<<< HEAD -- `timestampAttribute`: the name of the attribute to which this validator may assign the UNIX timestamp - converted from the input date/time. +<<<<<<< HEAD +======= + See the [[yii\validators\DateValidator::$format|API documentation]] for more details. +>>>>>>> master +- `timestampAttribute`: the name of the attribute to which this validator may assign the UNIX timestamp + converted from the input date/time. This can be the same attribute as the one being validated. If this is the case, + the original value will be overwritten with the timestamp value after validation. + See ["Handling date input with the DatePicker"](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/topics-date-picker.md) for a usage example. + + Since version 2.0.4, a format and timezone can be specified for this attribute using + [[yii\validators\DateValidator::$timestampAttributeFormat|$timestampAttributeFormat]] and + [[yii\validators\DateValidator::$timestampAttributeTimeZone|$timestampAttributeTimeZone]]. + +- Since version 2.0.4 it is also possible to specify a [[yii\validators\DateValidator::$min|minimum]] or + [[yii\validators\DateValidator::$max|maximum]] timestamp. + +<<<<<<< HEAD In case the input is optional you may also want to add a default value filter in addition to the date validator ======= See the [[yii\validators\DateValidator::$format|API documentation]] for more details. @@ -136,18 +155,27 @@ In case the input is optional you may also want to add a default value filter in In case the input is optional you may also want to add a [default value filter](#default) in addition to the date validator >>>>>>> yiichina/master +======= +In case the input is optional you may also want to add a [default value filter](#default) in addition to the date validator +>>>>>>> master to ensure empty input is stored as `NULL`. Other wise you may end up with dates like `0000-00-00` in your database or `1970-01-01` in the input field of a date picker. ```php <<<<<<< HEAD +<<<<<<< HEAD [['from_date', 'to_date'], 'default', 'value' => null], ======= +======= +>>>>>>> master [ [['from_date', 'to_date'], 'default', 'value' => null], [['from_date', 'to_date'], 'date'], ], +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ``` ## [[yii\validators\DefaultValueValidator|default]] @@ -199,8 +227,11 @@ This validator checks if the input value is a double number. It is equivalent to - `min`: the lower limit (inclusive) of the value. If not set, it means the validator does not check the lower limit. +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> master ## [[yii\validators\EachValidator|each]] > Info: This validator has been available since version 2.0.4. @@ -216,7 +247,11 @@ This validator only works with an array attribute. It validates if *every* eleme validated by a specified validation rule. In the above example, the `categoryIDs` attribute must take an array value and each array element will be validated by the `integer` validation rule. +<<<<<<< HEAD - `rule`: an array specifying a validation rule. The first element in the array specifies the class name or +======= +- `rule`: an array specifying a validation rule. The first element in the array specifies the class name or +>>>>>>> master the alias of the validator. The rest of the name-value pairs in the array are used to configure the validator object. - `allowMessageFromRule`: whether to use the error message returned by the embedded validation rule. Defaults to true. If false, it will use `message` as the error message. @@ -225,7 +260,10 @@ and each array element will be validated by the `integer` validation rule. as the error message. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ## [[yii\validators\EmailValidator|email]] ```php @@ -270,19 +308,26 @@ This validator checks if the input value is a valid email address. ] ``` +<<<<<<< HEAD <<<<<<< HEAD This validator checks if the input value can be found in a table column. It only works with [Active Record](db-active-record.md) model attributes. It supports validation against either a single column or multiple columns. ======= This validator checks if the input value can be found in a table column represented by +======= +This validator checks if the input value can be found in a table column represented by +>>>>>>> master an [Active Record](db-active-record.md) attribute. You can use `targetAttribute` to specify the [Active Record](db-active-record.md) attribute and `targetClass` the corresponding [Active Record](db-active-record.md) class. If you do not specify them, they will take the values of the attribute and the model class being validated. You can use this validator to validate against a single column or multiple columns (i.e., the combination of multiple attribute values should exist). +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master - `targetClass`: the name of the [Active Record](db-active-record.md) class that should be used to look for the input value being validated. If not set, the class of the model currently being validated will be used. @@ -318,7 +363,10 @@ This validator checks if the input is a valid uploaded file. extensions are allowed. - `mimeTypes`: a list of file MIME types that are allowed to be uploaded. This can be either an array or a string consisting of file MIME types separated by space or comma (e.g. "image/jpeg, image/png"). + The wildcard mask with the special character `*` can be used to match groups of mime types. + For example `image/*` will pass all mime types, that begin with `image/` (e.g. `image/jpeg`, `image/png`). Mime type names are case-insensitive. Defaults to null, meaning all MIME types are allowed. + For more details, please refer to [common media types](http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types). - `minSize`: the minimum number of bytes required for the uploaded file. Defaults to null, meaning no lower limit. - `maxSize`: the maximum number of bytes allowed for the uploaded file. Defaults to null, meaning no upper limit. - `maxFiles`: the maximum number of files that the given attribute can hold. Defaults to 1, meaning @@ -390,6 +438,79 @@ validation purpose: - `minHeight`: the minimum height of the image. Defaults to null, meaning no lower limit. - `maxHeight`: the maximum height of the image. Defaults to null, meaning no upper limit. +## [[yii\validators\IpValidator|ip]] +```php +[ + // checks if "ip_address" is a valid IPv4 or IPv6 address + ['ip_address', 'ip'], + + // checks if "ip_address" is a valid IPv6 address or subnet, + // value will be expanded to full IPv6 notation. + ['ip_address', 'ip', 'ipv4' => false, 'subnet' => null, 'expandIPv6' => true], + + // checks if "ip_address" is a valid IPv4 or IPv6 address, + // allows negation character `!` at the beginning + ['ip_address', 'ip', 'negation' => true], +] +``` + +The validator checks if the attribute value is a valid IPv4/IPv6 address or subnet. +It also may change attribute's value if normalization or IPv6 expansion is enabled. + +The validator has such configuration options: + +- `ipv4`: whether the validating value can be an IPv4 address. Defaults to true. +- `ipv6`: whether the validating value can be an IPv6 address. Defaults to true. +- `subnet`: whether the address can be an IP with CIDR subnet, like `192.168.10.0/24` + * `true` - the subnet is required, addresses without CIDR will be rejected + * `false` - the address can not have the CIDR + * `null` - the CIDR is optional + + Defaults to false. +- `normalize`: whether to add the CIDR prefix with the smallest length (32 for IPv4 and 128 for IPv6) to an +address without it. Works only when `subnet` is not `false`. For example: + * `10.0.1.5` will normalized to `10.0.1.5/32` + * `2008:db0::1` will be normalized to `2008:db0::1/128` + + Defaults to false. +- `negation`: whether the validation address can have a negation character `!` at the beginning. Defaults to false. +- `expandIPv6`: whether to expand an IPv6 address to the full notation format. +For example, `2008:db0::1` will be expanded to `2008:0db0:0000:0000:0000:0000:0000:0001`. Defaults to false. +- `ranges`: array of IPv4 or IPv6 ranges that are allowed or forbidden. + + When the array is empty, or the option is not set, all the IP addresses are allowed. + Otherwise, the rules are checked sequentially until the first match is found. + IP address is forbidden, when it has not matched any of the rules. + + For example: + ```php + [ + 'client_ip', 'ip', 'ranges' => [ + '192.168.10.128' + '!192.168.10.0/24', + 'any' // allows any other IP addresses + ] + ] + ``` +In this example, access is allowed for all the IPv4 and IPv6 addresses excluding `192.168.10.0/24` subnet. +IPv4 address `192.168.10.128` is also allowed, because it is listed before the restriction. +- `networks`: array of network aliases, that can be used in `ranges`. Format of array: + * key - alias name + * value - array of strings. String can be a range, IP address or another alias. String can be + negated with `!` (independent of `negation` option). + + The following aliases are defined by default: + + * `*`: `any` + * `any`: `0.0.0.0/0, ::/0` + * `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8` + * `multicast`: `224.0.0.0/4, ff00::/8` + * `linklocal`: `169.254.0.0/16, fe80::/10` + * `localhost`: `127.0.0.0/8', ::1` + * `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32` + * `system`: `multicast, linklocal, localhost, documentation` + +> Info: This validator has been available since version 2.0.7. ## [[yii\validators\RangeValidator|in]] diff --git a/docs/guide/tutorial-i18n.md b/docs/guide/tutorial-i18n.md index 8307ff2620..2e26d1d507 100644 --- a/docs/guide/tutorial-i18n.md +++ b/docs/guide/tutorial-i18n.md @@ -1,79 +1,83 @@ Internationalization ==================== -> Note: This section is under development. - Internationalization (I18N) refers to the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. For Web applications, this is of particular importance -because the potential users may be worldwide. +because the potential users may be worldwide. Yii offers a full spectrum of I18N features that support message +translation, view translation, date and number formatting. -Yii offers several tools that help with internationalization of a website such as message translation and -number- and date-formatting. -Locale and Language -------------------- +## Locale and Language -There are two languages defined in the Yii application: [[yii\base\Application::$sourceLanguage|source language]] and -[[yii\base\Application::$language|target language]]. +Locale is a set of parameters that defines the user's language, country and any special variant preferences +that the user wants to see in their user interface. It is usually identified by an ID consisting of a language +ID and a region ID. For example, the ID `en-US` stands for the locale of English and United States. +For consistency, all locale IDs used in Yii applications should be canonicalized to the format of +`ll-CC`, where `ll` is a two- or three-letter lowercase language code according to +[ISO-639](http://www.loc.gov/standards/iso639-2/) and `CC` is a two-letter country code according to +[ISO-3166](http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html). +More details about locale can be found in the +[documentation of the ICU project](http://userguide.icu-project.org/locale#TOC-The-Locale-Concept). -The source language is the language in which the original application messages are written directly in the code such as: +In Yii, we often use the term "language" to refer to a locale. -```php -echo \Yii::t('app', 'I am a message!'); -``` +A Yii application uses two kinds of languages: [[yii\base\Application::$sourceLanguage|source language]] and +[[yii\base\Application::$language|target language]]. The former refers to the language in which the text messages +in the source code are written, while the latter is the language that should be used to display content to end users. +The so-called message translation service mainly translates a text message from source language to target language. -The target language is the language that should be used to display the current page, i.e. the language that original messages need -to be translated to. It is defined in the application configuration like the following: +You can configure application languages in the application configuration like the following: ```php return [ - 'id' => 'applicationID', - 'basePath' => dirname(__DIR__), - // ... - 'language' => 'ru-RU', // <- here! - // ... -] + // set target language to be Russian + 'language' => 'ru-RU', + + // set source language to be English + 'sourceLanguage' => 'en-US', + + ...... +]; ``` -> **Tip**: The default value for the [[yii\base\Application::$sourceLanguage|source language]] is English and it is -> recommended to keep this value. The reason is that it's easier to find people translating from -> English to any language than from non-English to non-English. +The default value for the [[yii\base\Application::$sourceLanguage|source language]] is `en-US`, meaning +US English. It is recommended that you keep this default value unchanged, because it is usually much easier +to find people who can translate from English to other languages than from non-English to non-English. -You may set the application language at runtime to the language that the user has chosen. -This has to be done at a point before any output is generated so that it affects all the output correctly. -Therefor just change the application property to the desired value: +You often need to set the [[yii\base\Application::$language|target language]] dynamically based on different +factors, such as the language preference of end users. Instead of configuring it in the application configuration, +you can use the following statement to change the target language: ```php +// change target language to Chinese \Yii::$app->language = 'zh-CN'; ``` -The format for the language/locale is `ll-CC` where `ll` is a two- or three-letter lowercase code for a language according to -[ISO-639](http://www.loc.gov/standards/iso639-2/) and `CC` is the country code according to -[ISO-3166](http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html). +## Message Translation -> **Note**: For more information on the concept and syntax of locales, check the -> [documentation of the ICU project](http://userguide.icu-project.org/locale#TOC-The-Locale-Concept). +The message translation service translates a text message from one language (usually the [[yii\base\Application::$sourceLanguage|source language]]) +to another (usually the [[yii\base\Application::$language|target language]]). It does the translation by looking +up the message to be translated in a message source which stores the original messages and the translated messages. +If the message is found, the corresponding translated message will be returned; otherwise the original message will be +returned untranslated. -Message translation -------------------- +To use the message translation service, you mainly need to do the following work: -Message translation is used to translate the messages that are output by an application to different languages -so that users from different countries can use the application in their native language. +* Wrap every text message that needs to be translated in a call to the [[Yii::t()]] method; +* Configure one or multiple message sources in which the message translation service can look for translated messages; +* Let the translators translate messages and store them in the message source(s). -The message translation feature in Yii works simply as finding a -translation of the message from a source language into a target language. -To use the message translation feature you wrap your original message strings with a call to the [[Yii::t()]] method. -The first parameter of this method takes a category which helps to distinguish the source of messages in different parts -of the application and the second parameter is the message itself. +The method [[Yii::t()]] can be used like the following, ```php echo \Yii::t('app', 'This is a string to translate!'); ``` -Yii tries to load an appropriate translation according to the current [[yii\base\Application::$language|application language]] -from one of the message sources defined in the `i18n` [application component](structure-application-components.md). -A message source is a set of files or a database that provides translation messages. -The following configuration example defines a messages source that takes the messages from PHP files: +where the second parameter refers to the text message to be translated, while the first parameter refers to +the name of the category which is used to categorize the message. + +The [[Yii::t()]] method will call the `i18n` [application component](structure-application-components.md) `translate` +method to perform the actual translation work. The component can be configured in the application configuration as follows, ```php 'components' => [ @@ -94,195 +98,340 @@ The following configuration example defines a messages source that takes the mes ], ``` -In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're -handling everything that begins with `app`. Message files are located in `@app/messages`, the `messages` directory -in your application directory. The [[yii\i18n\PhpMessageSource::fileMap|fileMap]] array -defines which file is to be used for which category. -Instead of configuring `fileMap` you can rely on the convention which is to use the category name as the file name -(e.g. category `app/error` will result in the file name `app/error.php` under the [[yii\i18n\PhpMessageSource::basePath|basePath]]. +In the above code, a message source supported by [[yii\i18n\PhpMessageSource]] is being configured. The pattern +`app*` indicates that all message categories whose names start with `app` should be translated using this +message source. The [[yii\i18n\PhpMessageSource]] class uses PHP files to store message translations. Each +PHP file corresponds to the messages of a single category. By default, the file name should be the same as +the category name. However, you may configure [[yii\i18n\PhpMessageSource::fileMap|fileMap]] to map a category +to a PHP file with a different naming approach. In the above example, the category `app/error` is mapped to +the PHP file `@app/messages/ru-RU/error.php` (assuming `ru-RU` is the target language). Without this configuration, +the category would be mapped to `@app/messages/ru-RU/app/error.php`, instead. -When translating the message for `\Yii::t('app', 'This is a string to translate!')` with the application language being `ru-RU`, Yii -will first look for a file `@app/messages/ru-RU/app.php` to retrieve the list of available translations. -If there is no such file under `ru-RU`, it will try `ru` as well before failing. +Beside storing the messages in PHP files, you may also use the following message sources to store translated messages +in different storage: -Beside storing the messages in PHP files (using [[yii\i18n\PhpMessageSource|PhpMessageSource]]), Yii provides two other -classes: - -- [[yii\i18n\GettextMessageSource]] that uses GNU Gettext MO or PO files. -- [[yii\i18n\DbMessageSource]] that uses a database. +- [[yii\i18n\GettextMessageSource]] uses GNU Gettext MO or PO files to maintain translated messages. +- [[yii\i18n\DbMessageSource]] uses a database table to store translated messages. -### Named placeholders +## Message Formatting -You can add parameters to a translation message that will be substituted with the corresponding value after translation. -The format for this is to use curly brackets around the parameter name as you can see in the following example: +When translating a message, you can embed some placeholders and have them replaced by dynamic parameter values. +You can even use special placeholder syntax to have the parameter values formatted according to the target language. +In this subsection, we will describe different ways of formatting messages. + +### Message Parameters + +In a message to be translated, you can embed one or multiple parameters (also called placeholders) so that they can be +replaced by the given values. By giving different sets of values, you can variate the translated message dynamically. +In the following example, the placeholder `{username}` in the message `'Hello, {username}!'` will be replaced +by `'Alexander'` and `'Qiang'`, respectively. ```php $username = 'Alexander'; +// display a translated message with username being "Alexander" +echo \Yii::t('app', 'Hello, {username}!', [ + 'username' => $username, +]); + +$username = 'Qiang'; +// display a translated message with username being "Qiang" echo \Yii::t('app', 'Hello, {username}!', [ 'username' => $username, ]); ``` -Note that the parameter assignment is without the brackets. +While translating a message containing placeholders, you should leave the placeholders as is. This is because the placeholders +will be replaced with the actual values when you call `Yii::t()` to translate a message. -### Positional placeholders +You can use either *named placeholders* or *positional placeholders*, but not both, in a single message. + +The previous example shows how you can use named placeholders. That is, each placeholder is written in the format of +`{name}`, and you provide an associative array whose keys are the placeholder names +(without the curly brackets) and whose values are the corresponding values placeholder to be replaced with. + +Positional placeholders use zero-based integer sequence as names which are replaced by the provided values +according to their positions in the call of `Yii::t()`. In the following example, the positional placeholders +`{0}`, `{1}` and `{2}` will be replaced by the values of `$price`, `$count` and `$subtotal`, respectively. + +```php +$price = 100; +$count = 2; +$subtotal = 200; +echo \Yii::t('app', 'Price: {0}, Count: {1}, Subtotal: {2}', [$price, $count, $subtotal]); +``` + +In case of a single positional parameter its value could be specified without wrapping it into array: + +```php +echo \Yii::t('app', 'Price: {0}', $price); +``` + +> Tip: In most cases you should use named placeholders. This is because the names will make the translators +> understand better the whole messages being translated. + + +### Parameter Formatting + +You can specify additional formatting rules in the placeholders of a message so that the parameter values can be +formatted properly before they replace the placeholders. In the following example, the price parameter value will be +treated as a number and formatted as a currency value: + +```php +$price = 100; +echo \Yii::t('app', 'Price: {0,number,currency}', $price); +``` + +> Note: Parameter formatting requires the installation of the [intl PHP extension](http://www.php.net/manual/en/intro.intl.php). + +You can use either the short form or the full form to specify a placeholder with formatting: + +``` +short form: {name,type} +full form: {name,type,style} +``` + +> Note: If you need to use special characters such as `{`, `}`, `'`, `#`, wrap them in `'`: +> +```php +echo Yii::t('app', "Example of string with ''-escaped characters'': '{' '}' '{test}' {count,plural,other{''count'' value is # '#{}'}}", ['count' => 3]); +``` + +Complete format is described in the [ICU documentation](http://icu-project.org/apiref/icu4c/classMessageFormat.html). +In the following we will show some common usages. + + +#### Number + +The parameter value is treated as a number. For example, ```php $sum = 42; -echo \Yii::t('app', 'Balance: {0}', $sum); +echo \Yii::t('app', 'Balance: {0,number}', $sum); ``` -> **Tip**: Try to keep the message strings meaningful and avoid using too many positional parameters. Remember that -> the translator has only the source string, so it should be obvious about what will replace each placeholder. - -### Advanced placeholder formatting - -In order to use the advanced features you need to install and enable the [intl PHP extension](http://www.php.net/manual/en/intro.intl.php). -After installing and enabling it you will be able to use the extended syntax for placeholders: either the short form -`{placeholderName, argumentType}` that uses the default style, or the full form `{placeholderName, argumentType, argumentStyle}` -that allows you to specify the formatting style. - -A complete reference is available at the [ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but we will show some examples in the following. - -#### Numbers +You can specify an optional parameter style as `integer`, `currency`, or `percent`: ```php $sum = 42; -echo \Yii::t('app', 'Balance: {0, number}', $sum); +echo \Yii::t('app', 'Balance: {0,number,currency}', $sum); ``` -You can specify one of the built-in styles (`integer`, `currency`, `percent`): +You can also specify a custom pattern to format the number. For example, ```php $sum = 42; -echo \Yii::t('app', 'Balance: {0, number, currency}', $sum); +echo \Yii::t('app', 'Balance: {0,number,,000,000000}', $sum); ``` -Or specify a custom pattern: +Characters used in the custom format could be found in +[ICU API reference](http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html) under "Special Pattern Characters" +section. + + +The value is always formatted according to the locale you are translating to i.e. you cannot change decimal or thousands +separators, currency symbol etc. without changing translation locale. If you need to customize these you can +use [[yii\i18n\Formatter::asDecimal()]] and [[yii\i18n\Formatter::asCurrency()]]. + +#### Date + +The parameter value should be formatted as a date. For example, ```php -$sum = 42; -echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum); +echo \Yii::t('app', 'Today is {0,date}', time()); ``` -[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html). - -#### Dates +You can specify an optional parameter style as `short`, `medium`, `long`, or `full`: ```php -echo \Yii::t('app', 'Today is {0, date}', time()); +echo \Yii::t('app', 'Today is {0,date,short}', time()); ``` -Built in formats are `short`, `medium`, `long`, and `full`: +You can also specify a custom pattern to format the date value: ```php -echo \Yii::t('app', 'Today is {0, date, short}', time()); -``` - -You may also specify a custom pattern: - -```php -echo \Yii::t('app', 'Today is {0, date, yyyy-MM-dd}', time()); -``` - -[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). - -#### Time - -```php -echo \Yii::t('app', 'It is {0, time}', time()); -``` - -Built in formats are `short`, `medium`, `long`, and `full`: - -```php -echo \Yii::t('app', 'It is {0, time, short}', time()); -``` - -You may also specify a custom pattern: - -```php -echo \Yii::t('app', 'It is {0, date, HH:mm}', time()); +echo \Yii::t('app', 'Today is {0,date,yyyy-MM-dd}', time()); ``` [Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). -#### Spellout +#### Time + +The parameter value should be formatted as a time. For example, ```php -echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', ['n' => 42]); +echo \Yii::t('app', 'It is {0,time}', time()); ``` -#### Ordinal +You can specify an optional parameter style as `short`, `medium`, `long`, or `full`: ```php -echo \Yii::t('app', 'You are {n, ordinal} visitor here!', ['n' => 42]); +echo \Yii::t('app', 'It is {0,time,short}', time()); ``` -Will produce "You are 42nd visitor here!". - -#### Duration +You can also specify a custom pattern to format the time value: ```php -echo \Yii::t('app', 'You are here for {n, duration} already!', ['n' => 47]); +echo \Yii::t('app', 'It is {0,date,HH:mm}', time()); ``` -Will produce "You are here for 47 sec. already!". +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). -#### Plurals + +#### Spellout + +The parameter value should be treated as a number and formatted as a spellout. For example, + +```php +// may produce "42 is spelled as forty-two" +echo \Yii::t('app', '{n,number} is spelled as {n,spellout}', ['n' => 42]); +``` + +By default the number is spelled out as cardinal. It could be changed: + +```php +// may produce "I am forty-seventh agent" +echo \Yii::t('app', 'I am {n,spellout,%spellout-ordinal} agent', ['n' => 47]); +``` + +Note that there should be no space after `spellout,` and before `%`. + +To get a list of options available for locale you're using check +"Numbering schemas, Spellout" at [http://intl.rmcreative.ru/](http://intl.rmcreative.ru/). + +#### Ordinal + +The parameter value should be treated as a number and formatted as an ordinal name. For example, + +```php +// may produce "You are the 42nd visitor here!" +echo \Yii::t('app', 'You are the {n,ordinal} visitor here!', ['n' => 42]); +``` + +Ordinal supports more ways of formatting for languages such as Spanish: + +```php +// may produce 471ª +echo \Yii::t('app', '{n,ordinal,%digits-ordinal-feminine}', ['n' => 471]); +``` + +Note that there should be no space after `ordinal,` and before `%`. + +To get a list of options available for locale you're using check +"Numbering schemas, Ordinal" at [http://intl.rmcreative.ru/](http://intl.rmcreative.ru/). + +#### Duration + +The parameter value should be treated as the number of seconds and formatted as a time duration string. For example, + +```php +// may produce "You are here for 47 sec. already!" +echo \Yii::t('app', 'You are here for {n,duration} already!', ['n' => 47]); +``` + +Duration supports more ways of formatting: + +```php +// may produce 130:53:47 +echo \Yii::t('app', '{n,duration,%in-numerals}', ['n' => 471227]); +``` + +Note that there should be no space after `duration,` and before `%`. + +To get a list of options available for locale you're using check +"Numbering schemas, Duration" at [http://intl.rmcreative.ru/](http://intl.rmcreative.ru/). + +#### Plural Different languages have different ways to inflect plurals. Yii provides a convenient way for translating messages in different plural forms that works well even for very complex rules. Instead of dealing with the inflection rules directly, -it is sufficient to provide the translation of inflected words in certain situations only. +it is sufficient to provide the translation of inflected words in certain situations only. For example, ```php -echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', ['n' => $n]); +// When $n = 0, it may produce "There are no cats!" +// When $n = 1, it may produce "There is one cat!" +// When $n = 42, it may produce "There are 42 cats!" +echo \Yii::t('app', 'There {n,plural,=0{are no cats} =1{is one cat} other{are # cats}}!', ['n' => $n]); ``` -Will give us +In the plural rule arguments above, `=` means explicit value. So `=0` means exactly zero, `=1` means exactly one. +`other` stands for any other value. `#` is replaced with the value of `n` formatted according to target language. -- "There are no cats!" for `$n = 0`, -- "There is one cat!" for `$n = 1`, -- and "There are 42 cats!" for `$n = 42`. - -In the plural rule arguments above, `=0` means exactly zero, `=1` stands for exactly one, and `other` is for any other number. -`#` is replaced with the value of `n`. It's not that simple for languages other than English. Here's an example -for Russian: +Plural forms can be very complicated in some languages. In the following Russian example, `=1` matches exactly `n = 1` +while `one` matches `21` or `101`: ``` -Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}! +Здесь {n,plural,=0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}! ``` -In the above it's worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`. +These `other`, `few`, `many` and other special argument names vary depending on language. To learn which ones you should +specify for a particular locale, please refer to "Plural Rules, Cardinal" at [http://intl.rmcreative.ru/](http://intl.rmcreative.ru/). +Alternatively you can refer to [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). -Note, that you can not use the Russian example in `Yii::t()` directly if your -[[yii\base\Application::$sourceLanguage|source language]] isn't set to `ru-RU`. This however is not recommended, instead such -strings should go into message files or message database (in case DB source is used). Yii uses the plural rules of the -translated language strings and is falling back to the plural rules of the source language if the translation isn't available. +> Note: The above example Russian message is mainly used as a translated message, not an original message, unless you set +> the [[yii\base\Application::$sourceLanguage|source language]] of your application as `ru-RU` and translating from Russian. +> +> When a translation is not found for an original message specified in `Yii::t()` call, the plural rules for the +> [[yii\base\Application::$sourceLanguage|source language]] will be applied to the original message. -To learn which inflection forms you should specify for your language, you can refeer to the -[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). +There's an `offset` parameter for the cases when the string is like the following: + +```php +$likeCount = 2; +echo Yii::t('app', 'You {likeCount,plural, + offset: 1 + =0{did not like this} + =1{liked this} + one{and one other person liked this} + other{and # others liked this} +}', [ + 'likeCount' => $likeCount +]); -#### Selections +// You and one other person liked this +``` -You can select phrases based on keywords. The pattern in this case specifies how to map keywords to phrases and -provides a default phrase. +#### Ordinal selection + +The parameter type of `selectordinal` is meant to choose a string based on language rules for ordinals for the +locale you are translating to: ```php -echo \Yii::t('app', '{name} is a {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', [ +$n = 3; +echo Yii::t('app', 'You are the {n,selectordinal,one{#st} two{#nd} few{#rd} other{#th}} visitor', ['n' => $n]); +// For English it outputs: +// You are the 3rd visitor + +// Translation +'You are the {n,selectordinal,one{#st} two{#nd} few{#rd} other{#th}} visitor' => 'Вы {n,selectordinal,other{#-й}} посетитель', + +// For Russian translation it outputs: +// Вы 3-й посетитель +``` + +The format is very close to what's used for plurals. To learn which arguments you should specify for a particular locale, +please refer to "Plural Rules, Ordinal" at [http://intl.rmcreative.ru/](http://intl.rmcreative.ru/). +Alternatively you can refer to [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). + +#### Selection + +You can use the `select` parameter type to choose a phrase based on the parameter value. For example, + +```php +// It may produce "Snoopy is a dog and it loves Yii!" +echo \Yii::t('app', '{name} is a {gender} and {gender,select,female{she} male{he} other{it}} loves Yii!', [ 'name' => 'Snoopy', 'gender' => 'dog', ]); ``` -Will produce "Snoopy is a dog and it loves Yii!". +In the expression above, both `female` and `male` are possible parameter values, while `other` handles values that +do not match either one of them. Following each possible parameter value, you should specify a phrase and enclose +it in a pair of curly brackets. -In the expression above, `female` and `male` are possible values, while `other` handles values that do not match. A string inside -the brackets is a sub-expression, so it could be a plain string or a string with nested placeholders in it. -### Specifying default translation +### Specifying default translation You can specify default translations that will be used as a fallback for categories that don't match any other translation. This translation should be marked with `*`. In order to do it add the following to the application config: @@ -308,7 +457,7 @@ echo Yii::t('not_specified_category', 'message from unspecified category'); The message will be loaded from `@app/messages//not_specified_category.php`. -### Translating module messages +### Translating module messages If you want to translate the messages for a module and avoid using a single translation file for all the messages, you can do it like the following: @@ -355,7 +504,7 @@ In the example above we are using wildcard for matching and then filtering each use the convention of the category mapping to the same named file. Now you can use `Module::t('validation', 'your custom validation message')` or `Module::t('form', 'some form label')` directly. -### Translating widgets messages +### Translating widgets messages The same rule as applied for Modules above can be applied for widgets too, for example: @@ -405,10 +554,10 @@ class Menu extends Widget Instead of using `fileMap` you can simply use the convention of the category mapping to the same named file. Now you can use `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` directly. -> **Note**: For widgets you also can use i18n views, with the same rules as for controllers being applied to them too. +> Note: For widgets you also can use i18n views, with the same rules as for controllers being applied to them too. -### Translating framework messages +### Translating framework messages Yii comes with the default translation messages for validation errors and some other strings. These messages are all in the category `yii`. Sometimes you want to correct the default framework message translation for your application. @@ -428,13 +577,14 @@ In order to do so, configure the `i18n` [application component](structure-applic Now you can place your adjusted translations to `@app/messages//yii.php`. -### Handling missing translations +### Handling missing translations Even if the translation is missing from the source, Yii will display the requested message content. Such behavior is very convenient in case your raw message is a valid verbose text. However, sometimes it is not enough. You may need to perform some custom processing of the situation, when the requested translation is missing from the source. This can be achieved using the [[yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION|missingTranslation]]-event of [[yii\i18n\MessageSource]]. +<<<<<<< HEAD <<<<<<< HEAD For example, let us mark all the missing translations with something notable so that they can be easily found at the page. First we need to setup an event handler. This can be done in the application configuration: @@ -442,6 +592,10 @@ First we need to setup an event handler. This can be done in the application con For example, you may want to mark all the missing translations with something notable, so that they can be easily found at the page. First you need to setup an event handler. This can be done in the application configuration: >>>>>>> yiichina/master +======= +For example, you may want to mark all the missing translations with something notable, so that they can be easily found at the page. +First you need to setup an event handler. This can be done in the application configuration: +>>>>>>> master ```php 'components' => [ @@ -461,11 +615,15 @@ First you need to setup an event handler. This can be done in the application co ], ``` +<<<<<<< HEAD <<<<<<< HEAD Now we need to implement our own event handler: ======= Now you need to implement your own event handler: >>>>>>> yiichina/master +======= +Now you need to implement your own event handler: +>>>>>>> master ```php >>>>>> yiichina/master +======= + public static function handleMissingTranslation(MissingTranslationEvent $event) + { +>>>>>>> master $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @"; } } @@ -495,6 +658,7 @@ If [[yii\i18n\MissingTranslationEvent::translatedMessage]] is set by the event h ### Using the `message` command +<<<<<<< HEAD <<<<<<< HEAD TDB @@ -522,59 +686,97 @@ Once you're done with the config file you can finally extract your messages with You will then find your files (if you've choosen file based translations) in your `messagePath` directory. >>>>>>> yiichina/master +======= +Translations can be stored in [[yii\i18n\PhpMessageSource|php files]], [[yii\i18n\GettextMessageSource|.po files]] or in a [[yii\i18n\DbMessageSource|database]]. See specific classes for additional options. -Views ------ +First of all you need to create a configuration file. Decide where you want to store it and then issue the command +>>>>>>> master -Instead of translating messages as described in the last section, -you can also use `i18n` in your views to provide support for different languages. For example, if you have a view `views/site/index.php` and -you want to create a special version for the Russian language, you create a `ru-RU` folder under the view path of the current controller/widget and -put the file for the Russian language as `views/site/ru-RU/index.php`. Yii will then load the file for the current language if it exists -and fall back to the original view file if none was found. +```bash +./yii message/config-template path/to/config.php +``` -> **Note**: If language is specified as `en-US` and there are no corresponding views, Yii will try views under `en` -> before using original ones. +Open the created file and adjust the parameters to fit your needs. Pay special attention to: +* `languages`: an array representing what languages your app should be translated to; +* `messagePath`: path where to store message files, which should match the `i18n`'s `basePath` parameter stated in config. -Formatting Number and Date values ---------------------------------- +You may also use './yii message/config' command to dynamically generate configuration file with specified options via cli. +For example, you can set `languages` and `messagePath` parameters like the following: +```bash +./yii message/config --languages=de,ja --messagePath=messages path/to/config.php +``` + +<<<<<<< HEAD <<<<<<< HEAD See the [data formatter section](output-formatter.md) for details. ======= See the [data formatter section](output-formatting.md) for details. >>>>>>> yiichina/master +======= +To get list of available options execute next command: +>>>>>>> master + +```bash +./yii help message/config +``` + +Once you're done with the configuration file you can finally extract your messages with the command: + +```bash +./yii message path/to/config.php +``` + +Also, you may use options to dynamically change parameters for extraction. + +You will then find your files (if you've chosen file based translations) in your `messagePath` directory. -Setting up your PHP environment -------------------------------- +## View Translation -Yii uses the [PHP intl extension](http://php.net/manual/en/book.intl.php) to provide most of its internationalization features -such as the number and date formatting of the [[yii\i18n\Formatter]] class and the message formatting using [[yii\i18n\MessageFormatter]]. -Both classes provides a fallback implementation that provides basic functionality in case `intl` is not installed. -This fallback implementation however only works well for sites in English language and even there can not provide the -rich set of features that is available with the PHP intl extension, so its installation is highly recommended. +Instead of translating individual text messages, sometimes you may want to translate a whole view script. +To achieve this goal, simply translate the view and save it under a subdirectory whose name is the same as +target language. For example, if you want to translate the view script `views/site/index.php` and the target +language is `ru-RU`, you may translate the view and save it as the file `views/site/ru-RU/index.php`. Now +whenever you call [[yii\base\View::renderFile()]] or any method that invoke this method (e.g. [[yii\base\Controller::render()]]) +to render the view `views/site/index.php`, it will end up rendering the translated view `views/site/ru-RU/index.php`, instead. + +> Note: If the [[yii\base\Application::$language|target language]] is the same as [[yii\base\Application::$sourceLanguage|source language]] +> original view will be rendered regardless of presence of translated view. + + +## Formatting Date and Number Values + +See the [Data Formatting](output-formatting.md) section for details. + + +## Setting Up PHP Environment + +Yii uses the [PHP intl extension](http://php.net/manual/en/book.intl.php) to provide most of its I18N features, +such as the date and number formatting of the [[yii\i18n\Formatter]] class and the message formatting using [[yii\i18n\MessageFormatter]]. +Both classes provide a fallback mechanism when the `intl` extension is not installed. However, the fallback implementation +only works well for English target language. So it is highly recommended that you install `intl` when I18N is needed. The [PHP intl extension](http://php.net/manual/en/book.intl.php) is based on the [ICU library](http://site.icu-project.org/) which -provides the knowledge and formatting rules for all the different locales. According to this fact the formatting of dates and numbers -and also the supported syntax available for message formatting differs between different versions of the ICU library that is compiled with -you PHP binary. +provides the knowledge and formatting rules for all different locales. Different versions of ICU may produce different +formatting result of date and number values. To ensure your website produces the same results across all environments, +it is recommended that you install the same version of the `intl` extension (and thus the same version of ICU) +in all environments. -To ensure your website works with the same output in all environments it is recommended to install the PHP intl extension -in all environments and verify that the version of the ICU library compiled with PHP is the same. - -To find out which version of ICU is used by PHP you can run the following script, which will give you the PHP and ICU version used. +To find out which version of ICU is used by PHP, you can run the following script, which will give you the PHP and ICU version being used. ```php for a list of available ICU versions. Note that the version numbering has changed after the -4.8 release so that the first digits are now merged: the sequence is ICU 4.8, ICU 49, ICU 50. +It is also recommended that you use an ICU version equal or greater than version 49. This will ensure you can use all the features +described in this document. For example, an ICU version below 49 does not support using `#` placeholders in plural rules. +Please refer to for a complete list of available ICU versions. Note that the version +numbering has changed after the 4.8 release (e.g., ICU 4.8, ICU 49, ICU 50, etc.) Additionally the information in the time zone database shipped with the ICU library may be outdated. Please refer to the [ICU manual](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) for details diff --git a/docs/guide/tutorial-mailing.md b/docs/guide/tutorial-mailing.md index fc0f9a4f69..2099f1ea46 100644 --- a/docs/guide/tutorial-mailing.md +++ b/docs/guide/tutorial-mailing.md @@ -8,11 +8,15 @@ only the content composition functionality and basic interface. Actual mail send be provided by the extension, because different projects may require its different implementation and it usually depends on the external services and libraries. +<<<<<<< HEAD <<<<<<< HEAD For the most common cases you can use [yii2-swiftmailer](https://github.com/yiisoft/yii2/tree/master/extensions/swiftmailer) official extension. ======= For the most common cases you can use [yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer) official extension. >>>>>>> yiichina/master +======= +For the most common cases you can use [yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer) official extension. +>>>>>>> master Configuration @@ -136,11 +140,15 @@ Yii::$app->mailer->compose([ If you specify view name as a scalar string, its rendering result will be used as HTML body, while plain text body will be composed by removing all HTML entities from HTML one. +<<<<<<< HEAD <<<<<<< HEAD View rendering result can be wrapped into the layout, which an be setup using [[yii\mail\BaseMailer::htmlLayout]] ======= View rendering result can be wrapped into the layout, which can be setup using [[yii\mail\BaseMailer::htmlLayout]] >>>>>>> yiichina/master +======= +View rendering result can be wrapped into the layout, which can be setup using [[yii\mail\BaseMailer::htmlLayout]] +>>>>>>> master and [[yii\mail\BaseMailer::textLayout]]. It will work the same way like layouts in regular web application. Layout can be used to setup mail CSS styles or other shared content: diff --git a/docs/guide/tutorial-performance-tuning.md b/docs/guide/tutorial-performance-tuning.md index 7d013c3b31..63569aae56 100644 --- a/docs/guide/tutorial-performance-tuning.md +++ b/docs/guide/tutorial-performance-tuning.md @@ -1,63 +1,66 @@ Performance Tuning ================== -> Note: This section is under development. +There are many factors affecting the performance of your Web application. Some are environmental, some are related +with your code, while some others are related with Yii itself. In this section, we will enumerate most of these +factors and explain how you can improve your application performance by adjusting these factors. -The performance of your web application is based upon two parts. First is the framework performance -and the second is the application itself. Yii has a pretty low performance impact -on your application out of the box and can be fine-tuned further for production -environment. As for the application, we'll provide some of the best practices -along with examples on how to apply them to Yii. -Preparing environment ---------------------- +## Optimizing your PHP Environment -A well configured environment to run PHP application really matters. In order to get maximum performance: +A well configured PHP environment is very important. In order to get maximum performance, -- Always use the latest stable PHP version. Each major release brings significant performance improvements and reduced - memory usage. -- Use [APC](http://ru2.php.net/apc) for PHP 5.4 and less or [Opcache](http://php.net/opcache) for PHP 5.5 and more. It - gives a very good performance boost. +- Use the latest stable PHP version. Major releases of PHP may bring significant performance improvements. +- Enable bytecode caching with [Opcache](http://php.net/opcache) (PHP 5.5 or later) or [APC](http://ru2.php.net/apc) + (PHP 5.4 or earlier). Bytecode caching avoids the time spent in parsing and including PHP scripts for every + incoming request. +- [Tune `realpath()` cache](https://github.com/samdark/realpath_cache_tuner). -Preparing framework for production ----------------------------------- -### Disabling Debug Mode +## Disabling Debug Mode -First thing you should do before deploying your application to production environment -is to disable debug mode. A Yii application runs in debug mode if the constant -`YII_DEBUG` is defined as `true` in `index.php`. -So, in order to disable debug mode, the following should be in your `index.php`: +When running an application in production, you should disable debug mode. Yii uses the value of a constant +named `YII_DEBUG` to indicate whether debug mode should be enabled. When debug mode is enabled, Yii +will take extra time to generate and record debugging information. + +You may place the following line of code at the beginning of the [entry script](structure-entry-scripts.md) to +disable debug mode: ```php defined('YII_DEBUG') or define('YII_DEBUG', false); ``` -Debug mode is very useful during development stage, but it would impact performance -because some components cause extra burden in debug mode. For example, the message -logger may record additional debug information for every message being logged. +> Info: The default value of `YII_DEBUG` is false. So if you are certain that you do not change its default + value somewhere else in your application code, you may simply remove the above line to disable debug mode. + -### Enabling PHP opcode cache +## Using Caching Techniques -Enabling the PHP opcode cache improves any PHP application performance and lowers -memory usage significantly. Yii is no exception. It was tested with both -[PHP 5.5 OPcache](http://php.net/manual/en/book.opcache.php) and -[APC PHP extension](http://php.net/manual/en/book.apc.php). Both cache -optimize PHP intermediate code and avoid the time spent in parsing PHP -scripts for every incoming request. +You can use various caching techniques to significantly improve the performance of your application. For example, +if your application allows users to enter text in Markdown format, you may consider caching the parsed Markdown +content to avoid parsing the same Markdown text repeatedly in every request. Please refer to +the [Caching](caching-overview.md) section to learn about the caching support provided by Yii. -### Turning on ActiveRecord database schema caching -If the application is using Active Record, we should turn on the schema caching -to save the time of parsing database schema. This can be done by setting the -`Connection::enableSchemaCache` property to be `true` via application configuration -`config/web.php`: +## Enabling Schema Caching + +Schema caching is a special caching feature that should be enabled whenever you are using [Active Record](db-active-record.md). +As you know, Active Record is intelligent enough to detect schema information (e.g. column names, column types, constraints) +about a DB table without requiring you to manually describe them. Active Record obtains this information by executing +extra SQL queries. By enabling schema caching, the retrieved schema information will be saved in the cache and reused +in future requests. + +To enable schema caching, configure a `cache` [application component](structure-application-components.md) to store +the schema information and set [[yii\db\Connection::enableSchemaCache]] to be `true` in the [application configuration](concept-configurations.md): ```php return [ // ... 'components' => [ // ... + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=mydatabase', @@ -66,33 +69,33 @@ return [ 'enableSchemaCache' => true, // Duration of schema cache. - // 'schemaCacheDuration' => 3600, + 'schemaCacheDuration' => 3600, - // Name of the cache component used. Default is 'cache'. - //'schemaCache' => 'cache', - ], - 'cache' => [ - 'class' => 'yii\caching\FileCache', + // Name of the cache component used to store schema information + 'schemaCache' => 'cache', ], ], ]; ``` -Note that the `cache` [application component](structure-application-components.md) should be configured. -### Combining and Minimizing Assets +## Combining and Minimizing Assets -It is possible to combine and minimize assets, typically JavaScript and CSS, in order to slightly improve page load -time and therefore deliver better experience for end user of your application. +A complex Web page often includes many CSS and/or JavaScript asset files. To reduce the number of HTTP requests +and the overall download size of these assets, you should consider combining them into one single file and +compressing it. This may greatly improve the page loading time and reduce the server load. For more details, +please refer to the [Assets](structure-assets.md) section. -In order to learn how it can be achieved, refer to [assets](structure-assets.md) guide section. -### Using better storage for sessions +## Optimizing Session Storage -By default PHP uses files to handle sessions. It is OK for development and -small projects. But when it comes to handling concurrent requests, it's better to -switch to another storage such as database. You can do so by configuring your -application via `config/web.php`: +By default session data are stored in files. The implementation is locking a file from opening a session to the point it's +closed either by `session_write_close()` (in Yii it could be done as `Yii::$app->session->close()`) or at the end of request. +While session file is locked all other requests which are trying to use the same session are blocked i.e. waiting for the +initial request to release session file. This is fine for development and probably small projects. But when it comes +to handling massive concurrent requests, it is better to use more sophisticated storage, such as database. Yii supports +a variety of session storage out of box. You can use these storage by configuring the `session` component in the +[application configuration](concept-configurations.md) like the following, ```php return [ @@ -112,145 +115,115 @@ return [ ]; ``` -You can use `CacheSession` to store sessions using cache. Note that some -cache storage such as memcached has no guarantee that session data will not -be lost, and it would lead to unexpected logouts. +The above configuration uses a database table to store session data. By default, it will use the `db` application +component as the database connection and store the session data in the `session` table. You do have to create the +`session` table as follows in advance, though, -If you have [Redis](http://redis.io/) on your server, it's highly recommended as session storage. - -Improving application ---------------------- - -### Using Serverside Caching Techniques - -As described in the Caching section, Yii provides several caching solutions that -may improve the performance of a Web application significantly. If the generation -of some data takes long time, we can use the data caching approach to reduce the -data generation frequency; If a portion of page remains relatively static, we -can use the fragment caching approach to reduce its rendering frequency; -If a whole page remains relative static, we can use the page caching approach to -save the rendering cost for the whole page. - - -### Leveraging HTTP caching to save processing time and bandwidth - -Leveraging HTTP caching saves both processing time, bandwidth and resources significantly. It can be implemented by -sending either `ETag` or `Last-Modified` header in your application response. If browser is implemented according to -HTTP specification (most browsers are), content will be fetched only if it is different from what it was prevously. - -Forming proper headers is time consuming task so Yii provides a shortcut in form of controller filter -[[yii\filters\HttpCache]]. Using it is very easy. In a controller you need to implement `behaviors` method like -the following: - -```php -public function behaviors() -{ - return [ - 'httpCache' => [ - 'class' => \yii\filters\HttpCache::className(), - 'only' => ['list'], - 'lastModified' => function ($action, $params) { - $q = new Query(); - return strtotime($q->from('users')->max('updated_timestamp')); - }, - // 'etagSeed' => function ($action, $params) { - // return // generate etag seed here - //} - ], - ]; -} +```sql +CREATE TABLE session ( + id CHAR(40) NOT NULL PRIMARY KEY, + expire INTEGER, + data BLOB +) ``` -In the code above one can use either `etagSeed` or `lastModified`. Implementing both isn't necessary. The goal is to -determine if content was modified in a way that is cheaper than fetching and rendering that content. `lastModified` -should return unix timestamp of the last content modification while `etagSeed` should return a string that is then -used to generate `ETag` header value. +You may also store session data in a cache by using [[yii\web\CacheSession]]. In theory, you can use any supported +[cache storage](caching-data.md#supported-cache-storage). Note, however, that some cache storage may flush cached data +when the storage limit is reached. For this reason, you should mainly use those cache storage that do not enforce +storage limit. + +If you have [Redis](http://redis.io/) on your server, it is highly recommended you use it as session storage by using +[[yii\redis\Session]]. -### Database Optimization +## Optimizing Databases -Fetching data from database is often the main performance bottleneck in -a Web application. -Although using [caching](caching.md#Query-Caching) may alleviate the performance hit, -it does not fully solve the problem. When the database contains enormous data -and the cached data is invalid, fetching the latest data could be prohibitively -expensive without proper database and query design. +Execute DB queries and fetching data from databases is often the main performance bottleneck in +a Web application. Although using [data caching](caching-data.md) techniques may alleviate the performance hit, +it does not fully solve the problem. When the database contains enormous amounts of data and the cached data is invalid, +fetching the latest data could be prohibitively expensive without proper database and query design. -Design index wisely in a database. Indexing can make SELECT queries much faster, -but it may slow down INSERT, UPDATE or DELETE queries. +A general technique to improve the performance of DB queries is to create indices for table columns that +need to be filtered by. For example, if you need to look for a user record by `username`, you should create an index +on `username`. Note that while indexing can make SELECT queries much faster, it will slow down INSERT, UPDATE and DELETE queries. -For complex queries, it is recommended to create a database view for it instead -of issuing the queries inside the PHP code and asking DBMS to parse them repetitively. +For complex DB queries, it is recommended that you create database views to save the query parsing and preparation time. -Do not overuse Active Record. Although Active Record is good at modeling data -in an OOP fashion, it actually degrades performance due to the fact that it needs -to create one or several objects to represent each row of query result. For data -intensive applications, using DAO or database APIs at lower level could be -a better choice. +Last but not least, use `LIMIT` in your `SELECT` queries. This avoids fetching an overwhelming amount of data from the database +and exhausting the memory allocated to PHP. -Last but not least, use `LIMIT` in your `SELECT` queries. This avoids fetching -overwhelming data from database and exhausting the memory allocated to PHP. -### Using asArray +## Using Plain Arrays -A good way to save memory and processing time on read-only pages is to use -ActiveRecord's `asArray` method. +Although [Active Record](db-active-record.md) is very convenient to use, it is not as efficient as using plain arrays +when you need to retrieve a large amount of data from database. In this case, you may consider calling `asArray()` +while using Active Record to query data so that the retrieved data is represented as arrays instead of bulky Active +Record objects. For example, ```php class PostController extends Controller { public function actionIndex() { - $posts = Post::find()->orderBy('id DESC')->limit(100)->asArray()->all(); + $posts = Post::find()->limit(100)->asArray()->all(); + return $this->render('index', ['posts' => $posts]); } } ``` -In the view you should access fields of each individual record from `$posts` as array: +In the above code, `$posts` will be populated as an array of table rows. Each row is a plain array. To access +the `title` column of the i-th row, you may use the expression `$posts[$i]['title']`. -```php -foreach ($posts as $post) { - echo $post['title'] . "
"; -} +You may also use [DAO](db-dao.md) to build queries and retrieve data in plain arrays. + + +## Optimizing Composer Autoloader + +Because Composer autoloader is used to include most third-party class files, you should consider optimizing it +by executing the following command: + +``` +composer dumpautoload -o ``` -Note that you can use array notation even if `asArray` wasn't specified and you're -working with AR objects. -### Composer autoloader optimization +## Processing Data Offline -In order to improve overall performance you can execute `composer dumpautoload -o` to optimize Composer autoloader. +When a request involves some resource intensive operations, you should think of ways to perform those operations +in offline mode without having users wait for them to finish. -### Processing data in background +There are two methods to process data offline: pull and push. -In order to respond to user requests faster you can process heavy parts of the -request later if there's no need for immediate response. +In the pull method, whenever a request involves some complex operation, you create a task and save it in a persistent +storage, such as database. You then use a separate process (such as a cron job) to pull the tasks and process them. +This method is easy to implement, but it has some drawbacks. For example, the task process needs to periodically pull +from the task storage. If the pull frequency is too low, the tasks may be processed with great delay; but if the frequency +is too high, it will introduce high overhead. -There are two common ways to achieve it: cron job processing and specialized queues. +In the push method, you would use a message queue (e.g. RabbitMQ, ActiveMQ, Amazon SQS, etc.) to manage the tasks. +Whenever a new task is put on the queue, it will initiate or notify the task handling process to trigger the task processing. -In the first case we need to save the data that we want to process later to a persistent storage -such as database. A [console command](tutorial-console.md) that is run regularly via cron job queries -database and processes data if there's any. - -The solution is OK for most cases but has one significant drawback. We aren't aware if there's data to -process before we query database, so we're either querying database quite often or have a slight delay -between each data processing. - -This issue could be solved by queue and job servers such RabbitMQ, ActiveMQ, Amazon SQS and more. -In this case instead of writing data to persistent storage you're queueing it via APIs provided -by queue or job server. Processing is often put into job handler class. Job from the queue is executed -right after all jobs before it are done. - -### If nothing helps - -If nothing helps, never assume what may fix performance problem. Always profile your code instead before changing -anything. The following tools may be helpful: +<<<<<<< HEAD <<<<<<< HEAD - [Yii debug toolbar and debugger](tool-debugger.md) ======= - [Yii debug toolbar and debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) >>>>>>> yiichina/master - [XDebug profiler](http://xdebug.org/docs/profiler) +======= +## Performance Profiling + +You should profile your code to find out the performance bottlenecks and take appropriate measures accordingly. +The following profiling tools may be useful: + +- [Yii debug toolbar and debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) +- [Blackfire](https://blackfire.io/) +>>>>>>> master - [XHProf](http://www.php.net/manual/en/book.xhprof.php) +- [XDebug profiler](http://xdebug.org/docs/profiler) + +## Prepare application for scaling + +When nothing helps you may try making your application scalabe. A good introduction is provided in [Configuring a Yii2 Application for an Autoscaling Stack](https://github.com/samdark/yii2-cookbook/blob/master/book/scaling.md). For further reading you may refer to [Web apps performance and scaling](http://thehighload.com/). diff --git a/docs/guide/tutorial-shared-hosting.md b/docs/guide/tutorial-shared-hosting.md index 073bd26dbf..dd1058b59d 100644 --- a/docs/guide/tutorial-shared-hosting.md +++ b/docs/guide/tutorial-shared-hosting.md @@ -6,11 +6,15 @@ Shared hosting environments are often quite limited about configuration and dire Deploying a basic application --------------------------- +<<<<<<< HEAD <<<<<<< HEAD Since in a shared hosting environment there's typically only one webroot, use the basic application template if you can. Refer to the [Installing Yii chapter](start-installation.md) and install the basic application template locally. After you have the application working locally, we'll make some adjustments so it can be hosted on your shared hosting server. ======= Since in a shared hosting environment there's typically only one webroot, use the basic project template if you can. Refer to the [Installing Yii chapter](start-installation.md) and install the basic project template locally. After you have the application working locally, we'll make some adjustments so it can be hosted on your shared hosting server. >>>>>>> yiichina/master +======= +Since in a shared hosting environment there's typically only one webroot, use the basic project template if you can. Refer to the [Installing Yii chapter](start-installation.md) and install the basic project template locally. After you have the application working locally, we'll make some adjustments so it can be hosted on your shared hosting server. +>>>>>>> master ### Renaming webroot @@ -24,11 +28,15 @@ www In the above, `www` is your webserver webroot directory. It could be named differently. Common names are: `www`, `htdocs`, and `public_html`. +<<<<<<< HEAD <<<<<<< HEAD The webroot in our basic application template is named `web`. Before uploading the application to your webserver rename your local webroot to match your server, i.e., from `web` to `www`, `public_html` or whatever the name of your hosting webroot. ======= The webroot in our basic project template is named `web`. Before uploading the application to your webserver rename your local webroot to match your server, i.e., from `web` to `www`, `public_html` or whatever the name of your hosting webroot. >>>>>>> yiichina/master +======= +The webroot in our basic project template is named `web`. Before uploading the application to your webserver rename your local webroot to match your server, i.e., from `web` to `www`, `public_html` or whatever the name of your hosting webroot. +>>>>>>> master ### FTP root directory is writeable diff --git a/docs/guide/tutorial-start-from-scratch.md b/docs/guide/tutorial-start-from-scratch.md index b08f8014ee..1c2d1f8ede 100644 --- a/docs/guide/tutorial-start-from-scratch.md +++ b/docs/guide/tutorial-start-from-scratch.md @@ -3,6 +3,7 @@ Creating your own Application structure > Note: This section is under development. +<<<<<<< HEAD <<<<<<< HEAD While the [basic](https://github.com/yiisoft/yii2/tree/master/apps/basic) and [advanced](https://github.com/yiisoft/yii2/tree/master/apps/advanced) application templates are great for most of your needs, you may want to create your own application template with which @@ -16,6 +17,13 @@ to start your projects. Project templates in Yii are simply repositories containing a `composer.json` file, and registered as a Composer package. >>>>>>> yiichina/master +======= +While the [basic](https://github.com/yiisoft/yii2-app-basic) and [advanced](https://github.com/yiisoft/yii2-app-advanced) +project templates are great for most of your needs, you may want to create your own project template with which +to start your projects. + +Project templates in Yii are simply repositories containing a `composer.json` file, and registered as a Composer package. +>>>>>>> master Any repository can be identified as a Composer package, making it installable via `create-project` Composer command. Since it's a bit too much to start building your entire template from scratch, it is better to use one of the built-in @@ -55,6 +63,7 @@ For private templates, it is a bit more tricky to register the package. For inst Use the Template ------ +<<<<<<< HEAD <<<<<<< HEAD That's all that's required to create a new Yii application template. Now you can create projects using your template: @@ -66,5 +75,11 @@ That's all that's required to create a new Yii project template. Now you can cre ``` composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= +That's all that's required to create a new Yii project template. Now you can create projects using your template: + +``` +composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide/tutorial-template-engines.md b/docs/guide/tutorial-template-engines.md index c4ba3b4b1b..b9e7c34bc7 100644 --- a/docs/guide/tutorial-template-engines.md +++ b/docs/guide/tutorial-template-engines.md @@ -1,6 +1,7 @@ Using template engines ====================== +<<<<<<< HEAD <<<<<<< HEAD > Note: This section is under development. @@ -10,6 +11,10 @@ By default, Yii uses PHP as its template language, but you can configure Yii to By default, Yii uses PHP as its template language, but you can configure Yii to support other rendering engines, such as [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/) available as extensions. >>>>>>> yiichina/master +======= +By default, Yii uses PHP as its template language, but you can configure Yii to support other rendering engines, such as +[Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/) available as extensions. +>>>>>>> master The `view` component is responsible for rendering views. You can add a custom template engine by reconfiguring this component's behavior: @@ -50,6 +55,7 @@ your `composer.json` file to include them, too: ``` That code would be added to the `require` section of `composer.json`. After making that change and saving the file, you can install the extensions by running `composer update --prefer-dist` in the command-line. +<<<<<<< HEAD <<<<<<< HEAD Twig ---- @@ -463,3 +469,9 @@ For details about using concrete template engine please refer to its documentati - [Twig guide](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) - [Smarty guide](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) >>>>>>> yiichina/master +======= +For details about using concrete template engine please refer to its documentation: + +- [Twig guide](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) +- [Smarty guide](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) +>>>>>>> master diff --git a/docs/guide/tutorial-yii-integration.md b/docs/guide/tutorial-yii-integration.md index a8f9dac922..d122e2815c 100644 --- a/docs/guide/tutorial-yii-integration.md +++ b/docs/guide/tutorial-yii-integration.md @@ -82,17 +82,37 @@ take two steps: install Yii, and bootstrap Yii. If the third-party system uses Composer to manage its dependencies, you can simply run the following commands to install Yii: +<<<<<<< HEAD <<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= + composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer require yiisoft/yii2 composer install The first command installs the [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) which allows managing bower and npm package dependencies through Composer. Even if you only want to use the database layer or other non-asset related features of Yii, this is required to install the Yii composer package. + +If you want to use the [Asset publishing feature of Yii](structure-assets.md) you should also add the following configuration +to the `extra` section in your `composer.json`: + +```json +{ + ... + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } +} +``` + See also the general [section about installing Yii](start-installation.md#installing-via-composer) for more information on Composer and solution to possible issues popping up during the installation. @@ -164,7 +184,7 @@ class Yii extends \yii\BaseYii } Yii::$classMap = include($yii2path . '/classes.php'); -// register Yii2 autoloader via Yii1 +// register Yii 2 autoloader via Yii 1 Yii::registerAutoloader(['Yii', 'autoload']); // create the dependency injection container Yii::$container = new yii\di\Container; diff --git a/docs/guide/widget-bootstrap.md b/docs/guide/widget-bootstrap.md deleted file mode 100644 index 937d32e5dd..0000000000 --- a/docs/guide/widget-bootstrap.md +++ /dev/null @@ -1,70 +0,0 @@ -Bootstrap Widgets -================= - -> Note: This section is under development. - -Out of the box, Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework -(also known as "Twitter Bootstrap"). Bootstrap is an excellent, responsive framework that can greatly speed up the -client-side of your development process. - -The core of Bootstrap is represented by two parts: - -- CSS basics, such as a grid layout system, typography, helper classes, and responsive utilities. -- Ready to use components, such as forms, menus, pagination, modal boxes, tabs etc. - -Basics ------- - -Yii doesn't wrap the bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details -about using the basics at [bootstrap documentation website](http://getbootstrap.com/css/). Still Yii provides a -convenient way to include bootstrap assets in your pages with a single line added to `AppAsset.php` located in your -`@app/assets` directory: - -```php -public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', // this line -]; -``` - -Using bootstrap through Yii asset manager allows you to minimize its resources and combine with your own resources when -needed. - -Yii widgets ------------ - -Most complex bootstrap components are wrapped into Yii widgets to allow more robust syntax and integrate with -framework features. All widgets belong to `\yii\bootstrap` namespace: - -- [[yii\bootstrap\ActiveForm|ActiveForm]] -- [[yii\bootstrap\Alert|Alert]] -- [[yii\bootstrap\Button|Button]] -- [[yii\bootstrap\ButtonDropdown|ButtonDropdown]] -- [[yii\bootstrap\ButtonGroup|ButtonGroup]] -- [[yii\bootstrap\Carousel|Carousel]] -- [[yii\bootstrap\Collapse|Collapse]] -- [[yii\bootstrap\Dropdown|Dropdown]] -- [[yii\bootstrap\Modal|Modal]] -- [[yii\bootstrap\Nav|Nav]] -- [[yii\bootstrap\NavBar|NavBar]] -- [[yii\bootstrap\Progress|Progress]] -- [[yii\bootstrap\Tabs|Tabs]] - - -Using the .less files of Bootstrap directly -------------------------------------------- - -If you want to include the [Bootstrap css directly in your less files](http://getbootstrap.com/getting-started/#customizing) -you may need to disable the original bootstrap css files to be loaded. -You can do this by setting the css property of the [[yii\bootstrap\BootstrapAsset|BootstrapAsset]] to be empty. -For this you need to configure the `assetManager` [application component](structure-application-components.md) as follows: - -```php - 'assetManager' => [ - 'bundles' => [ - 'yii\bootstrap\BootstrapAsset' => [ - 'css' => [], - ] - ] - ] -``` diff --git a/docs/guide/widget-jui.md b/docs/guide/widget-jui.md deleted file mode 100644 index 5463b7001b..0000000000 --- a/docs/guide/widget-jui.md +++ /dev/null @@ -1,48 +0,0 @@ -jQuery UI Widgets -================= - -> Note: This section is under development. - -Yii includes support for the [jQuery UI](http://api.jqueryui.com/) library in an official extension. jQuery UI is -a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. - -Installation ------------- - -The preferred way to install the extension is through [composer](http://getcomposer.org/download/). - -Either run - -``` -php composer.phar require --prefer-dist yiisoft/yii2-jui "*" -``` - -or add - -``` -"yiisoft/yii2-jui": "*" -``` - -to the require section of your `composer.json` file. - -Yii widgets ------------ - -Most complex jQuery UI components are wrapped into Yii widgets to allow more robust syntax and integrate with -framework features. All widgets belong to `\yii\jui` namespace: - -- [[yii\jui\Accordion|Accordion]] -- [[yii\jui\AutoComplete|AutoComplete]] -- [[yii\jui\DatePicker|DatePicker]] -- [[yii\jui\Dialog|Dialog]] -- [[yii\jui\Draggable|Draggable]] -- [[yii\jui\Droppable|Droppable]] -- [[yii\jui\Menu|Menu]] -- [[yii\jui\ProgressBar|ProgressBar]] -- [[yii\jui\Resizable|Resizable]] -- [[yii\jui\Selectable|Selectable]] -- [[yii\jui\Slider|Slider]] -- [[yii\jui\SliderInput|SliderInput]] -- [[yii\jui\Sortable|Sortable]] -- [[yii\jui\Spinner|Spinner]] -- [[yii\jui\Tabs|Tabs]] diff --git a/docs/internals-es/translation-workflow.md b/docs/internals-es/translation-workflow.md index 3954f238fc..68317a9fc2 100644 --- a/docs/internals-es/translation-workflow.md +++ b/docs/internals-es/translation-workflow.md @@ -1,4 +1,4 @@ -Flujo de Trabajo de Traducción +Flujo de Trabajo de Traducción ============================== Yii se traduce en muchos idiomas con el fin de ser útil para desarrolladores de aplicaciones e internacionales. @@ -46,13 +46,6 @@ Las palabras en inglés que son propias del framework o de PHP se pueden dejar e Para las palabras que están muy ligadas a conceptos extendidos se deben traducir y poner entre paréntesis su equivalente en el idioma original. Ejemplos : `petición` (request), `respuesta` (response), `comportamiento` (behavior), etc. -Los bloques han de ser traducidos, las traducciones se muestran a continuación : - -* `Warning` : `Aviso` -* `Note` : `Nota` -* `Info` : `Información` -* `Tip` : `Consejo` - > Aclaraciones : * Sólo mencionar una vez entre paréntesis la palabra original en su primera aparición en el texto o en el fichero README.md, evitando redundancias. Ejemplo: vista(view), controlador(controller), etc. diff --git a/docs/internals-ja/core-code-style.md b/docs/internals-ja/core-code-style.md index 9e051d17a5..a9cdaac86b 100644 --- a/docs/internals-ja/core-code-style.md +++ b/docs/internals-ja/core-code-style.md @@ -8,6 +8,7 @@ Yii2 コアフレームワークのコードスタイル なお、CodeSniffer のための設定をここで入手できます: https://github.com/yiisoft/yii2-coding-standards +<<<<<<< HEAD <<<<<<< HEAD ======= > Note|注意: 以下では、説明のために、サンプル・コードのドキュメントやコメントを日本語に翻訳しています。 @@ -15,6 +16,12 @@ Yii2 コアフレームワークのコードスタイル >>>>>>> yiichina/master +======= +> Note: 以下では、説明のために、サンプル・コードのドキュメントやコメントを日本語に翻訳しています。 + しかし、コアコードや公式エクステンションに対して実際に寄稿する場合には、それらを英語で書く必要があります。 + + +>>>>>>> master 1. 概要 ------- @@ -59,7 +66,7 @@ PHP コードは BOM 無しの UTF-8 のみを使わなければなりません - クラスは `CamelCase` で命名されなければなりません。 - 中括弧は常にクラス名の下の行に書かれるべきです。 - 全てのクラスは PHPDoc に従ったドキュメントブロックを持たなければなりません。 -- クラス内のすべてのコードは一個のタブによってインデントされなければなりません。 +- クラス内のすべてのコードは4個の空白によってインデントされなければなりません。 - 一つの PHP ファイルにはクラスが一つだけあるべきです。 - 全てのクラスは名前空間に属すべきです。 - クラス名はファイル名と合致すべきです。クラスの名前空間はディレクトリ構造と合致すべきです。 @@ -93,8 +100,10 @@ class Foo - Public および protected な変数はクラスの冒頭で、すべてのメソッドの宣言に先立って宣言されるべきです。 Private な変数もまたクラスの冒頭で宣言されるべきですが、 変数がクラスのメソッドのごく一部分にのみ関係する場合は、変数を扱う一群のメソッドの直前に追加しても構いません。 -- クラスにおけるプロパティの宣言の順序は public から始まり、protected、private と続くべきです。 +- クラスにおけるプロパティの宣言の順序は、その可視性に基づいて、 public から始まり、protected、private と続くべきです。 +- 同じ可視性を持つプロパティの順序については、厳格な規則はありません。 - より読みやすいように、プロパティの宣言は空行を挟まずに続け、プロパティ宣言とメソッド宣言のブロック間には2行の空行を挟むべきです。 + また、異なる可視性のグループの間に、1行の空行を追加するべきです。 - Private 変数は `$_varName` のように名付けるべきです。 - Public なクラスメンバとスタンドアロンな変数は、先頭を小文字にした `$camelCase` で名付けるべきです。 - 説明的な名前を使うこと。`$i` や `$j` のような変数は使わないようにしましょう。 @@ -105,9 +114,18 @@ class Foo >>>>>> yiichina/master +======= +そうしても意味が通じる場合は、`return` の後の `else` は避けてください。 +>>>>>>> master [ガード条件](http://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html) を使用しましょう。 ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } else { - // $result を処理 + // $result を処理 } ``` @@ -280,7 +302,7 @@ if (empty($result)) { ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } // $result を処理 @@ -369,11 +391,14 @@ $mul = array_reduce($numbers, function($r, $x) use($n) { public function getErrors($attribute = null) ``` +<<<<<<< HEAD <<<<<<< HEAD >Note|注意: ここでは読みやすさを考慮してドキュメントの内容を日本語に翻訳していますが、コアコードや公式エクステンションに対して寄稿する場合は、当然ながら、コメントには英語だけを使う必要があります。 ======= >>>>>>> yiichina/master +======= +>>>>>>> master #### ファイル ```php @@ -408,9 +433,9 @@ class Component extends \yii\base\Object * 返された [[Vector]] オブジェクトを操作して、ハンドラを追加したり削除したり出来る。 * 例えば、 * - * ~~~ + * ``` * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); - * ~~~ + * ``` * * @param string $name イベントの名前 * @return Vector イベントにアタッチされたハンドラのリスト @@ -457,10 +482,14 @@ public function getEventHandlers($name) - 一行コメントは `//` で開始されるべきです。`#` は使いません。 <<<<<<< HEAD +<<<<<<< HEAD - 一行コメントはそれ自身の行に置くべきです。 ======= - 一行コメントはそれ自体で一行を占めるべきです。 >>>>>>> yiichina/master +======= +- 一行コメントはそれ自体で一行を占めるべきです。 +>>>>>>> master 追加の規則 ---------- @@ -489,5 +518,7 @@ public function getEventHandlers($name) ### ディレクトリ/名前空間の名前 - 小文字を使います。 -- オブジェクトを表すものには複数形の名詞を使います (例えば、validators) -- 機能や特徴を表す名前には単数形を使います (例えば、web) +- オブジェクトを表すものには複数形の名詞を使います (例えば、validators)。 +- 機能や特徴を表す名前には単数形を使います (例えば、web)。 +- 出来れば単一の語の名前空間にします。 +- 単一の語が適切でない場合は、camelCase を使います。 diff --git a/docs/internals-ja/design-decisions.md b/docs/internals-ja/design-decisions.md index 862eb9afdb..90b95faa98 100644 --- a/docs/internals-ja/design-decisions.md +++ b/docs/internals-ja/design-decisions.md @@ -15,7 +15,7 @@ 3. **[認証クライアントのサポートの追加](https://github.com/yiisoft/yii2/issues/1652)** 保守性を高めるために、コアエクステンションには認証クライアントをこれ以上追加しない。 認証クライアントの追加はユーザエクステンションの形でなされるべきである。 -4. **クロージャを使うときは**、たとえ使用されないものがある場合でも、**渡されたすべてのパラメータをシグニチャに含める** ことが推奨される。 +4. **クロージャを使うときは**、たとえ使用されないものがある場合でも、**渡されるすべてのパラメータをシグニチャに含める** ことが推奨される。 このようにすると、全ての情報が直接に見えるので、コードの修正やコピーがより容易になり、どのパラメータが実際に利用できるかをドキュメントで調べる必要がなくなる。 ([#6584](https://github.com/yiisoft/yii2/pull/6584), [#6875](https://github.com/yiisoft/yii2/issues/6875)) 5. データベーススキーマでは **unsigned int より int** を使う。 diff --git a/docs/internals-ja/getting-started.md b/docs/internals-ja/getting-started.md index e1dd9e1154..d7e4f7cd8f 100644 --- a/docs/internals-ja/getting-started.md +++ b/docs/internals-ja/getting-started.md @@ -1,6 +1,7 @@ Yii2 の開発を始めよう ===================== +<<<<<<< HEAD <<<<<<< HEAD 1. yii2 のフォークをクローンします `git clone git@github.com:/yii2.git`. 2. レポジトリのフォルダに入ります `cd yii2`. @@ -49,3 +50,6 @@ Codeception のテストを実行する方法について学習するために ` ======= 環境をセットアップする方法については、[Yii 2 寄稿者のための Git ワークフロー](git-workflow.md) を参照してください。 >>>>>>> yiichina/master +======= +環境をセットアップする方法については、[Yii 2 寄稿者のための Git ワークフロー](git-workflow.md) を参照してください。 +>>>>>>> master diff --git a/docs/internals-ja/git-workflow.md b/docs/internals-ja/git-workflow.md index e266154563..1325d4273b 100644 --- a/docs/internals-ja/git-workflow.md +++ b/docs/internals-ja/git-workflow.md @@ -3,6 +3,7 @@ Yii 2 寄稿者のための Git ワークフロー Yii の開発に寄稿したい、ですって? すばらしい! <<<<<<< HEAD +<<<<<<< HEAD けれども、あなたの修正案が速やかに採用されるチャンスを増やすためには、以下のステップを踏むようにしてください (最初の二つのステップは最初に寄稿するときにだけ必要になります)。 あなたが git と github については初めてだという場合は、最初に [github help](http://help.github.com/) や [try git](https://try.github.com) を精査したり、[git internal data model](http://nfarina.com/post/9868516270/git-is-simpler) についていくらか学習したりする必要があるかもしれません。 @@ -10,13 +11,21 @@ Yii の開発に寄稿したい、ですって? すばらしい! ただし、あなたの修正案が速やかに採用されるチャンスを増やすために、以下のステップを踏むようにしてください。 あなたが git と github については初めてだという場合は、最初に [github help](http://help.github.com/) や [try git](https://try.github.com) を精査したり、[git internal data model](http://nfarina.com/post/9868516270/git-is-simpler) についていくらか学習したりする必要があるかもしれません。 +======= +ただし、あなたの修正案が速やかに採用されるチャンスを増やすために、以下のステップを踏むようにしてください。 +あなたが git と github については初めてだという場合は、最初に [github help](http://help.github.com/) や [try git](https://try.github.com) を精査したり、[git internal data model](http://nfarina.com/post/9868516270/git-is-simpler) についていくらか学習したりする必要があるかもしれません。 + +>>>>>>> master あなたの開発環境を準備する -------------------------- 以下のステップを踏むと Yii の開発環境を作成し、それを使って Yii フレームワークのコアコードに取り組むことが出来るようになります。 これらのステップは、あなたが最初に寄稿するときにだけ必要になります。 +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ### 1. github で Yii リポジトリを [Fork](http://help.github.com/fork-a-repo/) し、あなたのフォークをあなたの開発環境にクローンする ``` @@ -33,14 +42,23 @@ Yii をクローンしたディレクトリ、通常は "yii2" に入って、 git remote add upstream git://github.com/yiisoft/yii2.git ``` +<<<<<<< HEAD <<<<<<< HEAD ### 3. あなたが取り組んでいる問題が、修正するために著しい努力を要求するものである場合は、それに対する課題 (issue) が作成されていることを確認する ======= +======= +>>>>>>> master ### 3. テスト環境を準備する 以下のステップは、翻訳またはドキュメントだけに取り組みたい場合は、必要ではありません。 - `composer update` を実行して、依存パッケージをインストールします ([composer をグローバルにインストール](https://getcomposer.org/doc/00-intro.md#globally) したものと仮定しています)。 +<<<<<<< HEAD +======= + +> Note: `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` というようなエラーが生ずる場合は、`composer global require "fxp/composer-asset-plugin:~1.1.1"` を実行する必要があります。 + +>>>>>>> master - `php build/build dev/app basic` を実行して、ベーシックアプリケーションをクローンし、その依存パッケージをインストールします。 このコマンドは外部 composer パッケージは通常どおりインストールしますが、yii2 レポジトリは現在チェックアウトされているものをリンクします。 これで、インストールされる全てのコードについて、一つのインスタンスを持つことになります。 @@ -50,6 +68,12 @@ git remote add upstream git://github.com/yiisoft/yii2.git このコマンドは後日、依存パッケージを更新するためにも使用されます。 このコマンドは内部的に `composer update` を実行します。 +<<<<<<< HEAD +======= +> Note: デフォルトの git レポジトリの Url を使うため、SSH 経由で github からクローンすることになります。 +> `build` コマンドに `--useHttp` フラグを追加すれば、代りに HTTP を使うことが出来ます。 + +>>>>>>> master **これであなたは Yii 2 をハックするための作業用の遊び場を手に入れました。** 以下のステップはオプションです。 @@ -64,15 +88,26 @@ phpunit をグローバルにインストールしていない場合は、代り 取り組んでいるグループのものだけにテストを限定することが出来ます。 例えば、バリデータと redis のためのテストだけを走らせるためには、`phpunit --group=validators,redis` とします。 +<<<<<<< HEAD 利用できるグループのリストを取得するためには、`phpunit --groups` を実行してください。 +======= +利用できるグループのリストを取得するためには、`phpunit --list-groups` を実行してください。 +>>>>>>> master ### エクステンション エクステンションに取り組むためには、エクステンションのレポジトリをクローンする必要があります。 私たちは、あなたに代ってそれをするコマンドを作っています。 +<<<<<<< HEAD php build/build dev/ext +======= +``` +php build/build dev/ext +``` + +>>>>>>> master ここで `` がエクステンションの名前、例えば `redis` です。 エクステンションをアプリケーションテンプレートのどちらかでテストしたい場合は、通常そうするように、アプリケーションの `composer.json` にそれを追加するだけです。 @@ -80,6 +115,11 @@ phpunit をグローバルにインストールしていない場合は、代り `php build/build dev/app basic` を実行すると、エクステンションとその依存パッケージがインストールされ、`extensions/redis` に対するシンボリックリンクが作成されます。 こうすることで、composer の vendor ディレクトリではなく、直接に yii2 のレポジトリで作業をすることが出来るようになります。 +<<<<<<< HEAD +======= +> Note: デフォルトの git レポジトリの Url を使うため、SSH 経由で github からクローンすることになります。 +> `build` コマンドに `--useHttp` フラグを追加すれば、代りに HTTP を使うことが出来ます。 +>>>>>>> master バグ修正と機能改良に取り組む ---------------------------- @@ -87,27 +127,38 @@ phpunit をグローバルにインストールしていない場合は、代り 上記で説明したように開発環境の準備を完了すれば、機能改良やバグ修正の取り組みを開始することが出来るようになります。 ### 1. あなたが取り組もうとする問題が、修正するために著しい努力を要求するものである場合は、それに対する課題 (issue) が作成されていることを確認する +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master 全ての新機能とバグ修正は、議論とドキュメントのための単一の参照ポイントを提供するために、それに結びつく課題 (issue) を持つべきです。 数分間時間を取って、既存の課題リストに目を通し、あなたが寄稿しようとしている問題に合致するものが無いかどうか調べてください。 もし課題リストにすでに挙っているのを見つけた場合は、その課題にコメントを残して、あなたがその項目について取り組もうとしていることを示してください。 <<<<<<< HEAD +<<<<<<< HEAD あなたが取り組もうとしている問題に合致する既存の課題が見つからなかった場合は、新しい課題を作成してください。 ======= あなたが取り組もうとしている問題に合致する既存の課題が見つからなかった場合は、[新しい課題を立て](report-an-issue.md) てください。 >>>>>>> yiichina/master +======= +あなたが取り組もうとしている問題に合致する既存の課題が見つからなかった場合は、[新しい課題を立て](report-an-issue.md) てください。 +>>>>>>> master 単純な修正であれば、直接にプルリクエストをしてください。 こうすることで、開発チームはあなたの提案をレビューし、将来にわたって適切なフィードバックを提供することが可能になります。 > 小さな変更や、ドキュメントの問題、または単純な修正については、課題を作成する必要はありません。 それらについては、プルリクエストだけで十分です。 +<<<<<<< HEAD <<<<<<< HEAD ### 4. メインの Yii ブランチから最新のコードをフェッチする ======= ### 2. メインの Yii ブランチから最新のコードをフェッチする >>>>>>> yiichina/master +======= +### 2. メインの Yii ブランチから最新のコードをフェッチする +>>>>>>> master ``` git fetch upstream @@ -115,11 +166,15 @@ git fetch upstream 最新のコードに対して作業することを保証するために、すべての新しい寄稿においてこのステップから作業を開始すべきです。 +<<<<<<< HEAD <<<<<<< HEAD ### 5. 現在の Yii のマスターブランチに基いて、あなたの寄稿のための新しいブランチを作成する ======= ### 3. 現在の Yii のマスターブランチに基いて、あなたの寄稿のための新しいブランチを作成する >>>>>>> yiichina/master +======= +### 3. 現在の Yii のマスターブランチに基いて、あなたの寄稿のための新しいブランチを作成する +>>>>>>> master > これは非常に重要です。なぜなら、master ブランチを使うと、あなたのアカウントからは一つ以上のプルリクエストを送信することが出来なくなるからです。 @@ -133,25 +188,33 @@ git checkout upstream/master git checkout -b 999-name-of-your-branch-goes-here ``` +<<<<<<< HEAD <<<<<<< HEAD ### 6. あなたの魔法を使って、あなたのコードを書く ======= ### 4. あなたの魔法を使って、あなたのコードを書く >>>>>>> yiichina/master +======= +### 4. あなたの魔法を使って、あなたのコードを書く +>>>>>>> master 動くことを確認してくださいね :) 単体テストは常に歓迎されます。テストされ、十分にカバーされたコードは、あなたの寄稿をチェックするタスクを非常に単純化してくれます。 単体テストの失敗を中身とする課題を立てることも許容されています。 +<<<<<<< HEAD <<<<<<< HEAD ### 7. CHANGELOG を更新する ======= ### 5. CHANGELOG を更新する >>>>>>> yiichina/master +======= +### 5. CHANGELOG を更新する +>>>>>>> master CHANGELOG ファイルを編集して、あなたの修正を追加します。 -新しい行は、ファイル冒頭の "Work in progress" 見出しの下に挿入してください。 +新しい行は、ファイル冒頭の最初の見出し (現在開発中のバージョン) の下に挿入してください。 チェンジログの行は、下記のどちらかのように書いてください。 ``` @@ -164,11 +227,15 @@ Enh #999: 機能拡張の内容説明 (あなたの名前) 非常に小さな修正、すなわち、タイポやドキュメントの修正については、CHANGELOG を更新する必要はありません。 +<<<<<<< HEAD <<<<<<< HEAD ### 8. 修正をコミットする ======= ### 6. 修正をコミットする >>>>>>> yiichina/master +======= +### 6. 修正をコミットする +>>>>>>> master 以下のコマンドを使って、コミットしたいファイルや変更を [staging area](http://gitref.org/basic/#add) に追加します。 @@ -182,14 +249,18 @@ git add path/to/my/file.php github があなたのコミットを自動的にチケットとリンクするように、必ず `#XXX` でチケット番号に言及してください。 ``` -git commit -m "#42 を解決する変更の短い説明をここに入れる" +git commit -m "#999 を解決する変更の短い説明をここに入れる" ``` +<<<<<<< HEAD <<<<<<< HEAD ### 9. 最新の Yii コードを upstream からあなたのブランチにプルする ======= ### 7. 最新の Yii コードを upstream からあなたのブランチにプルする >>>>>>> yiichina/master +======= +### 7. 最新の Yii コードを upstream からあなたのブランチにプルする +>>>>>>> master ``` git pull upstream master @@ -199,20 +270,30 @@ git pull upstream master もし何かマージ衝突がある場合は、ここでそれを修正してから再度変更をコミットすべきです。 こうすると、Yii 開発チームがワンクリックであなたの変更をマージすることが確実に出来るようになります。 +<<<<<<< HEAD <<<<<<< HEAD ### 10. 衝突をすべて解決したら、あなたのコードを github にプッシュする ======= ### 8. 衝突をすべて解決したら、あなたのコードを github にプッシュする >>>>>>> yiichina/master +======= +### 8. 衝突をすべて解決したら、あなたのコードを github にプッシュする +>>>>>>> master ``` git push -u origin 999-name-of-your-branch-goes-here ``` +<<<<<<< HEAD <<<<<<< HEAD `-u` パラメータによって、あなたのブランチが今後は自動的に github のブランチに対してプッシュおよびプルを行うようになります。 +======= +`-u` パラメータによって、今後はあなたのそのブランチが github のブランチに対して自動的にプッシュおよびプルされるようになります。 +>>>>>>> master つまり、次回 `git push` とタイプしたときには、git は既にどこにプッシュすればよいか知っている、ということです。 +こうしておくと、後でプルリクエストにコミットを追加したくなった場合に便利です。 +<<<<<<< HEAD ### 11. upstream に対して [プルリクエスト](http://help.github.com/send-pull-requests/) を発行する ======= `-u` パラメータによって、今後はあなたのそのブランチが github のブランチに対して自動的にプッシュおよびプルされるようになります。 @@ -221,17 +302,24 @@ git push -u origin 999-name-of-your-branch-goes-here ### 9. upstream に対して [プルリクエスト](http://help.github.com/send-pull-requests/) を発行する >>>>>>> yiichina/master +======= +### 9. upstream に対して [プルリクエスト](http://help.github.com/send-pull-requests/) を発行する +>>>>>>> master github 上のあなたのリポジトリに入って、"Pull Request" をクリックし、右側にあるブランチを選び、コメントボックスにもう少し詳細を記述します。 プルリクエストを課題とリンクさせるために、プルコメントのどこかに `#999` という書式で課題番号を記載します。 > 全てのプルリクエストはそれぞれ一つの課題を解決すべきものであることに注意してください。 +<<<<<<< HEAD <<<<<<< HEAD ### 12. 誰かがあなたのコードをレビューする ======= ### 10. 誰かがあなたのコードをレビューする >>>>>>> yiichina/master +======= +### 10. 誰かがあなたのコードをレビューする +>>>>>>> master 誰かがあなたのコードをレビューします。あなたは修正を求められるかも知れません。その場合は、ステップ #6 に戻ってください (現在のプルリクエストが open である限りは、別の新しいプルリクエストをする必要はありません)。 @@ -240,11 +328,15 @@ github 上のあなたのリポジトリに入って、"Pull Request" をクリ 必要とする機能は人によってさまざまに異なりますし、Yii は全ての人に対して全てを提供することは出来ません。 また、却下された場合でも、あなたのコードはそれを必要とする人々から参照されるように github 上で公開され続けます。 +<<<<<<< HEAD <<<<<<< HEAD ### 13. クリーンアップする ======= ### 11. クリーンアップする >>>>>>> yiichina/master +======= +### 11. クリーンアップする +>>>>>>> master あなたのコードが受け入れられるか却下されるかした後、あなたが作業してきたブランチをローカルレポジトリおよび `origin` から削除することが出来ます。 @@ -283,7 +375,7 @@ git checkout -b 999-name-of-your-branch-goes-here /* 魔法を使い、必要なら changelog を更新 */ git add path/to/my/file.php -git commit -m "A brief description of this change which fixes #42 goes here" +git commit -m "A brief description of this change which fixes #999 goes here" git pull upstream master git push -u origin 999-name-of-your-branch-goes-here ``` diff --git a/docs/internals-ja/pull-request-qa.md b/docs/internals-ja/pull-request-qa.md new file mode 100644 index 0000000000..23b9c9556b --- /dev/null +++ b/docs/internals-ja/pull-request-qa.md @@ -0,0 +1,10 @@ +vNGXg̕iۏ +======================== + +PR }[W邩ۂ`FbNƂɂ́AɈȉ̊lׂłB + +- PR ɃNĂۑ(CbV[)݂邩A܂́APR ǂ̂悤ȂƂCȂlj悤ƂĂ̂Ɋւ\Ȑ邱ƁB +- P̃eXgBK{ł͂܂񂪁Aɍ]܂BPR ɂďCꂽR[hΎsAƂeXgł邱ƁB +- CHANGELOG ̃Gg邱ƁBGg͎̃[X̃ZNVɁACbV[̃^CvƔԍ̏ɏ܂B + S҂̃jbNl[邱ƁB +- [R[hX^C](core-code-style.md) [r[R[hX^C](view-code-style.md) OK ł邱ƁB́A}[WۂɁA}[W҂̔fɏ]ďCꍇ܂B diff --git a/docs/internals-ja/report-an-issue.md b/docs/internals-ja/report-an-issue.md index c305b2c37d..3f9a38fe6d 100644 --- a/docs/internals-ja/report-an-issue.md +++ b/docs/internals-ja/report-an-issue.md @@ -7,12 +7,18 @@ * 出来れば、**完全な**エラーのコールスタックを提供して下さい。問題を説明するスクリーンショットは大いに歓迎されます。 * 問題を再現する手順を記述して下さい。問題を再現するコードを提供してくださるならば、なお一層良いでしょう。 <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * 可能であれば、さらに進んで、失敗する単体テストを作成して [プルリクエストを発行](git-workflow.md) してくださっても構いません。 課題が公式エクステンションのどれかと関係がある場合は、エクステンションのレポジトリで課題を報告してください。 エクステンションとの関係がよく分らない場合は、[メインのレポジトリに報告](https://github.com/yiisoft/yii2/issues/new) () してください。 +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master **以下の場合は課題を報告しないでください** diff --git a/docs/internals-ja/translation-workflow.md b/docs/internals-ja/translation-workflow.md index f37d31553c..9c22a37fc5 100644 --- a/docs/internals-ja/translation-workflow.md +++ b/docs/internals-ja/translation-workflow.md @@ -12,14 +12,14 @@ Yii は国際的なアプリケーションと開発者にとって役に立つ メッセージの翻訳を開始するためには: -1. `framework/messages/config.php` をチェックして、あなたの言語が `languages` のリストに挙っていることを確認してください。 - もし挙っていなければ、あなたの言語をそこに追加します (リストをアルファベット順に保つことを忘れないでください)。 +1. `framework/messages/config.php` をチェックして、あなたの言語が `languages` のリストに載っていることを確認してください。 + もし無ければ、あなたの言語をそこに追加します (リストをアルファベット順に保つことを忘れないでください)。 言語コードの形式は、例えば `ru` や `zh-CN` のように、[IETF言語タグ](http://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0) に従うべきです。 -2. `framework` に入って、`yii message/extract messages/config.php` を走らせます。 +2. `framework` に入って、`./yii message/extract --languages=` を走らせます。 3. `framework/messages/your_language/yii.php` のメッセージを翻訳します。ファイルは必ず UTF-8 エンコーディングを使って保存してください。 -4. [プルリクエスト](https://github.com/yiisoft/yii2/blob/master/docs/internals/git-workflow.md) をします。 +4. [プルリクエスト](git-workflow.md) をします。 -あなたの翻訳を最新状態に保つために、`yii message/extract messages/config.php` を再び走らせることが出来ます。 +あなたの翻訳を最新状態に保つために、`./yii message/extract --languages=` を再び走らせることが出来ます。 このコマンドは、変更のなかった箇所には触れることなく、自動的にメッセージを再抽出してくれます。 翻訳ファイルの中で、配列の各要素は、メッセージ(キー)と翻訳(値)をあらわします。 @@ -42,3 +42,5 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat ``` このコマンドが composer に関して不平を言うようであれば、ソースのルートディレクトリで `composer install` を実行してください。 + +ドキュメントの文法の情報およびスタイルガイドに関して、[documentation_style_guide.md](../documentation_style_guide.md) を参照して下さい。 diff --git a/docs/internals-ja/versions.md b/docs/internals-ja/versions.md index daf226b37c..35bb174c19 100644 --- a/docs/internals-ja/versions.md +++ b/docs/internals-ja/versions.md @@ -1,6 +1,7 @@ Yii バージョン規約 ================== +<<<<<<< HEAD <<<<<<< HEAD この文書は Yii のバージョン番号に関するポリシーを要約するものです。 概して言えば、Yii は [Semantic Versioning](http://semver.org/) に従います。 @@ -26,6 +27,25 @@ ferver の記事は、Semantic Versioning を使おうが使うまいが、こ 理想的には、互換性が損なわれる可能性を減らすために、バグ修正のみを含ませたいと私たちは望んでいます。 実際には、2.0.x はより頻繁にリリースされており、また、小さな機能改良をも追加して、ユーザが新機能をより早く享受できるようにしています。 >>>>>>> yiichina/master +======= +この文書は Yii のバージョン付与ポリシーを要約するものです。 +私たちの現在のバージョン付与戦略は、[ferver](https://github.com/jonathanong/ferver) によって記述されているものです。 +私たちはこれを [Semantic Versioning](http://semver.org/) より現実的で合理的であると考えます +(詳細については [#7408](https://github.com/yiisoft/yii2/issues/7408) を参照してください)。 + +コア開発者チームの内部では、2.0.x リリースを 100% 後方互換に保つことが重要であることが、何度かに渡って強調されました。 +しかし、これは理想としての計画です。 +ferver の記事は、Semantic Versioning を使おうが使うまいが、これが現実には達成が困難な計画であることを示す現実世界の実例を示しています。 + +要約すれば、私たちのバージョン付与ポリシーは次のようになります。 + + +## パッチリリース `2.x.Y` + +パッチリリースは、100% 後方互換であるべきです。 +理想的には、互換性が損なわれる可能性を減らすために、バグ修正のみを含ませたいと私たちは望んでいます。 +実際には、2.0.x はより頻繁にリリースされており、また、小さな機能改良をも追加して、ユーザが新機能をより早く享受できるようにしています。 +>>>>>>> master * `2.x` という名前のブランチ上で保守される。 * 主としてバグ修正と小さな機能強化を含む。 * 大きな機能拡張はしない。 @@ -37,12 +57,18 @@ ferver の記事は、Semantic Versioning を使おうが使うまいが、こ ## マイナーリリース `2.X.0` +<<<<<<< HEAD <<<<<<< HEAD ======= 後方互換性を損ないうる大きな機能と変更を含む非後方互換なリリース。 以前のバージョンからのアップグレードは簡単ではないかもしれないが、完全なアップグレードのガイド、または可能であればスクリプトが提供される。 >>>>>>> yiichina/master +======= +後方互換性を損ないうる大きな機能と変更を含む非後方互換なリリース。 +以前のバージョンからのアップグレードは簡単ではないかもしれないが、完全なアップグレードのガイド、または可能であればスクリプトが提供される。 + +>>>>>>> master * マスターブランチ上で開発。 * 主として新機能とバグ修正を含む。 * パッチリリースからマージされた小さな機能強化とバグ修正を含む。 @@ -54,6 +80,7 @@ ferver の記事は、Semantic Versioning を使おうが使うまいが、こ ## メジャーリリース `X.0.0` +<<<<<<< HEAD <<<<<<< HEAD 計画はない。 ======= @@ -63,3 +90,10 @@ ferver の記事は、Semantic Versioning を使おうが使うまいが、こ > Note|注意: 公式エクステンションも同じバージョン付与ポリシーに従っていますが、フレームワークとは独立にリリースされることがあります。 すなわち、フレームワークとエクステンションの間で、バージョン番号が異なることが予想されます。 >>>>>>> yiichina/master +======= +1.0 に対する 2.0 など。 +これは外部的な技術の進歩 (例えば、PHP が 5.0 から 5.4 へアップグレードされた、など) に依存して、3年から5年の間に一度だけ生じるものであると私たちは予想しています。 + +> Note: 公式エクステンションも同じバージョン付与ポリシーに従っていますが、フレームワークとは独立にリリースされることがあります。 + すなわち、フレームワークとエクステンションの間で、バージョン番号が異なることが予想されます。 +>>>>>>> master diff --git a/docs/internals-pt-BR/translation-workflow.md b/docs/internals-pt-BR/translation-workflow.md index 77bc4e2b15..8e7a325bb7 100644 --- a/docs/internals-pt-BR/translation-workflow.md +++ b/docs/internals-pt-BR/translation-workflow.md @@ -75,10 +75,14 @@ Regras e Observações - action — ação - application system - sistema <<<<<<< HEAD +<<<<<<< HEAD - application template — template de aplicação ======= - project template — template de projetos >>>>>>> yiichina/master +======= +- project template — template de projetos +>>>>>>> master - controller — controller (controlador) - eager loading — eager loading (carregamento na inicialização) - lazy loading — lazy loading (carregamento retardado) @@ -93,10 +97,19 @@ Regras e Observações - inline action — ação inline - standalone action — ação standalone <<<<<<< HEAD +<<<<<<< HEAD ======= - advanced project template — template avançado de projetos - basic project template — template básico de projetos >>>>>>> yiichina/master +======= +- advanced project template — template avançado de projetos +- basic project template — template básico de projetos +- behaviors — behaviors (comportamentos) +- pretty URL — URL amigável (pretty URL) +- class member variable - atributo da classe +- endpoint - URL (também chamadas *endpoints*) +>>>>>>> master ### Termos Sem Tradução @@ -114,8 +127,17 @@ Regras e Observações - backend - frontend <<<<<<< HEAD +<<<<<<< HEAD - web service ======= - web service - template >>>>>>> yiichina/master +======= +- web service +- template +- query string +- case-sensitive +- case-insensitive +- callback +>>>>>>> master diff --git a/docs/internals-ru/automation.md b/docs/internals-ru/automation.md new file mode 100644 index 0000000000..71973e2242 --- /dev/null +++ b/docs/internals-ru/automation.md @@ -0,0 +1,15 @@ +Автоматизация +============= + +При работе над Yii, некоторые задачи можно выполнять автоматически: + +- Генерация карты классов в файл `classes.php`, который находится в корневой директории фреймворка. + Запустите `./build/build classmap` для его генерации. + +- Генерация аннотаций `@property` в файлах классов, описывающих свойства, введённые геттерами и сеттерами. + Запустите `./build/build php-doc/property` для их обновления. + +- Исправление стиля кодирования и других мелких ошибок в комментариях phpdoc. + Запустите `./build/build php-doc/fix` для их исправления. + Проверьте изменения перед тем, как их закоммитить, так как могут быть нежелательные изменения, потому что команда не + является совершенной. Вы можете использовать `git add -p` для обзора изменений. diff --git a/docs/internals-ru/blocktypes.json b/docs/internals-ru/blocktypes.json new file mode 100644 index 0000000000..d3137e5536 --- /dev/null +++ b/docs/internals-ru/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Внимание:", + "Note:": "Примечание:", + "Info:": "Информация:", + "Tip:": "Подсказка:" +} diff --git a/docs/internals-ru/core-code-style.md b/docs/internals-ru/core-code-style.md new file mode 100644 index 0000000000..c8b73748c2 --- /dev/null +++ b/docs/internals-ru/core-code-style.md @@ -0,0 +1,477 @@ +Стиль кодирования Yii2 framework +============================== + +Описанный ниже стиль кодирования используется при разработке ядра Yii 2.x и его официальных расширений. Если вы хотите участвовать в разработке фреймворка, постарайтесь придерживаться данного стиля. Мы не принуждаем вас использовать этот стиль при разработке ваших приложений с использованием Yii 2. В данном случае можете использовать тот стиль, который вам больше подходит. + +Пример конфигурационного файла для CodeSniffer вы можете найти здесь: https://github.com/yiisoft/yii2-coding-standards + +1. Обзор +----------- + +В общем, мы используем совместимый со стандартом [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) стиль, так что все положения [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) вполне применимы к нашему стилю кодирования. + +- ДОЛЖНЫ использоваться только открывающие теги `` или `` не нужен; +- Не добавляйте лишние пробелы в конец строки; +- Все файлы, содержащие PHP код, должны иметь расширение `.php`. + +### 2.2. Кодировка символов + +PHP код должен содержать только символы в кодировке UTF-8 без BOM. + +3. Имена Классов +-------------- + +Имена классов ДОЛЖНЫ быть определены используя `StudlyCaps`. Например, `Controller`, `Model`. + +4. Классы +---------- + +В данном случае, под классом подразумеваются все классы и интерфейсы. + +- При именовании классов следует использовать `CamelCase`; +- Открывающая фигурная скобка всегда должна быть на следующей строке после имена класса; +- Все классы должны быть документированы в соответствии с PHPDoc; +- Весь код класса должен быть выровнен с использованием 4 пробелов; +- В одном PHP файле должен быть только один класс; +- Все классы должны использовать пространства имен; +- Имя класса должно совпадать с именем файла. Пространство имен класса должно соответствовать структуре каталогов. + +```php +/** + * Документация + */ +class MyClass extends \yii\Object implements MyInterface +{ + // код +} +``` + +### 4.1. Константы + +Константы класса ДОЛЖНЫ быть объявлены в верхнем регистре с подчеркиванием в качестве разделителей. +Пример: + +```php + 'Yii', + 'options' => ['usePHP' => true], +]; +``` + +### 5.4 Управляющие конструкции + +- Оставляйте один пробел перед открывающей круглой скобкой и после закрывающей круглой скобки в управляющих конструкциях; +- Операторы внутри круглых скобок должны разделяться пробелами; +- Открывающая фигурная скобка должна быть на той же строке; +- Закрывающая фигурная скобка должна быть на новой строке; +- Всегда используйте фигурные скобки для однострочных выражений. + +```php +if ($event === null) { + return new Event(); +} +if ($event instanceof CoolEvent) { + return $event->instance(); +} +return null; + + +// такой код недопустим: +if (!$model && null === $event) + throw new Exception('test'); +``` + +Старайтесь избегать использования `else` после `return` там, где это возможно. +Используйте [граничные операторы](https://refactoring.guru/ru/replace-nested-conditional-with-guard-clauses). + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} else { + // дальнейшие вычисления +} +``` + +лучше переписать так: + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} + +// дальнейшие вычисления +``` + +#### switch + +Используйте следующий стиль для switch: + +```php +switch ($this->phpType) { + case 'string': + $a = (string) $value; + break; + case 'integer': + case 'int': + $a = (int) $value; + break; + case 'boolean': + $a = (bool) $value; + break; + default: + $a = null; +} +``` + +### 5.5 Вызовы функций + +```php +doIt(2, 3); + +doIt(['a' => 'b']); + +doIt('a', [ + 'a' => 'b', + 'c' => 'd', +]); +``` + +### 5.6 Анонимные (lambda) функции + +Не забывайте про пробелы между ключевыми словами `function`/`use` и открывающими круглыми скобками: + +```php +// правильно +$n = 100; +$sum = array_reduce($numbers, function ($r, $x) use ($n) { + $this->doMagic(); + $r += $x * $n; + return $r; +}); + +// неправильно +$n = 100; +$mul = array_reduce($numbers, function($r, $x) use($n) { + $this->doMagic(); + $r *= $x * $n; + return $r; +}); +``` + +Документация +------------- + +- Для получения информации по синтаксису документации обратитесь к первоисточнику [PHPDoc](http://phpdoc.org/); +- Код без документации недопустим; +- Все файлы классов должны содержать блок документации в начале файла и блок документации непосредственно перед каждым классом; +- Нет необходимости использовать тег `@return` если метод не возвращает значение; +- Все виртуальные свойства классов, наследованных от `yii\base\Object`, документируются тегом `@property` в блоке документации класса; + Аннотации геттеров и сеттеров автоматически генерируются из соответствующих тегов `@return` or `@param` + посредством выполнения команды `./build php-doc` в соответствующем каталоге; + Вы можете добавить дополнительный тег `@property` для геттера или сеттера для пояснения назначения переменной метода, если это необходимо. + Например: + +```php + + * @since 2.0 + */ +class Component extends \yii\base\Object +``` + + +#### Функция / метод + +```php +/** + * Returns the list of attached event handlers for an event. + * You may manipulate the returned [[Vector]] object by adding or removing handlers. + * For example, + * + * ``` + * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); + * ``` + * + * @param string $name the event name + * @return Vector list of attached event handlers for the event + * @throws Exception if the event is not defined + */ +public function getEventHandlers($name) +{ + if (!isset($this->_e[$name])) { + $this->_e[$name] = new Vector; + } + $this->ensureBehaviors(); + return $this->_e[$name]; +} +``` + +#### Разметка + +Как вы можете видеть в примерах выше, мы используем специальную разметку для форматирования комментариев PHPDoc. + +Ниже описан дополнительный синтаксис для описания связей между классами, методами и свойствами в документации: + +- `'[[canSetProperty]] ` создаст ссылку на метод или свойство `canSetProperty` этого класса; +- `'[[Component::canSetProperty]]` создаст ссылку на метод `canSetProperty` класса `Component` того же пространства имен; +- `'[[yii\base\Component::canSetProperty]]` создаст ссылку на метод `canSetProperty` класса `Component` в пространстве имен `yii\base`; +- `'[[Component]]` создаст ссылку на класс `Component` в том же пространстве имен. Здесь так же возможно явное указание пространства имен. + +Для явного указания текста ссылки возможно использование следующего синтаксиса: + +``` +... as displayed in the [[header|header cell]]. +``` + +Часть до | это имя свойства, метода или класса для ссылки, а часто поле | это текст ссылки. + +Так же, возможно создание ссылок на Руководство: + +```markdown +[Руководство](guide:file-name.md) +[Раздел руководства](guide:file-name.md#subsection) +``` + + +#### Комментарии + +- Однострочные комментарии должны начинаться с `//`, а не с `#`; +- Однострочные комментарии должны располагаться на отдельной строке. + +Дополнительные правила +---------------- + +### `=== []` или `empty()` + +Используйте `empty()`, где это возможно. + +### Несколько точек возврата + +Не допускайте запутанных вложенных условных конструкций, используйте return. Для коротких методов это не актуально. + +### `self` или `static` + +Всегда используйте `static`, за исключением следующих случаев: + +- доступ к константам ДОЛЖЕН осуществляться через `self`: `self::MY_CONSTANT`; +- доступ к защищенным статическим свойствам ДОЛЖЕН осуществляться через `self`: `self::$_events`; +- допустимо использовать `self` для рекурсивного обращения к текущему классу, вместо класса наследника. + +### Значение "ничего не делать" + +Свойства указывающее компоненту на отсутствие необходимости что-либо делать, должны принимать значение `false`. `null`, `''`, or `[]` не должны использоваться в таких случаях. + +### Каталоги/пространства имен + +- Используйте нижний регистр; +- используйте множественную форму для существительных, представляющих объекты (например валидаторы); +- используйте единичную форму для имен, представляющих соответствующий функционал (например web). diff --git a/docs/internals-ru/getting-started.md b/docs/internals-ru/getting-started.md index bec4223934..ba15a13d6c 100644 --- a/docs/internals-ru/getting-started.md +++ b/docs/internals-ru/getting-started.md @@ -1,45 +1,4 @@ Подготовка к разработке Yii2 -============================ +===================================== -1. Создаём клон своего форка yii2 `git clone git@github.com:<ваше имя>/yii2.git`. -2. Переходим в папку репозитория `cd yii2`. -3. Запускаем `./build/build app/link basic` для установки composer зависимостей приложения basic. - *Эта команда установит сторонние пакеты composer как обычно, но создаст ссылку с репозитория yii2 - на только что загуженный репозиторий. Таким образом у вас будет только один экземпляр кода.* -4. При необходимости делаем тоже самое для приложения advanced: `./build/build app/link advanced` - Внутри эта команда использует `composer update` для обновления кода. -5. Теперь у нас есть рабочая площадка для экспериментов с Yii 2. - -Можно так же добавить репозиторий yii2 upstream для получения последних изменений: - -``` -git remote add upstream https://github.com/yiisoft/yii2.git -``` - -Пожалуйста ознакомьтесь с разделом «[рабочий процесс Git для разработчиков Yii 2](git-workflow.md)» -для получения подробной информации о создании pull request-ов. - -Модульные тесты ---------------- - -Для запуска модульных тестов нужно установить composer пакеты для dev-репозитория. -В корневой директории делаем `composer update` для получения последней версии пакетов. - -Теперь можно выполнить модульные тесты, запустив `phpunit`. - -Можно ограничиться группой тестов, над которыми вы работаете. Например, следующая команда запустит тесты только для -валидаторов и redis `phpunit --group=validators,redis`. - -Расширения ----------- - -Для работы над расширениями необходимо установить их в приложение. Добавляем их в `composer.json` как обычно. Например, -добавим `"yiisoft/yii2-redis": "*"` в секцию `require` для приложения basic. -Запускаем `./build/build app/link basic` для установки расширения, его зависимостей и создания символической -ссылки на `extensions/redis`. Теперь вы работаете с репозиторием yii2, а не с директорией vendor. - -Функциональные и приёмочные тесты для приложений ------------------------------------------------- - -Cмотрите `apps/advanced/tests/README.md` и `apps/basic/tests/README.md`, чтобы узнать о том как запускать -тесты Codeception. +Смотрите [Рабочий процесс Git для разработчиков Yii2](git-workflow.md) о том, как подготовить ваше рабочее окружение. diff --git a/docs/internals-ru/git-workflow.md b/docs/internals-ru/git-workflow.md new file mode 100644 index 0000000000..5c8f91470e --- /dev/null +++ b/docs/internals-ru/git-workflow.md @@ -0,0 +1,242 @@ +Рабочий процесс Git для разработчиков Yii2 +========================================== + +Итак, вы хотите разрабатывать Yii? Хорошо! Но для того чтоб увеличить шанс принятия ваших изменений, +пожалуйста следуйте следующим шагам. Если вы новичок в git и github, вы можете сначала проверить +[github help](http://help.github.com/), [try git](https://try.github.com) или прочитать о +[внутренней модели данных git](http://nfarina.com/post/9868516270/git-is-simpler). + +Подготовка вашего рабочего окружения +------------------------------------ + +Следующие шаги будут создавать рабочее окружение для Yii, которое вы можете использовать для работы над основным кодом +фреймворка. *Эти шаги будут нужны только в первый раз*. + +### 1. [Форкаем](http://help.github.com/fork-a-repo/) репозиторий Yii на github и клонируйте ваш форк в ваше рабочее окружение. + +``` +git clone git@github.com:YOUR-GITHUB-USERNAME/yii2.git +``` + +Если у вас есть проблемы с настройкой GIT для работы с GitHub в Linux, или вы получаете ошибку похожую на "Permission Denied +(publickey)", тогда вы должны настроить ваш GIT по [этой инструкции](http://help.github.com/linux-set-up-git/) + +### 2. Добавляем главный репозиторий Yii как дополнительный внешний git репозиторий называемый "upstream" + +Перейдите в каталог, куда вы склонировали Yii, как правило "yii2". Затем введите следующую команду: + +``` +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +### 3. Настройка тестовой среды + +Следующие шаги не обязательны, если вы хотите работать только с переводом или документацией. + +- выполните `composer update` для установки зависимостей (если [composer у вас установлен глобально](https://getcomposer.org/doc/00-intro.md#globally)). + +> Note: Если вы видите такие ошибки, как `Problem 1 The requested package bower-asset/jquery could not be found in +> any version, there may be a typo in the package name.`, необходимо запустить `composer global require "fxp/composer-asset-plugin:~1.1.1"` + +- выполните `php build/build dev/app basic` для клонирования базового приложения и установки его зависимостей. + Эта команда установит сторонние пакеты composer обычным образом, но создаст ссылку с репозитория yii2 на только + что загруженный репозиторий. Таким образом у вас будет только один экземпляр кода. + + При необходимости делаем тоже самое для приложения advanced: `php build/build dev/app advanced`. + + Данная команда также может быть использована для обновления зависимостей, внутри она использует `composer update`. + +> Note: по умолчанию URL репозиториев git на GitHub работают через SSH. Чтобы использовать HTTPS, добавьте +> флаг `--useHttp` к команде `build`. + +**Теперь у нас есть рабочая площадка для экспериментов с Yii 2.** + +Следующие шаги не обязательны. + +### Модульные тесты + +Вы можете выполнить модульные тесты с помощью команды `phpunit` в корневой директории приложения. Если у вас phpunit +не установлен глобально, вы можете запустить `php vendor/bin/phpunit`. + +Некоторые тесты требуют дополнительно установки и настройки баз данных. Вы можете создать `tests/data/config.local.php` +для переопределения настроек, которые определены в `tests/data/config.php`. + +Вы можете ограничить тестирование группой тестов, с которой вы сейчас работаете, например запускать только тесты для +валидаторов и redis. Вы можете получить список доступных групп выполнив `phpunit --list-groups`. + +### Расширения + +Для работы над расширениями вы можете склонировать репозиторий расширения. Мы сделали команду, которая поможет вам +сделать это: + +``` +php build/build dev/ext +``` + +где `` это имя расширения, например `redis`. + +Если вы хотите протестировать расширение в одном из шаблонов приложений, просто добавьте его в `composer.json` +приложения, например добавив `"yiisoft/yii2-redis": "*"` в секцию `require` базового приложения. +Запустите `php build/build dev/app basic` для установки расширения и его зависимостей и создания символической +ссылки на `extensions/redis` так чтоб вы работали не папке вендорных пакетов composer, а напрямую в репозиторий yii2. + +> Note: по умолчанию URL репозиториев git на GitHub работают через SSH. Чтобы использовать HTTPS, добавьте +> флаг `--useHttp` к команде `build`. + +Работа над багами и новыми функциями +------------------------------------ + +Приготовив вашу среду разработки вы можете начать работу над исправлением багов и разработкой новых функций. + +### 1. Убедитесь, что issue создана для того, над чем вы работаете, если потребуется много времени для исправления + +Все новые функции и баги должны быть связаны с issue для того, чтоб иметь единое место для обсуждения и документирования. +Потратьте несколько минут на поиск существующей issue, которая соответствует вашим изменениям. Если вы найдёте её в +списке, то пожалуйста оставьте комментарий, что вы начали над ней работать. Если вы не нашли нужной issue пожалуйста +[создайте новую issue](report-an-issue.md) или создайте сразу запрос на изменения если это простые изменения. +Это позволит команде проверить ваше предложение, и обеспечивать соответствующую обратную связь. + +> Для небольших изменений или документации, или простых исправлений, вам нет необходимости создавать issue, + запрос на изменения в этом случае подходит лучше. + +### 2. Вытягивание последнего кода из основного репозитория Yii + +``` +git fetch upstream +``` + +Вы должны начинать с этого действия работу над каждым новым предложением, убеждайтесь что вы работаете над самой +последней версией кода. + +### 3. Создание новой ветки для ваших изменений, основанных на текущем мастере Yii + +> Это очень важно, так как вы не сможете отправлять более одного запроса на изменения из вашего репозитория, если + будете использовать ветку master. + +Каждое отдельное исправление или изменение должно разрабатываться в своей ветке. Имя ветки должно быть описательным +и начинаться с номера issue, с которым связан ваш код. Если вы не исправляете какой-то конкретный issue, просто +пропустите номер. +Например: + +``` +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here +``` + +### 4. Делайте свою магию, пишите ваш код + +Убедитесь, что он работает :) + +Модульные тесты всегда приветствуются. Протестированный и с хорошим покрытием код значительно упрощает задачу проверки +вашего предложения. Сломанные модульные тесты, как описание проблемы, тоже приветствуются. + +### 5. Обновите CHANGELOG + +Отредактируйте файл CHANGELOG, включив ваши изменения. Для этого нужно вставить строки в начало файла под заголовком +"Work in progress", строки с описанием изменений должны выглядеть похожими на следующие: + +``` +Bug #999: a description of the bug fix (Your Name) +Enh #999: a description of the enhancement (Your Name) +``` + +`#999` это номер issue описывающей `Bug` или `Enh`. +Лог изменений должен быть сгруппирован по типу (`Bug`,`Enh`) и отсортирован по номеру issue. + +Для очень маленьких исправлений, например опечаток и изменений документации, нет необходимости обновлять CHANGELOG. + +### 6. Фиксация ваших изменений + +Добавляем файлы/изменения которые вы хотите зафиксировать в [staging area](http://gitref.org/basic/#add) + +``` +git add path/to/my/file.php +``` + +Вы можете использовать опцию `-p` для того, чтоб выбрать, какие изменения вы хотите добавить в коммит. + +Фиксируйте ваши изменения с описательным сообщением. Убедитесь что в сообщение есть номер `#XXX`, так github +автоматически свяжет ваш коммит с тикетом: + +``` +git commit -m "A brief description of this change which fixes #999 goes here" +``` + +### 7. Получение последнего кода из апстрима Yii в вашу ветку + +``` +git pull upstream master +``` + +Это гарантирует, что вы используете самый последний код в вашей ветке перед отправкой запроса на изменения. Если есть +какие-либо конфликты слияния, вы должны исправить их и зафиксировать изменения ещё раз. Это гарантирует, что команда Yii +сможет слить ваши изменения одним кликом. + +### 8. Разрешив зависимости, отправляем код на github + +``` +git push -u origin 999-name-of-your-branch-goes-here +``` + +Опция `-u` сохранит указание на ветку в github, чтобы при следующем выполнении `git push`, git знал, куда отправлять +изменения. Это полезно, если вы хотите позже отправлять ещё коммиты в этот запрос на слияние. + +### 9. Открываем [запрос на слияние](http://help.github.com/send-pull-requests/) в *upstream*. + +Перейдите в репозиторий на github и нажмите "Pull Request", выберите ветку справа и введите больше информации +в поле комментариев. Чтобы связать запрос на изменение с issue, добавьте в комментарий `#999` - где 999 это номер issue. + +> Обратите внимание, что каждый запрос на слияние должен исправлять единственное изменение. Для множества независимых + изменений, пожалуйста откройте несколько запросов на слияние. + +### 10. Проверка вашего кода кем-то + +Кто-то будет проверять ваш код, и, возможно, вам будет предложено внести некоторые изменения, если это так, то перейдите +к шагу #6 (вам не надо открывать новый запрос на слияние, если текущий ещё открыт). Если код будет принят, он будет +влит в основную ветку и станет частью следующего релиза Yii. Если нет, не унывайте, разным людям необходимы различные +функции и Yii не может реализовывать всё для всех, ваш код будет ещё доступен на github как ссылка для людей кому он +может пригодится. + +### 11. Очистка + +После того, как код был принят или отклонён, вы можете удалить ветки, над которыми вы работали в локальном репозитории +и в `origin`. + +``` +git checkout master +git branch -D 999-name-of-your-branch-goes-here +git push origin --delete 999-name-of-your-branch-goes-here +``` + +### Примечание + +Для обнаружения регрессии как можно раньше, каждое слияние кодовой базы Yii на github будет подхвачено +[Travis CI](http://travis-ci.org) для автоматического запуска тестов. Люди из *core team* не хотят нагружать +этот сервис, поэтому добавляют текст [`[ci skip]`](http://about.travis-ci.org/docs/user/how-to-skip-a-build/) в описание +запроса на слияние, в следующих ситуациях: + +* затронуты только javascript, css файлы или файлы изображений, +* обновление документации, +* изменения затрагивают только строки (например обновление переводов). + +Это защитит travis от запуска тестов на изменениях, которые не покрыты тестами. + +### Обзор команд (для продвинутых участников) + +``` +git clone git@github.com:YOUR-GITHUB-USERNAME/yii2.git +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +``` +git fetch upstream +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here + +/* ваша магия, обновление changelog если нужно */ + +git add path/to/my/file.php +git commit -m "A brief description of this change which fixes #999 goes here" +git pull upstream master +git push -u origin 999-name-of-your-branch-goes-here +``` diff --git a/docs/internals-ru/report-an-issue.md b/docs/internals-ru/report-an-issue.md index 64c1e3729d..dc41bff2fb 100644 --- a/docs/internals-ru/report-an-issue.md +++ b/docs/internals-ru/report-an-issue.md @@ -8,7 +8,11 @@ * Опишите шаги, приводящие к ошибке. Было бы еще лучше, предоставить код для воспроизведения проблемы. * Если возможно создайте unit-тест и [пришлите его как pull request](git-workflow.md). +<<<<<<< HEAD Если проблема связана с одним из официальных расширений, пожалуйста, сообщайте о ней в репозитории этого расширения. +======= +Если проблема связана с одним из официальных расширений, пожалуйста, сообщайте о ней в репозиторий этого расширения. +>>>>>>> master Если вы не уверены, [сообщайте в главном репозитории](https://github.com/yiisoft/yii2/issues/new) (). **Не сообщайте о проблеме, если:** @@ -19,4 +23,8 @@ **Избегайте дублирования вопросов** Перед тем, как сообщать о проблеме, пожалуйста, совершите поиск среди [существующих вопросов](https://github.com/yiisoft/yii2/issues), +<<<<<<< HEAD возможно о проблеме уже сообщили или проблема уже решена. Также убедитесь, что имеете последнюю версию Yii и в случае обновления проверьте или проблема сохранилась. +======= +возможно о проблеме уже сообщили или проблема уже решена. Также убедитесь, что имеете последнюю версию Yii и в случае обновления ещё раз проверьте наличие проблемы. +>>>>>>> master diff --git a/docs/internals-ru/translation-workflow.md b/docs/internals-ru/translation-workflow.md index 425c86d657..66c761faf0 100644 --- a/docs/internals-ru/translation-workflow.md +++ b/docs/internals-ru/translation-workflow.md @@ -6,7 +6,7 @@ Yii переводится на множество языков, в том чи Сообщения фреймворка -------------------- -Есть два типа сообщений: исключения, которые нацелены на разработчиков и не переводятся и сообщения, которые показваются +Есть два типа сообщений: исключения, которые нацелены на разработчиков и не переводятся и сообщения, которые показываются пользователям. Например, ошибки валидации. Для того, чтобы обновить перевод: @@ -35,6 +35,8 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat Если ругается на composer, выполните `composer install` в корневой директории. +Информацию о синтаксисе и стиле документации можно найти в [documentation_style_guide.md](../documentation_style_guide.md). + Перед тем, как начать перевод, убедитесь, что никто им ещё не занимается и запишите себя в [список всех переводимых документов](https://docs.google.com/spreadsheets/d/1uxV0LwmR-8XXqlT8C6VqWllZjuoyIj-UkYpAQPWyUzE/edit?usp=sharing). @@ -80,9 +82,7 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat ### Перевод специальных сообщений -- Tip → Подсказка -- Note → Примечание -- Info → Информация +Специальные сообщения `Tip:`, `Note:`, `Info:`, `Warning:` не переводятся. ### Перевод рисунков @@ -123,8 +123,9 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat - helper - помощник. - id — идентификатор. - instance — экземпляр. +- junction table — промежуточная таблица. - lazy loading — отложенная загрузка (загрузим как понадобится и не раньше). -- method — метод (объекта) //Внимание! У объета/класса нет функций, есть только методы. +- method — метод (объекта) //Внимание! У объекта/класса нет функций, есть только методы. - model — модель, модель данных. - model form — модель формы. - parameter — параметр (у метода или функции, никак не у класса). @@ -145,3 +146,7 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat - validator class — класс валидатора. - view — представление. - query builder — конструктор запросов. +- time zone — часовой пояс. +- to trigger — инициализировать +- event — событие +- to implement (class implements interface) — реализовывать (класс реализует интерфейс) diff --git a/docs/internals-ru/view-code-style.md b/docs/internals-ru/view-code-style.md new file mode 100644 index 0000000000..b07a6be00a --- /dev/null +++ b/docs/internals-ru/view-code-style.md @@ -0,0 +1,53 @@ +Стиль кодирования представлений Yii2 +==================================== + +Данный стиль кодирования используется для представлений в ядре Yii 2.x и официальных представлениях. Мы не заставляем +вас использовать данный стиль кодирования для ваших приложений. Не стесняйтесь использовать тот стиль, который вам +больше подходит. + +```php +title = 'Posts'; +?> + + + +

+

+ + + + + ['id' => 'contact-message-form'], + 'fieldConfig' => ['inputOptions' => ['class' => 'common-input']], +]); ?> + + field($contactMessage, 'name')->textInput() ?> + field($contactMessage, 'email')->textInput() ?> + field($contactMessage, 'subject')->textInput() ?> + field($contactMessage, 'body')->textArea(['rows' => 6]) ?> + +
+ 'common-button']) ?> +
+ + + + +``` diff --git a/docs/internals-sr-Latn/automation.md b/docs/internals-sr-Latn/automation.md new file mode 100644 index 0000000000..cefbc629d7 --- /dev/null +++ b/docs/internals-sr-Latn/automation.md @@ -0,0 +1,15 @@ +Automatizacija +============== + +Postoje taskovi koji se rade automatski kada radite sa Yii frejmvorkom: + +- Generisanje mape klasa `classes.php` koji se nalazi u rutu frejmvork direktorijuma. + Pokrenite `./build/build classmap` kako bi izgenerisali fajl. + +- Generisanje `@property` anotacija u fajlovima sa klasama koje opisuju osobine koje su uveli geteri i seteri. + Pokrenite `./build/build php-doc/property` kako bi ih osvežili. + +- Ispravljanje stila pisanja koda i ostalih sitnijih problema u phpdoc komentarima. + Pokrenite `./build/build php-doc/fix` kako bi ih ispravili. + Proverite izmene pre njihovog komitovanja zato što se mogu desiti neželjene promene zato što komanda nije idealna. + Možete koristiti `git add -p` kako bi pregledali izmene. \ No newline at end of file diff --git a/docs/internals-sr-Latn/getting-started.md b/docs/internals-sr-Latn/getting-started.md new file mode 100644 index 0000000000..b5ad5fb4da --- /dev/null +++ b/docs/internals-sr-Latn/getting-started.md @@ -0,0 +1,4 @@ +Kako započeti sa Yii2 razvojem +============================== + +Pogledajte [Git proces rada za Yii 2 saradnike](git-workflow.md) o tome kako podesiti vaše okruženje. diff --git a/docs/internals-sr-Latn/git-workflow.md b/docs/internals-sr-Latn/git-workflow.md new file mode 100644 index 0000000000..7cd93e51dc --- /dev/null +++ b/docs/internals-sr-Latn/git-workflow.md @@ -0,0 +1,203 @@ +Git proces rada za Yii 2 saradnike +=================================== + +Želite da doprinesete Yii razvoju? Divno! Kako bi povećali šanse da vaše izmene budu prihvaćene što pre, molimo da +ispratite sledeće korake. Ako ste novi sa Git-om +i GitHub-om, možda bi želeli da prvo pogledate [GitHub pomoć](http://help.github.com/), [probate Git](https://try.github.com) +ili naučite nešto novo o [Git internom modelu podataka](http://nfarina.com/post/9868516270/git-is-simpler). + +Pripremite vaše razvojno okruženje +------------------------------------ + +Sledeći koraci će napraviti razvojno okruženje za Yii, koje možete koristiti kako bi radili +na baznom kodu Yii frejmvorka. Ovi se koraci trebaju uraditi samo jednom. + +### 1. [Forkujte](http://help.github.com/fork-a-repo/) Yii repozitorijum na GitHub-u i klonirajte vaš fork na vešem razvojnom okruženju + +``` +git clone git@github.com:VASE-GITHUB-KORISNICKO-IME/yii2.git +``` + +Ako imate problema sa podešavanjem Git-a sa GitHub-om na Linux-u ili dobijate greške tipa "Permission Denied (publickey)", +onda morate [podesiti vašu Git instalaciju da radi sa GitHub-om](http://help.github.com/linux-set-up-git/) + +### 2. Dodajte glavni Yii repozitorijum kao dodatni git repozitorijum sa nazivom "upstream" + +Locirajte se u direktorijum gde ste klonirali Yii, podrazumevano, "yii2" direktorijum. Nakon toga izvršite sledeću komandu: + +``` +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +### 3. Pripremite okruženje za testiranje + +Sledeći koraci nisu neophodni ako želite da radite samo na prevodima i dokumentaciji. + +- pokrenite `composer update` kako bi instalirali neophodne pakete (podrazumeva se da imate [composer instaliran globalno](https://getcomposer.org/doc/00-intro.md#globally)). +- pokrenite `php build/build dev/app basic` kako bi klonirali "basic" aplikaciju i instalirali composer neophonde pakete "basic" aplikacije. + Ova komanda će instlirati spoljne composer pakete i ulinkovati yii2 repozitorujum sa trenutnim preuzetim repozitorijumom, tako da imate samo jednu instancu celog instaliranog koda. + + Ponovite postupak za "advanced" aplikaciju ako je potrebno, pokretanjem: `php build/build dev/app advanced`. + + Ova komanda će se takođe koristiti da bi se osvežili potrebni paketi, ona pokreće `composer update` interno. + +**Sada ste spremni za rad na Yii 2 frejmvorku.** + +Sledeći koraci su neobavezni. + +### Unit testovi + +Možete izvršiti unit testove pokretanjem `phpunit` unutr root direktorijuma repozitorijuma. Ako nemate phpunit instaliran globalno možete pokrenuti `php vendor/bin/phpunit` umesto toga. + +Neki testovi zahtevaju dodatne baze podataka da budu postavljene i podešene. Možete napraviti `tests/data/config.local.php` fajl kako bi pregazili podešavanja koja su definisana unutar `tests/data/config.php` fajla. + +Možete ograničiti testove na grupu testova na kojima radite, na primer, da pokrenete testove za samo validaciju i redis koristite `phpunit --group=validators,redis`. Listu dostupnih grupa možete dobiti pokretanjem `phpunit --list-groups`. + +### Ekstenzije + +Kako bi radili na ekstenzijama morate klonirati repozitorijum ekstenzije. Napravili smo komandu koja može to uraditi umesto vas: + +``` +php build/build dev/ext +``` + +gde je `` ime ekstenzije, na primer `redis`. + +Ako želite da testirate ekstenziju u jednom od aplikacijskih šablona, samo dodajte repozitorijum u `composer.json` aplikacije kao što bi to radili normalno, na primer dodali bi `"yiisoft/yii2-redis": "*"` unutar`require` sekcije za "basic" aplikaciju. +Pokretanjem `php build/build dev/app basic` ćete instalirati ekstenziju i njene neophonde pakete i ulinkovaće se `extensions/redis` direktorijum kako ne bi radili u vendor direktorijumu nego u yii2 repozitorijumu direktno. + + +Rad na bagovima i poboljšanjima +------------------------------- + +Pošto je razvojno okruženje spremno kako je objašnjeno iznad možete započeti rad na nekoj novoj funkcionalnosti ili bagu. + +### 1. Postarajte se da je problem prijavljen za bug na kom radite ako zahteva značajniji rad na ispravljanju + +Sve nove funkcionalnosti i bugovi bi trebali imati povezanu temu koju bi koristili kao jedinstvenu tačku za diskusiju i dokumentaciju. Bacite pogled na postojeću listu koja ima temu koja se poklapa sa onim na čemu bi želeli da radite. Ako pronađete da tema već postoji u listi, onda ostavite komentar na toj temi u kome iskažite da želite da radite na tome. Ako ne pronađete postojeću temu/problem koja se poklapa sa onim na čemu bi želeli da radite, molimo da [postavite temu/prijavite problem](report-an-issue.md) ili napravite pull zahtev direktno ako nije komplikovano rešenje. Na ovaj način, tim će moći da pogleda vaše rešenje i uputi vas sa dodatno. + + +> Za site izmene ili dokumentacijske probleme ili za jednostavnije probleme, nije potrebno praviti posebnu temu, pull zahtev je više nego dovoljan u ovom slučaju. + +### 2. Dohvatite poslednji kod sa glavne Yii grane + +``` +git fetch upstream +``` + +Trebali bi krenuti uvek od ove tačke kada krećete sa radom kako bi se osigurali da radite sa poslednjim kodom. + +### 3. Napravite novu granu za novu funkcionalnost/rešenje baga baziranu na trenutnoj Yii master grani + +> Ovo je jako važno zato što nećete moći da pošaljete više od jednog pull zahteva sa vašeg naloga ako koristite master. + +Svako posebno rešenje baga ili izmena bi trebala da se nalazi u svojoj posebnoj grani. Imena grana trebaju biti opisna i u imenu sadrže broj teme na koju se odnosi. Ako ne radite na ispravci nekog određenog probelma, prekočite broj teme. Na primer: + +``` +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here +``` + +### 4. Bacite se na posao, napišite vaš kod + +Potrudite se da funkcioniše :) + +Unit testovi su uvek dobrodošli. Testiranje i dobro pokriven kod značajno pojednostavljuje proveru koda. +Neuspeli unit testovi kao opis teme su takođe prihvataju. + +### 5. Izmenite CHANGELOG + +Izmenite CHANGELOG fajl kako bi uključili vašu izmenu, trebali bi uneti je na vrhu fajla ispod "Work in progress" naslova, linija u CHNAGELOG fajlu bi trebalo da izgleda nešto nalik sledećem: + +``` +Bug #999: a description of the bug fix (Your Name) +Enh #999: a description of the enhancement (Your Name) +``` + +`#999` je broj teme na koju se `Bug` ili `Enh`odnosi. +CHANGELOG no trebao biti grupisan po tipu (`Bug`,`Enh`) i sortiran po broju teme. + +Za veoma male izmene, na primer, greške u tekstu, izmene na dokumentaciji, nije potrebno menjati CHANGELOG. + +### 6. Komitujte promene + +dodajte fajlove/promene koje želite da [komitujete](http://gitref.org/basic/#add) sa + +``` +git add path/to/my/file.php +``` + +Možete koristit `-p` opciju kako bi izabrali izmene koje želite da komitujete. + +Komitujte vaše izmene sa opisnom porukom komita. Potrudite se napomente broj teme sa `#XXX` kako bi GitHub automatski ulinkovao vaš komit sa temom: + +``` +git commit -m "A brief description of this change which fixes #999 goes here" +``` + +### 7. Preuzmite poslednji Yii kod sa upstream-a u vašu granu + +``` +git pull upstream master +``` + +Ovo nas osigurava da imamo poslednji kod u vašoj lokalnoj grani pre nego napravimo pull zahtev. Ako postoje konfilkti, trebali bi ih ispraviti i komitovati izmene ponovo. Na ovaj način biće lakše Yii timu da poveže izmene sa jednim klikom. + +### 8. Nakon razrešenih svih konflikata, postavite vaš kod na GitHub + +``` +git push -u origin 999-name-of-your-branch-goes-here +``` + +Parametar `-u` će osigurati da će vaša grana moći da šalje pull i push zahteve sa GitHub grane. To znači da ako pozovete `git push` sledeći put će znati gde treba kod da se pošalje. Ovo je korisno ako budete hteli da kasnije dodate više komitova u jednom pull zahtevu. + +### 9. Otvorite [pull zahtev](http://help.github.com/send-pull-requests/) na upstream-u. + +Posetite vaš repozitorijum na Github-u i kliknite na "Pull Request", izaberite vašu granu na desnoj strani i unesite neki opis u polje za komentar. Kako bi povezali pull zahtev sa temom unesite bilo gde u komentaru `#999` gde 999 je broj teme. + +> Imajte na umu da svaki pull zahtev treba ispraviti samo jednu stvar. Za više, nevezanih izmena, molimo koristite više pull zahteva. + +### 10. Neko će pregledati vaš kod + +Neko će pregledati vaš kod, i možda će se od vas tražiti još neke izmene, ako je tako idite na korak #6 (ne morate da pravite novi pull zahtev ako je vaš trenutni još uvek otvoren). Ako je vaš kod prihvaćen biće spojen u glavnu granu i postaće deo sledećeg Yii izdanja. Ako to nije slučaj, ne budite obeshrabreni, različiti ljudi žele različite funkcionalnosti i Yii ne može biti sve svima, vaš kod će biti dostupan na GitHub-u kao referenca za ljude kojima je to potrebno. + +### 11. Čišćenje + +Nakom što je vaš kod ili prihvaćen ili odbijen možete obrisati vaše grane na kojima ste radili na vašem lokalnom repozitorijumu i `origin`. + +``` +git checkout master +git branch -D 999-name-of-your-branch-goes-here +git push origin --delete 999-name-of-your-branch-goes-here +``` + +### Napomena: + +Kako bi rano otkrili regresije u Yii kodu prilikom svake integracije na GitHub-u pokreće se [Travis CI](http://travis-ci.org) kako bi se radilo testiranje. Pošto Yii tim ne želi da preoptereti ovaj servis, +[`[ci skip]`](http://about.travis-ci.org/docs/user/how-to-skip-a-build/) će biti uključen prilikom svake integracije ako pull zahtev: + +* utiče samo na javascript, css i slike, +* osvežava dokumentaciju, +* menja samo fiksne stringove (npr. izmene u prevodu) + +Na ovaj način će travi započinjati testiranje samo izmena koje nisu prvenstveno pokrivene testovima. + +### Pregled komandi (za napredne saradnike) + +``` +git clone git@github.com:YOUR-GITHUB-USERNAME/yii2.git +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +``` +git fetch upstream +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here + +/* bacite se na posao, izmenite changelog ako je potrebno */ + +git add path/to/my/file.php +git commit -m "A brief description of this change which fixes #999 goes here" +git pull upstream master +git push -u origin 999-name-of-your-branch-goes-here +``` diff --git a/docs/internals-sr-Latn/report-an-issue.md b/docs/internals-sr-Latn/report-an-issue.md new file mode 100644 index 0000000000..2bd0b0faa9 --- /dev/null +++ b/docs/internals-sr-Latn/report-an-issue.md @@ -0,0 +1,21 @@ +Prijavite Problem +================= + +Molimo da ispratite smernice ispod kada prijavljujete problem kako bi vaš problem bio što pre razrešen: + +* Priložite informacije uključujući: verziju PHP-a i Yii-a, tip operativnog sistema i web servera, tip brauzera i njegovu verziju; +* Priložite **kompletan** spisak grešaka ako postoji. Snimak ekrana koji objašnjava problem je vrlo dobrodošao. +* Opišite korake za reprodukovanje problema. Bilo bi još bolje ako možete da priložite kod koji reprodukuje problem. +* Ako je moguće možete i da napravite neuspeli unit test i [pošaljete ga kao pull zahtev](git-workflow.md). + +Ako je problem povezan sa nekom zvaničnom ekstenzijom , molimo da prijavite problem u repozitorijima date ekstenzije. +Ukoliko niste sigurni, [prijavite problem u glavnom repozitorijumu](https://github.com/yiisoft/yii2/issues/new) (). + +**Ne prijavljujte problem ako** + +* tražite pomoć pri korišćenju nekih Yii funkcionalnosti. Koristite [forum](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) ili [čet sobu](http://www.yiiframework.com/chat/) u te svrhe. +* je vaš problem sigurnosnog tipa. Molimo da nas [kontaktirate direktno](http://www.yiiframework.com/security/) za prijavu sigurnosnih problema. + +**Izbegavajte duple probleme** + +Pre nego prijavite problem, molimo da pretražite [postojeće probleme](https://github.com/yiisoft/yii2/issues) kako bi videli da li je vaš problem već prijavljen ili rešen kako bi bili sigurni da ne prijavljujete dupli problem. Takođe proverite da li imate poslednju verziju Yii-a i vidite da li problem i dalje postoji. diff --git a/docs/internals-sr-Latn/translation-workflow.md b/docs/internals-sr-Latn/translation-workflow.md new file mode 100644 index 0000000000..bc18072b87 --- /dev/null +++ b/docs/internals-sr-Latn/translation-workflow.md @@ -0,0 +1,35 @@ +Proces rada u prevođenju +======================== + +Yii je preveden na više jezika kako bi bio koristan za internacionalne aplikacije i programere. Dve glavne oblasti u kojima je doprinos veoma poželjan jeste dokumentacija i frejmvork poruke. + +Frejmvork poruke +---------------- + +Frejmvork ima dva tipa poruka: izuzeci koji su namenjeni programeru i koje se nikada ne prevode i poruke koje su zapravo vidljive krajnjem korisniku kao na primer validacijske greške. + +Da bi započeli rad sa prevodom poruka potrebno je da: + +1. Otvorite `framework/messages/config.php` i proverite da li je vaš jezik naveden u `languages`. Ako nije, + dodajte tu vaš jezik (ne zaboravite da zadržite alfabetički rapored). Format koda jezika treba da prati + [IETF jezičku tag specifikaciju](http://en.wikipedia.org/wiki/IETF_language_tag), na primer, `ru`, `zh-CN`. +2. Uđite u `framework` direktorijum i pokrenite `yii message/extract messages/config.php`. +3. Prevedite poruke unutar `framework/messages/your_language/yii.php` fajla. Pobrinite se da se fajl sačuva sa UTF-8 enkodingom. +4. [Napravite pull zahtev](https://github.com/yiisoft/yii2/blob/master/docs/internals-sr-Latn/git-workflow.md). + +Kako bi vaš prevod bio ažuran možete pokrenuti komandu `yii message/extract messages/config.php` još jednom. Ona će automatski ponovo izvući poruke ostavljajući nepromenjene netaknutim. + +U fajlu za prevođenje svaki element niza predstavlja prevod (verdnost) poruke (ključ). Ako je vrednost prazna, poruka se smatra neprevedenom. Poruke koje više ne trebaju prevod će imati svoje prevode zatvorene između para '@@' znaka. Poruke se mogu koristiti i formatu za množinu. Pogledajte [i18n sekciju uputstva](../guide-sr-Latn/tutorial-i18n.md) za više informacija. + +Dokumentacija +------------- + +Stavite prevode dokumentacije unuta `docs/-` gde `` je originalno ime dokumentacije kao što je `guide` ili `internals`, a `` je jezički kod od dokumentacije jezika u koji se prevodi. Za Rusko uputstvo prevodi se nalaze u `docs/guide-ru`. + +Nakon što je inicijalni posao završen možete dobiti šta je promenjeno nakon poslednjeg prevoda fajla korišćenjem specijalne komande unutar `build` direktorijuma: + +``` +php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translation report" > report_guide_ru.html +``` + +Ako se bude bunio u vezi composer-a, izvršite `composer install` u samom root-u direktorijuma. \ No newline at end of file diff --git a/docs/internals-uk/automation.md b/docs/internals-uk/automation.md index 73bcbb0fac..209cba06dd 100644 --- a/docs/internals-uk/automation.md +++ b/docs/internals-uk/automation.md @@ -1,15 +1,28 @@ Автоматизація ============= +<<<<<<< HEAD Є кілька задач, які можна автоматизувати працюючи з Yii: +======= +Є кілька задач, які можна автоматизувати, працюючи з Yii: +>>>>>>> master - Створення мапи класів `classes.php` у кореневій директорії фреймворку. Виконати `./build/build classmap` для її створення. +<<<<<<< HEAD - Створення анотацій `@property` у файлах класів, що описують властивості представлені функціями для отримання (getters) та призначення (setters) властивостей. Виконати `./build/build php-doc/property` для їх оновлення. - Виправлення стилю кодування та інших невеличких проблем у коментарях phpdoc. Виконати `./build/build php-doc/fix` для виправлення. Перед тим як створювати коміт, необхідно перевіряти зміни, оскільки команда не є досконалою й можливі не бажані зміни. +======= +- Створення анотацій `@property` у файлах класів, що описують властивості представлені геттерами та сеттерами. + Виконати `./build/build php-doc/property` для їх оновлення. + +- Виправлення стилю кодування та інших невеличких проблем у коментарях PHPDoc. + Виконати `./build/build php-doc/fix` для виправлення. + Перед тим як створювати комміт, необхідно перевіряти зміни, оскільки команда не є досконалою й можливі не бажані зміни. +>>>>>>> master Можна використовувати `git add -p` для перегляду змін. diff --git a/docs/internals-uk/core-code-style.md b/docs/internals-uk/core-code-style.md new file mode 100644 index 0000000000..95687c5ab9 --- /dev/null +++ b/docs/internals-uk/core-code-style.md @@ -0,0 +1,478 @@ +Оформлення основного коду фреймворку Yii 2 +========================================== + +Нижченаведений стиль кодування використовується у розробці основного коду Yii 2.x та офіційних розширень. Дотримуйтесь його, +якщо хочете вносити зміни в основний код. Команда розробників не наполягає на використанні цього стилю для вашого додатка. +Вільно обирайте те, що підходить вам більше. + +Ви можете отримати конфігурацію для CodeSniffer за посиланням: https://github.com/yiisoft/yii2-coding-standards + +1. Загальні положення +--------------------- + +В основному командою розробників використовується стиль сумісний з [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md), +тому все, що стосується +[PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md), застосовується також. + +- У файлах НЕОБХІДНО використовувати теги або `` або ``. +- Не додавайте пробіли у кінці рядку. +- Будь-який файл, який містить код PHP, повинен мати розширення `.php`. + +### 2.2. Кодування символів + +PHP код ПОВИНЕН використовувати лише UTF-8 без BOM. + +3. Імена класів +--------------- + +Імена класів ПОВИННІ оголошуватись як `StudlyCaps`. Наприклад: `Controller`, `Model`. + +4. Класи +-------- + +Тут термін "клас" відноситься до всіх класів та інтерфейсів. + +- Класи повинні іменуватись як `CamelCase`. +- Початкова фігурна дужка повинна завжди розміщуватись на наступному рядку після імені класу. +- Кожний клас повинен мати блок документації, який відповідає стандарту PHPDoc. +- Код всередині класу повинен бути зміщений на рівень відступу. +- Один PHP файл може містити лише один клас. +- Кожний клас повинен бути у просторі імен. +- Ім’я класу повинне відповідати імені файлу. Простір імен класу повинен відповідати структурі директорій. + +```php +/** + * Документація + */ +class MyClass extends \yii\Object implements MyInterface +{ + // код +} +``` + +### 4.1. Константи + +Імена констант ПОВИННІ оголошуватись повністю у верхньому регістрі, підкреслення використовується як роздільник. +Наприклад: + +```php + 'Yii', + 'options' => ['usePHP' => true], +]; +``` + +### 5.4 Керування порядком виконання + +- Перед умовою керувальної інструкції та після неї повинен бути пробіл. +- Оператори всередині круглих дужок повинні відокремлюватись пробілами. +- Початкова фігурна дужка розміщена на тому самому рядку. +- Кінцева фігурна дужка розміщена на новому рядку. +- Завжди використовуйте фігурні дужки для конструкцій з одного рядка. + +```php +if ($event === null) { + return new Event(); +} +if ($event instanceof CoolEvent) { + return $event->instance(); +} +return null; + + +// нижченаведений приклад НЕ допускається: +if (!$model && null === $event) + throw new Exception('test'); +``` + +Краще уникайте використання `else` після `return`, де це має сенс. +Використовуйте [вартові умови](http://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} else { + // обробка результату +} +``` + +буде краще так: + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} + +// обробка результату +``` + +#### switch + +Використовуйте наступний формат для switch: + +```php +switch ($this->phpType) { + case 'string': + $a = (string) $value; + break; + case 'integer': + case 'int': + $a = (int) $value; + break; + case 'boolean': + $a = (bool) $value; + break; + default: + $a = null; +} +``` + +### 5.5 Виклики функції + +```php +doIt(2, 3); + +doIt(['a' => 'b']); + +doIt('a', [ + 'a' => 'b', + 'c' => 'd', +]); +``` + +### 5.6 Оголошення анонімних (лямбда) функцій + +Зверніть увагу на пробіл між `function`/`use` та початковою круглою дужкою: + +```php +// добре +$n = 100; +$sum = array_reduce($numbers, function ($r, $x) use ($n) { + $this->doMagic(); + $r += $x * $n; + return $r; +}); + +// погано +$n = 100; +$mul = array_reduce($numbers, function($r, $x) use($n) { + $this->doMagic(); + $r *= $x * $n; + return $r; +}); +``` + +Документація +------------ + +- Див. [PHPDoc](http://phpdoc.org/) для довідки про синтаксис документації. +- Код без документації не допускається. +- Усі файли класів повинні містити файловий ("file-level") doc-блок на початку + та класовий ("class-level") doc-блок безпосередньо над кожним класом. +- Нема потреби використовувати `@return`, якщо метод нічого не повертає. +- Усі віртуальні властивості у класах успадкованих від `yii\base\Object` + документуються з тегом `@property` у класовому doc-блоці. + Ці анотації генеруються автоматично із тегів `@return` чи `@param` + відповідних геттерів або сеттерів виконанням команди `./build php-doc` у директорії build. + Ви можете додати тег `@property` + до геттеру або сеттеру, щоб точно визначити повідомлення для документації властивості, + яка представляється цими методами, коли опис відрізняється від того, що встановлено + тегом `@return`. Наприклад: + + ```php + + * @since 2.0 + */ +class Component extends \yii\base\Object +``` + + +#### Функція / метод + +```php +/** + * Returns the list of attached event handlers for an event. + * You may manipulate the returned [[Vector]] object by adding or removing handlers. + * For example, + * + * ``` + * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); + * ``` + * + * @param string $name the event name + * @return Vector list of attached event handlers for the event + * @throws Exception if the event is not defined + */ +public function getEventHandlers($name) +{ + if (!isset($this->_e[$name])) { + $this->_e[$name] = new Vector; + } + $this->ensureBehaviors(); + return $this->_e[$name]; +} +``` + +#### Markdown + +Як ви могли побачити у прикладах вище, для форматування коментарів PHPDoc використовується markdown. + +Існує додатковий синтаксис для створення перехресних посилань між класами, методами та властивостями у документації: + +- `'[[canSetProperty]] ` створить посилання на метод або властивість `canSetProperty` того ж самого класу. +- `'[[Component::canSetProperty]]` створить посилання на метод `canSetProperty` класу `Component` в тому ж самому просторі імен. +- `'[[yii\base\Component::canSetProperty]]` створить посилання на метод `canSetProperty` класу `Component` з простору імен `yii\base`. +- `'[[Component]]` створить посилання на клас `Component` в тому ж самому просторі імен. Додавання простору імен до імені класу тут також можливе. + +Щоб для одного з вище зазначених посилань вказати мітку відмінну від імені класу чи методу, ви можете використовувати синтаксис, показаний в нижченаведеному прикладі: + +``` +... as displayed in the [[header|header cell]]. +``` + +Частина перед | є найменуванням методу, властивості або класу, в той час як частина після | є міткою посилання. + +Також можливо посилатись на Посібник, використовуючи наступний синтаксис: + +```markdown +[link to guide](guide:file-name.md) +[link to guide](guide:file-name.md#subsection) +``` + + +#### Коментарі + +- Одно-рядкові коментарі повинні починатись з `//`, а не з `#`. +- Одно-рядковий коментар повинен бути розміщений на власному рядку. + +Додаткові правила +----------------- + +### `=== []` проти `empty()` + +Використовуйте `empty()`, де можливо. + +### Кілька інструкцій return + +При великій вкладеності умов, використовуйте інструкцію return раніше. Якщо метод не великий, це неважливо. + +### `self` проти `static` + +Завжди використовуйте `static`, за виключенням наведених випадків: + +- отримання значень констант ПОВИННО виконуватись через `self`: `self::MY_CONSTANT` +- отримання значень приватних статичних властивостей ПОВИННО виконуватись через `self`: `self::$_events` +- Дозволено використовувати `self` для рекурсії, щоб викликати поточне втілення знову замість розширення реалізації класу. + +### Значення для "не робити чогось" + +Властивості, яким дозволено сконфігурувати компонент не робити чогось, повинні приймати значення `false`. Значення `null`, `''` чи `[]` не повинні вважатись такими. + +### Імена директорій та просторів імен + +- використовуйте нижній регістр +- використовуйте форму множини для іменників, які представляють об’єкти (наприклад, validators) +- використовуйте форму однини для імен, які представляють відповідну функціональність/можливості (наприклад, web) diff --git a/docs/internals-uk/design-decisions.md b/docs/internals-uk/design-decisions.md new file mode 100644 index 0000000000..fec8e888f6 --- /dev/null +++ b/docs/internals-uk/design-decisions.md @@ -0,0 +1,25 @@ +Проектні рішення +================ + +Цей документ перелічує проектні рішення, які були прийняті командою розробників після широких дискусій. +Допоки немає дуже вагомих причин, ці рішення повинні дотримуватись для узгодженості. Для будь-якої зміни +у цих рішеннях необхідно отримати згоду команди головних розробників. + +1. **[Коли підтримувати псевдоніми шляху?](https://github.com/yiisoft/yii2/pull/3079#issuecomment-40312268)** + Псевдоніми шляху повинні підтримуватися для властивостей, які можна сконфігурувати, тому-що використання псевдонімів шляху + у конфігураціях дуже зручно. В інших випадках, потрібно обмежувати підтримку псевдонімів шляху. +2. **Коли перекладати повідомлення?** + Повідомлення повинні перекладатись, якщо вони показуються кінцевому користувачу та мають для нього значення. Повідомлення статусу HTTP, + виключення і т. п. не повинні перекладатись. Повідомлення консолі завжди англійською мовою, через труднощі обробки кодування + та кодової сторінки. +3. **[Додавання підтримки нового клієнту аутентифікації](https://github.com/yiisoft/yii2/issues/1652)** + Для кращого супроводу, ніякі додаткові клієнти аутентифікації не будуть додаватись до базового розширення. Вони + повинні бути виконані у вигляді користувацьких розширень. +4. **При використанні замикань**, рекомендується **вказувати всі параметри, які передаються**, у сигнатурі, навіть, якщо не всі з них + використовуються. Таким чином змінювати чи копіювати код легше, тому-що вся інформація перед очима та нема потреби переглядати + документацію для пошуку доступних параметрів. ([#6584](https://github.com/yiisoft/yii2/pull/6584), [#6875](https://github.com/yiisoft/yii2/issues/6875)) +5. Надавайте перевагу **int замість unsigned int** у схемі бази даних. Використання int має перевагу, бо може бути представлене в PHP як число. + Щоб представити unsigned, для 32-розрядної системи необхідно використовувати текстовий рядок. + Але також unsigned int подвоює діапазон позитивних чисел. Якщо у вас є таблиця, яка потребує такий великий числовий простір, + то безпечніше використовувати bigint або mediumint, а не покладатися на unsigned. + diff --git a/docs/internals-uk/getting-started.md b/docs/internals-uk/getting-started.md new file mode 100644 index 0000000000..783cc66846 --- /dev/null +++ b/docs/internals-uk/getting-started.md @@ -0,0 +1,4 @@ +Підготовка до розробки Yii 2 +============================ + +Інформація про те, як налаштувати середовище для розробки представлена у розділі [Робота з Git для учасників Yii 2](git-workflow.md). diff --git a/docs/internals-uk/git-workflow.md b/docs/internals-uk/git-workflow.md new file mode 100644 index 0000000000..38e6331744 --- /dev/null +++ b/docs/internals-uk/git-workflow.md @@ -0,0 +1,233 @@ +Робота з Git для учасників Yii 2 +================================ + +Ви бажаєте взяти участь в розробці Yii? Чудово! Але, щоб підвищити шанси швидкого прийняття ваших змін, будь ласка, +дотримуйтесь наступних кроків. Якщо ви новачок у Git та GitHub, спершу можете ознайомитись із +[довідкою GitHub](http://help.github.com/), [тренером Git](https://try.github.com), [інтерактивним туром Git How To](http://githowto.com/) +або почитати книгу [Магія Git](http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/uk/) +чи [Розділ Git у Вікіпідручнику](http://uk.wikibooks.org/wiki/Git), щоб краще зрозуміти внутрішню структуру Git. + +Підготовка вашого середовища розробки +------------------------------------- + +Наступні кроки створять середовище розробки для Yii, яке ви зможете використовувати для роботи +над основним кодом фреймворку Yii. Ці кроки необхідні лише тоді, коли ви вперше долучаєтесь до співпраці. + +### 1. [Створіть форк](http://help.github.com/fork-a-repo/) репозиторію Yii на GitHub та клонуйте свій форк у своє середовище розробки + +``` +git clone git@github.com:ВАШЕ-ІМ’Я-НА-GITHUB/yii2.git +``` + +Якщо у вас виникли проблеми із роботою Git з GitHub в Linux, або ви отримали помилку заборони доступу "Permission Denied (publickey)", +то вам необхідно [налаштувати Git для роботи з GitHub](https://help.github.com/articles/set-up-git/#platform-linux). + +### 2. Додайте головний репозиторій Yii як додатковий віддалений репозиторій із назвою "upstream" + +Перейдіть у директорію, в яку ви клонували Yii, зазвичай "yii2". Потім виконайте наведену команду: + +``` +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +### 3. Підготуйте середовище тестування + +Наступні кроки не обов'язкові, якщо ви хочете працювати лише над перекладом або документацією. + +- виконайте `composer update` для встановлення залежностей (припускається, що ви маєте [глобально встановлений composer](https://getcomposer.org/doc/00-intro.md#globally)). +- виконайте `php build/build dev/app basic` для клонування базового додатку та встановлення його залежностей. + Ця команда встановить сторонні пакунки composer як завжди, а також створить посилання з репозиторію yii2 + на поточний репозиторій. Таким чином ви будете мати один екземпляр встановленого коду. + +Якщо необхідно, зробіть те ж саме для розширеного додатку: `php build/build dev/app advanced`. + +Ця команда може використовуватись для оновлення залежностей, вона викликає `composer update` в процесі виконання. + +**Тепер ви маєте робочий майданчик для експериментів з Yii 2.** + +Наступні кроки не обов’язкові. + +### Модульні тести + +Ви можете виконати модульні тести, запустивши `phpunit` у кореневій директорії репозиторію. Якщо у вас phpunit не встановлений глобально, +ви можете запускати `php vendor/bin/phpunit`. + +Деякі тести потребують додатково встановлення та налаштування баз даних. Ви можете створити `tests/data/config.local.php` для перевизначення +налаштувань сконфігурованих у `tests/data/config.php`. + +Можливо обмежити тести групою тестів, що покривають область над якою ви працюєте, наприклад, щоб запустити тести для валідаторів +та redis, виконайте `phpunit --group=validators,redis`. Для отримання списку доступних груп виконайте `phpunit --list-groups`. + +### Розширення + +Для роботи з розширеннями необхідно клонувати відповідні репозиторії. Наступна команда зробить це для вас: + +``` +php build/build dev/ext +``` + +де `` є назвою розширення, наприклад `redis`. + +Якщо бажаєте протестувати розширення в одному із шаблонів додатку, просто додайте його до `composer.json` додатку, як ви будете +робити зазвичай. Наприклад, додайте `"yiisoft/yii2-redis": "*"` до секції `require` базового додатку. +Команда `php build/build dev/app basic` встановить розширення та його залежності й створить +символьне посилання на `extensions/redis`, тому ви можете працювати безпосередньо в директорії репозиторію yii2, +а не у специфічній для composer директорії vendor. + + +Робота з помилками та функціоналом +---------------------------------- + +Отримавши середовище розробки, як було описано вище, ви можете розпочати роботу над функціоналом або виправленням помилок. + +### 1. Переконайтесь, що створено питання стосовно речі, над якою ви працюєте, якщо це потребує багатьох зусиль для виконання + +Усі нові можливості та виправлення помилок повинні мати пов’язане запитання, яке забезпечує єдину точку посилання для обговорення +та документації. Витратьте декілька хвилин на перегляд списку створених питань, щоб знайти ті, що стосуються внеску, який ви +збираєтесь зробити. Якщо знайдете таке у списку запитань, потім, будь ласка, залиште коментар із зазначенням, що ви +збираєтесь працювати над цим. Якщо не знайшли створеного питання, що стосується того, над чим ви збираєтесь працювати, будь ласка +[створіть нове запитання](report-an-issue.md) або відправте "pull request" безпосередньо, якщо це не складне виправлення. Це дозволить команді +розробників розглянути вашу пропозицію та надавати відповідний зворотний зв’язок протягом шляху. + +> Для невеликих змін, проблем документації або простих виправлень нема потреби створювати питання, достатньо відправити "pull request" у цих випадках. + +### 2. Отримайте останній код з головної гілки Yii + +``` +git fetch upstream +``` + +З цього необхідно розпочинати кожний новий внесок, щоб бути впевненим, що ви працюєте з найновішим кодом. + +### 3. Створіть нову гілку для вашого внеску на базі поточної основної гілки Yii + +> Це дуже важливо, тому що ви не зможете відправляти більше, ніж один "pull request" від вашого імені, у разі + використання основної (master) гілки. + +Кожні окремі виправлення помилок або зміни повинні мати власні гілки. Назви гілок повинні бути наочними та починатись з +номеру питання, яке пов’язане із вашим кодом. Якщо ви працюєте не над специфічним питанням, просто пропустіть номер. +Наприклад: + +``` +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here +``` + +### 4. Робіть вашу магію, пишіть ваш код + +Переконайтесь, що він працює :) + +Модульні тести завжди вітаються. Протестований та добре покритий код надзвичайно полегшує перевірку вашого внеску. +Провальні модульні тести як опис проблеми також приймаються. + +### 5. Оновіть журнал змін (CHANGELOG) + +Додайте до файлу CHANGELOG зроблені вами зміни у верхній частині документу під заголовком +"Work in progress", запис у журналі змін повинен виглядати подібно до наведеного прикладу: + +``` +Bug #999: a description of the bug fix (Your Name) +Enh #999: a description of the enhancement (Your Name) +``` + +`#999` - це номер питання, на яке посилається виправлення помилки (`Bug`) або покращення (`Enh`). + +Записи журналу змін повинні бути згруповані за типом (`Bug`, `Enh`) та сортовані за номером питання. + +Для дуже малих виправлень, наприклад, друкарських помилок та змін у документації, нема потреби оновлювати CHANGELOG. + +### 6. Створіть комміт ваших змін + +Додайте файли/зміни, призначені для комміту, в [буферну зону](http://gitref.org/basic/#add) за допомогою команди: + +``` +git add path/to/my/file.php +``` + +Використовуйте опцію `-p` для відбору змін, які ви бажаєте додати до вашого комміту. + +Створіть комміт з описовим повідомленням. Переконайтесь, що вказали номер питання як `#XXX`, щоб GitHub +автоматично пов’язав ваш комміт із питанням: + +``` +git commit -m "A brief description of this change which fixes #999 goes here" +``` + +### 7. Додайте останній код Yii з upstream до вашої гілки + +``` +git pull upstream master +``` + +Це гарантує, що ви матимете останній код у вашій гілці перед тим, як відправити "pull request". Якщо є будь-які конфлікти поєднання, +треба виправити їх одразу та знову створити комміт. Це забезпечить команду розробників Yii можливістю легко приєднати ваші зміни +одним натисканням кнопки. + +### 8. Вирішивши будь-які конфлікти, відправте ваш код до GitHub + +``` +git push -u origin 999-name-of-your-branch-goes-here +``` + +Опція `-u` забезпечує те, що ваша гілка відтепер оброблятиметься автоматично при запитах push та pull до GitHub гілки. +Це означає, якщо ви виконаєте `git push` наступного разу, програма буде знати куди відправляти. Це корисно, якщо ви +бажаєте пізніше додавати більше коммітів у "pull request". + +### 9. Відправте ["pull request"](http://help.github.com/send-pull-requests/) до upstream. + +Перейдіть до вашого репозиторію на GitHub та натисніть "Pull Request", оберіть вашу гілку справа та внесіть трохи деталей +у полі коментарю. Щоб пов’язати "pull request" із запитанням, внесіть десь у коментарі `#999`, де 999 - це номер питання. + +> Зауважте, що кожний "pull request" повинен стосуватись окремої зміни. Для багатьох змін, не пов’язаних між собою, + будь ласка, відправляйте "pull request" окремо для кожної. + +### 10. Дехто перевірить ваш код + +Дехто перевірить ваш код, й, можливо, вас попросять внести деякі зміни. У цьому випадку перейдіть до кроку #6 (нема потреби +відправляти інший "pull request", якщо ваш поточний досі відкритий). Якщо ваш код прийнято, то він буде поєднаний з головною гілкою +та стане частиною наступного релізу Yii. Якщо ж ні, не сумуйте, різні люди потребують різних можливостей, та Yii +не може бути всім для всіх, ваш код залишатиметься доступним на GitHub для людей, які його потребують. + +### 11. Проведіть чистку + +Після того, як ваш код був прийнятий або відхилений, можете видалити гілки, над якими ви працювали, із локального репозиторію +та з `origin`. + +``` +git checkout master +git branch -D 999-name-of-your-branch-goes-here +git push origin --delete 999-name-of-your-branch-goes-here +``` + +### Примітка: + +Для виявлення регресу на ранніх стадіях кожне поєднання з кодовою базою Yii на GitHub опрацьовується у +[Travis CI](http://travis-ci.org) для автоматичного запуску тестів. Оскільки основна команда розробників не бажає +перевантажувати сервіс, додавайте [`[ci skip]`](http://about.travis-ci.org/docs/user/how-to-skip-a-build/) до +опису поєднання, якщо ваш "pull request": + +* зачіпає лише файли javascript, css або файли зображень, +* оновлює документацію, +* змінює лише фіксовані текстові рядки (наприклад, оновлення перекладу) + +Це захистить travis від запуску тестів на змінах, які не покриті тестами. + +### Огляд команд (для досвідчених учасників) + +``` +git clone git@github.com:YOUR-GITHUB-USERNAME/yii2.git +git remote add upstream git://github.com/yiisoft/yii2.git +``` + +``` +git fetch upstream +git checkout upstream/master +git checkout -b 999-name-of-your-branch-goes-here + +/* робіть вашу магію; оновіть журнал змін, якщо необхідно */ + +git add path/to/my/file.php +git commit -m "A brief description of this change which fixes #999 goes here" +git pull upstream master +git push -u origin 999-name-of-your-branch-goes-here +``` diff --git a/docs/internals-uk/report-an-issue.md b/docs/internals-uk/report-an-issue.md new file mode 100644 index 0000000000..b9fee14a96 --- /dev/null +++ b/docs/internals-uk/report-an-issue.md @@ -0,0 +1,22 @@ +Повідомлення про проблему +========================= + +Будь ласка, дотримуйтесь рекомендацій, зазначених нижче, при створенні питання, щоб прискорити його вирішення: + +* Подавайте інформацію, включаючи: версію PHP та Yii, тип операційної системи та веб-сервера, тип та версію браузера. +* Додавайте **повний** вивід про помилку, якщо є. Знімок екрану, що пояснює проблему, дуже вітається. +* Опишіть кроки, які призводять до помилки. Було б ще краще, надати код для відтворення проблеми. +* Якщо можливо створіть модульний тест, навіть провальний, та [надішліть його як "pull request"](git-workflow.md). + +Якщо проблема повʼязана з одним із офіційних розширень, будь ласка, повідомляйте про неї у репозиторії цього розширення. +Якщо ви не впевнені, [повідомляйте у головному репозиторії](https://github.com/yiisoft/yii2/issues/new) (). + +**Не повідомляйте про проблему, якщо:** + +* Ви хочете спитати як використовувати ту чи іншу можливість Yii. Для цього користуйтесь [форумом](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) або [чатом](http://www.yiiframework.com/chat/). +* Проблема стосується безпеки. Будь ласка, [звертайтесь безпосередньо до розробників](http://www.yiiframework.com/security/), щодо проблем з безпекою. + +**Уникайте дублювання питань** + +Перед тим, як повідомити про проблему, будь ласка, здійсніть пошук серед [створених запитань](https://github.com/yiisoft/yii2/issues), +можливо про проблему вже повідомили або проблема вже вирішена. Також переконайтесь, що маєте останню версію Yii та у разі оновлення перевірте чи проблема при цьому присутня. diff --git a/docs/internals-uk/translation-workflow.md b/docs/internals-uk/translation-workflow.md new file mode 100644 index 0000000000..685f30b97b --- /dev/null +++ b/docs/internals-uk/translation-workflow.md @@ -0,0 +1,203 @@ +Процес перекладу +================ + +Yii перекладається на багато різних мов, щоб бути корисним для міжнародних додатків та розробників. Основними двома областями, +де вітається співпраця є документація та повідомлення фреймворку. + +Повідомлення фреймворку +----------------------- + +Фреймворк має два типи повідомлень: виключення, які призначені для розробників й ніколи не перекладаються, та повідомлення +видимі кінцевому користувачу, такі як помилки перевірки. + +Щоб розпочати переклад повідомлень необхідно: + +1. Перейти до директорії `framework` та виконати команду `./yii message/extract messages/config.php`. +2. Перекласти повідомлення у файлі `framework/messages/uk/yii.php`. Зберегти файл у кодуванні UTF-8. +3. [Відправити "pull request"](git-workflow.md). + +Для підтримання перекладу в актуальному стані можна знову використовувати команду `./yii message/extract messages/config.php`. +Це автоматично здобуде нові повідомлення, зберігаючи при цьому вже перекладені. + +У файлі перекладу кожний ключ елементу масиву представляє повідомлення, а значення елементу масиву представляє переклад. +Якщо значення порожнє, повідомлення вважається не перекладеним. Переклади повідомлень, які вже не потребують перекладу, +замкнуті між парами знаків '@@'. Текст перекладу може містити формат кількох форм множини. +Ознайомтесь з розділом посібника [Інтернаціоналізація](../guide-uk/tutorial-i18n.md) для більш детальної інформації. + +Документація +------------ + +Переклад документації знаходиться у `docs/-uk`, де `` відповідає оригінальній директорії, +наприклад `guide` або `internals`. + +Після завершення розпочатої роботи можна отримати звіт про стан перекладу за допомогою виклику спеціальної команди з +директорії `build`: + +``` +php build translation "../docs/guide" "../docs/guide-uk" "Ukrainian guide translation report" > report_guide_uk.html +``` + +У разі необхідності встановіть [Composer](https://getcomposer.org/) в кореневій директорії вашого локального репозиторію. + +### Список документів + +Перелік документів, що потребують перекладу, можна знайти за нижченаведеними посиланнями: + +- [список документів для guide-uk](https://ethercalc.org/yii2.docs.guide-uk); +- [список документів для internals-uk](https://ethercalc.org/yii2.docs.internals-uk). + +Перед тим, як розпочати переклад, переконайтесь, що їм ніхто не займається, та запишіть себе у списку документів. + +В залежності від прогресу оберіть відповідний статус перекладу: +- В роботі — переклад готується перекладачем до відправлення "pull request"; +- Ревізія — відправлений переклад перевіряється ревізором; +- Перекладено — переклад прийнято до головної (master) гілки проекту. + +За додатковою інформацією можете звертатись до учасників української [команди перекладачів](../internals/translation-teams.md). + +### Переклад зображень + +Зображення до документації знаходяться у вкладеній директорії `images`. Усі вони створенні програмою [yED](http://www.yworks.com/en/products/yfiles/yed/). +При необхідності перекладу оригінальний файл копіюється в директорію `images` перекладу, перекладається та зберігається у форматі png. + +Перелік зображень, що потребують перекладу, можна знайти за нижченаведеним посиланням: + +- [список зображень для guide-uk](https://ethercalc.org/yii2.docs.guide-uk.images). + +### Переклад спеціальних повідомлень + +- Tip → Підказка +- Note → Примітка +- Info → Інформація +- Warning → Попередження + +### Список термінів + +[Англійсько-українські словники](http://e2u.org.ua) + +- action — дія; +- Active Record — (не перекладається); +- Advanced/Basic Project Template — Розширений/Базовий шаблон проекту; +- alias — псевдонім; +- alphanumeric — буквено-цифровий; +- (Web) application — (веб-)додаток; +- assignment — призначення; +- attach handler — приєднати обробник; +- attribute of the model — атрибут моделі; +- authentication — аутентифікація / установлення справжності; +- authorization — авторизація/уповноваження; +- autoloader — автозавантажувач; +- back-end — (не перекладається); +- backward compatibility / BC — зворотна сумісність; +- bootstrap, bootstrapping — початкове завантаження; +- branch — гілка; +- browser — браузер; +- (asset) bundle — колекція (ресурсів); +- cache, caching — кеш, кешування; +- camel case — (не перекладається); +- case-sensitive — регістр-залежний; +- column — колонка; +- commit — комміт; +- concatenation — конкатенація; +- configuration — конфігурація; +- content — вміст; +- content view — вкладене представлення; +- contributor — учасник; +- cookies — кукі; +- customization — (тонке) налаштування; +- debug mode — режим налагодження (див. production mode); +- debugger — налагоджувач; +- (function) declaration — оголошення (функції); +- definition — визначення; +- design pattern — шаблон проектування; +- development mode — режим розробки; +- (root) directory — (коренева) директорія; +- eager loading — жадібне завантаження (див. lazy loading); +- email address — адреса електронної пошти; +- environment — середовище; +- exception — виключення; +- existing — наявний/присутній; // перекладати як "існуючий" не вірно +- (PHP) extension — розширення (PHP); +- Facebook — Фейсбук; +- field (of the table) — поле/атрибут (таблиці); +- fixture — фікстура; +- folder — папка/каталог; +- footer — футер; +- fork — форк; +- formatter — форматтер; +- framework — фреймворк; +- front-controller — фронт-контролер; +- front-end — (не перекладається); +- getter — геттер; +- (event) handler — обробник (події); +- hash — хеш; +- header — шапка; +- help — довідка; +- helper — хелпер; +- id — ідентифікатор; +- image — зображення; +- initialize — ініціалізувати/встановлювати; +- to initiate — ініціювати/розпочинати; +- instance — екземпляр; +- instantiate — створювати екземпляр; +- issue — питання/проблема; // в залежності від контексту +- layout — макет; +- lazy loading — відкладене завантаження; +- log, logging — журнал, журналювання; +- markdown — (не перекладається); +- method — метод (обʼєкта/класу); +- merge — поєднання; +- Model-View-Controller (MVC) — Модель-Представлення-Контролер (MVC); +- namespace — простір імен; +- out of the box — "з коробки"; +- package — пакунок; +- pagination — розділення на сторінки; +- parameter — параметр; +- to parse — обробляти; +- (application) performance — швидкодія (додатка); +- placeholder — заповнювач; +- plugin — плагін; +- postprocessing — після-обробка; +- predefined — попередньо визначений; +- production mode — робочий режим (див. debug mode); +- profiling — профілювання; +- property — властивість (обʼєкта); +- pull request — (не перекладається); +- query builder — конструктор запитів; +- refactoring — рефакторинг; +- to render, rendering — формувати, формування; +- related, relation — повʼязаний, звʼязок; +- release — реліз; +- repo, repository — репозиторій; +- resolve request — попередня обробка запиту; +- route, routing — маршрут, маршрутизація; +- row (of the table) — рядок (таблиці); +- screenshot — знімок екрану; +- Service Locator — Локатор служб; +- setter — сеттер; +- shared hosting — віртуальний хостинг; +- (call) stack — стек (викликів); +- staging area — буферна зона; +- standalone — автономний; +- string — текстовий рядок; +- sub-directory — під-директорія; +- substitution — підставлення/заміщення; +- tabular input — табличний ввід; +- tag — тег; +- template engine — шаблонізатор; +- theming — темізація; +- third party — сторонній; +- thumbnail — мініатюра; +- tracing — трасування; +- trait — трейт; +- trigger event — породжувати подію; +- Twitter — Твіттер; +- Unicode — (не перекладається); +- unit tests — модульні тести; +- to validate — перевіряти; +- valid — коректний; +- validator — валідатор; +- validation — перевірка; +- validator class — клас валідатора; +- versioning — версіонування; +- widget — віджет. diff --git a/docs/internals-uk/versions.md b/docs/internals-uk/versions.md index 1448952d36..edd3e9de7b 100644 --- a/docs/internals-uk/versions.md +++ b/docs/internals-uk/versions.md @@ -1,38 +1,51 @@ <<<<<<< HEAD +<<<<<<< HEAD Yii version numbering ===================== +======= +Версіонування Yii +================= +>>>>>>> master -Релізи ------- +Цей документ описує політику призначення версій Yii. Поточна стратегія призначення версій +базується на [ferver](https://github.com/jonathanong/ferver), це за думкою розробників є більш практичним +та розумним рішенням, ніж використання [semver](http://semver.org/) (див. [#7408](https://github.com/yiisoft/yii2/issues/7408) для довідки). -A.B.C +У колі головних розробників неодноразово підкреслювалась важливість зберігати зворотну сумісність релізів 2.0.x на 100%. +Але це ідеалістичний план. Стаття про ferver доводить, що досягнути цього на практиці дуже важко, +не зважаючи на те, використовується semver чи ні. -A = Для Yii2 це завжди 2. -B = Основна версія. Не-BC (`BC`, від англ. Backward compatibility - зворотна сумісність) зміни із інструкціями щодо оновлення. -C = BC зміни та доповнення. +Загалом, політика призначення версій наступна: -Реліз-кандидати ---------------- +## Патч-релізи `2.x.Y` -A.B.C-rc -A.B.C-rc2 +Патч-релізи, які мають бути на 100% зворотно сумісними. В ідеалі, вони містять лише виправлення помилок, що зменшує +можливість порушення зворотної сумісності. На практиці, релізи починаючи з 2.0.x стали частішими та зазвичай містять невеликі доповнення, +що дає можливість користувачам почати використовувати ці зміни раніше. -Це коли ми хочемо зробити реліз-кандидат. Номер RC збільшується доки ми не отримаємо стабільний реліз без будь-яких -критичних помилок і звітів про звортню несумісність. +* Підтримуються у гілці `2.x` +* Переважно містять виправлення помилок та невеликі покращення +* Відсутні великі зміни та доповнення +* 100%-ва зворотна сумісність, що гарантує оновлення без проблем. Виключенням можуть бути лише проблеми безпеки, які потребують порушення зворотної сумісності +* Цикл релізу близько 1-2 місяців +* Не має необхідності у пре-релізах (альфа, бета, реліз-кандидат) +* Регулярно обʼєднуються з головною (master) гілкою (щонайменш раз у тиждень вручну) -Альфи та бети -------------- -A.B.C-alpha -A.B.C-alpha2 +## Молодші (мінорні) релізи `2.X.0` -Альфи це є нестабільні версії, де значні помилки можуть і, ймовірно, дійсно будуть. -API ще фіксується і може бути суттєво змінений. -`alpha2` і т.д. можуть або не можуть бути випущені на основі загальної стабільності коду та API. +Зворотно несумісні релізи, що містять великі доповнення та зміни, які можуть порушувати зворотну сумісність. Оновлення з ранніх версій +може бути не простим, але у наявності повна інструкція по оновленню або навіть скрипт. -A.B.C-beta -A.B.C-beta2 +* Розроблюються у головній (майстер) гілці +* Переважно містять нові доповнення та виправлення помилок +* Містять невеликі доповнення та виправлення помилок з патч-релізів +* Можуть мати зворотно несумісні зміни, які записуються у файл `UPGRADE-2.X.md` +* Цикл релізу близько 6-8 місяців +* Необхідні пре-релізи: `2.X.0-alpha`, `2.X.0-beta`, `2.X.0-rc` +* Потребують маркетингових зусиль та публікування у головних новинах +<<<<<<< HEAD Бета більш-менш стабільна із меншою кількістю помилок та меншою нестабільністю API, ніж альфа. Там все ще можуть бути зміни в API, але на це повинна бути вагома причина. ======= @@ -86,3 +99,13 @@ A.B.C-beta2 > Примітка: Офіційні розширення використовують таку ж саму політику призначення версій але можуть публікуватись незалежно від фреймворку, тобто номера версій фреймворку та розширення не повинні обов'язково збігатсь. >>>>>>> yiichina/master +======= + +## Основні (мажорні) релізи `X.0.0` + +Це наче 2.0 після 1.0. Такий перехід, вірогідніше, буде не частіше ніж кожні 3-5 років, у звʼязку з просуванням сторонніх технологій +(наприклад, оновлення PHP з 5.0 до 5.4). + +> Примітка: Офіційні розширення використовують таку ж саму політику призначення версій, але можуть публікуватись незалежно від +фреймворку, тобто номера версій фреймворку та розширення не повинні обовʼязково збігатися. +>>>>>>> master diff --git a/docs/internals-uk/view-code-style.md b/docs/internals-uk/view-code-style.md new file mode 100644 index 0000000000..0e5ee91b89 --- /dev/null +++ b/docs/internals-uk/view-code-style.md @@ -0,0 +1,52 @@ +Оформлення коду представлень у Yii 2 +==================================== + +Нижченаведений стиль кодування використовується у представленнях основи Yii 2.x та у представленнях офіційних розширень. +Команда розробників не наполягає на використанні цього стилю для вашого додатка. Вільно обирайте те, що підходить вам більше. + +```php +title = 'Posts'; +?> + + + +

+

+ + + + + ['id' => 'contact-message-form'], + 'fieldConfig' => ['inputOptions' => ['class' => 'common-input']], +]); ?> + + field($contactMessage, 'name')->textInput() ?> + field($contactMessage, 'email')->textInput() ?> + field($contactMessage, 'subject')->textInput() ?> + field($contactMessage, 'body')->textArea(['rows' => 6]) ?> + +
+ 'common-button']) ?> +
+ + + + +``` diff --git a/docs/internals-uz/translation-workflow.md b/docs/internals-uz/translation-workflow.md index c703be0cd3..f29b723b3b 100644 --- a/docs/internals-uz/translation-workflow.md +++ b/docs/internals-uz/translation-workflow.md @@ -1,4 +1,4 @@ -O'zbekchaga tarjima qilish bilan qanday ishlash kerak +O'zbekchaga tarjima qilish bilan qanday ishlash kerak ===================================================== Yii juda ko'p tillarga tarjima qilinayabdi shu jumladan o'zbekchaga ham. Tarjima qo'llanma va habarlarni o'z ichiga oladi. diff --git a/docs/internals/core-code-style.md b/docs/internals/core-code-style.md index 2016a0a15f..4f50a917e9 100644 --- a/docs/internals/core-code-style.md +++ b/docs/internals/core-code-style.md @@ -53,7 +53,7 @@ The term "class" refers to all classes and interfaces here. - Classes should be named using `CamelCase`. - The brace should always be written on the line underneath the class name. - Every class must have a documentation block that conforms to the PHPDoc. -- All code in a class must be indented with a single tab. +- All code in a class must be indented with 4 spaces. - There should be only one class in a single PHP file. - All classes should be namespaced. - Class name should match file name. Class namespace should match directory structure. @@ -87,9 +87,10 @@ class Foo - Public and protected variables should be declared at the top of the class before any method declarations. Private variables should also be declared at the top of the class but may be added right before the methods that are dealing with them in cases where they are only related to a small subset of the class methods. -- The order of property declaration in a class should be ascending from public over protected to private. +- The order of property declaration in a class should be ascending based on their visibility: from public over protected to private. +- There are no strict rules for ordering properties that have the same visibility. - For better readability there should be no blank lines between property declarations and two blank lines - between property and method declaration sections. + between property and method declaration sections. One blank line should be added between the different visibility groups. - Private variables should be named like `$_varName`. - Public class members and standalone variables should be named using `$camelCase` with first letter lowercase. @@ -101,9 +102,18 @@ For example: getResult(); if (empty($result)) { - return true; + return true; } else { - // process result + // process result } ``` @@ -271,7 +281,7 @@ is better as ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } // process result @@ -398,9 +408,9 @@ class Component extends \yii\base\Object * You may manipulate the returned [[Vector]] object by adding or removing handlers. * For example, * - * ~~~ + * ``` * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); - * ~~~ + * ``` * * @param string $name the event name * @return Vector list of attached event handlers for the event @@ -476,3 +486,6 @@ Properties allowing to configure component not to do something should accept val - use lower case - use plural form for nouns which represent objects (e.g. validators) - use singular form for names representing relevant functionality/features (e.g. web) +- prefer single word namespaces +- if single word isn't suitable, use camelCase + diff --git a/docs/internals/design-decisions.md b/docs/internals/design-decisions.md index 5ec8797b49..fcb3f8db32 100644 --- a/docs/internals/design-decisions.md +++ b/docs/internals/design-decisions.md @@ -13,7 +13,7 @@ the core developers. exceptions about the code etc. should not be translated. Console messages are always in English because of encoding and codepage handling difficulties. 3. **[Adding new auth client support](https://github.com/yiisoft/yii2/issues/1652)** - For better maintenability, we will not add any additional auth clients to the core extension. They should be done + For better maintainability, we will not add any additional auth clients to the core extension. They should be done in terms of user extensions. 4. **When using closures** it is recommended to **include all passed parameters** in the signature even if not all of them are used. This way modifying or copying code is easier because all information is directly visible and it is not necessary to diff --git a/docs/internals/getting-started.md b/docs/internals/getting-started.md index ebaca27a4c..f27c2c9088 100644 --- a/docs/internals/getting-started.md +++ b/docs/internals/getting-started.md @@ -1,6 +1,7 @@ Getting started with Yii2 development ===================================== +<<<<<<< HEAD <<<<<<< HEAD 1. Clone your fork of yii2 `git clone git@github.com:/yii2.git`. 2. Change into the repo folder `cd yii2`. @@ -46,3 +47,6 @@ See `apps/advanced/tests/README.md` and `apps/basic/tests/README.md` to learn ab ======= See [Git workflow for Yii 2 contributors](git-workflow.md) on how to set up your environment. >>>>>>> yiichina/master +======= +See [Git workflow for Yii 2 contributors](git-workflow.md) on how to set up your environment. +>>>>>>> master diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index 17b8d0341e..f37dc524d2 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -3,6 +3,7 @@ Git workflow for Yii 2 contributors So you want to contribute to Yii? Great! But to increase the chances of your changes being accepted quickly, please <<<<<<< HEAD +<<<<<<< HEAD follow the following steps (the first 2 steps only need to be done the first time you contribute). If you are new to git and github, you might want to first check out [github help](http://help.github.com/), [try git](https://try.github.com) or learn something about [git internal data model](http://nfarina.com/post/9868516270/git-is-simpler). @@ -14,21 +15,31 @@ follow the following steps. If you are new to git and github, you might want to first check out [github help](http://help.github.com/), [try git](https://try.github.com) or learn something about [git internal data model](http://nfarina.com/post/9868516270/git-is-simpler). +======= +follow the following steps. If you are new to Git +and GitHub, you might want to first check out [GitHub help](http://help.github.com/), [try Git](https://try.github.com) +or learn something about [Git internal data model](http://nfarina.com/post/9868516270/git-is-simpler). + +>>>>>>> master Prepare your development environment ------------------------------------ The following steps will create a development environment for Yii, which you can use to work on the core code of Yii framework. These steps only need to be done the first time you contribute. +<<<<<<< HEAD ### 1. [Fork](http://help.github.com/fork-a-repo/) the Yii repository on github and clone your fork to your development environment >>>>>>> yiichina/master +======= +### 1. [Fork](http://help.github.com/fork-a-repo/) the Yii repository on GitHub and clone your fork to your development environment +>>>>>>> master ``` git clone git@github.com:YOUR-GITHUB-USERNAME/yii2.git ``` -If you have trouble setting up GIT with GitHub in Linux, or are getting errors like "Permission Denied (publickey)", -then you must [setup your GIT installation to work with GitHub](http://help.github.com/linux-set-up-git/) +If you have trouble setting up Git with GitHub in Linux, or are getting errors like "Permission Denied (publickey)", +then you must [setup your Git installation to work with GitHub](http://help.github.com/linux-set-up-git/) ### 2. Add the main Yii repository as an additional git remote called "upstream" @@ -38,14 +49,23 @@ Change to the directory where you cloned Yii, normally, "yii2". Then enter the f git remote add upstream git://github.com/yiisoft/yii2.git ``` +<<<<<<< HEAD <<<<<<< HEAD ### 3. Make sure there is an issue created for the thing you are working on if it requires significant effort to fix ======= +======= +>>>>>>> master ### 3. Prepare the testing environment The following steps are not necessary if you want to work only on translations or documentation. - run `composer update` to install dependencies (assuming you have [composer installed globally](https://getcomposer.org/doc/00-intro.md#globally)). +<<<<<<< HEAD +======= + +> Note: If you see errors like `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.`, you will need to run `composer global require "fxp/composer-asset-plugin:~1.1.1"` + +>>>>>>> master - run `php build/build dev/app basic` to clone the basic app and install composer dependencies for the basic app. This command will install foreign composer packages as normal but will link the yii2 repo to the currently checked out repo, so you have one instance of all the code installed. @@ -54,6 +74,12 @@ The following steps are not necessary if you want to work only on translations o This command will also be used to update dependencies, it runs `composer update` internally. +<<<<<<< HEAD +======= +> Note: The default git repository Urls clone from github via SSH, you may add the `--useHttp` flag to the `build` command +> to use HTTPs instead. + +>>>>>>> master **Now you have a working playground for hacking on Yii 2.** The following steps are optional. @@ -67,14 +93,25 @@ Some tests require additional databases to be set up and configured. You can cre settings that are configured in `tests/data/config.php`. You may limit the tests to a group of tests you are working on e.g. to run only tests for the validators and redis +<<<<<<< HEAD `phpunit --group=validators,redis`. You get the list of available groups by running `phpunit --groups`. +======= +`phpunit --group=validators,redis`. You get the list of available groups by running `phpunit --list-groups`. +>>>>>>> master ### Extensions To work on extensions you have to clone the extension repository. We have created a command that can do this for you: +<<<<<<< HEAD php build/build dev/ext +======= +``` +php build/build dev/ext +``` + +>>>>>>> master where `` is the name of the extension, e.g. `redis`. If you want to test the extension in one of the application templates, just add it to the `composer.json` of the application as you would @@ -82,6 +119,12 @@ normally do e.g. add `"yiisoft/yii2-redis": "*"` to the `require` section of the Running `php build/build dev/app basic` will install the extension and its dependecies and create a symlink to `extensions/redis` so you are not working in the composer vendor dir but in the yii2 repository directly. +<<<<<<< HEAD +======= +> Note: The default git repository Urls clone from github via SSH, you may add the `--useHttp` flag to the `build` command +> to use HTTPs instead. + +>>>>>>> master Working on bugs and features ---------------------------- @@ -89,12 +132,16 @@ Working on bugs and features Having prepared your develop environment as explained above you can now start working on the feature or bugfix. ### 1. Make sure there is an issue created for the thing you are working on if it requires significant effort to fix +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master All new features and bug fixes should have an associated issue to provide a single point of reference for discussion and documentation. Take a few minutes to look through the existing issue list for one that matches the contribution you intend to make. If you find one already on the issue list, then please leave a comment on that issue indicating you <<<<<<< HEAD +<<<<<<< HEAD intend to work on that item. If you do not find an existing issue matching what you intend to work on, please open a new issue for your item or create a pull request directly if it is straightforward fix. This will allow the team to review your suggestion, and provide appropriate feedback along the way. @@ -111,6 +158,15 @@ review your suggestion, and provide appropriate feedback along the way. ### 2. Fetch the latest code from the main Yii branch >>>>>>> yiichina/master +======= +intend to work on that item. If you do not find an existing issue matching what you intend to work on, please +[open a new issue](report-an-issue.md) or create a pull request directly if it is straightforward fix. This will allow the team to +review your suggestion, and provide appropriate feedback along the way. + +> For small changes or documentation issues or straightforward fixes, you don't need to create an issue, a pull request is enough in this case. + +### 2. Fetch the latest code from the main Yii branch +>>>>>>> master ``` git fetch upstream @@ -118,11 +174,15 @@ git fetch upstream You should start at this point for every new contribution to make sure you are working on the latest code. +<<<<<<< HEAD <<<<<<< HEAD ### 5. Create a new branch for your feature based on the current Yii master branch ======= ### 3. Create a new branch for your feature based on the current Yii master branch >>>>>>> yiichina/master +======= +### 3. Create a new branch for your feature based on the current Yii master branch +>>>>>>> master > That's very important since you will not be able to submit more than one pull request from your account if you'll use master. @@ -136,25 +196,33 @@ git checkout upstream/master git checkout -b 999-name-of-your-branch-goes-here ``` +<<<<<<< HEAD <<<<<<< HEAD ### 6. Do your magic, write your code ======= ### 4. Do your magic, write your code >>>>>>> yiichina/master +======= +### 4. Do your magic, write your code +>>>>>>> master Make sure it works :) Unit tests are always welcome. Tested and well covered code greatly simplifies the task of checking your contributions. Failing unit tests as issue description are also accepted. +<<<<<<< HEAD <<<<<<< HEAD ### 7. Update the CHANGELOG ======= ### 5. Update the CHANGELOG >>>>>>> yiichina/master +======= +### 5. Update the CHANGELOG +>>>>>>> master Edit the CHANGELOG file to include your change, you should insert this at the top of the file under the -"Work in progress" heading, the line in the change log should look like one of the following: +first heading (the version that is currently under development), the line in the change log should look like one of the following: ``` Bug #999: a description of the bug fix (Your Name) @@ -166,11 +234,15 @@ The changelog should be grouped by type (`Bug`,`Enh`) and ordered by issue numbe For very small fixes, e.g. typos and documentation changes, there is no need to update the CHANGELOG. +<<<<<<< HEAD <<<<<<< HEAD ### 8. Commit your changes ======= ### 6. Commit your changes >>>>>>> yiichina/master +======= +### 6. Commit your changes +>>>>>>> master add the files/changes you want to commit to the [staging area](http://gitref.org/basic/#add) with @@ -180,18 +252,22 @@ git add path/to/my/file.php You can use the `-p` option to select the changes you want to have in your commit. -Commit your changes with a descriptive commit message. Make sure to mention the ticket number with `#XXX` so github will +Commit your changes with a descriptive commit message. Make sure to mention the ticket number with `#XXX` so GitHub will automatically link your commit with the ticket: ``` -git commit -m "A brief description of this change which fixes #42 goes here" +git commit -m "A brief description of this change which fixes #999 goes here" ``` +<<<<<<< HEAD <<<<<<< HEAD ### 9. Pull the latest Yii code from upstream into your branch ======= ### 7. Pull the latest Yii code from upstream into your branch >>>>>>> yiichina/master +======= +### 7. Pull the latest Yii code from upstream into your branch +>>>>>>> master ``` git pull upstream master @@ -201,16 +277,21 @@ This ensures you have the latest code in your branch before you open your pull r you should fix them now and commit the changes again. This ensures that it's easy for the Yii team to merge your changes with one click. +<<<<<<< HEAD <<<<<<< HEAD ### 10. Having resolved any conflicts, push your code to github ======= ### 8. Having resolved any conflicts, push your code to github >>>>>>> yiichina/master +======= +### 8. Having resolved any conflicts, push your code to GitHub +>>>>>>> master ``` git push -u origin 999-name-of-your-branch-goes-here ``` +<<<<<<< HEAD The `-u` parameter ensures that your branch will now automatically push and pull from the github branch. That means <<<<<<< HEAD if you type `git push` the next time it will know where to push to. @@ -222,11 +303,19 @@ to the pull request. ### 9. Open a [pull request](http://help.github.com/send-pull-requests/) against upstream. >>>>>>> yiichina/master +======= +The `-u` parameter ensures that your branch will now automatically push and pull from the GitHub branch. That means +if you type `git push` the next time it will know where to push to. This is useful if you want to later add more commits +to the pull request. -Go to your repository on github and click "Pull Request", choose your branch on the right and enter some more details +### 9. Open a [pull request](http://help.github.com/send-pull-requests/) against upstream. +>>>>>>> master + +Go to your repository on GitHub and click "Pull Request", choose your branch on the right and enter some more details in the comment box. To link the pull request to the issue put anywhere in the pull comment `#999` where 999 is the issue number. +<<<<<<< HEAD <<<<<<< HEAD > Note that each pull-request should fix a single change. @@ -236,17 +325,26 @@ issue number. ### 10. Someone will review your code >>>>>>> yiichina/master +======= +> Note that each pull-request should fix a single change. For multiple, unrelated changes, please open multiple pull requests. + +### 10. Someone will review your code +>>>>>>> master Someone will review your code, and you might be asked to make some changes, if so go to step #6 (you don't need to open another pull request if your current one is still open). If your code is accepted it will be merged into the main branch and become part of the next Yii release. If not, don't be disheartened, different people need different features and Yii -can't be everything to everyone, your code will still be available on github as a reference for people who need it. +can't be everything to everyone, your code will still be available on GitHub as a reference for people who need it. +<<<<<<< HEAD <<<<<<< HEAD ### 13. Cleaning it up ======= ### 11. Cleaning it up >>>>>>> yiichina/master +======= +### 11. Cleaning it up +>>>>>>> master After your code was either accepted or declined you can delete branches you've worked with from your local repository and `origin`. @@ -259,7 +357,7 @@ git push origin --delete 999-name-of-your-branch-goes-here ### Note: -To detect regressions early every merge to the Yii codebase on github will be picked up by +To detect regressions early every merge to the Yii codebase on GitHub will be picked up by [Travis CI](http://travis-ci.org) for an automated testrun. As core team doesn't wish to overtax this service, [`[ci skip]`](http://about.travis-ci.org/docs/user/how-to-skip-a-build/) will be included to the merge description if the pull request: @@ -285,7 +383,7 @@ git checkout -b 999-name-of-your-branch-goes-here /* do your magic, update changelog if needed */ git add path/to/my/file.php -git commit -m "A brief description of this change which fixes #42 goes here" +git commit -m "A brief description of this change which fixes #999 goes here" git pull upstream master git push -u origin 999-name-of-your-branch-goes-here ``` diff --git a/docs/internals/pull-request-qa.md b/docs/internals/pull-request-qa.md new file mode 100644 index 0000000000..fbfb1a831d --- /dev/null +++ b/docs/internals/pull-request-qa.md @@ -0,0 +1,11 @@ +Pull request quality assurance +============================== + +When checking if PR could be merged or not the following criteria should be considered among others: + +- There should be either an issue linked to PR or PR should have a good description on what is it fixing or adding. +- Unit tests. Not mandatory but very appreciated. These should fail without the code that the PR is fixing. +- CHANGELOG entry should present. It should be put into next release section, ordered by issue type and number. +Nicknames of people responsible should present. +- [Code style](core-code-style.md) and [views code style](view-code-style.md) should be OK. These could be fixed while + merging if the person merging prefers it that way. diff --git a/docs/internals/report-an-issue.md b/docs/internals/report-an-issue.md index 9368950f99..751ca84c5c 100644 --- a/docs/internals/report-an-issue.md +++ b/docs/internals/report-an-issue.md @@ -7,12 +7,18 @@ Please follow the guidelines below when creating an issue so that your issue can * Provide the **complete** error call stack if available. A screenshot to explain the issue is very welcome. * Describe the steps for reproducing the issue. It would be even better if you could provide code to reproduce the issue. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * If possible you may even create a failing unit test and [send it as a pull request](git-workflow.md). If the issue is related to one of the official extensions, please report it in the extension repositories issue tracker. If you are unsure, [report it to the main repository](https://github.com/yiisoft/yii2/issues/new) (). +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master **Do not report an issue if** diff --git a/docs/internals/schema-builder-patterns.xlsx b/docs/internals/schema-builder-patterns.xlsx new file mode 100644 index 0000000000..32bf142e59 Binary files /dev/null and b/docs/internals/schema-builder-patterns.xlsx differ diff --git a/docs/internals/translation-status.md b/docs/internals/translation-status.md index d908209cc6..597f300efe 100644 --- a/docs/internals/translation-status.md +++ b/docs/internals/translation-status.md @@ -45,22 +45,28 @@ concept-di-container.md | Yes db-dao.md | Yes db-query-builder.md | Yes <<<<<<< HEAD +<<<<<<< HEAD db-active-record.md | db-migrations.md | ======= db-active-record.md | Yes db-migrations.md | Yes >>>>>>> yiichina/master +======= +db-active-record.md | Yes +db-migrations.md | Yes +>>>>>>> master db-sphinx.md | db-redis.md | db-mongodb.md | db-elasticsearch.md | input-forms.md | input-validation.md | Yes -input-file-upload.md | -input-multiple-models.md | +input-file-upload.md | Yes +input-multiple-models.md | Yes input-tabular-input.md | <<<<<<< HEAD +<<<<<<< HEAD output-formatter.md | output-pagination.md | output-sorting.md | @@ -78,6 +84,16 @@ output-theming.md | Yes security-authentication.md | Yes >>>>>>> yiichina/master security-authorization.md | +======= +output-formatting.md | Yes +output-pagination.md | Yes +output-sorting.md | Yes +output-data-providers.md | Yes +output-data-widgets.md | +output-theming.md | Yes +security-authentication.md | Yes +security-authorization.md | Yes +>>>>>>> master security-passwords.md | security-auth-clients.md | security-best-practices.md | @@ -108,7 +124,7 @@ tutorial-start-from-scratch.md | tutorial-console.md | tutorial-i18n.md | tutorial-mailing.md | -tutorial-performance-tuning.md | +tutorial-performance-tuning.md | Yes tutorial-shared-hosting.md | tutorial-template-engines.md | tutorial-core-validators.md | Yes diff --git a/docs/internals/translation-teams.md b/docs/internals/translation-teams.md index 9538a15e26..1782887241 100644 --- a/docs/internals/translation-teams.md +++ b/docs/internals/translation-teams.md @@ -62,6 +62,11 @@ Ukrainian - **Alexandr Bordun [@borales](https://github.com/Borales), admin@yiiframework.com.ua** - Roman Bahatyi [@RichWeber](https://github.com/RichWeber), rbagatyi@gmail.com <<<<<<< HEAD +<<<<<<< HEAD ======= - Igor Zozulinskyi [@3y3ik](https://github.com/3y3ik) >>>>>>> yiichina/master +======= +- Igor Zozulinskyi [@3y3ik](https://github.com/3y3ik) +- Vadym Chenin [@vchenin](https://github.com/vchenin), vchenin@meta.ua +>>>>>>> master diff --git a/docs/internals/translation-workflow.md b/docs/internals/translation-workflow.md index 3c631cedee..eaa152d728 100644 --- a/docs/internals/translation-workflow.md +++ b/docs/internals/translation-workflow.md @@ -7,6 +7,7 @@ where contribution is very welcome are documentation and framework messages. Framework messages ------------------ +<<<<<<< HEAD <<<<<<< HEAD Framework has two types of messages: exceptions that are intended to developer and are never translated and messages that are actually visible to end user such as validation errors. @@ -14,6 +15,10 @@ that are actually visible to end user such as validation errors. The framework has two types of messages: exceptions that are intended to developer and are never translated and messages that are actually visible to the end user such as validation errors. >>>>>>> yiichina/master +======= +The framework has two types of messages: exceptions that are intended to developer and are never translated and messages +that are actually visible to the end user such as validation errors. +>>>>>>> master In order to start with message translation: @@ -21,11 +26,11 @@ In order to start with message translation: add your language there (remember to keep the list in alphabetical order). The format of language code should follow [IETF language tag spec](http://en.wikipedia.org/wiki/IETF_language_tag), for example, `ru`, `zh-CN`. -2. Go to `framework` and run `yii message/extract messages/config.php`. +2. Go to `framework` and run `./yii message/extract --languages=`. 3. Translate messages in `framework/messages/your_language/yii.php`. Make sure file is saved using UTF-8 encoding. -4. [Make a pull request](https://github.com/yiisoft/yii2/blob/master/docs/internals/git-workflow.md). +4. [Make a pull request](git-workflow.md). -In order to keep your translation up to date you may run `yii message/extract messages/config.php` again. It will +In order to keep your translation up to date you may run `./yii message/extract --languages=` again. It will automatically re-extract messages keeping unchanged ones intact. In the translation file each array element represents the translation (value) of a message (key). If the value is empty, @@ -48,3 +53,5 @@ php build translation "../docs/guide" "../docs/guide-ru" "Russian guide translat ``` If it will complain about composer, perform `composer install` in the source root dir. + +For information on the documentation syntax and style guide, see [documentation_style_guide.md](../documentation_style_guide.md). diff --git a/docs/internals/versions.md b/docs/internals/versions.md index e2354beaf3..6f662928fb 100644 --- a/docs/internals/versions.md +++ b/docs/internals/versions.md @@ -45,8 +45,14 @@ not be trivial, but a complete upgrade guide or even script will be available. It's like 2.0 over 1.0. We expect this only happens every 3 to 5 years, depending on external technology advancement (such as PHP upgraded from 5.0 to 5.4). <<<<<<< HEAD +<<<<<<< HEAD ======= > Note: Official extensions are following the same versioning policy but could be released independently from the framework i.e. version number mismatch between framework and extension is expected. >>>>>>> yiichina/master +======= + +> Note: Official extensions are following the same versioning policy but could be released independently from +the framework i.e. version number mismatch between framework and extension is expected. +>>>>>>> master diff --git a/extensions/README.md b/extensions/README.md deleted file mode 100644 index 07f0a9764e..0000000000 --- a/extensions/README.md +++ /dev/null @@ -1,18 +0,0 @@ -This folder contains official Yii 2 extensions. - -To add a new extension named `xyz` (must be in lower case), take the following steps: - -1. create a folder named `xyz` under `yii` and put all relevant source code there; -2. create the following accessory files (please refer to any existing extension): - * `composer.json` - * `README.md` - * `CHANGELOG.md` - * `LICENSE.md` -3. ask Qiang to create a subsplit for `xyz` and a composer package named `yii2-xyz`; -4. If an extension depends on external bower/npm packages: - * in the `composer.json` file of the extension, list the dependencies in the format of `'bower-asset/PackageName': '1.1'`; - * create an asset bundle class to list the needed js/css files from the package. The `sourcePath` - property of the bundle should point to the distribution path of the package, such as - `@bower/PackageName`, or `@bower/PackageName/dist`. -5. modify `/composer.json` and add `yiisoft/yii2-xyz` to the `replace` section. Also add any bower/npm - dependencies to the `require` section. diff --git a/extensions/apidoc/.gitignore b/extensions/apidoc/.gitignore deleted file mode 100644 index 8b7ef35032..0000000000 --- a/extensions/apidoc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/vendor -composer.lock diff --git a/extensions/apidoc/LICENSE.md b/extensions/apidoc/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/apidoc/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/apidoc/apidoc b/extensions/apidoc/apidoc deleted file mode 100755 index 0dd7d372e8..0000000000 --- a/extensions/apidoc/apidoc +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env php - 'yii2-apidoc', - 'basePath' => __DIR__, - 'enableCoreCommands' => false, - 'controllerNamespace' => 'yii\\apidoc\\commands', -]); -if ($vendorPath !== null) { - $application->setVendorPath($vendorPath); -} -$exitCode = $application->run(); -exit($exitCode); diff --git a/extensions/apidoc/apidoc.bat b/extensions/apidoc/apidoc.bat deleted file mode 100644 index 950276fab9..0000000000 --- a/extensions/apidoc/apidoc.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%apidoc" %* - -@endlocal diff --git a/extensions/apidoc/commands/ApiController.php b/extensions/apidoc/commands/ApiController.php deleted file mode 100644 index ee0a23a536..0000000000 --- a/extensions/apidoc/commands/ApiController.php +++ /dev/null @@ -1,167 +0,0 @@ - - * @since 2.0 - */ -class ApiController extends BaseController -{ - /** - * @var string url to where the guide files are located - */ - public $guide; - /** - * @var string prefix to prepend to all guide file names. - */ - public $guidePrefix = 'guide-'; - - - // TODO add force update option - /** - * Renders API documentation files - * @param array $sourceDirs - * @param string $targetDir - * @return int - */ - public function actionIndex(array $sourceDirs, $targetDir) - { - $renderer = $this->findRenderer($this->template); - $targetDir = $this->normalizeTargetDir($targetDir); - if ($targetDir === false || $renderer === false) { - return 1; - } - - $renderer->apiUrl = './'; - $renderer->guidePrefix = $this->guidePrefix; - - // setup reference to guide - if ($this->guide !== null) { - $renderer->guideUrl = $guideUrl = $this->guide; - } else { - $guideUrl = './'; - $renderer->guideUrl = $targetDir; - if (file_exists($renderer->generateGuideUrl('README.md'))) { - $renderer->guideUrl = $guideUrl; - } else { - $renderer->guideUrl = null; - } - } - - // search for files to process - if (($files = $this->searchFiles($sourceDirs)) === false) { - return 1; - } - - // load context from cache - $context = $this->loadContext($targetDir); - $this->stdout('Checking for updated files... '); - foreach ($context->files as $file => $sha) { - if (!file_exists($file)) { - $this->stdout('At least one file has been removed. Rebuilding the context...'); - $context = new Context(); - if (($files = $this->searchFiles($sourceDirs)) === false) { - return 1; - } - break; - } - if (sha1_file($file) === $sha) { - unset($files[$file]); - } - } - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - - // process files - $fileCount = count($files); - $this->stdout($fileCount . ' file' . ($fileCount == 1 ? '' : 's') . ' to update.' . PHP_EOL); - Console::startProgress(0, $fileCount, 'Processing files... ', false); - $done = 0; - foreach ($files as $file) { - $context->addFile($file); - Console::updateProgress(++$done, $fileCount); - } - Console::endProgress(true); - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - - // save processed data to cache - $this->storeContext($context, $targetDir); - - $this->updateContext($context); - - // render models - $renderer->controller = $this; - $renderer->render($context, $targetDir); - - if (!empty($context->errors)) { - ArrayHelper::multisort($context->errors, 'file'); - file_put_contents($targetDir . '/errors.txt', print_r($context->errors, true)); - $this->stdout(count($context->errors) . " errors have been logged to $targetDir/errors.txt\n", Console::FG_RED, Console::BOLD); - } - } - - /** - * @inheritdoc - */ - protected function findFiles($path, $except = []) - { - if (empty($except)) { - $except = ['vendor/', 'tests/']; - } - $path = FileHelper::normalizePath($path); - $options = [ - 'filter' => function ($path) { - if (is_file($path)) { - $file = basename($path); - if ($file[0] < 'A' || $file[0] > 'Z') { - return false; - } - } - - return null; - }, - 'only' => ['*.php'], - 'except' => $except, - ]; - - return FileHelper::findFiles($path, $options); - } - - /** - * @inheritdoc - * @return ApiRenderer - */ - protected function findRenderer($template) - { - $rendererClass = 'yii\\apidoc\\templates\\' . $template . '\\ApiRenderer'; - if (!class_exists($rendererClass)) { - $this->stderr('Renderer not found.' . PHP_EOL); - - return false; - } - - return new $rendererClass(); - } - - /** - * @inheritdoc - */ - public function options($actionID) - { - return array_merge(parent::options($actionID), ['guide', 'guidePrefix']); - } -} diff --git a/extensions/apidoc/commands/GuideController.php b/extensions/apidoc/commands/GuideController.php deleted file mode 100644 index 1bd97b2c72..0000000000 --- a/extensions/apidoc/commands/GuideController.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @since 2.0 - */ -class GuideController extends BaseController -{ - /** - * @var string path or URL to the api docs to allow links to classes and properties/methods. - */ - public $apiDocs; - /** - * @var string prefix to prepend to all output file names generated for the guide. - */ - public $guidePrefix = 'guide-'; - - - /** - * Renders API documentation files - * @param array $sourceDirs - * @param string $targetDir - * @return int - */ - public function actionIndex(array $sourceDirs, $targetDir) - { - $renderer = $this->findRenderer($this->template); - $targetDir = $this->normalizeTargetDir($targetDir); - if ($targetDir === false || $renderer === false) { - return 1; - } - - if ($renderer->guideUrl === null) { - $renderer->guideUrl = './'; - } - $renderer->guidePrefix = $this->guidePrefix; - - // setup reference to apidoc - if ($this->apiDocs !== null) { - $path = $this->apiDocs; - if ($renderer->apiUrl === null) { - $renderer->apiUrl = $path; - } - // use relative paths relative to targetDir - if (strncmp($path, '.', 1) === 0) { - $renderer->apiContext = $this->loadContext("$targetDir/$path"); - } else { - $renderer->apiContext = $this->loadContext($path); - } - } elseif (file_exists($targetDir . '/cache/apidoc.data')) { - if ($renderer->apiUrl === null) { - $renderer->apiUrl = './'; - } - $renderer->apiContext = $this->loadContext($targetDir); - } else { - $renderer->apiContext = new Context(); - } - $this->updateContext($renderer->apiContext); - - // search for files to process - if (($files = $this->searchFiles($sourceDirs)) === false) { - return 1; - } - - $renderer->controller = $this; - $renderer->render($files, $targetDir); - - $this->stdout('Publishing images...'); - foreach ($sourceDirs as $source) { - $imageDir = rtrim($source, '/\\') . '/images'; - if (file_exists($imageDir)) { - FileHelper::copyDirectory($imageDir, $targetDir . '/images'); - } - } - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } - - - /** - * @inheritdoc - */ - protected function findFiles($path, $except = []) - { - $path = FileHelper::normalizePath($path); - $options = [ - 'only' => ['*.md'], - 'except' => $except, - ]; - - return FileHelper::findFiles($path, $options); - } - - /** - * @inheritdoc - * @return GuideRenderer - */ - protected function findRenderer($template) - { - $rendererClass = 'yii\\apidoc\\templates\\' . $template . '\\GuideRenderer'; - if (!class_exists($rendererClass)) { - $this->stderr('Renderer not found.' . PHP_EOL); - - return false; - } - - return new $rendererClass(); - } - - /** - * @inheritdoc - */ - public function options($actionID) - { - return array_merge(parent::options($actionID), ['apiDocs', 'guidePrefix']); - } -} diff --git a/extensions/apidoc/components/BaseController.php b/extensions/apidoc/components/BaseController.php deleted file mode 100644 index 40108c500c..0000000000 --- a/extensions/apidoc/components/BaseController.php +++ /dev/null @@ -1,161 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseController extends Controller -{ - /** - * @var string template to use for rendering - */ - public $template = 'bootstrap'; - /** - * @var string|array files to exclude. - */ - public $exclude; - - - /** - * Checks that target directory is valid. Asks questions in tricky cases. - * @param string $target - * @return boolean|string - */ - protected function normalizeTargetDir($target) - { - $target = rtrim(Yii::getAlias($target), '\\/'); - if (file_exists($target)) { - if (is_dir($target) && !$this->confirm('TargetDirectory already exists. Overwrite?', true)) { - $this->stderr('User aborted.' . PHP_EOL); - - return false; - } - if (is_file($target)) { - $this->stderr("Error: Target directory \"$target\" is a file!" . PHP_EOL); - - return false; - } - } else { - mkdir($target, 0777, true); - } - - return $target; - } - - /** - * Finds files to process - * @param array $sourceDirs - * @return array|boolean list of files to process or false on failure - */ - protected function searchFiles($sourceDirs) - { - $this->stdout('Searching files to process... '); - $files = []; - - if (is_array($this->exclude)) { - $exclude = $this->exclude; - } elseif (is_string($this->exclude)) { - $exclude = explode(',', $this->exclude); - } else { - $exclude = []; - } - - foreach ($sourceDirs as $source) { - foreach ($this->findFiles($source, $exclude) as $fileName) { - $files[$fileName] = $fileName; - } - } - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - - if (empty($files)) { - $this->stderr('Error: No files found to process.' . PHP_EOL); - - return false; - } - - return $files; - } - - /** - * Finds files - * - * @param string $dir directory to search files in. - * @param array $except list of names to exclude from search. - * @return array files found. - */ - abstract protected function findFiles($dir, $except = []); - - /** - * Loads context from cache - * @param string $location - * @return Context - */ - protected function loadContext($location) - { - $context = new Context(); - - $cacheFile = $location . '/cache/apidoc.data'; - $this->stdout('Loading apidoc data from cache... '); - if (file_exists($cacheFile)) { - $context = unserialize(file_get_contents($cacheFile)); - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } else { - $this->stdout('no data available.' . PHP_EOL, Console::FG_YELLOW); - } - - return $context; - } - - /** - * Writes context into cache file - * @param Context $context - * @param string $location - */ - protected function storeContext($context, $location) - { - $cacheFile = $location . '/cache/apidoc.data'; - if (!is_dir($dir = dirname($cacheFile))) { - mkdir($dir, 0777, true); - } - file_put_contents($cacheFile, serialize($context)); - } - - /** - * @param Context $context - */ - protected function updateContext($context) - { - $this->stdout('Updating cross references and backlinks... '); - $context->updateReferences(); - $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } - - /** - * @param string $template - * @return BaseRenderer - */ - abstract protected function findRenderer($template); - - /** - * @inheritdoc - */ - public function options($actionID) - { - return array_merge(parent::options($actionID), ['template', 'exclude']); - } -} diff --git a/extensions/apidoc/helpers/ApiMarkdownLaTeX.php b/extensions/apidoc/helpers/ApiMarkdownLaTeX.php deleted file mode 100644 index 0ce830f932..0000000000 --- a/extensions/apidoc/helpers/ApiMarkdownLaTeX.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @since 2.0 - */ -class ApiMarkdownLaTeX extends GithubMarkdown -{ - use ApiMarkdownTrait; - - /** - * @var BaseRenderer - */ - public static $renderer; - - protected $renderingContext; - - - /** - * @inheritdoc - */ - protected function renderApiLink($block) - { - // TODO allow break also on camel case - $latex = '\texttt{'.str_replace(['\\textbackslash', '::'], ['\allowbreak{}\\textbackslash', '\allowbreak{}::\allowbreak{}'], $this->escapeLatex(strip_tags($block[1]))).'}'; - return $latex; - } - - /** - * @inheritdoc - */ - protected function renderBrokenApiLink($block) - { - return $this->renderApiLink($block); - } - - /** - * Converts markdown into HTML - * - * @param string $content - * @param TypeDoc $context - * @param boolean $paragraph - * @return string - */ - public static function process($content, $context = null, $paragraph = false) - { - if (!isset(Markdown::$flavors['api-latex'])) { - Markdown::$flavors['api-latex'] = new static; - } - - if (is_string($context)) { - $context = static::$renderer->apiContext->getType($context); - } - Markdown::$flavors['api-latex']->renderingContext = $context; - - if ($paragraph) { - return Markdown::processParagraph($content, 'api-latex'); - } else { - return Markdown::process($content, 'api-latex'); - } - } -} diff --git a/extensions/apidoc/helpers/ApiMarkdownTrait.php b/extensions/apidoc/helpers/ApiMarkdownTrait.php deleted file mode 100644 index 2976538176..0000000000 --- a/extensions/apidoc/helpers/ApiMarkdownTrait.php +++ /dev/null @@ -1,134 +0,0 @@ -renderingContext; - - if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) { - - $offset = strlen($matches[0]); - - $object = $matches[1]; - $title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1); - - if (($pos = strpos($object, '::')) !== false) { - $typeName = substr($object, 0, $pos); - $subjectName = substr($object, $pos + 2); - if ($context !== null) { - // Collection resolves relative types - $typeName = (new Collection([$typeName], $context->phpDocContext))->__toString(); - } - /** @var $type TypeDoc */ - $type = static::$renderer->apiContext->getType($typeName); - if ($type === null) { - static::$renderer->apiContext->errors[] = [ - 'file' => ($context !== null) ? $context->sourceFile : null, - 'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), - ]; - - return [ - ['brokenApiLink', '' . $typeName . '::' . $subjectName . ''], - $offset - ]; - } else { - if (($subject = $type->findSubject($subjectName)) !== null) { - if ($title === null) { - $title = $type->name . '::' . $subject->name; - if ($subject instanceof MethodDoc) { - $title .= '()'; - } - } - - return [ - ['apiLink', static::$renderer->createSubjectLink($subject, $title)], - $offset - ]; - } else { - static::$renderer->apiContext->errors[] = [ - 'file' => ($context !== null) ? $context->sourceFile : null, - 'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), - ]; - - return [ - ['brokenApiLink', '' . $type->name . '::' . $subjectName . ''], - $offset - ]; - } - } - } elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) { - return [ - ['apiLink', static::$renderer->createSubjectLink($subject, $title)], - $offset - ]; - } - - if ($context !== null) { - // Collection resolves relative types - $object = (new Collection([$object], $context->phpDocContext))->__toString(); - } - if (($type = static::$renderer->apiContext->getType($object)) !== null) { - return [ - ['apiLink', static::$renderer->createTypeLink($type, null, $title)], - $offset - ]; - } elseif (strpos($typeLink = static::$renderer->createTypeLink($object, null, $title), 'apiContext->errors[] = [ - 'file' => ($context !== null) ? $context->sourceFile : null, - 'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''), - ]; - - return [ - ['brokenApiLink', '' . $object . ''], - $offset - ]; - } - - return [['text', '[['], 2]; - } - - /** - * Renders API link - * @param array $block - * @return string - */ - protected function renderApiLink($block) - { - return $block[1]; - } - - /** - * Renders API link that is broken i.e. points nowhere - * @param array $block - * @return string - */ - protected function renderBrokenApiLink($block) - { - return $block[1]; - } -} diff --git a/extensions/apidoc/helpers/IndexFileAnalyzer.php b/extensions/apidoc/helpers/IndexFileAnalyzer.php deleted file mode 100644 index 767504859d..0000000000 --- a/extensions/apidoc/helpers/IndexFileAnalyzer.php +++ /dev/null @@ -1,85 +0,0 @@ -parse($text); - - return $this->_chapters; - } - - /** - * @inheritdoc - */ - protected function renderHeadline($block) - { - if ($this->_chapter === 0) { - $this->title = $this->renderAbsy($block['content']); - $this->introduction = ''; - $this->_chapter++; - } else { - $this->_chapter++; - $this->_chapters[$this->_chapter] = [ - 'headline' => $this->renderAbsy($block['content']), - 'content' => [], - ]; - } - return parent::renderHeadline($block); - } - - /** - * @inheritdoc - */ - protected function renderParagraph($block) - { - if ($this->_chapter < 1) { - $this->introduction .= $this->renderAbsy($block['content']); - } - return parent::renderParagraph($block); - } - - /** - * @inheritdoc - */ - protected function renderList($block) - { - if ($this->_chapter > 0) { - foreach ($block['items'] as $item => $absyElements) { - foreach($absyElements as $element) { - if ($element[0] === 'link') { - $this->_chapters[$this->_chapter]['content'][] = [ - 'headline' => $this->renderAbsy($element['text']), - 'file' => $element['url'], - ]; - } - } - } - } - return parent::renderList($block); - } -} diff --git a/extensions/apidoc/helpers/PrettyPrinter.php b/extensions/apidoc/helpers/PrettyPrinter.php deleted file mode 100644 index 214adc6405..0000000000 --- a/extensions/apidoc/helpers/PrettyPrinter.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @since 2.0 - */ -class PrettyPrinter extends \phpDocumentor\Reflection\PrettyPrinter -{ - /** - * @param PHPParser_Node_Expr_Array $node - * @return string - */ - public function pExpr_Array(PHPParser_Node_Expr_Array $node) - { - return '[' . $this->pCommaSeparated($node->items) . ']'; - } - - /** - * Returns a simple human readable output for a value. - * - * @param PHPParser_Node_Expr $value The value node as provided by PHP-Parser. - * @return string - */ - public static function getRepresentationOfValue(PHPParser_Node_Expr $value) - { - if ($value === null) { - return ''; - } - - $printer = new static(); - - return $printer->prettyPrintExpr($value); - } -} diff --git a/extensions/apidoc/models/BaseDoc.php b/extensions/apidoc/models/BaseDoc.php deleted file mode 100644 index 8edb1c98ea..0000000000 --- a/extensions/apidoc/models/BaseDoc.php +++ /dev/null @@ -1,157 +0,0 @@ - - * @since 2.0 - */ -class BaseDoc extends Object -{ - /** - * @var \phpDocumentor\Reflection\DocBlock\Context - */ - public $phpDocContext; - public $name; - public $sourceFile; - public $startLine; - public $endLine; - public $shortDescription; - public $description; - public $since; - public $deprecatedSince; - public $deprecatedReason; - /** - * @var \phpDocumentor\Reflection\DocBlock\Tag[] - */ - public $tags = []; - - - /** - * Checks if doc has tag of a given name - * @param string $name tag name - * @return boolean if doc has tag of a given name - */ - public function hasTag($name) - { - foreach ($this->tags as $tag) { - if (strtolower($tag->getName()) == $name) { - return true; - } - } - return false; - } - - /** - * Removes tag of a given name - * @param string $name - */ - public function removeTag($name) - { - foreach ($this->tags as $i => $tag) { - if (strtolower($tag->getName()) == $name) { - unset($this->tags[$i]); - } - } - } - - - /** - * @param \phpDocumentor\Reflection\BaseReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($config); - - if ($reflector === null) { - return; - } - - // base properties - $this->name = ltrim($reflector->getName(), '\\'); - $this->startLine = $reflector->getNode()->getAttribute('startLine'); - $this->endLine = $reflector->getNode()->getAttribute('endLine'); - - $docblock = $reflector->getDocBlock(); - if ($docblock !== null) { - $this->shortDescription = ucfirst($docblock->getShortDescription()); - if (empty($this->shortDescription) && !($this instanceof PropertyDoc) && $context !== null && $docblock->getTagsByName('inheritdoc') === null) { - $context->errors[] = [ - 'line' => $this->startLine, - 'file' => $this->sourceFile, - 'message' => "No short description for " . substr(StringHelper::basename(get_class($this)), 0, -3) . " '{$this->name}'", - ]; - } - $this->description = $docblock->getLongDescription()->getContents(); - - $this->phpDocContext = $docblock->getContext(); - - $this->tags = $docblock->getTags(); - foreach ($this->tags as $i => $tag) { - if ($tag instanceof SinceTag) { - $this->since = $tag->getVersion(); - unset($this->tags[$i]); - } elseif ($tag instanceof DeprecatedTag) { - $this->deprecatedSince = $tag->getVersion(); - $this->deprecatedReason = $tag->getDescription(); - unset($this->tags[$i]); - } - } - } elseif ($context !== null) { - $context->errors[] = [ - 'line' => $this->startLine, - 'file' => $this->sourceFile, - 'message' => "No docblock for element '{$this->name}'", - ]; - } - } - - // TODO implement -// public function loadSource($reflection) -// { -// $this->sourceFile; -// $this->startLine; -// $this->endLine; -// } -// -// public function getSourceCode() -// { -// $lines = file(YII2_PATH . $this->sourcePath); -// return implode("", array_slice($lines, $this->startLine - 1, $this->endLine - $this->startLine + 1)); -// } - - /** - * Extracts first sentence out of text - * @param string $text - * @return string - */ - public static function extractFirstSentence($text) - { - if (mb_strlen($text) > 4 && ($pos = mb_strpos($text, '.', 4, 'utf-8')) !== false) { - $sentence = mb_substr($text, 0, $pos + 1, 'utf-8'); - if (mb_strlen($text) >= $pos + 3) { - $abbrev = mb_substr($text, $pos - 1, 4); - if ($abbrev === 'e.g.' || $abbrev === 'i.e.') { // do not break sentence after abbreviation - $sentence .= static::extractFirstSentence(mb_substr($text, $pos + 1)); - } - } - return $sentence; - } else { - return $text; - } - } -} diff --git a/extensions/apidoc/models/ClassDoc.php b/extensions/apidoc/models/ClassDoc.php deleted file mode 100644 index 473979053f..0000000000 --- a/extensions/apidoc/models/ClassDoc.php +++ /dev/null @@ -1,115 +0,0 @@ - - * @since 2.0 - */ -class ClassDoc extends TypeDoc -{ - public $parentClass; - public $isAbstract; - public $isFinal; - /** - * @var string[] - */ - public $interfaces = []; - public $traits = []; - // will be set by Context::updateReferences() - public $subclasses = []; - /** - * @var EventDoc[] - */ - public $events = []; - /** - * @var ConstDoc[] - */ - public $constants = []; - - - /** - * @inheritdoc - */ - public function findSubject($subjectName) - { - if (($subject = parent::findSubject($subjectName)) !== null) { - return $subject; - } - foreach ($this->events as $name => $event) { - if ($subjectName == $name) { - return $event; - } - } - foreach ($this->constants as $name => $constant) { - if ($subjectName == $name) { - return $constant; - } - } - - return null; - } - - /** - * @return EventDoc[] - */ - public function getNativeEvents() - { - $events = []; - foreach ($this->events as $name => $event) { - if ($event->definedBy != $this->name) { - continue; - } - $events[$name] = $event; - } - - return $events; - } - - /** - * @inheritdoc - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - $this->parentClass = ltrim($reflector->getParentClass(), '\\'); - if (empty($this->parentClass)) { - $this->parentClass = null; - } - $this->isAbstract = $reflector->isAbstract(); - $this->isFinal = $reflector->isFinal(); - - foreach ($reflector->getInterfaces() as $interface) { - $this->interfaces[] = ltrim($interface, '\\'); - } - foreach ($reflector->getTraits() as $trait) { - $this->traits[] = ltrim($trait, '\\'); - } - foreach ($reflector->getConstants() as $constantReflector) { - $docblock = $constantReflector->getDocBlock(); - if ($docblock !== null && count($docblock->getTagsByName('event')) > 0) { - $event = new EventDoc($constantReflector); - $event->definedBy = $this->name; - $this->events[$event->name] = $event; - } else { - $constant = new ConstDoc($constantReflector); - $constant->definedBy = $this->name; - $this->constants[$constant->name] = $constant; - } - } - } -} diff --git a/extensions/apidoc/models/ConstDoc.php b/extensions/apidoc/models/ConstDoc.php deleted file mode 100644 index 2d8355512d..0000000000 --- a/extensions/apidoc/models/ConstDoc.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @since 2.0 - */ -class ConstDoc extends BaseDoc -{ - public $definedBy; - public $value; - - - /** - * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - $this->value = $reflector->getValue(); - } -} diff --git a/extensions/apidoc/models/Context.php b/extensions/apidoc/models/Context.php deleted file mode 100644 index f35d77f096..0000000000 --- a/extensions/apidoc/models/Context.php +++ /dev/null @@ -1,398 +0,0 @@ - - * @since 2.0 - */ -class Context extends Component -{ - /** - * @var array list of php files that have been added to this context. - */ - public $files = []; - /** - * @var ClassDoc[] - */ - public $classes = []; - /** - * @var InterfaceDoc[] - */ - public $interfaces = []; - /** - * @var TraitDoc[] - */ - public $traits = []; - public $errors = []; - - - /** - * Returning TypeDoc for a type given - * @param string $type - * @return null|ClassDoc|InterfaceDoc|TraitDoc - */ - public function getType($type) - { - $type = ltrim($type, '\\'); - if (isset($this->classes[$type])) { - return $this->classes[$type]; - } elseif (isset($this->interfaces[$type])) { - return $this->interfaces[$type]; - } elseif (isset($this->traits[$type])) { - return $this->traits[$type]; - } - - return null; - } - - /** - * Adds file to context - * @param string $fileName - */ - public function addFile($fileName) - { - $this->files[$fileName] = sha1_file($fileName); - - $reflection = new FileReflector($fileName, true); - $reflection->process(); - - foreach ($reflection->getClasses() as $class) { - $class = new ClassDoc($class, $this, ['sourceFile' => $fileName]); - $this->classes[$class->name] = $class; - } - foreach ($reflection->getInterfaces() as $interface) { - $interface = new InterfaceDoc($interface, $this, ['sourceFile' => $fileName]); - $this->interfaces[$interface->name] = $interface; - } - foreach ($reflection->getTraits() as $trait) { - $trait = new TraitDoc($trait, $this, ['sourceFile' => $fileName]); - $this->traits[$trait->name] = $trait; - } - } - - /** - * Updates references - */ - public function updateReferences() - { - // update all subclass references - foreach ($this->classes as $class) { - $className = $class->name; - while (isset($this->classes[$class->parentClass])) { - $class = $this->classes[$class->parentClass]; - $class->subclasses[] = $className; - } - } - // update interfaces of subclasses - foreach ($this->classes as $class) { - $this->updateSubclassInterfacesTraits($class); - } - // update implementedBy and usedBy for interfaces and traits - foreach ($this->classes as $class) { - foreach ($class->traits as $trait) { - if (isset($this->traits[$trait])) { - $trait = $this->traits[$trait]; - $trait->usedBy[] = $class->name; - $class->properties = array_merge($trait->properties, $class->properties); - $class->methods = array_merge($trait->methods, $class->methods); - } - } - foreach ($class->interfaces as $interface) { - if (isset($this->interfaces[$interface])) { - $this->interfaces[$interface]->implementedBy[] = $class->name; - if ($class->isAbstract) { - // add not implemented interface methods - foreach ($this->interfaces[$interface]->methods as $method) { - if (!isset($class->methods[$method->name])) { - $class->methods[$method->name] = $method; - } - } - } - } - } - } - // inherit docs - foreach ($this->classes as $class) { - $this->inheritDocs($class); - } - // inherit properties, methods, contants and events to subclasses - foreach ($this->classes as $class) { - $this->updateSubclassInheritance($class); - } - // add properties from getters and setters - foreach ($this->classes as $class) { - $this->handlePropertyFeature($class); - } - - // TODO reference exceptions to methods where they are thrown - } - - /** - * Add implemented interfaces and used traits to subclasses - * @param ClassDoc $class - */ - protected function updateSubclassInterfacesTraits($class) - { - foreach ($class->subclasses as $subclass) { - $subclass = $this->classes[$subclass]; - $subclass->interfaces = array_unique(array_merge($subclass->interfaces, $class->interfaces)); - $subclass->traits = array_unique(array_merge($subclass->traits, $class->traits)); - $this->updateSubclassInterfacesTraits($subclass); - } - } - - /** - * Add implemented interfaces and used traits to subclasses - * @param ClassDoc $class - */ - protected function updateSubclassInheritance($class) - { - foreach ($class->subclasses as $subclass) { - $subclass = $this->classes[$subclass]; - $subclass->events = array_merge($class->events, $subclass->events); - $subclass->constants = array_merge($class->constants, $subclass->constants); - $subclass->properties = array_merge($class->properties, $subclass->properties); - $subclass->methods = array_merge($class->methods, $subclass->methods); - $this->updateSubclassInheritance($subclass); - } - } - - /** - * Inhertit docsblocks using `@inheritDoc` tag. - * @param ClassDoc $class - * @see http://phpdoc.org/docs/latest/guides/inheritance.html - */ - protected function inheritDocs($class) - { - // TODO also for properties? - foreach ($class->methods as $m) { - if ($m->hasTag('inheritdoc')) { - $inheritedMethod = $this->inheritMethodRecursive($m, $class); - if (!$inheritedMethod) { - $this->errors[] = [ - 'line' => $m->startLine, - 'file' => $class->sourceFile, - 'message' => "Method {$m->name} has no parent to inherit from in {$class->name}.", - ]; - continue; - } - foreach (['shortDescription', 'description', 'return', 'returnType', 'returnTypes', 'exceptions'] as $property) { - // set all properties that are empty. descriptions will be concatenated. - if (empty($m->$property) || is_string($m->$property) && trim($m->$property) === '') { - $m->$property = $inheritedMethod->$property; - } elseif ($property == 'description') { - $m->$property = rtrim($m->$property) . "\n\n" . ltrim($inheritedMethod->$property); - } - } - foreach ($m->params as $i => $param) { - if (!isset($inheritedMethod->params[$i])) { - $this->errors[] = [ - 'line' => $m->startLine, - 'file' => $class->sourceFile, - 'message' => "Method param $i does not exist in parent method, @inheritdoc not possible in {$m->name} in {$class->name}.", - ]; - continue; - } - if (empty($param->description) || trim($param->description) === '') { - $param->description = $inheritedMethod->params[$i]->description; - } - if (empty($param->type) || trim($param->type) === '') { - $param->type = $inheritedMethod->params[$i]->type; - } - if (empty($param->types)) { - $param->types = $inheritedMethod->params[$i]->types; - } - } - $m->removeTag('inheritdoc'); - } - } - } - - /** - * @param MethodDoc $method - * @param ClassDoc $class - * @return mixed - */ - private function inheritMethodRecursive($method, $class) - { - $inheritanceCandidates = array_merge( - $this->getParents($class), - $this->getInterfaces($class) - ); - - $methods = []; - foreach($inheritanceCandidates as $candidate) { - if (isset($candidate->methods[$method->name])) { - $cmethod = $candidate->methods[$method->name]; - if ($cmethod->hasTag('inheritdoc')) { - $this->inheritDocs($candidate); - } - $methods[] = $cmethod; - } - } - - return reset($methods); - } - - /** - * @param ClassDoc $class - * @return array - */ - private function getParents($class) - { - if ($class->parentClass === null || !isset($this->classes[$class->parentClass])) { - return []; - } - return array_merge([$this->classes[$class->parentClass]], $this->getParents($this->classes[$class->parentClass])); - } - - /** - * @param ClassDoc $class - * @return array - */ - private function getInterfaces($class) - { - $interfaces = []; - foreach($class->interfaces as $interface) { - if (isset($this->interfaces[$interface])) { - $interfaces[] = $this->interfaces[$interface]; - } - } - return $interfaces; - } - - /** - * Add properties for getters and setters if class is subclass of [[\yii\base\Object]]. - * @param ClassDoc $class - */ - protected function handlePropertyFeature($class) - { - if (!$this->isSubclassOf($class, 'yii\base\Object')) { - return; - } - foreach ($class->getPublicMethods() as $name => $method) { - if ($method->isStatic) { - continue; - } - if (!strncmp($name, 'get', 3) && strlen($name) > 3 && $this->hasNonOptionalParams($method)) { - $propertyName = '$' . lcfirst(substr($method->name, 3)); - if (isset($class->properties[$propertyName])) { - $property = $class->properties[$propertyName]; - if ($property->getter === null && $property->setter === null) { - $this->errors[] = [ - 'line' => $property->startLine, - 'file' => $class->sourceFile, - 'message' => "Property $propertyName conflicts with a defined getter {$method->name} in {$class->name}.", - ]; - } - $property->getter = $method; - } else { - $class->properties[$propertyName] = new PropertyDoc(null, $this, [ - 'name' => $propertyName, - 'definedBy' => $method->definedBy, - 'sourceFile' => $class->sourceFile, - 'visibility' => 'public', - 'isStatic' => false, - 'type' => $method->returnType, - 'types' => $method->returnTypes, - 'shortDescription' => BaseDoc::extractFirstSentence($method->return), - 'description' => $method->return, - 'getter' => $method - // TODO set default value - ]); - } - } - if (!strncmp($name, 'set', 3) && strlen($name) > 3 && $this->hasNonOptionalParams($method, 1)) { - $propertyName = '$' . lcfirst(substr($method->name, 3)); - if (isset($class->properties[$propertyName])) { - $property = $class->properties[$propertyName]; - if ($property->getter === null && $property->setter === null) { - $this->errors[] = [ - 'line' => $property->startLine, - 'file' => $class->sourceFile, - 'message' => "Property $propertyName conflicts with a defined setter {$method->name} in {$class->name}.", - ]; - } - $property->setter = $method; - } else { - $param = $this->getFirstNotOptionalParameter($method); - $class->properties[$propertyName] = new PropertyDoc(null, $this, [ - 'name' => $propertyName, - 'definedBy' => $method->definedBy, - 'sourceFile' => $class->sourceFile, - 'visibility' => 'public', - 'isStatic' => false, - 'type' => $param->type, - 'types' => $param->types, - 'shortDescription' => BaseDoc::extractFirstSentence($param->description), - 'description' => $param->description, - 'setter' => $method - ]); - } - } - } - } - - /** - * Check whether a method has `$number` non-optional parameters. - * @param MethodDoc $method - * @param integer $number number of not optional parameters - * @return bool - */ - private function hasNonOptionalParams($method, $number = 0) - { - $count = 0; - foreach ($method->params as $param) { - if (!$param->isOptional) { - $count++; - } - } - return $count == $number; - } - - /** - * @param MethodDoc $method - * @return ParamDoc - */ - private function getFirstNotOptionalParameter($method) - { - foreach ($method->params as $param) { - if (!$param->isOptional) { - return $param; - } - } - return null; - } - - /** - * @param ClassDoc $classA - * @param ClassDoc|string $classB - * @return boolean - */ - protected function isSubclassOf($classA, $classB) - { - if (is_object($classB)) { - $classB = $classB->name; - } - if ($classA->name == $classB) { - return true; - } - while ($classA->parentClass !== null && isset($this->classes[$classA->parentClass])) { - $classA = $this->classes[$classA->parentClass]; - if ($classA->name == $classB) { - return true; - } - } - return false; - } -} diff --git a/extensions/apidoc/models/EventDoc.php b/extensions/apidoc/models/EventDoc.php deleted file mode 100644 index 7bb99914c9..0000000000 --- a/extensions/apidoc/models/EventDoc.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @since 2.0 - */ -class EventDoc extends ConstDoc -{ - public $type; - public $types; - - - /** - * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - foreach ($this->tags as $i => $tag) { - if ($tag->getName() == 'event') { - $eventTag = new ReturnTag('event', $tag->getContent(), $tag->getDocBlock(), $tag->getLocation()); - $this->type = $eventTag->getType(); - $this->types = $eventTag->getTypes(); - $this->description = ucfirst($eventTag->getDescription()); - $this->shortDescription = BaseDoc::extractFirstSentence($this->description); - unset($this->tags[$i]); - } - } - } -} diff --git a/extensions/apidoc/models/FunctionDoc.php b/extensions/apidoc/models/FunctionDoc.php deleted file mode 100644 index 3a022d87ee..0000000000 --- a/extensions/apidoc/models/FunctionDoc.php +++ /dev/null @@ -1,82 +0,0 @@ - - * @since 2.0 - */ -class FunctionDoc extends BaseDoc -{ - /** - * @var ParamDoc[] - */ - public $params = []; - public $exceptions = []; - public $return; - public $returnType; - public $returnTypes; - public $isReturnByReference; - - - /** - * @param \phpDocumentor\Reflection\FunctionReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - $this->isReturnByReference = $reflector->isByRef(); - - foreach ($reflector->getArguments() as $arg) { - $arg = new ParamDoc($arg, $context, ['sourceFile' => $this->sourceFile]); - $this->params[$arg->name] = $arg; - } - - foreach ($this->tags as $i => $tag) { - if ($tag instanceof ThrowsTag) { - $this->exceptions[$tag->getType()] = $tag->getDescription(); - unset($this->tags[$i]); - } elseif ($tag instanceof PropertyTag) { - // ignore property tag - } elseif ($tag instanceof ParamTag) { - $paramName = $tag->getVariableName(); - if (!isset($this->params[$paramName]) && $context !== null) { - $context->errors[] = [ - 'line' => $this->startLine, - 'file' => $this->sourceFile, - 'message' => "Undefined parameter documented: $paramName in {$this->name}().", - ]; - continue; - } - $this->params[$paramName]->description = ucfirst($tag->getDescription()); - $this->params[$paramName]->type = $tag->getType(); - $this->params[$paramName]->types = $tag->getTypes(); - unset($this->tags[$i]); - } elseif ($tag instanceof ReturnTag) { - $this->returnType = $tag->getType(); - $this->returnTypes = $tag->getTypes(); - $this->return = ucfirst($tag->getDescription()); - unset($this->tags[$i]); - } - } - } -} diff --git a/extensions/apidoc/models/InterfaceDoc.php b/extensions/apidoc/models/InterfaceDoc.php deleted file mode 100644 index 44261044a2..0000000000 --- a/extensions/apidoc/models/InterfaceDoc.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @since 2.0 - */ -class InterfaceDoc extends TypeDoc -{ - public $parentInterfaces = []; - // will be set by Context::updateReferences() - public $implementedBy = []; - - - /** - * @param \phpDocumentor\Reflection\InterfaceReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - foreach ($reflector->getParentInterfaces() as $interface) { - $this->parentInterfaces[] = ltrim($interface, '\\'); - } - - foreach ($this->methods as $method) { - $method->isAbstract = true; - } - - // interface can not have properties - $this->properties = null; - } -} diff --git a/extensions/apidoc/models/MethodDoc.php b/extensions/apidoc/models/MethodDoc.php deleted file mode 100644 index 6b0d3299cf..0000000000 --- a/extensions/apidoc/models/MethodDoc.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @since 2.0 - */ -class MethodDoc extends FunctionDoc -{ - public $isAbstract; - public $isFinal; - public $isStatic; - public $visibility; - // will be set by creating class - public $definedBy; - - - /** - * @param \phpDocumentor\Reflection\ClassReflector\MethodReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - $this->isAbstract = $reflector->isAbstract(); - $this->isFinal = $reflector->isFinal(); - $this->isStatic = $reflector->isStatic(); - - $this->visibility = $reflector->getVisibility(); - } -} diff --git a/extensions/apidoc/models/ParamDoc.php b/extensions/apidoc/models/ParamDoc.php deleted file mode 100644 index d9f65522bd..0000000000 --- a/extensions/apidoc/models/ParamDoc.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @since 2.0 - */ -class ParamDoc extends Object -{ - public $name; - public $typeHint; - public $isOptional; - public $defaultValue; - public $isPassedByReference; - // will be set by creating class - public $description; - public $type; - public $types; - public $sourceFile; - - - /** - * @param \phpDocumentor\Reflection\FunctionReflector\ArgumentReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($config); - - if ($reflector === null) { - return; - } - - $this->name = $reflector->getName(); - $this->typeHint = $reflector->getType(); - $this->isOptional = $reflector->getDefault() !== null; - - // bypass $reflector->getDefault() for short array syntax - if ($reflector->getNode()->default) { - $this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default); - } - $this->isPassedByReference = $reflector->isByRef(); - } -} diff --git a/extensions/apidoc/models/PropertyDoc.php b/extensions/apidoc/models/PropertyDoc.php deleted file mode 100644 index 8ee0485530..0000000000 --- a/extensions/apidoc/models/PropertyDoc.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @since 2.0 - */ -class PropertyDoc extends BaseDoc -{ - public $visibility; - public $isStatic; - public $type; - public $types; - public $defaultValue; - // will be set by creating class - public $getter; - public $setter; - // will be set by creating class - public $definedBy; - - - /** - * @return bool if property is read only - */ - public function getIsReadOnly() - { - return $this->getter !== null && $this->setter === null; - } - - /** - * @return bool if property is write only - */ - public function getIsWriteOnly() - { - return $this->getter === null && $this->setter !== null; - } - - /** - * @param \phpDocumentor\Reflection\ClassReflector\PropertyReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - $this->visibility = $reflector->getVisibility(); - $this->isStatic = $reflector->isStatic(); - - // bypass $reflector->getDefault() for short array syntax - if ($reflector->getNode()->default) { - $this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default); - } - - $hasInheritdoc = false; - foreach ($this->tags as $tag) { - if ($tag->getName() === 'inheritdoc') { - $hasInheritdoc = true; - } - if ($tag instanceof VarTag) { - $this->type = $tag->getType(); - $this->types = $tag->getTypes(); - $this->description = ucfirst($tag->getDescription()); - $this->shortDescription = BaseDoc::extractFirstSentence($this->description); - } - } - if (empty($this->shortDescription) && $context !== null && !$hasInheritdoc) { - $context->errors[] = [ - 'line' => $this->startLine, - 'file' => $this->sourceFile, - 'message' => "No short description for element '{$this->name}'", - ]; - } - } -} diff --git a/extensions/apidoc/models/TraitDoc.php b/extensions/apidoc/models/TraitDoc.php deleted file mode 100644 index a49bc95283..0000000000 --- a/extensions/apidoc/models/TraitDoc.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @since 2.0 - */ -class TraitDoc extends TypeDoc -{ - // classes using the trait - // will be set by Context::updateReferences() - public $usedBy = []; - public $traits = []; - - - /** - * @param \phpDocumentor\Reflection\TraitReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - if ($reflector === null) { - return; - } - - foreach ($reflector->getTraits() as $trait) { - $this->traits[] = ltrim($trait, '\\'); - } - } -} diff --git a/extensions/apidoc/models/TypeDoc.php b/extensions/apidoc/models/TypeDoc.php deleted file mode 100644 index 4278fb3be0..0000000000 --- a/extensions/apidoc/models/TypeDoc.php +++ /dev/null @@ -1,210 +0,0 @@ - - * @since 2.0 - */ -class TypeDoc extends BaseDoc -{ - public $authors = []; - /** - * @var MethodDoc[] - */ - public $methods = []; - /** - * @var PropertyDoc[] - */ - public $properties = []; - public $namespace; - - - /** - * Finds subject (method or property) by name - * - * If there is a property with the same as a method, the method will be returned if the name is not stated - * explicitly by prefixing with `$`. - * - * Example for method `attributes()` and property `$attributes` which both may exist: - * - * - `$subjectName = '$attributes'` finds a property or nothing. - * - `$subjectName = 'attributes()'` finds a method or nothing. - * - `$subjectName = 'attributes'` finds the method if it exists, if not it will find the property. - * - * @param $subjectName - * @return null|MethodDoc|PropertyDoc - */ - public function findSubject($subjectName) - { - if ($subjectName[0] != '$') { - foreach ($this->methods as $name => $method) { - if (rtrim($subjectName, '()') == $name) { - return $method; - } - } - } - if (!empty($subjectName) && substr_compare($subjectName, '()', -2, 2) === 0) { - return null; - } - if ($this->properties === null) { - return null; - } - foreach ($this->properties as $name => $property) { - if (ltrim($subjectName, '$') == ltrim($name, '$')) { - return $property; - } - } - - return null; - } - - /** - * @return MethodDoc[] - */ - public function getNativeMethods() - { - return $this->getFilteredMethods(null, $this->name); - } - - /** - * @return MethodDoc[] - */ - public function getPublicMethods() - { - return $this->getFilteredMethods('public'); - } - - /** - * @return MethodDoc[] - */ - public function getProtectedMethods() - { - return $this->getFilteredMethods('protected'); - } - - /** - * @param string|null $visibility - * @param string|null $definedBy - * @return MethodDoc[] - */ - private function getFilteredMethods($visibility = null, $definedBy = null) - { - $methods = []; - foreach ($this->methods as $name => $method) { - if ($visibility !== null && $method->visibility != $visibility) { - continue; - } - if ($definedBy !== null && $method->definedBy != $definedBy) { - continue; - } - $methods[$name] = $method; - } - - return $methods; - } - - /** - * @return PropertyDoc[] - */ - public function getNativeProperties() - { - return $this->getFilteredProperties(null, $this->name); - } - - /** - * @return PropertyDoc[] - */ - public function getPublicProperties() - { - return $this->getFilteredProperties('public'); - } - - /** - * @return PropertyDoc[] - */ - public function getProtectedProperties() - { - return $this->getFilteredProperties('protected'); - } - - /** - * @param null $visibility - * @param null $definedBy - * @return PropertyDoc[] - */ - private function getFilteredProperties($visibility = null, $definedBy = null) - { - if ($this->properties === null) { - return []; - } - $properties = []; - foreach ($this->properties as $name => $property) { - if ($visibility !== null && $property->visibility != $visibility) { - continue; - } - if ($definedBy !== null && $property->definedBy != $definedBy) { - continue; - } - $properties[$name] = $property; - } - - return $properties; - } - - /** - * @param \phpDocumentor\Reflection\InterfaceReflector $reflector - * @param Context $context - * @param array $config - */ - public function __construct($reflector = null, $context = null, $config = []) - { - parent::__construct($reflector, $context, $config); - - $this->namespace = trim(StringHelper::dirname($this->name), '\\'); - - if ($reflector === null) { - return; - } - - foreach ($this->tags as $i => $tag) { - if ($tag instanceof AuthorTag) { - $this->authors[$tag->getAuthorName()] = $tag->getAuthorEmail(); - unset($this->tags[$i]); - } - } - - foreach ($reflector->getProperties() as $propertyReflector) { - if ($propertyReflector->getVisibility() != 'private') { - $property = new PropertyDoc($propertyReflector, $context, ['sourceFile' => $this->sourceFile]); - $property->definedBy = $this->name; - $this->properties[$property->name] = $property; - } - } - - foreach ($reflector->getMethods() as $methodReflector) { - if ($methodReflector->getVisibility() != 'private') { - $method = new MethodDoc($methodReflector, $context, ['sourceFile' => $this->sourceFile]); - $method->definedBy = $this->name; - $this->methods[$method->name] = $method; - } - } - } -} diff --git a/extensions/apidoc/renderers/ApiRenderer.php b/extensions/apidoc/renderers/ApiRenderer.php deleted file mode 100644 index b082a113da..0000000000 --- a/extensions/apidoc/renderers/ApiRenderer.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @since 2.0 - */ -abstract class ApiRenderer extends BaseRenderer -{ - /** - * Renders a given [[Context]]. - * - * @param Context $context the api documentation context to render. - * @param $targetDir - */ - abstract public function render($context, $targetDir); -} diff --git a/extensions/apidoc/renderers/BaseRenderer.php b/extensions/apidoc/renderers/BaseRenderer.php deleted file mode 100644 index bb7c5051be..0000000000 --- a/extensions/apidoc/renderers/BaseRenderer.php +++ /dev/null @@ -1,213 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseRenderer extends Component -{ - /** - * @deprecated since 2.0.1 use [[$guidePrefix]] instead which allows configuring this options - */ - const GUIDE_PREFIX = 'guide-'; - - public $guidePrefix = 'guide-'; - public $apiUrl; - /** - * @var Context the [[Context]] currently being rendered. - */ - public $apiContext; - /** - * @var Controller the apidoc controller instance. Can be used to control output. - */ - public $controller; - public $guideUrl; - - - public function init() - { - ApiMarkdown::$renderer = $this; - ApiMarkdownLaTeX::$renderer = $this; - } - - /** - * creates a link to a type (class, interface or trait) - * @param ClassDoc|InterfaceDoc|TraitDoc|ClassDoc[]|InterfaceDoc[]|TraitDoc[]|string|string[] $types - * @param string $title a title to be used for the link TODO check whether [[yii\...|Class]] is supported - * @param BaseDoc $context - * @param array $options additional HTML attributes for the link. - * @return string - */ - public function createTypeLink($types, $context = null, $title = null, $options = []) - { - if (!is_array($types)) { - $types = [$types]; - } - if (count($types) > 1) { - $title = null; - } - $links = []; - foreach ($types as $type) { - $postfix = ''; - if (is_string($type)) { - if (!empty($type) && substr_compare($type, '[]', -2, 2) === 0) { - $postfix = '[]'; - $type = substr($type, 0, -2); - } - - if (($t = $this->apiContext->getType(ltrim($type, '\\'))) !== null) { - $type = $t; - } elseif ($type[0] !== '\\' && ($t = $this->apiContext->getType($this->resolveNamespace($context) . '\\' . ltrim($type, '\\'))) !== null) { - $type = $t; - } else { - ltrim($type, '\\'); - } - } - if (is_string($type)) { - $linkText = ltrim($type, '\\'); - if ($title !== null) { - $linkText = $title; - } - $phpTypes = [ - 'callable', - 'array', - 'string', - 'boolean', - 'integer', - 'float', - 'object', - 'resource', - 'null', - ]; - // check if it is PHP internal class - if (((class_exists($type, false) || interface_exists($type, false) || trait_exists($type, false)) && - ($reflection = new \ReflectionClass($type)) && $reflection->isInternal())) { - $links[] = $this->generateLink($linkText, 'http://www.php.net/class.' . strtolower(ltrim($type, '\\')), $options) . $postfix; - } elseif (in_array($type, $phpTypes)) { - $links[] = $this->generateLink($linkText, 'http://www.php.net/language.types.' . strtolower(ltrim($type, '\\')), $options) . $postfix; - } else { - $links[] = $type; - } - } elseif ($type instanceof BaseDoc) { - $linkText = $type->name; - if ($title !== null) { - $linkText = $title; - } - $links[] = $this->generateLink($linkText, $this->generateApiUrl($type->name), $options) . $postfix; - } - } - - return implode('|', $links); - } - - /** - * creates a link to a subject - * @param PropertyDoc|MethodDoc|ConstDoc|EventDoc $subject - * @param string $title - * @param array $options additional HTML attributes for the link. - * @return string - */ - public function createSubjectLink($subject, $title = null, $options = []) - { - if ($title === null) { - if ($subject instanceof MethodDoc) { - $title = $subject->name . '()'; - } else { - $title = $subject->name; - } - } - if (($type = $this->apiContext->getType($subject->definedBy)) === null) { - return $subject->name; - } else { - $link = $this->generateApiUrl($type->name); - if ($subject instanceof MethodDoc) { - $link .= '#' . $subject->name . '()'; - } else { - $link .= '#' . $subject->name; - } - $link .= '-detail'; - - return $this->generateLink($title, $link, $options); - } - } - - /** - * @param BaseDoc|string $context - * @return string - */ - private function resolveNamespace($context) - { - // TODO use phpdoc Context for this - if ($context === null) { - return ''; - } - if ($context instanceof TypeDoc) { - return $context->namespace; - } - if ($context->hasProperty('definedBy')) { - $type = $this->apiContext->getType($context); - if ($type !== null) { - return $type->namespace; - } - } - - return ''; - } - - /** - * generate link markup - * @param $text - * @param $href - * @param array $options additional HTML attributes for the link. - * @return mixed - */ - abstract protected function generateLink($text, $href, $options = []); - - /** - * Generate an url to a type in apidocs - * @param $typeName - * @return mixed - */ - abstract public function generateApiUrl($typeName); - - /** - * Generate an url to a guide page - * @param string $file - * @return string - */ - public function generateGuideUrl($file) - { - $hash = ''; - if (($pos = strpos($file, '#')) !== false) { - $hash = substr($file, $pos); - $file = substr($file, 0, $pos); - } - - return rtrim($this->guideUrl, '/') . '/' . $this->guidePrefix . basename($file, '.md') . '.html' . $hash; - } -} diff --git a/extensions/apidoc/renderers/GuideRenderer.php b/extensions/apidoc/renderers/GuideRenderer.php deleted file mode 100644 index 53a879ad52..0000000000 --- a/extensions/apidoc/renderers/GuideRenderer.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @since 2.0 - */ -abstract class GuideRenderer extends BaseRenderer -{ - /** - * Render markdown files - * - * @param array $files list of markdown files to render - * @param $targetDir - */ - abstract public function render($files, $targetDir); - - - /** - * Loads guide structure from a set of files - * @param array $files - * @return array - */ - protected function loadGuideStructure($files) - { - $chapters = []; - foreach ($files as $file) { - $contents = file_get_contents($file); - if (basename($file) == 'README.md') { - $indexAnalyzer = new IndexFileAnalyzer(); - $chapters = $indexAnalyzer->analyze($contents); - break; - } - if (preg_match("/^(.*)\n=+/", $contents, $matches)) { - $headlines[$file] = $matches[1]; - } else { - $headlines[$file] = basename($file); - } - - } - return $chapters; - } -} diff --git a/extensions/apidoc/templates/bootstrap/ApiRenderer.php b/extensions/apidoc/templates/bootstrap/ApiRenderer.php deleted file mode 100644 index 4eec0c1acf..0000000000 --- a/extensions/apidoc/templates/bootstrap/ApiRenderer.php +++ /dev/null @@ -1,125 +0,0 @@ - - * @since 2.0 - */ -class ApiRenderer extends \yii\apidoc\templates\html\ApiRenderer -{ - use RendererTrait; - - public $layout = '@yii/apidoc/templates/bootstrap/layouts/api.php'; - public $indexView = '@yii/apidoc/templates/bootstrap/views/index.php'; - - - /** - * @inheritdoc - */ - public function render($context, $targetDir) - { - $types = array_merge($context->classes, $context->interfaces, $context->traits); - - $extTypes = []; - foreach ($this->extensions as $k => $ext) { - $extType = $this->filterTypes($types, $ext); - if (empty($extType)) { - unset($this->extensions[$k]); - continue; - } - $extTypes[$ext] = $extType; - } - - // render view files - parent::render($context, $targetDir); - - if ($this->controller !== null) { - $this->controller->stdout('generating extension index files...'); - } - - foreach ($extTypes as $ext => $extType) { - $readme = @file_get_contents("https://raw.github.com/yiisoft/yii2-$ext/master/README.md"); - $indexFileContent = $this->renderWithLayout($this->indexView, [ - 'docContext' => $context, - 'types' => $extType, - 'readme' => $readme ?: null, - ]); - file_put_contents($targetDir . "/ext-{$ext}-index.html", $indexFileContent); - } - - $yiiTypes = $this->filterTypes($types, 'yii'); - if (empty($yiiTypes)) { -// $readme = @file_get_contents("https://raw.github.com/yiisoft/yii2-framework/master/README.md"); - $indexFileContent = $this->renderWithLayout($this->indexView, [ - 'docContext' => $context, - 'types' => $this->filterTypes($types, 'app'), - 'readme' => null, - ]); - } else { - $readme = @file_get_contents("https://raw.github.com/yiisoft/yii2-framework/master/README.md"); - $indexFileContent = $this->renderWithLayout($this->indexView, [ - 'docContext' => $context, - 'types' => $yiiTypes, - 'readme' => $readme ?: null, - ]); - } - file_put_contents($targetDir . '/index.html', $indexFileContent); - - if ($this->controller !== null) { - $this->controller->stdout('done.' . PHP_EOL, Console::FG_GREEN); - $this->controller->stdout('generating search index...'); - } - - $indexer = new ApiIndexer(); - $indexer->indexFiles(FileHelper::findFiles($targetDir, ['only' => ['*.html']]), $targetDir); - $js = $indexer->exportJs(); - file_put_contents($targetDir . '/jssearch.index.js', $js); - - if ($this->controller !== null) { - $this->controller->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } - } - - /** - * @inheritdoc - */ - public function getSourceUrl($type, $line = null) - { - if (is_string($type)) { - $type = $this->apiContext->getType($type); - } - - $baseUrl = 'https://github.com/yiisoft/yii2/blob/master'; - switch ($this->getTypeCategory($type)) { - case 'yii': - if ($type->name == 'Yii') { - $url = '/framework/Yii.php'; - } else { - $url = '/framework/' . str_replace('\\', '/', substr($type->name, 4)) . '.php'; - } - break; - case 'app': - return null; - default: - $url = '/extensions/' . str_replace('\\', '/', substr($type->name, 4)) . '.php'; - break; - } - - if ($line === null) - return $baseUrl . $url; - else - return $baseUrl . $url . '#L' . $line; - } -} diff --git a/extensions/apidoc/templates/bootstrap/GuideRenderer.php b/extensions/apidoc/templates/bootstrap/GuideRenderer.php deleted file mode 100644 index 21a2bb63f1..0000000000 --- a/extensions/apidoc/templates/bootstrap/GuideRenderer.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @since 2.0 - */ -class GuideRenderer extends \yii\apidoc\templates\html\GuideRenderer -{ - use RendererTrait; - - public $layout = '@yii/apidoc/templates/bootstrap/layouts/guide.php'; - - - /** - * @inheritDoc - */ - public function render($files, $targetDir) - { - $types = array_merge($this->apiContext->classes, $this->apiContext->interfaces, $this->apiContext->traits); - - $extTypes = []; - foreach ($this->extensions as $k => $ext) { - $extType = $this->filterTypes($types, $ext); - if (empty($extType)) { - unset($this->extensions[$k]); - continue; - } - $extTypes[$ext] = $extType; - } - - parent::render($files, $targetDir); - - if ($this->controller !== null) { - $this->controller->stdout('generating search index...'); - } - - $indexer = new ApiIndexer(); - $indexer->indexFiles(FileHelper::findFiles($targetDir, ['only' => ['*.html']]), $targetDir); - $js = $indexer->exportJs(); - file_put_contents($targetDir . '/jssearch.index.js', $js); - - if ($this->controller !== null) { - $this->controller->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } - } -} diff --git a/extensions/apidoc/templates/bootstrap/RendererTrait.php b/extensions/apidoc/templates/bootstrap/RendererTrait.php deleted file mode 100644 index 6e1775de35..0000000000 --- a/extensions/apidoc/templates/bootstrap/RendererTrait.php +++ /dev/null @@ -1,119 +0,0 @@ -filterTypes($types, $this->getTypeCategory($type)); - } - - /** - * Returns category of TypeDoc - * @param TypeDoc $type - * @return string - */ - protected function getTypeCategory($type) - { - $extensions = $this->extensions; - $navClasses = 'app'; - if (isset($type)) { - if ($type->name == 'Yii') { - $navClasses = 'yii'; - } elseif (strncmp($type->name, 'yii\\', 4) == 0) { - $navClasses = 'yii'; - $subName = substr($type->name, 4); - if (($pos = strpos($subName, '\\')) !== false) { - $subNamespace = substr($subName, 0, $pos); - if (in_array($subNamespace, $extensions)) { - $navClasses = $subNamespace; - } - } - } - } - - return $navClasses; - } - - /** - * Returns types of a given class - * - * @param TypeDoc[] $types - * @param string $navClasses - * @return array - */ - protected function filterTypes($types, $navClasses) - { - switch ($navClasses) { - case 'app': - $types = array_filter($types, function ($val) { - return strncmp($val->name, 'yii\\', 4) !== 0; - }); - break; - case 'yii': - $self = $this; - $types = array_filter($types, function ($val) use ($self) { - if ($val->name == 'Yii') { - return true; - } - if (strlen($val->name) < 5) { - return false; - } - $subName = substr($val->name, 4, strpos($val->name, '\\', 5) - 4); - - return strncmp($val->name, 'yii\\', 4) === 0 && !in_array($subName, $self->extensions); - }); - break; - default: - $types = array_filter($types, function ($val) use ($navClasses) { - return strncmp($val->name, "yii\\$navClasses\\", strlen("yii\\$navClasses\\")) === 0; - }); - } - - return $types; - } -} diff --git a/extensions/apidoc/templates/bootstrap/SideNavWidget.php b/extensions/apidoc/templates/bootstrap/SideNavWidget.php deleted file mode 100644 index 0a2214f03f..0000000000 --- a/extensions/apidoc/templates/bootstrap/SideNavWidget.php +++ /dev/null @@ -1,181 +0,0 @@ - [ - * [ - * 'label' => 'Home', - * 'url' => ['site/index'], - * 'linkOptions' => [...], - * ], - * [ - * 'label' => 'Dropdown', - * 'items' => [ - * ['label' => 'Level 1 - Dropdown A', 'url' => '#'], - * '
  • ', - * '', - * ['label' => 'Level 1 - Dropdown B', 'url' => '#'], - * ], - * ], - * ], - * ]); - * ``` - * - * Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 3. - * - * @see http://getbootstrap.com/components.html#dropdowns - * @see http://getbootstrap.com/components/#nav - * - * @author Antonio Ramirez - * @since 2.0 - */ -class SideNavWidget extends Widget -{ - /** - * @var array list of items in the nav widget. Each array element represents a single - * menu item which can be either a string or an array with the following structure: - * - * - label: string, required, the nav item label. - * - url: optional, the item's URL. Defaults to "#". - * - visible: boolean, optional, whether this menu item is visible. Defaults to true. - * - linkOptions: array, optional, the HTML attributes of the item's link. - * - options: array, optional, the HTML attributes of the item container (LI). - * - active: boolean, optional, whether the item should be on active state or not. - * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget, - * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. - * - * If a menu item is a string, it will be rendered directly without HTML encoding. - */ - public $items = []; - /** - * @var boolean whether the nav items labels should be HTML-encoded. - */ - public $encodeLabels = true; - /** - * @var string the route used to determine if a menu item is active or not. - * If not set, it will use the route of the current request. - * @see params - * @see isItemActive - */ - public $activeUrl; - - - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - if (!isset($this->options['class'])) { - Html::addCssClass($this->options, 'list-group'); - } - } - - /** - * Renders the widget. - */ - public function run() - { - echo $this->renderItems(); - BootstrapAsset::register($this->getView()); - } - - /** - * Renders widget items. - */ - public function renderItems() - { - $items = []; - foreach ($this->items as $i => $item) { - if (isset($item['visible']) && !$item['visible']) { - unset($items[$i]); - continue; - } - $items[] = $this->renderItem($item, count($this->items) !== 1); - } - - return Html::tag('div', implode("\n", $items), $this->options); - } - - /** - * Renders a widget's item. - * @param string|array $item the item to render. - * @param boolean $collapsed whether to collapse item if not active - * @throws \yii\base\InvalidConfigException - * @return string the rendering result. - * @throws InvalidConfigException if label is not defined - */ - public function renderItem($item, $collapsed = true) - { - if (is_string($item)) { - return $item; - } - if (!isset($item['label'])) { - throw new InvalidConfigException("The 'label' option is required."); - } - - $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; - $items = ArrayHelper::getValue($item, 'items'); - $url = Url::to(ArrayHelper::getValue($item, 'url', '#')); - $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); - Html::addCssClass($linkOptions, 'list-group-item'); - - if (isset($item['active'])) { - $active = ArrayHelper::remove($item, 'active', false); - } else { - $active = ($url == $this->activeUrl); - } - - if ($items !== null) { - $linkOptions['data-toggle'] = 'collapse'; - $linkOptions['data-parent'] = '#' . $this->id; - $id = $this->id . '-' . static::$counter++; - $url = '#' . $id; - $label .= ' ' . Html::tag('b', '', ['class' => 'caret']); - if (is_array($items)) { - if ($active === false) { - foreach ($items as $subItem) { - if (isset($subItem['active']) && $subItem['active']) { - $active = true; - } - } - } - $items = static::widget([ - 'id' => $id, - 'items' => $items, - 'encodeLabels' => $this->encodeLabels, - 'view' => $this->getView(), - 'options' => [ - 'class' => "submenu panel-collapse collapse" . ($active || !$collapsed ? ' in' : '') - ] - ]); - } - } - - if ($active) { - Html::addCssClass($linkOptions, 'active'); - } - - return Html::a($label, $url, $linkOptions) . $items; - } -} diff --git a/extensions/apidoc/templates/bootstrap/assets/AssetBundle.php b/extensions/apidoc/templates/bootstrap/assets/AssetBundle.php deleted file mode 100644 index 9a3b01c138..0000000000 --- a/extensions/apidoc/templates/bootstrap/assets/AssetBundle.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @since 2.0 - */ -class AssetBundle extends \yii\web\AssetBundle -{ - public $sourcePath = '@yii/apidoc/templates/bootstrap/assets/css'; - public $css = [ -// 'api.css', - 'style.css', - ]; - public $depends = [ - 'yii\web\JqueryAsset', - 'yii\bootstrap\BootstrapAsset', - 'yii\bootstrap\BootstrapPluginAsset', - ]; - public $jsOptions = [ - 'position' => View::POS_HEAD, - ]; -} diff --git a/extensions/apidoc/templates/bootstrap/assets/css/api.css b/extensions/apidoc/templates/bootstrap/assets/css/api.css deleted file mode 100644 index 6699d11761..0000000000 --- a/extensions/apidoc/templates/bootstrap/assets/css/api.css +++ /dev/null @@ -1,99 +0,0 @@ -pre { - color: #000000; - background-color: #FFF5E6; - font-family: "courier new", "times new roman", monospace; - line-height: 1.3em; - /* Put a nice border around it. */ - padding: 1px; - width: 90%; - /* Don't wrap its contents, and show scrollbars. */ - /* white-space: nowrap;*/ - overflow: auto; - /* Stop after about 24 lines, and just show a scrollbar. */ - /* max-height: 24em; */ - margin: 5px; - padding-left: 20px; - border: 1px solid #FFE6BF; - border-left: 6px solid #FFE6BF; -} - -code { - color: #000000; - background-color: #FFF5E6; - padding: 1px; -} - -div.code { - display: none; - color: #000000; - background-color: #FFF5E6; - font-family: "courier new", "times new roman", monospace; - line-height: 1.3em; - /* Put a nice border around it. */ - padding: 1px; - width: 90%; - /* Don't wrap its contents, and show scrollbars. */ - /* white-space: nowrap;*/ - overflow: auto; - /* Stop after about 24 lines, and just show a scrollbar. */ - /* max-height: 24em; */ - margin: 5px; - padding-left: 20px; - border-left: 6px solid #FFE6BF; -} - -table.summaryTable { - background: #E6ECFF; - border-collapse: collapse; - width: 100%; -} - -table.summaryTable th, table.summaryTable td { - border: 1px #BFCFFF solid; - padding: 0.2em; -} - -table.summaryTable th { - background: #CCD9FF; - text-align: left; -} - -#nav { - padding: 3px; - margin: 0 0 10px 0; - border-top: 1px #BFCFFF solid; -} - -#classDescription { - padding: 5px; - margin: 10px 0 20px 0; - border-bottom: 1px solid #BFCFFF; -} - -.detailHeader { - font-weight: bold; - font-size: 12pt; - margin: 30px 0 5px 0; - border-bottom: 1px solid #BFCFFF; -} - -.detailHeaderTag { - font-weight: normal; - font-size: 10pt; -} - - -.paramNameCol { - width: 12%; - font-weight: bold; -} - -.paramTypeCol { - width: 12%; -} - -.sourceCode { - margin: 5px 0; - padding:5px; - background:#FFF5E6; -} \ No newline at end of file diff --git a/extensions/apidoc/templates/bootstrap/assets/css/style.css b/extensions/apidoc/templates/bootstrap/assets/css/style.css deleted file mode 100644 index 6468186a35..0000000000 --- a/extensions/apidoc/templates/bootstrap/assets/css/style.css +++ /dev/null @@ -1,213 +0,0 @@ -html, -body { - height: 100%; -} - -.guide-content, .api-content { - max-width: 1250px; -} - -.wrap { - min-height: 100%; - height: auto; - width: auto; - margin: 60px 30px 0 30px; - padding: 0; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding: 20px 30px; -} - -#navigation { - margin-top: 20px; -} - -.submenu a { - background: #f5f5f5; - border-radius: 0; -} - -.submenu a:hover, .submenu a:active, -.submenu a.active, .submenu a.active:hover, .submenu a.active:active { - background: #44b5f6; - border-color: #44b5f6; - border-radius: 0; - color: #fff; -} - -.signature, .table-striped > tbody > tr > td.signature { - margin: 10px 0 10px 0; - padding: 8px; - - color: #000000; - background: rgba(230, 236, 255, 0.81); - border: 1px rgba(191, 207, 255, 0.81) solid; - - font-family: "courier new", "times new roman", monospace; - line-height: 1.3em; - white-space: pre-line; - word-wrap: break-word; - word-break: break-all; -} - -blockquote { - font-size: 14px; -} - -td p { - margin: 0; -} - -table.detail-table .param-name-col { width: 15%; min-width: 100px; } -table.detail-table .param-type-col { width: 15%; min-width: 150px; } -table.detail-table .param-desc-col { width: 70%; } - -table.summary-table .col-method { width: 20%; } -table.summary-table .col-property { width: 20%; } -table.summary-table .col-const { width: 20%; } -table.summary-table .col-event { width: 20%; } -table.summary-table .col-defined { width: 15%; } - -.detail-header { - margin-top: 30px; -} - -.doc-description { - border-left: solid 1px #ddd; - border-right: solid 1px #ddd; - padding: 10px 8px; - margin: 0; -} - -.event-doc .doc-description { - border-bottom: solid 1px #ddd; -} - -.doc-description p:last-child { - margin-bottom: 0; -} - -.tool-link { - float: right; - color: #ddd; - margin: 0 8px; -} - -.icon-hash:before { - content: '#'; - font-size: 32px; - font-weight: bold; - /*top: -10px;*/ - line-height: 0.5; -} - -.tool-link:hover { - color: #bbb; - text-decoration: none; -} - -.hashlink { - display: none; -} - -h1:hover .hashlink, h2:hover .hashlink, h3:hover .hashlink, h4:hover .hashlink, h5:hover .hashlink { - display: inline; -} - -.toplink { - position: fixed; - bottom: 50px; - right: 50px; - - padding: 5px 8px 0 5px; - - border: solid 1px #ddd; - border-radius: 5px; - background: #fff; -} - -.toplink a { - color: #bbb; - text-decoration: none; -} - -.toplink a:hover { - color: #999; - text-decoration: none; -} - -#search-resultbox { - position: fixed; - left: 25%; - right: 25%; - bottom: 0; - top: 50px; - z-index: 1000; - - border-radius: 0; - /* reset all due to android browser issues http://caniuse.com/#search=border-radius */ - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -#search-results { - margin: 0; - padding: 0; - list-style: none; - - overflow: auto; - max-height: 100%; -} - -#search-results li, #search-results li a { - margin: 0; - padding: 0; - display: block; - min-height: 50px; - width: 100%; - - color: #333333; -} - -#search-results li a .title, #search-results li .no-results { - padding: 10px 20px 5px 20px; - display: block; - - text-decoration: none; - font-weight: bold; - font-size: 120%; -} - -#search-results li a .description { - padding: 5px 20px 10px 20px; - display: block; - - text-decoration: none; - font-weight: normal; - font-size: 90%; - border-bottom: solid 1px #dddddd; -} - -#search-results li a:hover, #search-results li a.selected { - background: #44B5F6; - text-decoration: none; -} - -.navbar-form { - width: 50%; - max-width: 350px; -} - -.navbar-form div, .navbar-form .form-control { - width: 100%; -} - -.broken-link { - background: lightpink; -} diff --git a/extensions/apidoc/templates/bootstrap/views/index.php b/extensions/apidoc/templates/bootstrap/views/index.php deleted file mode 100644 index 26a40471db..0000000000 --- a/extensions/apidoc/templates/bootstrap/views/index.php +++ /dev/null @@ -1,38 +0,0 @@ -context; - -if (isset($readme)) { - echo \yii\apidoc\helpers\ApiMarkdown::process($readme); -} - -?>

    Class Reference

    - - - - - - - - - - - - $class): -?> - - - - - -
    ClassDescription
    createTypeLink($class, $class, $class->name) ?>shortDescription, $class, true) ?>
    diff --git a/extensions/apidoc/templates/html/README.md b/extensions/apidoc/templates/html/README.md deleted file mode 100644 index 195ed5e7ab..0000000000 --- a/extensions/apidoc/templates/html/README.md +++ /dev/null @@ -1,4 +0,0 @@ -The html API doc template -========================= - -This templates provides view files and a Renderer class that can be reused in other html templates. \ No newline at end of file diff --git a/extensions/apidoc/templates/html/views/seeAlso.php b/extensions/apidoc/templates/html/views/seeAlso.php deleted file mode 100644 index 8abbf31497..0000000000 --- a/extensions/apidoc/templates/html/views/seeAlso.php +++ /dev/null @@ -1,34 +0,0 @@ -definedBy; - -$see = []; -foreach ($object->tags as $tag) { - /** @var $tag phpDocumentor\Reflection\DocBlock\Tag\SeeTag */ - if (get_class($tag) == 'phpDocumentor\Reflection\DocBlock\Tag\SeeTag') { - $ref = $tag->getReference(); - if (strpos($ref, '://') === false) { - $ref = '[[' . $ref . ']]'; - } - $see[] = rtrim(\yii\apidoc\helpers\ApiMarkdown::process($ref . ' ' . $tag->getDescription(), $type, true), ". \r\n"); - } -} -if (empty($see)) { - return; -} elseif (count($see) == 1) { - echo '

    See also ' . reset($see) . '.

    '; -} else { - echo '

    See also:

      '; - foreach ($see as $ref) { - if (!empty($ref)) { - if (substr_compare($ref, '>', -1, 1)) { - $ref .= '.'; - } - echo "
    • $ref
    • "; - } - } - echo '
    '; -} diff --git a/extensions/apidoc/templates/online/README.md b/extensions/apidoc/templates/online/README.md deleted file mode 100644 index c8e2a3f84b..0000000000 --- a/extensions/apidoc/templates/online/README.md +++ /dev/null @@ -1,4 +0,0 @@ -The `online` API doc template -============================= - -This template is used to generate the Yii framework API docs for yiiframework.com. \ No newline at end of file diff --git a/extensions/apidoc/templates/pdf/GuideRenderer.php b/extensions/apidoc/templates/pdf/GuideRenderer.php deleted file mode 100644 index da9b84e5f4..0000000000 --- a/extensions/apidoc/templates/pdf/GuideRenderer.php +++ /dev/null @@ -1,87 +0,0 @@ - - * @since 2.0 - */ -class GuideRenderer extends \yii\apidoc\templates\html\GuideRenderer -{ - /** - * @inheritDoc - */ - public function render($files, $targetDir) - { -// $types = array_merge($this->apiContext->classes, $this->apiContext->interfaces, $this->apiContext->traits); -// -// $extTypes = []; -// foreach ($this->extensions as $k => $ext) { -// $extType = $this->filterTypes($types, $ext); -// if (empty($extType)) { -// unset($this->extensions[$k]); -// continue; -// } -// $extTypes[$ext] = $extType; -// } - - $fileCount = count($files) + 1; - if ($this->controller !== null) { - Console::startProgress(0, $fileCount, 'Rendering markdown files: ', false); - } - $done = 0; - $fileData = []; - $chapters = $this->loadGuideStructure($files); - foreach ($files as $file) { - $fileData[basename($file)] = file_get_contents($file); -// if (preg_match("/^(.*)\n=+/", $fileData[$file], $matches)) { -// $headlines[$file] = $matches[1]; -// } else { -// $headlines[$file] = basename($file); -// } - } - - $md = new ApiMarkdownLaTeX(); - $output = ''; - foreach ($chapters as $chapter) { - - $output .= '\chapter{' . $chapter['headline'] . "}\n"; - foreach($chapter['content'] as $content) { - if (isset($fileData[$content['file']])) { - $md->labelPrefix = $content['file'] . '#'; - $output .= '\label{'. $content['file'] . '}'; - $output .= $md->parse($fileData[$content['file']]) . "\n\n"; - } else { - $output .= '\newpage'; - $output .= '\label{'. $content['file'] . '}'; - $output .= '\textbf{Error: not existing file: '.$content['file'].'}\newpage'."\n"; - } - - if ($this->controller !== null) { - Console::updateProgress(++$done, $fileCount); - } - } - } - file_put_contents($targetDir . '/guide.tex', $output); - copy(__DIR__ . '/main.tex', $targetDir . '/main.tex'); - copy(__DIR__ . '/Makefile', $targetDir . '/Makefile'); - - if ($this->controller !== null) { - Console::updateProgress(++$done, $fileCount); - Console::endProgress(true); - $this->controller->stdout('done.' . PHP_EOL, Console::FG_GREEN); - } - - echo "\nnow run `make pdf` in $targetDir (you need pdflatex to compile pdf file)\n\n"; - } -} diff --git a/extensions/apidoc/templates/pdf/Makefile b/extensions/apidoc/templates/pdf/Makefile deleted file mode 100644 index 03fb9bdc5c..0000000000 --- a/extensions/apidoc/templates/pdf/Makefile +++ /dev/null @@ -1,7 +0,0 @@ - - -pdf: - # run pdflatex twice to generate TOC correctly - pdflatex -halt-on-error main.tex - pdflatex -halt-on-error main.tex - mv main.pdf guide.pdf diff --git a/extensions/apidoc/templates/pdf/main.tex b/extensions/apidoc/templates/pdf/main.tex deleted file mode 100644 index e66a36c196..0000000000 --- a/extensions/apidoc/templates/pdf/main.tex +++ /dev/null @@ -1,159 +0,0 @@ -\documentclass[a4paper,11pt,twoside]{book} - -% english and utf8 -\usepackage[T2A]{fontenc} -\usepackage[utf8]{inputenc} -%\usepackage[british,russian]{babel} -\usepackage[british]{babel} - -% url support -\usepackage{url} - -% make links clickable -\usepackage{hyperref} - -% code listings -\usepackage{listings} -\usepackage{color} - -\definecolor{codebg}{rgb}{0.9,0.9,0.9} -\definecolor{mygreen}{rgb}{0,0.6,0} -\definecolor{mygray}{rgb}{0.5,0.5,0.5} -\definecolor{mymauve}{rgb}{0.58,0,0.82} - -% TODO ensure copyable indentation: -% http://tex.stackexchange.com/questions/142617/copy-pasting-leading-whitespace-and-blank-lines-in-listings-package-pdf - -\lstset{% - backgroundcolor=\color{codebg}, % choose the background color; you must add \usepackage{color} or \usepackage{xcolor} - basicstyle=\ttfamily\footnotesize, % the size of the fonts that are used for the code - columns=fullflexible, - breakatwhitespace=false, % sets if automatic breaks should only happen at whitespace - breaklines=true, % sets automatic line breaking - extendedchars=true, % lets you use non-ASCII characters; for 8-bits encodings only, does not work with UTF-8 - keepspaces=true, % keeps spaces in text, useful for keeping indentation of code (possibly needs columns=flexible) -% - commentstyle=\color{mygreen}, % comment style - keywordstyle=\color{blue}, % keyword style - stringstyle=\color{mymauve}, % string literal style -% -% language=Octave, % the language of the code -% morekeywords={*,...}, % if you want to add more keywords to the set -% deletekeywords={...}, % if you want to delete keywords from the given language -% - numbers=none, % where to put the line-numbers; possible values are (none, left, right), not using line numbers to allow copy&paste - stepnumber=1, % the step between two line-numbers. If it's 1, each line will be numbered - numbersep=5pt, % how far the line-numbers are from the code - numberstyle=\tiny\color{mygray}, % the style that is used for the line-numbers -% - showspaces=false, % show spaces everywhere adding particular underscores; it overrides 'showstringspaces' - showstringspaces=false, % underline spaces within strings only - showtabs=false, % show tabs within strings adding particular underscores -% - tabsize=2, % sets default tabsize to 2 spaces -% title=\lstname % show the filename of files included with \lstinputlisting; also try caption instead of title -% - literate={-}{-}1, - % {\'}{'}1, - % {\"}{\"}1 - extendedchars=false -} - -\lstdefinelanguage{json}{ - morekeywords={}, - sensitive=false, - morestring=[b]", -} - -\lstdefinelanguage{css}{ - morekeywords={}, - sensitive=false, - morestring=[b]", -} - -\lstdefinelanguage{javascript}{ - morekeywords={}, - sensitive=false, - morestring=[b]", -} - -% include images -\usepackage{graphicx} - -% support github markdown strikethrough -% http://tex.stackexchange.com/questions/23711/strikethrough-text -\usepackage{ulem} - -\newcommand{\centeronpage}{% Horizontal adjustment of image - \ifodd\value{page}\hspace*{\dimexpr\evensidemargin-\oddsidemargin}\else\hspace*{-\dimexpr\evensidemargin-\oddsidemargin}\fi% -} - -\title{The definitive Guide to Yii 2.0} -\author{ - Qiang Xue, - Alexander Makarov, - Carsten Brandt, - Klimov Paul, - and - many contributors from the Yii community -} - -% uncomment for debugging layout issues -%\usepackage{showframe} - -\begin{document} - - \frontmatter - - \begin{titlepage} - % ensure centering the title page by ignoring odd and even margin - \setlength{\oddsidemargin}{-1in} - \setlength{\evensidemargin}{-1in} - \setlength{\textwidth}{\paperwidth} - - \vspace*{\fill} - - % typesetting the title line - \noindent - \parbox{\textwidth}{\centering \bfseries \Huge - The Definitive Guide - }\vspace{.5cm} - \parbox{\textwidth}{\centering \bfseries \Huge - to - }\vspace{.5cm} - \parbox{\textwidth}{\centering \bfseries \Huge - Yii 2.0 - } - - \vfill - - % typesetting authors - \noindent - \parbox{\textwidth}{\centering \Large - Qiang Xue,\\ - Alexander Makarov,\\ - Carsten Brandt,\\ - Klimov Paul,\\ - and\\ - the Yii community - } - - \vspace*{\fill} - - \noindent - \parbox{\textwidth}{\centering - Copyright 2014 Yii Software LLC. - } - \end{titlepage} - - %Take a blank Page - \pagebreak \thispagestyle{empty} \cleardoublepage - - \setcounter{tocdepth}{1} - \tableofcontents - - \mainmatter - - \include{guide} - -\end{document} diff --git a/extensions/authclient/BaseClient.php b/extensions/authclient/BaseClient.php deleted file mode 100644 index b22d9bbbc6..0000000000 --- a/extensions/authclient/BaseClient.php +++ /dev/null @@ -1,285 +0,0 @@ - optionValue. - * - * @author Paul Klimov - * @since 2.0 - */ -abstract class BaseClient extends Component implements ClientInterface -{ - /** - * @var string auth service id. - * This value mainly used as HTTP request parameter. - */ - private $_id; - /** - * @var string auth service name. - * This value may be used in database records, CSS files and so on. - */ - private $_name; - /** - * @var string auth service title to display in views. - */ - private $_title; - /** - * @var array authenticated user attributes. - */ - private $_userAttributes; - /** - * @var array map used to normalize user attributes fetched from external auth service - * in format: normalizedAttributeName => sourceSpecification - * 'sourceSpecification' can be: - * - string, raw attribute name - * - array, pass to raw attribute value - * - callable, PHP callback, which should accept array of raw attributes and return normalized value. - * - * For example: - * - * ```php - * 'normalizeUserAttributeMap' => [ - * 'about' => 'bio', - * 'language' => ['languages', 0, 'name'], - * 'fullName' => function ($attributes) { - * return $attributes['firstName'] . ' ' . $attributes['lastName']; - * }, - * ], - * ``` - */ - private $_normalizeUserAttributeMap; - /** - * @var array view options in format: optionName => optionValue - */ - private $_viewOptions; - - - /** - * @param string $id service id. - */ - public function setId($id) - { - $this->_id = $id; - } - - /** - * @return string service id - */ - public function getId() - { - if (empty($this->_id)) { - $this->_id = $this->getName(); - } - - return $this->_id; - } - - /** - * @param string $name service name. - */ - public function setName($name) - { - $this->_name = $name; - } - - /** - * @return string service name. - */ - public function getName() - { - if ($this->_name === null) { - $this->_name = $this->defaultName(); - } - - return $this->_name; - } - - /** - * @param string $title service title. - */ - public function setTitle($title) - { - $this->_title = $title; - } - - /** - * @return string service title. - */ - public function getTitle() - { - if ($this->_title === null) { - $this->_title = $this->defaultTitle(); - } - - return $this->_title; - } - - /** - * @param array $userAttributes list of user attributes - */ - public function setUserAttributes($userAttributes) - { - $this->_userAttributes = $this->normalizeUserAttributes($userAttributes); - } - - /** - * @return array list of user attributes - */ - public function getUserAttributes() - { - if ($this->_userAttributes === null) { - $this->_userAttributes = $this->normalizeUserAttributes($this->initUserAttributes()); - } - - return $this->_userAttributes; - } - - /** - * @param array $normalizeUserAttributeMap normalize user attribute map. - */ - public function setNormalizeUserAttributeMap($normalizeUserAttributeMap) - { - $this->_normalizeUserAttributeMap = $normalizeUserAttributeMap; - } - - /** - * @return array normalize user attribute map. - */ - public function getNormalizeUserAttributeMap() - { - if ($this->_normalizeUserAttributeMap === null) { - $this->_normalizeUserAttributeMap = $this->defaultNormalizeUserAttributeMap(); - } - - return $this->_normalizeUserAttributeMap; - } - - /** - * @param array $viewOptions view options in format: optionName => optionValue - */ - public function setViewOptions($viewOptions) - { - $this->_viewOptions = $viewOptions; - } - - /** - * @return array view options in format: optionName => optionValue - */ - public function getViewOptions() - { - if ($this->_viewOptions === null) { - $this->_viewOptions = $this->defaultViewOptions(); - } - - return $this->_viewOptions; - } - - /** - * Generates service name. - * @return string service name. - */ - protected function defaultName() - { - return Inflector::camel2id(StringHelper::basename(get_class($this))); - } - - /** - * Generates service title. - * @return string service title. - */ - protected function defaultTitle() - { - return StringHelper::basename(get_class($this)); - } - - /** - * Initializes authenticated user attributes. - * @return array auth user attributes. - */ - protected function initUserAttributes() - { - throw new NotSupportedException('Method "' . get_class($this) . '::' . __FUNCTION__ . '" not implemented.'); - } - - /** - * Returns the default [[normalizeUserAttributeMap]] value. - * Particular client may override this method in order to provide specific default map. - * @return array normalize attribute map. - */ - protected function defaultNormalizeUserAttributeMap() - { - return []; - } - - /** - * Returns the default [[viewOptions]] value. - * Particular client may override this method in order to provide specific default view options. - * @return array list of default [[viewOptions]] - */ - protected function defaultViewOptions() - { - return []; - } - - /** - * Normalize given user attributes according to [[normalizeUserAttributeMap]]. - * @param array $attributes raw attributes. - * @throws InvalidConfigException on incorrect normalize attribute map. - * @return array normalized attributes. - */ - protected function normalizeUserAttributes($attributes) - { - foreach ($this->getNormalizeUserAttributeMap() as $normalizedName => $actualName) { - if (is_scalar($actualName)) { - if (array_key_exists($actualName, $attributes)) { - $attributes[$normalizedName] = $attributes[$actualName]; - } - } else { - if (is_callable($actualName)) { - $attributes[$normalizedName] = call_user_func($actualName, $attributes); - } elseif (is_array($actualName)) { - $haystack = $attributes; - $searchKeys = $actualName; - $isFound = true; - while (($key = array_shift($searchKeys)) !== null) { - if (is_array($haystack) && array_key_exists($key, $haystack)) { - $haystack = $haystack[$key]; - } else { - $isFound = false; - break; - } - } - if ($isFound) { - $attributes[$normalizedName] = $haystack; - } - } else { - throw new InvalidConfigException('Invalid actual name "' . gettype($actualName) . '" specified at "' . get_class($this) . '::normalizeUserAttributeMap"'); - } - } - } - - return $attributes; - } -} diff --git a/extensions/authclient/BaseOAuth.php b/extensions/authclient/BaseOAuth.php deleted file mode 100644 index b4e1cc4f06..0000000000 --- a/extensions/authclient/BaseOAuth.php +++ /dev/null @@ -1,523 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseOAuth extends BaseClient implements ClientInterface -{ - const CONTENT_TYPE_JSON = 'json'; // JSON format - const CONTENT_TYPE_URLENCODED = 'urlencoded'; // urlencoded query string, like name1=value1&name2=value2 - const CONTENT_TYPE_XML = 'xml'; // XML format - const CONTENT_TYPE_AUTO = 'auto'; // attempts to determine format automatically - - /** - * @var string protocol version. - */ - public $version = '1.0'; - /** - * @var string API base URL. - */ - public $apiBaseUrl; - /** - * @var string authorize URL. - */ - public $authUrl; - /** - * @var string auth request scope. - */ - public $scope; - - /** - * @var string URL, which user will be redirected after authentication at the OAuth provider web site. - * Note: this should be absolute URL (with http:// or https:// leading). - * By default current URL will be used. - */ - private $_returnUrl; - /** - * @var array cURL request options. Option values from this field will overwrite corresponding - * values from [[defaultCurlOptions()]]. - */ - private $_curlOptions = []; - /** - * @var OAuthToken|array access token instance or its array configuration. - */ - private $_accessToken; - /** - * @var signature\BaseMethod|array signature method instance or its array configuration. - */ - private $_signatureMethod = []; - - - /** - * @param string $returnUrl return URL - */ - public function setReturnUrl($returnUrl) - { - $this->_returnUrl = $returnUrl; - } - - /** - * @return string return URL. - */ - public function getReturnUrl() - { - if ($this->_returnUrl === null) { - $this->_returnUrl = $this->defaultReturnUrl(); - } - return $this->_returnUrl; - } - - /** - * @param array $curlOptions cURL options. - */ - public function setCurlOptions(array $curlOptions) - { - $this->_curlOptions = $curlOptions; - } - - /** - * @return array cURL options. - */ - public function getCurlOptions() - { - return $this->_curlOptions; - } - - /** - * @param array|OAuthToken $token - */ - public function setAccessToken($token) - { - if (!is_object($token)) { - $token = $this->createToken($token); - } - $this->_accessToken = $token; - $this->saveAccessToken($token); - } - - /** - * @return OAuthToken auth token instance. - */ - public function getAccessToken() - { - if (!is_object($this->_accessToken)) { - $this->_accessToken = $this->restoreAccessToken(); - } - - return $this->_accessToken; - } - - /** - * @param array|signature\BaseMethod $signatureMethod signature method instance or its array configuration. - * @throws InvalidParamException on wrong argument. - */ - public function setSignatureMethod($signatureMethod) - { - if (!is_object($signatureMethod) && !is_array($signatureMethod)) { - throw new InvalidParamException('"' . get_class($this) . '::signatureMethod" should be instance of "\yii\autclient\signature\BaseMethod" or its array configuration. "' . gettype($signatureMethod) . '" has been given.'); - } - $this->_signatureMethod = $signatureMethod; - } - - /** - * @return signature\BaseMethod signature method instance. - */ - public function getSignatureMethod() - { - if (!is_object($this->_signatureMethod)) { - $this->_signatureMethod = $this->createSignatureMethod($this->_signatureMethod); - } - - return $this->_signatureMethod; - } - - /** - * Composes default [[returnUrl]] value. - * @return string return URL. - */ - protected function defaultReturnUrl() - { - return Yii::$app->getRequest()->getAbsoluteUrl(); - } - - /** - * Sends HTTP request. - * @param string $method request type. - * @param string $url request URL. - * @param array $params request params. - * @param array $headers additional request headers. - * @return array response. - * @throws Exception on failure. - */ - protected function sendRequest($method, $url, array $params = [], array $headers = []) - { - $curlOptions = $this->mergeCurlOptions( - $this->defaultCurlOptions(), - $this->getCurlOptions(), - [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_URL => $url, - ], - $this->composeRequestCurlOptions(strtoupper($method), $url, $params) - ); - $curlResource = curl_init(); - foreach ($curlOptions as $option => $value) { - curl_setopt($curlResource, $option, $value); - } - $response = curl_exec($curlResource); - $responseHeaders = curl_getinfo($curlResource); - - // check cURL error - $errorNumber = curl_errno($curlResource); - $errorMessage = curl_error($curlResource); - - curl_close($curlResource); - - if ($errorNumber > 0) { - throw new Exception('Curl error requesting "' . $url . '": #' . $errorNumber . ' - ' . $errorMessage); - } - if (strncmp($responseHeaders['http_code'], '20', 2) !== 0) { - throw new InvalidResponseException($responseHeaders, $response, 'Request failed with code: ' . $responseHeaders['http_code'] . ', message: ' . $response); - } - - return $this->processResponse($response, $this->determineContentTypeByHeaders($responseHeaders)); - } - - /** - * Merge CUrl options. - * If each options array has an element with the same key value, the latter - * will overwrite the former. - * @param array $options1 options to be merged to. - * @param array $options2 options to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array merged options (the original options are not changed.) - */ - protected function mergeCurlOptions($options1, $options2) - { - $args = func_get_args(); - $res = array_shift($args); - while (!empty($args)) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_array($v) && !empty($res[$k]) && is_array($res[$k])) { - $res[$k] = array_merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Returns default cURL options. - * @return array cURL options. - */ - protected function defaultCurlOptions() - { - return [ - CURLOPT_USERAGENT => Yii::$app->name . ' OAuth ' . $this->version . ' Client', - CURLOPT_CONNECTTIMEOUT => 30, - CURLOPT_TIMEOUT => 30, - CURLOPT_SSL_VERIFYPEER => false, - ]; - } - - /** - * Processes raw response converting it to actual data. - * @param string $rawResponse raw response. - * @param string $contentType response content type. - * @throws Exception on failure. - * @return array actual response. - */ - protected function processResponse($rawResponse, $contentType = self::CONTENT_TYPE_AUTO) - { - if (empty($rawResponse)) { - return []; - } - switch ($contentType) { - case self::CONTENT_TYPE_AUTO: { - $contentType = $this->determineContentTypeByRaw($rawResponse); - if ($contentType == self::CONTENT_TYPE_AUTO) { - throw new Exception('Unable to determine response content type automatically.'); - } - $response = $this->processResponse($rawResponse, $contentType); - break; - } - case self::CONTENT_TYPE_JSON: { - $response = Json::decode($rawResponse, true); - if (isset($response['error'])) { - throw new Exception('Response error: ' . $response['error']); - } - break; - } - case self::CONTENT_TYPE_URLENCODED: { - $response = []; - parse_str($rawResponse, $response); - break; - } - case self::CONTENT_TYPE_XML: { - $response = $this->convertXmlToArray($rawResponse); - break; - } - default: { - throw new Exception('Unknown response type "' . $contentType . '".'); - } - } - return $response; - } - - /** - * Converts XML document to array. - * @param string|\SimpleXMLElement $xml xml to process. - * @return array XML array representation. - */ - protected function convertXmlToArray($xml) - { - if (!is_object($xml)) { - $xml = simplexml_load_string($xml); - } - $result = (array) $xml; - foreach ($result as $key => $value) { - if (is_object($value)) { - $result[$key] = $this->convertXmlToArray($value); - } - } - return $result; - } - - /** - * Attempts to determine HTTP request content type by headers. - * @param array $headers request headers. - * @return string content type. - */ - protected function determineContentTypeByHeaders(array $headers) - { - if (isset($headers['content_type'])) { - if (stripos($headers['content_type'], 'json') !== false) { - return self::CONTENT_TYPE_JSON; - } - if (stripos($headers['content_type'], 'urlencoded') !== false) { - return self::CONTENT_TYPE_URLENCODED; - } - if (stripos($headers['content_type'], 'xml') !== false) { - return self::CONTENT_TYPE_XML; - } - } - return self::CONTENT_TYPE_AUTO; - } - - /** - * Attempts to determine the content type from raw content. - * @param string $rawContent raw response content. - * @return string response type. - */ - protected function determineContentTypeByRaw($rawContent) - { - if (preg_match('/^\\{.*\\}$/is', $rawContent)) { - return self::CONTENT_TYPE_JSON; - } - if (preg_match('/^[^=|^&]+=[^=|^&]+(&[^=|^&]+=[^=|^&]+)*$/is', $rawContent)) { - return self::CONTENT_TYPE_URLENCODED; - } - if (preg_match('/^<.*>$/is', $rawContent)) { - return self::CONTENT_TYPE_XML; - } - return self::CONTENT_TYPE_AUTO; - } - - /** - * Creates signature method instance from its configuration. - * @param array $signatureMethodConfig signature method configuration. - * @return signature\BaseMethod signature method instance. - */ - protected function createSignatureMethod(array $signatureMethodConfig) - { - if (!array_key_exists('class', $signatureMethodConfig)) { - $signatureMethodConfig['class'] = signature\HmacSha1::className(); - } - return Yii::createObject($signatureMethodConfig); - } - - /** - * Creates token from its configuration. - * @param array $tokenConfig token configuration. - * @return OAuthToken token instance. - */ - protected function createToken(array $tokenConfig = []) - { - if (!array_key_exists('class', $tokenConfig)) { - $tokenConfig['class'] = OAuthToken::className(); - } - return Yii::createObject($tokenConfig); - } - - /** - * Composes URL from base URL and GET params. - * @param string $url base URL. - * @param array $params GET params. - * @return string composed URL. - */ - protected function composeUrl($url, array $params = []) - { - if (strpos($url, '?') === false) { - $url .= '?'; - } else { - $url .= '&'; - } - $url .= http_build_query($params, '', '&', PHP_QUERY_RFC3986); - return $url; - } - - /** - * Saves token as persistent state. - * @param OAuthToken $token auth token - * @return static self reference. - */ - protected function saveAccessToken(OAuthToken $token) - { - return $this->setState('token', $token); - } - - /** - * Restores access token. - * @return OAuthToken auth token. - */ - protected function restoreAccessToken() - { - $token = $this->getState('token'); - if (is_object($token)) { - /* @var $token OAuthToken */ - if ($token->getIsExpired()) { - $token = $this->refreshAccessToken($token); - } - } - return $token; - } - - /** - * Sets persistent state. - * @param string $key state key. - * @param mixed $value state value - * @return static self reference. - */ - protected function setState($key, $value) - { - $session = Yii::$app->getSession(); - $key = $this->getStateKeyPrefix() . $key; - $session->set($key, $value); - return $this; - } - - /** - * Returns persistent state value. - * @param string $key state key. - * @return mixed state value. - */ - protected function getState($key) - { - $session = Yii::$app->getSession(); - $key = $this->getStateKeyPrefix() . $key; - $value = $session->get($key); - return $value; - } - - /** - * Removes persistent state value. - * @param string $key state key. - * @return boolean success. - */ - protected function removeState($key) - { - $session = Yii::$app->getSession(); - $key = $this->getStateKeyPrefix() . $key; - $session->remove($key); - return true; - } - - /** - * Returns session key prefix, which is used to store internal states. - * @return string session key prefix. - */ - protected function getStateKeyPrefix() - { - return get_class($this) . '_' . sha1($this->authUrl) . '_'; - } - - /** - * Performs request to the OAuth API. - * @param string $apiSubUrl API sub URL, which will be append to [[apiBaseUrl]], or absolute API URL. - * @param string $method request method. - * @param array $params request parameters. - * @param array $headers additional request headers. - * @return array API response - * @throws Exception on failure. - */ - public function api($apiSubUrl, $method = 'GET', array $params = [], array $headers = []) - { - if (preg_match('/^https?:\\/\\//is', $apiSubUrl)) { - $url = $apiSubUrl; - } else { - $url = $this->apiBaseUrl . '/' . $apiSubUrl; - } - $accessToken = $this->getAccessToken(); - if (!is_object($accessToken) || !$accessToken->getIsValid()) { - throw new Exception('Invalid access token.'); - } - return $this->apiInternal($accessToken, $url, $method, $params, $headers); - } - - /** - * Composes HTTP request CUrl options, which will be merged with the default ones. - * @param string $method request type. - * @param string $url request URL. - * @param array $params request params. - * @return array CUrl options. - * @throws Exception on failure. - */ - abstract protected function composeRequestCurlOptions($method, $url, array $params); - - /** - * Gets new auth token to replace expired one. - * @param OAuthToken $token expired auth token. - * @return OAuthToken new auth token. - */ - abstract public function refreshAccessToken(OAuthToken $token); - - /** - * Performs request to the OAuth API. - * @param OAuthToken $accessToken actual access token. - * @param string $url absolute API URL. - * @param string $method request method. - * @param array $params request parameters. - * @param array $headers additional request headers. - * @return array API response. - * @throws Exception on failure. - */ - abstract protected function apiInternal($accessToken, $url, $method, array $params, array $headers); -} diff --git a/extensions/authclient/ClientInterface.php b/extensions/authclient/ClientInterface.php deleted file mode 100644 index 54c70bd10e..0000000000 --- a/extensions/authclient/ClientInterface.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @since 2.0 - */ -interface ClientInterface -{ - /** - * @param string $id service id. - */ - public function setId($id); - - /** - * @return string service id - */ - public function getId(); - - /** - * @return string service name. - */ - public function getName(); - - /** - * @param string $name service name. - */ - public function setName($name); - - /** - * @return string service title. - */ - public function getTitle(); - - /** - * @param string $title service title. - */ - public function setTitle($title); - - /** - * @return array list of user attributes - */ - public function getUserAttributes(); - - /** - * @return array view options in format: optionName => optionValue - */ - public function getViewOptions(); -} diff --git a/extensions/authclient/Collection.php b/extensions/authclient/Collection.php deleted file mode 100644 index 532264f68b..0000000000 --- a/extensions/authclient/Collection.php +++ /dev/null @@ -1,111 +0,0 @@ - [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'google' => [ - * 'class' => 'yii\authclient\clients\GoogleOpenId' - * ], - * 'facebook' => [ - * 'class' => 'yii\authclient\clients\Facebook', - * 'clientId' => 'facebook_client_id', - * 'clientSecret' => 'facebook_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @property ClientInterface[] $clients List of auth clients. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class Collection extends Component -{ - /** - * @var array list of Auth clients with their configuration in format: 'clientId' => [...] - */ - private $_clients = []; - - - /** - * @param array $clients list of auth clients - */ - public function setClients(array $clients) - { - $this->_clients = $clients; - } - - /** - * @return ClientInterface[] list of auth clients. - */ - public function getClients() - { - $clients = []; - foreach ($this->_clients as $id => $client) { - $clients[$id] = $this->getClient($id); - } - - return $clients; - } - - /** - * @param string $id service id. - * @return ClientInterface auth client instance. - * @throws InvalidParamException on non existing client request. - */ - public function getClient($id) - { - if (!array_key_exists($id, $this->_clients)) { - throw new InvalidParamException("Unknown auth client '{$id}'."); - } - if (!is_object($this->_clients[$id])) { - $this->_clients[$id] = $this->createClient($id, $this->_clients[$id]); - } - - return $this->_clients[$id]; - } - - /** - * Checks if client exists in the hub. - * @param string $id client id. - * @return boolean whether client exist. - */ - public function hasClient($id) - { - return array_key_exists($id, $this->_clients); - } - - /** - * Creates auth client instance from its array configuration. - * @param string $id auth client id. - * @param array $config auth client instance configuration. - * @return ClientInterface auth client instance. - */ - protected function createClient($id, $config) - { - $config['id'] = $id; - - return Yii::createObject($config); - } -} diff --git a/extensions/authclient/InvalidResponseException.php b/extensions/authclient/InvalidResponseException.php deleted file mode 100644 index ef5871a1a6..0000000000 --- a/extensions/authclient/InvalidResponseException.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @since 2.0 - */ -class InvalidResponseException extends Exception -{ - /** - * @var array response headers. - */ - public $responseHeaders = []; - /** - * @var string response body. - */ - public $responseBody = ''; - - - /** - * Constructor. - * @param array $responseHeaders response headers - * @param string $responseBody response body - * @param string $message error message - * @param integer $code error code - * @param \Exception $previous The previous exception used for the exception chaining. - * @internal param int $status HTTP status code, such as 404, 500, etc. - */ - public function __construct($responseHeaders, $responseBody, $message = null, $code = 0, \Exception $previous = null) - { - $this->responseBody = $responseBody; - $this->responseHeaders = $responseHeaders; - parent::__construct($message, $code, $previous); - } -} \ No newline at end of file diff --git a/extensions/authclient/LICENSE.md b/extensions/authclient/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/authclient/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/authclient/OAuth1.php b/extensions/authclient/OAuth1.php deleted file mode 100644 index 9330a9ae05..0000000000 --- a/extensions/authclient/OAuth1.php +++ /dev/null @@ -1,364 +0,0 @@ -fetchRequestToken(); // Get request token - * $url = $oauthClient->buildAuthUrl($requestToken); // Get authorization URL - * return Yii::$app->getResponse()->redirect($url); // Redirect to authorization URL - * // After user returns at our site: - * $accessToken = $oauthClient->fetchAccessToken($requestToken); // Upgrade to access token - * ~~~ - * - * @see http://oauth.net/ - * - * @author Paul Klimov - * @since 2.0 - */ -class OAuth1 extends BaseOAuth -{ - /** - * @var string protocol version. - */ - public $version = '1.0'; - /** - * @var string OAuth consumer key. - */ - public $consumerKey; - /** - * @var string OAuth consumer secret. - */ - public $consumerSecret; - /** - * @var string OAuth request token URL. - */ - public $requestTokenUrl; - /** - * @var string request token HTTP method. - */ - public $requestTokenMethod = 'GET'; - /** - * @var string OAuth access token URL. - */ - public $accessTokenUrl; - /** - * @var string access token HTTP method. - */ - public $accessTokenMethod = 'GET'; - - - /** - * Fetches the OAuth request token. - * @param array $params additional request params. - * @return OAuthToken request token. - */ - public function fetchRequestToken(array $params = []) - { - $this->removeState('token'); - $defaultParams = [ - 'oauth_consumer_key' => $this->consumerKey, - 'oauth_callback' => $this->getReturnUrl(), - //'xoauth_displayname' => Yii::$app->name, - ]; - if (!empty($this->scope)) { - $defaultParams['scope'] = $this->scope; - } - $response = $this->sendSignedRequest($this->requestTokenMethod, $this->requestTokenUrl, array_merge($defaultParams, $params)); - $token = $this->createToken([ - 'params' => $response - ]); - $this->setState('requestToken', $token); - - return $token; - } - - /** - * Composes user authorization URL. - * @param OAuthToken $requestToken OAuth request token. - * @param array $params additional request params. - * @return string authorize URL - * @throws Exception on failure. - */ - public function buildAuthUrl(OAuthToken $requestToken = null, array $params = []) - { - if (!is_object($requestToken)) { - $requestToken = $this->getState('requestToken'); - if (!is_object($requestToken)) { - throw new Exception('Request token is required to build authorize URL!'); - } - } - $params['oauth_token'] = $requestToken->getToken(); - - return $this->composeUrl($this->authUrl, $params); - } - - /** - * Fetches OAuth access token. - * @param OAuthToken $requestToken OAuth request token. - * @param string $oauthVerifier OAuth verifier. - * @param array $params additional request params. - * @return OAuthToken OAuth access token. - * @throws Exception on failure. - */ - public function fetchAccessToken(OAuthToken $requestToken = null, $oauthVerifier = null, array $params = []) - { - if (!is_object($requestToken)) { - $requestToken = $this->getState('requestToken'); - if (!is_object($requestToken)) { - throw new Exception('Request token is required to fetch access token!'); - } - } - $this->removeState('requestToken'); - $defaultParams = [ - 'oauth_consumer_key' => $this->consumerKey, - 'oauth_token' => $requestToken->getToken() - ]; - if ($oauthVerifier === null) { - if (isset($_REQUEST['oauth_verifier'])) { - $oauthVerifier = $_REQUEST['oauth_verifier']; - } - } - if (!empty($oauthVerifier)) { - $defaultParams['oauth_verifier'] = $oauthVerifier; - } - $response = $this->sendSignedRequest($this->accessTokenMethod, $this->accessTokenUrl, array_merge($defaultParams, $params)); - - $token = $this->createToken([ - 'params' => $response - ]); - $this->setAccessToken($token); - - return $token; - } - - /** - * Sends HTTP request, signed by [[signatureMethod]]. - * @param string $method request type. - * @param string $url request URL. - * @param array $params request params. - * @param array $headers additional request headers. - * @return array response. - */ - protected function sendSignedRequest($method, $url, array $params = [], array $headers = []) - { - $params = array_merge($params, $this->generateCommonRequestParams()); - $params = $this->signRequest($method, $url, $params); - - return $this->sendRequest($method, $url, $params, $headers); - } - - /** - * Composes HTTP request CUrl options, which will be merged with the default ones. - * @param string $method request type. - * @param string $url request URL. - * @param array $params request params. - * @return array CUrl options. - * @throws Exception on failure. - */ - protected function composeRequestCurlOptions($method, $url, array $params) - { - $curlOptions = []; - switch ($method) { - case 'GET': { - $curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params); - break; - } - case 'POST': { - $curlOptions[CURLOPT_POST] = true; - if (!empty($params)) { - $curlOptions[CURLOPT_POSTFIELDS] = $params; - } - $authorizationHeader = $this->composeAuthorizationHeader($params); - if (!empty($authorizationHeader)) { - $curlOptions[CURLOPT_HTTPHEADER] = ['Content-Type: application/atom+xml', $authorizationHeader]; - } - break; - } - case 'HEAD': { - $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; - if (!empty($params)) { - $curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params); - } - break; - } - default: { - $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; - if (!empty($params)) { - $curlOptions[CURLOPT_POSTFIELDS] = $params; - } - } - } - - return $curlOptions; - } - - /** - * @inheritdoc - */ - protected function apiInternal($accessToken, $url, $method, array $params, array $headers) - { - $params['oauth_consumer_key'] = $this->consumerKey; - $params['oauth_token'] = $accessToken->getToken(); - $response = $this->sendSignedRequest($method, $url, $params, $headers); - - return $response; - } - - /** - * Gets new auth token to replace expired one. - * @param OAuthToken $token expired auth token. - * @return OAuthToken new auth token. - */ - public function refreshAccessToken(OAuthToken $token) - { - // @todo - return null; - } - - /** - * Composes default [[returnUrl]] value. - * @return string return URL. - */ - protected function defaultReturnUrl() - { - $params = $_GET; - unset($params['oauth_token']); - $params[0] = Yii::$app->controller->getRoute(); - - return Yii::$app->getUrlManager()->createAbsoluteUrl($params); - } - - /** - * Generates nonce value. - * @return string nonce value. - */ - protected function generateNonce() - { - return md5(microtime() . mt_rand()); - } - - /** - * Generates timestamp. - * @return integer timestamp. - */ - protected function generateTimestamp() - { - return time(); - } - - /** - * Generate common request params like version, timestamp etc. - * @return array common request params. - */ - protected function generateCommonRequestParams() - { - $params = [ - 'oauth_version' => $this->version, - 'oauth_nonce' => $this->generateNonce(), - 'oauth_timestamp' => $this->generateTimestamp(), - ]; - - return $params; - } - - /** - * Sign request with [[signatureMethod]]. - * @param string $method request method. - * @param string $url request URL. - * @param array $params request params. - * @return array signed request params. - */ - protected function signRequest($method, $url, array $params) - { - $signatureMethod = $this->getSignatureMethod(); - $params['oauth_signature_method'] = $signatureMethod->getName(); - $signatureBaseString = $this->composeSignatureBaseString($method, $url, $params); - $signatureKey = $this->composeSignatureKey(); - $params['oauth_signature'] = $signatureMethod->generateSignature($signatureBaseString, $signatureKey); - - return $params; - } - - /** - * Creates signature base string, which will be signed by [[signatureMethod]]. - * @param string $method request method. - * @param string $url request URL. - * @param array $params request params. - * @return string base signature string. - */ - protected function composeSignatureBaseString($method, $url, array $params) - { - unset($params['oauth_signature']); - uksort($params, 'strcmp'); // Parameters are sorted by name, using lexicographical byte value ordering. Ref: Spec: 9.1.1 - $parts = [ - strtoupper($method), - $url, - http_build_query($params, '', '&', PHP_QUERY_RFC3986) - ]; - $parts = array_map('rawurlencode', $parts); - - return implode('&', $parts); - } - - /** - * Composes request signature key. - * @return string signature key. - */ - protected function composeSignatureKey() - { - $signatureKeyParts = [ - $this->consumerSecret - ]; - $accessToken = $this->getAccessToken(); - if (is_object($accessToken)) { - $signatureKeyParts[] = $accessToken->getTokenSecret(); - } else { - $signatureKeyParts[] = ''; - } - $signatureKeyParts = array_map('rawurlencode', $signatureKeyParts); - - return implode('&', $signatureKeyParts); - } - - /** - * Composes authorization header content. - * @param array $params request params. - * @param string $realm authorization realm. - * @return string authorization header content. - */ - protected function composeAuthorizationHeader(array $params, $realm = '') - { - $header = 'Authorization: OAuth'; - $headerParams = []; - if (!empty($realm)) { - $headerParams[] = 'realm="' . rawurlencode($realm) . '"'; - } - foreach ($params as $key => $value) { - if (substr_compare($key, 'oauth', 0, 5)) { - continue; - } - $headerParams[] = rawurlencode($key) . '="' . rawurlencode($value) . '"'; - } - if (!empty($headerParams)) { - $header .= ' ' . implode(', ', $headerParams); - } - - return $header; - } -} diff --git a/extensions/authclient/OpenId.php b/extensions/authclient/OpenId.php deleted file mode 100644 index b0471bc7e3..0000000000 --- a/extensions/authclient/OpenId.php +++ /dev/null @@ -1,870 +0,0 @@ -authUrl = 'https://open.id.provider.url'; // Setup provider endpoint - * $url = $client->buildAuthUrl(); // Get authentication URL - * return Yii::$app->getResponse()->redirect($url); // Redirect to authentication URL - * // After user returns at our site: - * if ($client->validate()) { // validate response - * $userAttributes = $client->getUserAttributes(); // get account info - * ... - * } - * ~~~ - * - * AX and SREG extensions are supported. - * To use them, specify [[requiredAttributes]] and/or [[optionalAttributes]]. - * - * @see http://openid.net/ - * - * @property string $claimedId Claimed identifier (identity). - * @property string $returnUrl Authentication return URL. - * @property string $trustRoot Client trust root (realm). - * - * @author Paul Klimov - * @since 2.0 - */ -class OpenId extends BaseClient implements ClientInterface -{ - /** - * @var string authentication base URL, which should be used to compose actual authentication URL - * by [[buildAuthUrl()]] method. - */ - public $authUrl; - /** - * @var array list of attributes, which always should be returned from server. - * Attribute names should be always specified in AX format. - * For example: - * ~~~ - * ['namePerson/friendly', 'contact/email'] - * ~~~ - */ - public $requiredAttributes = []; - /** - * @var array list of attributes, which could be returned from server. - * Attribute names should be always specified in AX format. - * For example: - * ~~~ - * ['namePerson/first', 'namePerson/last'] - * ~~~ - */ - public $optionalAttributes = []; - /** - * @var boolean whether to verify the peer's certificate. - */ - public $verifyPeer; - /** - * @var string directory that holds multiple CA certificates. - * This value will take effect only if [[verifyPeer]] is set. - */ - public $capath; - /** - * @var string the name of a file holding one or more certificates to verify the peer with. - * This value will take effect only if [[verifyPeer]] is set. - */ - public $cainfo; - /** - * @var array data, which should be used to retrieve the OpenID response. - * If not set combination of GET and POST will be used. - */ - public $data; - /** - * @var array map of matches between AX and SREG attribute names in format: axAttributeName => sregAttributeName - */ - public $axToSregMap = [ - 'namePerson/friendly' => 'nickname', - 'contact/email' => 'email', - 'namePerson' => 'fullname', - 'birthDate' => 'dob', - 'person/gender' => 'gender', - 'contact/postalCode/home' => 'postcode', - 'contact/country/home' => 'country', - 'pref/language' => 'language', - 'pref/timezone' => 'timezone', - ]; - - /** - * @var string authentication return URL. - */ - private $_returnUrl; - /** - * @var string claimed identifier (identity) - */ - private $_claimedId; - /** - * @var string client trust root (realm), by default [[\yii\web\Request::hostInfo]] value will be used. - */ - private $_trustRoot; - - - /** - * @inheritdoc - */ - public function init() - { - if ($this->data === null) { - $this->data = array_merge($_GET, $_POST); // OPs may send data as POST or GET. - } - } - - /** - * @param string $claimedId claimed identifier (identity). - */ - public function setClaimedId($claimedId) - { - $this->_claimedId = $claimedId; - } - - /** - * @return string claimed identifier (identity). - */ - public function getClaimedId() - { - if ($this->_claimedId === null) { - if (isset($this->data['openid_claimed_id'])) { - $this->_claimedId = $this->data['openid_claimed_id']; - } elseif (isset($this->data['openid_identity'])) { - $this->_claimedId = $this->data['openid_identity']; - } - } - - return $this->_claimedId; - } - - /** - * @param string $returnUrl authentication return URL. - */ - public function setReturnUrl($returnUrl) - { - $this->_returnUrl = $returnUrl; - } - - /** - * @return string authentication return URL. - */ - public function getReturnUrl() - { - if ($this->_returnUrl === null) { - $this->_returnUrl = $this->defaultReturnUrl(); - } - - return $this->_returnUrl; - } - - /** - * @param string $value client trust root (realm). - */ - public function setTrustRoot($value) - { - $this->_trustRoot = $value; - } - - /** - * @return string client trust root (realm). - */ - public function getTrustRoot() - { - if ($this->_trustRoot === null) { - $this->_trustRoot = Yii::$app->getRequest()->getHostInfo(); - } - - return $this->_trustRoot; - } - - /** - * Generates default [[returnUrl]] value. - * @return string default authentication return URL. - */ - protected function defaultReturnUrl() - { - $params = $_GET; - foreach ($params as $name => $value) { - if (strncmp('openid', $name, 6) === 0) { - unset($params[$name]); - } - } - $params[0] = Yii::$app->requestedRoute; - $url = Yii::$app->getUrlManager()->createUrl($params); - - return $this->getTrustRoot() . $url; - } - - /** - * Checks if the server specified in the url exists. - * @param string $url URL to check - * @return boolean true, if the server exists; false otherwise - */ - public function hostExists($url) - { - if (strpos($url, '/') === false) { - $server = $url; - } else { - $server = @parse_url($url, PHP_URL_HOST); - } - if (!$server) { - return false; - } - $ips = gethostbynamel($server); - - return !empty($ips); - } - - /** - * Sends request to the server - * @param string $url request URL. - * @param string $method request method. - * @param array $params request parameters. - * @return array|string response. - * @throws \yii\base\Exception on failure. - */ - protected function sendRequest($url, $method = 'GET', $params = []) - { - $params = http_build_query($params, '', '&'); - $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($curl, CURLOPT_HEADER, false); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_HTTPHEADER, ['Accept: application/xrds+xml, */*']); - - if ($this->verifyPeer !== null) { - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifyPeer); - if ($this->capath) { - curl_setopt($curl, CURLOPT_CAPATH, $this->capath); - } - if ($this->cainfo) { - curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); - } - } - - if ($method == 'POST') { - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, $params); - } elseif ($method == 'HEAD') { - curl_setopt($curl, CURLOPT_HEADER, true); - curl_setopt($curl, CURLOPT_NOBODY, true); - } else { - curl_setopt($curl, CURLOPT_HTTPGET, true); - } - $response = curl_exec($curl); - - if ($method == 'HEAD') { - $headers = []; - foreach (explode("\n", $response) as $header) { - $pos = strpos($header, ':'); - $name = strtolower(trim(substr($header, 0, $pos))); - $headers[$name] = trim(substr($header, $pos+1)); - } - - return $headers; - } - - if (curl_errno($curl)) { - throw new Exception(curl_error($curl), curl_errno($curl)); - } - - return $response; - } - - /** - * Combines given URLs into single one. - * @param string $baseUrl base URL. - * @param string|array $additionalUrl additional URL string or information array. - * @return string composed URL. - */ - protected function buildUrl($baseUrl, $additionalUrl) - { - $baseUrl = parse_url($baseUrl); - if (!is_array($additionalUrl)) { - $additionalUrl = parse_url($additionalUrl); - } - - if (isset($baseUrl['query'], $additionalUrl['query'])) { - $additionalUrl['query'] = $baseUrl['query'] . '&' . $additionalUrl['query']; - } - - $urlInfo = array_merge($baseUrl, $additionalUrl); - $url = $urlInfo['scheme'] . '://' - . (empty($urlInfo['username']) ? '' - :(empty($urlInfo['password']) ? "{$urlInfo['username']}@" - :"{$urlInfo['username']}:{$urlInfo['password']}@")) - . $urlInfo['host'] - . (empty($urlInfo['port']) ? '' : ":{$urlInfo['port']}") - . (empty($urlInfo['path']) ? '' : $urlInfo['path']) - . (empty($urlInfo['query']) ? '' : "?{$urlInfo['query']}") - . (empty($urlInfo['fragment']) ? '' : "#{$urlInfo['fragment']}"); - - return $url; - } - - /** - * Scans content for / tags and extract information from them. - * @param string $content HTML content to be be parsed. - * @param string $tag name of the source tag. - * @param string $matchAttributeName name of the source tag attribute, which should contain $matchAttributeValue - * @param string $matchAttributeValue required value of $matchAttributeName - * @param string $valueAttributeName name of the source tag attribute, which should contain searched value. - * @return string|boolean searched value, "false" on failure. - */ - protected function extractHtmlTagValue($content, $tag, $matchAttributeName, $matchAttributeValue, $valueAttributeName) - { - preg_match_all("#<{$tag}[^>]*$matchAttributeName=['\"].*?$matchAttributeValue.*?['\"][^>]*$valueAttributeName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); - preg_match_all("#<{$tag}[^>]*$valueAttributeName=['\"](.+?)['\"][^>]*$matchAttributeName=['\"].*?$matchAttributeValue.*?['\"][^>]*/?>#i", $content, $matches2); - $result = array_merge($matches1[1], $matches2[1]); - - return empty($result) ? false : $result[0]; - } - - /** - * Performs Yadis and HTML discovery. - * @param string $url Identity URL. - * @return array OpenID provider info, following keys will be available: - * - 'url' - string OP Endpoint (i.e. OpenID provider address). - * - 'version' - integer OpenID protocol version used by provider. - * - 'identity' - string identity value. - * - 'identifier_select' - boolean whether to request OP to select identity for an user in OpenID 2, does not affect OpenID 1. - * - 'ax' - boolean whether AX attributes should be used. - * - 'sreg' - boolean whether SREG attributes should be used. - * @throws Exception on failure. - */ - public function discover($url) - { - if (empty($url)) { - throw new Exception('No identity supplied.'); - } - $result = [ - 'url' => null, - 'version' => null, - 'identity' => $url, - 'identifier_select' => false, - 'ax' => false, - 'sreg' => false, - ]; - - // Use xri.net proxy to resolve i-name identities - if (!preg_match('#^https?:#', $url)) { - $url = 'https://xri.net/' . $url; - } - - /* We save the original url in case of Yadis discovery failure. - It can happen when we'll be lead to an XRDS document - which does not have any OpenID2 services.*/ - $originalUrl = $url; - - // A flag to disable yadis discovery in case of failure in headers. - $yadis = true; - - // We'll jump a maximum of 5 times, to avoid endless redirections. - for ($i = 0; $i < 5; $i ++) { - if ($yadis) { - $headers = $this->sendRequest($url, 'HEAD'); - - $next = false; - if (isset($headers['x-xrds-location'])) { - $url = $this->buildUrl($url, trim($headers['x-xrds-location'])); - $next = true; - } - - if (isset($headers['content-type']) - && (strpos($headers['content-type'], 'application/xrds+xml') !== false - || strpos($headers['content-type'], 'text/xml') !== false) - ) { - /* Apparently, some providers return XRDS documents as text/html. - While it is against the spec, allowing this here shouldn't break - compatibility with anything. - --- - Found an XRDS document, now let's find the server, and optionally delegate.*/ - $content = $this->sendRequest($url, 'GET'); - - preg_match_all('#(.*?)#s', $content, $m); - foreach ($m[1] as $content) { - $content = ' ' . $content; // The space is added, so that strpos doesn't return 0. - - // OpenID 2 - $ns = preg_quote('http://specs.openid.net/auth/2.0/'); - if (preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { - if ($type[1] == 'server') { - $result['identifier_select'] = true; - } - - preg_match('#(.*)#', $content, $server); - preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); - if (empty($server)) { - throw new Exception('No servers found!'); - } - // Does the server advertise support for either AX or SREG? - $result['ax'] = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); - $result['sreg'] = strpos($content, 'http://openid.net/sreg/1.0') || strpos($content, 'http://openid.net/extensions/sreg/1.1'); - - $server = $server[1]; - if (isset($delegate[2])) { - $result['identity'] = trim($delegate[2]); - } - - $result['url'] = $server; - $result['version'] = 2; - - return $result; - } - - // OpenID 1.1 - $ns = preg_quote('http://openid.net/signon/1.1'); - if (preg_match('#\s*'.$ns.'\s*#s', $content)) { - preg_match('#(.*)#', $content, $server); - preg_match('#<.*?Delegate>(.*)#', $content, $delegate); - if (empty($server)) { - throw new Exception('No servers found!'); - } - // AX can be used only with OpenID 2.0, so checking only SREG - $result['sreg'] = strpos($content, 'http://openid.net/sreg/1.0') || strpos($content, 'http://openid.net/extensions/sreg/1.1'); - - $server = $server[1]; - if (isset($delegate[1])) { - $result['identity'] = $delegate[1]; - } - - $result['url'] = $server; - $result['version'] = 1; - - return $result; - } - } - - $next = true; - $yadis = false; - $url = $originalUrl; - $content = null; - break; - } - if ($next) { - continue; - } - - // There are no relevant information in headers, so we search the body. - $content = $this->sendRequest($url, 'GET'); - $location = $this->extractHtmlTagValue($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); - if ($location) { - $url = $this->buildUrl($url, $location); - continue; - } - } - - if (!isset($content)) { - $content = $this->sendRequest($url, 'GET'); - } - - // At this point, the YADIS Discovery has failed, so we'll switch to openid2 HTML discovery, then fallback to openid 1.1 discovery. - $server = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid2.provider', 'href'); - if (!$server) { - // The same with openid 1.1 - $server = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid.server', 'href'); - $delegate = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid.delegate', 'href'); - $version = 1; - } else { - $delegate = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid2.local_id', 'href'); - $version = 2; - } - - if ($server) { - // We found an OpenID2 OP Endpoint - if ($delegate) { - // We have also found an OP-Local ID. - $result['identity'] = $delegate; - } - $result['url'] = $server; - $result['version'] = $version; - - return $result; - } - throw new Exception('No servers found!'); - } - throw new Exception('Endless redirection!'); - } - - /** - * Composes SREG request parameters. - * @return array SREG parameters. - */ - protected function buildSregParams() - { - $params = []; - /* We always use SREG 1.1, even if the server is advertising only support for 1.0. - That's because it's fully backwards compatible with 1.0, and some providers - advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com */ - $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; - if (!empty($this->requiredAttributes)) { - $params['openid.sreg.required'] = []; - foreach ($this->requiredAttributes as $required) { - if (!isset($this->axToSregMap[$required])) { - continue; - } - $params['openid.sreg.required'][] = $this->axToSregMap[$required]; - } - $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); - } - - if (!empty($this->optionalAttributes)) { - $params['openid.sreg.optional'] = []; - foreach ($this->optionalAttributes as $optional) { - if (!isset($this->axToSregMap[$optional])) { - continue; - } - $params['openid.sreg.optional'][] = $this->axToSregMap[$optional]; - } - $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); - } - - return $params; - } - - /** - * Composes AX request parameters. - * @return array AX parameters. - */ - protected function buildAxParams() - { - $params = []; - if (!empty($this->requiredAttributes) || !empty($this->optionalAttributes)) { - $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; - $params['openid.ax.mode'] = 'fetch_request'; - $aliases = []; - $counts = []; - $requiredAttributes = []; - $optionalAttributes = []; - foreach (['requiredAttributes', 'optionalAttributes'] as $type) { - foreach ($this->$type as $alias => $field) { - if (is_int($alias)) { - $alias = strtr($field, '/', '_'); - } - $aliases[$alias] = 'http://axschema.org/' . $field; - if (empty($counts[$alias])) { - $counts[$alias] = 0; - } - $counts[$alias] += 1; - ${$type}[] = $alias; - } - } - foreach ($aliases as $alias => $ns) { - $params['openid.ax.type.' . $alias] = $ns; - } - foreach ($counts as $alias => $count) { - if ($count == 1) { - continue; - } - $params['openid.ax.count.' . $alias] = $count; - } - - // Don't send empty ax.required and ax.if_available. - // Google and possibly other providers refuse to support ax when one of these is empty. - if (!empty($requiredAttributes)) { - $params['openid.ax.required'] = implode(',', $requiredAttributes); - } - if (!empty($optionalAttributes)) { - $params['openid.ax.if_available'] = implode(',', $optionalAttributes); - } - } - - return $params; - } - - /** - * Builds authentication URL for the protocol version 1. - * @param array $serverInfo OpenID server info. - * @return string authentication URL. - */ - protected function buildAuthUrlV1($serverInfo) - { - $returnUrl = $this->getReturnUrl(); - /* If we have an openid.delegate that is different from our claimed id, - we need to somehow preserve the claimed id between requests. - The simplest way is to just send it along with the return_to url.*/ - if ($serverInfo['identity'] != $this->getClaimedId()) { - $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->getClaimedId(); - } - - $params = array_merge( - [ - 'openid.return_to' => $returnUrl, - 'openid.mode' => 'checkid_setup', - 'openid.identity' => $serverInfo['identity'], - 'openid.trust_root' => $this->trustRoot, - ], - $this->buildSregParams() - ); - - return $this->buildUrl($serverInfo['url'], ['query' => http_build_query($params, '', '&')]); - } - - /** - * Builds authentication URL for the protocol version 2. - * @param array $serverInfo OpenID server info. - * @return string authentication URL. - */ - protected function buildAuthUrlV2($serverInfo) - { - $params = [ - 'openid.ns' => 'http://specs.openid.net/auth/2.0', - 'openid.mode' => 'checkid_setup', - 'openid.return_to' => $this->getReturnUrl(), - 'openid.realm' => $this->getTrustRoot(), - ]; - if ($serverInfo['ax']) { - $params = array_merge($params, $this->buildAxParams()); - } - if ($serverInfo['sreg']) { - $params = array_merge($params, $this->buildSregParams()); - } - if (!$serverInfo['ax'] && !$serverInfo['sreg']) { - // If OP doesn't advertise either SREG, nor AX, let's send them both in worst case we don't get anything in return. - $params = array_merge($this->buildSregParams(), $this->buildAxParams(), $params); - } - - if ($serverInfo['identifier_select']) { - $url = 'http://specs.openid.net/auth/2.0/identifier_select'; - $params['openid.identity'] = $url; - $params['openid.claimed_id']= $url; - } else { - $params['openid.identity'] = $serverInfo['identity']; - $params['openid.claimed_id'] = $this->getClaimedId(); - } - - return $this->buildUrl($serverInfo['url'], ['query' => http_build_query($params, '', '&')]); - } - - /** - * Returns authentication URL. Usually, you want to redirect your user to it. - * @param boolean $identifierSelect whether to request OP to select identity for an user in OpenID 2, does not affect OpenID 1. - * @return string the authentication URL. - * @throws Exception on failure. - */ - public function buildAuthUrl($identifierSelect = null) - { - $authUrl = $this->authUrl; - $claimedId = $this->getClaimedId(); - if (empty($claimedId)) { - $this->setClaimedId($authUrl); - } - $serverInfo = $this->discover($authUrl); - if ($serverInfo['version'] == 2) { - if ($identifierSelect !== null) { - $serverInfo['identifier_select'] = $identifierSelect; - } - - return $this->buildAuthUrlV2($serverInfo); - } - - return $this->buildAuthUrlV1($serverInfo); - } - - /** - * Performs OpenID verification with the OP. - * @param boolean $validateRequiredAttributes whether to validate required attributes. - * @return boolean whether the verification was successful. - */ - public function validate($validateRequiredAttributes = true) - { - $claimedId = $this->getClaimedId(); - if (empty($claimedId)) { - return false; - } - $params = [ - 'openid.assoc_handle' => $this->data['openid_assoc_handle'], - 'openid.signed' => $this->data['openid_signed'], - 'openid.sig' => $this->data['openid_sig'], - ]; - - if (isset($this->data['openid_ns'])) { - /* We're dealing with an OpenID 2.0 server, so let's set an ns - Even though we should know location of the endpoint, - we still need to verify it by discovery, so $server is not set here*/ - $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; - } elseif (isset($this->data['openid_claimed_id']) && $this->data['openid_claimed_id'] != $this->data['openid_identity']) { - // If it's an OpenID 1 provider, and we've got claimed_id, - // we have to append it to the returnUrl, like authUrlV1 does. - $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $claimedId; - } - - if (!$this->compareUrl($this->data['openid_return_to'], $this->returnUrl)) { - // The return_to url must match the url of current request. - return false; - } - - $serverInfo = $this->discover($claimedId); - - foreach (explode(',', $this->data['openid_signed']) as $item) { - $value = $this->data['openid_' . str_replace('.', '_', $item)]; - $params['openid.' . $item] = $value; - } - - $params['openid.mode'] = 'check_authentication'; - - $response = $this->sendRequest($serverInfo['url'], 'POST', $params); - - if (preg_match('/is_valid\s*:\s*true/i', $response)) { - if ($validateRequiredAttributes) { - return $this->validateRequiredAttributes(); - } else { - return true; - } - } else { - return false; - } - } - - /** - * Checks if all required attributes are present in the server response. - * @return boolean whether all required attributes are present. - */ - protected function validateRequiredAttributes() - { - if (!empty($this->requiredAttributes)) { - $attributes = $this->fetchAttributes(); - foreach ($this->requiredAttributes as $openIdAttributeName) { - if (!isset($attributes[$openIdAttributeName])) { - return false; - } - } - } - - return true; - } - - /** - * Gets AX attributes provided by OP. - * @return array array of attributes. - */ - protected function fetchAxAttributes() - { - $alias = null; - if (isset($this->data['openid_ns_ax']) && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0') { - // It's the most likely case, so we'll check it before - $alias = 'ax'; - } else { - // 'ax' prefix is either undefined, or points to another extension, so we search for another prefix - foreach ($this->data as $key => $value) { - if (strncmp($key, 'openid_ns_', 10) === 0 && $value == 'http://openid.net/srv/ax/1.0') { - $alias = substr($key, strlen('openid_ns_')); - break; - } - } - } - if (!$alias) { - // An alias for AX schema has not been found, so there is no AX data in the OP's response - return []; - } - - $attributes = []; - foreach ($this->data as $key => $value) { - $keyMatch = 'openid_' . $alias . '_value_'; - if (strncmp($key, $keyMatch, strlen($keyMatch))) { - continue; - } - $key = substr($key, strlen($keyMatch)); - if (!isset($this->data['openid_' . $alias . '_type_' . $key])) { - /* OP is breaking the spec by returning a field without - associated ns. This shouldn't happen, but it's better - to check, than cause an E_NOTICE.*/ - continue; - } - $key = substr($this->data['openid_' . $alias . '_type_' . $key], strlen('http://axschema.org/')); - $attributes[$key] = $value; - } - - return $attributes; - } - - /** - * Gets SREG attributes provided by OP. SREG names will be mapped to AX names. - * @return array array of attributes with keys being the AX schema names, e.g. 'contact/email' - */ - protected function fetchSregAttributes() - { - $attributes = []; - $sregToAx = array_flip($this->axToSregMap); - foreach ($this->data as $key => $value) { - $keyMatch = 'openid_sreg_'; - if (strncmp($key, $keyMatch, strlen($keyMatch))) { - continue; - } - $key = substr($key, strlen($keyMatch)); - if (!isset($sregToAx[$key])) { - // The field name isn't part of the SREG spec, so we ignore it. - continue; - } - $attributes[$sregToAx[$key]] = $value; - } - - return $attributes; - } - - /** - * Gets AX/SREG attributes provided by OP. Should be used only after successful validation. - * Note that it does not guarantee that any of the required/optional parameters will be present, - * or that there will be no other attributes besides those specified. - * In other words. OP may provide whatever information it wants to. - * SREG names will be mapped to AX names. - * @return array array of attributes with keys being the AX schema names, e.g. 'contact/email' - * @see http://www.axschema.org/types/ - */ - public function fetchAttributes() - { - if (isset($this->data['openid_ns']) && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0') { - // OpenID 2.0 - // We search for both AX and SREG attributes, with AX taking precedence. - return array_merge($this->fetchSregAttributes(), $this->fetchAxAttributes()); - } - - return $this->fetchSregAttributes(); - } - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return array_merge(['id' => $this->getClaimedId()], $this->fetchAttributes()); - } - - /** - * Compares 2 URLs taking in account possible GET parameters order miss match and URL encoding inconsistencies. - * @param string $expectedUrl expected URL. - * @param string $actualUrl actual URL. - * @return boolean whether URLs are equal. - */ - protected function compareUrl($expectedUrl, $actualUrl) - { - $expectedUrlInfo = parse_url($expectedUrl); - $actualUrlInfo = parse_url($actualUrl); - foreach ($expectedUrlInfo as $name => $expectedValue) { - if ($name == 'query') { - parse_str($expectedValue, $expectedUrlParams); - parse_str($actualUrlInfo[$name], $actualUrlParams); - $paramsDiff = array_diff_assoc($expectedUrlParams, $actualUrlParams); - if (!empty($paramsDiff)) { - return false; - } - } elseif ($expectedValue != $actualUrlInfo[$name]) { - return false; - } - } - return true; - } -} diff --git a/extensions/authclient/assets/authchoice.css b/extensions/authclient/assets/authchoice.css deleted file mode 100644 index ed18a9fd5f..0000000000 --- a/extensions/authclient/assets/authchoice.css +++ /dev/null @@ -1,82 +0,0 @@ -.clients { - overflow:auto; -} - -.auth-icon { - display: block; - width: 32px; - height: 32px; - background: url(authchoice.png) no-repeat; -} - -.auth-icon.google, -.auth-icon.google_openid, -.auth-icon.google_oauth { - background-position: 0 -34px; -} -.auth-icon.twitter { - background-position: 0 -68px; -} -.auth-icon.yandex, -.auth-icon.yandex_openid, -.auth-icon.yandex_oauth { - background-position: 0 -102px; -} -.auth-icon.vkontakte { - background-position: 0 -136px; -} -.auth-icon.facebook { - background-position: 0 -170px; -} -.auth-icon.mailru { - background-position: 0 -204px; -} -.auth-icon.moikrug { - background-position: 0 -238px; -} -.auth-icon.odnoklassniki { - background-position: 0 -272px; -} -.auth-icon.linkedin { - background-position: 0 -306px; -} -.auth-icon.github { - background-position: 0 -340px; -} -.auth-icon.live { - background-position: 0 -372px; -} - -.auth-link:hover .auth-icon i, -.auth-link:focus .auth-icon i { - display: block; - width: 32px; - height: 32px; - background: url(authchoice.png) 0 0 no-repeat; -} - -.auth-clients { - margin: 0 0 1em; - list-style: none; - overflow: auto; -} - -.auth-client { - float: left; - margin: 0 1em 0 0; -} - -.auth-clients .auth-client .auth-link { - display: block; - width: 58px; -} - -.auth-client .auth-link .auth-icon { - margin: 0 auto; -} - -.auth-client .auth-link .auth-title { - display: block; - margin-top: 0.4em; - text-align: center; -} \ No newline at end of file diff --git a/extensions/authclient/assets/authchoice.js b/extensions/authclient/assets/authchoice.js deleted file mode 100644 index 07a8f0f5f2..0000000000 --- a/extensions/authclient/assets/authchoice.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Yii auth choice widget. - * - * This is the JavaScript widget used by the yii\authclient\widgets\AuthChoice widget. - * - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - * @author Paul Klimov - * @since 2.0 - */ -jQuery(function($) { - $.fn.authchoice = function(options) { - options = $.extend({ - popup: { - resizable: 'yes', - scrollbars: 'no', - toolbar: 'no', - menubar: 'no', - location: 'no', - directories: 'no', - status: 'yes', - width: 450, - height: 380 - } - }, options); - - return this.each(function() { - var $container = $(this); - - $container.find('a').on('click', function(e) { - e.preventDefault(); - - var authChoicePopup = $container.data('authChoicePopup'); - - if (authChoicePopup) { - authChoicePopup.close(); - } - - var url = this.href; - var popupOptions = $.extend({}, options.popup); // clone - - var localPopupWidth = this.getAttribute('data-popup-width'); - if (localPopupWidth) { - popupOptions.width = localPopupWidth; - } - var localPopupHeight = this.getAttribute('data-popup-height'); - if (localPopupWidth) { - popupOptions.height = localPopupHeight; - } - - popupOptions.left = (window.screen.width - popupOptions.width) / 2; - popupOptions.top = (window.screen.height - popupOptions.height) / 2; - - var popupFeatureParts = []; - for (var propName in popupOptions) { - if (popupOptions.hasOwnProperty(propName)) { - popupFeatureParts.push(propName + '=' + popupOptions[propName]); - } - } - var popupFeature = popupFeatureParts.join(','); - - authChoicePopup = window.open(url, 'yii_auth_choice', popupFeature); - authChoicePopup.focus(); - - $container.data('authChoicePopup', authChoicePopup); - }); - }); - }; -}); diff --git a/extensions/authclient/assets/authchoice.png b/extensions/authclient/assets/authchoice.png deleted file mode 100644 index ffc3c5e7ec..0000000000 Binary files a/extensions/authclient/assets/authchoice.png and /dev/null differ diff --git a/extensions/authclient/clients/Facebook.php b/extensions/authclient/clients/Facebook.php deleted file mode 100644 index e257092245..0000000000 --- a/extensions/authclient/clients/Facebook.php +++ /dev/null @@ -1,84 +0,0 @@ -. - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'facebook' => [ - * 'class' => 'yii\authclient\clients\Facebook', - * 'clientId' => 'facebook_client_id', - * 'clientSecret' => 'facebook_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see https://developers.facebook.com/apps - * @see http://developers.facebook.com/docs/reference/api - * - * @author Paul Klimov - * @since 2.0 - */ -class Facebook extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://www.facebook.com/dialog/oauth'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://graph.facebook.com/oauth/access_token'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://graph.facebook.com'; - /** - * @inheritdoc - */ - public $scope = 'email'; - - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return $this->api('me', 'GET'); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'facebook'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Facebook'; - } -} diff --git a/extensions/authclient/clients/GitHub.php b/extensions/authclient/clients/GitHub.php deleted file mode 100644 index 2fbc1231f3..0000000000 --- a/extensions/authclient/clients/GitHub.php +++ /dev/null @@ -1,94 +0,0 @@ -. - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'github' => [ - * 'class' => 'yii\authclient\clients\GitHub', - * 'clientId' => 'github_client_id', - * 'clientSecret' => 'github_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see http://developer.github.com/v3/oauth/ - * @see https://github.com/settings/applications/new - * - * @author Paul Klimov - * @since 2.0 - */ -class GitHub extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://github.com/login/oauth/authorize'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://github.com/login/oauth/access_token'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://api.github.com'; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if ($this->scope === null) { - $this->scope = implode(' ', [ - 'user', - 'user:email', - ]); - } - } - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return $this->api('user', 'GET'); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'github'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'GitHub'; - } -} diff --git a/extensions/authclient/clients/GoogleOAuth.php b/extensions/authclient/clients/GoogleOAuth.php deleted file mode 100644 index 0a30e637fd..0000000000 --- a/extensions/authclient/clients/GoogleOAuth.php +++ /dev/null @@ -1,96 +0,0 @@ - - * and setup its credentials at . - * In order to enable using scopes for retrieving user attributes, you should also enable Google+ API at - * - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'google' => [ - * 'class' => 'yii\authclient\clients\GoogleOAuth', - * 'clientId' => 'google_client_id', - * 'clientSecret' => 'google_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see https://console.developers.google.com/project - * - * @author Paul Klimov - * @since 2.0 - */ -class GoogleOAuth extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://accounts.google.com/o/oauth2/auth'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://accounts.google.com/o/oauth2/token'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://www.googleapis.com/plus/v1'; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if ($this->scope === null) { - $this->scope = implode(' ', [ - 'profile', - 'email', - ]); - } - } - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return $this->api('people/me', 'GET'); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'google'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Google'; - } -} diff --git a/extensions/authclient/clients/GoogleOpenId.php b/extensions/authclient/clients/GoogleOpenId.php deleted file mode 100644 index 3b7d113c9e..0000000000 --- a/extensions/authclient/clients/GoogleOpenId.php +++ /dev/null @@ -1,91 +0,0 @@ - [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'google' => [ - * 'class' => 'yii\authclient\clients\GoogleOpenId' - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @author Paul Klimov - * @since 2.0 - */ -class GoogleOpenId extends OpenId -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://www.google.com/accounts/o8/id'; - /** - * @inheritdoc - */ - public $requiredAttributes = [ - 'namePerson/first', - 'namePerson/last', - 'contact/email', - 'pref/language', - ]; - - - /** - * @inheritdoc - */ - protected function defaultNormalizeUserAttributeMap() - { - return [ - 'first_name' => 'namePerson/first', - 'last_name' => 'namePerson/last', - 'email' => 'contact/email', - 'language' => 'pref/language', - ]; - } - - /** - * @inheritdoc - */ - protected function defaultViewOptions() - { - return [ - 'popupWidth' => 880, - 'popupHeight' => 520, - ]; - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'google'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Google'; - } -} diff --git a/extensions/authclient/clients/LinkedIn.php b/extensions/authclient/clients/LinkedIn.php deleted file mode 100644 index cbbdc188c4..0000000000 --- a/extensions/authclient/clients/LinkedIn.php +++ /dev/null @@ -1,176 +0,0 @@ -. - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'linkedin' => [ - * 'class' => 'yii\authclient\clients\LinkedIn', - * 'clientId' => 'linkedin_client_id', - * 'clientSecret' => 'linkedin_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see http://developer.linkedin.com/documents/authentication - * @see https://www.linkedin.com/secure/developer - * @see http://developer.linkedin.com/apis - * - * @author Paul Klimov - * @since 2.0 - */ -class LinkedIn extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://www.linkedin.com/uas/oauth2/authorization'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://www.linkedin.com/uas/oauth2/accessToken'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://api.linkedin.com/v1'; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if ($this->scope === null) { - $this->scope = implode(' ', [ - 'r_basicprofile', - 'r_emailaddress', - ]); - } - } - - /** - * @inheritdoc - */ - protected function defaultNormalizeUserAttributeMap() - { - return [ - 'email' => 'email-address', - 'first_name' => 'first-name', - 'last_name' => 'last-name', - ]; - } - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - $attributeNames = [ - 'id', - 'email-address', - 'first-name', - 'last-name', - 'public-profile-url', - ]; - - return $this->api('people/~:(' . implode(',', $attributeNames) . ')', 'GET'); - } - - /** - * @inheritdoc - */ - public function buildAuthUrl(array $params = []) - { - $authState = $this->generateAuthState(); - $this->setState('authState', $authState); - $params['state'] = $authState; - - return parent::buildAuthUrl($params); - } - - /** - * @inheritdoc - */ - public function fetchAccessToken($authCode, array $params = []) - { - $authState = $this->getState('authState'); - if (!isset($_REQUEST['state']) || empty($authState) || strcmp($_REQUEST['state'], $authState) !== 0) { - throw new HttpException(400, 'Invalid auth state parameter.'); - } else { - $this->removeState('authState'); - } - - return parent::fetchAccessToken($authCode, $params); - } - - /** - * @inheritdoc - */ - protected function apiInternal($accessToken, $url, $method, array $params, array $headers) - { - $params['oauth2_access_token'] = $accessToken->getToken(); - - return $this->sendRequest($method, $url, $params, $headers); - } - - /** - * @inheritdoc - */ - protected function defaultReturnUrl() - { - $params = $_GET; - unset($params['code']); - unset($params['state']); - $params[0] = Yii::$app->controller->getRoute(); - - return Yii::$app->getUrlManager()->createAbsoluteUrl($params); - } - - /** - * Generates the auth state value. - * @return string auth state value. - */ - protected function generateAuthState() - { - return sha1(uniqid(get_class($this), true)); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'linkedin'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'LinkedIn'; - } -} diff --git a/extensions/authclient/clients/Live.php b/extensions/authclient/clients/Live.php deleted file mode 100644 index 472f47c1c5..0000000000 --- a/extensions/authclient/clients/Live.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'live' => [ - * 'class' => 'yii\authclient\clients\Live', - * 'clientId' => 'live_client_id', - * 'clientSecret' => 'live_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see https://account.live.com/developers/applications - * @see http://msdn.microsoft.com/en-us/library/live/hh243647.aspx - * - * @author Paul Klimov - * @since 2.0 - */ -class Live extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://login.live.com/oauth20_authorize.srf'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://login.live.com/oauth20_token.srf'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://apis.live.net/v5.0'; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if ($this->scope === null) { - $this->scope = implode(',', [ - 'wl.basic', - 'wl.emails', - ]); - } - } - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return $this->api('me', 'GET'); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'live'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Live'; - } -} diff --git a/extensions/authclient/clients/VKontakte.php b/extensions/authclient/clients/VKontakte.php deleted file mode 100644 index 23edfb675a..0000000000 --- a/extensions/authclient/clients/VKontakte.php +++ /dev/null @@ -1,115 +0,0 @@ -. - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'vkontakte' => [ - * 'class' => 'yii\authclient\clients\VKontakte', - * 'clientId' => 'vkontakte_client_id', - * 'clientSecret' => 'vkontakte_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see http://vk.com/editapp?act=create - * @see http://vk.com/developers.php?oid=-1&p=users.get - * - * @author Paul Klimov - * @since 2.0 - */ -class VKontakte extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'http://api.vk.com/oauth/authorize'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://api.vk.com/oauth/access_token'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://api.vk.com/method'; - - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - $attributes = $this->api('users.get.json', 'GET', [ - 'fields' => implode(',', [ - 'uid', - 'first_name', - 'last_name', - 'nickname', - 'screen_name', - 'sex', - 'bdate', - 'city', - 'country', - 'timezone', - 'photo' - ]), - ]); - return array_shift($attributes['response']); - } - - /** - * @inheritdoc - */ - protected function apiInternal($accessToken, $url, $method, array $params, array $headers) - { - $params['uids'] = $accessToken->getParam('user_id'); - $params['access_token'] = $accessToken->getToken(); - return $this->sendRequest($method, $url, $params, $headers); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'vkontakte'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'VKontakte'; - } - - /** - * @inheritdoc - */ - protected function defaultNormalizeUserAttributeMap() - { - return [ - 'id' => 'uid' - ]; - } -} diff --git a/extensions/authclient/clients/YandexOAuth.php b/extensions/authclient/clients/YandexOAuth.php deleted file mode 100644 index f449f74699..0000000000 --- a/extensions/authclient/clients/YandexOAuth.php +++ /dev/null @@ -1,93 +0,0 @@ -. - * - * Example application configuration: - * - * ~~~ - * 'components' => [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'yandex' => [ - * 'class' => 'yii\authclient\clients\YandexOAuth', - * 'clientId' => 'yandex_client_id', - * 'clientSecret' => 'yandex_client_secret', - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @see https://oauth.yandex.ru/client/new - * @see http://api.yandex.ru/login/doc/dg/reference/response.xml - * - * @author Paul Klimov - * @since 2.0 - */ -class YandexOAuth extends OAuth2 -{ - /** - * @inheritdoc - */ - public $authUrl = 'https://oauth.yandex.ru/authorize'; - /** - * @inheritdoc - */ - public $tokenUrl = 'https://oauth.yandex.ru/token'; - /** - * @inheritdoc - */ - public $apiBaseUrl = 'https://login.yandex.ru'; - - - /** - * @inheritdoc - */ - protected function initUserAttributes() - { - return $this->api('info', 'GET'); - } - - /** - * @inheritdoc - */ - protected function apiInternal($accessToken, $url, $method, array $params, array $headers) - { - if (!isset($params['format'])) { - $params['format'] = 'json'; - } - $params['oauth_token'] = $accessToken->getToken(); - - return $this->sendRequest($method, $url, $params, $headers); - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'yandex'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Yandex'; - } -} diff --git a/extensions/authclient/clients/YandexOpenId.php b/extensions/authclient/clients/YandexOpenId.php deleted file mode 100644 index 58ddcfa92a..0000000000 --- a/extensions/authclient/clients/YandexOpenId.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - * 'authClientCollection' => [ - * 'class' => 'yii\authclient\Collection', - * 'clients' => [ - * 'yandex' => [ - * 'class' => 'yii\authclient\clients\YandexOpenId' - * ], - * ], - * ] - * ... - * ] - * ~~~ - * - * @author Paul Klimov - * @since 2.0 - */ -class YandexOpenId extends OpenId -{ - /** - * @inheritdoc - */ - public $authUrl = 'http://openid.yandex.ru'; - /** - * @inheritdoc - */ - public $requiredAttributes = [ - 'namePerson', - 'contact/email', - ]; - - - /** - * @inheritdoc - */ - protected function defaultNormalizeUserAttributeMap() - { - return [ - 'name' => 'namePerson', - 'email' => 'contact/email', - ]; - } - - /** - * @inheritdoc - */ - protected function defaultViewOptions() - { - return [ - 'popupWidth' => 900, - 'popupHeight' => 550, - ]; - } - - /** - * @inheritdoc - */ - protected function defaultName() - { - return 'yandex'; - } - - /** - * @inheritdoc - */ - protected function defaultTitle() - { - return 'Yandex'; - } -} diff --git a/extensions/authclient/composer.json b/extensions/authclient/composer.json deleted file mode 100644 index 789259f4ba..0000000000 --- a/extensions/authclient/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "yiisoft/yii2-authclient", - "description": "External authentication via OAuth and OpenID for the Yii framework", - "keywords": ["yii2", "OAuth", "OpenID", "auth"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "ext-curl": "*" - }, - "autoload": { - "psr-4": { "yii\\authclient\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/authclient/signature/BaseMethod.php b/extensions/authclient/signature/BaseMethod.php deleted file mode 100644 index c4873afe76..0000000000 --- a/extensions/authclient/signature/BaseMethod.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseMethod extends Object -{ - /** - * Return the canonical name of the Signature Method. - * @return string method name. - */ - abstract public function getName(); - - /** - * Generates OAuth request signature. - * @param string $baseString signature base string. - * @param string $key signature key. - * @return string signature string. - */ - abstract public function generateSignature($baseString, $key); - - /** - * Verifies given OAuth request. - * @param string $signature signature to be verified. - * @param string $baseString signature base string. - * @param string $key signature key. - * @return boolean success. - */ - public function verify($signature, $baseString, $key) - { - $expectedSignature = $this->generateSignature($baseString, $key); - if (empty($signature) || empty($expectedSignature)) { - return false; - } - - return (strcmp($expectedSignature, $signature) === 0); - } -} diff --git a/extensions/authclient/signature/HmacSha1.php b/extensions/authclient/signature/HmacSha1.php deleted file mode 100644 index 408e0b08b0..0000000000 --- a/extensions/authclient/signature/HmacSha1.php +++ /dev/null @@ -1,47 +0,0 @@ - **Note:** This class requires PHP "Hash" extension(). - * - * @author Paul Klimov - * @since 2.0 - */ -class HmacSha1 extends BaseMethod -{ - /** - * @inheritdoc - */ - public function init() - { - if (!function_exists('hash_hmac')) { - throw new NotSupportedException('PHP "Hash" extension is required.'); - } - } - - /** - * @inheritdoc - */ - public function getName() - { - return 'HMAC-SHA1'; - } - - /** - * @inheritdoc - */ - public function generateSignature($baseString, $key) - { - return base64_encode(hash_hmac('sha1', $baseString, $key, true)); - } -} diff --git a/extensions/authclient/signature/PlainText.php b/extensions/authclient/signature/PlainText.php deleted file mode 100644 index d512730d86..0000000000 --- a/extensions/authclient/signature/PlainText.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @since 2.0 - */ -class PlainText extends BaseMethod -{ - /** - * @inheritdoc - */ - public function getName() - { - return 'PLAINTEXT'; - } - - /** - * @inheritdoc - */ - public function generateSignature($baseString, $key) - { - return $key; - } -} diff --git a/extensions/authclient/signature/RsaSha1.php b/extensions/authclient/signature/RsaSha1.php deleted file mode 100644 index c950c4efb8..0000000000 --- a/extensions/authclient/signature/RsaSha1.php +++ /dev/null @@ -1,176 +0,0 @@ - **Note:** This class requires PHP "OpenSSL" extension(). - * - * @property string $privateCertificate Private key certificate content. - * @property string $publicCertificate Public key certificate content. - * - * @author Paul Klimov - * @since 2.0 - */ -class RsaSha1 extends BaseMethod -{ - /** - * @var string path to the file, which holds private key certificate. - */ - public $privateCertificateFile = ''; - /** - * @var string path to the file, which holds public key certificate. - */ - public $publicCertificateFile = ''; - - /** - * @var string OpenSSL private key certificate content. - * This value can be fetched from file specified by [[privateCertificateFile]]. - */ - protected $_privateCertificate; - /** - * @var string OpenSSL public key certificate content. - * This value can be fetched from file specified by [[publicCertificateFile]]. - */ - protected $_publicCertificate; - - - /** - * @inheritdoc - */ - public function init() - { - if (!function_exists('openssl_sign')) { - throw new NotSupportedException('PHP "OpenSSL" extension is required.'); - } - } - - /** - * @param string $publicCertificate public key certificate content. - */ - public function setPublicCertificate($publicCertificate) - { - $this->_publicCertificate = $publicCertificate; - } - - /** - * @return string public key certificate content. - */ - public function getPublicCertificate() - { - if ($this->_publicCertificate === null) { - $this->_publicCertificate = $this->initPublicCertificate(); - } - - return $this->_publicCertificate; - } - - /** - * @param string $privateCertificate private key certificate content. - */ - public function setPrivateCertificate($privateCertificate) - { - $this->_privateCertificate = $privateCertificate; - } - - /** - * @return string private key certificate content. - */ - public function getPrivateCertificate() - { - if ($this->_privateCertificate === null) { - $this->_privateCertificate = $this->initPrivateCertificate(); - } - - return $this->_privateCertificate; - } - - /** - * @inheritdoc - */ - public function getName() - { - return 'RSA-SHA1'; - } - - /** - * Creates initial value for [[publicCertificate]]. - * This method will attempt to fetch the certificate value from [[publicCertificateFile]] file. - * @throws InvalidConfigException on failure. - * @return string public certificate content. - */ - protected function initPublicCertificate() - { - if (!empty($this->publicCertificateFile)) { - if (!file_exists($this->publicCertificateFile)) { - throw new InvalidConfigException("Public certificate file '{$this->publicCertificateFile}' does not exist!"); - } - - return file_get_contents($this->publicCertificateFile); - } else { - return ''; - } - } - - /** - * Creates initial value for [[privateCertificate]]. - * This method will attempt to fetch the certificate value from [[privateCertificateFile]] file. - * @throws InvalidConfigException on failure. - * @return string private certificate content. - */ - protected function initPrivateCertificate() - { - if (!empty($this->privateCertificateFile)) { - if (!file_exists($this->privateCertificateFile)) { - throw new InvalidConfigException("Private certificate file '{$this->privateCertificateFile}' does not exist!"); - } - - return file_get_contents($this->privateCertificateFile); - } else { - return ''; - } - } - - /** - * @inheritdoc - */ - public function generateSignature($baseString, $key) - { - $privateCertificateContent = $this->getPrivateCertificate(); - // Pull the private key ID from the certificate - $privateKeyId = openssl_pkey_get_private($privateCertificateContent); - // Sign using the key - openssl_sign($baseString, $signature, $privateKeyId); - // Release the key resource - openssl_free_key($privateKeyId); - - return base64_encode($signature); - } - - /** - * @inheritdoc - */ - public function verify($signature, $baseString, $key) - { - $decodedSignature = base64_decode($signature); - // Fetch the public key cert based on the request - $publicCertificate = $this->getPublicCertificate(); - // Pull the public key ID from the certificate - $publicKeyId = openssl_pkey_get_public($publicCertificate); - // Check the computed signature against the one passed in the query - $verificationResult = openssl_verify($baseString, $decodedSignature, $publicKeyId); - // Release the key resource - openssl_free_key($publicKeyId); - - return ($verificationResult == 1); - } -} diff --git a/extensions/authclient/views/redirect.php b/extensions/authclient/views/redirect.php deleted file mode 100644 index 5f8e51d89a..0000000000 --- a/extensions/authclient/views/redirect.php +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - -
    - - - diff --git a/extensions/authclient/widgets/AuthChoice.php b/extensions/authclient/widgets/AuthChoice.php deleted file mode 100644 index e380e7e2f8..0000000000 --- a/extensions/authclient/widgets/AuthChoice.php +++ /dev/null @@ -1,249 +0,0 @@ - ['site/auth'] - * ]); ?> - * ~~~ - * - * You can customize the widget appearance by using [[begin()]] and [[end()]] syntax - * along with using method [[clientLink()]] or [[createClientUrl()]]. - * For example: - * - * ~~~php - * - * ['site/auth'] - * ]); ?> - *
      - * getClients() as $client): ?> - *
    • clientLink($client) ?>
    • - * - *
    - * - * ~~~ - * - * This widget supports following keys for [[ClientInterface::getViewOptions()]] result: - * - popupWidth - integer width of the popup window in pixels. - * - popupHeight - integer height of the popup window in pixels. - * - * @see \yii\authclient\AuthAction - * - * @property array $baseAuthUrl Base auth URL configuration. This property is read-only. - * @property ClientInterface[] $clients Auth providers. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class AuthChoice extends Widget -{ - /** - * @var string name of the auth client collection application component. - * This component will be used to fetch services value if it is not set. - */ - public $clientCollection = 'authClientCollection'; - /** - * @var string name of the GET param , which should be used to passed auth client id to URL - * defined by [[baseAuthUrl]]. - */ - public $clientIdGetParamName = 'authclient'; - /** - * @var array the HTML attributes that should be rendered in the div HTML tag representing the container element. - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $options = [ - 'class' => 'auth-clients' - ]; - /** - * @var boolean indicates if popup window should be used instead of direct links. - */ - public $popupMode = true; - /** - * @var boolean indicates if widget content, should be rendered automatically. - * Note: this value automatically set to 'false' at the first call of [[createClientUrl()]] - */ - public $autoRender = true; - - /** - * @var array configuration for the external clients base authentication URL. - */ - private $_baseAuthUrl; - /** - * @var ClientInterface[] auth providers list. - */ - private $_clients; - - - /** - * @param ClientInterface[] $clients auth providers - */ - public function setClients(array $clients) - { - $this->_clients = $clients; - } - - /** - * @return ClientInterface[] auth providers - */ - public function getClients() - { - if ($this->_clients === null) { - $this->_clients = $this->defaultClients(); - } - - return $this->_clients; - } - - /** - * @param array $baseAuthUrl base auth URL configuration. - */ - public function setBaseAuthUrl(array $baseAuthUrl) - { - $this->_baseAuthUrl = $baseAuthUrl; - } - - /** - * @return array base auth URL configuration. - */ - public function getBaseAuthUrl() - { - if (!is_array($this->_baseAuthUrl)) { - $this->_baseAuthUrl = $this->defaultBaseAuthUrl(); - } - - return $this->_baseAuthUrl; - } - - /** - * Returns default auth clients list. - * @return ClientInterface[] auth clients list. - */ - protected function defaultClients() - { - /* @var $collection \yii\authclient\Collection */ - $collection = Yii::$app->get($this->clientCollection); - - return $collection->getClients(); - } - - /** - * Composes default base auth URL configuration. - * @return array base auth URL configuration. - */ - protected function defaultBaseAuthUrl() - { - $baseAuthUrl = [ - Yii::$app->controller->getRoute() - ]; - $params = $_GET; - unset($params[$this->clientIdGetParamName]); - $baseAuthUrl = array_merge($baseAuthUrl, $params); - - return $baseAuthUrl; - } - - /** - * Outputs client auth link. - * @param ClientInterface $client external auth client instance. - * @param string $text link text, if not set - default value will be generated. - * @param array $htmlOptions link HTML options. - */ - public function clientLink($client, $text = null, array $htmlOptions = []) - { - if ($text === null) { - $text = Html::tag('span', '', ['class' => 'auth-icon ' . $client->getName()]); - $text .= Html::tag('span', $client->getTitle(), ['class' => 'auth-title']); - } - if (!array_key_exists('class', $htmlOptions)) { - $htmlOptions['class'] = 'auth-link ' . $client->getName(); - } - if ($this->popupMode) { - $viewOptions = $client->getViewOptions(); - if (isset($viewOptions['popupWidth'])) { - $htmlOptions['data-popup-width'] = $viewOptions['popupWidth']; - } - if (isset($viewOptions['popupHeight'])) { - $htmlOptions['data-popup-height'] = $viewOptions['popupHeight']; - } - } - echo Html::a($text, $this->createClientUrl($client), $htmlOptions); - } - - /** - * Composes client auth URL. - * @param ClientInterface $provider external auth client instance. - * @return string auth URL. - */ - public function createClientUrl($provider) - { - $this->autoRender = false; - $url = $this->getBaseAuthUrl(); - $url[$this->clientIdGetParamName] = $provider->getId(); - - return Url::to($url); - } - - /** - * Renders the main content, which includes all external services links. - */ - protected function renderMainContent() - { - echo Html::beginTag('ul', ['class' => 'auth-clients clear']); - foreach ($this->getClients() as $externalService) { - echo Html::beginTag('li', ['class' => 'auth-client']); - $this->clientLink($externalService); - echo Html::endTag('li'); - } - echo Html::endTag('ul'); - } - - /** - * Initializes the widget. - */ - public function init() - { - $view = Yii::$app->getView(); - if ($this->popupMode) { - AuthChoiceAsset::register($view); - $view->registerJs("\$('#" . $this->getId() . "').authchoice();"); - } else { - AuthChoiceStyleAsset::register($view); - } - $this->options['id'] = $this->getId(); - echo Html::beginTag('div', $this->options); - } - - /** - * Runs the widget. - */ - public function run() - { - if ($this->autoRender) { - $this->renderMainContent(); - } - echo Html::endTag('div'); - } -} diff --git a/extensions/authclient/widgets/AuthChoiceAsset.php b/extensions/authclient/widgets/AuthChoiceAsset.php deleted file mode 100644 index abba083d57..0000000000 --- a/extensions/authclient/widgets/AuthChoiceAsset.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @since 2.0 - */ -class AuthChoiceAsset extends AssetBundle -{ - public $sourcePath = '@yii/authclient/assets'; - public $js = [ - 'authchoice.js', - ]; - public $depends = [ - 'yii\authclient\widgets\AuthChoiceStyleAsset', - 'yii\web\YiiAsset', - ]; -} diff --git a/extensions/authclient/widgets/AuthChoiceStyleAsset.php b/extensions/authclient/widgets/AuthChoiceStyleAsset.php deleted file mode 100644 index 37e770b01b..0000000000 --- a/extensions/authclient/widgets/AuthChoiceStyleAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class AuthChoiceStyleAsset extends AssetBundle -{ - public $sourcePath = '@yii/authclient/assets'; - public $css = [ - 'authchoice.css', - ]; -} \ No newline at end of file diff --git a/extensions/bootstrap/ActiveForm.php b/extensions/bootstrap/ActiveForm.php deleted file mode 100644 index 9ff20d7950..0000000000 --- a/extensions/bootstrap/ActiveForm.php +++ /dev/null @@ -1,101 +0,0 @@ - 'horizontal']) - * ``` - * - * This will set default values for the [[yii\bootstrap\ActiveField|ActiveField]] - * to render horizontal form fields. In particular the [[yii\bootstrap\ActiveField::template|template]] - * is set to `{label} {beginWrapper} {input} {error} {endWrapper} {hint}` and the - * [[yii\bootstrap\ActiveField::horizontalCssClasses|horizontalCssClasses]] are set to: - * - * ```php - * [ - * 'offset' => 'col-sm-offset-3', - * 'label' => 'col-sm-3', - * 'wrapper' => 'col-sm-6', - * 'error' => '', - * 'hint' => 'col-sm-3', - * ] - * ``` - * - * To get a different column layout in horizontal mode you can modify those options - * through [[fieldConfig]]: - * - * ```php - * $form = ActiveForm::begin([ - * 'layout' => 'horizontal', - * 'fieldConfig' => [ - * 'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}", - * 'horizontalCssClasses' => [ - * 'label' => 'col-sm-4', - * 'offset' => 'col-sm-offset-4', - * 'wrapper' => 'col-sm-8', - * 'error' => '', - * 'hint' => '', - * ], - * ], - * ]); - * ``` - * - * @see \yii\bootstrap\ActiveField for details on the [[fieldConfig]] options - * @see http://getbootstrap.com/css/#forms - * - * @author Michael Härtl - * @since 2.0 - */ -class ActiveForm extends \yii\widgets\ActiveForm -{ - /** - * @var string the default field class name when calling [[field()]] to create a new field. - * @see fieldConfig - */ - public $fieldClass = 'yii\bootstrap\ActiveField'; - /** - * @var array HTML attributes for the form tag. Default is `['role' => 'form']`. - */ - public $options = ['role' => 'form']; - /** - * @var string the form layout. Either 'default', 'horizontal' or 'inline'. - * By choosing a layout, an appropriate default field configuration is applied. This will - * render the form fields with slightly different markup for each layout. You can - * override these defaults through [[fieldConfig]]. - * @see \yii\bootstrap\ActiveField for details on Bootstrap 3 field configuration - */ - public $layout = 'default'; - - - /** - * @inheritdoc - */ - public function init() - { - if (!in_array($this->layout, ['default', 'horizontal', 'inline'])) { - throw new InvalidConfigException('Invalid layout type: ' . $this->layout); - } - - if ($this->layout !== 'default') { - Html::addCssClass($this->options, 'form-' . $this->layout); - } - parent::init(); - } -} diff --git a/extensions/bootstrap/Alert.php b/extensions/bootstrap/Alert.php deleted file mode 100644 index 287385a52a..0000000000 --- a/extensions/bootstrap/Alert.php +++ /dev/null @@ -1,151 +0,0 @@ - [ - * 'class' => 'alert-info', - * ], - * 'body' => 'Say hello...', - * ]); - * ``` - * - * The following example will show the content enclosed between the [[begin()]] - * and [[end()]] calls within the alert box: - * - * ```php - * Alert::begin([ - * 'options' => [ - * 'class' => 'alert-warning', - * ], - * ]); - * - * echo 'Say hello...'; - * - * Alert::end(); - * ``` - * - * @see http://getbootstrap.com/components/#alerts - * @author Antonio Ramirez - * @since 2.0 - */ -class Alert extends Widget -{ - /** - * @var string the body content in the alert component. Note that anything between - * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated - * as the body content, and will be rendered before this. - */ - public $body; - /** - * @var array the options for rendering the close button tag. - * The close button is displayed in the header of the modal window. Clicking - * on the button will hide the modal window. If this is false, no close button will be rendered. - * - * The following special options are supported: - * - * - tag: string, the tag name of the button. Defaults to 'button'. - * - label: string, the label of the button. Defaults to '×'. - * - * The rest of the options will be rendered as the HTML attributes of the button tag. - * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) - * for the supported HTML attributes. - */ - public $closeButton = []; - - - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - - $this->initOptions(); - - echo Html::beginTag('div', $this->options) . "\n"; - echo $this->renderBodyBegin() . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo "\n" . $this->renderBodyEnd(); - echo "\n" . Html::endTag('div'); - - $this->registerPlugin('alert'); - } - - /** - * Renders the close button if any before rendering the content. - * @return string the rendering result - */ - protected function renderBodyBegin() - { - return $this->renderCloseButton(); - } - - /** - * Renders the alert body (if any). - * @return string the rendering result - */ - protected function renderBodyEnd() - { - return $this->body . "\n"; - } - - /** - * Renders the close button. - * @return string the rendering result - */ - protected function renderCloseButton() - { - if ($this->closeButton !== false) { - $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); - $label = ArrayHelper::remove($this->closeButton, 'label', '×'); - if ($tag === 'button' && !isset($this->closeButton['type'])) { - $this->closeButton['type'] = 'button'; - } - - return Html::tag($tag, $label, $this->closeButton); - } else { - return null; - } - } - - /** - * Initializes the widget options. - * This method sets the default values for various options. - */ - protected function initOptions() - { - Html::addCssClass($this->options, 'alert'); - Html::addCssClass($this->options, 'fade'); - Html::addCssClass($this->options, 'in'); - - if ($this->closeButton !== false) { - $this->closeButton = array_merge([ - 'data-dismiss' => 'alert', - 'aria-hidden' => 'true', - 'class' => 'close', - ], $this->closeButton); - } - } -} diff --git a/extensions/bootstrap/BootstrapAsset.php b/extensions/bootstrap/BootstrapAsset.php deleted file mode 100644 index 313a7870af..0000000000 --- a/extensions/bootstrap/BootstrapAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class BootstrapAsset extends AssetBundle -{ - public $sourcePath = '@bower/bootstrap/dist'; - public $css = [ - 'css/bootstrap.css', - ]; -} diff --git a/extensions/bootstrap/BootstrapPluginAsset.php b/extensions/bootstrap/BootstrapPluginAsset.php deleted file mode 100644 index 6af1adcf73..0000000000 --- a/extensions/bootstrap/BootstrapPluginAsset.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @since 2.0 - */ -class BootstrapPluginAsset extends AssetBundle -{ - public $sourcePath = '@bower/bootstrap/dist'; - public $js = [ - 'js/bootstrap.js', - ]; - public $depends = [ - 'yii\web\JqueryAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/extensions/bootstrap/BootstrapThemeAsset.php b/extensions/bootstrap/BootstrapThemeAsset.php deleted file mode 100644 index 60747a9059..0000000000 --- a/extensions/bootstrap/BootstrapThemeAsset.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @since 2.0 - */ -class BootstrapThemeAsset extends AssetBundle -{ - public $sourcePath = '@bower/bootstrap/dist'; - public $css = [ - 'css/bootstrap-theme.css', - ]; - public $depends = [ - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/extensions/bootstrap/Modal.php b/extensions/bootstrap/Modal.php deleted file mode 100644 index ccb62299bb..0000000000 --- a/extensions/bootstrap/Modal.php +++ /dev/null @@ -1,251 +0,0 @@ - '

    Hello world

    ', - * 'toggleButton' => ['label' => 'click me'], - * ]); - * - * echo 'Say hello...'; - * - * Modal::end(); - * ~~~ - * - * @see http://getbootstrap.com/javascript/#modals - * @author Antonio Ramirez - * @author Qiang Xue - * @since 2.0 - */ -class Modal extends Widget -{ - const SIZE_LARGE = "modal-lg"; - const SIZE_SMALL = "modal-sm"; - const SIZE_DEFAULT = ""; - - /** - * @var string the header content in the modal window. - */ - public $header; - /** - * @var string additional header options - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - * @since 2.0.1 - */ - public $headerOptions; - /** - * @var string the footer content in the modal window. - */ - public $footer; - /** - * @var string additional footer options - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - * @since 2.0.1 - */ - public $footerOptions; - /** - * @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default. - */ - public $size; - /** - * @var array|false the options for rendering the close button tag. - * The close button is displayed in the header of the modal window. Clicking - * on the button will hide the modal window. If this is false, no close button will be rendered. - * - * The following special options are supported: - * - * - tag: string, the tag name of the button. Defaults to 'button'. - * - label: string, the label of the button. Defaults to '×'. - * - * The rest of the options will be rendered as the HTML attributes of the button tag. - * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) - * for the supported HTML attributes. - */ - public $closeButton = []; - /** - * @var array the options for rendering the toggle button tag. - * The toggle button is used to toggle the visibility of the modal window. - * If this property is false, no toggle button will be rendered. - * - * The following special options are supported: - * - * - tag: string, the tag name of the button. Defaults to 'button'. - * - label: string, the label of the button. Defaults to 'Show'. - * - * The rest of the options will be rendered as the HTML attributes of the button tag. - * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) - * for the supported HTML attributes. - */ - public $toggleButton = false; - - - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - - $this->initOptions(); - - echo $this->renderToggleButton() . "\n"; - echo Html::beginTag('div', $this->options) . "\n"; - echo Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n"; - echo Html::beginTag('div', ['class' => 'modal-content']) . "\n"; - echo $this->renderHeader() . "\n"; - echo $this->renderBodyBegin() . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo "\n" . $this->renderBodyEnd(); - echo "\n" . $this->renderFooter(); - echo "\n" . Html::endTag('div'); // modal-content - echo "\n" . Html::endTag('div'); // modal-dialog - echo "\n" . Html::endTag('div'); - - $this->registerPlugin('modal'); - } - - /** - * Renders the header HTML markup of the modal - * @return string the rendering result - */ - protected function renderHeader() - { - $button = $this->renderCloseButton(); - if ($button !== null) { - $this->header = $button . "\n" . $this->header; - } - if ($this->header !== null) { - Html::addCssClass($this->headerOptions, 'modal-header'); - return Html::tag('div', "\n" . $this->header . "\n", $this->headerOptions); - } else { - return null; - } - } - - /** - * Renders the opening tag of the modal body. - * @return string the rendering result - */ - protected function renderBodyBegin() - { - return Html::beginTag('div', ['class' => 'modal-body']); - } - - /** - * Renders the closing tag of the modal body. - * @return string the rendering result - */ - protected function renderBodyEnd() - { - return Html::endTag('div'); - } - - /** - * Renders the HTML markup for the footer of the modal - * @return string the rendering result - */ - protected function renderFooter() - { - if ($this->footer !== null) { - Html::addCssClass($this->footerOptions, 'modal-footer'); - return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions); - } else { - return null; - } - } - - /** - * Renders the toggle button. - * @return string the rendering result - */ - protected function renderToggleButton() - { - if ($this->toggleButton !== false) { - $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button'); - $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show'); - if ($tag === 'button' && !isset($this->toggleButton['type'])) { - $this->toggleButton['type'] = 'button'; - } - - return Html::tag($tag, $label, $this->toggleButton); - } else { - return null; - } - } - - /** - * Renders the close button. - * @return string the rendering result - */ - protected function renderCloseButton() - { - if ($this->closeButton !== false) { - $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); - $label = ArrayHelper::remove($this->closeButton, 'label', '×'); - if ($tag === 'button' && !isset($this->closeButton['type'])) { - $this->closeButton['type'] = 'button'; - } - - return Html::tag($tag, $label, $this->closeButton); - } else { - return null; - } - } - - /** - * Initializes the widget options. - * This method sets the default values for various options. - */ - protected function initOptions() - { - $this->options = array_merge([ - 'class' => 'fade', - 'role' => 'dialog', - 'tabindex' => -1, - ], $this->options); - Html::addCssClass($this->options, 'modal'); - - if ($this->clientOptions !== false) { - $this->clientOptions = array_merge(['show' => false], $this->clientOptions); - } - - if ($this->closeButton !== false) { - $this->closeButton = array_merge([ - 'data-dismiss' => 'modal', - 'aria-hidden' => 'true', - 'class' => 'close', - ], $this->closeButton); - } - - if ($this->toggleButton !== false) { - $this->toggleButton = array_merge([ - 'data-toggle' => 'modal', - ], $this->toggleButton); - if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { - $this->toggleButton['data-target'] = '#' . $this->options['id']; - } - } - } -} diff --git a/extensions/bootstrap/NavBar.php b/extensions/bootstrap/NavBar.php deleted file mode 100644 index 93c3cb7c52..0000000000 --- a/extensions/bootstrap/NavBar.php +++ /dev/null @@ -1,161 +0,0 @@ - 'NavBar Test']); - * echo Nav::widget([ - * 'items' => [ - * ['label' => 'Home', 'url' => ['/site/index']], - * ['label' => 'About', 'url' => ['/site/about']], - * ], - * ]); - * NavBar::end(); - * ``` - * - * @see http://getbootstrap.com/components/#navbar - * @author Antonio Ramirez - * @author Alexander Kochetov - * @since 2.0 - */ -class NavBar extends Widget -{ - /** - * @var array the HTML attributes for the widget container tag. The following special options are recognized: - * - * - tag: string, defaults to "nav", the name of the container tag. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $options = []; - /** - * @var array the HTML attributes for the container tag. The following special options are recognized: - * - * - tag: string, defaults to "div", the name of the container tag. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $containerOptions = []; - /** - * @var string|boolean the text of the brand of false if it's not used. Note that this is not HTML-encoded. - * @see http://getbootstrap.com/components/#navbar - */ - public $brandLabel = false; - /** - * @param array|string|boolean $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Url::to()]] - * and will be used for the "href" attribute of the brand link. Default value is false that means - * [[\yii\web\Application::homeUrl]] will be used. - */ - public $brandUrl = false; - /** - * @var array the HTML attributes of the brand link. - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $brandOptions = []; - /** - * @var string text to show for screen readers for the button to toggle the navbar. - */ - public $screenReaderToggleText = 'Toggle navigation'; - /** - * @var boolean whether the navbar content should be included in an inner div container which by default - * adds left and right padding. Set this to false for a 100% width navbar. - */ - public $renderInnerContainer = true; - /** - * @var array the HTML attributes of the inner container. - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $innerContainerOptions = []; - - - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - $this->clientOptions = false; - Html::addCssClass($this->options, 'navbar'); - if ($this->options['class'] === 'navbar') { - Html::addCssClass($this->options, 'navbar-default'); - } - Html::addCssClass($this->brandOptions, 'navbar-brand'); - if (empty($this->options['role'])) { - $this->options['role'] = 'navigation'; - } - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'nav'); - echo Html::beginTag($tag, $options); - if ($this->renderInnerContainer) { - if (!isset($this->innerContainerOptions['class'])) { - Html::addCssClass($this->innerContainerOptions, 'container'); - } - echo Html::beginTag('div', $this->innerContainerOptions); - } - echo Html::beginTag('div', ['class' => 'navbar-header']); - if (!isset($this->containerOptions['id'])) { - $this->containerOptions['id'] = "{$this->options['id']}-collapse"; - } - echo $this->renderToggleButton(); - if ($this->brandLabel !== false) { - Html::addCssClass($this->brandOptions, 'navbar-brand'); - echo Html::a($this->brandLabel, $this->brandUrl === false ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions); - } - echo Html::endTag('div'); - Html::addCssClass($this->containerOptions, 'collapse'); - Html::addCssClass($this->containerOptions, 'navbar-collapse'); - $options = $this->containerOptions; - $tag = ArrayHelper::remove($options, 'tag', 'div'); - echo Html::beginTag($tag, $options); - } - - /** - * Renders the widget. - */ - public function run() - { - $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div'); - echo Html::endTag($tag); - if ($this->renderInnerContainer) { - echo Html::endTag('div'); - } - $tag = ArrayHelper::remove($this->options, 'tag', 'nav'); - echo Html::endTag($tag, $this->options); - BootstrapPluginAsset::register($this->getView()); - } - - /** - * Renders collapsible toggle button. - * @return string the rendering toggle button. - */ - protected function renderToggleButton() - { - $bar = Html::tag('span', '', ['class' => 'icon-bar']); - $screenReader = "{$this->screenReaderToggleText}"; - - return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [ - 'class' => 'navbar-toggle', - 'data-toggle' => 'collapse', - 'data-target' => "#{$this->containerOptions['id']}", - ]); - } -} diff --git a/extensions/bootstrap/composer.json b/extensions/bootstrap/composer.json deleted file mode 100644 index 4a5da0d46b..0000000000 --- a/extensions/bootstrap/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "yiisoft/yii2-bootstrap", - "description": "The Twitter Bootstrap extension for the Yii framework", - "keywords": ["yii2", "bootstrap"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Abootstrap", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*" - }, - "autoload": { - "psr-4": { - "yii\\bootstrap\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/codeception/BasePage.php b/extensions/codeception/BasePage.php deleted file mode 100644 index 133632557c..0000000000 --- a/extensions/codeception/BasePage.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @since 2.0 - */ -abstract class BasePage extends Component -{ - /** - * @var string|array the route (controller ID and action ID, e.g. `site/about`) to this page. - * Use array to represent a route with GET parameters. The first element of the array represents - * the route and the rest of the name-value pairs are treated as GET parameters, e.g. `array('site/page', 'name' => 'about')`. - */ - public $route; - - /** - * @var \Codeception\Actor the testing guy object - */ - protected $actor; - - - /** - * Constructor. - * - * @param \Codeception\Actor $I the testing guy object - */ - public function __construct($I) - { - $this->actor = $I; - } - - /** - * Returns the URL to this page. - * The URL will be returned by calling the URL manager of the application - * with [[route]] and the provided parameters. - * @param array $params the GET parameters for creating the URL - * @return string the URL to this page - * @throws InvalidConfigException if [[route]] is not set or invalid - */ - public function getUrl($params = []) - { - if (is_string($this->route)) { - $params[0] = $this->route; - - return Yii::$app->getUrlManager()->createUrl($params); - } elseif (is_array($this->route) && isset($this->route[0])) { - return Yii::$app->getUrlManager()->createUrl(array_merge($this->route, $params)); - } else { - throw new InvalidConfigException('The "route" property must be set.'); - } - } - - /** - * Creates a page instance and sets the test guy to use [[url]]. - * @param \Codeception\Actor $I the test guy instance - * @param array $params the GET parameters to be used to generate [[url]] - * @return static the page instance - */ - public static function openBy($I, $params = []) - { - $page = new static($I); - $I->amOnPage($page->getUrl($params)); - - return $page; - } -} diff --git a/extensions/codeception/DbTestCase.php b/extensions/codeception/DbTestCase.php deleted file mode 100644 index c5e3cf9a76..0000000000 --- a/extensions/codeception/DbTestCase.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class DbTestCase extends TestCase -{ - /** - * @inheritdoc - */ - public function globalFixtures() - { - return [ - InitDbFixture::className(), - ]; - } -} diff --git a/extensions/codeception/LICENSE.md b/extensions/codeception/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/codeception/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/codeception/composer.json b/extensions/codeception/composer.json deleted file mode 100644 index da0fb8ece5..0000000000 --- a/extensions/codeception/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "yiisoft/yii2-codeception", - "description": "The Codeception integration for the Yii framework", - "keywords": ["yii2", "codeception"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Mark Jebri", - "email": "mark.github@yandex.ru" - } - ], - "require": { - "yiisoft/yii2": "*" - }, - "autoload": { - "psr-4": { "yii\\codeception\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/composer/LICENSE.md b/extensions/composer/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/composer/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/composer/Plugin.php b/extensions/composer/Plugin.php deleted file mode 100644 index 3ddf277eb5..0000000000 --- a/extensions/composer/Plugin.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @since 2.0 - */ -class Plugin implements PluginInterface -{ - /** - * @inheritdoc - */ - public function activate(Composer $composer, IOInterface $io) - { - $installer = new Installer($io, $composer); - $composer->getInstallationManager()->addInstaller($installer); - $file = rtrim($composer->getConfig()->get('vendor-dir'), '/') . '/yiisoft/extensions.php'; - if (!is_file($file)) { - @mkdir(dirname($file), 0777, true); - file_put_contents($file, " - * @since 2.0 - */ -class DebugAsset extends AssetBundle -{ - public $sourcePath = '@yii/debug/assets'; - public $css = [ - 'main.css', - 'toolbar.css', - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/extensions/debug/LogTarget.php b/extensions/debug/LogTarget.php deleted file mode 100644 index 84c6278cfa..0000000000 --- a/extensions/debug/LogTarget.php +++ /dev/null @@ -1,173 +0,0 @@ - - * @since 2.0 - */ -class LogTarget extends Target -{ - /** - * @var Module - */ - public $module; - public $tag; - - - /** - * @param \yii\debug\Module $module - * @param array $config - */ - public function __construct($module, $config = []) - { - parent::__construct($config); - $this->module = $module; - $this->tag = uniqid(); - } - - /** - * Exports log messages to a specific destination. - * Child classes must implement this method. - */ - public function export() - { - $path = $this->module->dataPath; - if (!is_dir($path)) { - mkdir($path); - } - - $summary = $this->collectSummary(); - $dataFile = "$path/{$this->tag}.data"; - $data = []; - foreach ($this->module->panels as $id => $panel) { - $data[$id] = $panel->save(); - } - $data['summary'] = $summary; - file_put_contents($dataFile, serialize($data)); - - $indexFile = "$path/index.data"; - $this->updateIndexFile($indexFile, $summary); - } - - /** - * Updates index file with summary log data - * - * @param string $indexFile path to index file - * @param array $summary summary log data - * @throws \yii\base\InvalidConfigException - */ - private function updateIndexFile($indexFile, $summary) - { - touch($indexFile); - if (($fp = @fopen($indexFile, 'r+')) === false) { - throw new InvalidConfigException("Unable to open debug data index file: $indexFile"); - } - @flock($fp, LOCK_EX); - $manifest = ''; - while (($buffer = fgets($fp)) !== false) { - $manifest .= $buffer; - } - if (!feof($fp) || empty($manifest)) { - // error while reading index data, ignore and create new - $manifest = []; - } else { - $manifest = unserialize($manifest); - } - - $manifest[$this->tag] = $summary; - $this->gc($manifest); - - ftruncate($fp, 0); - rewind($fp); - fwrite($fp, serialize($manifest)); - - @flock($fp, LOCK_UN); - @fclose($fp); - } - - /** - * Processes the given log messages. - * This method will filter the given messages with [[levels]] and [[categories]]. - * And if requested, it will also export the filtering result to specific medium (e.g. email). - * @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure - * of each message. - * @param boolean $final whether this method is called at the end of the current application - */ - public function collect($messages, $final) - { - $this->messages = array_merge($this->messages, $messages); - if ($final) { - $this->export($this->messages); - } - } - - protected function gc(&$manifest) - { - if (count($manifest) > $this->module->historySize + 10) { - $n = count($manifest) - $this->module->historySize; - foreach (array_keys($manifest) as $tag) { - $file = $this->module->dataPath . "/$tag.data"; - @unlink($file); - unset($manifest[$tag]); - if (--$n <= 0) { - break; - } - } - } - } - - /** - * Collects summary data of current request. - * @return array - */ - protected function collectSummary() - { - $request = Yii::$app->getRequest(); - $response = Yii::$app->getResponse(); - $summary = [ - 'tag' => $this->tag, - 'url' => $request->getAbsoluteUrl(), - 'ajax' => (int) $request->getIsAjax(), - 'method' => $request->getMethod(), - 'ip' => $request->getUserIP(), - 'time' => time(), - 'statusCode' => $response->statusCode, - 'sqlCount' => $this->getSqlTotalCount(), - ]; - - if (isset($this->module->panels['mail'])) { - $summary['mailCount'] = count($this->module->panels['mail']->getMessages()); - } - - return $summary; - } - - /** - * Returns total sql count executed in current request. If database panel is not configured - * returns 0. - * @return integer - */ - protected function getSqlTotalCount() - { - if (!isset($this->module->panels['db'])) { - return 0; - } - $profileLogs = $this->module->panels['db']->getProfileLogs(); - - # / 2 because messages are in couple (begin/end) - - return count($profileLogs) / 2; - } -} diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php deleted file mode 100644 index 3a6fa09b85..0000000000 --- a/extensions/debug/Module.php +++ /dev/null @@ -1,219 +0,0 @@ - - * @since 2.0 - */ -class Module extends \yii\base\Module implements BootstrapInterface -{ - /** - * @var array the list of IPs that are allowed to access this module. - * Each array element represents a single IP filter which can be either an IP address - * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. - * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed - * by localhost. - */ - public $allowedIPs = ['127.0.0.1', '::1']; - /** - * @inheritdoc - */ - public $controllerNamespace = 'yii\debug\controllers'; - /** - * @var LogTarget - */ - public $logTarget; - /** - * @var array list of debug panels. The array keys are the panel IDs, and values are the corresponding - * panel class names or configuration arrays. This will be merged with [[corePanels()]]. - * You may reconfigure a core panel via this property by using the same panel ID. - * You may also disable a core panel by setting it to be false in this property. - */ - public $panels = []; - /** - * @var string the directory storing the debugger data files. This can be specified using a path alias. - */ - public $dataPath = '@runtime/debug'; - /** - * @var integer the maximum number of debug data files to keep. If there are more files generated, - * the oldest ones will be removed. - */ - public $historySize = 50; - /** - * @var boolean whether to enable message logging for the requests about debug module actions. - * You normally do not want to keep these logs because they may distract you from the logs about your applications. - * You may want to enable the debug logs if you want to investigate how the debug module itself works. - */ - public $enableDebugLogs = false; - - - /** - * Returns Yii logo ready to use in `'; - /* @var $view View */ - $view = $event->sender; - echo ''; - echo ''; - } - - /** - * Checks if current user is allowed to access the module - * @return boolean if access is granted - */ - protected function checkAccess() - { - $ip = Yii::$app->getRequest()->getUserIP(); - foreach ($this->allowedIPs as $filter) { - if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { - return true; - } - } - Yii::warning('Access to debugger is denied due to IP address restriction. The requesting IP address is ' . $ip, __METHOD__); - return false; - } - - /** - * @return array default set of panels - */ - protected function corePanels() - { - return [ - 'config' => ['class' => 'yii\debug\panels\ConfigPanel'], - 'request' => ['class' => 'yii\debug\panels\RequestPanel'], - 'log' => ['class' => 'yii\debug\panels\LogPanel'], - 'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'], - 'db' => ['class' => 'yii\debug\panels\DbPanel'], - 'assets' => ['class' => 'yii\debug\panels\AssetPanel'], - 'mail' => ['class' => 'yii\debug\panels\MailPanel'], - ]; - } -} diff --git a/extensions/debug/Panel.php b/extensions/debug/Panel.php deleted file mode 100644 index 80cf8451db..0000000000 --- a/extensions/debug/Panel.php +++ /dev/null @@ -1,108 +0,0 @@ - - * @since 2.0 - */ -class Panel extends Component -{ - /** - * @var string panel unique identifier. - * It is set automatically by the container module. - */ - public $id; - /** - * @var string request data set identifier. - */ - public $tag; - /** - * @var Module - */ - public $module; - /** - * @var mixed data associated with panel - */ - public $data; - /** - * @var array array of actions to add to the debug modules default controller. - * This array will be merged with all other panels actions property. - * See [[\yii\base\Controller::actions()]] for the format. - */ - public $actions = []; - - - /** - * @return string name of the panel - */ - public function getName() - { - return ''; - } - - /** - * @return string content that is displayed at debug toolbar - */ - public function getSummary() - { - return ''; - } - - /** - * @return string content that is displayed in debugger detail view - */ - public function getDetail() - { - return ''; - } - - /** - * Saves data to be later used in debugger detail view. - * This method is called on every page where debugger is enabled. - * - * @return mixed data to be saved - */ - public function save() - { - return null; - } - - /** - * Loads data into the panel - * - * @param mixed $data - */ - public function load($data) - { - $this->data = $data; - } - - /** - * @return string URL pointing to panel detail view - */ - public function getUrl() - { - return Url::toRoute(['/' . $this->module->id . '/default/view', - 'panel' => $this->id, - 'tag' => $this->tag, - ]); - } -} diff --git a/extensions/debug/assets/bg.png b/extensions/debug/assets/bg.png deleted file mode 100644 index 459dd78889..0000000000 Binary files a/extensions/debug/assets/bg.png and /dev/null differ diff --git a/extensions/debug/assets/main.css b/extensions/debug/assets/main.css deleted file mode 100644 index 4381555ce3..0000000000 --- a/extensions/debug/assets/main.css +++ /dev/null @@ -1,106 +0,0 @@ -span.indent { - color: #ccc; -} - -ul.trace { - font-size: 12px; - color: #999; - margin: 2px 0 0 0; - padding: 0; - list-style: none; - white-space: normal; -} - -ul.assets { - margin: 2px 0 0 0; - padding: 0; - list-style: none; - white-space: normal; -} - -.callout-danger { - background-color: #fcf2f2; - border-color: #dFb5b4; -} -.callout { - margin: 0 0 10px 0; - padding: 5px; -} - -.list-group .glyphicon { - float: right; -} - -td, th { - white-space: pre-wrap; - word-wrap: break-word; -} - -.request-table td { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - word-break: break-all; -} - -.config-php-info-table td.v { - word-break: break-all; -} - -.not-set { - color: #c55; - font-style: italic; -} - -.detail-grid-view th { - white-space: nowrap; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.mail-sorter { - margin-top: 7px; -} - -.mail-sorter li { - list-style: none; - float: left; - width: 12%; - font-weight: bold; -} - -.nowrap { - white-space: nowrap; -} diff --git a/extensions/debug/assets/toolbar.js b/extensions/debug/assets/toolbar.js deleted file mode 100644 index ef8ee74e73..0000000000 --- a/extensions/debug/assets/toolbar.js +++ /dev/null @@ -1,42 +0,0 @@ -(function () { - var ajax = function (url, settings) { - var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); - settings = settings || {}; - xhr.open(settings.method || 'GET', url, true); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.setRequestHeader('Accept', 'text/html'); - xhr.onreadystatechange = function (state) { - if (xhr.readyState == 4) { - if (xhr.status == 200 && settings.success) { - settings.success(xhr); - } else if (xhr.status != 200 && settings.error) { - settings.error(xhr); - } - } - }; - xhr.send(settings.data || ''); - }; - - var e = document.getElementById('yii-debug-toolbar'); - if (e) { - e.style.display = 'block'; - var url = e.getAttribute('data-url'); - ajax(url, { - success: function (xhr) { - var div = document.createElement('div'); - div.innerHTML = xhr.responseText; - e.parentNode.replaceChild(div, e); - if (window.localStorage) { - var pref = localStorage.getItem('yii-debug-toolbar'); - if (pref == 'minimized') { - document.getElementById('yii-debug-toolbar').style.display = 'none'; - document.getElementById('yii-debug-toolbar-min').style.display = 'block'; - } - } - }, - error: function (xhr) { - e.innerHTML = xhr.responseText; - } - }); - } -})(); diff --git a/extensions/debug/components/search/Filter.php b/extensions/debug/components/search/Filter.php deleted file mode 100644 index c30779ee1f..0000000000 --- a/extensions/debug/components/search/Filter.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @since 2.0 - */ -class Filter extends Component -{ - /** - * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]] - */ - protected $rules = []; - - - /** - * Adds data filtering rule. - * - * @param string $name attribute name - * @param MatcherInterface $rule - */ - public function addMatcher($name, MatcherInterface $rule) - { - if ($rule->hasValue()) { - $this->rules[$name][] = $rule; - } - } - - /** - * Applies filter on a given array and returns filtered data. - * - * @param array $data data to filter - * @return array filtered data - */ - public function filter(array $data) - { - $filtered = []; - - foreach ($data as $row) { - if ($this->passesFilter($row)) { - $filtered[] = $row; - } - } - - return $filtered; - } - - /** - * Checks if the given data satisfies filters. - * - * @param array $row data - * @return boolean if data passed filtering - */ - private function passesFilter(array $row) - { - foreach ($row as $name => $value) { - if (isset($this->rules[$name])) { - // check all rules for a given attribute - foreach ($this->rules[$name] as $rule) { - /* @var $rule MatcherInterface */ - if (!$rule->match($value)) { - return false; - } - } - } - } - - return true; - } -} diff --git a/extensions/debug/components/search/matchers/Base.php b/extensions/debug/components/search/matchers/Base.php deleted file mode 100644 index 4fa4b9b64b..0000000000 --- a/extensions/debug/components/search/matchers/Base.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @since 2.0 - */ -abstract class Base extends Component implements MatcherInterface -{ - /** - * @var mixed base value to check - */ - protected $baseValue; - - - /** - * @inheritdoc - */ - public function setValue($value) - { - $this->baseValue = $value; - } - - /** - * @inheritdoc - */ - public function hasValue() - { - return !empty($this->baseValue) || ($this->baseValue === '0'); - } -} diff --git a/extensions/debug/components/search/matchers/GreaterThan.php b/extensions/debug/components/search/matchers/GreaterThan.php deleted file mode 100644 index abc907baab..0000000000 --- a/extensions/debug/components/search/matchers/GreaterThan.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class GreaterThan extends Base -{ - /** - * @inheritdoc - */ - public function match($value) - { - return ($value > $this->baseValue); - } -} diff --git a/extensions/debug/components/search/matchers/LowerThan.php b/extensions/debug/components/search/matchers/LowerThan.php deleted file mode 100644 index c3d4f32b8e..0000000000 --- a/extensions/debug/components/search/matchers/LowerThan.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class LowerThan extends Base -{ - /** - * @inheritdoc - */ - public function match($value) - { - return ($value < $this->baseValue); - } -} diff --git a/extensions/debug/components/search/matchers/MatcherInterface.php b/extensions/debug/components/search/matchers/MatcherInterface.php deleted file mode 100644 index 88fb3af075..0000000000 --- a/extensions/debug/components/search/matchers/MatcherInterface.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @since 2.0 - */ -interface MatcherInterface -{ - /** - * Checks if the value passed matches base value. - * - * @param mixed $value value to be matched - * @return boolean if there is a match - */ - public function match($value); - - /** - * Sets base value to match against - * - * @param mixed $value - */ - public function setValue($value); - - /** - * Checks if base value is set - * - * @return boolean if base value is set - */ - public function hasValue(); -} diff --git a/extensions/debug/components/search/matchers/SameAs.php b/extensions/debug/components/search/matchers/SameAs.php deleted file mode 100644 index 89c1c5939b..0000000000 --- a/extensions/debug/components/search/matchers/SameAs.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @since 2.0 - */ -class SameAs extends Base -{ - /** - * @var boolean if partial match should be used. - */ - public $partial = false; - - - /** - * @inheritdoc - */ - public function match($value) - { - if ($this->partial) { - return mb_stripos($value, $this->baseValue, 0, \Yii::$app->charset) !== false; - } else { - return strcmp(mb_strtoupper($this->baseValue, \Yii::$app->charset), mb_strtoupper($value, \Yii::$app->charset)) === 0; - } - } -} diff --git a/extensions/debug/composer.json b/extensions/debug/composer.json deleted file mode 100644 index 6edd31af17..0000000000 --- a/extensions/debug/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "yiisoft/yii2-debug", - "description": "The debugger extension for the Yii framework", - "keywords": ["yii2", "debug", "debugger"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Adebug", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "yiisoft/yii2-bootstrap": "*" - }, - "autoload": { - "psr-4": { - "yii\\debug\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/debug/models/search/Base.php b/extensions/debug/models/search/Base.php deleted file mode 100644 index dd20fb8eaa..0000000000 --- a/extensions/debug/models/search/Base.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @since 2.0 - */ -class Base extends Model -{ - /** - * Adds filtering condition for a given attribute - * - * @param Filter $filter filter instance - * @param string $attribute attribute to filter - * @param boolean $partial if partial match should be used - */ - public function addCondition(Filter $filter, $attribute, $partial = false) - { - $value = $this->$attribute; - - if (mb_strpos($value, '>') !== false) { - $value = intval(str_replace('>', '', $value)); - $filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value])); - - } elseif (mb_strpos($value, '<') !== false) { - $value = intval(str_replace('<', '', $value)); - $filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value])); - } else { - $filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial])); - } - } -} diff --git a/extensions/debug/models/search/Db.php b/extensions/debug/models/search/Db.php deleted file mode 100644 index 771fb9f142..0000000000 --- a/extensions/debug/models/search/Db.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @author Mark Jebri - * @since 2.0 - */ -class Db extends Base -{ - /** - * @var string type of the input search value - */ - public $type; - /** - * @var integer query attribute input search value - */ - public $query; - - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['type', 'query'], 'safe'], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'type' => 'Type', - 'query' => 'Query', - ]; - } - - /** - * Returns data provider with filled models. Filter applied if needed. - * - * @param array $params an array of parameter values indexed by parameter names - * @param array $models data to return provider for - * @return \yii\data\ArrayDataProvider - */ - public function search($params, $models) - { - $dataProvider = new ArrayDataProvider([ - 'allModels' => $models, - 'pagination' => false, - 'sort' => [ - 'attributes' => ['duration', 'seq', 'type', 'query'], - 'defaultOrder' => [ - 'duration' => SORT_DESC, - ], - ], - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - $filter = new Filter(); - $this->addCondition($filter, 'type', true); - $this->addCondition($filter, 'query', true); - $dataProvider->allModels = $filter->filter($models); - - return $dataProvider; - } -} diff --git a/extensions/debug/models/search/Debug.php b/extensions/debug/models/search/Debug.php deleted file mode 100644 index 1f496c8b65..0000000000 --- a/extensions/debug/models/search/Debug.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @author Mark Jebri - * @since 2.0 - */ -class Debug extends Base -{ - /** - * @var string tag attribute input search value - */ - public $tag; - /** - * @var string ip attribute input search value - */ - public $ip; - /** - * @var string method attribute input search value - */ - public $method; - /** - * @var integer ajax attribute input search value - */ - public $ajax; - /** - * @var string url attribute input search value - */ - public $url; - /** - * @var string status code attribute input search value - */ - public $statusCode; - /** - * @var integer sql count attribute input search value - */ - public $sqlCount; - /** - * @var integer total mail count attribute input search value - */ - public $mailCount; - /** - * @var array critical codes, used to determine grid row options. - */ - public $criticalCodes = [400, 404, 500]; - - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'tag' => 'Tag', - 'ip' => 'Ip', - 'method' => 'Method', - 'ajax' => 'Ajax', - 'url' => 'url', - 'statusCode' => 'Status code', - 'sqlCount' => 'Query Count', - 'mailCount' => 'Mail Count', - ]; - } - - /** - * Returns data provider with filled models. Filter applied if needed. - * @param array $params an array of parameter values indexed by parameter names - * @param array $models data to return provider for - * @return \yii\data\ArrayDataProvider - */ - public function search($params, $models) - { - $dataProvider = new ArrayDataProvider([ - 'allModels' => $models, - 'sort' => [ - 'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'], - ], - 'pagination' => [ - 'pageSize' => 50, - ], - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - $filter = new Filter(); - $this->addCondition($filter, 'tag', true); - $this->addCondition($filter, 'ip', true); - $this->addCondition($filter, 'method'); - $this->addCondition($filter, 'ajax'); - $this->addCondition($filter, 'url', true); - $this->addCondition($filter, 'statusCode'); - $this->addCondition($filter, 'sqlCount'); - $this->addCondition($filter, 'mailCount'); - $dataProvider->allModels = $filter->filter($models); - - return $dataProvider; - } - - /** - * Checks if code is critical. - * - * @param integer $code - * @return boolean - */ - public function isCodeCritical($code) - { - return in_array($code, $this->criticalCodes); - } -} diff --git a/extensions/debug/models/search/Log.php b/extensions/debug/models/search/Log.php deleted file mode 100644 index fe9dcf4f71..0000000000 --- a/extensions/debug/models/search/Log.php +++ /dev/null @@ -1,87 +0,0 @@ - - * @author Mark Jebri - * @since 2.0 - */ -class Log extends Base -{ - /** - * @var string ip attribute input search value - */ - public $level; - /** - * @var string method attribute input search value - */ - public $category; - /** - * @var integer message attribute input search value - */ - public $message; - - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['level', 'message', 'category'], 'safe'], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'level' => 'Level', - 'category' => 'Category', - 'message' => 'Message', - ]; - } - - /** - * Returns data provider with filled models. Filter applied if needed. - * - * @param array $params an array of parameter values indexed by parameter names - * @param array $models data to return provider for - * @return \yii\data\ArrayDataProvider - */ - public function search($params, $models) - { - $dataProvider = new ArrayDataProvider([ - 'allModels' => $models, - 'pagination' => false, - 'sort' => [ - 'attributes' => ['time', 'level', 'category', 'message'], - ], - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - $filter = new Filter(); - $this->addCondition($filter, 'level'); - $this->addCondition($filter, 'category', true); - $this->addCondition($filter, 'message', true); - $dataProvider->allModels = $filter->filter($models); - - return $dataProvider; - } -} diff --git a/extensions/debug/models/search/Mail.php b/extensions/debug/models/search/Mail.php deleted file mode 100644 index f30076a802..0000000000 --- a/extensions/debug/models/search/Mail.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @since 2.0 - */ -class Mail extends Base -{ - /** - * @var string from attribute input search value - */ - public $from; - /** - * @var string to attribute input search value - */ - public $to; - /** - * @var string reply attribute input search value - */ - public $reply; - /** - * @var string cc attribute input search value - */ - public $cc; - /** - * @var string bcc attribute input search value - */ - public $bcc; - /** - * @var string subject attribute input search value - */ - public $subject; - /** - * @var string body attribute input search value - */ - public $body; - /** - * @var string charset attribute input search value - */ - public $charset; - /** - * @var string headers attribute input search value - */ - public $headers; - /** - * @var string file attribute input search value - */ - public $file; - - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'from' => 'From', - 'to' => 'To', - 'reply' => 'Reply', - 'cc' => 'Copy receiver', - 'bcc' => 'Hidden copy receiver', - 'subject' => 'Subject', - 'charset' => 'Charset' - ]; - } - - /** - * Returns data provider with filled models. Filter applied if needed. - * @param array $params - * @param array $models - * @return \yii\data\ArrayDataProvider - */ - public function search($params, $models) - { - $dataProvider = new ArrayDataProvider([ - 'allModels' => $models, - 'pagination' => [ - 'pageSize' => 20, - ], - 'sort' => [ - 'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], - ], - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - $filter = new Filter(); - $this->addCondition($filter, 'from', true); - $this->addCondition($filter, 'to', true); - $this->addCondition($filter, 'reply', true); - $this->addCondition($filter, 'cc', true); - $this->addCondition($filter, 'bcc', true); - $this->addCondition($filter, 'subject', true); - $this->addCondition($filter, 'body', true); - $this->addCondition($filter, 'charset', true); - $dataProvider->allModels = $filter->filter($models); - - return $dataProvider; - } -} diff --git a/extensions/debug/models/search/Profile.php b/extensions/debug/models/search/Profile.php deleted file mode 100644 index 85f713c550..0000000000 --- a/extensions/debug/models/search/Profile.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @author Mark Jebri - * @since 2.0 - */ -class Profile extends Base -{ - /** - * @var string method attribute input search value - */ - public $category; - /** - * @var integer info attribute input search value - */ - public $info; - - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['category', 'info'], 'safe'], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'category' => 'Category', - 'info' => 'Info', - ]; - } - - /** - * Returns data provider with filled models. Filter applied if needed. - * - * @param array $params an array of parameter values indexed by parameter names - * @param array $models data to return provider for - * @return \yii\data\ArrayDataProvider - */ - public function search($params, $models) - { - $dataProvider = new ArrayDataProvider([ - 'allModels' => $models, - 'pagination' => false, - 'sort' => [ - 'attributes' => ['category', 'seq', 'duration', 'info'], - 'defaultOrder' => [ - 'seq' => SORT_ASC, - ], - ], - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - $filter = new Filter(); - $this->addCondition($filter, 'category', true); - $this->addCondition($filter, 'info', true); - $dataProvider->allModels = $filter->filter($models); - - return $dataProvider; - } -} diff --git a/extensions/debug/panels/AssetPanel.php b/extensions/debug/panels/AssetPanel.php deleted file mode 100644 index 80a6e7bf6f..0000000000 --- a/extensions/debug/panels/AssetPanel.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @since 2.0 - */ -class AssetPanel extends Panel -{ - /** - * @inheritdoc - */ - public function getName() - { - return 'Asset Bundles'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - return Yii::$app->view->render('panels/assets/summary', ['panel' => $this]); - } - - /** - * @inheritdoc - */ - public function getDetail() - { - return Yii::$app->view->render('panels/assets/detail', ['panel' => $this]); - } - - /** - * @inheritdoc - */ - public function save() - { - $bundles = Yii::$app->view->assetManager->bundles; - if (empty($bundles)) { // bundles can be false - return []; - } - $data = []; - foreach ($bundles as $name => $bundle) { - if ($bundle instanceof AssetBundle) { - $bundleData = (array) $bundle; - if (isset($bundleData['publishOptions']['beforeCopy']) && $bundleData['publishOptions']['beforeCopy'] instanceof \Closure) { - $bundleData['publishOptions']['beforeCopy'] = '\Closure'; - } - if (isset($bundleData['publishOptions']['afterCopy']) && $bundleData['publishOptions']['afterCopy'] instanceof \Closure) { - $bundleData['publishOptions']['afterCopy'] = '\Closure'; - } - $data[$name] = $bundleData; - } - } - return $data; - } - - /** - * Additional formatting for view. - * - * @param AssetBundle[] $bundles Array of bundles to formatting. - * - * @return AssetManager - */ - protected function format(array $bundles) - { - foreach ($bundles as $bundle) { - - $this->cssCount += count($bundle->css); - $this->jsCount += count($bundle->js); - - array_walk($bundle->css, function(&$file, $key, $userdata) { - $file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']); - }, $bundle); - - array_walk($bundle->js, function(&$file, $key, $userdata) { - $file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']); - }, $bundle); - - array_walk($bundle->depends, function(&$depend) { - $depend = Html::a($depend, '#' . $depend); - }); - - $this->formatOptions($bundle->publishOptions); - $this->formatOptions($bundle->jsOptions); - $this->formatOptions($bundle->cssOptions); - } - - return $bundles; - } - - /** - * Format associative array of params to simple value. - * - * @param array $params - * - * @return array - */ - protected function formatOptions(array &$params) - { - if (!is_array($params)) { - return $params; - } - - foreach ($params as $param => $value) { - $params[$param] = Html::tag('strong', '\'' . $param . '\' => ') . (string) $value; - } - - return $params; - } -} diff --git a/extensions/debug/panels/LogPanel.php b/extensions/debug/panels/LogPanel.php deleted file mode 100644 index f7e5bb1f55..0000000000 --- a/extensions/debug/panels/LogPanel.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @since 2.0 - */ -class LogPanel extends Panel -{ - /** - * @var array log messages extracted to array as models, to use with data provider. - */ - private $_models; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Logs'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]); - } - - /** - * @inheritdoc - */ - public function getDetail() - { - $searchModel = new Log(); - $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); - - return Yii::$app->view->render('panels/log/detail', [ - 'dataProvider' => $dataProvider, - 'panel' => $this, - 'searchModel' => $searchModel, - ]); - } - - /** - * @inheritdoc - */ - public function save() - { - $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); - return ['messages' => $messages]; - } - - /** - * Returns an array of models that represents logs of the current request. - * Can be used with data providers, such as \yii\data\ArrayDataProvider. - * - * @param boolean $refresh if need to build models from log messages and refresh them. - * @return array models - */ - protected function getModels($refresh = false) - { - if ($this->_models === null || $refresh) { - $this->_models = []; - - foreach ($this->data['messages'] as $message) { - $this->_models[] = [ - 'message' => $message[0], - 'level' => $message[1], - 'category' => $message[2], - 'time' => ($message[3] * 1000), // time in milliseconds - 'trace' => $message[4] - ]; - } - } - - return $this->_models; - } -} diff --git a/extensions/debug/panels/MailPanel.php b/extensions/debug/panels/MailPanel.php deleted file mode 100644 index d6daf027f3..0000000000 --- a/extensions/debug/panels/MailPanel.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @since 2.0 - */ -class MailPanel extends Panel -{ - /** - * @var string path where all emails will be saved. should be an alias. - */ - public $mailPath = '@runtime/debug/mail'; - - /** - * @var array current request sent messages - */ - private $_messages = []; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - Event::on(BaseMailer::className(), BaseMailer::EVENT_AFTER_SEND, function ($event) { - - /* @var $message MessageInterface */ - $message = $event->message; - $messageData = [ - 'isSuccessful' => $event->isSuccessful, - 'from' => $this->convertParams($message->getFrom()), - 'to' => $this->convertParams($message->getTo()), - 'reply' => $this->convertParams($message->getReplyTo()), - 'cc' => $this->convertParams($message->getCc()), - 'bcc' => $this->convertParams($message->getBcc()), - 'subject' => $message->getSubject(), - 'charset' => $message->getCharset(), - ]; - - // add more information when message is a SwiftMailer message - if ($message instanceof \yii\swiftmailer\Message) { - /* @var $swiftMessage \Swift_Message */ - $swiftMessage = $message->getSwiftMessage(); - - $body = $swiftMessage->getBody(); - if (empty($body)) { - $parts = $swiftMessage->getChildren(); - foreach ($parts as $part) { - if (!($part instanceof \Swift_Mime_Attachment)) { - /* @var $part \Swift_Mime_MimePart */ - if ($part->getContentType() == 'text/plain') { - $messageData['charset'] = $part->getCharset(); - $body = $part->getBody(); - break; - } - } - } - } - - $messageData['body'] = $body; - $messageData['time'] = $swiftMessage->getDate(); - $messageData['headers'] = $swiftMessage->getHeaders(); - - } - - // store message as file - $fileName = $event->sender->generateMessageFileName(); - FileHelper::createDirectory(Yii::getAlias($this->mailPath)); - file_put_contents(Yii::getAlias($this->mailPath) . '/' . $fileName, $message->toString()); - $messageData['file'] = $fileName; - - $this->_messages[] = $messageData; - }); - } - - /** - * @inheritdoc - */ - public function getName() - { - return 'Mail'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - return Yii::$app->view->render('panels/mail/summary', ['panel' => $this, 'mailCount' => count($this->data)]); - } - - /** - * @inheritdoc - */ - public function getDetail() - { - $searchModel = new Mail(); - $dataProvider = $searchModel->search(Yii::$app->request->get(), $this->data); - - return Yii::$app->view->render('panels/mail/detail', [ - 'panel' => $this, - 'dataProvider' => $dataProvider, - 'searchModel' => $searchModel - ]); - } - - /** - * @inheritdoc - */ - public function save() - { - return $this->getMessages(); - } - - /** - * Returns info about messages of current request. Each element is array holding - * message info, such as: time, reply, bc, cc, from, to and other. - * @return array messages - */ - public function getMessages() - { - return $this->_messages; - } - - private function convertParams($attr) - { - if (is_array($attr)) { - $attr = implode(', ', array_keys($attr)); - } - - return $attr; - } -} diff --git a/extensions/debug/panels/ProfilingPanel.php b/extensions/debug/panels/ProfilingPanel.php deleted file mode 100644 index 16f1c9a787..0000000000 --- a/extensions/debug/panels/ProfilingPanel.php +++ /dev/null @@ -1,104 +0,0 @@ - - * @since 2.0 - */ -class ProfilingPanel extends Panel -{ - /** - * @var array current request profile timings - */ - private $_models; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Profiling'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - return Yii::$app->view->render('panels/profile/summary', [ - 'memory' => sprintf('%.1f MB', $this->data['memory'] / 1048576), - 'time' => number_format($this->data['time'] * 1000) . ' ms', - 'panel' => $this - ]); - } - - /** - * @inheritdoc - */ - public function getDetail() - { - $searchModel = new Profile(); - $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); - - return Yii::$app->view->render('panels/profile/detail', [ - 'panel' => $this, - 'dataProvider' => $dataProvider, - 'searchModel' => $searchModel, - 'memory' => sprintf('%.1f MB', $this->data['memory'] / 1048576), - 'time' => number_format($this->data['time'] * 1000) . ' ms', - ]); - } - - /** - * @inheritdoc - */ - public function save() - { - $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); - return [ - 'memory' => memory_get_peak_usage(), - 'time' => microtime(true) - YII_BEGIN_TIME, - 'messages' => $messages, - ]; - } - - /** - * Returns array of profiling models that can be used in a data provider. - * @return array models - */ - protected function getModels() - { - if ($this->_models === null) { - $this->_models = []; - $timings = Yii::getLogger()->calculateTimings($this->data['messages']); - - foreach ($timings as $seq => $profileTiming) { - $this->_models[] = [ - 'duration' => $profileTiming['duration'] * 1000, // in milliseconds - 'category' => $profileTiming['category'], - 'info' => $profileTiming['info'], - 'level' => $profileTiming['level'], - 'timestamp' => $profileTiming['timestamp'] * 1000, //in milliseconds - 'seq' => $seq, - ]; - } - } - - return $this->_models; - } -} diff --git a/extensions/debug/panels/RequestPanel.php b/extensions/debug/panels/RequestPanel.php deleted file mode 100644 index d67ea19632..0000000000 --- a/extensions/debug/panels/RequestPanel.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @since 2.0 - */ -class RequestPanel extends Panel -{ - /** - * @inheritdoc - */ - public function getName() - { - return 'Request'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - return Yii::$app->view->render('panels/request/summary', ['panel' => $this]); - } - - /** - * @inheritdoc - */ - public function getDetail() - { - return Yii::$app->view->render('panels/request/detail', ['panel' => $this]); - } - - /** - * @inheritdoc - */ - public function save() - { - $headers = Yii::$app->getRequest()->getHeaders(); - $requestHeaders = []; - foreach ($headers as $name => $value) { - if (is_array($value) && count($value) == 1) { - $requestHeaders[$name] = current($value); - } else { - $requestHeaders[$name] = $value; - } - } - - $responseHeaders = []; - foreach (headers_list() as $header) { - if (($pos = strpos($header, ':')) !== false) { - $name = substr($header, 0, $pos); - $value = trim(substr($header, $pos + 1)); - if (isset($responseHeaders[$name])) { - if (!is_array($responseHeaders[$name])) { - $responseHeaders[$name] = [$responseHeaders[$name], $value]; - } else { - $responseHeaders[$name][] = $value; - } - } else { - $responseHeaders[$name] = $value; - } - } else { - $responseHeaders[] = $header; - } - } - if (Yii::$app->requestedAction) { - if (Yii::$app->requestedAction instanceof InlineAction) { - $action = get_class(Yii::$app->requestedAction->controller) . '::' . Yii::$app->requestedAction->actionMethod . '()'; - } else { - $action = get_class(Yii::$app->requestedAction) . '::run()'; - } - } else { - $action = null; - } - - return [ - 'flashes' => $this->getFlashes(), - 'statusCode' => Yii::$app->getResponse()->getStatusCode(), - 'requestHeaders' => $requestHeaders, - 'responseHeaders' => $responseHeaders, - 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, - 'action' => $action, - 'actionParams' => Yii::$app->requestedParams, - 'requestBody' => Yii::$app->getRequest()->getRawBody() == '' ? [] : [ - 'Content Type' => Yii::$app->getRequest()->getContentType(), - 'Raw' => Yii::$app->getRequest()->getRawBody(), - 'Decoded to Params' => Yii::$app->getRequest()->getBodyParams(), - ], - 'SERVER' => empty($_SERVER) ? [] : $_SERVER, - 'GET' => empty($_GET) ? [] : $_GET, - 'POST' => empty($_POST) ? [] : $_POST, - 'COOKIE' => empty($_COOKIE) ? [] : $_COOKIE, - 'FILES' => empty($_FILES) ? [] : $_FILES, - 'SESSION' => empty($_SESSION) ? [] : $_SESSION, - ]; - } - - /** - * Getting flash messages without deleting them or touching deletion counters - * - * @return array flash messages (key => message). - */ - protected function getFlashes() - { - /* @var $session \yii\web\Session */ - $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null; - if ($session === null) { - return []; - } - - $counters = $session->get($session->flashParam, []); - $flashes = []; - foreach (array_keys($counters) as $key) { - if (array_key_exists($key, $_SESSION)) { - $flashes[$key] = $_SESSION[$key]; - } - } - return $flashes; - } -} diff --git a/extensions/debug/views/default/panels/assets/detail.php b/extensions/debug/views/default/panels/assets/detail.php deleted file mode 100644 index 1628309206..0000000000 --- a/extensions/debug/views/default/panels/assets/detail.php +++ /dev/null @@ -1,70 +0,0 @@ - -

    Asset Bundles

    - -data)) { - echo '

    No asset bundle was used.

    '; - return; -} ?> -
    - - - data as $name => $bundle) { - ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    Total data) ?> asset bundles were loaded.

    -

    sourcePath
    basePath
    baseUrl
    css 'assets']) ?>
    js 'assets']) ?>
    depends
      - -
    • - -
    -
    diff --git a/extensions/debug/views/default/panels/assets/summary.php b/extensions/debug/views/default/panels/assets/summary.php deleted file mode 100644 index 945dcf1d38..0000000000 --- a/extensions/debug/views/default/panels/assets/summary.php +++ /dev/null @@ -1,8 +0,0 @@ -data)): -?> - - diff --git a/extensions/debug/views/default/panels/config/detail.php b/extensions/debug/views/default/panels/config/detail.php deleted file mode 100644 index 3febbdb44a..0000000000 --- a/extensions/debug/views/default/panels/config/detail.php +++ /dev/null @@ -1,35 +0,0 @@ -getExtensions(); -?> -

    Configuration

    - -render('table', [ - 'caption' => 'Application Configuration', - 'values' => [ - 'Yii Version' => $panel->data['application']['yii'], - 'Application Name' => $panel->data['application']['name'], - 'Environment' => $panel->data['application']['env'], - 'Debug Mode' => $panel->data['application']['debug'] ? 'Yes' : 'No', - ], -]); - -if (!empty($extensions)) { - echo $this->render('table', [ - 'caption' => 'Installed Extensions', - 'values' => $extensions, - ]); -} - -echo $this->render('table', [ - 'caption' => 'PHP Configuration', - 'values' => [ - 'PHP Version' => $panel->data['php']['version'], - 'Xdebug' => $panel->data['php']['xdebug'] ? 'Enabled' : 'Disabled', - 'APC' => $panel->data['php']['apc'] ? 'Enabled' : 'Disabled', - 'Memcache' => $panel->data['php']['memcache'] ? 'Enabled' : 'Disabled', - ], -]); - -echo $panel->getPhpInfo(); diff --git a/extensions/debug/views/default/panels/config/summary.php b/extensions/debug/views/default/panels/config/summary.php deleted file mode 100644 index 07846f372e..0000000000 --- a/extensions/debug/views/default/panels/config/summary.php +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/extensions/debug/views/default/panels/config/table.php b/extensions/debug/views/default/panels/config/table.php deleted file mode 100644 index 17c40e3bc4..0000000000 --- a/extensions/debug/views/default/panels/config/table.php +++ /dev/null @@ -1,33 +0,0 @@ - - -

    - - - -

    Empty.

    - - -
    - - - - - - - - - $value): ?> - - - - - - -
    NameValue
    -
    - diff --git a/extensions/debug/views/default/panels/db/summary.php b/extensions/debug/views/default/panels/db/summary.php deleted file mode 100644 index 91b5ec71ec..0000000000 --- a/extensions/debug/views/default/panels/db/summary.php +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/extensions/debug/views/default/panels/log/detail.php b/extensions/debug/views/default/panels/log/detail.php deleted file mode 100644 index ecea870295..0000000000 --- a/extensions/debug/views/default/panels/log/detail.php +++ /dev/null @@ -1,76 +0,0 @@ - -

    Log Messages

    - $dataProvider, - 'id' => 'log-panel-detailed-grid', - 'options' => ['class' => 'detail-grid-view table-responsive'], - 'filterModel' => $searchModel, - 'filterUrl' => $panel->getUrl(), - 'rowOptions' => function ($model, $key, $index, $grid) { - switch ($model['level']) { - case Logger::LEVEL_ERROR : return ['class' => 'danger']; - case Logger::LEVEL_WARNING : return ['class' => 'warning']; - case Logger::LEVEL_INFO : return ['class' => 'success']; - default: return []; - } - }, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - [ - 'attribute' => 'time', - 'value' => function ($data) { - $timeInSeconds = $data['time'] / 1000; - $millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000); - - return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff); - }, - 'headerOptions' => [ - 'class' => 'sort-numerical' - ] - ], - [ - 'attribute' => 'level', - 'value' => function ($data) { - return Logger::getLevelName($data['level']); - }, - 'filter' => [ - Logger::LEVEL_TRACE => ' Trace ', - Logger::LEVEL_INFO => ' Info ', - Logger::LEVEL_WARNING => ' Warning ', - Logger::LEVEL_ERROR => ' Error ', - ], - ], - 'category', - [ - 'attribute' => 'message', - 'value' => function ($data) { - $message = Html::encode(is_string($data['message']) ? $data['message'] : VarDumper::export($data['message'])); - if (!empty($data['trace'])) { - $message .= Html::ul($data['trace'], [ - 'class' => 'trace', - 'item' => function ($trace) { - return "
  • {$trace['file']} ({$trace['line']})
  • "; - } - ]); - }; - return $message; - }, - 'format' => 'html', - 'options' => [ - 'width' => '50%', - ], - ], - ], -]); diff --git a/extensions/debug/views/default/panels/log/summary.php b/extensions/debug/views/default/panels/log/summary.php deleted file mode 100644 index 3ce1f3f155..0000000000 --- a/extensions/debug/views/default/panels/log/summary.php +++ /dev/null @@ -1,32 +0,0 @@ - - -$errorCount
    "; - $title .= ", $errorCount errors"; -} - -if ($warningCount) { - $output[] = "$warningCount"; - $title .= ", $warningCount warnings"; -} -?> - - diff --git a/extensions/debug/views/default/panels/mail/_item.php b/extensions/debug/views/default/panels/mail/_item.php deleted file mode 100644 index 9d935c53fb..0000000000 --- a/extensions/debug/views/default/panels/mail/_item.php +++ /dev/null @@ -1,37 +0,0 @@ - $model, - 'attributes' => [ - 'headers', - 'from', - 'to', - 'charset', - [ - 'attribute' => 'time', - 'format' => 'datetime', - ], - 'subject', - [ - 'attribute' => 'body', - 'label' => 'Text body', - ], - [ - 'attribute' => 'isSuccessful', - 'label' => 'Successfully sent', - 'value' => $model['isSuccessful'] ? 'Yes' : 'No' - ], - 'reply', - 'bcc', - 'cc', - [ - 'attribute' => 'file', - 'format' => 'html', - 'value' => Html::a('Download eml', ['download-mail', 'file' => $model['file']]), - ], - ], -]); diff --git a/extensions/debug/views/default/panels/mail/detail.php b/extensions/debug/views/default/panels/mail/detail.php deleted file mode 100644 index e2211f613f..0000000000 --- a/extensions/debug/views/default/panels/mail/detail.php +++ /dev/null @@ -1,59 +0,0 @@ - $dataProvider, - 'itemView' => '_item', - 'layout' => "{summary}\n{items}\n{pager}\n", -]); -$listView->sorter = ['options' => ['class' => 'mail-sorter']]; -?> - -

    Email messages

    - -
    -
    - 'btn btn-default', 'onclick' => '$("#email-form").toggle();']) ?> -
    -
    - renderSorter() ?> -
    -
    - - - -run() ?> diff --git a/extensions/debug/views/default/panels/mail/summary.php b/extensions/debug/views/default/panels/mail/summary.php deleted file mode 100644 index 9195f09788..0000000000 --- a/extensions/debug/views/default/panels/mail/summary.php +++ /dev/null @@ -1,8 +0,0 @@ - -
    - Mail -
    - diff --git a/extensions/debug/views/default/panels/profile/detail.php b/extensions/debug/views/default/panels/profile/detail.php deleted file mode 100644 index ad0ae33627..0000000000 --- a/extensions/debug/views/default/panels/profile/detail.php +++ /dev/null @@ -1,60 +0,0 @@ - -

    Performance Profiling

    -

    Total processing time: ; Peak memory: .

    - $dataProvider, - 'id' => 'profile-panel-detailed-grid', - 'options' => ['class' => 'detail-grid-view table-responsive'], - 'filterModel' => $searchModel, - 'filterUrl' => $panel->getUrl(), - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - [ - 'attribute' => 'seq', - 'label' => 'Time', - 'value' => function ($data) { - $timeInSeconds = $data['timestamp'] / 1000; - $millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000); - - return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff); - }, - 'headerOptions' => [ - 'class' => 'sort-numerical' - ] - ], - [ - 'attribute' => 'duration', - 'value' => function ($data) { - return sprintf('%.1f ms', $data['duration']); - }, - 'options' => [ - 'width' => '10%', - ], - 'headerOptions' => [ - 'class' => 'sort-numerical' - ] - ], - 'category', - [ - 'attribute' => 'info', - 'value' => function ($data) { - return str_repeat('', $data['level']) . Html::encode($data['info']); - }, - 'format' => 'html', - 'options' => [ - 'width' => '60%', - ], - ], - ], -]); diff --git a/extensions/debug/views/default/panels/profile/summary.php b/extensions/debug/views/default/panels/profile/summary.php deleted file mode 100644 index c78f2f1d1d..0000000000 --- a/extensions/debug/views/default/panels/profile/summary.php +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/extensions/debug/views/default/panels/request/detail.php b/extensions/debug/views/default/panels/request/detail.php deleted file mode 100644 index 59e9fe004b..0000000000 --- a/extensions/debug/views/default/panels/request/detail.php +++ /dev/null @@ -1,35 +0,0 @@ -Request"; - -echo Tabs::widget([ - 'items' => [ - [ - 'label' => 'Parameters', - 'content' => $this->render('table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]]) - . $this->render('table', ['caption' => '$_GET', 'values' => $panel->data['GET']]) - . $this->render('table', ['caption' => '$_POST', 'values' => $panel->data['POST']]) - . $this->render('table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']]) - . $this->render('table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']]) - . $this->render('table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]), - 'active' => true, - ], - [ - 'label' => 'Headers', - 'content' => $this->render('table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']]) - . $this->render('table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']]) - ], - [ - 'label' => 'Session', - 'content' => $this->render('table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']]) - . $this->render('table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']]) - ], - [ - 'label' => '$_SERVER', - 'content' => $this->render('table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]), - ], - ], -]); diff --git a/extensions/debug/views/default/panels/request/summary.php b/extensions/debug/views/default/panels/request/summary.php deleted file mode 100644 index 2800eab232..0000000000 --- a/extensions/debug/views/default/panels/request/summary.php +++ /dev/null @@ -1,23 +0,0 @@ -data['statusCode']; -if ($statusCode === null) { - $statusCode = 200; -} -if ($statusCode >= 200 && $statusCode < 300) { - $class = 'label-success'; -} elseif ($statusCode >= 300 && $statusCode < 400) { - $class = 'label-info'; -} else { - $class = 'label-important'; -} -$statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Response::$httpStatuses[$statusCode] : ''); -?> - \ No newline at end of file diff --git a/extensions/debug/views/default/panels/request/table.php b/extensions/debug/views/default/panels/request/table.php deleted file mode 100644 index 02d7bd0d23..0000000000 --- a/extensions/debug/views/default/panels/request/table.php +++ /dev/null @@ -1,35 +0,0 @@ - -

    - - - -

    Empty.

    - - - -
    - - - - - - - - - $value): ?> - - - - - - -
    NameValue
    charset, true) ?>
    -
    - - diff --git a/extensions/debug/views/default/toolbar.php b/extensions/debug/views/default/toolbar.php deleted file mode 100644 index 9309f6418b..0000000000 --- a/extensions/debug/views/default/toolbar.php +++ /dev/null @@ -1,46 +0,0 @@ -getUrl(); -?> -
    - - - - getSummary() ?> - - -
    -
    - - -
    diff --git a/extensions/debug/views/default/view.php b/extensions/debug/views/default/view.php deleted file mode 100644 index 149089743c..0000000000 --- a/extensions/debug/views/default/view.php +++ /dev/null @@ -1,82 +0,0 @@ -title = 'Yii Debugger'; -?> -
    -
    - - - - - getSummary() ?> - -
    - -
    -
    -
    -
    - $panel) { - $label = '' . Html::encode($panel->getName()); - echo Html::a($label, ['view', 'tag' => $tag, 'panel' => $id], [ - 'class' => $panel === $activePanel ? 'list-group-item active' : 'list-group-item', - ]); - } - ?> -
    -
    -
    -
    - $meta['tag'], 'panel' => $activePanel->id]; - $items[] = [ - 'label' => $label, - 'url' => $url, - ]; - if (++$count >= 10) { - break; - } - } - echo ButtonGroup::widget([ - 'buttons' => [ - Html::a('All', ['index'], ['class' => 'btn btn-default']), - Html::a('Latest', ['view', 'panel' => $activePanel->id], ['class' => 'btn btn-default']), - ButtonDropdown::widget([ - 'label' => 'Last 10', - 'options' => ['class' => 'btn-default'], - 'dropdown' => ['items' => $items], - ]), - ], - ]); - echo "\n" . $summary['tag'] . ': ' . $summary['method'] . ' ' . Html::a(Html::encode($summary['url']), $summary['url']); - echo ' at ' . date('Y-m-d h:i:s a', $summary['time']) . ' by ' . $summary['ip']; - ?> -
    - getDetail() ?> -
    -
    -
    -
    diff --git a/extensions/debug/views/layouts/main.php b/extensions/debug/views/layouts/main.php deleted file mode 100644 index d0012c6e07..0000000000 --- a/extensions/debug/views/layouts/main.php +++ /dev/null @@ -1,24 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - -beginBody() ?> - -endBody() ?> - - -endPage() ?> diff --git a/extensions/elasticsearch/ActiveFixture.php b/extensions/elasticsearch/ActiveFixture.php deleted file mode 100644 index 9774e526a8..0000000000 --- a/extensions/elasticsearch/ActiveFixture.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @author Qiang Xue - * @since 2.0.2 - */ -class ActiveFixture extends BaseActiveFixture -{ - /** - * @var Connection|string the DB connection object or the application component ID of the DB connection. - * After the DbFixture object is created, if you want to change this property, you should only assign it - * with a DB connection object. - */ - public $db = 'elasticsearch'; - /** - * @var string the name of the index that this fixture is about. If this property is not set, - * the name will be determined via [[modelClass]]. - * @see modelClass - */ - public $index; - /** - * @var string the name of the type that this fixture is about. If this property is not set, - * the name will be determined via [[modelClass]]. - * @see modelClass - */ - public $type; - /** - * @var string|boolean the file path or path alias of the data file that contains the fixture data - * to be returned by [[getData()]]. If this is not set, it will default to `FixturePath/data/Index/Type.php`, - * where `FixturePath` stands for the directory containing this fixture class, `Index` stands for the elasticsearch [[index]] name - * and `Type` stands for the [[type]] associated with this fixture. - * You can set this property to be false to prevent loading any data. - */ - public $dataFile; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if (!isset($this->modelClass) && (!isset($this->index) || !isset($this->type))) { - throw new InvalidConfigException('Either "modelClass" or "index" and "type" must be set.'); - } - /* @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - if ($this->index === null) { - $this->index = $modelClass::index(); - } - if ($this->type === null) { - $this->type = $modelClass::type(); - } - } - - /** - * Loads the fixture. - * - * The default implementation will first clean up the index by calling [[resetIndex()]]. - * It will then populate the index with the data returned by [[getData()]]. - * - * If you override this method, you should consider calling the parent implementation - * so that the data returned by [[getData()]] can be populated into the index. - */ - public function load() - { - $this->resetIndex(); - $this->data = []; - - $mapping = $this->db->createCommand()->getMapping($this->index, $this->type); - if (isset($mapping[$this->index]['mappings'][$this->type]['_id']['path'])) { - $idField = $mapping[$this->index]['mappings'][$this->type]['_id']['path']; - } else { - $idField = '_id'; - } - - foreach ($this->getData() as $alias => $row) { - $options = []; - $id = isset($row[$idField]) ? $row[$idField] : null; - if ($idField === '_id') { - unset($row[$idField]); - } - if (isset($row['_parent'])) { - $options['parent'] = $row['_parent']; - unset($row['_parent']); - } - - try { - $response = $this->db->createCommand()->insert($this->index, $this->type, $row, $id, $options); - } catch(\yii\db\Exception $e) { - throw new \yii\base\Exception("Failed to insert fixture data \"$alias\": " . $e->getMessage() . "\n" . print_r($e->errorInfo, true), $e->getCode(), $e); - } - if ($id === null) { - $row[$idField] = $response['_id']; - } - $this->data[$alias] = $row; - } - // ensure all data is flushed and immediately available in the test - $this->db->createCommand()->flushIndex($this->index); - } - - /** - * Returns the fixture data. - * - * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]]. - * The file should return an array of data rows (column name => column value), each corresponding to a row in the index. - * - * If the data file does not exist, an empty array will be returned. - * - * @return array the data rows to be inserted into the database index. - */ - protected function getData() - { - if ($this->dataFile === null) { - $class = new \ReflectionClass($this); - $dataFile = dirname($class->getFileName()) . "/data/{$this->index}/{$this->type}.php"; - return is_file($dataFile) ? require($dataFile) : []; - } else { - return parent::getData(); - } - } - - /** - * Removes all existing data from the specified index and type. - * This method is called before populating fixture data into the index associated with this fixture. - */ - protected function resetIndex() - { - $this->db->createCommand([ - 'index' => $this->index, - 'type' => $this->type, - 'queryParts' => ['query' => ['match_all' => new \stdClass()]], - ])->deleteByQuery(); - } -} diff --git a/extensions/elasticsearch/Command.php b/extensions/elasticsearch/Command.php deleted file mode 100644 index 6555113d85..0000000000 --- a/extensions/elasticsearch/Command.php +++ /dev/null @@ -1,471 +0,0 @@ - - * @since 2.0 - */ -class Command extends Component -{ - /** - * @var Connection - */ - public $db; - /** - * @var string|array the indexes to execute the query on. Defaults to null meaning all indexes - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-multi-index - */ - public $index; - /** - * @var string|array the types to execute the query on. Defaults to null meaning all types - */ - public $type; - /** - * @var array list of arrays or json strings that become parts of a query - */ - public $queryParts; - public $options = []; - - - /** - * Sends a request to the _search API and returns the result - * @param array $options - * @return mixed - */ - public function search($options = []) - { - $query = $this->queryParts; - if (empty($query)) { - $query = '{}'; - } - if (is_array($query)) { - $query = Json::encode($query); - } - $url = [ - $this->index !== null ? $this->index : '_all', - $this->type !== null ? $this->type : '_all', - '_search' - ]; - - return $this->db->get($url, array_merge($this->options, $options), $query); - } - - /** - * Sends a request to the delete by query - * @param array $options - * @return mixed - */ - public function deleteByQuery($options = []) - { - if (!isset($this->queryParts['query'])) { - throw new InvalidCallException('Can not call deleteByQuery when no query is given.'); - } - $query = [ - 'query' => $this->queryParts['query'], - ]; - if (isset($this->queryParts['filter'])) { - $query['filter'] = $this->queryParts['filter']; - } - $query = Json::encode($query); - $url = [ - $this->index !== null ? $this->index : '_all', - $this->type !== null ? $this->type : '_all', - '_query' - ]; - - return $this->db->delete($url, array_merge($this->options, $options), $query); - } - - /** - * Sends a request to the _suggest API and returns the result - * @param string|array $suggester the suggester body - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html - */ - public function suggest($suggester, $options = []) - { - if (empty($suggester)) { - $suggester = '{}'; - } - if (is_array($suggester)) { - $suggester = Json::encode($suggester); - } - $url = [ - $this->index !== null ? $this->index : '_all', - '_suggest' - ]; - - return $this->db->post($url, array_merge($this->options, $options), $suggester); - } - - /** - * Inserts a document into an index - * @param string $index - * @param string $type - * @param string|array $data json string or array of data to store - * @param null $id the documents id. If not specified Id will be automatically chosen - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html - */ - public function insert($index, $type, $data, $id = null, $options = []) - { - if (empty($data)) { - $body = '{}'; - } else { - $body = is_array($data) ? Json::encode($data) : $data; - } - - if ($id !== null) { - return $this->db->put([$index, $type, $id], $options, $body); - } else { - return $this->db->post([$index, $type], $options, $body); - } - } - - /** - * gets a document from the index - * @param $index - * @param $type - * @param $id - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html - */ - public function get($index, $type, $id, $options = []) - { - return $this->db->get([$index, $type, $id], $options); - } - - /** - * gets multiple documents from the index - * - * TODO allow specifying type and index + fields - * @param $index - * @param $type - * @param $ids - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-multi-get.html - */ - public function mget($index, $type, $ids, $options = []) - { - $body = Json::encode(['ids' => array_values($ids)]); - - return $this->db->get([$index, $type, '_mget'], $options, $body); - } - - /** - * gets a documents _source from the index (>=v0.90.1) - * @param $index - * @param $type - * @param $id - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html#_source - */ - public function getSource($index, $type, $id) - { - return $this->db->get([$index, $type, $id]); - } - - /** - * gets a document from the index - * @param $index - * @param $type - * @param $id - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html - */ - public function exists($index, $type, $id) - { - return $this->db->head([$index, $type, $id]); - } - - /** - * deletes a document from the index - * @param $index - * @param $type - * @param $id - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html - */ - public function delete($index, $type, $id, $options = []) - { - return $this->db->delete([$index, $type, $id], $options); - } - - /** - * updates a document - * @param $index - * @param $type - * @param $id - * @param array $options - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-update.html - */ - public function update($index, $type, $id, $data, $options = []) - { - $body = [ - 'doc' => empty($data) ? new \stdClass() : $data, - ]; - if (isset($options["detect_noop"])) { - $body["detect_noop"] = $options["detect_noop"]; - unset($options["detect_noop"]); - } - - return $this->db->post([$index, $type, $id, '_update'], $options, Json::encode($body)); - } - - // TODO bulk http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html - - /** - * creates an index - * @param $index - * @param array $configuration - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-create-index.html - */ - public function createIndex($index, $configuration = null) - { - $body = $configuration !== null ? Json::encode($configuration) : null; - - return $this->db->put([$index], [], $body); - } - - /** - * deletes an index - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html - */ - public function deleteIndex($index) - { - return $this->db->delete([$index]); - } - - /** - * deletes all indexes - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html - */ - public function deleteAllIndexes() - { - return $this->db->delete(['_all']); - } - - /** - * checks whether an index exists - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-exists.html - */ - public function indexExists($index) - { - return $this->db->head([$index]); - } - - /** - * @param $index - * @param $type - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-types-exists.html - */ - public function typeExists($index, $type) - { - return $this->db->head([$index, $type]); - } - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-aliases.html - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-settings.html - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-warmers.html - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html - */ - public function openIndex($index) - { - return $this->db->post([$index, '_open']); - } - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html - */ - public function closeIndex($index) - { - return $this->db->post([$index, '_close']); - } - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-status.html - */ - public function getIndexStatus($index = '_all') - { - return $this->db->get([$index, '_status']); - } - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-stats.html - // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-segments.html - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-clearcache.html - */ - public function clearIndexCache($index) - { - return $this->db->post([$index, '_cache', 'clear']); - } - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-flush.html - */ - public function flushIndex($index = '_all') - { - return $this->db->post([$index, '_flush']); - } - - /** - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-refresh.html - */ - public function refreshIndex($index) - { - return $this->db->post([$index, '_refresh']); - } - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-optimize.html - - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-gateway-snapshot.html - - /** - * @param $index - * @param $type - * @param $mapping - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-put-mapping.html - */ - public function setMapping($index, $type, $mapping, $options = []) - { - $body = $mapping !== null ? (is_string($mapping) ? $mapping : Json::encode($mapping)) : null; - - return $this->db->put([$index, '_mapping', $type], $options, $body); - } - - /** - * @param string $index - * @param string $type - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-mapping.html - */ - public function getMapping($index = '_all', $type = '_all') - { - return $this->db->get([$index, '_mapping', $type]); - } - - /** - * @param $index - * @param $type - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-put-mapping.html - */ - public function deleteMapping($index, $type) - { - return $this->db->delete([$index, '_mapping', $type]); - } - - /** - * @param $index - * @param string $type - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html - */ -// public function getFieldMapping($index, $type = '_all') -// { -// // TODO implement -// return $this->db->put([$index, $type, '_mapping']); -// } - - /** - * @param $options - * @param $index - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-analyze.html - */ -// public function analyze($options, $index = null) -// { -// // TODO implement -//// return $this->db->put([$index]); -// } - - /** - * @param $name - * @param $pattern - * @param $settings - * @param $mappings - * @param integer $order - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html - */ - public function createTemplate($name, $pattern, $settings, $mappings, $order = 0) - { - $body = Json::encode([ - 'template' => $pattern, - 'order' => $order, - 'settings' => (object) $settings, - 'mappings' => (object) $mappings, - ]); - - return $this->db->put(['_template', $name], [], $body); - - } - - /** - * @param $name - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html - */ - public function deleteTemplate($name) - { - return $this->db->delete(['_template', $name]); - - } - - /** - * @param $name - * @return mixed - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html - */ - public function getTemplate($name) - { - return $this->db->get(['_template', $name]); - } -} diff --git a/extensions/elasticsearch/DebugAction.php b/extensions/elasticsearch/DebugAction.php deleted file mode 100644 index 4147700aa5..0000000000 --- a/extensions/elasticsearch/DebugAction.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @since 2.0 - */ -class DebugAction extends Action -{ - /** - * @var string the connection id to use - */ - public $db; - /** - * @var DebugPanel - */ - public $panel; - /** - * @var \yii\debug\controllers\DefaultController - */ - public $controller; - - - public function run($logId, $tag) - { - $this->controller->loadData($tag); - - $timings = $this->panel->calculateTimings(); - ArrayHelper::multisort($timings, 3, SORT_DESC); - if (!isset($timings[$logId])) { - throw new HttpException(404, 'Log message not found.'); - } - $message = $timings[$logId][1]; - if (($pos = mb_strpos($message, "#")) !== false) { - $url = mb_substr($message, 0, $pos); - $body = mb_substr($message, $pos + 1); - } else { - $url = $message; - $body = null; - } - $method = mb_substr($url, 0, $pos = mb_strpos($url, ' ')); - $url = mb_substr($url, $pos + 1); - - $options = ['pretty' => true]; - - /* @var $db Connection */ - $db = \Yii::$app->get($this->db); - $time = microtime(true); - switch ($method) { - case 'GET': $result = $db->get($url, $options, $body, true); break; - case 'POST': $result = $db->post($url, $options, $body, true); break; - case 'PUT': $result = $db->put($url, $options, $body, true); break; - case 'DELETE': $result = $db->delete($url, $options, $body, true); break; - case 'HEAD': $result = $db->head($url, $options, $body); break; - default: - throw new NotSupportedException("Request method '$method' is not supported by elasticsearch."); - } - $time = microtime(true) - $time; - - if ($result === true) { - $result = 'success'; - } elseif ($result === false) { - $result = 'no success'; - } - - Yii::$app->response->format = Response::FORMAT_JSON; - - return [ - 'time' => sprintf('%.1f ms', $time * 1000), - 'result' => $result, - ]; - } -} diff --git a/extensions/elasticsearch/DebugPanel.php b/extensions/elasticsearch/DebugPanel.php deleted file mode 100644 index 9be7acadf6..0000000000 --- a/extensions/elasticsearch/DebugPanel.php +++ /dev/null @@ -1,195 +0,0 @@ - - * @since 2.0 - */ -class DebugPanel extends Panel -{ - public $db = 'elasticsearch'; - - - public function init() - { - $this->actions['elasticsearch-query'] = [ - 'class' => 'yii\\elasticsearch\\DebugAction', - 'panel' => $this, - 'db' => $this->db, - ]; - } - - /** - * @inheritdoc - */ - public function getName() - { - return 'Elasticsearch'; - } - - /** - * @inheritdoc - */ - public function getSummary() - { - $timings = $this->calculateTimings(); - $queryCount = count($timings); - $queryTime = 0; - foreach ($timings as $timing) { - $queryTime += $timing[3]; - } - $queryTime = number_format($queryTime * 1000) . ' ms'; - $url = $this->getUrl(); - $output = << - - ES $queryCount $queryTime - - -EOD; - - return $queryCount > 0 ? $output : ''; - } - - /** - * @inheritdoc - */ - public function getDetail() - { - $timings = $this->calculateTimings(); - ArrayHelper::multisort($timings, 3, SORT_DESC); - $rows = []; - $i = 0; - foreach ($timings as $logId => $timing) { - $duration = sprintf('%.1f ms', $timing[3] * 1000); - $message = $timing[1]; - $traces = $timing[4]; - if (($pos = mb_strpos($message, "#")) !== false) { - $url = mb_substr($message, 0, $pos); - $body = mb_substr($message, $pos + 1); - } else { - $url = $message; - $body = null; - } - $traceString = ''; - if (!empty($traces)) { - $traceString .= Html::ul($traces, [ - 'class' => 'trace', - 'item' => function ($trace) { - return "
  • {$trace['file']}({$trace['line']})
  • "; - }, - ]); - } - $ajaxUrl = Url::to(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]); - \Yii::$app->view->registerJs(<<Error: ' + errorThrown + ' - ' + textStatus + '

    ' + jqXHR.responseText); - }, - dataType: "json" - }); - - return false; -}); -JS -, View::POS_READY); - $runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '
    '; - $rows[] = << - $duration -
    $url

    $body

    $traceString
    - $runLink - - -HTML; - $i++; - } - $rows = implode("\n", $rows); - - return <<Elasticsearch Queries - - - - - - - - - - -$rows - -
    TimeUrl / QueryRun Query on node
    -HTML; - } - - private $_timings; - - public function calculateTimings() - { - if ($this->_timings !== null) { - return $this->_timings; - } - $messages = $this->data['messages']; - $timings = []; - $stack = []; - foreach ($messages as $i => $log) { - list($token, $level, $category, $timestamp) = $log; - $log[5] = $i; - if ($level == Logger::LEVEL_PROFILE_BEGIN) { - $stack[] = $log; - } elseif ($level == Logger::LEVEL_PROFILE_END) { - if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]]; - } - } - } - - $now = microtime(true); - while (($last = array_pop($stack)) !== null) { - $delta = $now - $last[3]; - $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]]; - } - ksort($timings); - - return $this->_timings = $timings; - } - - /** - * @inheritdoc - */ - public function save() - { - $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']); - - return ['messages' => $messages]; - } -} diff --git a/extensions/elasticsearch/Exception.php b/extensions/elasticsearch/Exception.php deleted file mode 100644 index dd4c1628e6..0000000000 --- a/extensions/elasticsearch/Exception.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class Exception extends \yii\db\Exception -{ - /** - * @return string the user-friendly name of this exception - */ - public function getName() - { - return 'Elasticsearch Database Exception'; - } -} diff --git a/extensions/elasticsearch/LICENSE.md b/extensions/elasticsearch/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/elasticsearch/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php deleted file mode 100644 index 365e72cdf6..0000000000 --- a/extensions/elasticsearch/Query.php +++ /dev/null @@ -1,517 +0,0 @@ -fields('id, name') - * ->from('myindex', 'users') - * ->limit(10); - * // build and execute the query - * $command = $query->createCommand(); - * $rows = $command->search(); // this way you get the raw output of elasticsearch. - * ~~~ - * - * You would normally call `$query->search()` instead of creating a command as this method - * adds the `indexBy()` feature and also removes some inconsistencies from the response. - * - * Query also provides some methods to easier get some parts of the result only: - * - * - [[one()]]: returns a single record populated with the first row of data. - * - [[all()]]: returns all records based on the query results. - * - [[count()]]: returns the number of records. - * - [[scalar()]]: returns the value of the first column in the first row of the query result. - * - [[column()]]: returns the value of the first column in the query result. - * - [[exists()]]: returns a value indicating whether the query result has data or not. - * - * NOTE: elasticsearch limits the number of records returned to 10 records by default. - * If you expect to get more records you should specify limit explicitly. - * - * @author Carsten Brandt - * @since 2.0 - */ -class Query extends Component implements QueryInterface -{ - use QueryTrait; - - /** - * @var array the fields being retrieved from the documents. For example, `['id', 'name']`. - * If not set, this option will not be applied to the query and no fields will be returned. - * In this case the `_source` field will be returned by default which can be configured using [[source]]. - * Setting this to an empty array will result in no fields being retrieved, which means that only the primaryKey - * of a record will be available in the result. - * - * For each field you may also add an array representing a [script field]. Example: - * - * ```php - * $query->fields = [ - * 'id', - * 'name', - * 'value_times_two' => [ - * 'script' => "doc['my_field_name'].value * 2", - * ], - * 'value_times_factor' => [ - * 'script' => "doc['my_field_name'].value * factor", - * 'params' => [ - * 'factor' => 2.0 - * ], - * ], - * ] - * ``` - * - * > Note: Field values are [always returned as arrays] even if they only have one value. - * - * [always returned as arrays]: http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/_return_values.html#_return_values - * [script field]: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-script-fields.html - * - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html#search-request-fields - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-script-fields.html - * @see fields() - * @see source - */ - public $fields; - /** - * @var array this option controls how the `_source` field is returned from the documents. For example, `['id', 'name']` - * means that only the `id` and `name` field should be returned from `_source`. - * If not set, it means retrieving the full `_source` field unless [[fields]] are specified. - * Setting this option to `false` will disable return of the `_source` field, this means that only the primaryKey - * of a record will be available in the result. - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-source-filtering.html - * @see source() - * @see fields - */ - public $source; - /** - * @var string|array The index to retrieve data from. This can be a string representing a single index - * or a an array of multiple indexes. If this is not set, indexes are being queried. - * @see from() - */ - public $index; - /** - * @var string|array The type to retrieve data from. This can be a string representing a single type - * or a an array of multiple types. If this is not set, all types are being queried. - * @see from() - */ - public $type; - /** - * @var integer A search timeout, bounding the search request to be executed within the specified time value - * and bail with the hits accumulated up to that point when expired. Defaults to no timeout. - * @see timeout() - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 - */ - public $timeout; - /** - * @var array|string The query part of this search query. This is an array or json string that follows the format of - * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). - */ - public $query; - /** - * @var array|string The filter part of this search query. This is an array or json string that follows the format of - * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). - */ - public $filter; - /** - * @var array The highlight part of this search query. This is an array that allows to highlight search results - * on one or more fields. - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-request-highlighting.html - */ - public $highlight; - /** - * @var array List of aggregations to add to this query. - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html - */ - public $aggregations = []; - /** - * @var array the 'stats' part of the query. An array of groups to maintain a statistics aggregation for. - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups - */ - public $stats = []; - /** - * @var array list of suggesters to add to this query. - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html - */ - public $suggest = []; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - // setting the default limit according to elasticsearch defaults - // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 - if ($this->limit === null) { - $this->limit = 10; - } - } - - /** - * Creates a DB command that can be used to execute this query. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return Command the created DB command instance. - */ - public function createCommand($db = null) - { - if ($db === null) { - $db = Yii::$app->get('elasticsearch'); - } - - $commandConfig = $db->getQueryBuilder()->build($this); - - return $db->createCommand($commandConfig); - } - - /** - * Executes the query and returns all results as an array. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return array the query results. If the query results in nothing, an empty array will be returned. - */ - public function all($db = null) - { - $result = $this->createCommand($db)->search(); - if (empty($result['hits']['hits'])) { - return []; - } - $rows = $result['hits']['hits']; - if ($this->indexBy === null) { - return $rows; - } - $models = []; - foreach ($rows as $key => $row) { - if ($this->indexBy !== null) { - if (is_string($this->indexBy)) { - $key = isset($row['fields'][$this->indexBy]) ? reset($row['fields'][$this->indexBy]) : $row['_source'][$this->indexBy]; - } else { - $key = call_user_func($this->indexBy, $row); - } - } - $models[$key] = $row; - } - return $models; - } - - /** - * Executes the query and returns a single row of result. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query - * results in nothing. - */ - public function one($db = null) - { - $result = $this->createCommand($db)->search(['size' => 1]); - if (empty($result['hits']['hits'])) { - return false; - } - $record = reset($result['hits']['hits']); - - return $record; - } - - /** - * Executes the query and returns the complete search result including e.g. hits, facets, totalCount. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @param array $options The options given with this query. Possible options are: - * - * - [routing](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-routing) - * - [search_type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html) - * - * @return array the query results. - */ - public function search($db = null, $options = []) - { - $result = $this->createCommand($db)->search($options); - if (!empty($result['hits']['hits']) && $this->indexBy !== null) { - $rows = []; - foreach ($result['hits']['hits'] as $key => $row) { - if (is_string($this->indexBy)) { - $key = isset($row['fields'][$this->indexBy]) ? $row['fields'][$this->indexBy] : $row['_source'][$this->indexBy]; - } else { - $key = call_user_func($this->indexBy, $row); - } - $rows[$key] = $row; - } - $result['hits']['hits'] = $rows; - } - return $result; - } - - // TODO add scroll/scan http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#scan - - /** - * Executes the query and deletes all matching documents. - * - * Everything except query and filter will be ignored. - * - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @param array $options The options given with this query. - * @return array the query results. - */ - public function delete($db = null, $options = []) - { - return $this->createCommand($db)->deleteByQuery($options); - } - - /** - * Returns the query result as a scalar value. - * The value returned will be the specified field in the first document of the query results. - * @param string $field name of the attribute to select - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return string the value of the specified attribute in the first record of the query result. - * Null is returned if the query result is empty or the field does not exist. - */ - public function scalar($field, $db = null) - { - $record = self::one($db); - if ($record !== false) { - if ($field === '_id') { - return $record['_id']; - } elseif (isset($record['_source'][$field])) { - return $record['_source'][$field]; - } elseif (isset($record['fields'][$field])) { - return count($record['fields'][$field]) == 1 ? reset($record['fields'][$field]) : $record['fields'][$field]; - } - } - return null; - } - - /** - * Executes the query and returns the first column of the result. - * @param string $field the field to query over - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return array the first column of the query result. An empty array is returned if the query results in nothing. - */ - public function column($field, $db = null) - { - $command = $this->createCommand($db); - $command->queryParts['_source'] = [$field]; - $result = $command->search(); - if (empty($result['hits']['hits'])) { - return []; - } - $column = []; - foreach ($result['hits']['hits'] as $row) { - if (isset($row['fields'][$field])) { - $column[] = $row['fields'][$field]; - } elseif (isset($row['_source'][$field])) { - $column[] = $row['_source'][$field]; - } else { - $column[] = null; - } - } - return $column; - } - - /** - * Returns the number of records. - * @param string $q the COUNT expression. This parameter is ignored by this implementation. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return integer number of records - */ - public function count($q = '*', $db = null) - { - // TODO consider sending to _count api instead of _search for performance - // only when no facety are registerted. - // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-count.html - // http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/_search_requests.html - - $options = []; - $options['search_type'] = 'count'; - - return $this->createCommand($db)->search($options)['hits']['total']; - } - - /** - * Returns a value indicating whether the query result contains any row of data. - * @param Connection $db the database connection used to execute the query. - * If this parameter is not given, the `elasticsearch` application component will be used. - * @return boolean whether the query result contains any row of data. - */ - public function exists($db = null) - { - return self::one($db) !== false; - } - - /** - * Adds a 'stats' part to the query. - * @param array $groups an array of groups to maintain a statistics aggregation for. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups - */ - public function stats($groups) - { - $this->stats = $groups; - return $this; - } - - /** - * Sets a highlight parameters to retrieve from the documents. - * @param array $highlight array of parameters to highlight results. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html - */ - public function highlight($highlight) - { - $this->highlight = $highlight; - return $this; - } - - /** - * Adds an aggregation to this query. - * @param string $name the name of the aggregation - * @param string $type the aggregation type. e.g. `terms`, `range`, `histogram`... - * @param string|array $options the configuration options for this aggregation. Can be an array or a json string. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html - */ - public function addAggregation($name, $type, $options) - { - $this->aggregations[$name] = [$type => $options]; - return $this; - } - - /** - * Adds an aggregation to this query. - * - * This is an alias for [[addAggregation]]. - * - * @param string $name the name of the aggregation - * @param string $type the aggregation type. e.g. `terms`, `range`, `histogram`... - * @param string|array $options the configuration options for this aggregation. Can be an array or a json string. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html - */ - public function addAgg($name, $type, $options) - { - return $this->addAggregation($name, $type, $options); - } - - /** - * Adds a suggester to this query. - * @param string $name the name of the suggester - * @param string|array $definition the configuration options for this suggester. Can be an array or a json string. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html - */ - public function addSuggester($name, $definition) - { - $this->suggest[$name] = $definition; - return $this; - } - - // TODO add validate query http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-validate.html - - // TODO support multi query via static method http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html - - /** - * Sets the querypart of this search query. - * @param string $query - * @return static the query object itself - */ - public function query($query) - { - $this->query = $query; - return $this; - } - - /** - * Sets the filter part of this search query. - * @param string $filter - * @return static the query object itself - */ - public function filter($filter) - { - $this->filter = $filter; - return $this; - } - - /** - * Sets the index and type to retrieve documents from. - * @param string|array $index The index to retrieve data from. This can be a string representing a single index - * or a an array of multiple indexes. If this is `null` it means that all indexes are being queried. - * @param string|array $type The type to retrieve data from. This can be a string representing a single type - * or a an array of multiple types. If this is `null` it means that all types are being queried. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html#search-multi-index-type - */ - public function from($index, $type = null) - { - $this->index = $index; - $this->type = $type; - return $this; - } - - /** - * Sets the fields to retrieve from the documents. - * @param array $fields the fields to be selected. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html - */ - public function fields($fields) - { - if (is_array($fields) || $fields === null) { - $this->fields = $fields; - } else { - $this->fields = func_get_args(); - } - return $this; - } - - /** - * Sets the source filtering, specifying how the `_source` field of the document should be returned. - * @param array $source the source patterns to be selected. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-source-filtering.html - */ - public function source($source) - { - if (is_array($source) || $source === null) { - $this->source = $source; - } else { - $this->source = func_get_args(); - } - return $this; - } - - /** - * Sets the search timeout. - * @param integer $timeout A search timeout, bounding the search request to be executed within the specified time value - * and bail with the hits accumulated up to that point when expired. Defaults to no timeout. - * @return static the query object itself - * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 - */ - public function timeout($timeout) - { - $this->timeout = $timeout; - return $this; - } -} diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php deleted file mode 100644 index 0b04e4bf8c..0000000000 --- a/extensions/elasticsearch/QueryBuilder.php +++ /dev/null @@ -1,349 +0,0 @@ - - * @since 2.0 - */ -class QueryBuilder extends \yii\base\Object -{ - /** - * @var Connection the database connection. - */ - public $db; - - - /** - * Constructor. - * @param Connection $connection the database connection. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($connection, $config = []) - { - $this->db = $connection; - parent::__construct($config); - } - - /** - * Generates query from a [[Query]] object. - * @param Query $query the [[Query]] object from which the query will be generated - * @return array the generated SQL statement (the first array element) and the corresponding - * parameters to be bound to the SQL statement (the second array element). - */ - public function build($query) - { - $parts = []; - - if ($query->fields === []) { - $parts['fields'] = []; - } elseif ($query->fields !== null) { - $fields = []; - $scriptFields = []; - foreach($query->fields as $key => $field) { - if (is_int($key)) { - $fields[] = $field; - } else { - $scriptFields[$key] = $field; - } - } - if (!empty($fields)) { - $parts['fields'] = $fields; - } - if (!empty($scriptFields)) { - $parts['script_fields'] = $scriptFields; - } - } - if ($query->source !== null) { - $parts['_source'] = $query->source; - } - if ($query->limit !== null && $query->limit >= 0) { - $parts['size'] = $query->limit; - } - if ($query->offset > 0) { - $parts['from'] = (int) $query->offset; - } - - if (empty($query->query)) { - $parts['query'] = ["match_all" => (object) []]; - } else { - $parts['query'] = $query->query; - } - - $whereFilter = $this->buildCondition($query->where); - if (is_string($query->filter)) { - if (empty($whereFilter)) { - $parts['filter'] = $query->filter; - } else { - $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}'; - } - } elseif ($query->filter !== null) { - if (empty($whereFilter)) { - $parts['filter'] = $query->filter; - } else { - $parts['filter'] = ['and' => [$query->filter, $whereFilter]]; - } - } elseif (!empty($whereFilter)) { - $parts['filter'] = $whereFilter; - } - - if (!empty($query->highlight)) { - $parts['highlight'] = $query->highlight; - } - if (!empty($query->aggregations)) { - $parts['aggregations'] = $query->aggregations; - } - if (!empty($query->stats)) { - $parts['stats'] = $query->stats; - } - if (!empty($query->suggest)) { - $parts['suggest'] = $query->suggest; - } - - $sort = $this->buildOrderBy($query->orderBy); - if (!empty($sort)) { - $parts['sort'] = $sort; - } - - $options = []; - if ($query->timeout !== null) { - $options['timeout'] = $query->timeout; - } - - return [ - 'queryParts' => $parts, - 'index' => $query->index, - 'type' => $query->type, - 'options' => $options, - ]; - } - - /** - * adds order by condition to the query - */ - public function buildOrderBy($columns) - { - if (empty($columns)) { - return []; - } - $orders = []; - foreach ($columns as $name => $direction) { - if (is_string($direction)) { - $column = $direction; - $direction = SORT_ASC; - } else { - $column = $name; - } - if ($column == '_id') { - $column = '_uid'; - } - - // allow elasticsearch extended syntax as described in http://www.elasticsearch.org/guide/reference/api/search/sort/ - if (is_array($direction)) { - $orders[] = [$column => $direction]; - } else { - $orders[] = [$column => ($direction === SORT_DESC ? 'desc' : 'asc')]; - } - } - - return $orders; - } - - /** - * Parses the condition specification and generates the corresponding SQL expression. - * - * @param string|array $condition the condition specification. Please refer to [[Query::where()]] on how to specify a condition. - * @throws \yii\base\InvalidParamException if unknown operator is used in query - * @throws \yii\base\NotSupportedException if string conditions are used in where - * @return string the generated SQL expression - */ - public function buildCondition($condition) - { - static $builders = [ - 'not' => 'buildNotCondition', - 'and' => 'buildAndCondition', - 'or' => 'buildAndCondition', - 'between' => 'buildBetweenCondition', - 'not between' => 'buildBetweenCondition', - 'in' => 'buildInCondition', - 'not in' => 'buildInCondition', - 'like' => 'buildLikeCondition', - 'not like' => 'buildLikeCondition', - 'or like' => 'buildLikeCondition', - 'or not like' => 'buildLikeCondition', - ]; - - if (empty($condition)) { - return []; - } - if (!is_array($condition)) { - throw new NotSupportedException('String conditions in where() are not supported by elasticsearch.'); - } - if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... - $operator = strtolower($condition[0]); - if (isset($builders[$operator])) { - $method = $builders[$operator]; - array_shift($condition); - - return $this->$method($operator, $condition); - } else { - throw new InvalidParamException('Found unknown operator in query: ' . $operator); - } - } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... - - return $this->buildHashCondition($condition); - } - } - - private function buildHashCondition($condition) - { - $parts = []; - foreach ($condition as $attribute => $value) { - if ($attribute == '_id') { - if ($value === null) { // there is no null pk - $parts[] = ['terms' => ['_uid' => []]]; // this condition is equal to WHERE false - } else { - $parts[] = ['ids' => ['values' => is_array($value) ? $value : [$value]]]; - } - } else { - if (is_array($value)) { // IN condition - $parts[] = ['in' => [$attribute => $value]]; - } else { - if ($value === null) { - $parts[] = ['missing' => ['field' => $attribute, 'existence' => true, 'null_value' => true]]; - } else { - $parts[] = ['term' => [$attribute => $value]]; - } - } - } - } - - return count($parts) === 1 ? $parts[0] : ['and' => $parts]; - } - - private function buildNotCondition($operator, $operands) - { - if (count($operands) != 1) { - throw new InvalidParamException("Operator '$operator' requires exactly one operand."); - } - - $operand = reset($operands); - if (is_array($operand)) { - $operand = $this->buildCondition($operand); - } - - return [$operator => $operand]; - } - - private function buildAndCondition($operator, $operands) - { - $parts = []; - foreach ($operands as $operand) { - if (is_array($operand)) { - $operand = $this->buildCondition($operand); - } - if (!empty($operand)) { - $parts[] = $operand; - } - } - if (!empty($parts)) { - return [$operator => $parts]; - } else { - return []; - } - } - - private function buildBetweenCondition($operator, $operands) - { - if (!isset($operands[0], $operands[1], $operands[2])) { - throw new InvalidParamException("Operator '$operator' requires three operands."); - } - - list($column, $value1, $value2) = $operands; - if ($column == '_id') { - throw new NotSupportedException('Between condition is not supported for the _id field.'); - } - $filter = ['range' => [$column => ['gte' => $value1, 'lte' => $value2]]]; - if ($operator == 'not between') { - $filter = ['not' => $filter]; - } - - return $filter; - } - - private function buildInCondition($operator, $operands) - { - if (!isset($operands[0], $operands[1])) { - throw new InvalidParamException("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - $values = (array) $values; - - if (empty($values) || $column === []) { - return $operator === 'in' ? ['terms' => ['_uid' => []]] : []; // this condition is equal to WHERE false - } - - if (count($column) > 1) { - return $this->buildCompositeInCondition($operator, $column, $values); - } elseif (is_array($column)) { - $column = reset($column); - } - $canBeNull = false; - foreach ($values as $i => $value) { - if (is_array($value)) { - $values[$i] = $value = isset($value[$column]) ? $value[$column] : null; - } - if ($value === null) { - $canBeNull = true; - unset($values[$i]); - } - } - if ($column == '_id') { - if (empty($values) && $canBeNull) { // there is no null pk - $filter = ['terms' => ['_uid' => []]]; // this condition is equal to WHERE false - } else { - $filter = ['ids' => ['values' => array_values($values)]]; - if ($canBeNull) { - $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]]; - } - } - } else { - if (empty($values) && $canBeNull) { - $filter = ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]; - } else { - $filter = ['in' => [$column => array_values($values)]]; - if ($canBeNull) { - $filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]]; - } - } - } - if ($operator == 'not in') { - $filter = ['not' => $filter]; - } - - return $filter; - } - - protected function buildCompositeInCondition($operator, $columns, $values) - { - throw new NotSupportedException('composite in is not supported by elasticsearch.'); - } - - private function buildLikeCondition($operator, $operands) - { - throw new NotSupportedException('like conditions are not supported by elasticsearch.'); - } -} diff --git a/extensions/elasticsearch/composer.json b/extensions/elasticsearch/composer.json deleted file mode 100644 index 5d3c3ab012..0000000000 --- a/extensions/elasticsearch/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "yiisoft/yii2-elasticsearch", - "description": "Elasticsearch integration and ActiveRecord for the Yii framework", - "keywords": ["yii2", "elasticsearch", "active-record", "search", "fulltext"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aelasticsearch", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc" - } - ], - "require": { - "yiisoft/yii2": "*", - "ext-curl": "*" - }, - "autoload": { - "psr-4": { "yii\\elasticsearch\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/elasticsearch/images/README-debug.png b/extensions/elasticsearch/images/README-debug.png deleted file mode 100644 index 8877a604a7..0000000000 Binary files a/extensions/elasticsearch/images/README-debug.png and /dev/null differ diff --git a/extensions/faker/FixtureController.php b/extensions/faker/FixtureController.php deleted file mode 100644 index 390ac34abc..0000000000 --- a/extensions/faker/FixtureController.php +++ /dev/null @@ -1,468 +0,0 @@ - [ - * 'fixture' => [ - * 'class' => 'yii\faker\FixtureController', - * ], - * ], - * ~~~ - * - * To start using the command you need to be familiar (read guide) with the Faker library and - * generate fixtures template files, according to the given format: - * - * ```php - * // users.php file under template path (by default @tests/unit/templates/fixtures) - * return [ - * 'name' => $faker->firstName, - * 'phone' => $faker->phoneNumber, - * 'city' => $faker->city, - * 'password' => Yii::$app->getSecurity()->generatePasswordHash('password_' . $index), - * 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), - * 'intro' => $faker->sentence(7, true), // generate a sentence with 7 words - * ]; - * ``` - * - * If you use callback as an attribute value it will be called with the following three parameters: - * - * - `$faker`: the Faker generator instance - * - `$index`: the current fixture index. For example if user need to generate 3 fixtures for user table, it will be 0..2. - * - * After you set all needed fields in callback, you need to return $fixture array back from the callback. - * - * After you prepared needed templates for tables you can simply generate your fixtures via command - * - * ~~~ - * yii fixture/generate user - * - * //generate fixtures from several templates, for example: - * yii fixture/generate user profile team - * ~~~ - * - * In the code above "users" is template name, after this command run, new file named same as template - * will be created under the `$fixtureDataPath` folder. - * You can generate fixtures for all templates, for example: - * - * ~~~ - * yii fixture/generate-all - * ~~~ - * - * This command will generate fixtures for all template files that are stored under $templatePath and - * store fixtures under `$fixtureDataPath` with file names same as templates names. - * - * You can specify how many fixtures per file you need by the second parameter. In the code below we generate - * all fixtures and in each file there will be 3 rows (fixtures). - * - * ~~~ - * yii fixture/generate-all --count=3 - * ~~~ - * - * You can specify different options of this command: - * - * ~~~ - * //generate fixtures in russian language - * yii fixture/generate user --count=5 --language=ru_RU - * - * //read templates from the other path - * yii fixture/generate-all --templatePath=@app/path/to/my/custom/templates - * - * //generate fixtures into other folders - * yii fixture/generate-all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3 - * ~~~ - * - * You can see all available templates by running command: - * - * ~~~ - * //list all templates under default template path (i.e. '@tests/unit/templates/fixtures') - * yii fixture/templates - * - * //list all templates under specified template path - * yii fixture/templates --templatePath='@app/path/to/my/custom/templates' - * ~~~ - * - * You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker); - * After you created custom provider, for example: - * - * ~~~ - * class Book extends \Faker\Provider\Base - * { - * - * public function title($nbWords = 5) - * { - * $sentence = $this->generator->sentence($nbWords); - * return mb_substr($sentence, 0, mb_strlen($sentence) - 1); - * } - * - * } - * ~~~ - * - * you can use it by adding it to the $providers property of the current command. In your console.php config: - * - * ~~~ - * 'controllerMap' => [ - * 'fixture' => [ - * 'class' => 'yii\faker\FixtureController', - * 'providers' => [ - * 'app\tests\unit\faker\providers\Book', - * ], - * ], - * ], - * ~~~ - * - * @property \Faker\Generator $generator This property is read-only. - * - * @author Mark Jebri - * @since 2.0.0 - */ -class FixtureController extends \yii\console\controllers\FixtureController -{ - /** - * @var string Alias to the template path, where all tables templates are stored. - */ - public $templatePath = '@tests/unit/templates/fixtures'; - /** - * @var string Alias to the fixture data path, where data files should be written. - */ - public $fixtureDataPath = '@tests/unit/fixtures/data'; - /** - * @var string Language to use when generating fixtures data. - */ - public $language; - /** - * @var integer total count of data per fixture. Defaults to 2. - */ - public $count = 2; - /** - * @var array Additional data providers that can be created by user and will be added to the Faker generator. - * More info in [Faker](https://github.com/fzaninotto/Faker.) library docs. - */ - public $providers = []; - - /** - * @var \Faker\Generator Faker generator instance - */ - private $_generator; - - - /** - * @inheritdoc - */ - public function options($actionID) - { - return array_merge(parent::options($actionID), [ - 'templatePath', 'language', 'fixtureDataPath', 'count' - ]); - } - - public function beforeAction($action) - { - if (parent::beforeAction($action)) { - $this->checkPaths(); - $this->addProviders(); - return true; - } else { - return false; - } - } - - /** - * Lists all available fixtures template files. - */ - public function actionTemplates() - { - $foundTemplates = $this->findTemplatesFiles(); - - if (!$foundTemplates) { - $this->notifyNoTemplatesFound(); - } else { - $this->notifyTemplatesCanBeGenerated($foundTemplates); - } - } - - /** - * Generates fixtures and fill them with Faker data. - * For example, - * - * ~~~ - * //generate fixtures in russian language - * yii fixture/generate user --count=5 --language=ru_RU - * - * //generate several fixtures - * yii fixture/generate user profile team - * ~~~ - * - * @throws \yii\base\InvalidParamException - * @throws \yii\console\Exception - */ - public function actionGenerate() - { - $templatesInput = func_get_args(); - - if (empty($templatesInput)) { - throw new Exception('You should specify input fixtures template files'); - } - - $foundTemplates = $this->findTemplatesFiles($templatesInput); - - $notFoundTemplates = array_diff($templatesInput, $foundTemplates); - - if ($notFoundTemplates) { - $this->notifyNotFoundTemplates($notFoundTemplates); - } - - if (!$foundTemplates) { - $this->notifyNoTemplatesFound(); - return static::EXIT_CODE_NORMAL; - } - - if (!$this->confirmGeneration($foundTemplates)) { - return static::EXIT_CODE_NORMAL; - } - - $templatePath = Yii::getAlias($this->templatePath); - $fixtureDataPath = Yii::getAlias($this->fixtureDataPath); - - FileHelper::createDirectory($fixtureDataPath); - - $generatedTemplates = []; - - foreach ($foundTemplates as $templateName) { - $this->generateFixtureFile($templateName, $templatePath, $fixtureDataPath); - $generatedTemplates[] = $templateName; - } - - $this->notifyTemplatesGenerated($generatedTemplates); - } - - /** - * Generates all fixtures template path that can be found. - */ - public function actionGenerateAll() - { - $foundTemplates = $this->findTemplatesFiles(); - - if (!$foundTemplates) { - $this->notifyNoTemplatesFound(); - return static::EXIT_CODE_NORMAL; - } - - if (!$this->confirmGeneration($foundTemplates)) { - return static::EXIT_CODE_NORMAL; - } - - $templatePath = Yii::getAlias($this->templatePath); - $fixtureDataPath = Yii::getAlias($this->fixtureDataPath); - - FileHelper::createDirectory($fixtureDataPath); - - $generatedTemplates = []; - - foreach ($foundTemplates as $templateName) { - $this->generateFixtureFile($templateName, $templatePath, $fixtureDataPath); - $generatedTemplates[] = $templateName; - } - - $this->notifyTemplatesGenerated($generatedTemplates); - } - - /** - * Notifies user that given fixtures template files were not found. - * @param array $templatesNames - */ - private function notifyNotFoundTemplates($templatesNames) - { - $this->stdout("The following fixtures templates were NOT found:\n\n", Console::FG_RED); - - foreach ($templatesNames as $name) { - $this->stdout("\t * $name \n", Console::FG_GREEN); - } - - $this->stdout("\n"); - } - - /** - * Notifies user that there was not found any files matching given input conditions. - */ - private function notifyNoTemplatesFound() - { - $this->stdout("No fixtures template files matching input conditions were found under the path:\n\n", Console::FG_RED); - $this->stdout("\t " . Yii::getAlias($this->templatePath) . " \n\n", Console::FG_GREEN); - } - - /** - * Notifies user that given fixtures template files were generated. - * @param array $templatesNames - */ - private function notifyTemplatesGenerated($templatesNames) - { - $this->stdout("The following fixtures template files were generated:\n\n", Console::FG_YELLOW); - - foreach ($templatesNames as $name) { - $this->stdout("\t* " . $name . "\n", Console::FG_GREEN); - } - - $this->stdout("\n"); - } - - private function notifyTemplatesCanBeGenerated($templatesNames) - { - $this->stdout("Template files path: ", Console::FG_YELLOW); - $this->stdout(Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN); - - foreach ($templatesNames as $name) { - $this->stdout("\t* " . $name . "\n", Console::FG_GREEN); - } - - $this->stdout("\n"); - } - - /** - * Returns array containing fixtures templates file names. You can specify what files to find - * by the given parameter. - * @param array $templatesNames template file names to search. If empty then all files will be searched. - * @return array - */ - private function findTemplatesFiles(array $templatesNames = []) - { - $findAll = ($templatesNames == []); - - if ($findAll) { - $files = FileHelper::findFiles(Yii::getAlias($this->templatePath), ['only' => ['*.php']]); - } else { - $filesToSearch = []; - - foreach ($templatesNames as $fileName) { - $filesToSearch[] = $fileName . '.php'; - } - - $files = FileHelper::findFiles(Yii::getAlias($this->templatePath), ['only' => $filesToSearch]); - } - - $foundTemplates = []; - - foreach ($files as $fileName) { - $foundTemplates[] = basename($fileName, '.php'); - } - - return $foundTemplates; - } - - /** - * Returns Faker generator instance. Getter for private property. - * @return \Faker\Generator - */ - public function getGenerator() - { - if ($this->_generator === null) { - $language = $this->language === null ? Yii::$app->language : $this->language; - $this->_generator = \Faker\Factory::create(str_replace('-', '_', $language)); - } - return $this->_generator; - } - - /** - * Check if the template path and migrations path exists and writable. - */ - public function checkPaths() - { - $path = Yii::getAlias($this->templatePath, false); - - if (!$path || !is_dir($path)) { - throw new Exception("The template path \"{$this->templatePath}\" does not exist"); - } - } - - /** - * Adds users providers to the faker generator. - */ - public function addProviders() - { - foreach ($this->providers as $provider) { - $this->generator->addProvider(new $provider($this->generator)); - } - } - - /** - * Returns exported to the string representation of given fixtures array. - * @param array $fixtures - * @return string exported fixtures format - */ - public function exportFixtures($fixtures) - { - return "getGenerator(); - return require($_template_); - } - - /** - * Generates fixture file by the given fixture template file. - * @param string $templateName template file name - * @param string $templatePath path where templates are stored - * @param string $fixtureDataPath fixture data path where generated file should be written - */ - public function generateFixtureFile($templateName, $templatePath, $fixtureDataPath) - { - $fixtures = []; - - for ($i = 0; $i < $this->count; $i++) { - $fixtures[$i] = $this->generateFixture($templatePath . '/' . $templateName . '.php', $i); - } - - $content = $this->exportFixtures($fixtures); - - file_put_contents($fixtureDataPath . '/'. $templateName . '.php', $content); - } - - /** - * Prompts user with message if he confirm generation with given fixture templates files. - * @param array $files - * @return boolean - */ - public function confirmGeneration($files) - { - $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW); - $this->stdout("\t" . Yii::getAlias($this->fixtureDataPath) . "\n\n", Console::FG_GREEN); - $this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW); - $this->stdout("\t" . Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN); - - foreach ($files as $fileName) { - $this->stdout("\t* " . $fileName . "\n", Console::FG_GREEN); - } - - return $this->confirm('Generate above fixtures?'); - } - -} diff --git a/extensions/faker/LICENSE.md b/extensions/faker/LICENSE.md deleted file mode 100644 index 6edcc4f571..0000000000 --- a/extensions/faker/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/extensions/faker/composer.json b/extensions/faker/composer.json deleted file mode 100644 index 589cb1b251..0000000000 --- a/extensions/faker/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "yiisoft/yii2-faker", - "description": "Fixture generator. The Faker integration for the Yii framework.", - "keywords": ["yii2", "faker", "fixture"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Mark Jebri", - "email": "mark.github@yandex.ru" - } - ], - "require": { - "yiisoft/yii2": "*", - "fzaninotto/faker": "*" - }, - "autoload": { - "psr-4": { "yii\\faker\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/gii/CodeFile.php b/extensions/gii/CodeFile.php deleted file mode 100644 index aa7ebb7cca..0000000000 --- a/extensions/gii/CodeFile.php +++ /dev/null @@ -1,194 +0,0 @@ - - * @since 2.0 - */ -class CodeFile extends Object -{ - /** - * The code file is new. - */ - const OP_CREATE = 'create'; - /** - * The code file already exists, and the new one may need to overwrite it. - */ - const OP_OVERWRITE = 'overwrite'; - /** - * The new code file and the existing one are identical. - */ - const OP_SKIP = 'skip'; - - /** - * @var string an ID that uniquely identifies this code file. - */ - public $id; - /** - * @var string the file path that the new code should be saved to. - */ - public $path; - /** - * @var string the newly generated code content - */ - public $content; - /** - * @var string the operation to be performed. This can be [[OP_CREATE]], [[OP_OVERWRITE]] or [[OP_SKIP]]. - */ - public $operation; - - - /** - * Constructor. - * @param string $path the file path that the new code should be saved to. - * @param string $content the newly generated code content. - */ - public function __construct($path, $content) - { - $this->path = strtr($path, '/\\', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR); - $this->content = $content; - $this->id = md5($this->path); - if (is_file($path)) { - $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE; - } else { - $this->operation = self::OP_CREATE; - } - } - - /** - * Saves the code into the file specified by [[path]]. - * @return string|boolean the error occurred while saving the code file, or true if no error. - */ - public function save() - { - $module = Yii::$app->controller->module; - if ($this->operation === self::OP_CREATE) { - $dir = dirname($this->path); - if (!is_dir($dir)) { - $mask = @umask(0); - $result = @mkdir($dir, $module->newDirMode, true); - @umask($mask); - if (!$result) { - return "Unable to create the directory '$dir'."; - } - } - } - if (@file_put_contents($this->path, $this->content) === false) { - return "Unable to write the file '{$this->path}'."; - } else { - $mask = @umask(0); - @chmod($this->path, $module->newFileMode); - @umask($mask); - } - - return true; - } - - /** - * @return string the code file path relative to the application base path. - */ - public function getRelativePath() - { - if (strpos($this->path, Yii::$app->basePath) === 0) { - return substr($this->path, strlen(Yii::$app->basePath) + 1); - } else { - return $this->path; - } - } - - /** - * @return string the code file extension (e.g. php, txt) - */ - public function getType() - { - if (($pos = strrpos($this->path, '.')) !== false) { - return substr($this->path, $pos + 1); - } else { - return 'unknown'; - } - } - - /** - * Returns preview or false if it cannot be rendered - * - * @return boolean|string - */ - public function preview() - { - if (($pos = strrpos($this->path, '.')) !== false) { - $type = substr($this->path, $pos + 1); - } else { - $type = 'unknown'; - } - - if ($type === 'php') { - return highlight_string($this->content, true); - } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) { - return nl2br(Html::encode($this->content)); - } else { - return false; - } - } - - /** - * Returns diff or false if it cannot be calculated - * - * @return boolean|string - */ - public function diff() - { - $type = strtolower($this->getType()); - if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) { - return false; - } elseif ($this->operation === self::OP_OVERWRITE) { - return $this->renderDiff(file($this->path), $this->content); - } else { - return ''; - } - } - - /** - * Renders diff between two sets of lines - * - * @param mixed $lines1 - * @param mixed $lines2 - * @return string - */ - private function renderDiff($lines1, $lines2) - { - if (!is_array($lines1)) { - $lines1 = explode("\n", $lines1); - } - if (!is_array($lines2)) { - $lines2 = explode("\n", $lines2); - } - foreach ($lines1 as $i => $line) { - $lines1[$i] = rtrim($line, "\r\n"); - } - foreach ($lines2 as $i => $line) { - $lines2[$i] = rtrim($line, "\r\n"); - } - - $renderer = new DiffRendererHtmlInline(); - $diff = new \Diff($lines1, $lines2); - - return $diff->render($renderer); - } -} diff --git a/extensions/gii/Generator.php b/extensions/gii/Generator.php deleted file mode 100644 index 27ae8f2bd2..0000000000 --- a/extensions/gii/Generator.php +++ /dev/null @@ -1,521 +0,0 @@ - - * @since 2.0 - */ -abstract class Generator extends Model -{ - /** - * @var array a list of available code templates. The array keys are the template names, - * and the array values are the corresponding template paths or path aliases. - */ - public $templates = []; - /** - * @var string the name of the code template that the user has selected. - * The value of this property is internally managed by this class. - */ - public $template = 'default'; - /** - * @var boolean whether the strings will be generated using `Yii::t()` or normal strings. - */ - public $enableI18N = false; - /** - * @var string the message category used by `Yii::t()` when `$enableI18N` is `true`. - * Defaults to `app`. - */ - public $messageCategory = 'app'; - - - /** - * @return string name of the code generator - */ - abstract public function getName(); - /** - * Generates the code based on the current user input and the specified code template files. - * This is the main method that child classes should implement. - * Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example - * on how to implement this method. - * @return CodeFile[] a list of code files to be created. - */ - abstract public function generate(); - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if (!isset($this->templates['default'])) { - $this->templates['default'] = $this->defaultTemplate(); - } - foreach ($this->templates as $i => $template) { - $this->templates[$i] = Yii::getAlias($template); - } - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'enableI18N' => 'Enable I18N', - 'messageCategory' => 'Message Category', - ]; - } - - /** - * Returns a list of code template files that are required. - * Derived classes usually should override this method if they require the existence of - * certain template files. - * @return array list of code template files that are required. They should be file paths - * relative to [[templatePath]]. - */ - public function requiredTemplates() - { - return []; - } - - /** - * Returns the list of sticky attributes. - * A sticky attribute will remember its value and will initialize the attribute with this value - * when the generator is restarted. - * @return array list of sticky attributes - */ - public function stickyAttributes() - { - return ['template', 'enableI18N', 'messageCategory']; - } - - /** - * Returns the list of hint messages. - * The array keys are the attribute names, and the array values are the corresponding hint messages. - * Hint messages will be displayed to end users when they are filling the form for the generator. - * @return array the list of hint messages - */ - public function hints() - { - return [ - 'enableI18N' => 'This indicates whether the generator should generate strings using Yii::t() method. - Set this to true if you are planning to make your application translatable.', - 'messageCategory' => 'This is the category used by Yii::t() in case you enable I18N.', - ]; - } - - /** - * Returns the list of auto complete values. - * The array keys are the attribute names, and the array values are the corresponding auto complete values. - * Auto complete values can also be callable typed in order one want to make postponed data generation. - * @return array the list of auto complete values - */ - public function autoCompleteData() - { - return []; - } - - /** - * Returns the message to be displayed when the newly generated code is saved successfully. - * Child classes may override this method to customize the message. - * @return string the message to be displayed when the newly generated code is saved successfully. - */ - public function successMessage() - { - return 'The code has been generated successfully.'; - } - - /** - * Returns the view file for the input form of the generator. - * The default implementation will return the "form.php" file under the directory - * that contains the generator class file. - * @return string the view file for the input form of the generator. - */ - public function formView() - { - $class = new ReflectionClass($this); - - return dirname($class->getFileName()) . '/form.php'; - } - - /** - * Returns the root path to the default code template files. - * The default implementation will return the "templates" subdirectory of the - * directory containing the generator class file. - * @return string the root path to the default code template files. - */ - public function defaultTemplate() - { - $class = new ReflectionClass($this); - - return dirname($class->getFileName()) . '/default'; - } - - /** - * @return string the detailed description of the generator. - */ - public function getDescription() - { - return ''; - } - - /** - * @inheritdoc - * - * Child classes should override this method like the following so that the parent - * rules are included: - * - * ~~~ - * return array_merge(parent::rules(), [ - * ...rules for the child class... - * ]); - * ~~~ - */ - public function rules() - { - return [ - [['template'], 'required', 'message' => 'A code template must be selected.'], - [['template'], 'validateTemplate'], - ]; - } - - /** - * Loads sticky attributes from an internal file and populates them into the generator. - * @internal - */ - public function loadStickyAttributes() - { - $stickyAttributes = $this->stickyAttributes(); - $path = $this->getStickyDataFile(); - if (is_file($path)) { - $result = json_decode(file_get_contents($path), true); - if (is_array($result)) { - foreach ($stickyAttributes as $name) { - if (isset($result[$name])) { - $this->$name = $result[$name]; - } - } - } - } - } - - /** - * Saves sticky attributes into an internal file. - * @internal - */ - public function saveStickyAttributes() - { - $stickyAttributes = $this->stickyAttributes(); - $stickyAttributes[] = 'template'; - $values = []; - foreach ($stickyAttributes as $name) { - $values[$name] = $this->$name; - } - $path = $this->getStickyDataFile(); - @mkdir(dirname($path), 0755, true); - file_put_contents($path, json_encode($values)); - } - - /** - * @return string the file path that stores the sticky attribute values. - * @internal - */ - public function getStickyDataFile() - { - return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json'; - } - - /** - * Saves the generated code into files. - * @param CodeFile[] $files the code files to be saved - * @param array $answers - * @param string $results this parameter receives a value from this method indicating the log messages - * generated while saving the code files. - * @return boolean whether files are successfully saved without any error. - */ - public function save($files, $answers, &$results) - { - $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...']; - $hasError = false; - foreach ($files as $file) { - $relativePath = $file->getRelativePath(); - if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) { - $error = $file->save(); - if (is_string($error)) { - $hasError = true; - $lines[] = "generating $relativePath\n$error"; - } else { - $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; - } - } else { - $lines[] = " skipped $relativePath"; - } - } - $lines[] = "done!\n"; - $results = implode("\n", $lines); - - return !$hasError; - } - - /** - * @return string the root path of the template files that are currently being used. - * @throws InvalidConfigException if [[template]] is invalid - */ - public function getTemplatePath() - { - if (isset($this->templates[$this->template])) { - return $this->templates[$this->template]; - } else { - throw new InvalidConfigException("Unknown template: {$this->template}"); - } - } - - /** - * Generates code using the specified code template and parameters. - * Note that the code template will be used as a PHP file. - * @param string $template the code template file. This must be specified as a file path - * relative to [[templatePath]]. - * @param array $params list of parameters to be passed to the template file. - * @return string the generated code - */ - public function render($template, $params = []) - { - $view = new View(); - $params['generator'] = $this; - - return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this); - } - - /** - * Validates the template selection. - * This method validates whether the user selects an existing template - * and the template contains all required template files as specified in [[requiredTemplates()]]. - */ - public function validateTemplate() - { - $templates = $this->templates; - if (!isset($templates[$this->template])) { - $this->addError('template', 'Invalid template selection.'); - } else { - $templatePath = $this->templates[$this->template]; - foreach ($this->requiredTemplates() as $template) { - if (!is_file($templatePath . '/' . $template)) { - $this->addError('template', "Unable to find the required code template file '$template'."); - } - } - } - } - - /** - * An inline validator that checks if the attribute value refers to an existing class name. - * If the `extends` option is specified, it will also check if the class is a child class - * of the class represented by the `extends` option. - * @param string $attribute the attribute being validated - * @param array $params the validation options - */ - public function validateClass($attribute, $params) - { - $class = $this->$attribute; - try { - if (class_exists($class)) { - if (isset($params['extends'])) { - if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) { - $this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class."); - } - } - } else { - $this->addError($attribute, "Class '$class' does not exist or has syntax error."); - } - } catch (\Exception $e) { - $this->addError($attribute, "Class '$class' does not exist or has syntax error."); - } - } - - /** - * An inline validator that checks if the attribute value refers to a valid namespaced class name. - * The validator will check if the directory containing the new class file exist or not. - * @param string $attribute the attribute being validated - * @param array $params the validation options - */ - public function validateNewClass($attribute, $params) - { - $class = ltrim($this->$attribute, '\\'); - if (($pos = strrpos($class, '\\')) === false) { - $this->addError($attribute, "The class name must contain fully qualified namespace name."); - } else { - $ns = substr($class, 0, $pos); - $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); - if ($path === false) { - $this->addError($attribute, "The class namespace is invalid: $ns"); - } elseif (!is_dir($path)) { - $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); - } - } - } - - /** - * Checks if message category is not empty when I18N is enabled. - */ - public function validateMessageCategory() - { - if ($this->enableI18N && empty($this->messageCategory)) { - $this->addError('messageCategory', "Message Category cannot be blank."); - } - } - - /** - * @param string $value the attribute to be validated - * @return boolean whether the value is a reserved PHP keyword. - */ - public function isReservedKeyword($value) - { - static $keywords = [ - '__class__', - '__dir__', - '__file__', - '__function__', - '__line__', - '__method__', - '__namespace__', - '__trait__', - 'abstract', - 'and', - 'array', - 'as', - 'break', - 'case', - 'catch', - 'callable', - 'cfunction', - 'class', - 'clone', - 'const', - 'continue', - 'declare', - 'default', - 'die', - 'do', - 'echo', - 'else', - 'elseif', - 'empty', - 'enddeclare', - 'endfor', - 'endforeach', - 'endif', - 'endswitch', - 'endwhile', - 'eval', - 'exception', - 'exit', - 'extends', - 'final', - 'finally', - 'for', - 'foreach', - 'function', - 'global', - 'goto', - 'if', - 'implements', - 'include', - 'include_once', - 'instanceof', - 'insteadof', - 'interface', - 'isset', - 'list', - 'namespace', - 'new', - 'old_function', - 'or', - 'parent', - 'php_user_filter', - 'print', - 'private', - 'protected', - 'public', - 'require', - 'require_once', - 'return', - 'static', - 'switch', - 'this', - 'throw', - 'trait', - 'try', - 'unset', - 'use', - 'var', - 'while', - 'xor', - ]; - - return in_array(strtolower($value), $keywords, true); - } - - /** - * Generates a string depending on enableI18N property - * - * @param string $string the text be generated - * @param array $placeholders the placeholders to use by `Yii::t()` - * @return string - */ - public function generateString($string = '', $placeholders = []) - { - $string = addslashes($string); - if ($this->enableI18N) { - // If there are placeholders, use them - if (!empty($placeholders)) { - $ph = ', ' . VarDumper::export($placeholders); - } else { - $ph = ''; - } - $str = "Yii::t('" . $this->messageCategory . "', '" . $string . "'" . $ph . ")"; - } else { - // No I18N, replace placeholders by real words, if any - if (!empty($placeholders)) { - $phKeys = array_map(function($word) { - return '{' . $word . '}'; - }, array_keys($placeholders)); - $phValues = array_values($placeholders); - $str = "'" . str_replace($phKeys, $phValues, $string) . "'"; - } else { - // No placeholders, just the given string - $str = "'" . $string . "'"; - } - } - return $str; - } -} diff --git a/extensions/gii/GiiAsset.php b/extensions/gii/GiiAsset.php deleted file mode 100644 index 0e59d08f23..0000000000 --- a/extensions/gii/GiiAsset.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @since 2.0 - */ -class GiiAsset extends AssetBundle -{ - public $sourcePath = '@yii/gii/assets'; - public $css = [ - 'main.css', - ]; - public $js = [ - 'gii.js', - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - 'yii\bootstrap\BootstrapPluginAsset', - 'yii\gii\TypeAheadAsset', - ]; -} diff --git a/extensions/gii/Module.php b/extensions/gii/Module.php deleted file mode 100644 index 359a8d4a3d..0000000000 --- a/extensions/gii/Module.php +++ /dev/null @@ -1,165 +0,0 @@ - ['gii'], - * 'modules' => [ - * 'gii' => ['class' => 'yii\gii\Module'], - * ], - * ] - * ~~~ - * - * Because Gii generates new code files on the server, you should only use it on your own - * development machine. To prevent other people from using this module, by default, Gii - * can only be accessed by localhost. You may configure its [[allowedIPs]] property if - * you want to make it accessible on other machines. - * - * With the above configuration, you will be able to access GiiModule in your browser using - * the URL `http://localhost/path/to/index.php?r=gii` - * - * If your application enables [[\yii\web\UrlManager::enablePrettyUrl|pretty URLs]], - * you can then access Gii via URL: `http://localhost/path/to/index.php/gii` - * - * @author Qiang Xue - * @since 2.0 - */ -class Module extends \yii\base\Module implements BootstrapInterface -{ - /** - * @inheritdoc - */ - public $controllerNamespace = 'yii\gii\controllers'; - /** - * @var array the list of IPs that are allowed to access this module. - * Each array element represents a single IP filter which can be either an IP address - * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. - * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed - * by localhost. - */ - public $allowedIPs = ['127.0.0.1', '::1']; - /** - * @var array|Generator[] a list of generator configurations or instances. The array keys - * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator - * configurations or the instances. - * - * After the module is initialized, this property will become an array of generator instances - * which are created based on the configurations previously taken by this property. - * - * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former - * takes precedence in case when they have the same generator ID. - */ - public $generators = []; - /** - * @var integer the permission to be set for newly generated code files. - * This value will be used by PHP chmod function. - * Defaults to 0666, meaning the file is read-writable by all users. - */ - public $newFileMode = 0666; - /** - * @var integer the permission to be set for newly generated directories. - * This value will be used by PHP chmod function. - * Defaults to 0777, meaning the directory can be read, written and executed by all users. - */ - public $newDirMode = 0777; - - - /** - * @inheritdoc - */ - public function bootstrap($app) - { - if ($app instanceof \yii\web\Application) { - $app->getUrlManager()->addRules([ - $this->id => $this->id . '/default/index', - $this->id . '/' => $this->id . '/default/view', - $this->id . '//' => $this->id . '//', - ], false); - } elseif ($app instanceof \yii\console\Application) { - $app->controllerMap[$this->id] = [ - 'class' => 'yii\gii\console\GenerateController', - 'generators' => array_merge($this->coreGenerators(), $this->generators), - 'module' => $this, - ]; - } - } - - /** - * @inheritdoc - */ - public function beforeAction($action) - { - if (!parent::beforeAction($action)) { - return false; - } - - if (Yii::$app instanceof \yii\web\Application && !$this->checkAccess()) { - throw new ForbiddenHttpException('You are not allowed to access this page.'); - } - - foreach (array_merge($this->coreGenerators(), $this->generators) as $id => $config) { - $this->generators[$id] = Yii::createObject($config); - } - - $this->resetGlobalSettings(); - - return true; - } - - /** - * Resets potentially incompatible global settings done in app config. - */ - protected function resetGlobalSettings() - { - if (Yii::$app instanceof \yii\web\Application) { - Yii::$app->assetManager->bundles = []; - } - } - - /** - * @return boolean whether the module can be accessed by the current user - */ - protected function checkAccess() - { - $ip = Yii::$app->getRequest()->getUserIP(); - foreach ($this->allowedIPs as $filter) { - if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { - return true; - } - } - Yii::warning('Access to Gii is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); - - return false; - } - - /** - * Returns the list of the core code generator configurations. - * @return array the list of the core code generator configurations. - */ - protected function coreGenerators() - { - return [ - 'model' => ['class' => 'yii\gii\generators\model\Generator'], - 'crud' => ['class' => 'yii\gii\generators\crud\Generator'], - 'controller' => ['class' => 'yii\gii\generators\controller\Generator'], - 'form' => ['class' => 'yii\gii\generators\form\Generator'], - 'module' => ['class' => 'yii\gii\generators\module\Generator'], - 'extension' => ['class' => 'yii\gii\generators\extension\Generator'], - ]; - } -} diff --git a/extensions/gii/TypeAheadAsset.php b/extensions/gii/TypeAheadAsset.php deleted file mode 100644 index 49fd737568..0000000000 --- a/extensions/gii/TypeAheadAsset.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @since 2.0 - */ -class TypeAheadAsset extends AssetBundle -{ - public $sourcePath = '@bower/typeahead.js/dist'; - public $js = [ - 'typeahead.bundle.js', - ]; - public $depends = [ - 'yii\bootstrap\BootstrapAsset', - 'yii\bootstrap\BootstrapPluginAsset', - ]; -} diff --git a/extensions/gii/assets/gii.js b/extensions/gii/assets/gii.js deleted file mode 100644 index c9ab19875e..0000000000 --- a/extensions/gii/assets/gii.js +++ /dev/null @@ -1,187 +0,0 @@ -yii.gii = (function ($) { - var isActive = $('.default-view').length > 0; - - var initHintBlocks = function () { - $('.hint-block').each(function () { - var $hint = $(this); - $hint.parent().find('label').addClass('help').popover({ - html: true, - trigger: 'hover', - placement: 'right', - content: $hint.html() - }); - }); - }; - - var initStickyInputs = function () { - $('.sticky:not(.error)').find('input[type="text"],select,textarea').each(function () { - var value; - if (this.tagName === 'SELECT') { - value = this.options[this.selectedIndex].text; - } else if (this.tagName === 'TEXTAREA') { - value = $(this).html(); - } else { - value = $(this).val(); - } - if (value === '') { - value = '[empty]'; - } - $(this).before('
    ' + value + '
    ').hide(); - }); - $('.sticky-value').on('click', function () { - $(this).hide(); - $(this).next().show().get(0).focus(); - }); - }; - - var initPreviewDiffLinks = function () { - $('.preview-code, .diff-code, .modal-refresh, .modal-previous, .modal-next').on('click', function () { - var $modal = $('#preview-modal'); - var $link = $(this); - $modal.find('.modal-refresh').attr('href', $link.attr('href')); - if ($link.hasClass('preview-code') || $link.hasClass('diff-code')) { - $modal.data('action', ($link.hasClass('preview-code') ? 'preview-code' : 'diff-code')) - } - $modal.find('.modal-title').text($link.data('title')); - $modal.find('.modal-body').html('Loading ...'); - $modal.modal('show'); - var checkbox = $('a.' + $modal.data('action') + '[href="' + $link.attr('href') + '"]').closest('tr').find('input').get(0); - var checked = false; - if (checkbox) { - checked = checkbox.checked; - $modal.find('.modal-checkbox').removeClass('disabled'); - } else { - $modal.find('.modal-checkbox').addClass('disabled'); - } - $modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked); - $.ajax({ - type: 'POST', - cache: false, - url: $link.prop('href'), - data: $('.default-view form').serializeArray(), - success: function (data) { - if (!$link.hasClass('modal-refresh')) { - var filesSelector = 'a.' + $modal.data('action'); - var $files = $(filesSelector); - var index = $files.filter('[href="' + $link.attr('href') + '"]').index(filesSelector); - var $prev = $files.eq(index - 1); - var $next = $files.eq((index + 1 == $files.length ? 0 : index + 1)); - $modal.data('current', $files.eq(index)); - $modal.find('.modal-previous').attr('href', $prev.attr('href')).data('title', $prev.data('title')); - $modal.find('.modal-next').attr('href', $next.attr('href')).data('title', $next.data('title')); - } - $modal.find('.modal-body').html(data); - $modal.find('.content').css('max-height', ($(window).height() - 200) + 'px'); - }, - error: function (XMLHttpRequest, textStatus, errorThrown) { - $modal.find('.modal-body').html('
    ' + XMLHttpRequest.responseText + '
    '); - } - }); - return false; - }); - - $('#preview-modal').on('keydown', function (e) { - if (e.keyCode === 37) { - $('.modal-previous').trigger('click'); - } else if (e.keyCode === 39) { - $('.modal-next').trigger('click'); - } else if (e.keyCode === 82) { - $('.modal-refresh').trigger('click'); - } else if (e.keyCode === 32) { - $('.modal-checkbox').trigger('click'); - } - }); - - $('.modal-checkbox').on('click', checkFileToggle); - }; - - var checkFileToggle = function () { - var $modal = $('#preview-modal'); - var $checkbox = $modal.data('current').closest('tr').find('input'); - var checked = !$checkbox.prop('checked'); - $checkbox.prop('checked', checked); - $modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked); - return false; - }; - - var checkAllToggle = function () { - $('#check-all').prop('checked', !$('.default-view-files table .check input:enabled:not(:checked)').length); - }; - - var initConfirmationCheckboxes = function () { - var $checkAll = $('#check-all'); - $checkAll.click(function () { - $('.default-view-files table .check input:enabled').prop('checked', this.checked); - }); - $('.default-view-files table .check input').click(function () { - checkAllToggle(); - }); - checkAllToggle(); - }; - - var initToggleActions = function () { - $('#action-toggle :input').change(function () { - $(this).parent('label').toggleClass('active', this.checked); - $('.' + this.value, '.default-view-files table').toggle(this.checked).find('.check input').attr('disabled', !this.checked); - checkAllToggle(); - }); - }; - - return { - autocomplete: function (counter, data) { - var datum = new Bloodhound({ - datumTokenizer: function (d) { - return Bloodhound.tokenizers.whitespace(d.word); - }, - queryTokenizer: Bloodhound.tokenizers.whitespace, - local: data - }); - datum.initialize(); - jQuery('.typeahead-' + counter).typeahead(null, {displayKey: 'word', source: datum.ttAdapter()}); - }, - init: function () { - initHintBlocks(); - initStickyInputs(); - initPreviewDiffLinks(); - initConfirmationCheckboxes(); - initToggleActions(); - - // model generator: hide class name input when table name input contains * - $('#model-generator #generator-tablename').change(function () { - $('.field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); - }).change(); - - // model generator: translate table name to model class - $('#model-generator #generator-tablename').on('blur', function () { - var tableName = $(this).val(); - if ($('#generator-modelclass').val()=='' && tableName && tableName.indexOf('*') === -1){ - var modelClass=''; - $.each(tableName.split('_'), function() { - if(this.length>0) - modelClass+=this.substring(0,1).toUpperCase()+this.substring(1); - }); - $('#generator-modelclass').val(modelClass); - } - }); - - // hide message category when I18N is disabled - $('form #generator-enablei18n').change(function () { - $('form .field-generator-messagecategory').toggle($(this).is(':checked')); - }).change(); - - // hide Generate button if any input is changed - $('.default-view .form-group input,select,textarea').change(function () { - $('.default-view-results,.default-view-files').hide(); - $('.default-view button[name="generate"]').hide(); - }); - - $('.module-form #generator-moduleclass').change(function () { - var value = $(this).val().match(/(\w+)\\\w+$/); - var $idInput = $('#generator-moduleid'); - if (value && value[1] && $idInput.val() == '') { - $idInput.val(value[1]); - } - }); - } - }; -})(jQuery); diff --git a/extensions/gii/assets/logo.png b/extensions/gii/assets/logo.png deleted file mode 100644 index e48b5aad64..0000000000 Binary files a/extensions/gii/assets/logo.png and /dev/null differ diff --git a/extensions/gii/assets/main.css b/extensions/gii/assets/main.css deleted file mode 100644 index 87d5f90f6d..0000000000 --- a/extensions/gii/assets/main.css +++ /dev/null @@ -1,259 +0,0 @@ -body { - padding-top: 70px; -} - -.footer { - border-top: 1px solid #ddd; - margin-top: 30px; - padding: 15px 0 30px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.navbar-brand { - padding: 0; - margin: 0; -} - -.default-index .generator { - min-height: 200px; - margin-bottom: 20px; -} - -.list-group .glyphicon { - float: right; -} - -.popover { - max-width: 400px; - width: 400px; -} - -.hint-block { - display: none; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} - -.default-view .sticky-value { - padding: 6px 12px; - background: lightyellow; - white-space: pre; - word-wrap: break-word; -} - -.default-view .form-group label.help { - border-bottom: 1px dashed #888; - cursor: help; -} - -.default-view .modal-dialog { - width: 800px; -} - -.default-view .modal-dialog .error { - color: #d9534f; -} - -.default-view .modal-dialog .content { - background: #fafafa; - border-left: #eee 5px solid; - padding: 5px 10px; - overflow: auto; -} - -.default-view .modal-dialog code { - background: transparent; -} - -.default-view-files table .action { - width: 100px; -} - -.default-view-files table .check { - width: 25px; - text-align: center; -} - -.default-view-results pre { - overflow: auto; - background-color: #333; - max-height: 300px; - color: white; - padding: 10px; - border-radius: 0; - white-space: nowrap; -} - -.default-view-results pre .error { - background: #FFE0E1; - color: black; - padding: 1px; -} - -.default-view-results .alert pre { - background: white; -} - -.default-diff pre { - padding: 0; - margin: 0; - background: transparent; - border: none; -} - -.default-diff pre del { - background: pink; -} - -.default-diff pre ins { - background: lightgreen; - text-decoration: none; -} - -.Differences { - width: 100%; - border-collapse: collapse; - border-spacing: 0; - empty-cells: show; -} - -.Differences thead { - display: none; -} - -.Differences tbody th { - text-align: right; - background: #FAFAFA; - padding: 1px 2px; - border-right: 1px solid #eee; - vertical-align: top; - font-size: 13px; - font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; - font-weight: normal; - color: #999; - width: 5px; -} - -.Differences td { - padding: 1px 2px; - font-size: 13px; - font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; -} - -.DifferencesSideBySide .ChangeInsert td.Left { - background: #dfd; -} - -.DifferencesSideBySide .ChangeInsert td.Right { - background: #cfc; -} - -.DifferencesSideBySide .ChangeDelete td.Left { - background: #f88; -} - -.DifferencesSideBySide .ChangeDelete td.Right { - background: #faa; -} - -.DifferencesSideBySide .ChangeReplace .Left { - background: #fe9; -} - -.DifferencesSideBySide .ChangeReplace .Right { - background: #fd8; -} - -.Differences ins, .Differences del { - text-decoration: none; -} - -.DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del { - background: #fc0; -} - -.Differences .Skipped { - background: #f7f7f7; -} - -.DifferencesInline .ChangeReplace .Left, -.DifferencesInline .ChangeDelete .Left { - background: #fdd; -} - -.DifferencesInline .ChangeReplace .Right, -.DifferencesInline .ChangeInsert .Right { - background: #dfd; -} - -.DifferencesInline .ChangeReplace ins { - background: #9e9; -} - -.DifferencesInline .ChangeReplace del { - background: #e99; -} - -.DifferencesInline th[data-line-number]:before { - content: attr(data-line-number); -} - -/* additional styles for typeahead.js, adapted from http://twitter.github.io/typeahead.js/examples/ */ - -.twitter-typeahead { - display: block !important; -} - -.tt-query { - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.tt-hint { - color: #999 -} - -.tt-dropdown-menu { - width: 422px; - margin-top: 2px; - padding: 8px 0; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); -} - -.tt-suggestion { - padding: 3px 20px; - font-size: 18px; - line-height: 24px; -} - -.tt-suggestion.tt-cursor { - color: #fff; - background-color: #0097cf; - -} - -.tt-suggestion p { - margin: 0; -} - diff --git a/extensions/gii/components/ActiveField.php b/extensions/gii/components/ActiveField.php deleted file mode 100644 index a810338968..0000000000 --- a/extensions/gii/components/ActiveField.php +++ /dev/null @@ -1,75 +0,0 @@ - - * @since 2.0 - */ -class ActiveField extends \yii\widgets\ActiveField -{ - /** - * @var Generator - */ - public $model; - - - /** - * @inheritdoc - */ - public function init() - { - $stickyAttributes = $this->model->stickyAttributes(); - if (in_array($this->attribute, $stickyAttributes)) { - $this->sticky(); - } - $hints = $this->model->hints(); - if (isset($hints[$this->attribute])) { - $this->hint($hints[$this->attribute]); - } - $autoCompleteData = $this->model->autoCompleteData(); - if (isset($autoCompleteData[$this->attribute])) { - if (is_callable($autoCompleteData[$this->attribute])) { - $this->autoComplete(call_user_func($autoCompleteData[$this->attribute])); - } else { - $this->autoComplete($autoCompleteData[$this->attribute]); - } - } - } - - /** - * Makes field remember its value between page reloads - * @return static the field object itself - */ - public function sticky() - { - $this->options['class'] .= ' sticky'; - - return $this; - } - - /** - * Makes field auto completable - * @param array $data auto complete data (array of callables or scalars) - * @return static the field object itself - */ - public function autoComplete($data) - { - static $counter = 0; - $this->inputOptions['class'] .= ' typeahead typeahead-' . (++$counter); - foreach ($data as &$item) { - $item = ['word' => $item]; - } - $this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");"); - - return $this; - } -} diff --git a/extensions/gii/components/DiffRendererHtmlInline.php b/extensions/gii/components/DiffRendererHtmlInline.php deleted file mode 100644 index dbaf007bce..0000000000 --- a/extensions/gii/components/DiffRendererHtmlInline.php +++ /dev/null @@ -1,136 +0,0 @@ - - * @since 2.0 - */ -class DiffRendererHtmlInline extends \Diff_Renderer_Html_Array -{ - /** - * Render a and return diff with changes between the two sequences - * displayed inline (under each other) - * - * @return string The generated inline diff. - */ - public function render() - { - $changes = parent::render(); - $html = ''; - if (empty($changes)) { - return $html; - } - - $html .= << - - - Old - New - Differences - - -HTML; - foreach ($changes as $i => $blocks) { - // If this is a separate block, we're condensing code so output ..., - // indicating a significant portion of the code has been collapsed as - // it is the same - if ($i > 0) { - $html .= << - - -   - -HTML; - } - - foreach ($blocks as $change) { - $tag = ucfirst($change['tag']); - $html .= << -HTML; - // Equal changes should be shown on both sides of the diff - if ($change['tag'] === 'equal') { - foreach ($change['base']['lines'] as $no => $line) { - $fromLine = $change['base']['offset'] + $no + 1; - $toLine = $change['changed']['offset'] + $no + 1; - $html .= << - - - {$line} - -HTML; - } - } - // Added lines only on the right side - elseif ($change['tag'] === 'insert') { - foreach ($change['changed']['lines'] as $no => $line) { - $toLine = $change['changed']['offset'] + $no + 1; - $html .= << - - - {$line}  - -HTML; - } - } - // Show deleted lines only on the left side - elseif ($change['tag'] === 'delete') { - foreach ($change['base']['lines'] as $no => $line) { - $fromLine = $change['base']['offset'] + $no + 1; - $html .= << - - - {$line}  - -HTML; - } - } - // Show modified lines on both sides - elseif ($change['tag'] === 'replace') { - foreach ($change['base']['lines'] as $no => $line) { - $fromLine = $change['base']['offset'] + $no + 1; - $html .= << - - - {$line} - -HTML; - } - - foreach ($change['changed']['lines'] as $no => $line) { - $toLine = $change['changed']['offset'] + $no + 1; - $html .= << - - - {$line} - -HTML; - } - } - $html .= << -HTML; - } - } - $html .= << -HTML; - - return $html; - } -} diff --git a/extensions/gii/composer.json b/extensions/gii/composer.json deleted file mode 100644 index f1915eb95c..0000000000 --- a/extensions/gii/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "yiisoft/yii2-gii", - "description": "The Gii extension for the Yii framework", - "keywords": ["yii2", "gii", "code generator"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Agii", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "yiisoft/yii2-bootstrap": "*", - "phpspec/php-diff": ">=1.0.2", - "bower-asset/typeahead.js": "0.10.*" - }, - "autoload": { - "psr-4": { - "yii\\gii\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/gii/console/GenerateAction.php b/extensions/gii/console/GenerateAction.php deleted file mode 100644 index ff9637d943..0000000000 --- a/extensions/gii/console/GenerateAction.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -class GenerateAction extends \yii\base\Action -{ - /** - * @var \yii\gii\Generator - */ - public $generator; - /** - * @var GenerateController - */ - public $controller; - - /** - * @inheritdoc - */ - public function run() - { - echo "Running '{$this->generator->getName()}'...\n\n"; - - if ($this->generator->validate()) { - $this->generateCode(); - } else { - $this->displayValidationErrors(); - } - } - - protected function displayValidationErrors() - { - $this->controller->stdout("Code not generated. Please fix the following errors:\n\n", Console::FG_RED); - foreach ($this->generator->errors as $attribute => $errors) { - echo ' - ' . $this->controller->ansiFormat($attribute, Console::FG_CYAN) . ': ' . implode('; ', $errors) . "\n"; - } - echo "\n"; - } - - protected function generateCode() - { - $files = $this->generator->generate(); - $n = count($files); - if ($n === 0) { - echo "No code to be generated.\n"; - return; - } - echo "The following files will be generated:\n"; - $skipAll = $this->controller->interactive ? null : !$this->controller->overwrite; - $answers = []; - foreach ($files as $file) { - $path = $file->getRelativePath(); - if (is_file($file->path)) { - if (file_get_contents($file->path) === $file->content) { - echo ' ' . $this->controller->ansiFormat('[unchanged]', Console::FG_GREY); - echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); - $answers[$file->id] = false; - } else { - echo ' ' . $this->controller->ansiFormat('[changed]', Console::FG_RED); - echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); - if ($skipAll !== null) { - $answers[$file->id] = !$skipAll; - } else { - $answer = $this->controller->select("Do you want to overwrite this file?", [ - 'y' => 'Overwrite this file.', - 'n' => 'Skip this file.', - 'ya' => 'Overwrite this and the rest of the changed files.', - 'na' => 'Skip this and the rest of the changed files.', - ]); - $answers[$file->id] = $answer === 'y' || $answer === 'ya'; - if ($answer === 'ya') { - $skipAll = false; - } elseif ($answer === 'na') { - $skipAll = true; - } - } - } - } else { - echo ' ' . $this->controller->ansiFormat('[new]', Console::FG_GREEN); - echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); - $answers[$file->id] = true; - } - } - - if (!array_sum($answers)) { - $this->controller->stdout("\nNo files were chosen to be generated.\n", Console::FG_CYAN); - return; - } - - if (!$this->controller->confirm("\nReady to generate the selected files?", true)) { - $this->controller->stdout("\nNo file was generated.\n", Console::FG_CYAN); - return; - } - - if ($this->generator->save($files, (array) $answers, $results)) { - $this->controller->stdout("\nFiles were generated successfully!\n", Console::FG_GREEN); - } else { - $this->controller->stdout("\nSome errors occurred while generating the files.", Console::FG_RED); - } - echo preg_replace('%(.*?)%', '\1', $results) . "\n"; - } -} diff --git a/extensions/gii/console/GenerateController.php b/extensions/gii/console/GenerateController.php deleted file mode 100644 index 3592f688f2..0000000000 --- a/extensions/gii/console/GenerateController.php +++ /dev/null @@ -1,207 +0,0 @@ - - * @author Qiang Xue - * @since 2.0 - */ -class GenerateController extends Controller -{ - /** - * @var \yii\gii\Module - */ - public $module; - /** - * @var boolean whether to overwrite all existing code files when in non-interactive mode. - * Defaults to false, meaning none of the existing code files will be overwritten. - * This option is used only when `--interactive=0`. - */ - public $overwrite = false; - /** - * @var array a list of the available code generators - */ - public $generators = []; - - /** - * @var array generator option values - */ - private $_options = []; - - - /** - * @inheritdoc - */ - public function __get($name) - { - return isset($this->_options[$name]) ? $this->_options[$name] : null; - } - - /** - * @inheritdoc - */ - public function __set($name, $value) - { - $this->_options[$name] = $value; - } - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - foreach ($this->generators as $id => $config) { - $this->generators[$id] = Yii::createObject($config); - } - } - - /** - * @inheritdoc - */ - public function createAction($id) - { - /** @var $action GenerateAction */ - $action = parent::createAction($id); - foreach ($this->_options as $name => $value) { - $action->generator->$name = $value; - } - return $action; - } - - /** - * @inheritdoc - */ - public function actions() - { - $actions = []; - foreach ($this->generators as $name => $generator) { - $actions[$name] = [ - 'class' => 'yii\gii\console\GenerateAction', - 'generator' => $generator, - ]; - } - return $actions; - } - - public function actionIndex() - { - $this->run('/help', ['gii']); - } - - /** - * @inheritdoc - */ - public function getUniqueID() - { - return $this->id; - } - - /** - * @inheritdoc - */ - public function options($id) - { - $options = parent::options($id); - $options[] = 'overwrite'; - - if (!isset($this->generators[$id])) { - return $options; - } - - $attributes = $this->generators[$id]->attributes; - unset($attributes['templates']); - return array_merge( - $options, - array_keys($attributes) - ); - } - - /** - * @inheritdoc - */ - public function getActionHelpSummary($action) - { - if ($action instanceof InlineAction) { - return parent::getActionHelpSummary($action); - } else { - /** @var $action GenerateAction */ - return $action->generator->getName(); - } - } - - /** - * @inheritdoc - */ - public function getActionHelp($action) - { - if ($action instanceof InlineAction) { - return parent::getActionHelp($action); - } else { - /** @var $action GenerateAction */ - $description = $action->generator->getDescription(); - return wordwrap(preg_replace('/\s+/', ' ', $description)); - } - } - - /** - * @inheritdoc - */ - public function getActionArgsHelp($action) - { - return []; - } - - /** - * @inheritdoc - */ - public function getActionOptionsHelp($action) - { - if ($action instanceof InlineAction) { - return parent::getActionOptionsHelp($action); - } - /** @var $action GenerateAction */ - $attributes = $action->generator->attributes; - unset($attributes['templates']); - $hints = $action->generator->hints(); - - $options = parent::getActionOptionsHelp($action); - foreach ($attributes as $name => $value) { - $type = gettype($value); - $options[$name] = [ - 'type' => $type === 'NULL' ? 'string' : $type, - 'required' => $value === null && $action->generator->isAttributeRequired($name), - 'default' => $value, - 'comment' => isset($hints[$name]) ? $this->formatHint($hints[$name]) : '', - ]; - } - - return $options; - } - - protected function formatHint($hint) - { - $hint = preg_replace('%(.*?)%', '\1', $hint); - $hint = preg_replace('/\s+/', ' ', $hint); - return wordwrap($hint); - } -} diff --git a/extensions/gii/controllers/DefaultController.php b/extensions/gii/controllers/DefaultController.php deleted file mode 100644 index f1da8f7d15..0000000000 --- a/extensions/gii/controllers/DefaultController.php +++ /dev/null @@ -1,130 +0,0 @@ - - * @since 2.0 - */ -class DefaultController extends Controller -{ - public $layout = 'generator'; - /** - * @var \yii\gii\Module - */ - public $module; - /** - * @var \yii\gii\Generator - */ - public $generator; - - - public function actionIndex() - { - $this->layout = 'main'; - - return $this->render('index'); - } - - public function actionView($id) - { - $generator = $this->loadGenerator($id); - $params = ['generator' => $generator, 'id' => $id]; - if (isset($_POST['preview']) || isset($_POST['generate'])) { - if ($generator->validate()) { - $generator->saveStickyAttributes(); - $files = $generator->generate(); - if (isset($_POST['generate']) && !empty($_POST['answers'])) { - $params['hasError'] = !$generator->save($files, (array) $_POST['answers'], $results); - $params['results'] = $results; - } else { - $params['files'] = $files; - $params['answers'] = isset($_POST['answers']) ? $_POST['answers'] : null; - } - } - } - - return $this->render('view', $params); - } - - public function actionPreview($id, $file) - { - $generator = $this->loadGenerator($id); - if ($generator->validate()) { - foreach ($generator->generate() as $f) { - if ($f->id === $file) { - $content = $f->preview(); - if ($content !== false) { - return '
    ' . $content . ''; - } else { - return '
    Preview is not available for this file type.
    '; - } - } - } - } - throw new NotFoundHttpException("Code file not found: $file"); - } - - public function actionDiff($id, $file) - { - $generator = $this->loadGenerator($id); - if ($generator->validate()) { - foreach ($generator->generate() as $f) { - if ($f->id === $file) { - return $this->renderPartial('diff', [ - 'diff' => $f->diff(), - ]); - } - } - } - throw new NotFoundHttpException("Code file not found: $file"); - } - - /** - * Runs an action defined in the generator. - * Given an action named "xyz", the method "actionXyz()" in the generator will be called. - * If the method does not exist, a 400 HTTP exception will be thrown. - * @param string $id the ID of the generator - * @param string $name the action name - * @return mixed the result of the action. - * @throws NotFoundHttpException if the action method does not exist. - */ - public function actionAction($id, $name) - { - $generator = $this->loadGenerator($id); - $method = 'action' . $name; - if (method_exists($generator, $method)) { - return $generator->$method(); - } else { - throw new NotFoundHttpException("Unknown generator action: $name"); - } - } - - /** - * Loads the generator with the specified ID. - * @param string $id the ID of the generator to be loaded. - * @return \yii\gii\Generator the loaded generator - * @throws NotFoundHttpException - */ - protected function loadGenerator($id) - { - if (isset($this->module->generators[$id])) { - $this->generator = $this->module->generators[$id]; - $this->generator->loadStickyAttributes(); - $this->generator->load($_POST); - - return $this->generator; - } else { - throw new NotFoundHttpException("Code generator not found: $id"); - } - } -} diff --git a/extensions/gii/generators/controller/default/controller.php b/extensions/gii/generators/controller/default/controller.php deleted file mode 100644 index bc38880276..0000000000 --- a/extensions/gii/generators/controller/default/controller.php +++ /dev/null @@ -1,26 +0,0 @@ - - -namespace getControllerNamespace() ?>; - -class controllerClass) ?> extends baseClass, '\\') . "\n" ?> -{ -getActionIDs() as $action): ?> - public function action() - { - return $this->render(''); - } - - -} diff --git a/extensions/gii/generators/controller/default/view.php b/extensions/gii/generators/controller/default/view.php deleted file mode 100644 index 06f0ce25cc..0000000000 --- a/extensions/gii/generators/controller/default/view.php +++ /dev/null @@ -1,20 +0,0 @@ - -/* @var $this yii\web\View */ -" ?> - -

    getControllerID() . '/' . $action ?>

    - -

    - You may change the content of this page by modifying - the file __FILE__; ?>. -

    diff --git a/extensions/gii/generators/controller/form.php b/extensions/gii/generators/controller/form.php deleted file mode 100644 index 9a6e8cd2f9..0000000000 --- a/extensions/gii/generators/controller/form.php +++ /dev/null @@ -1,9 +0,0 @@ -field($generator, 'controllerClass'); -echo $form->field($generator, 'actions'); -echo $form->field($generator, 'viewPath'); -echo $form->field($generator, 'baseClass'); diff --git a/extensions/gii/generators/crud/default/controller.php b/extensions/gii/generators/crud/default/controller.php deleted file mode 100644 index 2c7f6f8643..0000000000 --- a/extensions/gii/generators/crud/default/controller.php +++ /dev/null @@ -1,173 +0,0 @@ -controllerClass); -$modelClass = StringHelper::basename($generator->modelClass); -$searchModelClass = StringHelper::basename($generator->searchModelClass); -if ($modelClass === $searchModelClass) { - $searchModelAlias = $searchModelClass . 'Search'; -} - -/* @var $class ActiveRecordInterface */ -$class = $generator->modelClass; -$pks = $class::primaryKey(); -$urlParams = $generator->generateUrlParams(); -$actionParams = $generator->generateActionParams(); -$actionParamComments = $generator->generateActionParamComments(); - -echo " - -namespace controllerClass, '\\')) ?>; - -use Yii; -use modelClass, '\\') ?>; -searchModelClass)): ?> -use searchModelClass, '\\') . (isset($searchModelAlias) ? " as $searchModelAlias" : "") ?>; - -use yii\data\ActiveDataProvider; - -use baseControllerClass, '\\') ?>; -use yii\web\NotFoundHttpException; -use yii\filters\VerbFilter; - -/** - * implements the CRUD actions for model. - */ -class extends baseControllerClass) . "\n" ?> -{ - public function behaviors() - { - return [ - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all models. - * @return mixed - */ - public function actionIndex() - { -searchModelClass)): ?> - $searchModel = new (); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - - $dataProvider = new ActiveDataProvider([ - 'query' => ::find(), - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - - } - - /** - * Displays a single model. - * - * @return mixed - */ - public function actionView() - { - return $this->render('view', [ - 'model' => $this->findModel(), - ]); - } - - /** - * Creates a new model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new (); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', ]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing model. - * If update is successful, the browser will be redirected to the 'view' page. - * - * @return mixed - */ - public function actionUpdate() - { - $model = $this->findModel(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', ]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * - * @return mixed - */ - public function actionDelete() - { - $this->findModel()->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * - * @return the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel() - { - \$$pk"; - } - $condition = '[' . implode(', ', $condition) . ']'; -} -?> - if (($model = ::findOne()) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/extensions/gii/generators/crud/default/views/_form.php b/extensions/gii/generators/crud/default/views/_form.php deleted file mode 100644 index b600266788..0000000000 --- a/extensions/gii/generators/crud/default/views/_form.php +++ /dev/null @@ -1,42 +0,0 @@ -modelClass(); -$safeAttributes = $model->safeAttributes(); -if (empty($safeAttributes)) { - $safeAttributes = $model->attributes(); -} - -echo " - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/* @var $this yii\web\View */ -/* @var $model modelClass, '\\') ?> */ -/* @var $form yii\widgets\ActiveForm */ -?> - -
    - - $form = ActiveForm::begin(); ?> - -getColumnNames() as $attribute) { - if (in_array($attribute, $safeAttributes)) { - echo " generateActiveField($attribute) . " ?>\n\n"; - } -} ?> -
    - Html::submitButton($model->isNewRecord ? generateString('Create') ?> : generateString('Update') ?>, ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - ActiveForm::end(); ?> - -
    diff --git a/extensions/gii/generators/crud/default/views/_search.php b/extensions/gii/generators/crud/default/views/_search.php deleted file mode 100644 index fc45d2e1fc..0000000000 --- a/extensions/gii/generators/crud/default/views/_search.php +++ /dev/null @@ -1,44 +0,0 @@ - - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/* @var $this yii\web\View */ -/* @var $model searchModelClass, '\\') ?> */ -/* @var $form yii\widgets\ActiveForm */ -?> - - diff --git a/extensions/gii/generators/crud/default/views/update.php b/extensions/gii/generators/crud/default/views/update.php deleted file mode 100644 index 6c2d75bb94..0000000000 --- a/extensions/gii/generators/crud/default/views/update.php +++ /dev/null @@ -1,32 +0,0 @@ -generateUrlParams(); - -echo " - -use yii\helpers\Html; - -/* @var $this yii\web\View */ -/* @var $model modelClass, '\\') ?> */ - -$this->title = generateString('Update {modelClass}: ', ['modelClass' => Inflector::camel2words(StringHelper::basename($generator->modelClass))]) ?> . ' ' . $model->getNameAttribute() ?>; -$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->getNameAttribute() ?>, 'url' => ['view', ]]; -$this->params['breadcrumbs'][] = generateString('Update') ?>; -?> -
    - -

    Html::encode($this->title) ?>

    - - $this->render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/extensions/gii/generators/crud/default/views/view.php b/extensions/gii/generators/crud/default/views/view.php deleted file mode 100644 index 00ee1d9d0e..0000000000 --- a/extensions/gii/generators/crud/default/views/view.php +++ /dev/null @@ -1,57 +0,0 @@ -generateUrlParams(); - -echo " - -use yii\helpers\Html; -use yii\widgets\DetailView; - -/* @var $this yii\web\View */ -/* @var $model modelClass, '\\') ?> */ - -$this->title = $model->getNameAttribute() ?>; -$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    Html::encode($this->title) ?>

    - -

    - Html::a(generateString('Update') ?>, ['update', ], ['class' => 'btn btn-primary']) ?> - Html::a(generateString('Delete') ?>, ['delete', ], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => generateString('Are you sure you want to delete this item?') ?>, - 'method' => 'post', - ], - ]) ?> -

    - - DetailView::widget([ - 'model' => $model, - 'attributes' => [ -getTableSchema()) === false) { - foreach ($generator->getColumnNames() as $name) { - echo " '" . $name . "',\n"; - } -} else { - foreach ($generator->getTableSchema()->columns as $column) { - $format = $generator->generateColumnFormat($column); - echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; - } -} -?> - ], - ]) ?> - -
    diff --git a/extensions/gii/generators/crud/form.php b/extensions/gii/generators/crud/form.php deleted file mode 100644 index 22aada31c0..0000000000 --- a/extensions/gii/generators/crud/form.php +++ /dev/null @@ -1,16 +0,0 @@ -field($generator, 'modelClass'); -echo $form->field($generator, 'searchModelClass'); -echo $form->field($generator, 'controllerClass'); -echo $form->field($generator, 'viewPath'); -echo $form->field($generator, 'baseControllerClass'); -echo $form->field($generator, 'indexWidgetType')->dropDownList([ - 'grid' => 'GridView', - 'list' => 'ListView', -]); -echo $form->field($generator, 'enableI18N')->checkbox(); -echo $form->field($generator, 'messageCategory'); diff --git a/extensions/gii/generators/extension/Generator.php b/extensions/gii/generators/extension/Generator.php deleted file mode 100644 index 6e95630cc7..0000000000 --- a/extensions/gii/generators/extension/Generator.php +++ /dev/null @@ -1,273 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $vendorName; - public $packageName = "yii2-"; - public $namespace; - public $type = "yii2-extension"; - public $keywords = "yii2,extension"; - public $title; - public $description; - public $outputPath = "@app/runtime/tmp-extensions"; - public $license; - public $authorName; - public $authorEmail; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Extension Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator helps you to generate the files needed by a Yii extension.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge( - parent::rules(), - [ - [['vendorName', 'packageName'], 'filter', 'filter' => 'trim'], - [ - [ - 'vendorName', - 'packageName', - 'namespace', - 'type', - 'license', - 'title', - 'description', - 'authorName', - 'authorEmail', - 'outputPath' - ], - 'required' - ], - [['keywords'], 'safe'], - [['authorEmail'], 'email'], - [ - ['vendorName', 'packageName'], - 'match', - 'pattern' => '/^[a-z0-9\-\.]+$/', - 'message' => 'Only lowercase word characters, dashes and dots are allowed.' - ], - [ - ['namespace'], - 'match', - 'pattern' => '/^[a-zA-Z0-9\\\]+\\\$/', - 'message' => 'Only letters, numbers and backslashes are allowed. PSR-4 namespaces must end with a namespace separator.' - ], - ] - ); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'vendorName' => 'Vendor Name', - 'packageName' => 'Package Name', - 'license' => 'License', - ]; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'vendorName' => 'This refers to the name of the publisher, your GitHub user name is usually a good choice, eg. myself.', - 'packageName' => 'This is the name of the extension on packagist, eg. yii2-foobar.', - 'namespace' => 'PSR-4, eg. myself\foobar\ This will be added to your autoloading by composer. Do not use yii, yii2 or yiisoft in the namespace.', - 'keywords' => 'Comma separated keywords for this extension.', - 'outputPath' => 'The temporary location of the generated files.', - 'title' => 'A more descriptive name of your application for the README file.', - 'description' => 'A sentence or subline describing the main purpose of the extension.', - ]; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return ['vendorName', 'outputPath', 'authorName', 'authorEmail']; - } - - /** - * @inheritdoc - */ - public function successMessage() - { - $outputPath = realpath(\Yii::getAlias($this->outputPath)); - $output1 = <<The extension has been generated successfully.

    -

    To enable it in your application, you need to create a git repository -and require it via composer.

    -EOD; - $code1 = <<packageName} - -git init -git add -A -git commit -git remote add origin https://path.to/your/repo -git push -u origin master -EOD; - $output2 = <<The next step is just for initial development, skip it if you directly publish the extension on packagist.org

    -

    Add the newly created repo to your composer.json.

    -EOD; - $code2 = <<Note: You may use the url file://{$outputPath}/{$this->packageName} for testing.

    -

    Require the package with composer

    -EOD; - $code3 = <<vendorName}/{$this->packageName}:dev-master -EOD; - $output4 = <<And use it in your application.

    -EOD; - $code4 = <<namespace}AutoloadExample::widget(); -EOD; - $output5 = <<When you have finished development register your extension at packagist.org.

    -EOD; - - $return = $output1 . '
    ' . highlight_string($code1, true) . '
    '; - $return .= $output2 . '
    ' . highlight_string($code2, true) . '
    '; - $return .= $output3 . '
    ' . highlight_string($code3, true) . '
    '; - $return .= $output4 . '
    ' . highlight_string($code4, true) . '
    '; - $return .= $output5; - - return $return; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['composer.json', 'AutoloadExample.php', 'README.md']; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $modulePath = $this->getOutputPath(); - $files[] = new CodeFile( - $modulePath . '/' . $this->packageName . '/composer.json', - $this->render("composer.json") - ); - $files[] = new CodeFile( - $modulePath . '/' . $this->packageName . '/AutoloadExample.php', - $this->render("AutoloadExample.php") - ); - $files[] = new CodeFile( - $modulePath . '/' . $this->packageName . '/README.md', - $this->render("README.md") - ); - - return $files; - } - - /** - * @return boolean the directory that contains the module class - */ - public function getOutputPath() - { - return Yii::getAlias($this->outputPath); - } - - /** - * @return string a json encoded array with the given keywords - */ - public function getKeywordsArrayJson() - { - return json_encode(explode(',', $this->keywords), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - } - - /** - * @return array options for type drop-down - */ - public function optsType() - { - $licenses = [ - 'yii2-extension', - 'library', - ]; - - return array_combine($licenses, $licenses); - } - - /** - * @return array options for license drop-down - */ - public function optsLicense() - { - $licenses = [ - 'Apache-2.0', - 'BSD-2-Clause', - 'BSD-3-Clause', - 'BSD-4-Clause', - 'GPL-2.0', - 'GPL-2.0+', - 'GPL-3.0', - 'GPL-3.0+', - 'LGPL-2.1', - 'LGPL-2.1+', - 'LGPL-3.0', - 'LGPL-3.0+', - 'MIT' - ]; - - return array_combine($licenses, $licenses); - } -} diff --git a/extensions/gii/generators/extension/default/AutoloadExample.php b/extensions/gii/generators/extension/default/AutoloadExample.php deleted file mode 100644 index 694f0ab21d..0000000000 --- a/extensions/gii/generators/extension/default/AutoloadExample.php +++ /dev/null @@ -1,14 +0,0 @@ - - -namespace namespace, 0, -1) ?>; - -/** - * This is just an example. - */ -class AutoloadExample extends \yii\base\Widget -{ - public function run() - { - return "Hello!"; - } -} diff --git a/extensions/gii/generators/extension/default/README.md b/extensions/gii/generators/extension/default/README.md deleted file mode 100644 index 08101f32b4..0000000000 --- a/extensions/gii/generators/extension/default/README.md +++ /dev/null @@ -1,35 +0,0 @@ -title ?> - -title, \Yii::$app->charset)) ?> - -description ?> - - -Installation ------------- - -The preferred way to install this extension is through [composer](http://getcomposer.org/download/). - -Either run - -``` -php composer.phar require --prefer-dist vendorName ?>/packageName ?> "*" -``` - -or add - -``` -"vendorName ?>/packageName ?>": "*" -``` - -to the require section of your `composer.json` file. - - -Usage ------ - -Once the extension is installed, simply use it in your code by : - -```php -namespace}AutoloadExample::widget(); ?>" ?> -``` \ No newline at end of file diff --git a/extensions/gii/generators/extension/default/composer.json b/extensions/gii/generators/extension/default/composer.json deleted file mode 100644 index aa7c12d832..0000000000 --- a/extensions/gii/generators/extension/default/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "vendorName ?>/packageName ?>", - "description": "description ?>", - "type": "type ?>", - "keywords": keywordsArrayJson ?>, - "license": "license ?>", - "authors": [ - { - "name": "authorName ?>", - "email": "authorEmail ?>" - } - ], - "require": { - "yiisoft/yii2": "*" - }, - "autoload": { - "psr-4": { - "namespace) ?>": "" - } - } -} diff --git a/extensions/gii/generators/extension/form.php b/extensions/gii/generators/extension/form.php deleted file mode 100644 index b100139e55..0000000000 --- a/extensions/gii/generators/extension/form.php +++ /dev/null @@ -1,25 +0,0 @@ - -
    - Please read the - 'new']) ?> - before creating an extension. -
    -
    -field($generator, 'vendorName'); - echo $form->field($generator, 'packageName'); - echo $form->field($generator, 'namespace'); - echo $form->field($generator, 'type')->dropDownList($generator->optsType()); - echo $form->field($generator, 'keywords'); - echo $form->field($generator, 'license')->dropDownList($generator->optsLicense(), ['prompt'=>'Choose...']); - echo $form->field($generator, 'title'); - echo $form->field($generator, 'description'); - echo $form->field($generator, 'authorName'); - echo $form->field($generator, 'authorEmail'); - echo $form->field($generator, 'outputPath'); -?> -
    diff --git a/extensions/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php deleted file mode 100644 index f1304f911a..0000000000 --- a/extensions/gii/generators/form/Generator.php +++ /dev/null @@ -1,159 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $modelClass; - public $viewPath = '@app/views'; - public $viewName; - public $scenarioName; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Form Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator generates a view script file that displays a form to collect input for the specified model class.'; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $files[] = new CodeFile( - Yii::getAlias($this->viewPath) . '/' . $this->viewName . '.php', - $this->render('form.php') - ); - - return $files; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'], - [['modelClass', 'viewName', 'viewPath'], 'required'], - [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]], - [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'], - [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'], - [['viewPath'], 'validateViewPath'], - [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], - [['enableI18N'], 'boolean'], - [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return array_merge(parent::attributeLabels(), [ - 'modelClass' => 'Model Class', - 'viewName' => 'View Name', - 'viewPath' => 'View Path', - 'scenarioName' => 'Scenario', - ]); - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['form.php', 'action.php']; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return array_merge(parent::stickyAttributes(), ['viewPath', 'scenarioName']); - } - - /** - * @inheritdoc - */ - public function hints() - { - return array_merge(parent::hints(), [ - 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., app\models\Post.', - 'viewName' => 'This is the view name with respect to the view path. For example, site/index would generate a site/index.php view file under the view path.', - 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., @app/views.', - 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.', - ]); - } - - /** - * @inheritdoc - */ - public function successMessage() - { - $code = highlight_string($this->render('action.php'), true); - - return <<The form has been generated successfully.

    -

    You may add the following code in an appropriate controller class to invoke the view:

    -
    $code
    -EOD; - } - - /** - * Validates [[viewPath]] to make sure it is a valid path or path alias and exists. - */ - public function validateViewPath() - { - $path = Yii::getAlias($this->viewPath, false); - if ($path === false || !is_dir($path)) { - $this->addError('viewPath', 'View path does not exist.'); - } - } - - /** - * @return array list of safe attributes of [[modelClass]] - */ - public function getModelAttributes() - { - /* @var $model Model */ - $model = new $this->modelClass(); - if (!empty($this->scenarioName)) { - $model->setScenario($this->scenarioName); - } - - return $model->safeAttributes(); - } -} diff --git a/extensions/gii/generators/form/default/form.php b/extensions/gii/generators/form/default/form.php deleted file mode 100644 index f27d27775b..0000000000 --- a/extensions/gii/generators/form/default/form.php +++ /dev/null @@ -1,33 +0,0 @@ - - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/* @var $this yii\web\View */ -/* @var $model modelClass ?> */ -/* @var $form ActiveForm */ -" ?> - -
    - - $form = ActiveForm::begin(); ?> - - getModelAttributes() as $attribute): ?> - $form->field($model, '') ?> - - -
    - Html::submitButton(generateString('Submit') ?>, ['class' => 'btn btn-primary']) ?> -
    - ActiveForm::end(); ?> - -
    diff --git a/extensions/gii/generators/form/form.php b/extensions/gii/generators/form/form.php deleted file mode 100644 index 0e2298737e..0000000000 --- a/extensions/gii/generators/form/form.php +++ /dev/null @@ -1,11 +0,0 @@ -field($generator, 'viewName'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'scenarioName'); -echo $form->field($generator, 'viewPath'); -echo $form->field($generator, 'enableI18N')->checkbox(); -echo $form->field($generator, 'messageCategory'); diff --git a/extensions/gii/generators/model/default/model.php b/extensions/gii/generators/model/default/model.php deleted file mode 100644 index cd5a2fc0fd..0000000000 --- a/extensions/gii/generators/model/default/model.php +++ /dev/null @@ -1,84 +0,0 @@ - label) */ -/* @var $rules string[] list of validation rules */ -/* @var $relations array list of relations (name => relation declaration) */ - -echo " - -namespace ns ?>; - -use Yii; - -/** - * This is the model class for table "generateTableName($tableName) ?>". - * -columns as $column): ?> - * @property phpType} \${$column->name}\n" ?> - - - * - $relation): ?> - * @property - - - */ -class extends baseClass, '\\') . "\n" ?> -{ - /** - * @inheritdoc - */ - public static function tableName() - { - return 'generateTableName($tableName) ?>'; - } -db !== 'db'): ?> - - /** - * @return \yii\db\Connection the database connection used by this AR class. - */ - public static function getDb() - { - return Yii::$app->get('db ?>'); - } - - - /** - * @inheritdoc - */ - public function rules() - { - return []; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - $label): ?> - " . $generator->generateString($label) . ",\n" ?> - - ]; - } - $relation): ?> - - /** - * @return \yii\db\ActiveQuery - */ - public function get() - { - - } - -} diff --git a/extensions/gii/generators/model/form.php b/extensions/gii/generators/model/form.php deleted file mode 100644 index 9dfba390ae..0000000000 --- a/extensions/gii/generators/model/form.php +++ /dev/null @@ -1,15 +0,0 @@ -field($generator, 'tableName'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'ns'); -echo $form->field($generator, 'baseClass'); -echo $form->field($generator, 'db'); -echo $form->field($generator, 'useTablePrefix')->checkbox(); -echo $form->field($generator, 'generateRelations')->checkbox(); -echo $form->field($generator, 'generateLabelsFromComments')->checkbox(); -echo $form->field($generator, 'enableI18N')->checkbox(); -echo $form->field($generator, 'messageCategory'); diff --git a/extensions/gii/generators/module/Generator.php b/extensions/gii/generators/module/Generator.php deleted file mode 100644 index 6b139ff56f..0000000000 --- a/extensions/gii/generators/module/Generator.php +++ /dev/null @@ -1,170 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $moduleClass; - public $moduleID; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Module Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator helps you to generate the skeleton code needed by a Yii module.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - [['moduleID', 'moduleClass'], 'filter', 'filter' => 'trim'], - [['moduleID', 'moduleClass'], 'required'], - [['moduleID'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], - [['moduleClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - [['moduleClass'], 'validateModuleClass'], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'moduleID' => 'Module ID', - 'moduleClass' => 'Module Class', - ]; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'moduleID' => 'This refers to the ID of the module, e.g., admin.', - 'moduleClass' => 'This is the fully qualified class name of the module, e.g., app\modules\admin\Module.', - ]; - } - - /** - * @inheritdoc - */ - public function successMessage() - { - if (Yii::$app->hasModule($this->moduleID)) { - $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($this->moduleID), ['target' => '_blank']); - - return "The module has been generated successfully. You may $link."; - } - - $output = <<The module has been generated successfully.

    -

    To access the module, you need to add this to your application configuration:

    -EOD; - $code = << [ - '{$this->moduleID}' => [ - 'class' => '{$this->moduleClass}', - ], - ], - ...... -EOD; - - return $output . '
    ' . highlight_string($code, true) . '
    '; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['module.php', 'controller.php', 'view.php']; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $modulePath = $this->getModulePath(); - $files[] = new CodeFile( - $modulePath . '/' . StringHelper::basename($this->moduleClass) . '.php', - $this->render("module.php") - ); - $files[] = new CodeFile( - $modulePath . '/controllers/DefaultController.php', - $this->render("controller.php") - ); - $files[] = new CodeFile( - $modulePath . '/views/default/index.php', - $this->render("view.php") - ); - - return $files; - } - - /** - * Validates [[moduleClass]] to make sure it is a fully qualified class name. - */ - public function validateModuleClass() - { - if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass), false) === false) { - $this->addError('moduleClass', 'Module class must be properly namespaced.'); - } - if (empty($this->moduleClass) || substr_compare($this->moduleClass, '\\', -1, 1) === 0) { - $this->addError('moduleClass', 'Module class name must not be empty. Please enter a fully qualified class name. e.g. "app\\modules\\admin\\Module".'); - } - } - - /** - * @return boolean the directory that contains the module class - */ - public function getModulePath() - { - return Yii::getAlias('@' . str_replace('\\', '/', substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')))); - } - - /** - * @return string the controller namespace of the module. - */ - public function getControllerNamespace() - { - return substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')) . '\controllers'; - } -} diff --git a/extensions/gii/generators/module/default/controller.php b/extensions/gii/generators/module/default/controller.php deleted file mode 100644 index b3d2e939ed..0000000000 --- a/extensions/gii/generators/module/default/controller.php +++ /dev/null @@ -1,22 +0,0 @@ - - -namespace getControllerNamespace() ?>; - -use yii\web\Controller; - -class DefaultController extends Controller -{ - public function actionIndex() - { - return $this->render('index'); - } -} diff --git a/extensions/gii/generators/module/default/module.php b/extensions/gii/generators/module/default/module.php deleted file mode 100644 index 9f9da20421..0000000000 --- a/extensions/gii/generators/module/default/module.php +++ /dev/null @@ -1,29 +0,0 @@ -moduleClass; -$pos = strrpos($className, '\\'); -$ns = ltrim(substr($className, 0, $pos), '\\'); -$className = substr($className, $pos + 1); - -echo " - -namespace ; - -class extends \yii\base\Module -{ - public $controllerNamespace = 'getControllerNamespace() ?>'; - - public function init() - { - parent::init(); - - // custom initialization code goes here - } -} diff --git a/extensions/gii/generators/module/default/view.php b/extensions/gii/generators/module/default/view.php deleted file mode 100644 index bacfd8e027..0000000000 --- a/extensions/gii/generators/module/default/view.php +++ /dev/null @@ -1,16 +0,0 @@ - -
    -

    $this->context->action->uniqueId ?>

    -

    - This is the view content for action "$this->context->action->id ?>". - The action belongs to the controller "get_class($this->context) ?>" - in the "$this->context->module->id ?>" module. -

    -

    - You may customize this page by editing the following file:
    - __FILE__ ?> -

    -
    diff --git a/extensions/gii/generators/module/form.php b/extensions/gii/generators/module/form.php deleted file mode 100644 index 5d1e55e507..0000000000 --- a/extensions/gii/generators/module/form.php +++ /dev/null @@ -1,12 +0,0 @@ - -
    -field($generator, 'moduleClass'); - echo $form->field($generator, 'moduleID'); -?> -
    diff --git a/extensions/gii/views/default/diff.php b/extensions/gii/views/default/diff.php deleted file mode 100644 index 19a09293fc..0000000000 --- a/extensions/gii/views/default/diff.php +++ /dev/null @@ -1,13 +0,0 @@ - -
    - -
    Diff is not supported for this file type.
    - -
    Identical.
    - -
    - -
    diff --git a/extensions/gii/views/default/index.php b/extensions/gii/views/default/index.php deleted file mode 100644 index c52b593ac1..0000000000 --- a/extensions/gii/views/default/index.php +++ /dev/null @@ -1,30 +0,0 @@ -controller->module->generators; -$this->title = 'Welcome to Gii'; -?> -
    - - -

    Start the fun with the following code generators:

    - -
    - $generator): ?> -
    -

    getName()) ?>

    -

    getDescription() ?>

    -

    $id], ['class' => 'btn btn-default']) ?>

    -
    - -
    - -

    Get More Generators

    - -
    diff --git a/extensions/gii/views/default/view.php b/extensions/gii/views/default/view.php deleted file mode 100644 index d727bee793..0000000000 --- a/extensions/gii/views/default/view.php +++ /dev/null @@ -1,71 +0,0 @@ -title = $generator->getName(); -$templates = []; -foreach ($generator->templates as $name => $path) { - $templates[$name] = "$name ($path)"; -} -?> -
    -

    title) ?>

    - -

    getDescription() ?>

    - - "$id-generator", - 'successCssClass' => '', - 'fieldConfig' => ['class' => ActiveField::className()], - ]); ?> -
    -
    - renderFile($generator->formView(), [ - 'generator' => $generator, - 'form' => $form, - ]) ?> - field($generator, 'template')->sticky() - ->label('Code Template') - ->dropDownList($templates)->hint(' - Please select which set of the templates should be used to generated the code. - ') ?> -
    - 'preview', 'class' => 'btn btn-primary']) ?> - - - 'generate', 'class' => 'btn btn-success']) ?> - -
    -
    -
    - - render('view/results', [ - 'generator' => $generator, - 'results' => $results, - 'hasError' => $hasError, - ]); - } elseif (isset($files)) { - echo $this->render('view/files', [ - 'id' => $id, - 'generator' => $generator, - 'files' => $files, - 'answers' => $answers, - ]); - } - ?> - -
    diff --git a/extensions/gii/views/default/view/files.php b/extensions/gii/views/default/view/files.php deleted file mode 100644 index 92de072411..0000000000 --- a/extensions/gii/views/default/view/files.php +++ /dev/null @@ -1,112 +0,0 @@ - -
    -
    - - - -
    - -

    Click on the above Generate button to generate the files selected below:

    - - - - - - - operation !== CodeFile::OP_SKIP) { - $fileChangeExists = true; - echo ''; - break; - } - } - ?> - - - - - - operation === CodeFile::OP_OVERWRITE) { - $trClass = 'warning'; - } elseif ($file->operation === CodeFile::OP_SKIP) { - $trClass = 'active'; - } elseif ($file->operation === CodeFile::OP_CREATE) { - $trClass = 'success'; - } else { - $trClass = ''; - } - ?> - operation $trClass" ?>"> - - - - - - - - -
    Code FileAction
    - getRelativePath()), ['preview', 'id' => $id, 'file' => $file->id], ['class' => 'preview-code', 'data-title' => $file->getRelativePath()]) ?> - operation === CodeFile::OP_OVERWRITE): ?> - $id, 'file' => $file->id], ['class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath()]) ?> - - - operation === CodeFile::OP_SKIP) { - echo 'unchanged'; - } else { - echo $file->operation; - } - ?> - - operation === CodeFile::OP_SKIP) { - echo ' '; - } else { - echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_CREATE)); - } - ?> -
    - - -
    diff --git a/extensions/gii/views/default/view/results.php b/extensions/gii/views/default/view/results.php deleted file mode 100644 index 54d750dbb8..0000000000 --- a/extensions/gii/views/default/view/results.php +++ /dev/null @@ -1,16 +0,0 @@ - -
    - There was something wrong when generating the code. Please check the following messages.
    '; - } else { - echo '
    ' . $generator->successMessage() . '
    '; - } - ?> -
    -
    diff --git a/extensions/gii/views/layouts/generator.php b/extensions/gii/views/layouts/generator.php deleted file mode 100644 index 0e26c841b5..0000000000 --- a/extensions/gii/views/layouts/generator.php +++ /dev/null @@ -1,30 +0,0 @@ -controller->module->generators; -$activeGenerator = Yii::$app->controller->generator; -?> -beginContent('@yii/gii/views/layouts/main.php'); ?> -
    -
    -
    - $generator) { - $label = '' . Html::encode($generator->getName()); - echo Html::a($label, ['default/view', 'id' => $id], [ - 'class' => $generator === $activeGenerator ? 'list-group-item active' : 'list-group-item', - ]); - } - ?> -
    -
    -
    - -
    -
    -endContent(); ?> diff --git a/extensions/gii/views/layouts/main.php b/extensions/gii/views/layouts/main.php deleted file mode 100644 index 06502d716b..0000000000 --- a/extensions/gii/views/layouts/main.php +++ /dev/null @@ -1,54 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - -beginBody() ?> - Html::img($asset->baseUrl . '/logo.png'), - 'brandUrl' => ['default/index'], - 'options' => ['class' => 'navbar-inverse navbar-fixed-top'], -]); -echo Nav::widget([ - 'options' => ['class' => 'nav navbar-nav navbar-right'], - 'items' => [ - ['label' => 'Home', 'url' => ['default/index']], - ['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc-2.0/guide-tool-gii.html'], - ['label' => 'Application', 'url' => Yii::$app->homeUrl], - ], -]); -NavBar::end(); -?> - -
    - -
    - - - -endBody() ?> - - -endPage() ?> diff --git a/extensions/imagine/BaseImage.php b/extensions/imagine/BaseImage.php deleted file mode 100644 index ac166e6640..0000000000 --- a/extensions/imagine/BaseImage.php +++ /dev/null @@ -1,259 +0,0 @@ - - * @author Qiang Xue - * @since 2.0 - */ -class BaseImage -{ - /** - * GD2 driver definition for Imagine implementation using the GD library. - */ - const DRIVER_GD2 = 'gd2'; - /** - * imagick driver definition. - */ - const DRIVER_IMAGICK = 'imagick'; - /** - * gmagick driver definition. - */ - const DRIVER_GMAGICK = 'gmagick'; - - /** - * @var array|string the driver to use. This can be either a single driver name or an array of driver names. - * If the latter, the first available driver will be used. - */ - public static $driver = [self::DRIVER_GMAGICK, self::DRIVER_IMAGICK, self::DRIVER_GD2]; - - /** - * @var ImagineInterface instance. - */ - private static $_imagine; - - - /** - * Returns the `Imagine` object that supports various image manipulations. - * @return ImagineInterface the `Imagine` object - */ - public static function getImagine() - { - if (self::$_imagine === null) { - self::$_imagine = static::createImagine(); - } - - return self::$_imagine; - } - - /** - * @param ImagineInterface $imagine the `Imagine` object. - */ - public static function setImagine($imagine) - { - self::$_imagine = $imagine; - } - - /** - * Creates an `Imagine` object based on the specified [[driver]]. - * @return ImagineInterface the new `Imagine` object - * @throws InvalidConfigException if [[driver]] is unknown or the system doesn't support any [[driver]]. - */ - protected static function createImagine() - { - foreach ((array) static::$driver as $driver) { - switch ($driver) { - case self::DRIVER_GMAGICK: - if (class_exists('Gmagick', false)) { - return new \Imagine\Gmagick\Imagine(); - } - break; - case self::DRIVER_IMAGICK: - if (class_exists('Imagick', false)) { - return new \Imagine\Imagick\Imagine(); - } - break; - case self::DRIVER_GD2: - if (function_exists('gd_info')) { - return new \Imagine\Gd\Imagine(); - } - break; - default: - throw new InvalidConfigException("Unknown driver: $driver"); - } - } - throw new InvalidConfigException("Your system does not support any of these drivers: " . implode(',', (array) static::$driver)); - } - - /** - * Crops an image. - * - * For example, - * - * ~~~ - * $obj->crop('path\to\image.jpg', 200, 200, [5, 5]); - * - * $point = new \Imagine\Image\Point(5, 5); - * $obj->crop('path\to\image.jpg', 200, 200, $point); - * ~~~ - * - * @param string $filename the image file path or path alias. - * @param integer $width the crop width - * @param integer $height the crop height - * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates. - * @return ImageInterface - * @throws InvalidParamException if the `$start` parameter is invalid - */ - public static function crop($filename, $width, $height, array $start = [0, 0]) - { - if (!isset($start[0], $start[1])) { - throw new InvalidParamException('$start must be an array of two elements.'); - } - - return static::getImagine() - ->open(Yii::getAlias($filename)) - ->copy() - ->crop(new Point($start[0], $start[1]), new Box($width, $height)); - } - - /** - * Creates a thumbnail image. The function differs from `\Imagine\Image\ImageInterface::thumbnail()` function that - * it keeps the aspect ratio of the image. - * @param string $filename the image file path or path alias. - * @param integer $width the width in pixels to create the thumbnail - * @param integer $height the height in pixels to create the thumbnail - * @param string $mode - * @return ImageInterface - */ - public static function thumbnail($filename, $width, $height, $mode = ManipulatorInterface::THUMBNAIL_OUTBOUND) - { - $box = new Box($width, $height); - $img = static::getImagine()->open(Yii::getAlias($filename)); - - if (($img->getSize()->getWidth() <= $box->getWidth() && $img->getSize()->getHeight() <= $box->getHeight()) || (!$box->getWidth() && !$box->getHeight())) { - return $img->copy(); - } - - $img = $img->thumbnail($box, $mode); - - // create empty image to preserve aspect ratio of thumbnail - $thumb = static::getImagine()->create($box, new Color('FFF', 100)); - - // calculate points - $size = $img->getSize(); - - $startX = 0; - $startY = 0; - if ($size->getWidth() < $width) { - $startX = ceil($width - $size->getWidth()) / 2; - } - if ($size->getHeight() < $height) { - $startY = ceil($height - $size->getHeight()) / 2; - } - - $thumb->paste($img, new Point($startX, $startY)); - - return $thumb; - } - - /** - * Adds a watermark to an existing image. - * @param string $filename the image file path or path alias. - * @param string $watermarkFilename the file path or path alias of the watermark image. - * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates. - * @return ImageInterface - * @throws InvalidParamException if `$start` is invalid - */ - public static function watermark($filename, $watermarkFilename, array $start = [0, 0]) - { - if (!isset($start[0], $start[1])) { - throw new InvalidParamException('$start must be an array of two elements.'); - } - - $img = static::getImagine()->open(Yii::getAlias($filename)); - $watermark = static::getImagine()->open(Yii::getAlias($watermarkFilename)); - $img->paste($watermark, new Point($start[0], $start[1])); - - return $img; - } - - /** - * Draws a text string on an existing image. - * @param string $filename the image file path or path alias. - * @param string $text the text to write to the image - * @param string $fontFile the file path or path alias - * @param array $start the starting position of the text. This must be an array with two elements representing `x` and `y` coordinates. - * @param array $fontOptions the font options. The following options may be specified: - * - * - color: The font color. Defaults to "fff". - * - size: The font size. Defaults to 12. - * - angle: The angle to use to write the text. Defaults to 0. - * - * @return ImageInterface - * @throws InvalidParamException if `$fontOptions` is invalid - */ - public static function text($filename, $text, $fontFile, array $start = [0, 0], array $fontOptions = []) - { - if (!isset($start[0], $start[1])) { - throw new InvalidParamException('$start must be an array of two elements.'); - } - - $fontSize = ArrayHelper::getValue($fontOptions, 'size', 12); - $fontColor = ArrayHelper::getValue($fontOptions, 'color', 'fff'); - $fontAngle = ArrayHelper::getValue($fontOptions, 'angle', 0); - - $img = static::getImagine()->open(Yii::getAlias($filename)); - $font = static::getImagine()->font(Yii::getAlias($fontFile), $fontSize, new Color($fontColor)); - - $img->draw()->text($text, $font, new Point($start[0], $start[1]), $fontAngle); - - return $img; - } - - /** - * Adds a frame around of the image. Please note that the image size will increase by `$margin` x 2. - * @param string $filename the full path to the image file - * @param integer $margin the frame size to add around the image - * @param string $color the frame color - * @param integer $alpha the alpha value of the frame. - * @return ImageInterface - */ - public static function frame($filename, $margin = 20, $color = '666', $alpha = 100) - { - $img = static::getImagine()->open(Yii::getAlias($filename)); - - $size = $img->getSize(); - - $pasteTo = new Point($margin, $margin); - $padColor = new Color($color, $alpha); - - $box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2)); - - $image = static::getImagine()->create($box, $padColor); - - $image->paste($img, $pasteTo); - - return $image; - } -} diff --git a/extensions/imagine/Image.php b/extensions/imagine/Image.php deleted file mode 100644 index 38ef293403..0000000000 --- a/extensions/imagine/Image.php +++ /dev/null @@ -1,27 +0,0 @@ -save(Yii::getAlias('@runtime/thumb-test-image.jpg'), ['quality' => 50]); - * ~~~ - * - * @author Antonio Ramirez - * @author Qiang Xue - * @since 2.0 - */ -class Image extends BaseImage -{ -} diff --git a/extensions/imagine/LICENSE.md b/extensions/imagine/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/imagine/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/imagine/composer.json b/extensions/imagine/composer.json deleted file mode 100644 index b695c35425..0000000000 --- a/extensions/imagine/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "yiisoft/yii2-imagine", - "description": "The Imagine integration for the Yii framework", - "keywords": ["yii2", "imagine", "image", "helper"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aimagine", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Antonio Ramirez", - "email": "amigo.cobos@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "imagine/imagine": "0.5.*" - }, - "autoload": { - "psr-4": { - "yii\\imagine\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/jui/Accordion.php b/extensions/jui/Accordion.php deleted file mode 100644 index 9428d62724..0000000000 --- a/extensions/jui/Accordion.php +++ /dev/null @@ -1,127 +0,0 @@ - [ - * [ - * 'header' => 'Section 1', - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * ], - * [ - * 'header' => 'Section 2', - * 'headerOptions' => ['tag' => 'h3'], - * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => ['tag' => 'div'], - * ], - * ], - * 'options' => ['tag' => 'div'], - * 'itemOptions' => ['tag' => 'div'], - * 'headerOptions' => ['tag' => 'h3'], - * 'clientOptions' => ['collapsible' => false], - * ]); - * ``` - * - * @see http://api.jqueryui.com/accordion/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Accordion extends Widget -{ - /** - * @var array the HTML attributes for the widget container tag. The following special options are recognized: - * - * - tag: string, defaults to "div", the tag name of the container tag of this widget - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $options = []; - /** - * @var array list of collapsible items. Each item can be an array of the following structure: - * - * ~~~ - * [ - * 'header' => 'Item header', - * 'content' => 'Item content', - * // the HTML attributes of the item header container tag. This will overwrite "headerOptions". - * 'headerOptions' => [], - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @var array list of HTML attributes for the item container tags. This will be overwritten - * by the "options" set in individual [[items]]. The following special options are recognized: - * - * - tag: string, defaults to "div", the tag name of the item container tags. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $itemOptions = []; - /** - * @var array list of HTML attributes for the item header container tags. This will be overwritten - * by the "headerOptions" set in individual [[items]]. The following special options are recognized: - * - * - tag: string, defaults to "h3", the tag name of the item container tags. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $headerOptions = []; - - - /** - * Renders the widget. - */ - public function run() - { - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'div'); - echo Html::beginTag($tag, $options) . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag($tag) . "\n"; - $this->registerWidget('accordion'); - } - - /** - * Renders collapsible items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - protected function renderItems() - { - $items = []; - foreach ($this->items as $item) { - if (!array_key_exists('header', $item)) { - throw new InvalidConfigException("The 'header' option is required."); - } - if (!array_key_exists('content', $item)) { - throw new InvalidConfigException("The 'content' option is required."); - } - $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); - $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3'); - $items[] = Html::tag($headerTag, $item['header'], $headerOptions); - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); - $tag = ArrayHelper::remove($options, 'tag', 'div'); - $items[] = Html::tag($tag, $item['content'], $options); - } - - return implode("\n", $items); - } -} diff --git a/extensions/jui/DatePickerLanguageAsset.php b/extensions/jui/DatePickerLanguageAsset.php deleted file mode 100644 index 1c0a7ff719..0000000000 --- a/extensions/jui/DatePickerLanguageAsset.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @since 2.0 - */ -class DatePickerLanguageAsset extends AssetBundle -{ - public $sourcePath = '@bower/jquery-ui'; - /** - * @var boolean whether to automatically generate the needed language js files. - * If this is true, the language js files will be determined based on the actual usage of [[DatePicker]] - * and its language settings. If this is false, you should explicitly specify the language js files via [[js]]. - */ - public $autoGenerate = true; - /** - * @inheritdoc - */ - public $depends = [ - 'yii\jui\JuiAsset', - ]; -} diff --git a/extensions/jui/Dialog.php b/extensions/jui/Dialog.php deleted file mode 100644 index 082e4fdecc..0000000000 --- a/extensions/jui/Dialog.php +++ /dev/null @@ -1,52 +0,0 @@ - [ - * 'modal' => true, - * ], - * ]); - * - * echo 'Dialog contents here...'; - * - * Dialog::end(); - * ``` - * - * @see http://api.jqueryui.com/dialog/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Dialog extends Widget -{ - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - echo Html::beginTag('div', $this->options) . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo Html::endTag('div') . "\n"; - $this->registerWidget('dialog'); - } -} diff --git a/extensions/jui/Draggable.php b/extensions/jui/Draggable.php deleted file mode 100644 index a759ee8197..0000000000 --- a/extensions/jui/Draggable.php +++ /dev/null @@ -1,50 +0,0 @@ - ['grid' => [50, 20]], - * ]); - * - * echo 'Draggable contents here...'; - * - * Draggable::end(); - * ``` - * - * @see http://api.jqueryui.com/draggable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Draggable extends Widget -{ - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - echo Html::beginTag('div', $this->options) . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo Html::endTag('div') . "\n"; - $this->registerWidget('draggable'); - } -} diff --git a/extensions/jui/Droppable.php b/extensions/jui/Droppable.php deleted file mode 100644 index 8676f30461..0000000000 --- a/extensions/jui/Droppable.php +++ /dev/null @@ -1,50 +0,0 @@ - ['accept' => '.special'], - * ]); - * - * echo 'Droppable body here...'; - * - * Droppable::end(); - * ``` - * - * @see http://api.jqueryui.com/droppable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Droppable extends Widget -{ - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - echo Html::beginTag('div', $this->options) . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo Html::endTag('div') . "\n"; - $this->registerWidget('droppable'); - } -} diff --git a/extensions/jui/JuiAsset.php b/extensions/jui/JuiAsset.php deleted file mode 100644 index 0ff2634125..0000000000 --- a/extensions/jui/JuiAsset.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @since 2.0 - */ -class JuiAsset extends AssetBundle -{ - public $sourcePath = '@bower/jquery-ui'; - public $js = [ - 'jquery-ui.js', - ]; - public $css = [ - 'themes/smoothness/jquery-ui.css', - ]; - public $depends = [ - 'yii\web\JqueryAsset', - ]; -} diff --git a/extensions/jui/LICENSE.md b/extensions/jui/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/jui/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/jui/Menu.php b/extensions/jui/Menu.php deleted file mode 100644 index 754c162587..0000000000 --- a/extensions/jui/Menu.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @since 2.0 - */ -class Menu extends \yii\widgets\Menu -{ - /** - * @var array the options for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible options. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported options (e.g. "header"). - */ - public $clientOptions = []; - /** - * @var array the event handlers for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible events. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported events (e.g. "create"). - */ - public $clientEvents = []; - - - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - */ - public function init() - { - parent::init(); - if (!isset($this->options['id'])) { - $this->options['id'] = $this->getId(); - } - } - - /** - * Renders the widget. - */ - public function run() - { - parent::run(); - - $view = $this->getView(); - JuiAsset::register($view); - - $id = $this->options['id']; - if ($this->clientOptions !== false) { - $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); - $js = "jQuery('#$id').menu($options);"; - $view->registerJs($js); - } - - if (!empty($this->clientEvents)) { - $js = []; - foreach ($this->clientEvents as $event => $handler) { - $js[] = "jQuery('#$id').on('menu$event', $handler);"; - } - $view->registerJs(implode("\n", $js)); - } - } -} diff --git a/extensions/jui/ProgressBar.php b/extensions/jui/ProgressBar.php deleted file mode 100644 index f161ed3bb7..0000000000 --- a/extensions/jui/ProgressBar.php +++ /dev/null @@ -1,60 +0,0 @@ - [ - * 'value' => 75, - * ], - * ]); - * ``` - * - * The following example will show the content enclosed between the [[begin()]] - * and [[end()]] calls within the widget container: - * - * ~~~php - * ProgressBar::begin([ - * 'clientOptions' => ['value' => 75], - * ]); - * - * echo '
    Loading...
    '; - * - * ProgressBar::end(); - * ~~~ - * @see http://api.jqueryui.com/progressbar/ - * @author Alexander Kochetov - * @since 2.0 - */ -class ProgressBar extends Widget -{ - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - echo Html::beginTag('div', $this->options) . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo Html::endTag('div') . "\n"; - $this->registerWidget('progressbar'); - } -} diff --git a/extensions/jui/Resizable.php b/extensions/jui/Resizable.php deleted file mode 100644 index aedc6139aa..0000000000 --- a/extensions/jui/Resizable.php +++ /dev/null @@ -1,52 +0,0 @@ - [ - * 'grid' => [20, 10], - * ], - * ]); - * - * echo 'Resizable contents here...'; - * - * Resizable::end(); - * ``` - * - * @see http://api.jqueryui.com/resizable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Resizable extends Widget -{ - /** - * Initializes the widget. - */ - public function init() - { - parent::init(); - echo Html::beginTag('div', $this->options) . "\n"; - } - - /** - * Renders the widget. - */ - public function run() - { - echo Html::endTag('div') . "\n"; - $this->registerWidget('resizable'); - } -} diff --git a/extensions/jui/Selectable.php b/extensions/jui/Selectable.php deleted file mode 100644 index 54ae461b16..0000000000 --- a/extensions/jui/Selectable.php +++ /dev/null @@ -1,121 +0,0 @@ - [ - * 'Item 1', - * [ - * 'content' => 'Item2', - * ], - * [ - * 'content' => 'Item3', - * 'options' => [ - * 'tag' => 'li', - * ], - * ], - * ), - * 'options' => [ - * 'tag' => 'ul', - * ], - * 'itemOptions' => [ - * 'tag' => 'li', - * ], - * 'clientOptions' => [ - * 'tolerance' => 'fit', - * ], - * ]); - * ``` - * - * @see http://api.jqueryui.com/selectable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Selectable extends Widget -{ - /** - * @var array the HTML attributes for the widget container tag. The following special options are recognized: - * - * - tag: string, defaults to "ul", the tag name of the container tag of this widget. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $options = []; - /** - * @var array list of selectable items. Each item can be a string representing the item content - * or an array of the following structure: - * - * ~~~ - * [ - * 'content' => 'item content', - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @var array list of HTML attributes for the item container tags. This will be overwritten - * by the "options" set in individual [[items]]. The following special options are recognized: - * - * - tag: string, defaults to "li", the tag name of the item container tags. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $itemOptions = []; - - - /** - * Renders the widget. - */ - public function run() - { - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'ul'); - echo Html::beginTag($tag, $options) . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag($tag) . "\n"; - $this->registerWidget('selectable'); - } - - /** - * Renders selectable items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - public function renderItems() - { - $items = []; - foreach ($this->items as $item) { - $options = $this->itemOptions; - $tag = ArrayHelper::remove($options, 'tag', 'li'); - if (is_array($item)) { - if (!array_key_exists('content', $item)) { - throw new InvalidConfigException("The 'content' option is required."); - } - $options = array_merge($options, ArrayHelper::getValue($item, 'options', [])); - $tag = ArrayHelper::remove($options, 'tag', $tag); - $items[] = Html::tag($tag, $item['content'], $options); - } else { - $items[] = Html::tag($tag, $item, $options); - } - } - - return implode("\n", $items); - } -} diff --git a/extensions/jui/Slider.php b/extensions/jui/Slider.php deleted file mode 100644 index 5fcc642ca3..0000000000 --- a/extensions/jui/Slider.php +++ /dev/null @@ -1,49 +0,0 @@ - [ - * 'min' => 1, - * 'max' => 10, - * ], - * ]); - * ``` - * - * @see http://api.jqueryui.com/slider/ - * @author Alexander Makarov - * @since 2.0 - */ -class Slider extends Widget -{ - /** - * @inheritDoc - */ - protected $clientEventMap = [ - 'change' => 'slidechange', - 'create' => 'slidecreate', - 'slide' => 'slide', - 'start' => 'slidestart', - 'stop' => 'slidestop', - ]; - - - /** - * Executes the widget. - */ - public function run() - { - echo Html::tag('div', '', $this->options); - $this->registerWidget('slider'); - } -} diff --git a/extensions/jui/Sortable.php b/extensions/jui/Sortable.php deleted file mode 100644 index 0dc7af6404..0000000000 --- a/extensions/jui/Sortable.php +++ /dev/null @@ -1,130 +0,0 @@ - [ - * 'Item 1', - * ['content' => 'Item2'], - * [ - * 'content' => 'Item3', - * 'options' => ['tag' => 'li'], - * ], - * ], - * 'options' => ['tag' => 'ul'], - * 'itemOptions' => ['tag' => 'li'], - * 'clientOptions' => ['cursor' => 'move'], - * ]); - * ``` - * - * @see http://api.jqueryui.com/sortable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Sortable extends Widget -{ - /** - * @var array the HTML attributes for the widget container tag. The following special options are recognized: - * - * - tag: string, defaults to "ul", the tag name of the container tag of this widget. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $options = []; - /** - * @var array list of sortable items. Each item can be a string representing the item content - * or an array of the following structure: - * - * ~~~ - * [ - * 'content' => 'item content', - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @var array list of HTML attributes for the item container tags. This will be overwritten - * by the "options" set in individual [[items]]. The following special options are recognized: - * - * - tag: string, defaults to "li", the tag name of the item container tags. - * - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $itemOptions = []; - - /** - * @inheritDoc - */ - protected $clientEventMap = [ - 'activate' => 'sortactivate', - 'beforeStop' => 'sortbeforestop', - 'change' => 'sortchange', - 'create' => 'sortcreate', - 'deactivate' => 'sortdeactivate', - 'out' => 'sortout', - 'over' => 'sortover', - 'receive' => 'sortreceive', - 'remove' => 'sortremove', - 'sort' => 'sort', - 'start' => 'sortstart', - 'stop' => 'sortstop', - 'update' => 'sortupdate', - ]; - - - /** - * Renders the widget. - */ - public function run() - { - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'ul'); - echo Html::beginTag($tag, $options) . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag($tag) . "\n"; - $this->registerWidget('sortable'); - } - - /** - * Renders sortable items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - public function renderItems() - { - $items = []; - foreach ($this->items as $item) { - $options = $this->itemOptions; - $tag = ArrayHelper::remove($options, 'tag', 'li'); - if (is_array($item)) { - if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' option is required."); - } - $options = array_merge($options, ArrayHelper::getValue($item, 'options', [])); - $tag = ArrayHelper::remove($options, 'tag', $tag); - $items[] = Html::tag($tag, $item['content'], $options); - } else { - $items[] = Html::tag($tag, $item, $options); - } - } - - return implode("\n", $items); - } -} diff --git a/extensions/jui/composer.json b/extensions/jui/composer.json deleted file mode 100644 index 094e4196c6..0000000000 --- a/extensions/jui/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "yiisoft/yii2-jui", - "description": "The Jquery UI extension for the Yii framework", - "keywords": ["yii2", "Jquery UI"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Ajui", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "bower-asset/jquery-ui": "1.11.*@stable" - }, - "autoload": { - "psr-4": { - "yii\\jui\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/mongodb/Connection.php b/extensions/mongodb/Connection.php deleted file mode 100644 index 27578ed476..0000000000 --- a/extensions/mongodb/Connection.php +++ /dev/null @@ -1,279 +0,0 @@ - $dsn, - * ]); - * $connection->open(); - * ~~~ - * - * After the Mongo connection is established, one can access Mongo databases and collections: - * - * ~~~ - * $database = $connection->getDatabase('my_mongo_db'); - * $collection = $database->getCollection('customer'); - * $collection->insert(['name' => 'John Smith', 'status' => 1]); - * ~~~ - * - * You can work with several different databases at the same server using this class. - * However, while it is unlikely your application will actually need it, the Connection class - * provides ability to use [[defaultDatabaseName]] as well as a shortcut method [[getCollection()]] - * to retrieve a particular collection instance: - * - * ~~~ - * // get collection 'customer' from default database: - * $collection = $connection->getCollection('customer'); - * // get collection 'customer' from database 'mydatabase': - * $collection = $connection->getCollection(['mydatabase', 'customer']); - * ~~~ - * - * Connection is often used as an application component and configured in the application - * configuration like the following: - * - * ~~~ - * [ - * 'components' => [ - * 'mongodb' => [ - * 'class' => '\yii\mongodb\Connection', - * 'dsn' => 'mongodb://developer:password@localhost:27017/mydatabase', - * ], - * ], - * ] - * ~~~ - * - * @property Database $database Database instance. This property is read-only. - * @property file\Collection $fileCollection Mongo GridFS collection instance. This property is read-only. - * @property boolean $isActive Whether the Mongo connection is established. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class Connection extends Component -{ - /** - * @event Event an event that is triggered after a DB connection is established - */ - const EVENT_AFTER_OPEN = 'afterOpen'; - - /** - * @var string host:port - * - * Correct syntax is: - * mongodb://[username:password@]host1[:port1][,host2[:port2:],...][/dbname] - * For example: - * mongodb://localhost:27017 - * mongodb://developer:password@localhost:27017 - * mongodb://developer:password@localhost:27017/mydatabase - */ - public $dsn; - /** - * @var array connection options. - * for example: - * - * ~~~ - * [ - * 'socketTimeoutMS' => 1000, // how long a send or receive on a socket can take before timing out - * 'journal' => true // block write operations until the journal be flushed the to disk - * ] - * ~~~ - * - * @see http://www.php.net/manual/en/mongoclient.construct.php - */ - public $options = []; - /** - * @var string name of the Mongo database to use by default. - * If this field left blank, connection instance will attempt to determine it from - * [[options]] and [[dsn]] automatically, if needed. - */ - public $defaultDatabaseName; - /** - * @var \MongoClient Mongo client instance. - */ - public $mongoClient; - - /** - * @var Database[] list of Mongo databases - */ - private $_databases = []; - - - /** - * Returns the Mongo collection with the given name. - * @param string|null $name collection name, if null default one will be used. - * @param boolean $refresh whether to reestablish the database connection even if it is found in the cache. - * @return Database database instance. - */ - public function getDatabase($name = null, $refresh = false) - { - if ($name === null) { - $name = $this->fetchDefaultDatabaseName(); - } - if ($refresh || !array_key_exists($name, $this->_databases)) { - $this->_databases[$name] = $this->selectDatabase($name); - } - - return $this->_databases[$name]; - } - - /** - * Returns [[defaultDatabaseName]] value, if it is not set, - * attempts to determine it from [[dsn]] value. - * @return string default database name - * @throws \yii\base\InvalidConfigException if unable to determine default database name. - */ - protected function fetchDefaultDatabaseName() - { - if ($this->defaultDatabaseName === null) { - if (isset($this->options['db'])) { - $this->defaultDatabaseName = $this->options['db']; - } elseif (preg_match('/^mongodb:\\/\\/.+\\/([^?&]+)/s', $this->dsn, $matches)) { - $this->defaultDatabaseName = $matches[1]; - } else { - throw new InvalidConfigException("Unable to determine default database name from dsn."); - } - } - - return $this->defaultDatabaseName; - } - - /** - * Selects the database with given name. - * @param string $name database name. - * @return Database database instance. - */ - protected function selectDatabase($name) - { - $this->open(); - - return Yii::createObject([ - 'class' => 'yii\mongodb\Database', - 'mongoDb' => $this->mongoClient->selectDB($name) - ]); - } - - /** - * Returns the Mongo collection with the given name. - * @param string|array $name collection name. If string considered as the name of the collection - * inside the default database. If array - first element considered as the name of the database, - * second - as name of collection inside that database - * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. - * @return Collection Mongo collection instance. - */ - public function getCollection($name, $refresh = false) - { - if (is_array($name)) { - list ($dbName, $collectionName) = $name; - - return $this->getDatabase($dbName)->getCollection($collectionName, $refresh); - } else { - return $this->getDatabase()->getCollection($name, $refresh); - } - } - - /** - * Returns the Mongo GridFS collection. - * @param string|array $prefix collection prefix. If string considered as the prefix of the GridFS - * collection inside the default database. If array - first element considered as the name of the database, - * second - as prefix of the GridFS collection inside that database, if no second element present - * default "fs" prefix will be used. - * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. - * @return file\Collection Mongo GridFS collection instance. - */ - public function getFileCollection($prefix = 'fs', $refresh = false) - { - if (is_array($prefix)) { - list ($dbName, $collectionPrefix) = $prefix; - if (!isset($collectionPrefix)) { - $collectionPrefix = 'fs'; - } - - return $this->getDatabase($dbName)->getFileCollection($collectionPrefix, $refresh); - } else { - return $this->getDatabase()->getFileCollection($prefix, $refresh); - } - } - - /** - * Returns a value indicating whether the Mongo connection is established. - * @return boolean whether the Mongo connection is established - */ - public function getIsActive() - { - return is_object($this->mongoClient) && $this->mongoClient->getConnections() != []; - } - - /** - * Establishes a Mongo connection. - * It does nothing if a Mongo connection has already been established. - * @throws Exception if connection fails - */ - public function open() - { - if ($this->mongoClient === null) { - if (empty($this->dsn)) { - throw new InvalidConfigException($this->className() . '::dsn cannot be empty.'); - } - $token = 'Opening MongoDB connection: ' . $this->dsn; - try { - Yii::trace($token, __METHOD__); - Yii::beginProfile($token, __METHOD__); - $options = $this->options; - $options['connect'] = true; - if ($this->defaultDatabaseName !== null) { - $options['db'] = $this->defaultDatabaseName; - } - $this->mongoClient = new \MongoClient($this->dsn, $options); - $this->initConnection(); - Yii::endProfile($token, __METHOD__); - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - } - - /** - * Closes the currently active DB connection. - * It does nothing if the connection is already closed. - */ - public function close() - { - if ($this->mongoClient !== null) { - Yii::trace('Closing MongoDB connection: ' . $this->dsn, __METHOD__); - $this->mongoClient = null; - $this->_databases = []; - } - } - - /** - * Initializes the DB connection. - * This method is invoked right after the DB connection is established. - * The default implementation triggers an [[EVENT_AFTER_OPEN]] event. - */ - protected function initConnection() - { - $this->trigger(self::EVENT_AFTER_OPEN); - } -} diff --git a/extensions/mongodb/Database.php b/extensions/mongodb/Database.php deleted file mode 100644 index 33ed6f7e2f..0000000000 --- a/extensions/mongodb/Database.php +++ /dev/null @@ -1,179 +0,0 @@ - - * @since 2.0 - */ -class Database extends Object -{ - /** - * @var \MongoDB Mongo database instance. - */ - public $mongoDb; - - /** - * @var Collection[] list of collections. - */ - private $_collections = []; - /** - * @var file\Collection[] list of GridFS collections. - */ - private $_fileCollections = []; - - - /** - * @return string name of this database. - */ - public function getName() - { - return $this->mongoDb->__toString(); - } - - /** - * Returns the Mongo collection with the given name. - * @param string $name collection name - * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. - * @return Collection Mongo collection instance. - */ - public function getCollection($name, $refresh = false) - { - if ($refresh || !array_key_exists($name, $this->_collections)) { - $this->_collections[$name] = $this->selectCollection($name); - } - - return $this->_collections[$name]; - } - - /** - * Returns Mongo GridFS collection with given prefix. - * @param string $prefix collection prefix. - * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. - * @return file\Collection Mongo GridFS collection. - */ - public function getFileCollection($prefix = 'fs', $refresh = false) - { - if ($refresh || !array_key_exists($prefix, $this->_fileCollections)) { - $this->_fileCollections[$prefix] = $this->selectFileCollection($prefix); - } - - return $this->_fileCollections[$prefix]; - } - - /** - * Selects collection with given name. - * @param string $name collection name. - * @return Collection collection instance. - */ - protected function selectCollection($name) - { - return Yii::createObject([ - 'class' => 'yii\mongodb\Collection', - 'mongoCollection' => $this->mongoDb->selectCollection($name) - ]); - } - - /** - * Selects GridFS collection with given prefix. - * @param string $prefix file collection prefix. - * @return file\Collection file collection instance. - */ - protected function selectFileCollection($prefix) - { - return Yii::createObject([ - 'class' => 'yii\mongodb\file\Collection', - 'mongoCollection' => $this->mongoDb->getGridFS($prefix) - ]); - } - - /** - * Creates new collection. - * Note: Mongo creates new collections automatically on the first demand, - * this method makes sense only for the migration script or for the case - * you need to create collection with the specific options. - * @param string $name name of the collection - * @param array $options collection options in format: "name" => "value" - * @return \MongoCollection new Mongo collection instance. - * @throws Exception on failure. - */ - public function createCollection($name, $options = []) - { - $token = $this->getName() . '.create(' . $name . ', ' . Json::encode($options) . ')'; - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $result = $this->mongoDb->createCollection($name, $options); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Executes Mongo command. - * @param array $command command specification. - * @param array $options options in format: "name" => "value" - * @return array database response. - * @throws Exception on failure. - */ - public function executeCommand($command, $options = []) - { - $token = $this->getName() . '.$cmd(' . Json::encode($command) . ', ' . Json::encode($options) . ')'; - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $result = $this->mongoDb->command($command, $options); - $this->tryResultError($result); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Checks if command execution result ended with an error. - * @param mixed $result raw command execution result. - * @throws Exception if an error occurred. - */ - protected function tryResultError($result) - { - if (is_array($result)) { - if (!empty($result['errmsg'])) { - $errorMessage = $result['errmsg']; - } elseif (!empty($result['err'])) { - $errorMessage = $result['err']; - } - if (isset($errorMessage)) { - if (array_key_exists('ok', $result)) { - $errorCode = (int) $result['ok']; - } else { - $errorCode = 0; - } - throw new Exception($errorMessage, $errorCode); - } - } elseif (!$result) { - throw new Exception('Unknown error, use "w=1" option to enable error tracking'); - } - } -} diff --git a/extensions/mongodb/Exception.php b/extensions/mongodb/Exception.php deleted file mode 100644 index 3cc5ae48b2..0000000000 --- a/extensions/mongodb/Exception.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class Exception extends \yii\base\Exception -{ - /** - * @return string the user-friendly name of this exception - */ - public function getName() - { - return 'MongoDB Exception'; - } -} diff --git a/extensions/mongodb/LICENSE.md b/extensions/mongodb/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/mongodb/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/mongodb/console/controllers/MigrateController.php b/extensions/mongodb/console/controllers/MigrateController.php deleted file mode 100644 index c42ddc78b5..0000000000 --- a/extensions/mongodb/console/controllers/MigrateController.php +++ /dev/null @@ -1,181 +0,0 @@ - [ - * 'mongodb-migrate' => 'yii\mongodb\console\controllers\MigrateController' - * ], - * ]; - * ~~~ - * - * Below are some common usages of this command: - * - * ~~~ - * # creates a new migration named 'create_user_collection' - * yii mongodb-migrate/create create_user_collection - * - * # applies ALL new migrations - * yii mongodb-migrate - * - * # reverts the last applied migration - * yii mongodb-migrate/down - * ~~~ - * - * @author Klimov Paul - * @since 2.0 - */ -class MigrateController extends BaseMigrateController -{ - /** - * @var string|array the name of the collection for keeping applied migration information. - */ - public $migrationCollection = 'migration'; - /** - * @inheritdoc - */ - public $templateFile = '@yii/mongodb/views/migration.php'; - /** - * @var Connection|string the DB connection object or the application - * component ID of the DB connection. - */ - public $db = 'mongodb'; - - - /** - * @inheritdoc - */ - public function options($actionID) - { - return array_merge( - parent::options($actionID), - ['migrationCollection', 'db'] // global for all actions - ); - } - - /** - * This method is invoked right before an action is to be executed (after all possible filters.) - * It checks the existence of the [[migrationPath]]. - * @param \yii\base\Action $action the action to be executed. - * @throws Exception if db component isn't configured - * @return boolean whether the action should continue to be executed. - */ - public function beforeAction($action) - { - if (parent::beforeAction($action)) { - if ($action->id !== 'create') { - if (is_string($this->db)) { - $this->db = Yii::$app->get($this->db); - } - if (!$this->db instanceof Connection) { - throw new Exception("The 'db' option must refer to the application component ID of a MongoDB connection."); - } - } - return true; - } else { - return false; - } - } - - /** - * Creates a new migration instance. - * @param string $class the migration class name - * @return \yii\mongodb\Migration the migration instance - */ - protected function createMigration($class) - { - $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; - require_once($file); - - return new $class(['db' => $this->db]); - } - - /** - * @inheritdoc - */ - protected function getMigrationHistory($limit) - { - $this->ensureBaseMigrationHistory(); - - $query = new Query; - $rows = $query->select(['version', 'apply_time']) - ->from($this->migrationCollection) - ->orderBy('version DESC') - ->limit($limit) - ->all($this->db); - $history = ArrayHelper::map($rows, 'version', 'apply_time'); - unset($history[self::BASE_MIGRATION]); - - return $history; - } - - private $baseMigrationEnsured = false; - - /** - * Ensures migration history contains at least base migration entry. - */ - protected function ensureBaseMigrationHistory() - { - if (!$this->baseMigrationEnsured) { - $query = new Query; - $row = $query->select(['version']) - ->from($this->migrationCollection) - ->andWhere(['version' => self::BASE_MIGRATION]) - ->limit(1) - ->one($this->db); - if (empty($row)) { - $this->addMigrationHistory(self::BASE_MIGRATION); - } - $this->baseMigrationEnsured = true; - } - } - - /** - * @inheritdoc - */ - protected function addMigrationHistory($version) - { - $this->db->getCollection($this->migrationCollection)->insert([ - 'version' => $version, - 'apply_time' => time(), - ]); - } - - /** - * @inheritdoc - */ - protected function removeMigrationHistory($version) - { - $this->db->getCollection($this->migrationCollection)->remove([ - 'version' => $version, - ]); - } -} diff --git a/extensions/mongodb/debug/MongoDbPanel.php b/extensions/mongodb/debug/MongoDbPanel.php deleted file mode 100644 index e8b5e2e4c8..0000000000 --- a/extensions/mongodb/debug/MongoDbPanel.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @since 2.0.1 - */ -class MongoDbPanel extends DbPanel -{ - /** - * @inheritdoc - */ - public function getName() - { - return 'MongoDB'; - } - - /** - * @inheritdoc - */ - public function getSummaryName() - { - return 'MongoDB'; - } - - /** - * Returns all profile logs of the current request for this panel. - * @return array - */ - public function getProfileLogs() - { - $target = $this->module->logTarget; - - return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, [ - 'yii\mongodb\Collection::*', - 'yii\mongodb\Query::*', - 'yii\mongodb\Database::*', - ]); - } -} \ No newline at end of file diff --git a/extensions/mongodb/file/ActiveRecord.php b/extensions/mongodb/file/ActiveRecord.php deleted file mode 100644 index 5387efd756..0000000000 --- a/extensions/mongodb/file/ActiveRecord.php +++ /dev/null @@ -1,336 +0,0 @@ -file = '/path/to/some/file.jpg'; - * $record->save(); - * ~~~ - * - * You can also specify file content via [[newFileContent]] attribute: - * - * ~~~ - * $record = new ImageFile(); - * $record->newFileContent = 'New file content'; - * $record->save(); - * ~~~ - * - * Note: [[newFileContent]] always takes precedence over [[file]]. - * - * @property null|string $fileContent File content. This property is read-only. - * @property resource $fileResource File stream resource. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -abstract class ActiveRecord extends \yii\mongodb\ActiveRecord -{ - /** - * @inheritdoc - * @return ActiveQuery the newly created [[ActiveQuery]] instance. - */ - public static function find() - { - return Yii::createObject(ActiveQuery::className(), [get_called_class()]); - } - - /** - * Return the Mongo GridFS collection instance for this AR class. - * @return Collection collection instance. - */ - public static function getCollection() - { - return static::getDb()->getFileCollection(static::collectionName()); - } - - /** - * Returns the list of all attribute names of the model. - * This method could be overridden by child classes to define available attributes. - * Note: all attributes defined in base Active Record class should be always present - * in returned array. - * For example: - * ~~~ - * public function attributes() - * { - * return array_merge( - * parent::attributes(), - * ['tags', 'status'] - * ); - * } - * ~~~ - * @return array list of attribute names. - */ - public function attributes() - { - return [ - '_id', - 'filename', - 'uploadDate', - 'length', - 'chunkSize', - 'md5', - 'file', - 'newFileContent' - ]; - } - - /** - * @see ActiveRecord::insert() - */ - protected function insertInternal($attributes = null) - { - if (!$this->beforeSave(true)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - $currentAttributes = $this->getAttributes(); - foreach ($this->primaryKey() as $key) { - $values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null; - } - } - $collection = static::getCollection(); - if (isset($values['newFileContent'])) { - $newFileContent = $values['newFileContent']; - unset($values['newFileContent']); - } - if (isset($values['file'])) { - $newFile = $values['file']; - unset($values['file']); - } - if (isset($newFileContent)) { - $newId = $collection->insertFileContent($newFileContent, $values); - } elseif (isset($newFile)) { - $fileName = $this->extractFileName($newFile); - $newId = $collection->insertFile($fileName, $values); - } else { - $newId = $collection->insert($values); - } - $this->setAttribute('_id', $newId); - $values['_id'] = $newId; - - $changedAttributes = array_fill_keys(array_keys($values), null); - $this->setOldAttributes($values); - $this->afterSave(true, $changedAttributes); - - return true; - } - - /** - * @see ActiveRecord::update() - * @throws StaleObjectException - */ - protected function updateInternal($attributes = null) - { - if (!$this->beforeSave(false)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - $this->afterSave(false, $values); - return 0; - } - - $collection = static::getCollection(); - if (isset($values['newFileContent'])) { - $newFileContent = $values['newFileContent']; - unset($values['newFileContent']); - } - if (isset($values['file'])) { - $newFile = $values['file']; - unset($values['file']); - } - if (isset($newFileContent) || isset($newFile)) { - $fileAssociatedAttributeNames = [ - 'filename', - 'uploadDate', - 'length', - 'chunkSize', - 'md5', - 'file', - 'newFileContent' - ]; - $values = array_merge($this->getAttributes(null, $fileAssociatedAttributeNames), $values); - $rows = $this->deleteInternal(); - $insertValues = $values; - $insertValues['_id'] = $this->getAttribute('_id'); - if (isset($newFileContent)) { - $collection->insertFileContent($newFileContent, $insertValues); - } else { - $fileName = $this->extractFileName($newFile); - $collection->insertFile($fileName, $insertValues); - } - $this->setAttribute('newFileContent', null); - $this->setAttribute('file', null); - } else { - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - if (!isset($values[$lock])) { - $values[$lock] = $this->$lock + 1; - } - $condition[$lock] = $this->$lock; - } - // We do not check the return value of update() because it's possible - // that it doesn't change anything and thus returns 0. - $rows = $collection->update($condition, $values); - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being updated is outdated.'); - } - } - - $changedAttributes = []; - foreach ($values as $name => $value) { - $changedAttributes[$name] = $this->getOldAttribute($name); - $this->setOldAttribute($name, $value); - } - $this->afterSave(false, $changedAttributes); - - return $rows; - } - - /** - * Extracts filename from given raw file value. - * @param mixed $file raw file value. - * @return string file name. - * @throws \yii\base\InvalidParamException on invalid file value. - */ - protected function extractFileName($file) - { - if ($file instanceof UploadedFile) { - return $file->tempName; - } elseif (is_string($file)) { - if (file_exists($file)) { - return $file; - } else { - throw new InvalidParamException("File '{$file}' does not exist."); - } - } else { - throw new InvalidParamException('Unsupported type of "file" attribute.'); - } - } - - /** - * Refreshes the [[file]] attribute from file collection, using current primary key. - * @return \MongoGridFSFile|null refreshed file value. - */ - public function refreshFile() - { - $mongoFile = $this->getCollection()->get($this->getPrimaryKey()); - $this->setAttribute('file', $mongoFile); - - return $mongoFile; - } - - /** - * Returns the associated file content. - * @return null|string file content. - * @throws \yii\base\InvalidParamException on invalid file attribute value. - */ - public function getFileContent() - { - $file = $this->getAttribute('file'); - if (empty($file) && !$this->getIsNewRecord()) { - $file = $this->refreshFile(); - } - if (empty($file)) { - return null; - } elseif ($file instanceof \MongoGridFSFile) { - $fileSize = $file->getSize(); - if (empty($fileSize)) { - return null; - } else { - return $file->getBytes(); - } - } elseif ($file instanceof UploadedFile) { - return file_get_contents($file->tempName); - } elseif (is_string($file)) { - if (file_exists($file)) { - return file_get_contents($file); - } else { - throw new InvalidParamException("File '{$file}' does not exist."); - } - } else { - throw new InvalidParamException('Unsupported type of "file" attribute.'); - } - } - - /** - * Writes the the internal file content into the given filename. - * @param string $filename full filename to be written. - * @return boolean whether the operation was successful. - * @throws \yii\base\InvalidParamException on invalid file attribute value. - */ - public function writeFile($filename) - { - $file = $this->getAttribute('file'); - if (empty($file) && !$this->getIsNewRecord()) { - $file = $this->refreshFile(); - } - if (empty($file)) { - throw new InvalidParamException('There is no file associated with this object.'); - } elseif ($file instanceof \MongoGridFSFile) { - return ($file->write($filename) == $file->getSize()); - } elseif ($file instanceof UploadedFile) { - return copy($file->tempName, $filename); - } elseif (is_string($file)) { - if (file_exists($file)) { - return copy($file, $filename); - } else { - throw new InvalidParamException("File '{$file}' does not exist."); - } - } else { - throw new InvalidParamException('Unsupported type of "file" attribute.'); - } - } - - /** - * This method returns a stream resource that can be used with all file functions in PHP, - * which deal with reading files. The contents of the file are pulled out of MongoDB on the fly, - * so that the whole file does not have to be loaded into memory first. - * @return resource file stream resource. - * @throws \yii\base\InvalidParamException on invalid file attribute value. - */ - public function getFileResource() - { - $file = $this->getAttribute('file'); - if (empty($file) && !$this->getIsNewRecord()) { - $file = $this->refreshFile(); - } - if (empty($file)) { - throw new InvalidParamException('There is no file associated with this object.'); - } elseif ($file instanceof \MongoGridFSFile) { - return $file->getResource(); - } elseif ($file instanceof UploadedFile) { - return fopen($file->tempName, 'r'); - } elseif (is_string($file)) { - if (file_exists($file)) { - return fopen($file, 'r'); - } else { - throw new InvalidParamException("File '{$file}' does not exist."); - } - } else { - throw new InvalidParamException('Unsupported type of "file" attribute.'); - } - } -} diff --git a/extensions/mongodb/file/Collection.php b/extensions/mongodb/file/Collection.php deleted file mode 100644 index bb37a023f9..0000000000 --- a/extensions/mongodb/file/Collection.php +++ /dev/null @@ -1,194 +0,0 @@ - - * @since 2.0 - */ -class Collection extends \yii\mongodb\Collection -{ - /** - * @var \MongoGridFS Mongo GridFS collection instance. - */ - public $mongoCollection; - - /** - * @var \yii\mongodb\Collection file chunks Mongo collection. - */ - private $_chunkCollection; - - - /** - * Returns the Mongo collection for the file chunks. - * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. - * @return \yii\mongodb\Collection mongo collection instance. - */ - public function getChunkCollection($refresh = false) - { - if ($refresh || !is_object($this->_chunkCollection)) { - $this->_chunkCollection = Yii::createObject([ - 'class' => 'yii\mongodb\Collection', - 'mongoCollection' => $this->mongoCollection->chunks - ]); - } - - return $this->_chunkCollection; - } - - /** - * Removes data from the collection. - * @param array $condition description of records to remove. - * @param array $options list of options in format: optionName => optionValue. - * @return integer|boolean number of updated documents or whether operation was successful. - * @throws Exception on failure. - */ - public function remove($condition = [], $options = []) - { - $result = parent::remove($condition, $options); - $this->tryLastError(); // MongoGridFS::remove will return even if the remove failed - - return $result; - } - - /** - * Creates new file in GridFS collection from given local filesystem file. - * Additional attributes can be added file document using $metadata. - * @param string $filename name of the file to store. - * @param array $metadata other metadata fields to include in the file document. - * @param array $options list of options in format: optionName => optionValue - * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] - * unless an "_id" was explicitly specified in the metadata. - * @throws Exception on failure. - */ - public function insertFile($filename, $metadata = [], $options = []) - { - $token = 'Inserting file into ' . $this->getFullName(); - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $options = array_merge(['w' => 1], $options); - $result = $this->mongoCollection->storeFile($filename, $metadata, $options); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Creates new file in GridFS collection with specified content. - * Additional attributes can be added file document using $metadata. - * @param string $bytes string of bytes to store. - * @param array $metadata other metadata fields to include in the file document. - * @param array $options list of options in format: optionName => optionValue - * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] - * unless an "_id" was explicitly specified in the metadata. - * @throws Exception on failure. - */ - public function insertFileContent($bytes, $metadata = [], $options = []) - { - $token = 'Inserting file content into ' . $this->getFullName(); - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $options = array_merge(['w' => 1], $options); - $result = $this->mongoCollection->storeBytes($bytes, $metadata, $options); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Creates new file in GridFS collection from uploaded file. - * Additional attributes can be added file document using $metadata. - * @param string $name name of the uploaded file to store. This should correspond to - * the file field's name attribute in the HTML form. - * @param array $metadata other metadata fields to include in the file document. - * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] - * unless an "_id" was explicitly specified in the metadata. - * @throws Exception on failure. - */ - public function insertUploads($name, $metadata = []) - { - $token = 'Inserting file uploads into ' . $this->getFullName(); - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $result = $this->mongoCollection->storeUpload($name, $metadata); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Retrieves the file with given _id. - * @param mixed $id _id of the file to find. - * @return \MongoGridFSFile|null found file, or null if file does not exist - * @throws Exception on failure. - */ - public function get($id) - { - $token = 'Inserting file uploads into ' . $this->getFullName(); - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $result = $this->mongoCollection->get($id); - Yii::endProfile($token, __METHOD__); - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Deletes the file with given _id. - * @param mixed $id _id of the file to find. - * @return boolean whether the operation was successful. - * @throws Exception on failure. - */ - public function delete($id) - { - $token = 'Inserting file uploads into ' . $this->getFullName(); - Yii::info($token, __METHOD__); - try { - Yii::beginProfile($token, __METHOD__); - $result = $this->mongoCollection->delete($id); - $this->tryResultError($result); - Yii::endProfile($token, __METHOD__); - - return true; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - } -} diff --git a/extensions/mongodb/file/Query.php b/extensions/mongodb/file/Query.php deleted file mode 100644 index 0f67a17ed3..0000000000 --- a/extensions/mongodb/file/Query.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @since 2.0 - */ -class Query extends \yii\mongodb\Query -{ - /** - * Returns the Mongo collection for this query. - * @param \yii\mongodb\Connection $db Mongo connection. - * @return Collection collection instance. - */ - public function getCollection($db = null) - { - if ($db === null) { - $db = Yii::$app->get('mongodb'); - } - - return $db->getFileCollection($this->from); - } - - /** - * @param \MongoGridFSCursor $cursor Mongo cursor instance to fetch data from. - * @param boolean $all whether to fetch all rows or only first one. - * @param string|callable $indexBy value to index by. - * @return array|boolean result. - * @see Query::fetchRows() - */ - protected function fetchRowsInternal($cursor, $all, $indexBy) - { - $result = []; - if ($all) { - foreach ($cursor as $file) { - $row = $file->file; - $row['file'] = $file; - if ($indexBy !== null) { - if (is_string($indexBy)) { - $key = $row[$indexBy]; - } else { - $key = call_user_func($indexBy, $row); - } - $result[$key] = $row; - } else { - $result[] = $row; - } - } - } else { - if ($cursor->hasNext()) { - $file = $cursor->getNext(); - $result = $file->file; - $result['file'] = $file; - } else { - $result = false; - } - } - - return $result; - } -} diff --git a/extensions/mongodb/gii/model/form.php b/extensions/mongodb/gii/model/form.php deleted file mode 100644 index 8ea5284e2b..0000000000 --- a/extensions/mongodb/gii/model/form.php +++ /dev/null @@ -1,14 +0,0 @@ -field($generator, 'collectionName'); -echo $form->field($generator, 'databaseName'); -echo $form->field($generator, 'attributeList'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'ns'); -echo $form->field($generator, 'baseClass'); -echo $form->field($generator, 'db'); -echo $form->field($generator, 'enableI18N')->checkbox(); -echo $form->field($generator, 'messageCategory'); diff --git a/extensions/mongodb/log/MongoDbTarget.php b/extensions/mongodb/log/MongoDbTarget.php deleted file mode 100644 index e2278203f7..0000000000 --- a/extensions/mongodb/log/MongoDbTarget.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @since 2.0 - */ -class MongoDbTarget extends Target -{ - /** - * @var Connection|string the MongoDB connection object or the application component ID of the MongoDB connection. - * After the MongoDbTarget object is created, if you want to change this property, you should only assign it - * with a MongoDB connection object. - */ - public $db = 'mongodb'; - /** - * @var string|array the name of the MongoDB collection that stores the session data. - * Please refer to [[Connection::getCollection()]] on how to specify this parameter. - * This collection is better to be pre-created with fields 'id' and 'expire' indexed. - */ - public $logCollection = 'log'; - - - /** - * Initializes the MongoDbTarget component. - * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection. - * @throws InvalidConfigException if [[db]] is invalid. - */ - public function init() - { - parent::init(); - $this->db = Instance::ensure($this->db, Connection::className()); - } - - /** - * Stores log messages to MongoDB collection. - */ - public function export() - { - $collection = $this->db->getCollection($this->logCollection); - foreach ($this->messages as $message) { - list($text, $level, $category, $timestamp) = $message; - if (!is_string($text)) { - $text = VarDumper::export($text); - } - $collection->insert([ - 'level' => $level, - 'category' => $category, - 'log_time' => $timestamp, - 'prefix' => $this->getMessagePrefix($message), - 'message' => $text, - ]); - } - } -} \ No newline at end of file diff --git a/extensions/mongodb/views/migration.php b/extensions/mongodb/views/migration.php deleted file mode 100644 index 54c4048c23..0000000000 --- a/extensions/mongodb/views/migration.php +++ /dev/null @@ -1,24 +0,0 @@ - - -class extends \yii\mongodb\Migration -{ - public function up() - { - - } - - public function down() - { - echo " cannot be reverted.\n"; - - return false; - } -} diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php deleted file mode 100644 index 81f71dc0a1..0000000000 --- a/extensions/redis/ActiveRecord.php +++ /dev/null @@ -1,331 +0,0 @@ - - * @since 2.0 - */ -class ActiveRecord extends BaseActiveRecord -{ - /** - * Returns the database connection used by this AR class. - * By default, the "redis" application component is used as the database connection. - * You may override this method if you want to use a different database connection. - * @return Connection the database connection used by this AR class. - */ - public static function getDb() - { - return \Yii::$app->get('redis'); - } - - /** - * @inheritdoc - * @return ActiveQuery the newly created [[ActiveQuery]] instance. - */ - public static function find() - { - return Yii::createObject(ActiveQuery::className(), [get_called_class()]); - } - - /** - * Returns the primary key name(s) for this AR class. - * This method should be overridden by child classes to define the primary key. - * - * Note that an array should be returned even when it is a single primary key. - * - * @return string[] the primary keys of this record. - */ - public static function primaryKey() - { - return ['id']; - } - - /** - * Returns the list of all attribute names of the model. - * This method must be overridden by child classes to define available attributes. - * @return array list of attribute names. - */ - public function attributes() - { - throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.'); - } - - /** - * Declares prefix of the key that represents the keys that store this records in redis. - * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]. - * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes - * 'order_item'. You may override this method if you want different key naming. - * @return string the prefix to apply to all AR keys - */ - public static function keyPrefix() - { - return Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); - } - - /** - * @inheritdoc - */ - public function insert($runValidation = true, $attributes = null) - { - if ($runValidation && !$this->validate($attributes)) { - return false; - } - if (!$this->beforeSave(true)) { - return false; - } - $db = static::getDb(); - $values = $this->getDirtyAttributes($attributes); - $pk = []; - foreach ($this->primaryKey() as $key) { - $pk[$key] = $values[$key] = $this->getAttribute($key); - if ($pk[$key] === null) { - $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]); - $this->setAttribute($key, $values[$key]); - } - } - // save pk in a findall pool - $db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]); - - $key = static::keyPrefix() . ':a:' . static::buildKey($pk); - // save attributes - $setArgs = [$key]; - foreach ($values as $attribute => $value) { - // only insert attributes that are not null - if ($value !== null) { - if (is_bool($value)) { - $value = (int) $value; - } - $setArgs[] = $attribute; - $setArgs[] = $value; - } - } - - if (count($setArgs) > 1) { - $db->executeCommand('HMSET', $setArgs); - } - - $changedAttributes = array_fill_keys(array_keys($values), null); - $this->setOldAttributes($values); - $this->afterSave(true, $changedAttributes); - - return true; - } - - /** - * Updates the whole table using the provided attribute values and conditions. - * For example, to change the status to be 1 for all customers whose status is 2: - * - * ~~~ - * Customer::updateAll(['status' => 1], ['id' => 2]); - * ~~~ - * - * @param array $attributes attribute values (name-value pairs) to be saved into the table - * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @return integer the number of rows updated - */ - public static function updateAll($attributes, $condition = null) - { - if (empty($attributes)) { - return 0; - } - $db = static::getDb(); - $n = 0; - foreach (self::fetchPks($condition) as $pk) { - $newPk = $pk; - $pk = static::buildKey($pk); - $key = static::keyPrefix() . ':a:' . $pk; - // save attributes - $delArgs = [$key]; - $setArgs = [$key]; - foreach ($attributes as $attribute => $value) { - if (isset($newPk[$attribute])) { - $newPk[$attribute] = $value; - } - if ($value !== null) { - if (is_bool($value)) { - $value = (int) $value; - } - $setArgs[] = $attribute; - $setArgs[] = $value; - } else { - $delArgs[] = $attribute; - } - } - $newPk = static::buildKey($newPk); - $newKey = static::keyPrefix() . ':a:' . $newPk; - // rename index if pk changed - if ($newPk != $pk) { - $db->executeCommand('MULTI'); - if (count($setArgs) > 1) { - $db->executeCommand('HMSET', $setArgs); - } - if (count($delArgs) > 1) { - $db->executeCommand('HDEL', $delArgs); - } - $db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]); - $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]); - $db->executeCommand('RENAME', [$key, $newKey]); - $db->executeCommand('EXEC'); - } else { - if (count($setArgs) > 1) { - $db->executeCommand('HMSET', $setArgs); - } - if (count($delArgs) > 1) { - $db->executeCommand('HDEL', $delArgs); - } - } - $n++; - } - - return $n; - } - - /** - * Updates the whole table using the provided counter changes and conditions. - * For example, to increment all customers' age by 1, - * - * ~~~ - * Customer::updateAllCounters(['age' => 1]); - * ~~~ - * - * @param array $counters the counters to be updated (attribute name => increment value). - * Use negative values if you want to decrement the counters. - * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @return integer the number of rows updated - */ - public static function updateAllCounters($counters, $condition = null) - { - if (empty($counters)) { - return 0; - } - $db = static::getDb(); - $n = 0; - foreach (self::fetchPks($condition) as $pk) { - $key = static::keyPrefix() . ':a:' . static::buildKey($pk); - foreach ($counters as $attribute => $value) { - $db->executeCommand('HINCRBY', [$key, $attribute, $value]); - } - $n++; - } - - return $n; - } - - /** - * Deletes rows in the table using the provided conditions. - * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. - * - * For example, to delete all customers whose status is 3: - * - * ~~~ - * Customer::deleteAll(['status' => 3]); - * ~~~ - * - * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. - * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @return integer the number of rows deleted - */ - public static function deleteAll($condition = null) - { - $pks = self::fetchPks($condition); - if (empty($pks)) { - return 0; - } - - $db = static::getDb(); - $attributeKeys = []; - $db->executeCommand('MULTI'); - foreach ($pks as $pk) { - $pk = static::buildKey($pk); - $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]); - $attributeKeys[] = static::keyPrefix() . ':a:' . $pk; - } - $db->executeCommand('DEL', $attributeKeys); - $result = $db->executeCommand('EXEC'); - - return end($result); - } - - private static function fetchPks($condition) - { - $query = static::find(); - $query->where($condition); - $records = $query->asArray()->all(); // TODO limit fetched columns to pk - $primaryKey = static::primaryKey(); - - $pks = []; - foreach ($records as $record) { - $pk = []; - foreach ($primaryKey as $key) { - $pk[$key] = $record[$key]; - } - $pks[] = $pk; - } - - return $pks; - } - - /** - * Builds a normalized key from a given primary key value. - * - * @param mixed $key the key to be normalized - * @return string the generated key - */ - public static function buildKey($key) - { - if (is_numeric($key)) { - return $key; - } elseif (is_string($key)) { - return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); - } elseif (is_array($key)) { - if (count($key) == 1) { - return self::buildKey(reset($key)); - } - ksort($key); // ensure order is always the same - $isNumeric = true; - foreach ($key as $value) { - if (!is_numeric($value)) { - $isNumeric = false; - } - } - if ($isNumeric) { - return implode('-', $key); - } - } - - return md5(json_encode($key)); - } -} diff --git a/extensions/redis/Cache.php b/extensions/redis/Cache.php deleted file mode 100644 index 995a0d7d87..0000000000 --- a/extensions/redis/Cache.php +++ /dev/null @@ -1,208 +0,0 @@ - [ - * 'cache' => [ - * 'class' => 'yii\redis\Cache', - * 'redis' => [ - * 'hostname' => 'localhost', - * 'port' => 6379, - * 'database' => 0, - * ] - * ], - * ], - * ] - * ~~~ - * - * Or if you have configured the redis [[Connection]] as an application component, the following is sufficient: - * - * ~~~ - * [ - * 'components' => [ - * 'cache' => [ - * 'class' => 'yii\redis\Cache', - * // 'redis' => 'redis' // id of the connection application component - * ], - * ], - * ] - * ~~~ - * - * @author Carsten Brandt - * @since 2.0 - */ -class Cache extends \yii\caching\Cache -{ - /** - * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]]. - * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure - * redis connection as an application component. - * After the Cache object is created, if you want to change this property, you should only assign it - * with a Redis [[Connection]] object. - */ - public $redis = 'redis'; - - - /** - * Initializes the redis Cache component. - * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection. - * @throws InvalidConfigException if [[redis]] is invalid. - */ - public function init() - { - parent::init(); - if (is_string($this->redis)) { - $this->redis = Yii::$app->get($this->redis); - } elseif (is_array($this->redis)) { - if (!isset($this->redis['class'])) { - $this->redis['class'] = Connection::className(); - } - $this->redis = Yii::createObject($this->redis); - } - if (!$this->redis instanceof Connection) { - throw new InvalidConfigException("Cache::redis must be either a Redis connection instance or the application component ID of a Redis connection."); - } - } - - /** - * Checks whether a specified key exists in the cache. - * This can be faster than getting the value from the cache if the data is big. - * Note that this method does not check whether the dependency associated - * with the cached data, if there is any, has changed. So a call to [[get]] - * may return false while exists returns true. - * @param mixed $key a key identifying the cached value. This can be a simple string or - * a complex data structure consisting of factors representing the key. - * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. - */ - public function exists($key) - { - return (bool) $this->redis->executeCommand('EXISTS', [$this->buildKey($key)]); - } - - /** - * @inheritdoc - */ - protected function getValue($key) - { - return $this->redis->executeCommand('GET', [$key]); - } - - /** - * @inheritdoc - */ - protected function getValues($keys) - { - $response = $this->redis->executeCommand('MGET', $keys); - $result = []; - $i = 0; - foreach ($keys as $key) { - $result[$key] = $response[$i++]; - } - - return $result; - } - - /** - * @inheritdoc - */ - protected function setValue($key, $value, $expire) - { - if ($expire == 0) { - return (bool) $this->redis->executeCommand('SET', [$key, $value]); - } else { - $expire = (int) ($expire * 1000); - - return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire]); - } - } - - /** - * @inheritdoc - */ - protected function setValues($data, $expire) - { - $args = []; - foreach ($data as $key => $value) { - $args[] = $key; - $args[] = $value; - } - - $failedKeys = []; - if ($expire == 0) { - $this->redis->executeCommand('MSET', $args); - } else { - $expire = (int) ($expire * 1000); - $this->redis->executeCommand('MULTI'); - $this->redis->executeCommand('MSET', $args); - $index = []; - foreach ($data as $key => $value) { - $this->redis->executeCommand('PEXPIRE', [$key, $expire]); - $index[] = $key; - } - $result = $this->redis->executeCommand('EXEC'); - array_shift($result); - foreach ($result as $i => $r) { - if ($r != 1) { - $failedKeys[] = $index[$i]; - } - } - } - - return $failedKeys; - } - - /** - * @inheritdoc - */ - protected function addValue($key, $value, $expire) - { - if ($expire == 0) { - return (bool) $this->redis->executeCommand('SET', [$key, $value, 'NX']); - } else { - $expire = (int) ($expire * 1000); - - return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']); - } - } - - /** - * @inheritdoc - */ - protected function deleteValue($key) - { - return (bool) $this->redis->executeCommand('DEL', [$key]); - } - - /** - * @inheritdoc - */ - protected function flushValues() - { - return $this->redis->executeCommand('FLUSHDB'); - } -} diff --git a/extensions/redis/LICENSE.md b/extensions/redis/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/redis/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/redis/LuaScriptBuilder.php b/extensions/redis/LuaScriptBuilder.php deleted file mode 100644 index 2ba81116f3..0000000000 --- a/extensions/redis/LuaScriptBuilder.php +++ /dev/null @@ -1,401 +0,0 @@ - - * @since 2.0 - */ -class LuaScriptBuilder extends \yii\base\Object -{ - /** - * Builds a Lua script for finding a list of records - * @param ActiveQuery $query the query used to build the script - * @return string - */ - public function buildAll($query) - { - // TODO add support for orderBy - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks'); - } - - /** - * Builds a Lua script for finding one record - * @param ActiveQuery $query the query used to build the script - * @return string - */ - public function buildOne($query) - { - // TODO add support for orderBy - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks'); - } - - /** - * Builds a Lua script for finding a column - * @param ActiveQuery $query the query used to build the script - * @param string $column name of the column - * @return string - */ - public function buildColumn($query, $column) - { - // TODO add support for orderBy and indexBy - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks'); - } - - /** - * Builds a Lua script for getting count of records - * @param ActiveQuery $query the query used to build the script - * @return string - */ - public function buildCount($query) - { - return $this->build($query, 'n=n+1', 'n'); - } - - /** - * Builds a Lua script for finding the sum of a column - * @param ActiveQuery $query the query used to build the script - * @param string $column name of the column - * @return string - */ - public function buildSum($query, $column) - { - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n'); - } - - /** - * Builds a Lua script for finding the average of a column - * @param ActiveQuery $query the query used to build the script - * @param string $column name of the column - * @return string - */ - public function buildAverage($query, $column) - { - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n'); - } - - /** - * Builds a Lua script for finding the min value of a column - * @param ActiveQuery $query the query used to build the script - * @param string $column name of the column - * @return string - */ - public function buildMin($query, $column) - { - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or nmodelClass; - $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); - - return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v'); - } - - /** - * @param ActiveQuery $query the query used to build the script - * @param string $buildResult the lua script for building the result - * @param string $return the lua variable that should be returned - * @throws NotSupportedException when query contains unsupported order by condition - * @return string - */ - private function build($query, $buildResult, $return) - { - if (!empty($query->orderBy)) { - throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.'); - } - - $columns = []; - if ($query->where !== null) { - $condition = $this->buildCondition($query->where, $columns); - } else { - $condition = 'true'; - } - - $start = $query->offset === null ? 0 : $query->offset; - $limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit)); - - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::keyPrefix()); - $loadColumnValues = ''; - foreach ($columns as $column => $alias) { - $loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n"; - } - - return << 'buildNotCondition', - 'and' => 'buildAndCondition', - 'or' => 'buildAndCondition', - 'between' => 'buildBetweenCondition', - 'not between' => 'buildBetweenCondition', - 'in' => 'buildInCondition', - 'not in' => 'buildInCondition', - 'like' => 'buildLikeCondition', - 'not like' => 'buildLikeCondition', - 'or like' => 'buildLikeCondition', - 'or not like' => 'buildLikeCondition', - ]; - - if (!is_array($condition)) { - throw new NotSupportedException('Where condition must be an array in redis ActiveRecord.'); - } - if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... - $operator = strtolower($condition[0]); - if (isset($builders[$operator])) { - $method = $builders[$operator]; - array_shift($condition); - - return $this->$method($operator, $condition, $columns); - } else { - throw new Exception('Found unknown operator in query: ' . $operator); - } - } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... - - return $this->buildHashCondition($condition, $columns); - } - } - - private function buildHashCondition($condition, &$columns) - { - $parts = []; - foreach ($condition as $column => $value) { - if (is_array($value)) { // IN condition - $parts[] = $this->buildInCondition('in', [$column, $value], $columns); - } else { - if (is_bool($value)) { - $value = (int) $value; - } - if ($value === null) { - $parts[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0"; - } elseif ($value instanceof Expression) { - $column = $this->addColumn($column, $columns); - $parts[] = "$column==" . $value->expression; - } else { - $column = $this->addColumn($column, $columns); - $value = $this->quoteValue($value); - $parts[] = "$column==$value"; - } - } - } - - return count($parts) === 1 ? $parts[0] : '(' . implode(') and (', $parts) . ')'; - } - - private function buildNotCondition($operator, $operands, &$params) - { - if (count($operands) != 1) { - throw new InvalidParamException("Operator '$operator' requires exactly one operand."); - } - - $operand = reset($operands); - if (is_array($operand)) { - $operand = $this->buildCondition($operand, $params); - } - - return "!($operand)"; - } - - private function buildAndCondition($operator, $operands, &$columns) - { - $parts = []; - foreach ($operands as $operand) { - if (is_array($operand)) { - $operand = $this->buildCondition($operand, $columns); - } - if ($operand !== '') { - $parts[] = $operand; - } - } - if (!empty($parts)) { - return '(' . implode(") $operator (", $parts) . ')'; - } else { - return ''; - } - } - - private function buildBetweenCondition($operator, $operands, &$columns) - { - if (!isset($operands[0], $operands[1], $operands[2])) { - throw new Exception("Operator '$operator' requires three operands."); - } - - list($column, $value1, $value2) = $operands; - - $value1 = $this->quoteValue($value1); - $value2 = $this->quoteValue($value2); - $column = $this->addColumn($column, $columns); - - return "$column >= $value1 and $column <= $value2"; - } - - private function buildInCondition($operator, $operands, &$columns) - { - if (!isset($operands[0], $operands[1])) { - throw new Exception("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - $values = (array) $values; - - if (empty($values) || $column === []) { - return $operator === 'in' ? 'false' : 'true'; - } - - if (count($column) > 1) { - return $this->buildCompositeInCondition($operator, $column, $values, $columns); - } elseif (is_array($column)) { - $column = reset($column); - } - $columnAlias = $this->addColumn($column, $columns); - $parts = []; - foreach ($values as $value) { - if (is_array($value)) { - $value = isset($value[$column]) ? $value[$column] : null; - } - if ($value === null) { - $parts[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0"; - } elseif ($value instanceof Expression) { - $parts[] = "$columnAlias==" . $value->expression; - } else { - $value = $this->quoteValue($value); - $parts[] = "$columnAlias==$value"; - } - } - $operator = $operator === 'in' ? '' : 'not '; - - return "$operator(" . implode(' or ', $parts) . ')'; - } - - protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns) - { - $vss = []; - foreach ($values as $value) { - $vs = []; - foreach ($inColumns as $column) { - if (isset($value[$column])) { - $columnAlias = $this->addColumn($column, $columns); - $vs[] = "$columnAlias==" . $this->quoteValue($value[$column]); - } else { - $vs[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0"; - } - } - $vss[] = '(' . implode(' and ', $vs) . ')'; - } - $operator = $operator === 'in' ? '' : 'not '; - - return "$operator(" . implode(' or ', $vss) . ')'; - } - - private function buildLikeCondition($operator, $operands, &$columns) - { - throw new NotSupportedException('LIKE conditions are not suppoerted by redis ActiveRecord.'); - } -} diff --git a/extensions/redis/Session.php b/extensions/redis/Session.php deleted file mode 100644 index 70a74b7dcd..0000000000 --- a/extensions/redis/Session.php +++ /dev/null @@ -1,154 +0,0 @@ - [ - * 'session' => [ - * 'class' => 'yii\redis\Session', - * 'redis' => [ - * 'hostname' => 'localhost', - * 'port' => 6379, - * 'database' => 0, - * ] - * ], - * ], - * ] - * ~~~ - * - * Or if you have configured the redis [[Connection]] as an application component, the following is sufficient: - * - * ~~~ - * [ - * 'components' => [ - * 'session' => [ - * 'class' => 'yii\redis\Session', - * // 'redis' => 'redis' // id of the connection application component - * ], - * ], - * ] - * ~~~ - * - * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. - * - * @author Carsten Brandt - * @since 2.0 - */ -class Session extends \yii\web\Session -{ - /** - * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]]. - * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure - * redis connection as an application component. - * After the Session object is created, if you want to change this property, you should only assign it - * with a Redis [[Connection]] object. - */ - public $redis = 'redis'; - /** - * @var string a string prefixed to every cache key so that it is unique. If not set, - * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string - * if you don't want to use key prefix. It is recommended that you explicitly set this property to some - * static value if the cached data needs to be shared among multiple applications. - */ - public $keyPrefix; - - - /** - * Initializes the redis Session component. - * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection. - * @throws InvalidConfigException if [[redis]] is invalid. - */ - public function init() - { - if (is_string($this->redis)) { - $this->redis = Yii::$app->get($this->redis); - } elseif (is_array($this->redis)) { - if (!isset($this->redis['class'])) { - $this->redis['class'] = Connection::className(); - } - $this->redis = Yii::createObject($this->redis); - } - if (!$this->redis instanceof Connection) { - throw new InvalidConfigException("Session::redis must be either a Redis connection instance or the application component ID of a Redis connection."); - } - if ($this->keyPrefix === null) { - $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5); - } - parent::init(); - } - - /** - * Returns a value indicating whether to use custom session storage. - * This method overrides the parent implementation and always returns true. - * @return boolean whether to use custom storage. - */ - public function getUseCustomStorage() - { - return true; - } - - /** - * Session read handler. - * Do not call this method directly. - * @param string $id session ID - * @return string the session data - */ - public function readSession($id) - { - $data = $this->redis->executeCommand('GET', [$this->calculateKey($id)]); - - return $data === false ? '' : $data; - } - - /** - * Session write handler. - * Do not call this method directly. - * @param string $id session ID - * @param string $data session data - * @return boolean whether session write is successful - */ - public function writeSession($id, $data) - { - return (bool) $this->redis->executeCommand('SET', [$this->calculateKey($id), $data, 'EX', $this->getTimeout()]); - } - - /** - * Session destroy handler. - * Do not call this method directly. - * @param string $id session ID - * @return boolean whether session is destroyed successfully - */ - public function destroySession($id) - { - return (bool) $this->redis->executeCommand('DEL', [$this->calculateKey($id)]); - } - - /** - * Generates a unique key used for storing session data in cache. - * @param string $id session variable name - * @return string a safe cache key associated with the session variable name - */ - protected function calculateKey($id) - { - return $this->keyPrefix . md5(json_encode([__CLASS__, $id])); - } -} diff --git a/extensions/redis/composer.json b/extensions/redis/composer.json deleted file mode 100644 index 6526e23308..0000000000 --- a/extensions/redis/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "yiisoft/yii2-redis", - "description": "Redis Cache, Session and ActiveRecord for the Yii framework", - "keywords": ["yii2", "redis", "active-record", "cache", "session"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aredis", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc" - } - ], - "require": { - "yiisoft/yii2": "*" - }, - "autoload": { - "psr-4": { "yii\\redis\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/smarty/LICENSE.md b/extensions/smarty/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/smarty/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/smarty/composer.json b/extensions/smarty/composer.json deleted file mode 100644 index 15940f686b..0000000000 --- a/extensions/smarty/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "yiisoft/yii2-smarty", - "description": "The Smarty integration for the Yii framework", - "keywords": ["yii2", "smarty", "renderer"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Asmarty", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru" - } - ], - "require": { - "yiisoft/yii2": "*", - "smarty/smarty": "*" - }, - "autoload": { - "psr-4": { "yii\\smarty\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/sphinx/ActiveRecord.php b/extensions/sphinx/ActiveRecord.php deleted file mode 100644 index 9dc858a9c5..0000000000 --- a/extensions/sphinx/ActiveRecord.php +++ /dev/null @@ -1,650 +0,0 @@ - - * @since 2.0 - */ -abstract class ActiveRecord extends BaseActiveRecord -{ - /** - * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_INSERT = 0x01; - /** - * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_UPDATE = 0x02; - /** - * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_DELETE = 0x04; - /** - * All three operations: insert, update, delete. - * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE. - */ - const OP_ALL = 0x07; - - /** - * @var string current snippet value for this Active Record instance. - * It will be filled up automatically when instance found using [[Query::snippetCallback]] - * or [[ActiveQuery::snippetByModel()]]. - */ - private $_snippet; - - - /** - * Returns the Sphinx connection used by this AR class. - * By default, the "sphinx" application component is used as the Sphinx connection. - * You may override this method if you want to use a different Sphinx connection. - * @return Connection the Sphinx connection used by this AR class. - */ - public static function getDb() - { - return \Yii::$app->get('sphinx'); - } - - /** - * Creates an [[ActiveQuery]] instance with a given SQL statement. - * - * Note that because the SQL statement is already specified, calling additional - * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]] - * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is - * still fine. - * - * Below is an example: - * - * ~~~ - * $customers = Article::findBySql("SELECT * FROM `idx_article` WHERE MATCH('development')")->all(); - * ~~~ - * - * @param string $sql the SQL statement to be executed - * @param array $params parameters to be bound to the SQL statement during execution. - * @return ActiveQuery the newly created [[ActiveQuery]] instance - */ - public static function findBySql($sql, $params = []) - { - $query = static::find(); - $query->sql = $sql; - - return $query->params($params); - } - - /** - * Updates the whole table using the provided attribute values and conditions. - * For example, to change the status to be 1 for all articles which status is 2: - * - * ~~~ - * Article::updateAll(['status' => 1], 'status = 2'); - * ~~~ - * - * @param array $attributes attribute values (name-value pairs) to be saved into the table - * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return integer the number of rows updated - */ - public static function updateAll($attributes, $condition = '', $params = []) - { - $command = static::getDb()->createCommand(); - $command->update(static::indexName(), $attributes, $condition, $params); - - return $command->execute(); - } - - /** - * Deletes rows in the index using the provided conditions. - * - * For example, to delete all articles whose status is 3: - * - * ~~~ - * Article::deleteAll('status = 3'); - * ~~~ - * - * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return integer the number of rows deleted - */ - public static function deleteAll($condition = '', $params = []) - { - $command = static::getDb()->createCommand(); - $command->delete(static::indexName(), $condition, $params); - - return $command->execute(); - } - - /** - * @inheritdoc - * @return ActiveQuery the newly created [[ActiveQuery]] instance. - */ - public static function find() - { - return Yii::createObject(ActiveQuery::className(), [get_called_class()]); - } - - /** - * Declares the name of the Sphinx index associated with this AR class. - * By default this method returns the class name as the index name by calling [[Inflector::camel2id()]]. - * For example, 'Article' becomes 'article', and 'StockItem' becomes - * 'stock_item'. You may override this method if the index is not named after this convention. - * @return string the index name - */ - public static function indexName() - { - return Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); - } - - /** - * Returns the schema information of the Sphinx index associated with this AR class. - * @return IndexSchema the schema information of the Sphinx index associated with this AR class. - * @throws InvalidConfigException if the index for the AR class does not exist. - */ - public static function getIndexSchema() - { - $schema = static::getDb()->getIndexSchema(static::indexName()); - if ($schema !== null) { - return $schema; - } else { - throw new InvalidConfigException("The index does not exist: " . static::indexName()); - } - } - - /** - * Returns the primary key name for this AR class. - * The default implementation will return the primary key as declared - * in the Sphinx index, which is associated with this AR class. - * - * Note that an array should be returned even for a table with single primary key. - * - * @return string[] the primary keys of the associated Sphinx index. - */ - public static function primaryKey() - { - return [static::getIndexSchema()->primaryKey]; - } - - /** - * Builds a snippet from provided data and query, using specified index settings. - * @param string|array $source is the source data to extract a snippet from. - * It could be either a single string or array of strings. - * @param string $match the full-text query to build snippets for. - * @param array $options list of options in format: optionName => optionValue - * @return string|array built snippet in case "source" is a string, list of built snippets - * in case "source" is an array. - */ - public static function callSnippets($source, $match, $options = []) - { - $command = static::getDb()->createCommand(); - $command->callSnippets(static::indexName(), $source, $match, $options); - if (is_array($source)) { - return $command->queryColumn(); - } else { - return $command->queryScalar(); - } - } - - /** - * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics. - * @param string $text the text to break down to keywords. - * @param boolean $fetchStatistic whether to return document and hit occurrence statistics - * @return array keywords and statistics - */ - public static function callKeywords($text, $fetchStatistic = false) - { - $command = static::getDb()->createCommand(); - $command->callKeywords(static::indexName(), $text, $fetchStatistic); - - return $command->queryAll(); - } - - /** - * @param string $snippet - */ - public function setSnippet($snippet) - { - $this->_snippet = $snippet; - } - - /** - * Returns current snippet value or generates new one from given match. - * @param string $match snippet source query - * @param array $options list of options in format: optionName => optionValue - * @return string snippet value - */ - public function getSnippet($match = null, $options = []) - { - if ($match !== null) { - $this->_snippet = $this->fetchSnippet($match, $options); - } - - return $this->_snippet; - } - - /** - * Builds up the snippet value from the given query. - * @param string $match the full-text query to build snippets for. - * @param array $options list of options in format: optionName => optionValue - * @return string snippet value. - */ - protected function fetchSnippet($match, $options = []) - { - return static::callSnippets($this->getSnippetSource(), $match, $options); - } - - /** - * Returns the string, which should be used as a source to create snippet for this - * Active Record instance. - * Child classes must implement this method to return the actual snippet source text. - * For example: - * ~~~ - * public function getSnippetSource() - * { - * return $this->snippetSourceRelation->content; - * } - * ~~~ - * @return string snippet source string. - * @throws \yii\base\NotSupportedException if this is not supported by the Active Record class - */ - public function getSnippetSource() - { - throw new NotSupportedException($this->className() . ' does not provide snippet source.'); - } - - /** - * Declares which operations should be performed within a transaction in different scenarios. - * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], - * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively. - * By default, these methods are NOT enclosed in a transaction. - * - * In some scenarios, to ensure data consistency, you may want to enclose some or all of them - * in transactions. You can do so by overriding this method and returning the operations - * that need to be transactional. For example, - * - * ~~~ - * return [ - * 'admin' => self::OP_INSERT, - * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, - * // the above is equivalent to the following: - * // 'api' => self::OP_ALL, - * - * ]; - * ~~~ - * - * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) - * should be done in a transaction; and in the "api" scenario, all the operations should be done - * in a transaction. - * - * @return array the declarations of transactional operations. The array keys are scenarios names, - * and the array values are the corresponding transaction operations. - */ - public function transactions() - { - return []; - } - - /** - * Returns the list of all attribute names of the model. - * The default implementation will return all column names of the table associated with this AR class. - * @return array list of attribute names. - */ - public function attributes() - { - return array_keys(static::getIndexSchema()->columns); - } - - /** - * Inserts a row into the associated Sphinx index using the attribute values of this record. - * - * This method performs the following steps in order: - * - * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation - * fails, it will skip the rest of the steps; - * 2. call [[afterValidate()]] when `$runValidation` is true. - * 3. call [[beforeSave()]]. If the method returns false, it will skip the - * rest of the steps; - * 4. insert the record into index. If this fails, it will skip the rest of the steps; - * 5. call [[afterSave()]]; - * - * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] - * will be raised by the corresponding methods. - * - * Only the [[changedAttributes|changed attribute values]] will be inserted. - * - * For example, to insert an article record: - * - * ~~~ - * $article = new Article; - * $article->id = $id; - * $article->genre_id = $genreId; - * $article->content = $content; - * $article->insert(); - * ~~~ - * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted. - * @param array $attributes list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from index will be saved. - * @return boolean whether the attributes are valid and the record is inserted successfully. - * @throws \Exception in case insert failed. - */ - public function insert($runValidation = true, $attributes = null) - { - if ($runValidation && !$this->validate($attributes)) { - return false; - } - $db = static::getDb(); - if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) { - $transaction = $db->beginTransaction(); - try { - $result = $this->insertInternal($attributes); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - } catch (\Exception $e) { - $transaction->rollBack(); - throw $e; - } - } else { - $result = $this->insertInternal($attributes); - } - - return $result; - } - - /** - * @see ActiveRecord::insert() - */ - private function insertInternal($attributes = null) - { - if (!$this->beforeSave(true)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - foreach ($this->getPrimaryKey(true) as $key => $value) { - $values[$key] = $value; - } - } - $db = static::getDb(); - $command = $db->createCommand()->insert($this->indexName(), $values); - if (!$command->execute()) { - return false; - } - - $changedAttributes = array_fill_keys(array_keys($values), null); - $this->setOldAttributes($values); - $this->afterSave(true, $changedAttributes); - - return true; - } - - /** - * Saves the changes to this active record into the associated Sphinx index. - * - * This method performs the following steps in order: - * - * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation - * fails, it will skip the rest of the steps; - * 2. call [[afterValidate()]] when `$runValidation` is true. - * 3. call [[beforeSave()]]. If the method returns false, it will skip the - * rest of the steps; - * 4. save the record into index. If this fails, it will skip the rest of the steps; - * 5. call [[afterSave()]]; - * - * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] - * will be raised by the corresponding methods. - * - * Only the [[changedAttributes|changed attribute values]] will be saved into database. - * - * For example, to update an article record: - * - * ~~~ - * $article = Article::findOne($id); - * $article->genre_id = $genreId; - * $article->group_id = $groupId; - * $article->update(); - * ~~~ - * - * Note that it is possible the update does not affect any row in the table. - * In this case, this method will return 0. For this reason, you should use the following - * code to check if update() is successful or not: - * - * ~~~ - * if ($this->update() !== false) { - * // update successful - * } else { - * // update failed - * } - * ~~~ - * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. - * @param array $attributeNames list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return integer|boolean the number of rows affected, or false if validation fails - * or [[beforeSave()]] stops the updating process. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being updated is outdated. - * @throws \Exception in case update failed. - */ - public function update($runValidation = true, $attributeNames = null) - { - if ($runValidation && !$this->validate($attributeNames)) { - return false; - } - $db = static::getDb(); - if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) { - $transaction = $db->beginTransaction(); - try { - $result = $this->updateInternal($attributeNames); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - } catch (\Exception $e) { - $transaction->rollBack(); - throw $e; - } - } else { - $result = $this->updateInternal($attributeNames); - } - - return $result; - } - - /** - * @see CActiveRecord::update() - * @throws StaleObjectException - */ - protected function updateInternal($attributes = null) - { - if (!$this->beforeSave(false)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - $this->afterSave(false, $values); - return 0; - } - - // Replace is supported only by runtime indexes and necessary only for field update - $useReplace = false; - $indexSchema = $this->getIndexSchema(); - if ($this->getIndexSchema()->isRuntime) { - foreach ($values as $name => $value) { - $columnSchema = $indexSchema->getColumn($name); - if ($columnSchema->isField) { - $useReplace = true; - break; - } - } - } - - if ($useReplace) { - $values = array_merge($values, $this->getOldPrimaryKey(true)); - $command = static::getDb()->createCommand(); - $command->replace(static::indexName(), $values); - // We do not check the return value of replace because it's possible - // that the REPLACE statement doesn't change anything and thus returns 0. - $rows = $command->execute(); - } else { - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - if (!isset($values[$lock])) { - $values[$lock] = $this->$lock + 1; - } - $condition[$lock] = $this->$lock; - } - // We do not check the return value of updateAll() because it's possible - // that the UPDATE statement doesn't change anything and thus returns 0. - $rows = $this->updateAll($values, $condition); - - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being updated is outdated.'); - } - } - - $changedAttributes = []; - foreach ($values as $name => $value) { - $changedAttributes[$name] = $this->getOldAttribute($name); - $this->setOldAttribute($name, $value); - } - $this->afterSave(false, $changedAttributes); - - return $rows; - } - - /** - * Deletes the index entry corresponding to this active record. - * - * This method performs the following steps in order: - * - * 1. call [[beforeDelete()]]. If the method returns false, it will skip the - * rest of the steps; - * 2. delete the record from the index; - * 3. call [[afterDelete()]]. - * - * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] - * will be raised by the corresponding methods. - * - * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. - * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being deleted is outdated. - * @throws \Exception in case delete failed. - */ - public function delete() - { - $db = static::getDb(); - $transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null; - try { - $result = false; - if ($this->beforeDelete()) { - // we do not check the return value of deleteAll() because it's possible - // the record is already deleted in the database and thus the method will return 0 - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - $condition[$lock] = $this->$lock; - } - $result = $this->deleteAll($condition); - if ($lock !== null && !$result) { - throw new StaleObjectException('The object being deleted is outdated.'); - } - $this->setOldAttributes(null); - $this->afterDelete(); - } - if ($transaction !== null) { - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - } - } catch (\Exception $e) { - if ($transaction !== null) { - $transaction->rollBack(); - } - throw $e; - } - - return $result; - } - - /** - * Returns a value indicating whether the given active record is the same as the current one. - * The comparison is made by comparing the index names and the primary key values of the two active records. - * If one of the records [[isNewRecord|is new]] they are also considered not equal. - * @param ActiveRecord $record record to compare to - * @return boolean whether the two active records refer to the same row in the same index. - */ - public function equals($record) - { - if ($this->isNewRecord || $record->isNewRecord) { - return false; - } - - return $this->indexName() === $record->indexName() && $this->getPrimaryKey() === $record->getPrimaryKey(); - } - - /** - * @inheritdoc - */ - public static function populateRecord($record, $row) - { - $columns = static::getIndexSchema()->columns; - foreach ($row as $name => $value) { - if (isset($columns[$name])) { - if ($columns[$name]->isMva) { - $mvaValue = explode(',', $value); - $row[$name] = array_map([$columns[$name], 'phpTypecast'], $mvaValue); - } else { - $row[$name] = $columns[$name]->phpTypecast($value); - } - } - } - parent::populateRecord($record, $row); - } - - /** - * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. - * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. - * @return boolean whether the specified operation is transactional in the current [[scenario]]. - */ - public function isTransactional($operation) - { - $scenario = $this->getScenario(); - $transactions = $this->transactions(); - - return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); - } -} diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php deleted file mode 100644 index 44fa3229ad..0000000000 --- a/extensions/sphinx/Command.php +++ /dev/null @@ -1,334 +0,0 @@ -createCommand("SELECT * FROM `idx_article` WHERE MATCH('programming')")->queryAll(); - * ~~~ - * - * Command supports SQL statement preparation and parameter binding just as [[\yii\db\Command]] does. - * - * Command also supports building SQL statements by providing methods such as [[insert()]], - * [[update()]], etc. For example, - * - * ~~~ - * $connection->createCommand()->update('idx_article', [ - * 'genre_id' => 15, - * 'author_id' => 157, - * ])->execute(); - * ~~~ - * - * To build SELECT SQL statements, please use [[Query]] and [[QueryBuilder]] instead. - * - * @author Paul Klimov - * @since 2.0 - */ -class Command extends \yii\db\Command -{ - /** - * @var \yii\sphinx\Connection the Sphinx connection that this command is associated with. - */ - public $db; - - - /** - * Creates a batch INSERT command. - * For example, - * - * ~~~ - * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [ - * ['Tom', 30], - * ['Jane', 20], - * ['Linda', 25], - * ])->execute(); - * ~~~ - * - * Note that the values in each row must match the corresponding column names. - * - * @param string $index the index that new rows will be inserted into. - * @param array $columns the column names - * @param array $rows the rows to be batch inserted into the index - * @return static the command object itself - */ - public function batchInsert($index, $columns, $rows) - { - $params = []; - $sql = $this->db->getQueryBuilder()->batchInsert($index, $columns, $rows, $params); - - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates an REPLACE command. - * For example, - * - * ~~~ - * $connection->createCommand()->insert('idx_user', [ - * 'name' => 'Sam', - * 'age' => 30, - * ])->execute(); - * ~~~ - * - * The method will properly escape the column names, and bind the values to be replaced. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $index the index that new rows will be replaced into. - * @param array $columns the column data (name => value) to be replaced into the index. - * @return static the command object itself - */ - public function replace($index, $columns) - { - $params = []; - $sql = $this->db->getQueryBuilder()->replace($index, $columns, $params); - - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates a batch REPLACE command. - * For example, - * - * ~~~ - * $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [ - * ['Tom', 30], - * ['Jane', 20], - * ['Linda', 25], - * ])->execute(); - * ~~~ - * - * Note that the values in each row must match the corresponding column names. - * - * @param string $index the index that new rows will be replaced. - * @param array $columns the column names - * @param array $rows the rows to be batch replaced in the index - * @return static the command object itself - */ - public function batchReplace($index, $columns, $rows) - { - $params = []; - $sql = $this->db->getQueryBuilder()->batchReplace($index, $columns, $rows, $params); - - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates an UPDATE command. - * For example, - * - * ~~~ - * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); - * ~~~ - * - * The method will properly escape the column names and bind the values to be updated. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $index the index to be updated. - * @param array $columns the column data (name => value) to be updated. - * @param string|array $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the command - * @param array $options list of options in format: optionName => optionValue - * @return static the command object itself - */ - public function update($index, $columns, $condition = '', $params = [], $options = []) - { - $sql = $this->db->getQueryBuilder()->update($index, $columns, $condition, $params, $options); - - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates a SQL command for truncating a runtime index. - * @param string $index the index to be truncated. The name will be properly quoted by the method. - * @return static the command object itself - */ - public function truncateIndex($index) - { - $sql = $this->db->getQueryBuilder()->truncateIndex($index); - - return $this->setSql($sql); - } - - /** - * Builds a snippet from provided data and query, using specified index settings. - * @param string $index name of the index, from which to take the text processing settings. - * @param string|array $source is the source data to extract a snippet from. - * It could be either a single string or array of strings. - * @param string $match the full-text query to build snippets for. - * @param array $options list of options in format: optionName => optionValue - * @return static the command object itself - */ - public function callSnippets($index, $source, $match, $options = []) - { - $params = []; - $sql = $this->db->getQueryBuilder()->callSnippets($index, $source, $match, $options, $params); - - return $this->setSql($sql)->bindValues($params); - } - - /** - * Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics. - * @param string $index the name of the index from which to take the text processing settings - * @param string $text the text to break down to keywords. - * @param boolean $fetchStatistic whether to return document and hit occurrence statistics - * @return static the command object itself - */ - public function callKeywords($index, $text, $fetchStatistic = false) - { - $params = []; - $sql = $this->db->getQueryBuilder()->callKeywords($index, $text, $fetchStatistic, $params); - - return $this->setSql($sql)->bindValues($params); - } - - // Not Supported : - - /** - * @inheritdoc - */ - public function createTable($table, $columns, $options = null) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function renameTable($table, $newName) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function dropTable($table) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function truncateTable($table) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function addColumn($table, $column, $type) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function dropColumn($table, $column) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function renameColumn($table, $oldName, $newName) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function alterColumn($table, $column, $type) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function addPrimaryKey($name, $table, $columns) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function dropPrimaryKey($name, $table) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function dropForeignKey($name, $table) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function createIndex($name, $table, $columns, $unique = false) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function dropIndex($name, $table) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function resetSequence($table, $value = null) - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * @inheritdoc - */ - public function checkIntegrity($check = true, $schema = '', $table = '') - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } -} diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php deleted file mode 100644 index 32ba492ccc..0000000000 --- a/extensions/sphinx/Connection.php +++ /dev/null @@ -1,148 +0,0 @@ - 'mysql:host=127.0.0.1;port=9306;', - * 'username' => $username, - * 'password' => $password, - * ]); - * $connection->open(); - * ~~~ - * - * After the Sphinx connection is established, one can execute SQL statements like the following: - * ~~~ - * $command = $connection->createCommand("SELECT * FROM idx_article WHERE MATCH('programming')"); - * $articles = $command->queryAll(); - * $command = $connection->createCommand('UPDATE idx_article SET status=2 WHERE id=1'); - * $command->execute(); - * ~~~ - * - * For more information about how to perform various DB queries, please refer to [[Command]]. - * - * This class supports transactions exactly as "yii\db\Connection". - * - * Note: while this class extends "yii\db\Connection" some of its methods are not supported. - * - * @method \yii\sphinx\Schema getSchema() The schema information for this Sphinx connection - * @method \yii\sphinx\QueryBuilder getQueryBuilder() the query builder for this Sphinx connection - * - * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the - * sequence object. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class Connection extends \yii\db\Connection -{ - /** - * @inheritdoc - */ - public $schemaMap = [ - 'mysqli' => 'yii\sphinx\Schema', // MySQL - 'mysql' => 'yii\sphinx\Schema', // MySQL - ]; - - - /** - * Obtains the schema information for the named index. - * @param string $name index name. - * @param boolean $refresh whether to reload the table schema even if it is found in the cache. - * @return IndexSchema index schema information. Null if the named index does not exist. - */ - public function getIndexSchema($name, $refresh = false) - { - return $this->getSchema()->getIndexSchema($name, $refresh); - } - - /** - * Quotes a index name for use in a query. - * If the index name contains schema prefix, the prefix will also be properly quoted. - * If the index name is already quoted or contains special characters including '(', '[[' and '{{', - * then this method will do nothing. - * @param string $name index name - * @return string the properly quoted index name - */ - public function quoteIndexName($name) - { - return $this->getSchema()->quoteIndexName($name); - } - - /** - * Alias of [[quoteIndexName()]]. - * @param string $name table name - * @return string the properly quoted table name - */ - public function quoteTableName($name) - { - return $this->quoteIndexName($name); - } - - /** - * Creates a command for execution. - * @param string $sql the SQL statement to be executed - * @param array $params the parameters to be bound to the SQL statement - * @return Command the Sphinx command - */ - public function createCommand($sql = null, $params = []) - { - $command = new Command([ - 'db' => $this, - 'sql' => $sql, - ]); - - return $command->bindValues($params); - } - - /** - * This method is not supported by Sphinx. - * @param string $sequenceName name of the sequence object - * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object - * @throws \yii\base\NotSupportedException always. - */ - public function getLastInsertID($sequenceName = '') - { - throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); - } - - /** - * Escapes all special characters from 'MATCH' statement argument. - * Make sure you are using this method whenever composing 'MATCH' search statement. - * Note: this method does not perform quoting, you should place the result in the quotes - * an perform additional escaping for it manually, the best way to do it is using PDO parameter. - * @param string $str string to be escaped. - * @return string the properly escaped string. - */ - public function escapeMatchValue($str) - { - return str_replace( - ['\\', '/', '"', '(', ')', '|', '-', '!', '@', '~', '&', '^', '$', '=', '>', '<', "\x00", "\n", "\r", "\x1a"], - ['\\\\', '\\/', '\\"', '\\(', '\\)', '\\|', '\\-', '\\!', '\\@', '\\~', '\\&', '\\^', '\\$', '\\=', '\\>', '\\<', "\\x00", "\\n", "\\r", "\\x1a"], - $str - ); - } -} diff --git a/extensions/sphinx/IndexSchema.php b/extensions/sphinx/IndexSchema.php deleted file mode 100644 index 21ede67bb2..0000000000 --- a/extensions/sphinx/IndexSchema.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @since 2.0 - */ -class IndexSchema extends Object -{ - /** - * @var string name of this index. - */ - public $name; - /** - * @var string type of the index. - */ - public $type; - /** - * @var boolean whether this index is a runtime index. - */ - public $isRuntime; - /** - * @var string primary key of this index. - */ - public $primaryKey; - /** - * @var ColumnSchema[] column metadata of this index. Each array element is a [[ColumnSchema]] object, indexed by column names. - */ - public $columns = []; - - - /** - * Gets the named column metadata. - * This is a convenient method for retrieving a named column even if it does not exist. - * @param string $name column name - * @return ColumnSchema metadata of the named column. Null if the named column does not exist. - */ - public function getColumn($name) - { - return isset($this->columns[$name]) ? $this->columns[$name] : null; - } - - /** - * Returns the names of all columns in this table. - * @return array list of column names - */ - public function getColumnNames() - { - return array_keys($this->columns); - } -} diff --git a/extensions/sphinx/LICENSE.md b/extensions/sphinx/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/sphinx/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php deleted file mode 100644 index 36fa86de48..0000000000 --- a/extensions/sphinx/QueryBuilder.php +++ /dev/null @@ -1,1130 +0,0 @@ - - * @since 2.0 - */ -class QueryBuilder extends Object -{ - /** - * The prefix for automatically generated query binding parameters. - */ - const PARAM_PREFIX = ':qp'; - - /** - * @var Connection the Sphinx connection. - */ - public $db; - /** - * @var string the separator between different fragments of a SQL statement. - * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. - */ - public $separator = " "; - - /** - * @var array map of query condition to builder methods. - * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. - */ - protected $conditionBuilders = [ - 'AND' => 'buildAndCondition', - 'OR' => 'buildAndCondition', - 'BETWEEN' => 'buildBetweenCondition', - 'NOT BETWEEN' => 'buildBetweenCondition', - 'IN' => 'buildInCondition', - 'NOT IN' => 'buildInCondition', - 'LIKE' => 'buildLikeCondition', - 'NOT LIKE' => 'buildLikeCondition', - 'OR LIKE' => 'buildLikeCondition', - 'OR NOT LIKE' => 'buildLikeCondition', - 'NOT' => 'buildNotCondition', - ]; - - - /** - * Constructor. - * @param Connection $connection the Sphinx connection. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($connection, $config = []) - { - $this->db = $connection; - parent::__construct($config); - } - - /** - * Generates a SELECT SQL statement from a [[Query]] object. - * @param Query $query the [[Query]] object from which the SQL statement will be generated - * @param array $params the parameters to be bound to the generated SQL statement. These parameters will - * be included in the result with the additional parameters generated during the query building process. - * @throws NotSupportedException if query contains 'join' option. - * @return array the generated SQL statement (the first array element) and the corresponding - * parameters to be bound to the SQL statement (the second array element). The parameters returned - * include those provided in `$params`. - */ - public function build($query, $params = []) - { - $query = $query->prepare($this); - - if (!empty($query->join)) { - throw new NotSupportedException('Build of "' . get_class($query) . '::join" is not supported.'); - } - - $params = empty($params) ? $query->params : array_merge($params, $query->params); - - $from = $query->from; - if ($from === null && $query instanceof ActiveQuery) { - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - $from = [$modelClass::indexName()]; - } - - $clauses = [ - $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), - $this->buildFrom($from, $params), - $this->buildWhere($query->from, $query->where, $params, $query->match), - $this->buildGroupBy($query->groupBy), - $this->buildWithin($query->within), - $this->buildHaving($query->from, $query->having, $params), - $this->buildOrderBy($query->orderBy), - $this->buildLimit($query->limit, $query->offset), - $this->buildOption($query->options, $params), - ]; - - return [implode($this->separator, array_filter($clauses)), $params]; - } - - /** - * Creates an INSERT SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->insert('idx_user', [ - * 'name' => 'Sam', - * 'age' => 30, - * 'id' => 10, - * ], $params); - * ~~~ - * - * The method will properly escape the index and column names. - * - * @param string $index the index that new rows will be inserted into. - * @param array $columns the column data (name => value) to be inserted into the index. - * @param array $params the binding parameters that will be generated by this method. - * They should be bound to the Sphinx command later. - * @return string the INSERT SQL - */ - public function insert($index, $columns, &$params) - { - return $this->generateInsertReplace('INSERT', $index, $columns, $params); - } - - /** - * Creates an REPLACE SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->replace('idx_user', [ - * 'name' => 'Sam', - * 'age' => 30, - * 'id' => 10, - * ], $params); - * ~~~ - * - * The method will properly escape the index and column names. - * - * @param string $index the index that new rows will be replaced. - * @param array $columns the column data (name => value) to be replaced in the index. - * @param array $params the binding parameters that will be generated by this method. - * They should be bound to the Sphinx command later. - * @return string the INSERT SQL - */ - public function replace($index, $columns, &$params) - { - return $this->generateInsertReplace('REPLACE', $index, $columns, $params); - } - - /** - * Generates INSERT/REPLACE SQL statement. - * @param string $statement statement ot be generated. - * @param string $index the affected index name. - * @param array $columns the column data (name => value). - * @param array $params the binding parameters that will be generated by this method. - * @return string generated SQL - */ - protected function generateInsertReplace($statement, $index, $columns, &$params) - { - if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { - $indexSchemas = [$indexSchema]; - } else { - $indexSchemas = []; - } - $names = []; - $placeholders = []; - foreach ($columns as $name => $value) { - $names[] = $this->db->quoteColumnName($name); - $placeholders[] = $this->composeColumnValue($indexSchemas, $name, $value, $params); - } - - return $statement . ' INTO ' . $this->db->quoteIndexName($index) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; - } - - /** - * Generates a batch INSERT SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->batchInsert('idx_user', ['id', 'name', 'age'], [ - * [1, 'Tom', 30], - * [2, 'Jane', 20], - * [3, 'Linda', 25], - * ], $params); - * ~~~ - * - * Note that the values in each row must match the corresponding column names. - * - * @param string $index the index that new rows will be inserted into. - * @param array $columns the column names - * @param array $rows the rows to be batch inserted into the index - * @param array $params the binding parameters that will be generated by this method. - * They should be bound to the Sphinx command later. - * @return string the batch INSERT SQL statement - */ - public function batchInsert($index, $columns, $rows, &$params) - { - return $this->generateBatchInsertReplace('INSERT', $index, $columns, $rows, $params); - } - - /** - * Generates a batch REPLACE SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->batchReplace('idx_user', ['id', 'name', 'age'], [ - * [1, 'Tom', 30], - * [2, 'Jane', 20], - * [3, 'Linda', 25], - * ], $params); - * ~~~ - * - * Note that the values in each row must match the corresponding column names. - * - * @param string $index the index that new rows will be replaced. - * @param array $columns the column names - * @param array $rows the rows to be batch replaced in the index - * @param array $params the binding parameters that will be generated by this method. - * They should be bound to the Sphinx command later. - * @return string the batch INSERT SQL statement - */ - public function batchReplace($index, $columns, $rows, &$params) - { - return $this->generateBatchInsertReplace('REPLACE', $index, $columns, $rows, $params); - } - - /** - * Generates a batch INSERT/REPLACE SQL statement. - * @param string $statement statement ot be generated. - * @param string $index the affected index name. - * @param array $columns the column data (name => value). - * @param array $rows the rows to be batch inserted into the index - * @param array $params the binding parameters that will be generated by this method. - * @return string generated SQL - */ - protected function generateBatchInsertReplace($statement, $index, $columns, $rows, &$params) - { - if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { - $indexSchemas = [$indexSchema]; - } else { - $indexSchemas = []; - } - - foreach ($columns as $i => $name) { - $columns[$i] = $this->db->quoteColumnName($name); - } - - $values = []; - foreach ($rows as $row) { - $vs = []; - foreach ($row as $i => $value) { - $vs[] = $this->composeColumnValue($indexSchemas, $columns[$i], $value, $params); - } - $values[] = '(' . implode(', ', $vs) . ')'; - } - - return $statement . ' INTO ' . $this->db->quoteIndexName($index) - . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); - } - - /** - * Creates an UPDATE SQL statement. - * For example, - * - * ~~~ - * $params = []; - * $sql = $queryBuilder->update('idx_user', ['status' => 1], 'age > 30', $params); - * ~~~ - * - * The method will properly escape the index and column names. - * - * @param string $index the index to be updated. - * @param array $columns the column data (name => value) to be updated. - * @param array|string $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the binding parameters that will be modified by this method - * so that they can be bound to the Sphinx command later. - * @param array $options list of options in format: optionName => optionValue - * @return string the UPDATE SQL - */ - public function update($index, $columns, $condition, &$params, $options) - { - if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { - $indexSchemas = [$indexSchema]; - } else { - $indexSchemas = []; - } - - $lines = []; - foreach ($columns as $name => $value) { - $lines[] = $this->db->quoteColumnName($name) . '=' . $this->composeColumnValue($indexSchemas, $name, $value, $params); - } - - $sql = 'UPDATE ' . $this->db->quoteIndexName($index) . ' SET ' . implode(', ', $lines); - $where = $this->buildWhere([$index], $condition, $params); - if ($where !== '') { - $sql = $sql . ' ' . $where; - } - $option = $this->buildOption($options, $params); - if ($option !== '') { - $sql = $sql . ' ' . $option; - } - - return $sql; - } - - /** - * Creates a DELETE SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->delete('idx_user', 'status = 0'); - * ~~~ - * - * The method will properly escape the index and column names. - * - * @param string $index the index where the data will be deleted from. - * @param array|string $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the binding parameters that will be modified by this method - * so that they can be bound to the Sphinx command later. - * @return string the DELETE SQL - */ - public function delete($index, $condition, &$params) - { - $sql = 'DELETE FROM ' . $this->db->quoteIndexName($index); - $where = $this->buildWhere([$index], $condition, $params); - - return $where === '' ? $sql : $sql . ' ' . $where; - } - - /** - * Builds a SQL statement for truncating an index. - * @param string $index the index to be truncated. The name will be properly quoted by the method. - * @return string the SQL statement for truncating an index. - */ - public function truncateIndex($index) - { - return 'TRUNCATE RTINDEX ' . $this->db->quoteIndexName($index); - } - - /** - * Builds a SQL statement for call snippet from provided data and query, using specified index settings. - * @param string $index name of the index, from which to take the text processing settings. - * @param string|array $source is the source data to extract a snippet from. - * It could be either a single string or array of strings. - * @param string $match the full-text query to build snippets for. - * @param array $options list of options in format: optionName => optionValue - * @param array $params the binding parameters that will be modified by this method - * so that they can be bound to the Sphinx command later. - * @return string the SQL statement for call snippets. - */ - public function callSnippets($index, $source, $match, $options, &$params) - { - if (is_array($source)) { - $dataSqlParts = []; - foreach ($source as $sourceRow) { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $sourceRow; - $dataSqlParts[] = $phName; - } - $dataSql = '(' . implode(',', $dataSqlParts) . ')'; - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $source; - $dataSql = $phName; - } - $indexParamName = self::PARAM_PREFIX . count($params); - $params[$indexParamName] = $index; - $matchParamName = self::PARAM_PREFIX . count($params); - $params[$matchParamName] = $match; - if (!empty($options)) { - $optionParts = []; - foreach ($options as $name => $value) { - if ($value instanceof Expression) { - $actualValue = $value->expression; - } else { - $actualValue = self::PARAM_PREFIX . count($params); - $params[$actualValue] = $value; - } - $optionParts[] = $actualValue . ' AS ' . $name; - } - $optionSql = ', ' . implode(', ', $optionParts); - } else { - $optionSql = ''; - } - - return 'CALL SNIPPETS(' . $dataSql. ', ' . $indexParamName . ', ' . $matchParamName . $optionSql. ')'; - } - - /** - * Builds a SQL statement for returning tokenized and normalized forms of the keywords, and, - * optionally, keyword statistics. - * @param string $index the name of the index from which to take the text processing settings - * @param string $text the text to break down to keywords. - * @param boolean $fetchStatistic whether to return document and hit occurrence statistics - * @param array $params the binding parameters that will be modified by this method - * so that they can be bound to the Sphinx command later. - * @return string the SQL statement for call keywords. - */ - public function callKeywords($index, $text, $fetchStatistic, &$params) - { - $indexParamName = self::PARAM_PREFIX . count($params); - $params[$indexParamName] = $index; - $textParamName = self::PARAM_PREFIX . count($params); - $params[$textParamName] = $text; - - return 'CALL KEYWORDS(' . $textParamName . ', ' . $indexParamName . ($fetchStatistic ? ', 1' : '') . ')'; - } - - /** - * @param array $columns - * @param array $params the binding parameters to be populated - * @param boolean $distinct - * @param string $selectOption - * @return string the SELECT clause built from [[query]]. - */ - public function buildSelect($columns, &$params, $distinct = false, $selectOption = null) - { - $select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; - if ($selectOption !== null) { - $select .= ' ' . $selectOption; - } - - if (empty($columns)) { - return $select . ' *'; - } - - foreach ($columns as $i => $column) { - if ($column instanceof Expression) { - $columns[$i] = $column->expression; - $params = array_merge($params, $column->params); - } elseif ($column instanceof Query) { - list($sql, $params) = $this->build($column, $params); - $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i); - } elseif (is_string($i)) { - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - $columns[$i] = "$column AS " . $this->db->quoteColumnName($i); - } elseif (strpos($column, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) { - $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]); - } else { - $columns[$i] = $this->db->quoteColumnName($column); - } - } - } - - return $select . ' ' . implode(', ', $columns); - } - - /** - * @param array $indexes - * @param array $params the binding parameters to be populated - * @return string the FROM clause built from [[query]]. - */ - public function buildFrom($indexes, &$params) - { - if (empty($indexes)) { - return ''; - } - - foreach ($indexes as $i => $index) { - if ($index instanceof Query) { - list($sql, $params) = $this->build($index, $params); - $indexes[$i] = "($sql) " . $this->db->quoteIndexName($i); - } elseif (is_string($i)) { - if (strpos($index, '(') === false) { - $index = $this->db->quoteIndexName($index); - } - $indexes[$i] = "$index " . $this->db->quoteIndexName($i); - } elseif (strpos($index, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $index, $matches)) { // with alias - $indexes[$i] = $this->db->quoteIndexName($matches[1]) . ' ' . $this->db->quoteIndexName($matches[2]); - } else { - $indexes[$i] = $this->db->quoteIndexName($index); - } - } - } - - if (is_array($indexes)) { - $indexes = implode(', ', $indexes); - } - - return 'FROM ' . $indexes; - } - - /** - * @param string[] $indexes list of index names, which affected by query - * @param string|array $condition - * @param array $params the binding parameters to be populated - * @param string|Expression|null $match - * @return string the WHERE clause built from [[query]]. - */ - public function buildWhere($indexes, $condition, &$params, $match = null) - { - if ($match !== null) { - if ($match instanceof Expression) { - $matchWhere = 'MATCH(' . $match->expression . ')'; - $params = array_merge($params, $match->params); - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $this->db->escapeMatchValue($match); - $matchWhere = 'MATCH(' . $phName . ')'; - } - - if ($condition === null) { - $condition = $matchWhere; - } else { - $condition = ['and', $matchWhere, $condition]; - } - } - - if (empty($condition)) { - return ''; - } - $indexSchemas = $this->getIndexSchemas($indexes); - $where = $this->buildCondition($indexSchemas, $condition, $params); - - return $where === '' ? '' : 'WHERE ' . $where; - } - - /** - * @param array $columns - * @return string the GROUP BY clause - */ - public function buildGroupBy($columns) - { - return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns); - } - - /** - * @param string[] $indexes list of index names, which affected by query - * @param string|array $condition - * @param array $params the binding parameters to be populated - * @return string the HAVING clause built from [[Query::$having]]. - */ - public function buildHaving($indexes, $condition, &$params) - { - if (empty($condition)) { - return ''; - } - - $indexSchemas = $this->getIndexSchemas($indexes); - $having = $this->buildCondition($indexSchemas, $condition, $params); - - return $having === '' ? '' : 'HAVING ' . $having; - } - - /** - * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL. - * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) - * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. - * @param integer $limit the limit number. See [[Query::limit]] for more details. - * @param integer $offset the offset number. See [[Query::offset]] for more details. - * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) - */ - public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset) - { - $orderBy = $this->buildOrderBy($orderBy); - if ($orderBy !== '') { - $sql .= $this->separator . $orderBy; - } - $limit = $this->buildLimit($limit, $offset); - if ($limit !== '') { - $sql .= $this->separator . $limit; - } - return $sql; - } - - /** - * @param array $columns - * @return string the ORDER BY clause built from [[query]]. - */ - public function buildOrderBy($columns) - { - if (empty($columns)) { - return ''; - } - $orders = []; - foreach ($columns as $name => $direction) { - if ($direction instanceof Expression) { - $orders[] = $direction->expression; - } else { - $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : 'ASC'); - } - } - - return 'ORDER BY ' . implode(', ', $orders); - } - - /** - * @param integer $limit - * @param integer $offset - * @return string the LIMIT and OFFSET clauses built from [[query]]. - */ - public function buildLimit($limit, $offset) - { - $sql = ''; - if (is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0') { - $sql = 'LIMIT ' . $offset; - } - if (is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0) { - $sql = $sql === '' ? "LIMIT $limit" : "$sql,$limit"; - } elseif ($sql !== '') { - $sql .= ',1000'; // this is the default limit by sphinx - } - - return $sql; - } - - /** - * Processes columns and properly quote them if necessary. - * It will join all columns into a string with comma as separators. - * @param string|array $columns the columns to be processed - * @return string the processing result - */ - public function buildColumns($columns) - { - if (!is_array($columns)) { - if (strpos($columns, '(') !== false) { - return $columns; - } else { - $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); - } - } - foreach ($columns as $i => $column) { - if ($column instanceof Expression) { - $columns[$i] = $column->expression; - } elseif (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } - } - - return is_array($columns) ? implode(', ', $columns) : $columns; - } - - /** - * Parses the condition specification and generates the corresponding SQL expression. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string|array $condition the condition specification. Please refer to [[Query::where()]] - * on how to specify a condition. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws \yii\db\Exception if the condition is in bad format - */ - public function buildCondition($indexes, $condition, &$params) - { - if (!is_array($condition)) { - return (string) $condition; - } elseif (empty($condition)) { - return ''; - } - if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... - $operator = strtoupper($condition[0]); - if (isset($this->conditionBuilders[$operator])) { - $method = $this->conditionBuilders[$operator]; - } else { - $method = 'buildSimpleCondition'; - } - array_shift($condition); - return $this->$method($indexes, $operator, $condition, $params); - } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... - return $this->buildHashCondition($indexes, $condition, $params); - } - } - - /** - * Creates a condition based on column-value pairs. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param array $condition the condition specification. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - */ - public function buildHashCondition($indexes, $condition, &$params) - { - $parts = []; - foreach ($condition as $column => $value) { - if (is_array($value) || $value instanceof Query) { - // IN condition - $parts[] = $this->buildInCondition($indexes, 'IN', [$column, $value], $params); - } else { - if (strpos($column, '(') === false) { - $quotedColumn = $this->db->quoteColumnName($column); - } else { - $quotedColumn = $column; - } - if ($value === null) { - $parts[] = "$quotedColumn IS NULL"; - } else { - $parts[] = $quotedColumn . '=' . $this->composeColumnValue($indexes, $column, $value, $params); - } - } - } - - return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; - } - - /** - * Connects two or more SQL expressions with the `AND` or `OR` operator. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use for connecting the given operands - * @param array $operands the SQL expressions to connect. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - */ - public function buildAndCondition($indexes, $operator, $operands, &$params) - { - $parts = []; - foreach ($operands as $operand) { - if (is_array($operand)) { - $operand = $this->buildCondition($indexes, $operand, $params); - } - if ($operand !== '') { - $parts[] = $operand; - } - } - if (!empty($parts)) { - return '(' . implode(") $operator (", $parts) . ')'; - } else { - return ''; - } - } - - /** - * Inverts an SQL expressions with `NOT` operator. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use for connecting the given operands - * @param array $operands the SQL expressions to connect. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws InvalidParamException if wrong number of operands have been given. - */ - public function buildNotCondition($indexes, $operator, $operands, &$params) - { - if (count($operands) != 1) { - throw new InvalidParamException("Operator '$operator' requires exactly one operand."); - } - - $operand = reset($operands); - if (is_array($operand)) { - $operand = $this->buildCondition($indexes, $operand, $params); - } - if ($operand === '') { - return ''; - } - - return "$operator ($operand)"; - } - - /** - * Creates an SQL expressions with the `BETWEEN` operator. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) - * @param array $operands the first operand is the column name. The second and third operands - * describe the interval that column value should be in. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws Exception if wrong number of operands have been given. - */ - public function buildBetweenCondition($indexes, $operator, $operands, &$params) - { - if (!isset($operands[0], $operands[1], $operands[2])) { - throw new Exception("Operator '$operator' requires three operands."); - } - - list($column, $value1, $value2) = $operands; - - if (strpos($column, '(') === false) { - $quotedColumn = $this->db->quoteColumnName($column); - } else { - $quotedColumn = $column; - } - $phName1 = $this->composeColumnValue($indexes, $column, $value1, $params); - $phName2 = $this->composeColumnValue($indexes, $column, $value2, $params); - - return "$quotedColumn $operator $phName1 AND $phName2"; - } - - /** - * Creates an SQL expressions with the `IN` operator. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use (e.g. `IN` or `NOT IN`) - * @param array $operands the first operand is the column name. If it is an array - * a composite IN condition will be generated. - * The second operand is an array of values that column value should be among. - * If it is an empty array the generated expression will be a `false` value if - * operator is `IN` and empty if operator is `NOT IN`. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws Exception if wrong number of operands have been given. - */ - public function buildInCondition($indexes, $operator, $operands, &$params) - { - if (!isset($operands[0], $operands[1])) { - throw new Exception("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - if ($values === [] || $column === []) { - return $operator === 'IN' ? '0=1' : ''; - } - - if ($values instanceof Query) { - // sub-query - list($sql, $params) = $this->build($values, $params); - $column = (array) $column; - if (is_array($column)) { - foreach ($column as $i => $col) { - if (strpos($col, '(') === false) { - $column[$i] = $this->db->quoteColumnName($col); - } - } - return '(' . implode(', ', $column) . ") $operator ($sql)"; - } else { - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - return "$column $operator ($sql)"; - } - } - - $values = (array) $values; - - if (count($column) > 1) { - return $this->buildCompositeInCondition($indexes, $operator, $column, $values, $params); - } - if (is_array($column)) { - $column = reset($column); - } - foreach ($values as $i => $value) { - if (is_array($value)) { - $value = isset($value[$column]) ? $value[$column] : null; - } - $values[$i] = $this->composeColumnValue($indexes, $column, $value, $params); - } - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - - if (count($values) > 1) { - return "$column $operator (" . implode(', ', $values) . ')'; - } else { - $operator = $operator === 'IN' ? '=' : '<>'; - - return $column . $operator . reset($values); - } - } - - /** - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use (e.g. `IN` or `NOT IN`) - * @param array $columns - * @param array $values - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - */ - protected function buildCompositeInCondition($indexes, $operator, $columns, $values, &$params) - { - $vss = []; - foreach ($values as $value) { - $vs = []; - foreach ($columns as $column) { - if (isset($value[$column])) { - $vs[] = $this->composeColumnValue($indexes, $column, $value[$column], $params); - } else { - $vs[] = 'NULL'; - } - } - $vss[] = '(' . implode(', ', $vs) . ')'; - } - foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } - } - - return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; - } - - /** - * Creates an SQL expressions with the `LIKE` operator. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) - * @param array $operands an array of two or three operands - * - * - The first operand is the column name. - * - The second operand is a single value or an array of values that column value - * should be compared with. If it is an empty array the generated expression will - * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator - * is `NOT LIKE` or `OR NOT LIKE`. - * - An optional third operand can also be provided to specify how to escape special characters - * in the value(s). The operand should be an array of mappings from the special characters to their - * escaped counterparts. If this operand is not provided, a default escape mapping will be used. - * You may use `false` or an empty array to indicate the values are already escaped and no escape - * should be applied. Note that when using an escape mapping (or the third operand is not provided), - * the values will be automatically enclosed within a pair of percentage characters. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws InvalidParamException if wrong number of operands have been given. - */ - public function buildLikeCondition($indexes, $operator, $operands, &$params) - { - if (!isset($operands[0], $operands[1])) { - throw new InvalidParamException("Operator '$operator' requires two operands."); - } - - $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']; - unset($operands[2]); - - list($column, $values) = $operands; - - if (!is_array($values)) { - $values = [$values]; - } - - if (empty($values)) { - return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : ''; - } - - if ($operator === 'LIKE' || $operator === 'NOT LIKE') { - $andor = ' AND '; - } else { - $andor = ' OR '; - $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; - } - - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - - $parts = []; - foreach ($values as $value) { - if ($value instanceof Expression) { - foreach ($value->params as $n => $v) { - $params[$n] = $v; - } - $phName = $value->expression; - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'); - } - $parts[] = "$column $operator $phName"; - } - - return implode($andor, $parts); - } - - /** - * Creates an SQL expressions like `"column" operator value`. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc. - * @param array $operands contains two column names. - * @param array $params the binding parameters to be populated - * @return string the generated SQL expression - * @throws InvalidParamException if count($operands) is not 2 - */ - public function buildSimpleCondition($indexes, $operator, $operands, &$params) - { - if (count($operands) !== 2) { - throw new InvalidParamException("Operator '$operator' requires two operands."); - } - - list($column, $value) = $operands; - - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - - if ($value === null) { - return "$column $operator NULL"; - } elseif ($value instanceof Expression) { - foreach ($value->params as $n => $v) { - $params[$n] = $v; - } - return "$column $operator {$value->expression}"; - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value; - return "$column $operator $phName"; - } - } - - /** - * @param array $columns - * @return string the ORDER BY clause built from [[query]]. - */ - public function buildWithin($columns) - { - if (empty($columns)) { - return ''; - } - $orders = []; - foreach ($columns as $name => $direction) { - if ($direction instanceof Expression) { - $orders[] = $direction->expression; - } else { - $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : ''); - } - } - - return 'WITHIN GROUP ORDER BY ' . implode(', ', $orders); - } - - /** - * @param array $options query options in format: optionName => optionValue - * @param array $params the binding parameters to be populated - * @return string the OPTION clause build from [[query]] - */ - public function buildOption($options, &$params) - { - if (empty($options)) { - return ''; - } - $optionLines = []; - foreach ($options as $name => $value) { - if ($value instanceof Expression) { - $actualValue = $value->expression; - } else { - if (is_array($value)) { - $actualValueParts = []; - foreach ($value as $key => $valuePart) { - if (is_numeric($key)) { - $actualValuePart = ''; - } else { - $actualValuePart = $key . ' = '; - } - if ($valuePart instanceof Expression) { - $actualValuePart .= $valuePart->expression; - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $valuePart; - $actualValuePart .= $phName; - } - $actualValueParts[] = $actualValuePart; - } - $actualValue = '(' . implode(', ', $actualValueParts) . ')'; - } else { - $actualValue = self::PARAM_PREFIX . count($params); - $params[$actualValue] = $value; - } - } - $optionLines[] = $name . ' = ' . $actualValue; - } - - return 'OPTION ' . implode(', ', $optionLines); - } - - /** - * Composes column value for SQL, taking in account the column type. - * @param IndexSchema[] $indexes list of indexes, which affected by query - * @param string $columnName name of the column - * @param mixed $value raw column value - * @param array $params the binding parameters to be populated - * @return string SQL expression, which represents column value - */ - protected function composeColumnValue($indexes, $columnName, $value, &$params) - { - if ($value === null) { - return 'NULL'; - } elseif ($value instanceof Expression) { - $params = array_merge($params, $value->params); - - return $value->expression; - } - foreach ($indexes as $index) { - $columnSchema = $index->getColumn($columnName); - if ($columnSchema !== null) { - break; - } - } - if (is_array($value)) { - // MVA : - $lineParts = []; - foreach ($value as $subValue) { - if ($subValue instanceof Expression) { - $params = array_merge($params, $subValue->params); - $lineParts[] = $subValue->expression; - } else { - $phName = self::PARAM_PREFIX . count($params); - $lineParts[] = $phName; - $params[$phName] = (isset($columnSchema)) ? $columnSchema->dbTypecast($subValue) : $subValue; - } - } - - return '(' . implode(',', $lineParts) . ')'; - } else { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = (isset($columnSchema)) ? $columnSchema->dbTypecast($value) : $value; - - return $phName; - } - } - - /** - * @param array $indexes index names. - * @return IndexSchema[] index schemas. - */ - private function getIndexSchemas($indexes) - { - $indexSchemas = []; - if (!empty($indexes)) { - foreach ($indexes as $indexName) { - $index = $this->db->getIndexSchema($indexName); - if ($index !== null) { - $indexSchemas[] = $index; - } - } - } - return $indexSchemas; - } -} diff --git a/extensions/sphinx/Schema.php b/extensions/sphinx/Schema.php deleted file mode 100644 index ea340da379..0000000000 --- a/extensions/sphinx/Schema.php +++ /dev/null @@ -1,541 +0,0 @@ - index type. This - * property is read-only. - * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class Schema extends Object -{ - /** - * The followings are the supported abstract column data types. - */ - const TYPE_PK = 'pk'; - const TYPE_STRING = 'string'; - const TYPE_INTEGER = 'integer'; - const TYPE_BIGINT = 'bigint'; - const TYPE_FLOAT = 'float'; - const TYPE_TIMESTAMP = 'timestamp'; - const TYPE_BOOLEAN = 'boolean'; - - /** - * @var Connection the Sphinx connection - */ - public $db; - - /** - * @var array list of ALL index names in the Sphinx - */ - private $_indexNames; - /** - * @var array list of ALL index types in the Sphinx (index name => index type) - */ - private $_indexTypes; - /** - * @var array list of loaded index metadata (index name => IndexSchema) - */ - private $_indexes = []; - /** - * @var QueryBuilder the query builder for this Sphinx connection - */ - private $_builder; - - - /** - * @var array mapping from physical column types (keys) to abstract column types (values) - */ - public $typeMap = [ - 'field' => self::TYPE_STRING, - 'string' => self::TYPE_STRING, - 'ordinal' => self::TYPE_STRING, - 'integer' => self::TYPE_INTEGER, - 'int' => self::TYPE_INTEGER, - 'uint' => self::TYPE_INTEGER, - 'bigint' => self::TYPE_BIGINT, - 'timestamp' => self::TYPE_TIMESTAMP, - 'bool' => self::TYPE_BOOLEAN, - 'float' => self::TYPE_FLOAT, - 'mva' => self::TYPE_INTEGER, - ]; - - /** - * Loads the metadata for the specified index. - * @param string $name index name - * @return IndexSchema driver dependent index metadata. Null if the index does not exist. - */ - protected function loadIndexSchema($name) - { - $index = new IndexSchema; - $this->resolveIndexNames($index, $name); - $this->resolveIndexType($index); - - if ($this->findColumns($index)) { - return $index; - } else { - return null; - } - } - - /** - * Resolves the index name. - * @param IndexSchema $index the index metadata object - * @param string $name the index name - */ - protected function resolveIndexNames($index, $name) - { - $index->name = str_replace('`', '', $name); - } - - /** - * Resolves the index name. - * @param IndexSchema $index the index metadata object - */ - protected function resolveIndexType($index) - { - $indexTypes = $this->getIndexTypes(); - $index->type = array_key_exists($index->name, $indexTypes) ? $indexTypes[$index->name] : 'unknown'; - $index->isRuntime = ($index->type == 'rt'); - } - - /** - * Obtains the metadata for the named index. - * @param string $name index name. The index name may contain schema name if any. Do not quote the index name. - * @param boolean $refresh whether to reload the index schema even if it is found in the cache. - * @return IndexSchema index metadata. Null if the named index does not exist. - */ - public function getIndexSchema($name, $refresh = false) - { - if (isset($this->_indexes[$name]) && !$refresh) { - return $this->_indexes[$name]; - } - - $db = $this->db; - $realName = $this->getRawIndexName($name); - - if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) { - /* @var $cache Cache */ - $cache = is_string($db->schemaCache) ? Yii::$app->get($db->schemaCache, false) : $db->schemaCache; - if ($cache instanceof Cache) { - $key = $this->getCacheKey($name); - if ($refresh || ($index = $cache->get($key)) === false) { - $index = $this->loadIndexSchema($realName); - if ($index !== null) { - $cache->set($key, $index, $db->schemaCacheDuration, new TagDependency([ - 'tags' => $this->getCacheTag(), - ])); - } - } - - return $this->_indexes[$name] = $index; - } - } - - return $this->_indexes[$name] = $this->loadIndexSchema($realName); - } - - /** - * Returns the cache key for the specified index name. - * @param string $name the index name - * @return mixed the cache key - */ - protected function getCacheKey($name) - { - return [ - __CLASS__, - $this->db->dsn, - $this->db->username, - $name, - ]; - } - - /** - * Returns the cache tag name. - * This allows [[refresh()]] to invalidate all cached index schemas. - * @return string the cache tag name - */ - protected function getCacheTag() - { - return md5(serialize([ - __CLASS__, - $this->db->dsn, - $this->db->username, - ])); - } - - /** - * Returns the metadata for all indexes in the database. - * @param boolean $refresh whether to fetch the latest available index schemas. If this is false, - * cached data may be returned if available. - * @return IndexSchema[] the metadata for all indexes in the Sphinx. - * Each array element is an instance of [[IndexSchema]] or its child class. - */ - public function getIndexSchemas($refresh = false) - { - $indexes = []; - foreach ($this->getIndexNames($refresh) as $name) { - if (($index = $this->getIndexSchema($name, $refresh)) !== null) { - $indexes[] = $index; - } - } - - return $indexes; - } - - /** - * Returns all index names in the Sphinx. - * @param boolean $refresh whether to fetch the latest available index names. If this is false, - * index names fetched previously (if available) will be returned. - * @return string[] all index names in the Sphinx. - */ - public function getIndexNames($refresh = false) - { - if (!isset($this->_indexNames) || $refresh) { - $this->initIndexesInfo(); - } - - return $this->_indexNames; - } - - /** - * Returns all index types in the Sphinx. - * @param boolean $refresh whether to fetch the latest available index types. If this is false, - * index types fetched previously (if available) will be returned. - * @return array all index types in the Sphinx in format: index name => index type. - */ - public function getIndexTypes($refresh = false) - { - if (!isset($this->_indexTypes) || $refresh) { - $this->initIndexesInfo(); - } - - return $this->_indexTypes; - } - - /** - * Initializes information about name and type of all index in the Sphinx. - */ - protected function initIndexesInfo() - { - $this->_indexNames = []; - $this->_indexTypes = []; - $indexes = $this->findIndexes(); - foreach ($indexes as $index) { - $indexName = $index['Index']; - $this->_indexNames[] = $indexName; - $this->_indexTypes[$indexName] = $index['Type']; - } - } - - /** - * Returns all index names in the Sphinx. - * @return array all index names in the Sphinx. - */ - protected function findIndexes() - { - $sql = 'SHOW TABLES'; - - return $this->db->createCommand($sql)->queryAll(); - } - - /** - * @return QueryBuilder the query builder for this connection. - */ - public function getQueryBuilder() - { - if ($this->_builder === null) { - $this->_builder = $this->createQueryBuilder(); - } - - return $this->_builder; - } - - /** - * Determines the PDO type for the given PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - public function getPdoType($data) - { - static $typeMap = [ - // php type => PDO type - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ]; - $type = gettype($data); - - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** - * Refreshes the schema. - * This method cleans up all cached index schemas so that they can be re-created later - * to reflect the Sphinx schema change. - */ - public function refresh() - { - /* @var $cache Cache */ - $cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache; - if ($this->db->enableSchemaCache && $cache instanceof Cache) { - TagDependency::invalidate($cache, $this->getCacheTag()); - } - $this->_indexNames = []; - $this->_indexes = []; - } - - /** - * Creates a query builder for the Sphinx. - * @return QueryBuilder query builder instance - */ - public function createQueryBuilder() - { - return new QueryBuilder($this->db); - } - - /** - * Quotes a string value for use in a query. - * Note that if the parameter is not a string, it will be returned without change. - * @param string $str string to be quoted - * @return string the properly quoted string - * @see http://www.php.net/manual/en/function.PDO-quote.php - */ - public function quoteValue($str) - { - if (is_string($str)) { - return $this->db->getSlavePdo()->quote($str); - } else { - return $str; - } - } - - /** - * Quotes a index name for use in a query. - * If the index name contains schema prefix, the prefix will also be properly quoted. - * If the index name is already quoted or contains '(' or '{{', - * then this method will do nothing. - * @param string $name index name - * @return string the properly quoted index name - * @see quoteSimpleTableName - */ - public function quoteIndexName($name) - { - if (strpos($name, '(') !== false || strpos($name, '{{') !== false) { - return $name; - } - - return $this->quoteSimpleIndexName($name); - } - - /** - * Quotes a column name for use in a query. - * If the column name contains prefix, the prefix will also be properly quoted. - * If the column name is already quoted or contains '(', '[[' or '{{', - * then this method will do nothing. - * @param string $name column name - * @return string the properly quoted column name - * @see quoteSimpleColumnName - */ - public function quoteColumnName($name) - { - if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { - return $name; - } - if (($pos = strrpos($name, '.')) !== false) { - $prefix = $this->quoteIndexName(substr($name, 0, $pos)) . '.'; - $name = substr($name, $pos + 1); - } else { - $prefix = ''; - } - - return $prefix . $this->quoteSimpleColumnName($name); - } - - /** - * Quotes a index name for use in a query. - * A simple index name has no schema prefix. - * @param string $name index name - * @return string the properly quoted index name - */ - public function quoteSimpleIndexName($name) - { - return strpos($name, "`") !== false ? $name : "`" . $name . "`"; - } - - /** - * Quotes a column name for use in a query. - * A simple column name has no prefix. - * @param string $name column name - * @return string the properly quoted column name - */ - public function quoteSimpleColumnName($name) - { - return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; - } - - /** - * Returns the actual name of a given index name. - * This method will strip off curly brackets from the given index name - * and replace the percentage character '%' with [[Connection::indexPrefix]]. - * @param string $name the index name to be converted - * @return string the real name of the given index name - */ - public function getRawIndexName($name) - { - if (strpos($name, '{{') !== false) { - $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name); - - return str_replace('%', $this->db->tablePrefix, $name); - } else { - return $name; - } - } - - /** - * Extracts the PHP type from abstract DB type. - * @param ColumnSchema $column the column schema information - * @return string PHP type name - */ - protected function getColumnPhpType($column) - { - static $typeMap = [ // abstract type => php type - 'smallint' => 'integer', - 'integer' => 'integer', - 'bigint' => 'integer', - 'boolean' => 'boolean', - 'float' => 'double', - ]; - if (isset($typeMap[$column->type])) { - if ($column->type === 'bigint') { - return PHP_INT_SIZE == 8 ? 'integer' : 'string'; - } elseif ($column->type === 'integer') { - return PHP_INT_SIZE == 4 ? 'string' : 'integer'; - } else { - return $typeMap[$column->type]; - } - } else { - return 'string'; - } - } - - /** - * Collects the metadata of index columns. - * @param IndexSchema $index the index metadata - * @return boolean whether the index exists in the database - * @throws \Exception if DB query fails - */ - protected function findColumns($index) - { - $sql = 'DESCRIBE ' . $this->quoteSimpleIndexName($index->name); - try { - $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { - $previous = $e->getPrevious(); - if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) { - // index does not exist - // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error - return false; - } - throw $e; - } - - if (empty($columns[0]['Agent'])) { - foreach ($columns as $info) { - $column = $this->loadColumnSchema($info); - $index->columns[$column->name] = $column; - if ($column->isPrimaryKey) { - $index->primaryKey = $column->name; - } - } - } else { - // Distributed index : - $agent = $this->getIndexSchema($columns[0]['Agent']); - $index->columns = $agent->columns; - } - - return true; - } - - /** - * Loads the column information into a [[ColumnSchema]] object. - * @param array $info column information - * @return ColumnSchema the column schema object - */ - protected function loadColumnSchema($info) - { - $column = new ColumnSchema; - - $column->name = $info['Field']; - $column->dbType = $info['Type']; - - $column->isPrimaryKey = ($column->name == 'id'); - - $type = $info['Type']; - if (isset($this->typeMap[$type])) { - $column->type = $this->typeMap[$type]; - } else { - $column->type = self::TYPE_STRING; - } - - $column->isField = ($type == 'field'); - $column->isAttribute = !$column->isField; - - $column->isMva = ($type == 'mva'); - - $column->phpType = $this->getColumnPhpType($column); - - return $column; - } - - /** - * Converts a DB exception to a more concrete one if possible. - * - * @param \Exception $e - * @param string $rawSql SQL that produced exception - * @return Exception - */ - public function convertException(\Exception $e, $rawSql) - { - if ($e instanceof Exception) { - return $e; - } else { - $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - return new Exception($message, $errorInfo, (int) $e->getCode(), $e); - } - } - - /** - * Returns a value indicating whether a SQL statement is for read purpose. - * @param string $sql the SQL statement - * @return boolean whether a SQL statement is for read purpose. - */ - public function isReadQuery($sql) - { - $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i'; - return preg_match($pattern, $sql) > 0; - } -} diff --git a/extensions/sphinx/composer.json b/extensions/sphinx/composer.json deleted file mode 100644 index b561ea9684..0000000000 --- a/extensions/sphinx/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "yiisoft/yii2-sphinx", - "description": "Sphinx full text search engine extension for the Yii framework", - "keywords": ["yii2", "sphinx", "active-record", "search", "fulltext"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Asphinx", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "ext-pdo": "*", - "ext-pdo_mysql": "*" - }, - "autoload": { - "psr-4": { "yii\\sphinx\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/sphinx/gii/model/Generator.php b/extensions/sphinx/gii/model/Generator.php deleted file mode 100644 index 37b8f1298b..0000000000 --- a/extensions/sphinx/gii/model/Generator.php +++ /dev/null @@ -1,390 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $db = 'sphinx'; - public $ns = 'app\models'; - public $indexName; - public $modelClass; - public $baseClass = 'yii\sphinx\ActiveRecord'; - public $useIndexPrefix = false; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Sphinx Model Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator generates an ActiveRecord class for the specified Sphinx index.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - [['db', 'ns', 'indexName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'], - [['ns'], 'filter', 'filter' => function($value) { return trim($value, '\\'); }], - - [['db', 'ns', 'indexName', 'baseClass'], 'required'], - [['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], - [['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], - [['indexName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], - [['db'], 'validateDb'], - [['ns'], 'validateNamespace'], - [['indexName'], 'validateIndexName'], - [['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], - [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], - [['enableI18N'], 'boolean'], - [['useIndexPrefix'], 'boolean'], - [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return array_merge(parent::attributeLabels(), [ - 'ns' => 'Namespace', - 'db' => 'Sphinx Connection ID', - 'indexName' => 'Index Name', - 'modelClass' => 'Model Class', - 'baseClass' => 'Base Class', - ]); - } - - /** - * @inheritdoc - */ - public function hints() - { - return array_merge(parent::hints(), [ - 'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., app\models', - 'db' => 'This is the ID of the Sphinx application component.', - 'indexName' => 'This is the name of the Sphinx index that the new ActiveRecord class is associated with, e.g. post. - The index name may end with asterisk to match multiple table names, e.g. idx_* - will match indexes, which name starts with idx_. In this case, multiple ActiveRecord classes - will be generated, one for each matching index name; and the class names will be generated from - the matching characters. For example, index idx_post will generate Post - class.', - 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain - the namespace part as it is specified in "Namespace". You do not need to specify the class name - if "Index Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', - 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', - 'useIndexPrefix' => 'This indicates whether the index name returned by the generated ActiveRecord class - should consider the tablePrefix setting of the Sphinx connection. For example, if the - index name is idx_post and tablePrefix=idx_, the ActiveRecord class - will return the table name as {{%post}}.', - ]); - } - - /** - * @inheritdoc - */ - public function autoCompleteData() - { - $db = $this->getDbConnection(); - if ($db !== null) { - return [ - 'indexName' => function () use ($db) { - return $db->getSchema()->getIndexNames(); - }, - ]; - } else { - return []; - } - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['model.php']; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return array_merge(parent::stickyAttributes(), ['ns', 'db', 'baseClass']); - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $db = $this->getDbConnection(); - foreach ($this->getIndexNames() as $indexName) { - $className = $this->generateClassName($indexName); - $indexSchema = $db->getIndexSchema($indexName); - $params = [ - 'indexName' => $indexName, - 'className' => $className, - 'indexSchema' => $indexSchema, - 'labels' => $this->generateLabels($indexSchema), - 'rules' => $this->generateRules($indexSchema), - ]; - $files[] = new CodeFile( - Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php', - $this->render('model.php', $params) - ); - } - - return $files; - } - - /** - * Generates the attribute labels for the specified table. - * @param \yii\db\TableSchema $table the table schema - * @return array the generated attribute labels (name => label) - */ - public function generateLabels($table) - { - $labels = []; - foreach ($table->columns as $column) { - if (!strcasecmp($column->name, 'id')) { - $labels[$column->name] = 'ID'; - } else { - $label = Inflector::camel2words($column->name); - if (substr_compare($label, ' id', -3, 3, true) === 0) { - $label = substr($label, 0, -3) . ' ID'; - } - $labels[$column->name] = $label; - } - } - - return $labels; - } - - /** - * Generates validation rules for the specified index. - * @param \yii\sphinx\IndexSchema $index the index schema - * @return array the generated validation rules - */ - public function generateRules($index) - { - $types = []; - foreach ($index->columns as $column) { - if ($column->isMva) { - $types['safe'][] = $column->name; - continue; - } - if ($column->isPrimaryKey) { - $types['required'][] = $column->name; - $types['unique'][] = $column->name; - } - switch ($column->type) { - case Schema::TYPE_PK: - case Schema::TYPE_INTEGER: - case Schema::TYPE_BIGINT: - $types['integer'][] = $column->name; - break; - case Schema::TYPE_BOOLEAN: - $types['boolean'][] = $column->name; - break; - case Schema::TYPE_FLOAT: - $types['number'][] = $column->name; - break; - case Schema::TYPE_TIMESTAMP: - $types['safe'][] = $column->name; - break; - default: // strings - $types['string'][] = $column->name; - } - } - $rules = []; - foreach ($types as $type => $columns) { - $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; - } - - return $rules; - } - - /** - * Validates the [[db]] attribute. - */ - public function validateDb() - { - if (!Yii::$app->has($this->db)) { - $this->addError('db', 'There is no application component named "' . $this->db . '".'); - } elseif (!Yii::$app->get($this->db) instanceof Connection) { - $this->addError('db', 'The "' . $this->db . '" application component must be a Sphinx connection instance.'); - } - } - - /** - * Validates the [[ns]] attribute. - */ - public function validateNamespace() - { - $this->ns = ltrim($this->ns, '\\'); - $path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false); - if ($path === false) { - $this->addError('ns', 'Namespace must be associated with an existing directory.'); - } - } - - /** - * Validates the [[modelClass]] attribute. - */ - public function validateModelClass() - { - if ($this->isReservedKeyword($this->modelClass)) { - $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); - } - if ((empty($this->indexName) || substr_compare($this->indexName, '*', -1, 1)) && $this->modelClass == '') { - $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); - } - } - - /** - * Validates the [[indexName]] attribute. - */ - public function validateIndexName() - { - if (strpos($this->indexName, '*') !== false && substr_compare($this->indexName, '*', -1, 1)) { - $this->addError('indexName', 'Asterisk is only allowed as the last character.'); - - return; - } - $tables = $this->getIndexNames(); - if (empty($tables)) { - $this->addError('indexName', "Table '{$this->indexName}' does not exist."); - } else { - foreach ($tables as $table) { - $class = $this->generateClassName($table); - if ($this->isReservedKeyword($class)) { - $this->addError('indexName', "Table '$table' will generate a class which is a reserved PHP keyword."); - break; - } - } - } - } - - private $_indexNames; - private $_classNames; - - /** - * @return array the index names that match the pattern specified by [[indexName]]. - */ - protected function getIndexNames() - { - if ($this->_indexNames !== null) { - return $this->_indexNames; - } - $db = $this->getDbConnection(); - if ($db === null) { - return []; - } - $indexNames = []; - if (strpos($this->indexName, '*') !== false) { - $indexNames = $db->getSchema()->getIndexNames(); - } elseif (($index = $db->getIndexSchema($this->indexName, true)) !== null) { - $indexNames[] = $this->indexName; - $this->_classNames[$this->indexName] = $this->modelClass; - } - - return $this->_indexNames = $indexNames; - } - - /** - * Generates the table name by considering table prefix. - * If [[useIndexPrefix]] is false, the table name will be returned without change. - * @param string $indexName the table name (which may contain schema prefix) - * @return string the generated table name - */ - public function generateIndexName($indexName) - { - if (!$this->useIndexPrefix) { - return $indexName; - } - - $db = $this->getDbConnection(); - if (preg_match("/^{$db->tablePrefix}(.*?)$/", $indexName, $matches)) { - $indexName = '{{%' . $matches[1] . '}}'; - } elseif (preg_match("/^(.*?){$db->tablePrefix}$/", $indexName, $matches)) { - $indexName = '{{' . $matches[1] . '%}}'; - } - return $indexName; - } - - /** - * Generates a class name from the specified table name. - * @param string $indexName the table name (which may contain schema prefix) - * @return string the generated class name - */ - protected function generateClassName($indexName) - { - if (isset($this->_classNames[$indexName])) { - return $this->_classNames[$indexName]; - } - - if (($pos = strrpos($indexName, '.')) !== false) { - $indexName = substr($indexName, $pos + 1); - } - - $db = $this->getDbConnection(); - $patterns = []; - $patterns[] = "/^{$db->tablePrefix}(.*?)$/"; - $patterns[] = "/^(.*?){$db->tablePrefix}$/"; - if (strpos($this->indexName, '*') !== false) { - $pattern = $this->indexName; - if (($pos = strrpos($pattern, '.')) !== false) { - $pattern = substr($pattern, $pos + 1); - } - $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; - } - $className = $indexName; - foreach ($patterns as $pattern) { - if (preg_match($pattern, $indexName, $matches)) { - $className = $matches[1]; - break; - } - } - - return $this->_classNames[$indexName] = Inflector::id2camel($className, '_'); - } - - /** - * @return Connection the Sphinx connection as specified by [[db]]. - */ - protected function getDbConnection() - { - return Yii::$app->get($this->db, false); - } -} diff --git a/extensions/sphinx/gii/model/default/model.php b/extensions/sphinx/gii/model/default/model.php deleted file mode 100644 index de743e3b65..0000000000 --- a/extensions/sphinx/gii/model/default/model.php +++ /dev/null @@ -1,67 +0,0 @@ - label) */ -/* @var $rules string[] list of validation rules */ - -echo " - -namespace ns ?>; - -use Yii; - -/** - * This is the model class for index "". - * -columns as $column): ?> - * @property isMva ? 'array' : $column->phpType ?> name}\n" ?> - - */ -class extends baseClass, '\\') . "\n" ?> -{ - /** - * @inheritdoc - */ - public static function indexName() - { - return 'generateIndexName($indexName) ?>'; - } -db !== 'sphinx'): ?> - - /** - * @return \yii\sphinx\Connection the database connection used by this AR class. - */ - public static function getDb() - { - return Yii::$app->get('db ?>'); - } - - - /** - * @inheritdoc - */ - public function rules() - { - return []; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - $label): ?> - " . $generator->generateString($label) . ",\n" ?> - - ]; - } -} diff --git a/extensions/sphinx/gii/model/form.php b/extensions/sphinx/gii/model/form.php deleted file mode 100644 index ab5fa1aa60..0000000000 --- a/extensions/sphinx/gii/model/form.php +++ /dev/null @@ -1,13 +0,0 @@ -field($generator, 'indexName'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'ns'); -echo $form->field($generator, 'baseClass'); -echo $form->field($generator, 'db'); -echo $form->field($generator, 'useIndexPrefix')->checkbox(); -echo $form->field($generator, 'enableI18N')->checkbox(); -echo $form->field($generator, 'messageCategory'); diff --git a/extensions/swiftmailer/LICENSE.md b/extensions/swiftmailer/LICENSE.md deleted file mode 100644 index 0bb1a8dca8..0000000000 --- a/extensions/swiftmailer/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php deleted file mode 100644 index 179365ca81..0000000000 --- a/extensions/swiftmailer/Mailer.php +++ /dev/null @@ -1,226 +0,0 @@ - [ - * ... - * 'mailer' => [ - * 'class' => 'yii\swiftmailer\Mailer', - * 'transport' => [ - * 'class' => 'Swift_SmtpTransport', - * 'host' => 'localhost', - * 'username' => 'username', - * 'password' => 'password', - * 'port' => '587', - * 'encryption' => 'tls', - * ], - * ], - * ... - * ], - * ~~~ - * - * You may also skip the configuration of the [[transport]] property. In that case, the default - * PHP `mail()` function will be used to send emails. - * - * You specify the transport constructor arguments using 'constructArgs' key in the config. - * You can also specify the list of plugins, which should be registered to the transport using - * 'plugins' key. For example: - * - * ~~~ - * 'transport' => [ - * 'class' => 'Swift_SmtpTransport', - * 'constructArgs' => ['localhost', 25] - * 'plugins' => [ - * [ - * 'class' => 'Swift_Plugins_ThrottlerPlugin', - * 'constructArgs' => [20], - * ], - * ], - * ], - * ~~~ - * - * To send an email, you may use the following code: - * - * ~~~ - * Yii::$app->mailer->compose('contact/html', ['contactForm' => $form]) - * ->setFrom('from@domain.com') - * ->setTo($form->email) - * ->setSubject($form->subject) - * ->send(); - * ~~~ - * - * @see http://swiftmailer.org - * - * @property array|\Swift_Mailer $swiftMailer Swift mailer instance or array configuration. This property is - * read-only. - * @property array|\Swift_Transport $transport This property is read-only. - * - * @author Paul Klimov - * @since 2.0 - */ -class Mailer extends BaseMailer -{ - /** - * @var string message default class name. - */ - public $messageClass = 'yii\swiftmailer\Message'; - - /** - * @var \Swift_Mailer Swift mailer instance. - */ - private $_swiftMailer; - /** - * @var \Swift_Transport|array Swift transport instance or its array configuration. - */ - private $_transport = []; - - - /** - * @return array|\Swift_Mailer Swift mailer instance or array configuration. - */ - public function getSwiftMailer() - { - if (!is_object($this->_swiftMailer)) { - $this->_swiftMailer = $this->createSwiftMailer(); - } - - return $this->_swiftMailer; - } - - /** - * @param array|\Swift_Transport $transport - * @throws InvalidConfigException on invalid argument. - */ - public function setTransport($transport) - { - if (!is_array($transport) && !is_object($transport)) { - throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); - } - $this->_transport = $transport; - } - - /** - * @return array|\Swift_Transport - */ - public function getTransport() - { - if (!is_object($this->_transport)) { - $this->_transport = $this->createTransport($this->_transport); - } - - return $this->_transport; - } - - /** - * @inheritdoc - */ - protected function sendMessage($message) - { - $address = $message->getTo(); - if (is_array($address)) { - $address = implode(', ', array_keys($address)); - } - Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); - - return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0; - } - - /** - * Creates Swift mailer instance. - * @return \Swift_Mailer mailer instance. - */ - protected function createSwiftMailer() - { - return \Swift_Mailer::newInstance($this->getTransport()); - } - - /** - * Creates email transport instance by its array configuration. - * @param array $config transport configuration. - * @throws \yii\base\InvalidConfigException on invalid transport configuration. - * @return \Swift_Transport transport instance. - */ - protected function createTransport(array $config) - { - if (!isset($config['class'])) { - $config['class'] = 'Swift_MailTransport'; - } - if (isset($config['plugins'])) { - $plugins = $config['plugins']; - unset($config['plugins']); - } - /* @var $transport \Swift_MailTransport */ - $transport = $this->createSwiftObject($config); - if (isset($plugins)) { - foreach ($plugins as $plugin) { - if (is_array($plugin) && isset($plugin['class'])) { - $plugin = $this->createSwiftObject($plugin); - } - $transport->registerPlugin($plugin); - } - } - - return $transport; - } - - /** - * Creates Swift library object, from given array configuration. - * @param array $config object configuration - * @return Object created object - * @throws \yii\base\InvalidConfigException on invalid configuration. - */ - protected function createSwiftObject(array $config) - { - if (isset($config['class'])) { - $className = $config['class']; - unset($config['class']); - } else { - throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); - } - if (isset($config['constructArgs'])) { - $args = []; - foreach ($config['constructArgs'] as $arg) { - if (is_array($arg) && isset($arg['class'])) { - $args[] = $this->createSwiftObject($arg); - } else { - $args[] = $arg; - } - } - unset($config['constructArgs']); - $object = Yii::createObject($className, $args); - } else { - $object = Yii::createObject($className); - } - if (!empty($config)) { - foreach ($config as $name => $value) { - if (property_exists($object, $name)) { - $object->$name = $value; - } else { - $setter = 'set' . $name; - if (method_exists($object, $setter) || method_exists($object, '__call')) { - $object->$setter($value); - } else { - throw new InvalidConfigException('Setting unknown property: ' . $className . '::' . $name); - } - } - } - } - - return $object; - } -} diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php deleted file mode 100644 index 5ca0ccaa85..0000000000 --- a/extensions/swiftmailer/Message.php +++ /dev/null @@ -1,319 +0,0 @@ - - * @since 2.0 - */ -class Message extends BaseMessage -{ - /** - * @var \Swift_Message Swift message instance. - */ - private $_swiftMessage; - - - /** - * @return \Swift_Message Swift message instance. - */ - public function getSwiftMessage() - { - if (!is_object($this->_swiftMessage)) { - $this->_swiftMessage = $this->createSwiftMessage(); - } - - return $this->_swiftMessage; - } - - /** - * @inheritdoc - */ - public function getCharset() - { - return $this->getSwiftMessage()->getCharset(); - } - - /** - * @inheritdoc - */ - public function setCharset($charset) - { - $this->getSwiftMessage()->setCharset($charset); - - return $this; - } - - /** - * @inheritdoc - */ - public function getFrom() - { - return $this->getSwiftMessage()->getFrom(); - } - - /** - * @inheritdoc - */ - public function setFrom($from) - { - $this->getSwiftMessage()->setFrom($from); - - return $this; - } - - /** - * @inheritdoc - */ - public function getReplyTo() - { - return $this->getSwiftMessage()->getReplyTo(); - } - - /** - * @inheritdoc - */ - public function setReplyTo($replyTo) - { - $this->getSwiftMessage()->setReplyTo($replyTo); - - return $this; - } - - /** - * @inheritdoc - */ - public function getTo() - { - return $this->getSwiftMessage()->getTo(); - } - - /** - * @inheritdoc - */ - public function setTo($to) - { - $this->getSwiftMessage()->setTo($to); - - return $this; - } - - /** - * @inheritdoc - */ - public function getCc() - { - return $this->getSwiftMessage()->getCc(); - } - - /** - * @inheritdoc - */ - public function setCc($cc) - { - $this->getSwiftMessage()->setCc($cc); - - return $this; - } - - /** - * @inheritdoc - */ - public function getBcc() - { - return $this->getSwiftMessage()->getBcc(); - } - - /** - * @inheritdoc - */ - public function setBcc($bcc) - { - $this->getSwiftMessage()->setBcc($bcc); - - return $this; - } - - /** - * @inheritdoc - */ - public function getSubject() - { - return $this->getSwiftMessage()->getSubject(); - } - - /** - * @inheritdoc - */ - public function setSubject($subject) - { - $this->getSwiftMessage()->setSubject($subject); - - return $this; - } - - /** - * @inheritdoc - */ - public function setTextBody($text) - { - $this->setBody($text, 'text/plain'); - - return $this; - } - - /** - * @inheritdoc - */ - public function setHtmlBody($html) - { - $this->setBody($html, 'text/html'); - - return $this; - } - - /** - * Sets the message body. - * If body is already set and its content type matches given one, it will - * be overridden, if content type miss match the multipart message will be composed. - * @param string $body body content. - * @param string $contentType body content type. - */ - protected function setBody($body, $contentType) - { - $message = $this->getSwiftMessage(); - $oldBody = $message->getBody(); - $charset = $message->getCharset(); - if (empty($oldBody)) { - $parts = $message->getChildren(); - $partFound = false; - foreach ($parts as $key => $part) { - if (!($part instanceof \Swift_Mime_Attachment)) { - /* @var $part \Swift_Mime_MimePart */ - if ($part->getContentType() == $contentType) { - $charset = $part->getCharset(); - unset($parts[$key]); - $partFound = true; - break; - } - } - } - if ($partFound) { - reset($parts); - $message->setChildren($parts); - $message->addPart($body, $contentType, $charset); - } else { - $message->setBody($body, $contentType); - } - } else { - $oldContentType = $message->getContentType(); - if ($oldContentType == $contentType) { - $message->setBody($body, $contentType); - } else { - $message->setBody(null); - $message->setContentType(null); - $message->addPart($oldBody, $oldContentType, $charset); - $message->addPart($body, $contentType, $charset); - } - } - } - - /** - * @inheritdoc - */ - public function attach($fileName, array $options = []) - { - $attachment = \Swift_Attachment::fromPath($fileName); - if (!empty($options['fileName'])) { - $attachment->setFilename($options['fileName']); - } - if (!empty($options['contentType'])) { - $attachment->setContentType($options['contentType']); - } - $this->getSwiftMessage()->attach($attachment); - - return $this; - } - - /** - * @inheritdoc - */ - public function attachContent($content, array $options = []) - { - $attachment = \Swift_Attachment::newInstance($content); - if (!empty($options['fileName'])) { - $attachment->setFilename($options['fileName']); - } - if (!empty($options['contentType'])) { - $attachment->setContentType($options['contentType']); - } - $this->getSwiftMessage()->attach($attachment); - - return $this; - } - - /** - * @inheritdoc - */ - public function embed($fileName, array $options = []) - { - $embedFile = \Swift_EmbeddedFile::fromPath($fileName); - if (!empty($options['fileName'])) { - $embedFile->setFilename($options['fileName']); - } - if (!empty($options['contentType'])) { - $embedFile->setContentType($options['contentType']); - } - - return $this->getSwiftMessage()->embed($embedFile); - } - - /** - * @inheritdoc - */ - public function embedContent($content, array $options = []) - { - $embedFile = \Swift_EmbeddedFile::newInstance($content); - if (!empty($options['fileName'])) { - $embedFile->setFilename($options['fileName']); - } - if (!empty($options['contentType'])) { - $embedFile->setContentType($options['contentType']); - } - - return $this->getSwiftMessage()->embed($embedFile); - } - - /** - * @inheritdoc - */ - public function toString() - { - return $this->getSwiftMessage()->toString(); - } - - /** - * Creates the Swift email message instance. - * @return \Swift_Message email message instance. - */ - protected function createSwiftMessage() - { - return new \Swift_Message(); - } -} diff --git a/extensions/swiftmailer/composer.json b/extensions/swiftmailer/composer.json deleted file mode 100644 index 409c710410..0000000000 --- a/extensions/swiftmailer/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "yiisoft/yii2-swiftmailer", - "description": "The SwiftMailer integration for the Yii framework", - "keywords": ["yii2", "swift", "swiftmailer", "mail", "email", "mailer"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aswiftmailer", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "require": { - "yiisoft/yii2": "*", - "swiftmailer/swiftmailer": "*" - }, - "autoload": { - "psr-4": { "yii\\swiftmailer\\": "" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - } -} diff --git a/extensions/twig/LICENSE.md b/extensions/twig/LICENSE.md deleted file mode 100644 index e98f03df86..0000000000 --- a/extensions/twig/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/twig/Optimizer.php b/extensions/twig/Optimizer.php deleted file mode 100644 index 9902dd2eab..0000000000 --- a/extensions/twig/Optimizer.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @author Alexander Makarov - */ -class Optimizer implements \Twig_NodeVisitorInterface -{ - /** - * @inheritdoc - */ - public function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) - { - return $node; - } - - /** - * @inheritdoc - */ - public function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) - { - if ($node instanceof \Twig_Node_Print) { - $expression = $node->getNode('expr'); - if ($expression instanceof \Twig_Node_Expression_Function) { - $name = $expression->getAttribute('name'); - if (preg_match('/^(?:register_.+_asset|use|.+_begin|.+_end)$/', $name)) { - return new \Twig_Node_Do($expression, $expression->getLine()); - } elseif (in_array($name, ['begin_page', 'end_page', 'begin_body', 'end_body', 'head'])) { - $arguments = [ - new \Twig_Node_Expression_Constant($name, $expression->getLine()), - ]; - if ($expression->hasNode('arguments') && $expression->getNode('arguments') !== null) { - foreach ($expression->getNode('arguments') as $key => $value) { - if (is_int($key)) { - $arguments[] = $value; - } else { - $arguments[$key] = $value; - } - } - } - $expression->setNode('arguments', new \Twig_Node($arguments)); - return new \Twig_Node_Do($expression, $expression->getLine()); - } - } - } - return $node; - } - - /** - * @inheritdoc - */ - public function getPriority() - { - return 100; - } -} \ No newline at end of file diff --git a/extensions/twig/Template.php b/extensions/twig/Template.php deleted file mode 100755 index 47b0f336c5..0000000000 --- a/extensions/twig/Template.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ -abstract class Template extends \Twig_Template -{ - /** - * @inheritdoc - */ - protected function getAttribute($object, $item, array $arguments = [], $type = \Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) - { - // Twig uses isset() to check if attribute exists which does not work when attribute exists but is null - if ($object instanceof \yii\base\Model) { - if ($type === \Twig_Template::METHOD_CALL) { - if ($this->env->hasExtension('sandbox')) { - $this->env->getExtension('sandbox')->checkMethodAllowed($object, $item); - } - return call_user_func_array([$object, $item], $arguments); - } else { - if ($this->env->hasExtension('sandbox')) { - $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); - } - return $object->$item; - } - } - - return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck); - } -} diff --git a/extensions/twig/ViewRenderer.php b/extensions/twig/ViewRenderer.php deleted file mode 100644 index 6c5fdf8d72..0000000000 --- a/extensions/twig/ViewRenderer.php +++ /dev/null @@ -1,256 +0,0 @@ - - * @since 2.0 - */ -class ViewRenderer extends BaseViewRenderer -{ - /** - * @var string the directory or path alias pointing to where Twig cache will be stored. Set to false to disable - * templates cache. - */ - public $cachePath = '@runtime/Twig/cache'; - /** - * @var array Twig options. - * @see http://twig.sensiolabs.org/doc/api.html#environment-options - */ - public $options = []; - /** - * @var array Objects or static classes. - * Keys of the array are names to call in template, values are objects or names of static classes. - * Example: `['html' => '\yii\helpers\Html']`. - * In the template you can use it like this: `{{ html.a('Login', 'site/login') | raw }}`. - */ - public $globals = []; - /** - * @var array Custom functions. - * Keys of the array are names to call in template, values are names of functions or static methods of some class. - * Example: `['rot13' => 'str_rot13', 'a' => '\yii\helpers\Html::a']`. - * In the template you can use it like this: `{{ rot13('test') }}` or `{{ a('Login', 'site/login') | raw }}`. - */ - public $functions = []; - /** - * @var array Custom filters. - * Keys of the array are names to call in template, values are names of functions or static methods of some class. - * Example: `['rot13' => 'str_rot13', 'jsonEncode' => '\yii\helpers\Json::encode']`. - * In the template you can use it like this: `{{ 'test'|rot13 }}` or `{{ model|jsonEncode }}`. - */ - public $filters = []; - /** - * @var array Custom extensions. - * Example: `['Twig_Extension_Sandbox', new \Twig_Extension_Text()]` - */ - public $extensions = []; - /** - * @var array Twig lexer options. - * - * Example: Smarty-like syntax: - * ```php - * [ - * 'tag_comment' => ['{*', '*}'], - * 'tag_block' => ['{', '}'], - * 'tag_variable' => ['{$', '}'] - * ] - * ``` - * @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax - */ - public $lexerOptions = []; - /** - * @var array namespaces and classes to import. - * - * Example: - * - * ```php - * [ - * 'yii\bootstrap', - * 'app\assets', - * \yii\bootstrap\NavBar::className(), - * ] - * ``` - */ - public $uses = []; - /** - * @var \Twig_Environment twig environment object that renders twig templates - */ - public $twig; - - - public function init() - { - $this->twig = new \Twig_Environment(null, array_merge([ - 'cache' => Yii::getAlias($this->cachePath), - 'charset' => Yii::$app->charset, - ], $this->options)); - - $this->twig->setBaseTemplateClass('yii\twig\Template'); - - // Adding custom globals (objects or static classes) - if (!empty($this->globals)) { - $this->addGlobals($this->globals); - } - - // Adding custom functions - if (!empty($this->functions)) { - $this->addFunctions($this->functions); - } - - // Adding custom filters - if (!empty($this->filters)) { - $this->addFilters($this->filters); - } - - $this->addExtensions([new Extension($this->uses)]); - - // Adding custom extensions - if (!empty($this->extensions)) { - $this->addExtensions($this->extensions); - } - - $this->twig->addGlobal('app', \Yii::$app); - - // Change lexer syntax (must be set after other settings) - if (!empty($this->lexerOptions)) { - $this->setLexerOptions($this->lexerOptions); - } - } - - /** - * Renders a view file. - * - * This method is invoked by [[View]] whenever it tries to render a view. - * Child classes must implement this method to render the given view file. - * - * @param View $view the view object used for rendering the file. - * @param string $file the view file. - * @param array $params the parameters to be passed to the view file. - * - * @return string the rendering result - */ - public function render($view, $file, $params) - { - $this->twig->addGlobal('this', $view); - $loader = new \Twig_Loader_Filesystem(dirname($file)); - $this->addAliases($loader, Yii::$aliases); - $this->twig->setLoader($loader); - - return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params); - } - - /** - * Adds aliases - * - * @param \Twig_Loader_Filesystem $loader - * @param array $aliases - */ - protected function addAliases($loader, $aliases) - { - foreach ($aliases as $alias => $path) { - if (is_array($path)) { - $this->addAliases($loader, $path); - } elseif (is_string($path) && is_dir($path)) { - $loader->addPath($path, substr($alias, 1)); - } - } - } - - /** - * Adds global objects or static classes - * @param array $globals @see self::$globals - */ - public function addGlobals($globals) - { - foreach ($globals as $name => $value) { - if (!is_object($value)) { - $value = new ViewRendererStaticClassProxy($value); - } - $this->twig->addGlobal($name, $value); - } - } - - /** - * Adds custom functions - * @param array $functions @see self::$functions - */ - public function addFunctions($functions) - { - $this->_addCustom('Function', $functions); - } - - /** - * Adds custom filters - * @param array $filters @see self::$filters - */ - public function addFilters($filters) - { - $this->_addCustom('Filter', $filters); - } - - /** - * Adds custom extensions - * @param array $extensions @see self::$extensions - */ - public function addExtensions($extensions) - { - foreach ($extensions as $extName) { - $this->twig->addExtension(is_object($extName) ? $extName : new $extName()); - } - } - - /** - * Sets Twig lexer options to change templates syntax - * @param array $options @see self::$lexerOptions - */ - public function setLexerOptions($options) - { - $lexer = new \Twig_Lexer($this->twig, $options); - $this->twig->setLexer($lexer); - } - - /** - * Adds custom function or filter - * @param string $classType 'Function' or 'Filter' - * @param array $elements Parameters of elements to add - * @throws \Exception - */ - private function _addCustom($classType, $elements) - { - $classFunction = 'Twig_' . $classType . '_Function'; - - foreach ($elements as $name => $func) { - $twigElement = null; - - switch ($func) { - // Just a name of function - case is_string($func): - $twigElement = new $classFunction($func); - break; - // Name of function + options array - case is_array($func) && is_string($func[0]) && isset($func[1]) && is_array($func[1]): - $twigElement = new $classFunction($func[0], $func[1]); - break; - } - - if ($twigElement !== null) { - $this->twig->{'add'.$classType}($name, $twigElement); - } else { - throw new \Exception("Incorrect options for \"$classType\" $name."); - } - } - } -} diff --git a/extensions/twig/ViewRendererStaticClassProxy.php b/extensions/twig/ViewRendererStaticClassProxy.php deleted file mode 100644 index facca46b6e..0000000000 --- a/extensions/twig/ViewRendererStaticClassProxy.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ -class ViewRendererStaticClassProxy -{ - private $_staticClassName; - - - /** - * @param string $staticClassName - */ - public function __construct($staticClassName) - { - $this->_staticClassName = $staticClassName; - } - - /** - * @param string $property - * @return mixed - */ - public function __get($property) - { - $class = new \ReflectionClass($this->_staticClassName); - - return $class->getStaticPropertyValue($property); - } - - /** - * @param string $property - * @param mixed $value - * @return mixed - */ - public function __set($property, $value) - { - $class = new \ReflectionClass($this->_staticClassName); - $class->setStaticPropertyValue($property, $value); - - return $value; - } - - /** - * @param string $method - * @param array $arguments - * @return mixed - */ - public function __call($method, $arguments) - { - return call_user_func_array([$this->_staticClassName, $method], $arguments); - } -} diff --git a/framework/BaseYii.php b/framework/BaseYii.php index 1254e882fa..00ee8d9981 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -93,11 +93,15 @@ class BaseYii */ public static function getVersion() { +<<<<<<< HEAD <<<<<<< HEAD return '2.0.4-dev'; ======= return '2.0.5'; >>>>>>> yiichina/master +======= + return '2.0.9-dev'; +>>>>>>> master } /** @@ -347,11 +351,11 @@ class BaseYii unset($type['class']); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { - return call_user_func($type, $params); + return static::$container->invoke($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } else { - throw new InvalidConfigException("Unsupported configuration type: " . gettype($type)); + throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type)); } } @@ -433,14 +437,14 @@ class BaseYii * This has to be matched with a call to [[endProfile]] with the same category name. * The begin- and end- calls must also be properly nested. For example, * - * ~~~ + * ```php * \Yii::beginProfile('block1'); * // some code to be profiled * \Yii::beginProfile('block2'); * // some other code to be profiled * \Yii::endProfile('block2'); * \Yii::endProfile('block1'); - * ~~~ + * ``` * @param string $token token for the code block * @param string $category the category of this log message * @see endProfile() @@ -468,7 +472,10 @@ class BaseYii */ public static function powered() { - return 'Powered by Yii Framework'; + return \Yii::t('yii', 'Powered by {yii}', [ + 'yii' => '' . \Yii::t('yii', + 'Yii Framework') . '' + ]); } /** diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e90af6f266..46b873b783 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -3,14 +3,423 @@ Yii Framework 2 Change Log <<<<<<< HEAD 2.0.4 under development + +2.0.9 under development ----------------------- +- Enh #8795: Refactored `yii\web\User::loginByCookie()` in order to make it easier to override (maine-mike, silverfire) +- Enh #9948: `yii\rbac\PhpManager` now invalidates script file cache performed by 'OPCache' or 'APC' on file saving (klimov-paul) +- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) +- Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) +- Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) +- Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) +- Bug #11429: Fixed `yii\i18n\PhpMessageSource::loadFallbackMessages()` not to log error when source and language is same, but locales are different (silverfire) +- Enh #11484: Speed up `yii\db\oci\Schema::loadTableSchema()` for Oracle DBMS (SSiwek) +- Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) +- Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) +- Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) +- Enh #11438: Configurable `yii\helpers\Markdown` default flavor (mdmunir) +- Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) +- Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) +- Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) +- Bug #11523: Fixed `yii\web\User::checkRedirectAcceptable()` to treat acceptable content type `*/*` as `*` (silverfire) +- Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) +- Bug #11571: Fixed `yii\db\ColumnSchemaBuilder` to work with custom column types (andrey-mokhov, silverfire) +- Bug #11662: Fixed `schema-oci.sql` for RBAC (jonny7) +- Bug #11527: Fixed `bigPrimaryKey()` for SQLite (dynasource) +- Bug #11686: `BaseArrayHelper::isIn()` comparison did not work in strict mode (taobig) +- Enh #11679: Extracted `CheckAccessInterface` from `ManagerInterface` (SamMousa, samdark, mdomba) +- Bug #11723: Fixed PHP 7 + XDebug error handling displaying "Expected array for frame 0" (tanakahisateru) +- Bug #11735: Fixed `yii\web\UploadedFile` to return `null` when there's no file uploaded (brummm) + +2.0.8 April 28, 2016 +-------------------- + +- Bug #7627: Fixed `yii\widgets\ActiveField` to handle inputs AJAX validation with changed ID properly (dizeee) +- Bug #7717: Fixed the bug that `$properties` parameter in `ArrayHelper::toArray()` was not passed to recursive calls (quantum13) +- Bug #9074: Fixed JS call `$('#grid').yiiGridView('getSelectedRows')` when `GridView::$showHeader` is set to false (NekitoSP, silverfire) +- Bug #9851: Fixed partial commit / rollback in nested transactions (sammousa) +- Bug #9935: Fixed `yii\validators\EachValidator` does not invoke `validateAttribute()` method of the embedded validator (klimov-paul) +- Bug #10201: Fixed bug in ActiveRecord, where relational data could not be fetched in case with composite key and join with IN condition (PaulVanSchayck, airmoi, joe-meyer, cebe) +- Bug #10235: Fixed `yii\console\Application::runAction` to not to corrupt response object (hiqsol) +- Bug #10480: Fixed removing old identity cookie when loggin in as another user without logging out first (maine-mike) +- Bug #10617: Fixed `yii\web\Request::getBodyParams()` returned `null` instead of empty array if request body is empty and content type is application/json (samdark) +- Bug #10784: Fixed `yii\grid\CheckboxColumn` to set correct value when `yii\grid\CheckboxColumn::$checkboxOptions` closure is used (nukkumatti) +- Bug #10850: Fixed unable to use 'definitions' and 'aliases' at `yii\widgets\MaskedInput` (rahimov, klimov-paul) +- Bug #10884: Fixed MessageFormatter for formatting messages when not all parameters are given (laxity7, cebe) +- Bug #10935: Fixed cache key collision in `yii\web\UrlManager::createUrl()` (sammousa) +- Bug #10946: Fixed parameters binding to the SQL query in `yii\db\mysqlSchema::findConstraints()` (silverfire) +- Bug #10969: Fixed generator migration tool with decimal params in column (pana1990) +- Bug #10974: `yii.js` - fixed error in ajaxPrefilter event handler, caused by blocked frame (maximal) +- Bug #11012: Fixed `yii\web\UploadedFile::getBaseName()` to work with UTF-8 file names (hiscaler, silverfire) +- Bug #11026: Fixed `StringHelper::truncateWords()` to count words properly for non-English text (samdark, tol17) +- Bug #11038: Fixed handling of intervals of 0 seconds in `yii\i18n\Formatter::asDuration()` (VirtualRJ) +- Bug #11040: Check parameter 'recursive' and disable recursive copying with option 'recursive' => false in method BaseFileHelper::copyDirectory (Ni-san) +- Bug #11052: Fixed `HtmlPurifier` configuration sequence (samdark) +- Bug #11066: `yii.js` - fixed `getQueryParams()` function to handle URLs with anchors correctly (DrDeath72) +- Bug #11088: Fixed bug with column name not being quoted correctly, when a quoted table name or a table name in prefix syntax was used (cebe, edgardmessias, Ni-san) +- Bug #11093: Fixed `yii\db\QueryBuilder::buildAndCondition()` to add query params passed directly by `yii\db\Expression` (CedricYii, silverfire) +- Bug #11125: Fixed `JSON_ERROR_SYNTAX` for `json_decode(null)` in PHP 7 (fps01) +- Bug #11132: Fixed `yii\widgets\FragmentCache` not handling empty content correctly in all cases (kidol) +- Bug #11188: Fixed wrong index usage in `CaptchaAction` when calling `imagefilledrectangle` (alsopub) +- Bug #11196: Fixed VarDumper throws PHP Fatal when dumping `__PHP_Incomplete_Class` (DamianZ) +- Bug #11220: NumberValidator now handles objects properly (samdark) +- Bug #11221: Boolean validator generates incorrect error message (azaikin, githubjeka) +- Bug #11223: Fixed returning an empty array when DbManager::getRolesByUser() was called on a user with user id 0 (VirtualRJ) +- Bug #11228: `yii.activeForm.js` - AJAX validation will not be triggered if client side validation failed (silverfire) +- Bug #11262: Enabled use of yii2 inside of PHAR packaged console applications (hiqsol) +- Bug #11270: Fixed `BaseActiveRecord::link()` method in order to support closure in `indexBy` for relations declaration (iushev) +- Bug #11280: Descendants of `yii\console\controllers\BaseMigrateController`, like the one for MongoDB, unable to create new migration (klimov-paul) +- Bug #11333: Avoid serializing PHP 7 errors (zuozp8) +- Bug #11425: Fixed namespace conflict with Markdown helper and Console helper (cebe, mdmunir) +- Bug: SQlite querybuilder did not create primary key with bigint for `TYPE_BIGPK` (cebe) +- Enh #5469: Add mimetype validation by mask in FileValidator (kirsenn, samdark, silverfire) +- Enh #7177, #10165: Added support for validating datetime and time values using intl short format to `DateValidator` (VirtualRJ, cebe) +- Enh #8145, #8139, #10234 #11153: `yii\validators\Validator::$attributes` property now supports `!attribute` notation to validate attribute, but do not mark it as safe (mdmunir) +- Enh #8148: Implemented ability to add comment on table and column in migration (vaseninm, silverfire) +- Enh #8505: `yii\db\Query` now contains a andFilterCompare() method that allows filtering using operators in the query value (lennartvdd) +- Enh #8602: `yii\validators\DateValidator` skip validation for `timestampAttribute`, if it is already in correct format (klimov-paul) +- Enh #8639: Improve ActiveRecord to not create new instances of classes when objects are available (cebe) +- Enh #8779: Automatically set enctype form option when using file input field (pana1990, arogachev) +- Enh #9340: Adds `after()` and `first()` column schema builder modifiers (df2) +- Enh #9425: `yii\db\Query::exists()` now uses SQL standard `EXISTS()` query via new `yii\db\QueryBuilder::selectExists()` method to improving performance in some cases (PowerGamer1) +- Enh #9562: Adds `char` datatype to framework (df2) +- Enh #9604: `yii\db\BaseActiveRecord` now triggers event `EVENT_AFTER_REFRESH` after a record is refreshed (raoul2000) +- Enh #9893: `yii.js` handleAction enhanced to support for data-form attribute, so links can trigger specific forms (SamMousa) +- Enh #10309: Extracted `yii\web\UrlManager` rule cache key into `$cacheKey` protected property (lordthorzonus) +- Enh #10322: ActiveForm now respects formtarget attribute of submit button (AnatolyRugalev) +- Enh #10451: Check of existence of `$_SERVER` in `\yii\web\Request` before using it (quantum13) +- Enh #10475: Extracted `getUrlFromCache()` and `setRuleToCache()` protected methods from `yii\web\UrlManager::createUrl()` (dmdark) +- Enh #10487: `yii\helpers\BaseArrayHelper::index()` got a third parameter `$groupBy` to group the input array by the key in one or more dimensions (quantum13, silverfire, samdark) +- Enh #10610: Added `BaseUrl::$urlManager` to be able to set URL manager used for creating URLs (samdark) +- Enh #10631: Splitted gettng label and rendering cell in `yii\grid\DataColumn::renderHeaderCellContent()` to make code simpler (t-kanstantsin, samdark) +- Enh #10710: `yii\helpers\FileHelper::copyDirectory()` is now throwing exception when trying to copy a directory to itself or a subdirectory (wallysalami, cebe, samdark) +- Enh #10764: `yii\helpers\Html::tag()` and `::beginTag()` return content without any HTML when the `$tag` attribute is `false` or `null` (pana1990) +- Enh #10840: Added `yii\console\Controller::optionAliases()` method to support aliases for commands (pana1990) +- Enh #10889: Allows unsigned primary key column definitions (df2) +- Enh #10908: Added Dependency Injection for Closure configuration (SamMousa) +- Enh #10910: Fixed Captcha client side validation after image refresh, when controller is under module (silverfire) +- Enh #10921: `__toString()` of column schema builder now adapts to column types (df2) +- Enh #10931: Removed hard dependency of `yii\di\Container` on `Yii::$app` (SamMousa) +- Enh #10937: `yii\web\User` will now confirm the request accepts an HTML response before redirecting to the login page. Added optional `$checkAcceptHeader` to `yii\web\User::loginRequired()` (sammousa) +- Enh #10941: Added `yii\helpers\ArrayHelper::isTraversable`, added support for traversable selections for dropdownList, radioList and checkboxList in `yii\helpers\Html` (sammousa) +- Enh #10941: Added `yii\helpers\ArrayHelper::isTraversable`, added support for traversable selections for dropdownList, radioList and checkboxList in `yii\helpers\Html`. +- Enh #10954: `yii\db\QueryBuilder` now accepts `\Traversable` objects for `in` condition (SamMousa, silverfire) +- Enh #10967: Simplified Javascript on the exception debug page (SamMousa) +- Enh #10976: `Inflector::transliterate()` now uses `strtr` instead of `str_replace` (DrDeath72) +- Enh #11002: `AttributeBehavior::$skipUpdateOnClean` which determines whether to skip a behavior when the behavior owner has not been modified (Faryshta) +- Enh #11056: Allow setting a custom logger configuration for `yii\log\Dispatcher` in configuration (bionoren, cebe) +- Enh #11058: `yii\web\User::loginRequired()` now does not set return URL when request method is not GET (dawei101, silverfire) +- Enh #11110: Added migrations for DB session (mdmunir) +- Enh #11137: Added weak ETag support to `yii\filters\HttpCache`. It could be turned on via setting `$weakEtag` to `true` (particleflux) +- Enh #11139: `yii\validators\EachValidator` injects specific attribute value in error message parameters (silverfire) +- Enh #11166: migrate command new option `useTablePrefix` (Faryshta) +- Enh #11187: migrate command now generates phpdoc for table migrations (Faryshta) +- Enh #11207: migrate command can create foreign keys. (Faryshta) +- Enh #11254: Added ability to attach RBAC rule using class name (mdmunir) +- Enh #11285: `yii\base\Security` enhancements (tom--, samdark) + - Avoid reading more bytes than needed from `/dev/urandom` and `/dev/random`. + - Pefer `/dev/random` to `/dev/urandom` when running on FreeBSD. + - Better RNG performance. +- Enh #11336: Allow resettting `$hostInfo`, `$scriptUrl`, and `$pathInfo` in `yii\web\Request` and `$baseUrl`, and `$hostInfo` in `yii\web\UrlManager` to `null`, to make Yii determine the value again (cebe) +- Enh: Added `StringHelper::countWords()` that given a string returns number of words in it (samdark) +- Chg #9854: Added `ActiveRecordInterface::populateRelation()` to respect the methods called by the implementation (SamMousa) +- Chg #10726: Added `yii\rbac\ManagerInterface::canAddChild()` (dkhlystov, samdark) +- Chg #10921: Inverts responsibility of database specific column schema builder classes (df2) +- Chg #11071: `yii\helpers\BaseArrayHelper::isIn()` and `isTraversable()` since now throw `\yii\base\InvalidParamException` instead of `\InvalidArgumentException` (nukkumatti) +- Chg #11283: `ActiveRecord::unlink()` is not setting FK to `null` before deleting itself anymore (samdark) +- Chg: HTMLPurifier dependency updated to `~4.6` (samdark) +- New #8920: Added `yii\mutex\PgsqlMutex` which implements mutex "lock" mechanism via PgSQL locks (nineinchnick, CSharpRU) + + +2.0.7 February 14, 2016 +----------------------- + +- Bug #6351: Find MySQL FK constraints from `information_schema` tables instead of `SHOW CREATE TABLE` to improve reliability (nineinchnick) +- Bug #6363, #8301, #8582, #9566: Fixed data methods and PJAX issues when used together (derekisbusy) +- Bug #6876: Fixed RBAC migration MSSQL cascade problem (thejahweh) +- Bug #7627: Fixed `yii\widgets\ActiveField` to handle inputs validation with changed ID properly (dynasource, cebe) +- Bug #7806: Fixed `yii\grid\CheckboxColumn` fixed `_all` checkbox column name generation (cebe, silverfire) +- Bug #7964: Fixed i18n message sources to load fallback messages in a smarter way (silverfire) +- Bug #8348: Fixed `yii\helpers\BaseArrayHelper` fixed PHP Fatal Error: Nesting level too deep - recursive dependency? (andrewnester) +- Bug #8466: Fixed `yii\validators\FileValidator` to display error for `tooBig` and `tooSmall` with formatted unit (silverfire) +- Bug #8573: Fixed incorrect data type mapping for NUMBER to INTEGER or DECIMAL in Oracle (vbelogai) +- Bug #8723: Fixed `yii\helpers\VarDumper::export()` unable to export circle referenced objects with `Closure` (klimov-paul) +- Bug #9061: Fixed `yii.activeForm.js`: input onBlur event forces field validation (githubjeka) +- Bug #9108: Negative number resulted in no formatting when using `Formatter::asSize()` or `Formatter::asShortSize` (nxnx, cebe) +- Bug #9288: Fixed `FileHelper::createDirectory` directory creation to be concurrency friendly (dynasource) +- Bug #9314: Fixed `yii\rbac\DbManager::getPermissionsByUser()` not returning permissions directly assigned to a user (hesna) +- Bug #9323: Fixed `yii\console\controllers\MessageController` not using database connection specified in config (raccoon69, samdark) +- Bug #9333: Fixed `yii\web\AssetManager` to publish bundles using symlink with nested directories in `hash` (silverfire) +- Bug #9415: Fixed regression in 2.0.6 where on Oracle DB `PDO::ATTR_CASE = PDO::CASE_LOWER` did not work anymore (cebe) +- Bug #9442: Fixed `yii\db\Migration::renameTable()` caused fatal error when using SQLite driver (fetus-hina) +- Bug #9452: Fixed `yii\db\Query::where()` does not add params from directly passed `yii\db\Expression` (klimov-paul) +- Bug #9454: Fixed MSSQL MARS wasn't working with transactions (daliran) +- Bug #9583: Server response on invalid JSON request included a wrong message about "Internal Server Error" with status 500 (cebe) +- Bug #9591: Fixed `yii.validation.js` code so it is compressable by YUICompressor (samdark, hofrob) +- Bug #9596: Fixed `\yii\web\UrlManager::createAbsoluteUrl(['site/index', '#' => 'testHash'])` losing hash (alchimik, samdark) +- Bug #9670: Fixed PJAX redirect in IE. `yii\web\Response::redirect()` - added check for `X-Ie-Redirect-Compatibility` header (silverfire) +- Bug #9678: `I18N::format()` wasn't able to handle named placeholder in "selectordinal" (samdark) +- Bug #9681: `Json::encode()` was erroring under CYGWIN (samdark) +- Bug #9689: Hidden input on `Html::activeFileInput()` had the wrong name if a name was explicitly given (graphcon, cebe) +- Bug #9707: Fixed Memcache duration which exceeds 30 days (vernik91) +- Bug #9714: Fixed `yii\rbac\PhpManager::updateItem()` unable to save users assignments (rezident1307) +- Bug #9754: Fixed `yii\web\Request` error when path info is empty (dynasource) +- Bug #9790: Fixed `yii\db\sqlite\QueryBuilder` to generate proper SQL for UNION (romeOz, samdark) +- Bug #9791: Fixed endless loop on file creation for non-existing device letters on windows (lukos, cebe) +- Bug #9874: Fixed outputting exception stacktrace in non-debug mode when `Response::FORMAT_RAW` is used (nainoon) +- Bug #9883: Passing a single `yii\db\Expression` to `Query::select()` or `::addSelect()` was not handled correctly in all cases (cebe) +- Bug #9911: Fixed `yii\helpers\BaseStringHelper::explode()` code so it doesn't remove items equal to 0 when `skip_empty` is true (silverfire, kidol) +- Bug #9924: Fixed `yii.js` handleAction corrupted parameter values containing quote (") character (silverfire) +- Bug #9984: Fixed wrong captcha color in case Imagick is used (DrDeath72) +- Bug #9999: Fixed `yii\web\UrlRule` to allow route parameter names with `-`, `_`, `.`characters (silverfire) +- Bug #10029: Fixed MaskedInput not working with PJAX (martrix78, samdark) +- Bug #10052: Fixed `yii\i18n\Formatter` to work with huge numbers on 32-bit arch (necrox87, silverfire) +- Bug #10101: Fixed assignments saving on role removing in `\yii\rbac\PhpManager` (rezident1307) +- Bug #10142: Fixed `yii\validators\EmailValidator` to check the length of email properly (silverfire) +- Bug #10218: Fixed Flash messages not showing after logging out a user (andrewnester) +- Bug #10263: Fixed `yii\validators\UniqueValidator` to work properly when model is not instance of `targetClass` (bupy7, githubjeka, silverfire) +- Bug #10278: Fixed `yii\helpers\BaseJson` support \SimpleXMLElement data (SilverFire, LAV45) +- Bug #10302: Fixed JS function `yii.getQueryParams`, which parsed array variables incorrectly (servocoder, silverfire) +- Bug #10363: Fixed fallback float and integer formatting (AnatolyRugalev, silverfire) +- Bug #10385: Fixed `yii\validators\CaptchaValidator` passed incorrect hashKey to JS validator when `captchaAction` begins with `/` (silverfire) +- Bug #10467: Fixed `yii\di\Instance::ensure()` to work with minimum settings (LAV45) +- Bug #10541: Fixed division by zero issue in `Console` helper progress bar (youmad) +- Bug #10580: Fixed `yii\grid\GridView::guessColumns()` to work with numeric column names (silverfire) +- Bug #10625: Fixed `activeForm.js` - when submit doesn't reload page, submit button value simulation with hidden input did not work (andrewnester) +- Bug #10629: Fixed `yii\helpers\BaseStringHelper` - BaseStringHelper::truncateHtml adds suffix regardless of the string length (andrewnester) +- Bug #10692: Fixed default value extraction in PostgreSQL Schema for null values (wallysalami, gabrielhomsi) +- Bug #10739: Fixed `yii\web\UrlManager::parseRequest()` to treat request URL with more than one slash at the end as invalid (andrewnester) +- Bug #10751: Fixed `yii\validators\UrlValidator` pattern to improve matching (silverfire) +- Bug #10760: `yii\widgets\DetailView::normalizeAttributes()` fixed for arrayable models (boehsermoe) +- Bug #10825: Fixed `yii\validators\EachValidator` does not respect `skipOnEmpty` rule parameter (klimov-paul) +- Bug: Fixed generation of canonical URLs for `ViewAction` pages (samdark) +- Bug: Fixed `mb_*` functions calls to use `UTF-8` or `Yii::$app->charset` (silverfire) +- Enh #2377: Allow specifying a table alias when joining relations via `joinWith()` (cebe, nainoon) +- Enh #3506: Added `yii\validators\IpValidator` to perform validation of IP addresses and subnets (SilverFire, samdark) +- Enh #4972: Added `yii\db\ActiveQuery::alias()` to allow specifying a table alias for the model table without having to know the name (cebe, stepanselyuk) +- Enh #5146: Added `yii\i18n\Formatter::asDuration()` method (nineinchnick, SilverFire) +- Enh #5902: `yii\widgets\Pjax::options` now support special option `tag` to specify tag of container (Alex-Code) +- Enh #6162, #9198: Added parameter visibleButtons for `yii\grid\ActionColumn` (fornit1917, silverfire) +- Enh #7341: Client validation now skips disabled inputs (SamMousa) +- Enh #7405: Added `yii\filters\auth\AuthMethod::optional` for optional authentification in all child classes (SilverFire) +- Enh #7566: Improved `\yii\validators\CompareValidator` default messages (slinstj) +- Enh #7581: Added ability to specify range using anonymous function in `RangeValidator` (RomeroMsk) +- Enh #7674: Added `yii\db\Connection::commandClass` to configure a command class that will be used by the connection (sammousa, silverfire, cebe) +- Enh #8284: Added `\yii\captcha\CaptchaAction::$imageLibrary` property allowing to set image rendering library (AnatolyRugalev) +- Enh #8329: Added support of options for `message` console command (vchenin) +- Enh #8613: `yii\widgets\FragmentCache` will not store empty content anymore which fixes some problems related to `yii\filters\PageCache` (kidol) +- Enh #8649: Added total applied migrations to final report (vernik91) +- Enh #8687: Added support for non-gregorian calendars, e.g. persian, taiwan, islamic to `yii\i18n\Formatter` (cebe, z-avanes, hooman-pro) +- Enh #8824: Allow passing a `yii\db\Expression` to `Query::groupBy()` (cebe) +- Enh #8995: `yii\validators\FileValidator::maxFiles` can be set to `0` to allow unlimited count of files (PowerGamer1, silverfire) +- Enh #9282: Improved JSON error handling to support PHP 5.5 error codes (freezy-sk) +- Enh #9337: Added `yii\db\ColumnSchemaBuilder::defaultExpression()` to support DB Expression as default value (kotchuprik) +- Enh #9412: `yii\web\Response::sendHeaders()` does now set the status header last which negates certain magic PHP behavior regarding the `header()` function (nd4c, kidol) +- Enh #9443: Added `unsigned()` to `ColumnSchemaBuilder` (samdark) +- Enh #9465: ./yii migrate/create now generates code based on migration name and --fields (pana1990) +- Enh #9573: Added `yii\rbac\ManagerInterface::getUserIdsByRole()` and implementations (samdark) +- Enh #9635: Added default CSS class for `\yii\grid\ActionColumn` header (arogachev, dynasource) +- Enh #9643: Added migrations for DB cache (mdmunir) +- Enh #9711: Added `yii\widgets\LinkPager::$pageCssClass` that allows to set default page class (ShNURoK42) +- Enh #9733: Added Unprocessable Entity HTTP Exception (janfrs) +- Enh #9762: Added `JsonResponseFormatter::$encodeOptions` and `::$prettyPrint` for better JSON output formatting (cebe) +- Enh #9783: jQuery inputmask dependency updated to `~3.2.2` (samdark) +- Enh #9785: Added ability to invoke callback with dependency resolution to DI container (mdmunir, SamMousa) +- Enh #9869: Allow path alias for SQLite database files in DSN config (ASlatius) +- Enh #9901: Default `Cache.SerializerPermissions` configuration option for `HTMLPurifier` is set to `0775` (klimov-paul) +- Enh #10056: Allowed any callable to be passed to `ActionColumn::$urlCreator` (freezy-sk) +- Enh #10061: `yii\helpers\BaseInflector::transliterate()` is now public. Introduced different levels of transliteration strictness (silverfire) +- Enh #10078: Added `csrf` option to `Html::beginForm()` to allow disabling the hidden csrf field generation (machour) +- Enh #10086: `yii\base\Controller::viewPath` is now configurable (Sibilino) +- Enh #10098: Changed `yii.confirm` context to the event's target DOM element which is triggered by clickable or changeable elements (lichunqiang) +- Enh #10108: Added support for events in interfaces (omnilight) +- Enh #10118: Allow easy extension of slug generation in `yii\behaviors\SluggableBehavior` (cebe, hesna, silverfire) +- Enh #10149: Made `yii\db\Connection` serializable (Sam Mousa) +- Enh #10154: Implemented support of traversable objects in `RangeValidator::ranges`, added `ArrayHelper::isIn()` and `ArrayHelper::isSubset()` (Sam Mousa) +- Enh #10158: Added the possibility to specify CSS and Javascript options per file in `\yii\web\AssetBundle` (machour) +- Enh #10170: `Yii::powered()` now uses `Yii::t()` (SamMousa) +- Enh #10217: Support for selecting multiple filter values with the same name was added to `yii.gridView.js` (omnilight, silverfire) +- Enh #10255: Added Yii warning to session initialization if session was already started (AnatolyRugalev) +- Enh #10264: Generation of HTML tags in Yii's JavaScript now uses jQuery style (silverfire) +- Enh #10267: `yii.js` - added original event passing to `pjaxOptions` for links with `data-method` and `data-pjax` (servocoder, silverfire) +- Enh #10319: `yii\helpers\VarDumper::dump()` now respects PHP magic method `__debugInfo()` (klimov-paul) +- Enh #10359: Support wildcard category name in `yii/console/controllers/MessageController` (rmrevin) +- Enh #10390: Added ability to disable outer tag for `\yii\helpers\BaseHtml::radiolist()`, `::checkboxList()` (TianJinRong, githubjeka, silverfire) +- Enh #10535: Allow passing a `yii\db\Expression` to `Query::orderBy()` and `Query::groupBy()` (andrewnester, cebe) +- Enh #10545: `yii\web\XMLResponseFormatter` changed to format models in a proper way (andrewnester) +- Enh #10783: Added migration and unit-tests for `yii\i18n\DbMessageSource` (silverfire) +- Enh #10797: Cleaned up requirements checker CSS (muhammadcahya) +- Enh: Added last resort measure for `FileHelper::removeDirectory()` fail to unlink symlinks under Windows (samdark) +- Enh: `AttributeBehavior::getValue()` now respects the callable in array format (silverfire) +- Chg #6419: Added `yii\web\ErrorHandler::displayVars` make list of displayed vars customizable. `$_ENV` and `$_SERVER` are not displayed by default anymore (silverfire) +- Chg #9369: `Yii::$app->user->can()` now returns `false` instead of erroring in case `authManager` component is not configured (creocoder) +- Chg #9411: `DetailView` now automatically sets container tag ID in case it's not specified (samdark) +- Chg #9528: Traversable objects are now formatted as arrays in `yii\web\XmlResponseFormatter` to support SPL objects and Generators (MaXL-ru) +- Chg #9878, #9879, #9880: Make `\base\Security` use `random_bytes()`, LibreSSL, mcrypt, limit OpenSSL to Windows, and to prefer `password_hash()` over `crypt()` (tom--) +- Chg #9953: `TimestampBehavior::getValue()` changed to make value processing consistent with `AttributeBehavior::getValue()` (silverfire) +- Chg #10296: Methods mset, mget and madd of `\yii\caching\Cache` have been marked as deprecated (trejder, githubjeka) +- Chg: ApcCache is now able to handle PHP 7 APCu (samdark) +- Chg: `BlameableBehavior::getValue()` changed to make value processing consistent with `AttributeBehavior::getValue()` (silverfire) +- New #10083: Added wrapper for PHP webserver (samdark) +- New: Added new requirement: ICU Data version >= 49.1 (SilverFire) + + +2.0.6 August 05, 2015 +--------------------- + +- Bug #4763: Fixed display issue with overlapping call stack item on exception display page (cebe) +- Bug #7305: Logging of Exception objects resulted in failure of the logger i.e. no logs being written (cebe) +- Bug #7374: Use proper INSERT syntax with default values when no values are specified (nineinchnick) +- Bug #7707: Client-side `trim` validator now passes the trimmed value to subsequent validators (nkovacs) +- Bug #7764: `\yii\helpers\ArrayHelper::toArray()` wasn't passing `$recursive` to `Arrayable::toArray` (brandonkelly) +- Bug #8161: Fixed active form `data-method` submit bug when client validation is used (vbelogai) +- Bug #8322: `yii\behaviors\TimestampBehavior::touch()` now throws an exception if owner is new record (klimov-paul) +- Bug #8451: `yii\i18n\Formatter` did not allow negative unix timestamps as input for date formatting (cebe) +- Bug #8483: Sequence name in `Schema::getLastInsertId()` was not properly quoted (nineinchnick) +- Bug #8506: Cleaning of output buffer in `Widget::run()` conflicts with `Pjax` widget which did the cleanup itself (cebe, joester89) +- Bug #8544: Fixed `yii\db\ActiveRecord` does not update attribute specified at `optimisticLock()` after save (klimov-paul) +- Bug #8549: Fixed `yii\caching\FileCache` doesn't lock cache files when reading (iworker) +- Bug #8551: `yii\pgsql\QueryBuilder::batchInsert()` may cause "undefined index" error (arkhamvm) +- Bug #8585: Fixed `yii\helpers\Html::activeTextarea()` does not allow value overriding via options (klimov-paul) +- Bug #8592: Fixed `yii\db\Command::getRawSql()` unable to parse params specified without colon (':') (klimov-paul) +- Bug #8593: Fixed `yii\db\ActiveQuery` produces incorrect SQL for aggregations, when `sql` field is set (klimov-paul) +- Bug #8595: Fixed `yii\rbac\DbManager::checkAccessFromCache()` to check against auth items loaded in cache recursively (achretien, qiangxue) +- Bug #8606: Fixed `yii\web\Response::xSendFile()` does not reset format (vyants) +- Bug #8627: Fixed `yii\db\Migration` produces incorrect results due to table schema caching (klimov-paul) +- Bug #8661: Fixed `yii.activeForm.js` scrolling to top (nkovacs) +- Bug #8684: Formatter ignored explicit decimal number settings when a default value is configured (leandrogehlen, cebe) +- Bug #8772: ActiveQuery failed removing duplicate records after join when the resultset did not contain the pk values e.g. after grouping (cebe) +- Bug #8844: Added a workaround for an oracle bug when fetching information about table constraints and filtering by `CONSTRAINT_TYPE` (nidgetgod) +- Bug #8900: Fixed determining active menu item with url-alias in route `\yii\widgets\Menu::isItemActive()` (demi) +- Bug #9006: Fixed bit column always returning true using certain version of PDO MySQL (stratoss, RusAlex, mj4444ru, samdark) +- Bug #9046: Fixed problem with endless error loop when an error occurred after sending a stream or file download response to the user (cebe) +- Bug #9059: Fixed PHP Notice in error handler view (dynasource, andrewnester, samdark) +- Bug #9063: Workaround for MySQL losing table case when adding index (sebathi) +- Bug #9076: Fixed `yii\filters\PageCache` not using the configured duration and dependency when caching the response data (kidol) +- Bug #9091: `UrlManager::createUrl()` did not create correct url when defaults were used, internal cache is now skipped in certain situations (cebe) +- Bug #9127, #9128: Fixed MSSQL `QueryBuilder::renameColumn()` and `QueryBuilder::renameTable()` escaping (sitawit) +- Bug #9161: Fixed `yii\web\Request` ignore `queryParams` when resolve request (zetamen) +- Bug: Fixed string comparison in `BaseActiveRecord::unlink()` which may result in wrong comparison result for hash valued primary keys starting with `0e` (cebe) +- Bug: Pass correct action name to `yii\console\Controller::options()` when default action was requested (cebe) +- Bug: Automatic garbage collection in `yii\caching\FileCache` was not triggered (kidol) +- Bug: Fixed missing stacktrace items on PHP syntax errors of "unexpected end of file" type (samdark) +- Enh #3335: Implemented `ColumnSchemaBuilder` (pana1990, vaseninm, samdark, cebe) +- Enh #5991: Added `updateMessages()` to `yii.activeForm.js` to support manually updating ActiveForm messages (nkovacs) +- Enh #6043: Specification for 'class' and 'style' in array format added to `yii\helpers\Html` (klimov-paul) +- Enh #6853: Console application will now register PHP constants for `STDIN`, `STDOUT`, and `STDERR` itself if they are not defined (cebe) +- Enh #7169: `yii\widgets\ActiveField` now uses corresponding methods for default parts rendering (klimov-paul) +- Enh #7259: Added `errorAttributes` parameter to ActiveForm `afterValidate` event. Made scrolling to first error optional (nkovacs) +- Enh #8070: `yii\console\controllers\MessageController` now sorts created messages, even if there is no new one, while saving to PHP file (klimov-paul) +- Enh #8286: `yii\console\controllers\MessageController` improved allowing extraction of nested translator calls (klimov-paul) +- Enh #8373: Check also `post_max_size` parameter in `yii\validators\FileValidator::getSizeLimit()` (maxxer) +- Enh #8415: `yii\helpers\Html` allows correct rendering of conditional comments containing `!IE` (salaros, klimov-paul) +- Enh #8444: Added `yii\widgets\LinkPager::$linkOptions` to allow configuring HTML attributes of the `a` tags (zinzinday) +- Enh #8486: Added support to automatically set the `maxlength` attribute for `Html::activeTextArea()` and `Html::activePassword()` (klimov-paul) +- Enh #8566: Added support for 'only' and 'except' options for `yii\web\AssetManager::publish()` (klimov-paul) +- Enh #8574: Added `yii\console\controllers\MessageController` support .pot file creation (pgaultier) +- Enh #8625: Added `markUnused` option to `yii\console\controllers\MessageController` (marius7383) +- Enh #8670: Added support for saving extra fields in session table for `yii\web\DbSession` (klimov-paul) +- Enh #8671: Extracted `yii\helpers\Html::escapeJsRegularExpression()` method from `yii\validators\RegularExpressionValidator` (silverfire, klimov-paul, samdark, qiangxue) +- Enh #8903: PostgreSQL `QueryBuilder::createIndex()` can now specify the index method to use (LAV45) +- Enh #8933: Yii is now able to properly handle HHVM fatal errors (dieend, samdark) +- Enh #9011: Allow `yii\widgets\MaskedInput` to produce an input tag of a custom type (TriAnMan) +- Enh #9038: Write warning to log in case `FileCache` fails to write into file (foccy) +- Enh #9072: `yii\web\ErrorAction` displays 404 error instead of blank page on direct access (klimov-paul) +- Enh #9149: Print directory migrationPath in a `yii migrate` command error. (RusAlex) +- Enh #9177: Added password hash cost setting to Security component (freezy-sk) +- Enh #9239: Better handling of `Json` errors (grzegorzkurtyka, samdark) +- Enh #9246: Added `yii\web\UrlRule::getParamRules()` (df2) +- Enh #9249: Added `hashCallback` in `yii\web\AssetManager` to allow custom hash generation for asset directory (petrabarus) +- Enh #9263: Avoid extra DB query in RBAC DbManager in case auth item name is empty (samdark) +- Enh #9268: Improved display of boolean parameters in logged SQL queries (arkhamvm, samdark) +- Enh: Improved Console helper progress bar ETA time estimation, updated only once per second to avoid flapping (cebe) +- Chg #6354: `ErrorHandler::logException()` will now log the whole exception object instead of only its string representation (cebe) +- Chg #8556: Extracted `yii\web\User::getAuthManager()` method (samdark) +- Chg #9181: `yii\helpers\BaseStringHelper::truncateHtml()` is now using `runtime` directory for `HTMLPurifier` cache (webdevsega) + + +2.0.5 July 11, 2015 +------------------- + +- Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept `/../` and `/./` (thejahweh, samdark) + + +2.0.4 May 10, 2015 +------------------ + +- Bug #5042: Use RETURNING for inserts for pgsql and oci to support PKs with a custom default value expression (nineinchnick, klimov-paul) +- Bug #6234: Fixed wrong table schema information for MSSQL when using multiple schemas (nineinchnick) +- Bug #6642: Fixed the bug that using confirmation dialog via `data-confirm` in an `ActiveForm` may cause the dialog to appear twice (pana1990, qiangxue) +- Bug #6871: Fixed the bug that using defaults and hostnames in URL rules may cause an out-of-range index issue (qiangxue) +- Bug #7036: Fixed `yii\helpers\Html::dropDownList()` overrides label specified at 'groups' option (aktec, klimov-paul) +- Bug #7473: Fixed `yii\console\controllers\AssetController` does not create missing folders for the target bundles (schmunk42, klimov-paul) +- Bug #7894: Fixed incorrect URL config processing at `yii\web\Application::handleRequest()` and `yii\widgets\Menu::items` if route element is not a first one (nkovacs, klimov-paul) - Bug #7529: Fixed `yii\web\Response::sendContentAsFile()` that was broken in 2.0.3 (samdark) - Bug #7603: Fixed escape characters in `FormatConverter` to work with unicode characters (maddoger, cebe) +- Bug #7656: Fixed `yii\rbac\DbManager::getRolesByUser()` and `yii\rbac\PhpManager::getRolesByUser()` to return roles only (samdark) +- Bug #7757: Fix fetching tables schema for oci and mysql when PDO::ATTR_CASE is set (nineinchnick) +- Bug #7761: Fixed formatting one digit month in date formatter using ICU format `L` (nkovacs) +- Bug #7775: Added more strict check on controller IDs when they are being used to create controller instances on Windows (Bhoft, qiangxue) +- Bug #7831: Add order when fetching database table names and constraints (nineinchnick) +- Bug #7846: Fixed `yii\base\Model` does not recognize scenario declared by rules using 'except' (klimov-paul) +- Bug #7847: `yii\db\ColumnSchema` was typecasting numerics to strings with incorrect decimal separator for some locales (nineinchnick) +- Bug #7861: Fixed `yii\helpers\VarDumper::export()` fails to export object containing `\Closure` (klimov-paul) +- Bug #7867: Fixed findUniqueIndexes not to perform any processing on unique index on function for pgsql (nineinchnick) +- Bug #7868: Fixed fetching columns definition and composite foreign keys for oci (nineinchnick) +- Bug #7868: Removed column's autoIncrement detection from oci (nineinchnick) +- Bug #7868: Fixed creating raw sql (for logging) by skipping object and resource params (nineinchnick) +- Bug #7868: Fixed Schema::getLastInsertID() by quoting sequence name (nineinchnick) +- Bug #7957: Removed extra `parseFloat()` call for the `compare` js validator (CthulhuDen) +- Bug #8012: Fixed fetching multiple relations between two tables for pgsql (nineinchnick) +- Bug #8014: Fixed setting incorrect form "action" property after submitting a form using a link with "data-method" and containing "action" among "data-params" (samdark) +- Bug #8032: `yii\rbac\PhpManager::updateItem()` was unable to rename item updated (ChristopheBrun, samdark) +- Bug #8036: Fixed `yii\log\Logger` unable to export session id (klimov-paul) +- Bug #8068: Fixed `yii\db\Query::count()` fails for query containing 'having' without 'group by' (klimov-paul) +- Bug #8073: Fixed `yii\data\ArrayDataProvider::getKeys()` return wrong when `yii\data\ArrayDataProvider::$allModels` contain integer key (mdmunir, klimov-paul) +- Bug #8082: Fixed `yii\db\BaseActiveRecord::getAttributeLabel()` return wrong label for related attribute, if several relations in chain share same name (klimov-paul) +- Bug #8149: Fixed `yii\db\BaseActiveRecord::updateCounters()` fails for new record saved with counter attribute not set (klimov-paul) +- Bug #8158: Avoid exception when detecting console screen size fails on windows (EliasZ) +- Bug #8165: Fixed `yii\db\ActiveRelationTrait::populateRelation()` fails when `link` refers to string convertable object attribute, like `\MongoId` (klimov-paul) +- Bug #8231: Configuration of GridView and DetailView where not preserved when used multiple times (cebe, idMolotov) +- Bug #8273: Fixed `yii\widgets\FragmentCache` when `enabled` is false (nkovacs) +- Bug #8291: Fixed numeric keys in $_GET transformed to 0-based, if 'pretty URL' enabled (quantum13, klimov-paul) +- Bug #5053: DateValidator is now more robust against different timezone settings (cebe) +- Bug (CVE-2015-3397): Added `Json::htmlEncode()` to support safer JSON data encoding in HTML code (samdark, Wojciech Janusz, Tomasz Tokarski) +- Enh #1468: Added ability to specify hints for model attributes via `attributeHints()` method (klimov-paul) +- Enh #3376: Added `yii\validators\EachValidator`, which allows validation of the array attributes (klimov-paul) +- Enh #5053: Added possibility to specify a format and time zone for the `timestampAttribute` of date validator making it fully usable for validating complete timestamps (cebe) +- Enh #6442: Improved error message on `FileHelper::createDirectory()` to include the path name of the directory (cebe) - Enh #6895: Added `ignoreCategories` config option for message command to ignore categories specified (samdark) +- Enh #6975: Pressing arrows while focused in inputs of Active Form with `validateOnType` enabled no longer triggers validation (slinstj) +- Enh #7409: Allow `yii\filters\auth\CompositeAuth::authMethods` to take authentication objects (fernandezekiel, qiangxue) +- Enh #7443: Allow specification of the `$key` as an array at `yii\helpers\ArrayHelper::getValue()` (Alex-Code) - Enh #7488: Added `StringHelper::explode` to perform explode with trimming and skipping of empty elements (SilverFire, nineinchnick, creocoder, samdark) +- Enh #7514: Added min/max validation to DateValidator (nkovacs) +- Enh #7515: Added support to use `indexBy()` together with `column()` in query builder (qiangxue) +- Enh #7530: Improved default values for `yii\data\Sort` link labels in a `ListView` when used with an `ActiveDataProvider` (cebe) +- Enh #7539: `yii\console\controllers\AssetController` provides dependency trace in case bundle circular dependency detected (klimov-paul) - Enh #7562: `yii help` now lists all sub-commands by default (callmez) - Enh #7571: HTTP status 500 and "An internal server error occurred." are now returned in case there was an exception in layout and `YII_DEBUG` is false (samdark) +- Enh #7636: `yii\web\Session::getHasSessionId()` uses a more lenient way to check if session ID is provided in URL (robsch) +- Enh #7637: Allow `yii\web\Request::validateCsrfToken()` to validate a manually provided token (miraage, qiangxue) +- Enh #7808: Adjusted Masked input to correctly generate the hash variable to store the plugin options (wbraganca) +- Enh #7833: Support (materialized) views and foreign tables along normal tables when fetching table schema (nineinchnick) +- Enh #7850: Added `yii\filters\PageCache::cacheCookies` and `cacheHeaders` to allow selectively caching cookies and HTTP headers (qiangxue) +- Enh #7867: Implemented findUniqueIndexes for oci and mssql (nineinchnick) +- Enh #7912: Added `aria-label` to ActionColumn buttons (LAV45, samdark) +- Enh #7915: Added `yii\i18n\Formatter::$numberFormatterSymbols` to allow setting custom symbols for the internally used IntlNumberFormatter, e.g. currency signs (cebe) +- Enh #7918: `yii\widgets\Pjax` got ability to avoid registering link/form handler via setting `false` to `$linkSelector`/`$formSelector` (usualdesigner, Alex-Code, samdark) +- Enh #7973: Added `Schema::getSchemaNames` method (nineinchnick) +- Enh #8027: Added support for using sub queries in simple Query WHERE conditions (cebe) +- Enh #8055: `yii\rest\UrlRule::extraPatterns` should take precedence over `patterns` (Agrumas) +- Enh #8064: Added ability to remove containing menu tag by setting `yii\widgets\Menu::$options['tag']` to `false` (kirsenn, samdark) +- Enh #8078: 'links' and 'meta' envelope names are now configurable at `yii\rest\Serializer` (arturf) +- Enh #8171: Allow the user to enforce the fileSize to allow sending files which are not seekable. Needed when using S3 Stream Wrapper (pgaultier) +- Enh #8179: Added parameter to define whether the comparison of `yii\db\BaseActiveRecord::isAttributeChanged()` method will be made as identical (thiagotalma) +- Enh #8194: Caching of the matched rules added to `yii\web\UrlManager::createUrl()` (laszlovl, klimov-paul) +- Enh #8268: Allow `QueryBuilder` to recognize more variations of `limit` and `offset` values (tino415, qiangxue) +- Enh: `yii\i18n\Formatter` now shows more information about errors which occured when formatting values (cebe) - Enh: Added `yii\helper\Console::wrapText()` method to wrap indented text by console window width and used it in `yii help` command (cebe) ======= @@ -257,6 +666,7 @@ Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept ` ======= - Bug #6404: advanced project template `Alert` widget was generating duplicate IDs in case of multiple flashes (SDKiller) >>>>>>> yiichina/master +- Bug #6404: advanced project template `Alert` widget was generating duplicate IDs in case of multiple flashes (SDKiller) - Bug #6557: Link URLs generated by `yii\widgets\Menu` are not encoded (qiangxue) - Bug #6632: `yii\di\Container::get()` did not handle config parameter correctly when it is passed as a constructor parameter (qiangxue) - Bug #6648: Added explicit type casting to avoid dblib issues on SQL Server 2014 (o-rey) @@ -857,6 +1267,7 @@ Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept ` ======= - Bug #1597: Added `enableAutoLogin` to basic and advanced project templates so "remember me" now works properly (samdark) >>>>>>> yiichina/master +- Bug #1597: Added `enableAutoLogin` to basic and advanced project templates so "remember me" now works properly (samdark) - Bug #1631: Charset is now explicitly set to UTF-8 when serving JSON (samdark) - Bug #1635: `yii\jui\SliderInput` wasn't properly initialized (samdark) - Bug #1659: MSSQL doesn't support limit (Ana1oliy) @@ -949,6 +1360,7 @@ Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept ` ======= - Enh #1633: Advanced project template now works with MongoDB by default (samdark) >>>>>>> yiichina/master +- Enh #1633: Advanced project template now works with MongoDB by default (samdark) - Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue) - Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue) - Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo) @@ -1016,6 +1428,7 @@ Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept ` ======= - Enh: Added `favicon.ico` and `robots.txt` to default project templates (samdark) >>>>>>> yiichina/master +- Enh: Added `favicon.ico` and `robots.txt` to default project templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) - Enh: Sort and Pagination can now create absolute URLs (cebe) @@ -1160,7 +1573,7 @@ Bug #9070 (CVE-2015-5467): Fixed `ViewAction::resolveViewName()` not to accept ` - New: Added `HtmlResponseFormatter` and `JsonResponseFormatter` (qiangxue) -2.0.0-alpha, December 1, 2013 +2.0.0-alpha December 1, 2013 ----------------------------- - Initial release. diff --git a/framework/README.md b/framework/README.md index ad874e7edb..c4318bf42c 100644 --- a/framework/README.md +++ b/framework/README.md @@ -3,7 +3,7 @@ Yii PHP Framework Version 2 This is the core framework code of [Yii 2](https://github.com/yiisoft/yii2#readme). -This repository is a git submodule of . +This repository is a read-only git subsplit of . Please submit issue reports and pull requests to the main repository. For license information check the [LICENSE](LICENSE.md)-file. @@ -16,10 +16,14 @@ Either run ``` <<<<<<< HEAD +<<<<<<< HEAD composer global require "fxp/composer-asset-plugin:1.0.0" ======= composer global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= +composer global require "fxp/composer-asset-plugin:~1.1.1" +>>>>>>> master composer require yiisoft/yii2 ``` diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 39cf5a7a73..4832e79194 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -5,17 +5,123 @@ Upgrading Instructions for Yii Framework v2 The following upgrading instructions are cumulative. That is, if you want to upgrade from version A to version C and there is -version B between A and C, you need to following the instructions +version B between A and C, you need to follow the instructions for both A and B. +Make sure you have global install of latest version of composer asset plugin as well as a stable version of composer: + +``` +php composer.phar self-update +php composer.phar global require "fxp/composer-asset-plugin:~1.1.1" +``` + +Upgrade from Yii 2.0.8 +---------------------- + +* Part of code from `yii\web\User::loginByCookie()` method was moved to new `getIdentityAndDurationFromCookie()` + and `removeIdentityCookie()` methods. If you override `loginByCookie()` method, update it in order use new methods. + +Upgrade from Yii 2.0.7 +---------------------- + +* The signature of `yii\helpers\BaseArrayHelper::index()` was changed. The method has got an extra optional parameter + `$groups`. + +* `yii\helpers\BaseArrayHelper` methods `isIn()` and `isSubset()` throw `\yii\base\InvalidParamException` + instead of `\InvalidArgumentException`. If you wrap calls of these methods in try/catch block, change expected + exception class. + +* `yii\rbac\ManagerInterface::canAddChild()` method was added. If you have custom backend for RBAC you need to implement + it. + +* The signature of `yii\web\User::loginRequired()` was changed. The method has got an extra optional parameter + `$checkAcceptHeader`. + +* The signature of `yii\db\ColumnSchemaBuilder::__construct()` was changed. The method has got an extra optional + parameter `$db`. In case you are instantiating this class yourself and using the `$config` parameter, you will need to + move it to the right by one. + +* String types in the MSSQL column schema map were upgraded to Unicode storage types. This will have no effect on + existing columns, but any new columns you generate via the migrations engine will now store data as Unicode. + +* Output buffering was introduced in the pair of `yii\widgets\ActiveForm::init()` and `::run()`. If you override any of + these methods, make sure that output buffer handling is not corrupted. If you call the parent implementation, when + overriding, everything should work fine. You should be doing that anyway. + +Upgrade from Yii 2.0.6 +---------------------- + +* Added new requirement: ICU Data version >= 49.1. Please, ensure that your environment has ICU data installed and + up to date to prevent unexpected behavior or crashes. This may not be the case on older systems e.g. running Debian Wheezy. + + > Tip: Use Yii2 Requirements checker for easy and fast check. Look for `requirements.php` in root of Basic and Advanced + templates (howto-comment is in head of the script). + +* The signature of `yii\helpers\BaseInflector::transliterate()` was changed. The method is now public and has an + extra optional parameter `$transliterator`. + +* In `yii\web\UrlRule` the `pattern` matching group names are being replaced with the placeholders on class + initialization to support wider range of allowed characters. Because of this change: + + - You are required to flush your application cache to remove outdated `UrlRule` serialized objects. + See the [Cache Flushing Guide](http://www.yiiframework.com/doc-2.0/guide-caching-data.html#cache-flushing) + - If you implement `parseRequest()` or `createUrl()` and rely on parameter names, call `substitutePlaceholderNames()` + in order to replace temporary IDs with parameter names after doing matching. + +* The context of `yii.confirm` JavaScript function was changed from `yii` object to the DOM element which triggered + the event. + + - If you overrode the `yii.confirm` function and accessed the `yii` object through `this`, you must access it + with global variable `yii` instead. + +* Traversable objects are now formatted as arrays in XML response to support SPL objects and Generators. Previous + behavior could be turned on by setting `XmlResponseFormatter::$useTraversableAsArray` to `false`. + +* If you've implemented `yii\rbac\ManagerInterface` you need to implement additional method `getUserIdsByRole($roleName)`. + +* If you're using ApcCache with APCu, set `useApcu` to `true` in the component config. + +* The `yii\behaviors\SluggableBehavior` class has been refactored to make it more reusable. + Added new `protected` methods: + + - `isSlugNeeded()` + - `makeUnique()` + + The visibility of the following Methods has changed from `private` to `protected`: + + - `validateSlug()` + - `generateUniqueSlug()` + +* The `yii\console\controllers\MessageController` class has been refactored to be better configurable and now also allows + setting a lot of configuration options via command line. If you extend from this class, make sure it works as expected after + these changes. + +Upgrade from Yii 2.0.5 +---------------------- + +* The signature of the following methods in `yii\console\controllers\MessageController` has changed. They have an extra parameter `$markUnused`. + - `saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages, $markUnused)` + - `saveMessagesToPHP($messages, $dirName, $overwrite, $removeUnused, $sort, $markUnused)` + - `saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category, $markUnused)` + - `saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog, $markUnused)` + +Upgrade from Yii 2.0.4 +---------------------- + +Upgrading from 2.0.4 to 2.0.5 does not require any changes. + Upgrade from Yii 2.0.3 ---------------------- +<<<<<<< HEAD <<<<<<< HEAD * Updated dependency to `cebe/markdown` to version `1.0.x`. ======= * Updated dependency to `cebe/markdown` to version `1.1.x`. >>>>>>> yiichina/master +======= +* Updated dependency to `cebe/markdown` to version `1.1.x`. +>>>>>>> master If you need stick with 1.0.x, you can specify that in your `composer.json` by adding the following line in the `require` section: @@ -78,7 +184,6 @@ Upgrade from Yii 2.0 RC Quoting of values is broken in prior versions and Yii has no reliable way to work around this issue. A workaround that may have worked before has been removed in this release because it was not reliable. - Upgrade from Yii 2.0 Beta ------------------------- @@ -86,11 +191,15 @@ Upgrade from Yii 2.0 Beta the composer-asset-plugin, *before* you update your project: ``` +<<<<<<< HEAD <<<<<<< HEAD php composer.phar global require "fxp/composer-asset-plugin:1.0.0" ======= php composer.phar global require "fxp/composer-asset-plugin:~1.0.0" >>>>>>> yiichina/master +======= + php composer.phar global require "fxp/composer-asset-plugin:~1.0.0" +>>>>>>> master ``` You also need to add the following code to your project's `composer.json` file: @@ -192,11 +301,15 @@ Upgrade from Yii 2.0 Beta ]; ``` +<<<<<<< HEAD <<<<<<< HEAD > Note: If you are using the `Advanced Application Template` you should not add this configuration to `common/config` ======= > Note: If you are using the `Advanced Project Template` you should not add this configuration to `common/config` >>>>>>> yiichina/master +======= + > Note: If you are using the `Advanced Project Template` you should not add this configuration to `common/config` +>>>>>>> master or `console/config` because the console application doesn't have to deal with CSRF and uses its own request that doesn't have `cookieValidationKey` property. diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 622f1dc842..c992d7e575 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -40,11 +40,12 @@ /** * afterValidate event is triggered after validating the whole form. * The signature of the event handler should be: - * function (event, messages) + * function (event, messages, errorAttributes) * where * - event: an Event object. * - messages: an associative array with keys being attribute IDs and values being error message arrays * for the corresponding attributes. + * - errorAttributes: an array of attributes that have validation errors. Please refer to attributeDefaults for the structure of this parameter. */ afterValidate: 'afterValidate', /** @@ -97,7 +98,7 @@ * where * - event: an Event object. * - jqXHR: a jqXHR object - * - settings: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). + * - textStatus: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). */ ajaxComplete: 'ajaxComplete' }; @@ -121,7 +122,9 @@ // the type of data that you're expecting back from the server ajaxDataType: 'json', // the URL for performing AJAX-based validation. If not set, it will use the the form's action - validationUrl: undefined + validationUrl: undefined, + // whether to scroll to first visible error after validation. + scrollToError: true }; // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well @@ -132,7 +135,7 @@ name: undefined, // the jQuery selector of the container of the input field container: undefined, - // the jQuery selector of the input field under the context of the container + // the jQuery selector of the input field under the context of the form input: undefined, // the jQuery selector of the error tag under the context of the container error: '.help-block', @@ -158,6 +161,24 @@ value: undefined }; + + var submitDefer; + + var setSubmitFinalizeDefer = function($form) { + submitDefer = $.Deferred(); + $form.data('yiiSubmitFinalizePromise', submitDefer.promise()); + }; + + // finalize yii.js $form.submit + var submitFinalize = function($form) { + if(submitDefer) { + submitDefer.resolve(); + submitDefer = undefined; + $form.removeData('yiiSubmitFinalizePromise'); + } + }; + + var methods = { init: function (attributes, options) { return this.each(function () { @@ -168,11 +189,15 @@ var settings = $.extend({}, defaults, options || {}); if (settings.validationUrl === undefined) { +<<<<<<< HEAD <<<<<<< HEAD settings.validationUrl = $form.prop('action'); ======= settings.validationUrl = $form.attr('action'); >>>>>>> yiichina/master +======= + settings.validationUrl = $form.attr('action'); +>>>>>>> master } $.each(attributes, function (i) { @@ -184,7 +209,8 @@ settings: settings, attributes: attributes, submitting: false, - validated: false + validated: false, + target: $form.attr('target') }); /** @@ -277,31 +303,34 @@ $form.trigger(event, [messages, deferreds]); if (event.result === false) { data.submitting = false; + submitFinalize($form); return; } } // client-side validation $.each(data.attributes, function () { - this.cancelled = false; - // perform validation only if the form is being submitted or if an attribute is pending validation - if (data.submitting || this.status === 2 || this.status === 3) { - var msg = messages[this.id]; - if (msg === undefined) { - msg = []; - messages[this.id] = msg; - } - var event = $.Event(events.beforeValidateAttribute); - $form.trigger(event, [this, msg, deferreds]); - if (event.result !== false) { - if (this.validate) { - this.validate(this, getValue($form, this), msg, deferreds, $form); + if (!$(this.input).is(":disabled")) { + this.cancelled = false; + // perform validation only if the form is being submitted or if an attribute is pending validation + if (data.submitting || this.status === 2 || this.status === 3) { + var msg = messages[this.id]; + if (msg === undefined) { + msg = []; + messages[this.id] = msg; } - if (this.enableAjaxValidation) { - needAjaxValidation = true; + var event = $.Event(events.beforeValidateAttribute); + $form.trigger(event, [this, msg, deferreds]); + if (event.result !== false) { + if (this.validate) { + this.validate(this, getValue($form, this), msg, deferreds, $form); + } + if (this.enableAjaxValidation) { + needAjaxValidation = true; + } + } else { + this.cancelled = true; } - } else { - this.cancelled = true; } } }); @@ -314,8 +343,9 @@ delete messages[i]; } } - if (needAjaxValidation) { + if ($.isEmptyObject(messages) && needAjaxValidation) { var $button = data.submitObject, +<<<<<<< HEAD <<<<<<< HEAD extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); if ($button && $button.length && $button.prop('name')) { @@ -333,6 +363,15 @@ url: data.settings.validationUrl, type: $form.attr('method'), >>>>>>> yiichina/master +======= + extData = '&' + data.settings.ajaxParam + '=' + $form.attr('id'); + if ($button && $button.length && $button.attr('name')) { + extData += '&' + $button.attr('name') + '=' + $button.attr('value'); + } + $.ajax({ + url: data.settings.validationUrl, + type: $form.attr('method'), +>>>>>>> master data: $form.serialize() + extData, dataType: data.settings.ajaxDataType, complete: function (jqXHR, textStatus) { @@ -355,6 +394,7 @@ }, error: function () { data.submitting = false; + submitFinalize($form); } }); } else if (data.submitting) { @@ -373,15 +413,21 @@ data = $form.data('yiiActiveForm'); if (data.validated) { + // Second submit's call (from validate/updateInputs) data.submitting = false; var event = $.Event(events.beforeSubmit); $form.trigger(event); if (event.result === false) { data.validated = false; + submitFinalize($form); return false; } + updateHiddenButton($form); return true; // continue submitting the form since validation passes } else { + // First submit's call (from yii.js/handleAction) - execute validating + setSubmitFinalizeDefer($form); + if (data.settings.timer !== undefined) { clearTimeout(data.settings.timer); } @@ -412,7 +458,40 @@ }); $form.find(data.settings.errorSummary).hide().find('ul').html(''); }, 1); + }, + + /** + * Updates error messages, input containers, and optionally summary as well. + * If an attribute is missing from messages, it is considered valid. + * @param messages array the validation error messages, indexed by attribute IDs + * @param summary whether to update summary as well. + */ + updateMessages: function (messages, summary) { + var $form = $(this); + var data = $form.data('yiiActiveForm'); + $.each(data.attributes, function () { + updateInput($form, this, messages); + }); + if (summary) { + updateSummary($form, messages); + } + }, + + /** + * Updates error messages and input container of a single attribute. + * If messages is empty, the attribute is considered valid. + * @param id attribute ID + * @param messages array with error messages + */ + updateAttribute: function(id, messages) { + var attribute = methods.find.call(this, id); + if (attribute != undefined) { + var msg = {}; + msg[id] = messages; + updateInput($(this), attribute, msg); + } } + }; var watchAttribute = function ($form, attribute) { @@ -425,19 +504,25 @@ if (attribute.validateOnBlur) { $input.on('blur.yiiActiveForm', function () { if (attribute.status == 0 || attribute.status == 1) { - validateAttribute($form, attribute, !attribute.status); + validateAttribute($form, attribute, true); } }); } if (attribute.validateOnType) { +<<<<<<< HEAD <<<<<<< HEAD $input.on('keyup.yiiActiveForm', function () { ======= +======= +>>>>>>> master $input.on('keyup.yiiActiveForm', function (e) { if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1 ) { return; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master if (attribute.value !== getValue($form, attribute)) { validateAttribute($form, attribute, false, attribute.validationDelay); } @@ -481,7 +566,7 @@ methods.validate.call($form); }, validationDelay ? validationDelay : 200); }; - + /** * Returns an array prototype with a shortcut method for adding a new deferred. * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()``` @@ -505,26 +590,31 @@ var data = $form.data('yiiActiveForm'); if (submitting) { - var errorInputs = []; + var errorAttributes = []; $.each(data.attributes, function () { - if (!this.cancelled && updateInput($form, this, messages)) { - errorInputs.push(this.input); + if (!$(this.input).is(":disabled") && !this.cancelled && updateInput($form, this, messages)) { + errorAttributes.push(this); } }); - $form.trigger(events.afterValidate, [messages]); + $form.trigger(events.afterValidate, [messages, errorAttributes]); updateSummary($form, messages); - if (errorInputs.length) { - var top = $form.find(errorInputs.join(',')).first().closest(':visible').offset().top; - var wtop = $(window).scrollTop(); - if (top < wtop || top > wtop + $(window).height) { - $(window).scrollTop(top); + if (errorAttributes.length) { + if (data.settings.scrollToError) { + var top = $form.find($.map(errorAttributes, function(attribute) { + return attribute.input; + }).join(',')).first().closest(':visible').offset().top; + var wtop = $(window).scrollTop(); + if (top < wtop || top > wtop + $(window).height()) { + $(window).scrollTop(top); + } } data.submitting = false; } else { data.validated = true; +<<<<<<< HEAD var $button = data.submitObject || $form.find(':submit:first'); // TODO: if the submission is caused by "change" event, it will not work <<<<<<< HEAD @@ -550,6 +640,16 @@ } $form.submit(); >>>>>>> yiichina/master +======= + var buttonTarget = data.submitObject ? data.submitObject.attr('formtarget') : null; + if (buttonTarget) { + // set target attribute to form tag before submit + $form.attr('target', buttonTarget); + } + $form.submit(); + // restore original target attribute value + $form.attr('target', data.target); +>>>>>>> master } } else { $.each(data.attributes, function () { @@ -558,6 +658,30 @@ } }); } + submitFinalize($form); + }; + + /** + * Updates hidden field that represents clicked submit button. + * @param $form the form jQuery object. + */ + var updateHiddenButton = function ($form) { + var data = $form.data('yiiActiveForm'); + var $button = data.submitObject || $form.find(':submit:first'); + // TODO: if the submission is caused by "change" event, it will not work + if ($button.length && $button.attr('type') == 'submit' && $button.attr('name')) { + // simulate button input value + var $hiddenButton = $('input[type="hidden"][name="' + $button.attr('name') + '"]', $form); + if (!$hiddenButton.length) { + $('').attr({ + type: 'hidden', + name: $button.attr('name'), + value: $button.attr('value') + }).appendTo($form); + } else { + $hiddenButton.attr('value', $button.attr('value')); + } + } }; /** @@ -628,6 +752,7 @@ var getValue = function ($form, attribute) { var $input = findInput($form, attribute); +<<<<<<< HEAD <<<<<<< HEAD var type = $input.prop('type'); if (type === 'checkbox' || type === 'radio') { @@ -641,6 +766,13 @@ if (!$realInput.length) { $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]'); >>>>>>> yiichina/master +======= + var type = $input.attr('type'); + if (type === 'checkbox' || type === 'radio') { + var $realInput = $input.filter(':checked'); + if (!$realInput.length) { + $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]'); +>>>>>>> master } return $realInput.val(); } else { diff --git a/framework/assets/yii.gridView.js b/framework/assets/yii.gridView.js index 16c681ca1f..a2b2ced08c 100644 --- a/framework/assets/yii.gridView.js +++ b/framework/assets/yii.gridView.js @@ -49,17 +49,26 @@ */ afterFilter: 'afterFilter' }; - + var methods = { init: function (options) { return this.each(function () { var $e = $(this); var settings = $.extend({}, defaults, options || {}); +<<<<<<< HEAD <<<<<<< HEAD gridData[$e.prop('id')] = {settings: settings}; ======= gridData[$e.attr('id')] = {settings: settings}; >>>>>>> yiichina/master +======= + var id = $e.attr('id'); + if (gridData[id] === undefined) { + gridData[id] = {}; + } + + gridData[id] = $.extend(gridData[id], {settings: settings}); +>>>>>>> master var enterPressed = false; $(document).off('change.yiiGridView keydown.yiiGridView', settings.filterSelector) @@ -87,19 +96,39 @@ applyFilter: function () { var $grid = $(this), event; +<<<<<<< HEAD <<<<<<< HEAD var settings = gridData[$grid.prop('id')].settings; ======= var settings = gridData[$grid.attr('id')].settings; >>>>>>> yiichina/master +======= + var settings = gridData[$grid.attr('id')].settings; +>>>>>>> master var data = {}; $.each($(settings.filterSelector).serializeArray(), function () { - data[this.name] = this.value; + if (!(this.name in data)) { + data[this.name] = []; + } + data[this.name].push(this.value); }); + var namesInFilter = Object.keys(data); + $.each(yii.getQueryParams(settings.filterUrl), function (name, value) { - if (data[name] === undefined) { - data[name] = value; + if (namesInFilter.indexOf(name) === -1 && namesInFilter.indexOf(name.replace(/\[\]$/, '')) === -1) { + if (!$.isArray(value)) { + value = [value]; + } + if (!(name in data)) { + data[name] = value; + } else { + $.each(value, function (i, val) { + if ($.inArray(val, data[name])) { + data[name].push(val); + } + }); + } } }); @@ -107,11 +136,19 @@ var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos); $grid.find('form.gridview-filter-form').remove(); - var $form = $('').appendTo($grid); - $.each(data, function (name, value) { - $form.append($('').attr('name', name).val(value)); + var $form = $('
    ', { + action: url, + method: 'get', + 'class': 'gridview-filter-form', + style: 'display:none', + 'data-pjax': '' + }).appendTo($grid); + $.each(data, function (name, values) { + $.each(values, function (index, value) { + $form.append($('').attr({type: 'hidden', name: name, value: value})); + }); }); - + event = $.Event(gridEvents.beforeFilter); $grid.trigger(event); if (event.result === false) { @@ -119,19 +156,26 @@ } $form.submit(); - + $grid.trigger(gridEvents.afterFilter); }, setSelectionColumn: function (options) { var $grid = $(this); +<<<<<<< HEAD <<<<<<< HEAD var id = $(this).prop('id'); ======= var id = $(this).attr('id'); >>>>>>> yiichina/master +======= + var id = $(this).attr('id'); + if (gridData.id === undefined) { + gridData[id] = {}; + } +>>>>>>> master gridData[id].selectionColumn = options.name; - if (!options.multiple) { + if (!options.multiple || !options.checkAll) { return; } var checkAll = "#" + id + " input[name='" + options.checkAll + "']"; @@ -147,11 +191,15 @@ getSelectedRows: function () { var $grid = $(this); +<<<<<<< HEAD <<<<<<< HEAD var data = gridData[$grid.prop('id')]; ======= var data = gridData[$grid.attr('id')]; >>>>>>> yiichina/master +======= + var data = gridData[$grid.attr('id')]; +>>>>>>> master var keys = []; if (data.selectionColumn) { $grid.find("input[name='" + data.selectionColumn + "']:checked").each(function () { @@ -169,11 +217,15 @@ }, data: function () { +<<<<<<< HEAD <<<<<<< HEAD var id = $(this).prop('id'); ======= var id = $(this).attr('id'); >>>>>>> yiichina/master +======= + var id = $(this).attr('id'); +>>>>>>> master return gridData[id]; } }; diff --git a/framework/assets/yii.js b/framework/assets/yii.js index 6a10ef319f..09e2f7d737 100644 --- a/framework/assets/yii.js +++ b/framework/assets/yii.js @@ -16,7 +16,7 @@ * * A module may be structured as follows: * - * ~~~ + * ```javascript * yii.sample = (function($) { * var pub = { * // whether this module is currently active. If false, init() will not be called for this module @@ -33,7 +33,7 @@ * * return pub; * })(jQuery); - * ~~~ + * ``` * * Using this structure, you can define public and private functions/properties for a module. * Private functions/properties are only visible within the module, while public functions/properties @@ -61,22 +61,30 @@ yii = (function ($) { * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled. */ getCsrfParam: function () { +<<<<<<< HEAD <<<<<<< HEAD return $('meta[name=csrf-param]').prop('content'); ======= return $('meta[name=csrf-param]').attr('content'); >>>>>>> yiichina/master +======= + return $('meta[name=csrf-param]').attr('content'); +>>>>>>> master }, /** * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled. */ getCsrfToken: function () { +<<<<<<< HEAD <<<<<<< HEAD return $('meta[name=csrf-token]').prop('content'); ======= return $('meta[name=csrf-token]').attr('content'); >>>>>>> yiichina/master +======= + return $('meta[name=csrf-token]').attr('content'); +>>>>>>> master }, /** @@ -86,6 +94,7 @@ yii = (function ($) { * @param value the CSRF token value */ setCsrfToken: function (name, value) { +<<<<<<< HEAD <<<<<<< HEAD $('meta[name=csrf-param]').prop('content', name); $('meta[name=csrf-token]').prop('content', value) @@ -93,6 +102,10 @@ yii = (function ($) { $('meta[name=csrf-param]').attr('content', name); $('meta[name=csrf-token]').attr('content', value) >>>>>>> yiichina/master +======= + $('meta[name=csrf-param]').attr('content', name); + $('meta[name=csrf-token]').attr('content', value); +>>>>>>> master }, /** @@ -156,16 +169,59 @@ yii = (function ($) { * * @param $e the jQuery representation of the element */ - handleAction: function ($e) { - var method = $e.data('method'), - $form = $e.closest('form'), + handleAction: function ($e, event) { + var $form = $e.attr('data-form') ? $('#' + $e.attr('data-form')) : $e.closest('form'), + method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'), action = $e.attr('href'), - params = $e.data('params'); + params = $e.data('params'), + pjax = $e.data('pjax'), + pjaxPushState = !!$e.data('pjax-push-state'), + pjaxReplaceState = !!$e.data('pjax-replace-state'), + pjaxTimeout = $e.data('pjax-timeout'), + pjaxScrollTo = $e.data('pjax-scrollto'), + pjaxPushRedirect = $e.data('pjax-push-redirect'), + pjaxReplaceRedirect = $e.data('pjax-replace-redirect'), + pjaxSkipOuterContainers = $e.data('pjax-skip-outer-containers'), + pjaxContainer, + pjaxOptions = {}; + + if (pjax !== undefined && $.support.pjax) { + if ($e.data('pjax-container')) { + pjaxContainer = $e.data('pjax-container'); + } else { + pjaxContainer = $e.closest('[data-pjax-container=""]'); + } + // default to body if pjax container not found + if (!pjaxContainer.length) { + pjaxContainer = $('body'); + } + pjaxOptions = { + container: pjaxContainer, + push: pjaxPushState, + replace: pjaxReplaceState, + scrollTo: pjaxScrollTo, + pushRedirect: pjaxPushRedirect, + replaceRedirect: pjaxReplaceRedirect, + pjaxSkipOuterContainers: pjaxSkipOuterContainers, + timeout: pjaxTimeout, + originalEvent: event, + originalTarget: $e + } + } if (method === undefined) { if (action && action != '#') { - window.location = action; + if (pjax !== undefined && $.support.pjax) { + $.pjax.click(event, pjaxOptions); + } else { + window.location = action; + } } else if ($e.is(':submit') && $form.length) { + if (pjax !== undefined && $.support.pjax) { + $form.on('submit',function(e){ + $.pjax.submit(e, pjaxOptions); + }) + } $form.trigger('submit'); } return; @@ -176,6 +232,7 @@ yii = (function ($) { if (!action || !action.match(/(^\/|:\/\/)/)) { action = window.location.href; } +<<<<<<< HEAD $form = $('
    '); <<<<<<< HEAD $form.prop('action', action); @@ -184,17 +241,21 @@ yii = (function ($) { $form.attr('action', action); var target = $e.attr('target'); >>>>>>> yiichina/master +======= + $form = $('
    ', {method: method, action: action}); + var target = $e.attr('target'); +>>>>>>> master if (target) { $form.attr('target', target); } if (!method.match(/(get|post)/i)) { - $form.append(''); + $form.append($('', {name: '_method', value: method, type: 'hidden'})); method = 'POST'; } if (!method.match(/(get|head|options)/i)) { var csrfParam = pub.getCsrfParam(); if (csrfParam) { - $form.append(''); + $form.append($('', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'})); } } $form.hide().appendTo('body'); @@ -209,10 +270,11 @@ yii = (function ($) { // temporarily add hidden inputs according to data-params if (params && $.isPlainObject(params)) { $.each(params, function (idx, obj) { - $form.append(''); + $form.append($('').attr({name: idx, value: obj, type: 'hidden'})); }); } +<<<<<<< HEAD <<<<<<< HEAD var oldMethod = $form.prop('method'); $form.prop('method', method); @@ -242,17 +304,40 @@ yii = (function ($) { } $form.attr('method', oldMethod); >>>>>>> yiichina/master - - // remove the temporarily added hidden inputs - if (params && $.isPlainObject(params)) { - $.each(params, function (idx, obj) { - $('input[name="' + idx + '"]', $form).remove(); - }); +======= + var oldMethod = $form.attr('method'); + $form.attr('method', method); + var oldAction = null; + if (action && action != '#') { + oldAction = $form.attr('action'); + $form.attr('action', action); } - - if (newForm) { - $form.remove(); + if (pjax !== undefined && $.support.pjax) { + $form.on('submit',function(e){ + $.pjax.submit(e, pjaxOptions); + }) } + $form.trigger('submit'); + $.when($form.data('yiiSubmitFinalizePromise')).then( + function () { + if (oldAction != null) { + $form.attr('action', oldAction); + } + $form.attr('method', oldMethod); +>>>>>>> master + + // remove the temporarily added hidden inputs + if (params && $.isPlainObject(params)) { + $.each(params, function (idx, obj) { + $('input[name="' + idx + '"]', $form).remove(); + }); + } + + if (newForm) { + $form.remove(); + } + } + ); }, getQueryParams: function (url) { @@ -260,12 +345,28 @@ yii = (function ($) { if (pos < 0) { return {}; } - var qs = url.substring(pos + 1).split('&'); - for (var i = 0, result = {}; i < qs.length; i++) { - qs[i] = qs[i].split('='); - result[decodeURIComponent(qs[i][0])] = decodeURIComponent(qs[i][1]); + + var pairs = url.substring(pos + 1).split('#')[0].split('&'), + params = {}, + pair, + i; + + for (i = 0; i < pairs.length; i++) { + pair = pairs[i].split('='); + var name = decodeURIComponent(pair[0]); + var value = decodeURIComponent(pair[1]); + if (name.length) { + if (params[name] !== undefined) { + if (!$.isArray(params[name])) { + params[name] = [params[name]]; + } + params[name].push(value || ''); + } else { + params[name] = value || ''; + } + } } - return result; + return params; }, initModule: function (module) { @@ -292,7 +393,7 @@ yii = (function ($) { function initRedirectHandler() { // handle AJAX redirection $(document).ajaxComplete(function (event, xhr, settings) { - var url = xhr.getResponseHeader('X-Redirect'); + var url = xhr && xhr.getResponseHeader('X-Redirect'); if (url) { window.location = url; } @@ -313,18 +414,19 @@ yii = (function ($) { var handler = function (event) { var $this = $(this), method = $this.data('method'), - message = $this.data('confirm'); + message = $this.data('confirm'), + form = $this.data('form'); - if (method === undefined && message === undefined) { + if (method === undefined && message === undefined && form === undefined) { return true; } if (message !== undefined) { - pub.confirm(message, function () { - pub.handleAction($this); + $.proxy(pub.confirm, this)(message, function () { + pub.handleAction($this, event); }); } else { - pub.handleAction($this); + pub.handleAction($this, event); } event.stopImmediatePropagation(); return false; @@ -337,6 +439,7 @@ yii = (function ($) { function initScriptFilter() { var hostInfo = location.protocol + '//' + location.host; + var loadedScripts = $('script[src]').map(function () { return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src; }).toArray(); @@ -345,14 +448,15 @@ yii = (function ($) { if (options.dataType == 'jsonp') { return; } + var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url; if ($.inArray(url, loadedScripts) === -1) { loadedScripts.push(url); } else { - var found = $.inArray(url, $.map(pub.reloadableScripts, function (script) { - return script.charAt(0) === '/' ? hostInfo + script : script; - })) !== -1; - if (!found) { + var isReloadable = $.inArray(url, $.map(pub.reloadableScripts, function (script) { + return script.charAt(0) === '/' ? hostInfo + script : script; + })) !== -1; + if (!isReloadable) { xhr.abort(); } } @@ -379,3 +483,4 @@ yii = (function ($) { jQuery(document).ready(function () { yii.initModule(yii); }); + diff --git a/framework/assets/yii.validation.js b/framework/assets/yii.validation.js index f9dc81672b..2edc22dbbc 100644 --- a/framework/assets/yii.validation.js +++ b/framework/assets/yii.validation.js @@ -36,7 +36,7 @@ yii.validation = (function ($) { } }, - boolean: function (value, messages, options) { + 'boolean': function (value, messages, options) { if (options.skipOnEmpty && pub.isEmpty(value)) { return; } @@ -68,17 +68,17 @@ yii.validation = (function ($) { pub.addMessage(messages, options.notEqual, value); } }, - + file: function (attribute, messages, options) { var files = getUploadedFiles(attribute, messages, options); $.each(files, function (i, file) { validateFile(file, messages, options); }); }, - + image: function (attribute, messages, options, deferred) { var files = getUploadedFiles(attribute, messages, options); - + $.each(files, function (i, file) { validateFile(file, messages, options); @@ -90,45 +90,45 @@ yii.validation = (function ($) { var def = $.Deferred(), fr = new FileReader(), img = new Image(); - + img.onload = function () { if (options.minWidth && this.width < options.minWidth) { messages.push(options.underWidth.replace(/\{file\}/g, file.name)); } - + if (options.maxWidth && this.width > options.maxWidth) { messages.push(options.overWidth.replace(/\{file\}/g, file.name)); } - + if (options.minHeight && this.height < options.minHeight) { messages.push(options.underHeight.replace(/\{file\}/g, file.name)); } - + if (options.maxHeight && this.height > options.maxHeight) { messages.push(options.overHeight.replace(/\{file\}/g, file.name)); } def.resolve(); }; - + img.onerror = function () { messages.push(options.notImage.replace(/\{file\}/g, file.name)); def.resolve(); }; - + fr.onload = function () { img.src = fr.result; }; - + // Resolve deferred if there was error while reading data fr.onerror = function () { def.resolve(); }; - + fr.readAsDataURL(file); - + deferred.push(def); }); - + }, number: function (value, messages, options) { @@ -161,7 +161,7 @@ yii.validation = (function ($) { var inArray = true; - $.each($.isArray(value) ? value : [value], function(i, v) { + $.each($.isArray(value) ? value : [value], function (i, v) { if ($.inArray(v, options.range) == -1) { inArray = false; return false; @@ -192,17 +192,30 @@ yii.validation = (function ($) { var valid = true; - if (options.enableIDN) { - var regexp = /^(.*?)$/, - matches = regexp.exec(value); - if (matches === null) { + + var regexp = /^((?:"?([^"]*)"?\s)?)(?:\s+)?(?:(]+))(>?))$/, + matches = regexp.exec(value); + + if (matches === null) { + valid = false + } else { + if (options.enableIDN) { + matches[5] = punycode.toASCII(matches[5]); + matches[6] = punycode.toASCII(matches[6]); + + value = matches[1] + matches[3] + matches[5] + '@' + matches[6] + matches[7]; + } + + if (matches[5].length > 64) { + valid = false; + } else if ((matches[5] + '@' + matches[6]).length > 254) { valid = false; } else { - value = matches[1] + punycode.toASCII(matches[2]) + '@' + punycode.toASCII(matches[3]) + matches[4]; + valid = value.match(options.pattern) || (options.allowName && value.match(options.fullPattern)); } } - if (!valid || !(value.match(options.pattern) || (options.allowName && value.match(options.fullPattern)))) { + if (!valid) { pub.addMessage(messages, options.message, value); } }, @@ -237,8 +250,10 @@ yii.validation = (function ($) { var $input = $form.find(attribute.input); var value = $input.val(); if (!options.skipOnEmpty || !pub.isEmpty(value)) { - $input.val($.trim(value)); + value = $.trim(value); + $input.val(value); } + return value; }, captcha: function (value, messages, options) { @@ -292,16 +307,21 @@ yii.validation = (function ($) { valid = value !== compareValue; break; case '>': +<<<<<<< HEAD <<<<<<< HEAD valid = parseFloat(value) > parseFloat(compareValue); +======= + valid = value > compareValue; +>>>>>>> master break; case '>=': - valid = parseFloat(value) >= parseFloat(compareValue); + valid = value >= compareValue; break; case '<': - valid = parseFloat(value) < parseFloat(compareValue); + valid = value < compareValue; break; case '<=': +<<<<<<< HEAD valid = parseFloat(value) <= parseFloat(compareValue); ======= valid = value > compareValue; @@ -315,6 +335,9 @@ yii.validation = (function ($) { case '<=': valid = value <= compareValue; >>>>>>> yiichina/master +======= + valid = value <= compareValue; +>>>>>>> master break; default: valid = false; @@ -324,6 +347,54 @@ yii.validation = (function ($) { if (!valid) { pub.addMessage(messages, options.message, value); } + }, + + ip: function (value, messages, options) { + var getIpVersion = function (value) { + return value.indexOf(':') === -1 ? 4 : 6; + }; + + var negation = null, cidr = null; + + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + var matches = new RegExp(options.ipParsePattern).exec(value); + if (matches) { + negation = matches[1] || null; + value = matches[2]; + cidr = matches[4] || null; + } + + if (options.subnet === true && cidr === null) { + pub.addMessage(messages, options.messages.noSubnet, value); + return; + } + if (options.subnet === false && cidr !== null) { + pub.addMessage(messages, options.messages.hasSubnet, value); + return; + } + if (options.negation === false && negation !== null) { + pub.addMessage(messages, options.messages.message, value); + return; + } + + if (getIpVersion(value) == 6) { + if (!options.ipv6) { + pub.addMessage(messages, options.messages.ipv6NotAllowed, value); + } + if (!(new RegExp(options.ipv6Pattern)).test(value)) { + pub.addMessage(messages, options.messages.message, value); + } + } else { + if (!options.ipv4) { + pub.addMessage(messages, options.messages.ipv4NotAllowed, value); + } + if (!(new RegExp(options.ipv4Pattern)).test(value)) { + pub.addMessage(messages, options.messages.message, value); + } + } } }; @@ -332,7 +403,7 @@ yii.validation = (function ($) { if (typeof File === "undefined") { return []; } - + var files = $(attribute.input).get(0).files; if (!files) { messages.push(options.message); @@ -372,7 +443,7 @@ yii.validation = (function ($) { } if (options.mimeTypes && options.mimeTypes.length > 0) { - if (!~options.mimeTypes.indexOf(file.type)) { + if (!validateMimeType(options.mimeTypes, file.type)) { messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name)); } } @@ -386,5 +457,15 @@ yii.validation = (function ($) { } } + function validateMimeType(mimeTypes, fileType) { + for (var i = 0, len = mimeTypes.length; i < len; i++) { + if (new RegExp(mimeTypes[i]).test(fileType)) { + return true; + } + } + + return false; + } + return pub; })(jQuery); diff --git a/framework/base/Action.php b/framework/base/Action.php index 11b56ba7aa..2c0ff1b23f 100644 --- a/framework/base/Action.php +++ b/framework/base/Action.php @@ -12,8 +12,8 @@ use Yii; /** * Action is the base class for all controller action classes. * - * Action provides a way to divide a complex controller into - * smaller actions in separate class files. + * Action provides a way to reuse action method code. An action method in an Action + * class can be used in multiple controllers or in different projects. * * Derived classes must implement a method named `run()`. This method * will be invoked by the controller when the action is requested. @@ -21,9 +21,9 @@ use Yii; * with user input values automatically according to their names. * For example, if the `run()` method is declared as follows: * - * ~~~ + * ```php * public function run($id, $type = 'book') { ... } - * ~~~ + * ``` * * And the parameters provided for the action are: `['id' => 1]`. * Then the `run()` method will be invoked as `run(1)` automatically. diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php index e1dc5dbcfd..1c4c624e34 100644 --- a/framework/base/ActionFilter.php +++ b/framework/base/ActionFilter.php @@ -28,6 +28,8 @@ class ActionFilter extends Behavior * Note that if the filter is attached to a module, the action IDs should also include child module IDs (if any) * and controller IDs. * + * Since version 2.0.9 action IDs can be specified as wildcards, e.g. `site/*`. + * * @see except */ public $only; @@ -111,14 +113,14 @@ class ActionFilter extends Behavior } /** - * Returns a value indicating whether the filer is active for the given action. - * @param Action $action the action being filtered - * @return boolean whether the filer is active for the given action. + * Returns an action ID by converting [[Action::$uniqueId]] into an ID relative to the module + * @param Action $action + * @return string + * @since 2.0.7 */ - protected function isActive($action) + protected function getActionId($action) { if ($this->owner instanceof Module) { - // convert action uniqueId into an ID relative to the module $mid = $this->owner->getUniqueId(); $id = $action->getUniqueId(); if ($mid !== '' && strpos($id, $mid) === 0) { @@ -127,6 +129,39 @@ class ActionFilter extends Behavior } else { $id = $action->id; } - return !in_array($id, $this->except, true) && (empty($this->only) || in_array($id, $this->only, true)); + + return $id; + } + + /** + * Returns a value indicating whether the filter is active for the given action. + * @param Action $action the action being filtered + * @return boolean whether the filter is active for the given action. + */ + protected function isActive($action) + { + $id = $this->getActionId($action); + + if (empty($this->only)) { + $onlyMatch = true; + } else { + $onlyMatch = false; + foreach ($this->only as $pattern) { + if (fnmatch($pattern, $id)) { + $onlyMatch = true; + break; + } + } + } + + $exceptMatch = false; + foreach ($this->except as $pattern) { + if (fnmatch($pattern, $id)) { + $exceptMatch = true; + break; + } + } + + return !$exceptMatch && $onlyMatch; } } diff --git a/framework/base/Application.php b/framework/base/Application.php index cdcb5774c2..91e9ffe41d 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -140,7 +140,7 @@ abstract class Application extends Module * @var array list of installed Yii extensions. Each array element represents a single extension * with the following structure: * - * ~~~ + * ```php * [ * 'name' => 'extension name', * 'version' => 'version number', @@ -150,7 +150,7 @@ abstract class Application extends Module * '@alias2' => 'to/path2', * ], * ] - * ~~~ + * ``` * * The "bootstrap" class listed above will be instantiated during the application * [[bootstrap()|bootstrapping process]]. If the class implements [[BootstrapInterface]], @@ -195,7 +195,7 @@ abstract class Application extends Module public function __construct($config = []) { Yii::$app = $this; - $this->setInstance($this); + static::setInstance($this); $this->state = self::STATE_BEGIN; @@ -287,10 +287,10 @@ abstract class Application extends Module if (isset($extension['bootstrap'])) { $component = Yii::createObject($extension['bootstrap']); if ($component instanceof BootstrapInterface) { - Yii::trace("Bootstrap with " . get_class($component) . '::bootstrap()', __METHOD__); + Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__); $component->bootstrap($this); } else { - Yii::trace("Bootstrap with " . get_class($component), __METHOD__); + Yii::trace('Bootstrap with ' . get_class($component), __METHOD__); } } } @@ -311,10 +311,10 @@ abstract class Application extends Module } if ($component instanceof BootstrapInterface) { - Yii::trace("Bootstrap with " . get_class($component) . '::bootstrap()', __METHOD__); + Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__); $component->bootstrap($this); } else { - Yii::trace("Bootstrap with " . get_class($component), __METHOD__); + Yii::trace('Bootstrap with ' . get_class($component), __METHOD__); } } } diff --git a/framework/base/ArrayAccessTrait.php b/framework/base/ArrayAccessTrait.php index df1ab603fd..43a229ab8b 100644 --- a/framework/base/ArrayAccessTrait.php +++ b/framework/base/ArrayAccessTrait.php @@ -22,7 +22,7 @@ trait ArrayAccessTrait { /** * Returns an iterator for traversing the data. - * This method is required by the SPL interface `IteratorAggregate`. + * This method is required by the SPL interface [[\IteratorAggregate]]. * It will be implicitly called when you use `foreach` to traverse the collection. * @return \ArrayIterator an iterator for traversing the cookies in the collection. */ @@ -42,7 +42,7 @@ trait ArrayAccessTrait } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param mixed $offset the offset to check on * @return boolean */ @@ -52,7 +52,7 @@ trait ArrayAccessTrait } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param integer $offset the offset to retrieve element. * @return mixed the element at the offset, null if no element is found at the offset */ @@ -62,7 +62,7 @@ trait ArrayAccessTrait } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param integer $offset the offset to set element * @param mixed $item the element value */ @@ -72,7 +72,7 @@ trait ArrayAccessTrait } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param mixed $offset the offset to unset element */ public function offsetUnset($offset) diff --git a/framework/base/Arrayable.php b/framework/base/Arrayable.php index 6958b022c4..92572a0c2f 100644 --- a/framework/base/Arrayable.php +++ b/framework/base/Arrayable.php @@ -34,7 +34,7 @@ interface Arrayable * returning the corresponding field value. The signature of the callable should be: * * ```php - * function ($field, $model) { + * function ($model, $field) { * // return field value * } * ``` @@ -62,6 +62,7 @@ interface Arrayable * @see toArray() */ public function fields(); + /** * Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]]. * @@ -75,6 +76,7 @@ interface Arrayable * @see fields() */ public function extraFields(); + /** * Converts the object into an array. * diff --git a/framework/base/ArrayableTrait.php b/framework/base/ArrayableTrait.php index 8b17323c85..308feeea1b 100644 --- a/framework/base/ArrayableTrait.php +++ b/framework/base/ArrayableTrait.php @@ -141,7 +141,7 @@ trait ArrayableTrait $result = []; foreach ($this->fields() as $field => $definition) { - if (is_integer($field)) { + if (is_int($field)) { $field = $definition; } if (empty($fields) || in_array($field, $fields, true)) { @@ -154,7 +154,7 @@ trait ArrayableTrait } foreach ($this->extraFields() as $field => $definition) { - if (is_integer($field)) { + if (is_int($field)) { $field = $definition; } if (in_array($field, $expand, true)) { diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php index c544db7a7c..0b67990aea 100644 --- a/framework/base/Behavior.php +++ b/framework/base/Behavior.php @@ -36,11 +36,15 @@ class Behavior extends Object * attached to the owner; and they will be detached from the events when * the behavior is detached from the component. * +<<<<<<< HEAD <<<<<<< HEAD * The callbacks can be any of the followings: ======= * The callbacks can be any of the following: >>>>>>> yiichina/master +======= + * The callbacks can be any of the following: +>>>>>>> master * * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']` * - object method: `[$object, 'handleClick']` @@ -49,12 +53,12 @@ class Behavior extends Object * * The following is an example: * - * ~~~ + * ```php * [ * Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate', * Model::EVENT_AFTER_VALIDATE => 'myAfterValidate', * ] - * ~~~ + * ``` * * @return array events (array keys) and the corresponding event handler methods (array values). */ diff --git a/framework/base/Component.php b/framework/base/Component.php index b5316123a9..c189dd3ceb 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -27,11 +27,11 @@ use Yii; * * To attach an event handler to an event, call [[on()]]: * - * ~~~ + * ```php * $post->on('update', function ($event) { * // send email notification * }); - * ~~~ + * ``` * * In the above, an anonymous function is attached to the "update" event of the post. You may attach * the following types of event handlers: @@ -43,31 +43,31 @@ use Yii; * * The signature of an event handler should be like the following: * - * ~~~ + * ```php * function foo($event) - * ~~~ + * ``` * * where `$event` is an [[Event]] object which includes parameters associated with the event. * * You can also attach a handler to an event when configuring a component with a configuration array. * The syntax is like the following: * - * ~~~ + * ```php * [ * 'on add' => function ($event) { ... } * ] - * ~~~ + * ``` * * where `on add` stands for attaching an event to the `add` event. * * Sometimes, you may want to associate extra data with an event handler when you attach it to an event * and then access it when the handler is invoked. You may do so by * - * ~~~ + * ```php * $post->on('update', function ($event) { * // the data can be accessed via $event->data * }, $data); - * ~~~ + * ``` * * A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple * behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the @@ -79,13 +79,13 @@ use Yii; * One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the * following: * - * ~~~ + * ```php * [ * 'as tree' => [ * 'class' => 'Tree', * ], * ] - * ~~~ + * ``` * * where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]] * to create the behavior object. @@ -411,13 +411,13 @@ class Component extends Object * indexed by behavior names. A behavior configuration can be either a string specifying * the behavior class or an array of the following structure: * - * ~~~ + * ```php * 'behaviorName' => [ * 'class' => 'BehaviorClass', * 'property1' => 'value1', * 'property2' => 'value2', * ] - * ~~~ + * ``` * * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding @@ -447,25 +447,29 @@ class Component extends Object /** * Attaches an event handler to an event. * +<<<<<<< HEAD <<<<<<< HEAD * The event handler must be a valid PHP callback. The followings are ======= * The event handler must be a valid PHP callback. The following are >>>>>>> yiichina/master +======= + * The event handler must be a valid PHP callback. The following are +>>>>>>> master * some examples: * - * ~~~ + * ``` * function ($event) { ... } // anonymous function * [$object, 'handleClick'] // $object->handleClick() * ['Page', 'handleClick'] // Page::handleClick() * 'handleClick' // global function handleClick() - * ~~~ + * ``` * * The event handler must be defined with the following signature, * - * ~~~ + * ``` * function ($event) - * ~~~ + * ``` * * where `$event` is an [[Event]] object which includes parameters associated with the event. * @@ -556,7 +560,7 @@ class Component extends Object /** * Returns the named behavior object. * @param string $name the behavior name - * @return Behavior the behavior object, or null if the behavior does not exist + * @return null|Behavior the behavior object, or null if the behavior does not exist */ public function getBehavior($name) { @@ -614,7 +618,7 @@ class Component extends Object * Detaches a behavior from the component. * The behavior's [[Behavior::detach()]] method will be invoked. * @param string $name the behavior's name. - * @return Behavior the detached behavior. Null if the behavior does not exist. + * @return null|Behavior the detached behavior. Null if the behavior does not exist. */ public function detachBehavior($name) { diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 4e3454f005..5407e91725 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -19,8 +19,7 @@ use Yii; * @property string $uniqueId The controller ID that is prefixed with the module ID (if any). This property is * read-only. * @property View|\yii\web\View $view The view object that can be used to render views or view files. - * @property string $viewPath The directory containing the view files for this controller. This property is - * read-only. + * @property string $viewPath The directory containing the view files for this controller. * * @author Qiang Xue * @since 2.0 @@ -51,7 +50,7 @@ class Controller extends Component implements ViewContextInterface */ public $defaultAction = 'index'; /** - * @var string|boolean the name of the layout to be applied to this controller's views. + * @var null|string|false the name of the layout to be applied to this controller's views. * This property mainly affects the behavior of [[render()]]. * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value. * If false, no layout will be applied. @@ -67,6 +66,10 @@ class Controller extends Component implements ViewContextInterface * @var View the view object that can be used to render views or view files. */ private $_view; + /** + * @var string the root directory that contains view files for this controller. + */ + private $_viewPath; /** @@ -87,7 +90,7 @@ class Controller extends Component implements ViewContextInterface * It should return an array, with array keys being action IDs, and array values the corresponding * action class names or action configuration arrays. For example, * - * ~~~ + * ```php * return [ * 'action1' => 'app\components\Action1', * 'action2' => [ @@ -96,7 +99,7 @@ class Controller extends Component implements ViewContextInterface * 'property2' => 'value2', * ], * ]; - * ~~~ + * ``` * * [[\Yii::createObject()]] will be used later to create the requested action * using the configuration provided here. @@ -122,7 +125,7 @@ class Controller extends Component implements ViewContextInterface throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id); } - Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__); + Yii::trace('Route to run: ' . $action->getUniqueId(), __METHOD__); if (Yii::$app->requestedAction === null) { Yii::$app->requestedAction = $action; @@ -237,16 +240,23 @@ class Controller extends Component implements ViewContextInterface * will determine whether the action should continue to run. * <<<<<<< HEAD +<<<<<<< HEAD ======= * In case the action should not run, the request should be handled inside of the `beforeAction` code * by either providing the necessary output or redirecting the request. Otherwise the response will be empty. * >>>>>>> yiichina/master +======= + * In case the action should not run, the request should be handled inside of the `beforeAction` code + * by either providing the necessary output or redirecting the request. Otherwise the response will be empty. + * +>>>>>>> master * If you override this method, your code should look like the following: * * ```php * public function beforeAction($action) * { +<<<<<<< HEAD <<<<<<< HEAD * if (parent::beforeAction($action)) { * // your custom code here @@ -263,6 +273,18 @@ class Controller extends Component implements ViewContextInterface * * return true; // or false to not run the action >>>>>>> yiichina/master +======= + * // your custom code here, if you want the code to run before action filters, + * // which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl + * + * if (!parent::beforeAction($action)) { + * return false; + * } + * + * // other custom code here + * + * return true; // or false to not run the action +>>>>>>> master * } * ``` * @@ -323,6 +345,7 @@ class Controller extends Component implements ViewContextInterface } /** + * Returns the unique ID of the controller. * @return string the controller ID that is prefixed with the module ID (if any). */ public function getUniqueId() @@ -458,7 +481,21 @@ class Controller extends Component implements ViewContextInterface */ public function getViewPath() { - return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id; + if ($this->_viewPath === null) { + $this->_viewPath = $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id; + } + return $this->_viewPath; + } + + /** + * Sets the directory that contains the view files. + * @param string $path the root directory of view files. + * @throws InvalidParamException if the directory is invalid + * @since 2.0.7 + */ + public function setViewPath($path) + { + $this->_viewPath = Yii::getAlias($path); } /** diff --git a/framework/base/DynamicModel.php b/framework/base/DynamicModel.php index 782244e917..69e271aa7f 100644 --- a/framework/base/DynamicModel.php +++ b/framework/base/DynamicModel.php @@ -66,7 +66,7 @@ class DynamicModel extends Model public function __construct(array $attributes = [], $config = []) { foreach ($attributes as $name => $value) { - if (is_integer($name)) { + if (is_int($name)) { $this->_attributes[$value] = null; } else { $this->_attributes[$name] = $value; @@ -150,7 +150,7 @@ class DynamicModel extends Model * @param mixed $validator the validator for the rule.This can be a built-in validator name, * a method name of the model class, an anonymous function, or a validator class name. * @param array $options the options (name-value pairs) to be applied to the validator - * @return static the model itself + * @return $this the model itself */ public function addRule($attributes, $validator, $options = []) { diff --git a/framework/base/ErrorException.php b/framework/base/ErrorException.php index 580a8280cd..d9da27b360 100644 --- a/framework/base/ErrorException.php +++ b/framework/base/ErrorException.php @@ -17,6 +17,17 @@ use Yii; */ class ErrorException extends \ErrorException { + /** + * This constant represents a fatal error in the HHVM engine. + * + * PHP Zend runtime won't call the error handler on fatals, HHVM will, with an error code of 16777217 + * We will handle fatal error a bit different on HHVM. + * @see https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/runtime-error.h#L62 + * @since 2.0.6 + */ + const E_HHVM_FATAL_ERROR = 16777217; // E_ERROR | (1 << 24) + + /** * Constructs the exception. * @link http://php.net/manual/en/errorexception.construct.php @@ -32,8 +43,11 @@ class ErrorException extends \ErrorException parent::__construct($message, $code, $severity, $filename, $lineno, $previous); if (function_exists('xdebug_get_function_stack')) { - $trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1); - foreach ($trace as &$frame) { + // XDebug trace can't be modified and used directly with PHP 7 + // @see https://github.com/yiisoft/yii2/pull/11723 + $xDebugTrace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1); + $trace = []; + foreach ($xDebugTrace as $frame) { if (!isset($frame['function'])) { $frame['function'] = 'unknown'; } @@ -49,6 +63,7 @@ class ErrorException extends \ErrorException if (isset($frame['params']) && !isset($frame['args'])) { $frame['args'] = $frame['params']; } + $trace[] = $frame; } $ref = new \ReflectionProperty('Exception', 'trace'); @@ -65,7 +80,7 @@ class ErrorException extends \ErrorException */ public static function isFatalError($error) { - return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING]); + return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, self::E_HHVM_FATAL_ERROR]); } /** @@ -89,6 +104,7 @@ class ErrorException extends \ErrorException E_USER_NOTICE => 'PHP User Notice', E_USER_WARNING => 'PHP User Warning', E_WARNING => 'PHP Warning', + self::E_HHVM_FATAL_ERROR => 'HHVM Fatal Error', ]; return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error'; diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index 3af2625b75..73c6c9a205 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -44,6 +44,10 @@ abstract class ErrorHandler extends Component * @var string Used to reserve memory for fatal error handler. */ private $_memoryReserve; + /** + * @var \Exception from HHVM error that stores backtrace + */ + private $_hhvmException; /** @@ -53,7 +57,11 @@ abstract class ErrorHandler extends Component { ini_set('display_errors', false); set_exception_handler([$this, 'handleException']); - set_error_handler([$this, 'handleError']); + if (defined('HHVM_VERSION')) { + set_error_handler([$this, 'handleHhvmError']); + } else { + set_error_handler([$this, 'handleError']); + } if ($this->memoryReserveSize > 0) { $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize); } @@ -87,15 +95,21 @@ abstract class ErrorHandler extends Component // disable error capturing to avoid recursive errors while handling exceptions $this->unregister(); +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> master // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent // HTTP exceptions will override this value in renderException() if (PHP_SAPI !== 'cli') { http_response_code(500); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master try { $this->logException($exception); if ($this->discardExistingOutput) { @@ -103,16 +117,25 @@ abstract class ErrorHandler extends Component } $this->renderException($exception); if (!YII_ENV_TEST) { + \Yii::getLogger()->flush(true); + if (defined('HHVM_VERSION')) { + flush(); + } exit(1); } } catch (\Exception $e) { // an other exception could be thrown while displaying the exception +<<<<<<< HEAD <<<<<<< HEAD $msg = (string) $e; ======= $msg = "An Error occurred while handling another error:\n"; $msg .= (string) $e; >>>>>>> yiichina/master +======= + $msg = "An Error occurred while handling another error:\n"; + $msg .= (string) $e; +>>>>>>> master $msg .= "\nPrevious exception:\n"; $msg .= (string) $exception; if (YII_DEBUG) { @@ -126,10 +149,15 @@ abstract class ErrorHandler extends Component } $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER); error_log($msg); +<<<<<<< HEAD <<<<<<< HEAD if (PHP_SAPI !== 'cli') { http_response_code(500); +======= + if (defined('HHVM_VERSION')) { + flush(); +>>>>>>> master } ======= >>>>>>> yiichina/master @@ -139,6 +167,38 @@ abstract class ErrorHandler extends Component $this->exception = null; } + /** + * Handles HHVM execution errors such as warnings and notices. + * + * This method is used as a HHVM error handler. It will store exception that will + * be used in fatal error handler + * + * @param integer $code the level of the error raised. + * @param string $message the error message. + * @param string $file the filename that the error was raised in. + * @param integer $line the line number the error was raised at. + * @param mixed $context + * @param mixed $backtrace trace of error + * @return boolean whether the normal error handler continues. + * + * @throws ErrorException + * @since 2.0.6 + */ + public function handleHhvmError($code, $message, $file, $line, $context, $backtrace) + { + if ($this->handleError($code, $message, $file, $line)) { + return true; + } + if (E_ERROR & $code) { + $exception = new ErrorException($message, $code, $code, $file, $line); + $ref = new \ReflectionProperty('\Exception', 'trace'); + $ref->setAccessible(true); + $ref->setValue($exception, $backtrace); + $this->_hhvmException = $exception; + } + return false; + } + /** * Handles PHP execution errors such as warnings and notices. * @@ -166,8 +226,11 @@ abstract class ErrorHandler extends Component $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); array_shift($trace); foreach ($trace as $frame) { - if ($frame['function'] == '__toString') { + if ($frame['function'] === '__toString') { $this->handleException($exception); + if (defined('HHVM_VERSION')) { + flush(); + } exit(1); } } @@ -193,7 +256,11 @@ abstract class ErrorHandler extends Component $error = error_get_last(); if (ErrorException::isFatalError($error)) { - $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); + if (!empty($this->_hhvmException)) { + $exception = $this->_hhvmException; + } else { + $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); + } $this->exception = $exception; $this->logException($exception); @@ -205,7 +272,9 @@ abstract class ErrorHandler extends Component // need to explicitly flush logs because exit() next will terminate the app immediately Yii::getLogger()->flush(true); - + if (defined('HHVM_VERSION')) { + flush(); + } exit(1); } } @@ -229,7 +298,7 @@ abstract class ErrorHandler extends Component } elseif ($exception instanceof \ErrorException) { $category .= ':' . $exception->getSeverity(); } - Yii::error((string) $exception, $category); + Yii::error($exception, $category); } /** diff --git a/framework/base/Event.php b/framework/base/Event.php index e6a602c7bc..a57e027344 100644 --- a/framework/base/Event.php +++ b/framework/base/Event.php @@ -48,6 +48,9 @@ class Event extends Object */ public $data; + /** + * @var array contains all globally registered event handlers. + */ private static $_events = []; @@ -60,11 +63,11 @@ class Event extends Object * For example, the following code attaches an event handler to `ActiveRecord`'s * `afterInsert` event: * - * ~~~ + * ```php * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { * Yii::trace(get_class($event->sender) . ' is inserted.'); * }); - * ~~~ + * ``` * * The handler will be invoked for EVERY successful ActiveRecord insertion. * @@ -145,11 +148,18 @@ class Event extends Object } else { $class = ltrim($class, '\\'); } - do { + + $classes = array_merge( + [$class], + class_parents($class, true), + class_implements($class, true) + ); + + foreach ($classes as $class) { if (!empty(self::$_events[$name][$class])) { return true; } - } while (($class = get_parent_class($class)) !== false); + } return false; } @@ -181,7 +191,14 @@ class Event extends Object } else { $class = ltrim($class, '\\'); } - do { + + $classes = array_merge( + [$class], + class_parents($class, true), + class_implements($class, true) + ); + + foreach ($classes as $class) { if (!empty(self::$_events[$name][$class])) { foreach (self::$_events[$name][$class] as $handler) { $event->data = $handler[1]; @@ -191,6 +208,6 @@ class Event extends Object } } } - } while (($class = get_parent_class($class)) !== false); + } } } diff --git a/framework/base/InvalidValueException.php b/framework/base/InvalidValueException.php index a3727ce44d..a4786e297e 100644 --- a/framework/base/InvalidValueException.php +++ b/framework/base/InvalidValueException.php @@ -16,10 +16,10 @@ namespace yii\base; class InvalidValueException extends \UnexpectedValueException { /** - * @return string the user-friendly name of this exception - */ - public function getName() - { - return 'Invalid Return Value'; - } + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Return Value'; + } } diff --git a/framework/base/Model.php b/framework/base/Model.php index a850d9df58..4857725e5d 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -92,21 +92,21 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * * Each rule is an array with the following structure: * - * ~~~ + * ```php * [ * ['attribute1', 'attribute2'], * 'validator type', * 'on' => ['scenario1', 'scenario2'], - * ...other parameters... + * //...other parameters... * ] - * ~~~ + * ``` * * where * - * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string; + * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass a string; * - validator type: required, specifies the validator to be used. It can be a built-in validator name, * a method name of the model class, an anonymous function, or a validator class name. - * - on: optional, specifies the [[scenario|scenarios]] array when the validation + * - on: optional, specifies the [[scenario|scenarios]] array in which the validation * rule can be applied. If this option is not set, the rule will apply to all scenarios. * - additional name-value pairs can be specified to initialize the corresponding validator properties. * Please refer to individual validator class API for possible properties. @@ -114,21 +114,22 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * A validator can be either an object of a class extending [[Validator]], or a model class method * (called *inline validator*) that has the following signature: * - * ~~~ + * ```php * // $params refers to validation parameters given in the rule * function validatorName($attribute, $params) - * ~~~ + * ``` * - * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of - * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value - * can be accessed as `$this->[$attribute]`. + * In the above `$attribute` refers to the attribute currently being validated while `$params` contains an array of + * validator configuration options such as `max` in case of `string` validator. The value of the attribute currently being validated + * can be accessed as `$this->$attribute`. Note the `$` before `attribute`; this is taking the value of the variable + * `$attribute` and using it as the name of the property to access. * * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. - * They each has an alias name which can be used when specifying a validation rule. + * Each one has an alias name which can be used when specifying a validation rule. * * Below are some examples: * - * ~~~ + * ```php * [ * // built-in "required" validator * [['username', 'password'], 'required'], @@ -141,7 +142,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * // a validator of class "DateRangeValidator" * ['dateRange', 'DateRangeValidator'], * ]; - * ~~~ + * ``` * * Note, in order to inherit rules defined in the parent class, a child class needs to * merge the parent rules with child rules using functions such as `array_merge()`. @@ -159,16 +160,21 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * An active attribute is one that is subject to validation in the current scenario. * The returned array should be in the following format: * +<<<<<<< HEAD <<<<<<< HEAD * ~~~ ======= * ```php >>>>>>> yiichina/master +======= + * ```php +>>>>>>> master * [ * 'scenario1' => ['attribute11', 'attribute12', ...], * 'scenario2' => ['attribute21', 'attribute22', ...], * ... * ] +<<<<<<< HEAD <<<<<<< HEAD * ~~~ * @@ -182,6 +188,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * If an attribute should NOT be massively assigned (thus considered unsafe), * please prefix the attribute with an exclamation character (e.g. `'!rank'`). >>>>>>> yiichina/master +======= + * ``` + * + * By default, an active attribute is considered safe and can be massively assigned. + * If an attribute should NOT be massively assigned (thus considered unsafe), + * please prefix the attribute with an exclamation character (e.g. `'!rank'`). +>>>>>>> master * * The default implementation of this method will return all scenarios found in the [[rules()]] * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes @@ -228,6 +241,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab } foreach ($scenarios as $scenario => $attributes) { +<<<<<<< HEAD <<<<<<< HEAD if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) { unset($scenarios[$scenario]); @@ -235,6 +249,9 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ======= if (!empty($attributes)) { >>>>>>> yiichina/master +======= + if (!empty($attributes)) { +>>>>>>> master $scenarios[$scenario] = array_keys($attributes); } } @@ -250,15 +267,19 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * name is "b", then the corresponding input name would be "A[b]". If the form name is * an empty string, then the input name would be "b". * + * The purpose of the above naming schema is that for forms which contain multiple different models, + * the attributes of each model are grouped in sub-arrays of the POST-data and it is easier to + * differentiate between them. + * * By default, this method returns the model class name (without the namespace part) * as the form name. You may override it when the model is used in different forms. * * @return string the form name of this model class. + * @see load() */ public function formName() { $reflector = new ReflectionClass($this); - return $reflector->getShortName(); } @@ -304,7 +325,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Returns the attribute hints. * * Attribute hints are mainly used for display purpose. For example, given an attribute @@ -325,7 +349,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Performs the data validation. * * This method executes the validation rules applicable to the current [[scenario]]. @@ -413,9 +440,9 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * manipulate it by inserting or removing validators (useful in model behaviors). * For example, * - * ~~~ + * ```php * $model->validators[] = $newValidator; - * ~~~ + * ``` * * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model. */ @@ -528,7 +555,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Returns the text hint for the specified attribute. * @param string $attribute the attribute name * @return string the attribute hint @@ -542,7 +572,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Returns a value indicating whether there is any validation error. * @param string|null $attribute attribute name. Use null to check all attributes. * @return boolean whether there is any error. @@ -560,7 +593,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * @return array errors for all attributes or the specified attribute. Empty array is returned if no error. * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following: * - * ~~~ + * ```php * [ * 'username' => [ * 'Username is required.', @@ -570,7 +603,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * 'Email address is invalid.', * ] * ] - * ~~~ + * ``` * * @see getFirstErrors() * @see getFirstError() @@ -641,7 +674,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab { foreach ($items as $attribute => $errors) { if (is_array($errors)) { - foreach($errors as $error) { + foreach ($errors as $error) { $this->addError($attribute, $error); } } else { @@ -773,7 +806,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab } $attributes = []; foreach ($scenarios[$scenario] as $attribute) { - if ($attribute[0] !== '!') { + if ($attribute[0] !== '!' && !in_array('!' . $attribute, $scenarios[$scenario])) { $attributes[] = $attribute; } } @@ -803,15 +836,37 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab } /** - * Populates the model with the data from end user. - * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]]. - * If [[formName()]] is empty, the whole `$data` array will be used to populate the model. - * The data being populated is subject to the safety check by [[setAttributes()]]. - * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array - * supplied by end user. - * @param string $formName the form name to be used for loading the data into the model. - * If not set, [[formName()]] will be used. - * @return boolean whether the model is successfully populated with some data. + * Populates the model with input data. + * + * This method provides a convenient shortcut for: + * + * ```php + * if (isset($_POST['FormName'])) { + * $model->attributes = $_POST['FormName']; + * if ($model->save()) { + * // handle success + * } + * } + * ``` + * + * which, with `load()` can be written as: + * + * ```php + * if ($model->load($_POST) && $model->save()) { + * // handle success + * } + * ``` + * + * `load()` gets the `'FormName'` from the model's [[formName()]] method (which you may override), unless the + * `$formName` parameter is given. If the form name is empty, `load()` populates the model with the whole of `$data`, + * instead of `$data['FormName']`. + * + * Note, that the data being populated is subject to the safety check by [[setAttributes()]]. + * + * @param array $data the data array to load, typically `$_POST` or `$_GET`. + * @param string $formName the form name to use to load the data into the model. + * If not set, [[formName()]] is used. + * @return boolean whether `load()` found the expected form in `$data`. */ public function load($data, $formName = null) { @@ -906,7 +961,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab * returning the corresponding field value. The signature of the callable should be: * * ```php - * function ($field, $model) { + * function ($model, $field) { * // return field value * } * ``` @@ -948,7 +1003,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** * Returns an iterator for traversing the attributes in the model. - * This method is required by the interface IteratorAggregate. + * This method is required by the interface [[\IteratorAggregate]]. * @return ArrayIterator an iterator for traversing the items in the list. */ public function getIterator() @@ -959,7 +1014,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** * Returns whether there is an element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `isset($model[$offset])`. * @param mixed $offset the offset to check on * @return boolean @@ -971,7 +1026,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** * Returns the element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$value = $model[$offset];`. * @param mixed $offset the offset to retrieve element. * @return mixed the element at the offset, null if no element is found at the offset @@ -983,7 +1038,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** * Sets the element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$model[$offset] = $item;`. * @param integer $offset the offset to set element * @param mixed $item the element value @@ -995,7 +1050,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab /** * Sets the element value at the specified offset to null. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `unset($model[$offset])`. * @param mixed $offset the offset to unset element */ diff --git a/framework/base/Module.php b/framework/base/Module.php index bff2447191..63e14781c0 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -74,7 +74,7 @@ class Module extends ServiceLocator * the controller's fully qualified class name, and the rest of the name-value pairs * in the array are used to initialize the corresponding controller properties. For example, * - * ~~~ + * ```php * [ * 'account' => 'app\controllers\UserController', * 'article' => [ @@ -82,7 +82,7 @@ class Module extends ServiceLocator * 'pageTitle' => 'something new', * ], * ] - * ~~~ + * ``` */ public $controllerMap = []; /** @@ -217,7 +217,7 @@ class Module extends ServiceLocator public function setBasePath($path) { $path = Yii::getAlias($path); - $p = realpath($path); + $p = strncmp($path, 'phar://', 7) === 0 ? $path : realpath($path); if ($p !== false && is_dir($p)) { $this->_basePath = $p; } else { @@ -243,11 +243,10 @@ class Module extends ServiceLocator */ public function getViewPath() { - if ($this->_viewPath !== null) { - return $this->_viewPath; - } else { - return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; + if ($this->_viewPath === null) { + $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; } + return $this->_viewPath; } /** @@ -266,11 +265,11 @@ class Module extends ServiceLocator */ public function getLayoutPath() { - if ($this->_layoutPath !== null) { - return $this->_layoutPath; - } else { - return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; + if ($this->_layoutPath === null) { + $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; } + + return $this->_layoutPath; } /** @@ -294,12 +293,12 @@ class Module extends ServiceLocator * (must start with '@') and the array values are the corresponding paths or aliases. * For example, * - * ~~~ + * ```php * [ * '@models' => '@app/models', // an existing alias * '@backend' => __DIR__ . '/../backend', // a directory * ] - * ~~~ + * ``` */ public function setAliases($aliases) { @@ -364,11 +363,15 @@ class Module extends ServiceLocator * Adds a sub-module to this module. * @param string $id module ID * @param Module|array|null $module the sub-module to be added to this module. This can +<<<<<<< HEAD <<<<<<< HEAD * be one of the followings: ======= * be one of the following: >>>>>>> yiichina/master +======= + * be one of the following: +>>>>>>> master * * - a [[Module]] object * - a configuration array: when [[getModule()]] is called initially, the array @@ -419,7 +422,7 @@ class Module extends ServiceLocator * * The following is an example for registering two sub-modules: * - * ~~~ + * ```php * [ * 'comment' => [ * 'class' => 'app\modules\comment\CommentModule', @@ -427,7 +430,7 @@ class Module extends ServiceLocator * ], * 'booking' => ['class' => 'app\modules\booking\BookingModule'], * ] - * ~~~ + * ``` * * @param array $modules modules (id => module configuration or instances) */ @@ -569,12 +572,17 @@ class Module extends ServiceLocator } if (is_subclass_of($className, 'yii\base\Controller')) { +<<<<<<< HEAD <<<<<<< HEAD return Yii::createObject($className, [$id, $this]); ======= $controller = Yii::createObject($className, [$id, $this]); return get_class($controller) === $className ? $controller : null; >>>>>>> yiichina/master +======= + $controller = Yii::createObject($className, [$id, $this]); + return get_class($controller) === $className ? $controller : null; +>>>>>>> master } elseif (YII_DEBUG) { throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); } else { @@ -589,16 +597,23 @@ class Module extends ServiceLocator * will determine whether the action should continue to run. * <<<<<<< HEAD +<<<<<<< HEAD ======= * In case the action should not run, the request should be handled inside of the `beforeAction` code * by either providing the necessary output or redirecting the request. Otherwise the response will be empty. * >>>>>>> yiichina/master +======= + * In case the action should not run, the request should be handled inside of the `beforeAction` code + * by either providing the necessary output or redirecting the request. Otherwise the response will be empty. + * +>>>>>>> master * If you override this method, your code should look like the following: * * ```php * public function beforeAction($action) * { +<<<<<<< HEAD <<<<<<< HEAD * if (parent::beforeAction($action)) { * // your custom code here @@ -610,11 +625,19 @@ class Module extends ServiceLocator * if (!parent::beforeAction($action)) { * return false; * } +======= + * if (!parent::beforeAction($action)) { + * return false; + * } +>>>>>>> master * * // your custom code here * * return true; // or false to not run the action +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * } * ``` * diff --git a/framework/base/Object.php b/framework/base/Object.php index 05457110d7..220ab3e1e3 100644 --- a/framework/base/Object.php +++ b/framework/base/Object.php @@ -15,7 +15,7 @@ use Yii; * A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example, * the following getter and setter methods define a property named `label`: * - * ~~~ + * ```php * private $_label; * * public function getLabel() @@ -27,19 +27,19 @@ use Yii; * { * $this->_label = $value; * } - * ~~~ + * ``` * * Property names are *case-insensitive*. * * A property can be accessed like a member variable of an object. Reading or writing a property will cause the invocation * of the corresponding getter or setter method. For example, * - * ~~~ + * ```php * // equivalent to $label = $object->getLabel(); * $label = $object->label; * // equivalent to $object->setLabel('abc'); * $object->label = 'abc'; - * ~~~ + * ``` * * If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying * to modify the property value will cause an exception. @@ -60,13 +60,13 @@ use Yii; * In order to ensure the above life cycles, if a child class of Object needs to override the constructor, * it should be done like the following: * - * ~~~ + * ```php * public function __construct($param1, $param2, ..., $config = []) * { * ... * parent::__construct($config); * } - * ~~~ + * ``` * * That is, a `$config` parameter (defaults to `[]`) should be declared as the last parameter * of the constructor, and the parent implementation should be called at the end of the constructor. diff --git a/framework/base/Security.php b/framework/base/Security.php index ad6556ab65..bc75fe19c2 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -77,8 +77,17 @@ class Security extends Component * - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm. * This option is recommended, but it requires PHP version >= 5.5.0 * - 'crypt' - use PHP `crypt()` function. + * @deprecated Since version 2.0.7, [[generatePasswordHash()]] ignores [[passwordHashStrategy]] and + * uses `password_hash()` when available or `crypt()` when not. */ - public $passwordHashStrategy = 'crypt'; + public $passwordHashStrategy; + /** + * @var integer Default cost used for password hashing. + * Allowed value is between 4 and 31. + * @see generatePasswordHash() + * @since 2.0.6 + */ + public $passwordHashCost = 13; /** @@ -103,7 +112,7 @@ class Security extends Component } /** - * Encrypts data using a cryptograhic key. + * Encrypts data using a cryptographic key. * Derives keys for encryption and authentication from the input key using HKDF and a random salt, * which is very fast relative to [[encryptByPassword()]]. The input key must be properly * random -- use [[generateRandomKey()]] to generate keys. @@ -113,8 +122,8 @@ class Security extends Component * @param string $inputKey the input to use for encryption and authentication * @param string $info optional context and application specific information, see [[hkdf()]] * @return string the encrypted data - * @see decryptByPassword() - * @see encryptByKey() + * @see decryptByKey() + * @see encryptByPassword() */ public function encryptByKey($data, $inputKey, $info = null) { @@ -125,7 +134,7 @@ class Security extends Component * Verifies and decrypts data encrypted with [[encryptByPassword()]]. * @param string $data the encrypted data to decrypt * @param string $password the password to use for decryption - * @return bool|string the decrypted data or false on authentication failure + * @return boolean|string the decrypted data or false on authentication failure * @see encryptByPassword() */ public function decryptByPassword($data, $password) @@ -138,7 +147,7 @@ class Security extends Component * @param string $data the encrypted data to decrypt * @param string $inputKey the input to use for encryption and authentication * @param string $info optional context and application specific information, see [[hkdf()]] - * @return bool|string the decrypted data or false on authentication failure + * @return boolean|string the decrypted data or false on authentication failure * @see encryptByKey() */ public function decryptByKey($data, $inputKey, $info = null) @@ -205,7 +214,7 @@ class Security extends Component * @param string $secret the decryption password or key * @param string $info context/application specific information, @see encrypt() * - * @return bool|string the decrypted data or false on authentication failure + * @return boolean|string the decrypted data or false on authentication failure * @throws InvalidConfigException on OpenSSL not loaded * @throws Exception on OpenSSL error * @see encrypt() @@ -270,7 +279,7 @@ class Security extends Component if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { $length = (int) $length; } - if (!is_integer($length) || $length < 0 || $length > 255 * $hashLength) { + if (!is_int($length) || $length < 0 || $length > 255 * $hashLength) { throw new InvalidParamException('Invalid length'); } $blocks = $length !== 0 ? ceil($length / $hashLength) : 1; @@ -325,13 +334,13 @@ class Security extends Component if (is_string($iterations) && preg_match('{^\d{1,16}$}', $iterations)) { $iterations = (int) $iterations; } - if (!is_integer($iterations) || $iterations < 1) { + if (!is_int($iterations) || $iterations < 1) { throw new InvalidParamException('Invalid iterations'); } if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { $length = (int) $length; } - if (!is_integer($length) || $length < 0) { + if (!is_int($length) || $length < 0) { throw new InvalidParamException('Invalid length'); } $hashLength = StringHelper::byteLength($test); @@ -414,6 +423,9 @@ class Security extends Component return false; } + private $_useLibreSSL; + private $_randomFile; + /** * Generates specified number of random bytes. * Note that output may not be ASCII. @@ -421,72 +433,109 @@ class Security extends Component * * @param integer $length the number of bytes to generate * @return string the generated random bytes - * @throws InvalidConfigException if OpenSSL extension is required (e.g. on Windows) but not installed. + * @throws InvalidParamException if wrong length is specified * @throws Exception on failure. */ public function generateRandomKey($length = 32) { - /* - * Strategy - * - * The most common platform is Linux, on which /dev/urandom is the best choice. Many other OSs - * implement a device called /dev/urandom for Linux compat and it is good too. So if there is - * a /dev/urandom then it is our first choice regardless of OS. - * - * Nearly all other modern Unix-like systems (the BSDs, Unixes and OS X) have a /dev/random - * that is a good choice. If we didn't get bytes from /dev/urandom then we try this next but - * only if the system is not Linux. Do not try to read /dev/random on Linux. - * - * Finally, OpenSSL can supply CSPR bytes. It is our last resort. On Windows this reads from - * CryptGenRandom, which is the right thing to do. On other systems that don't have a Unix-like - * /dev/urandom, it will deliver bytes from its own CSPRNG that is seeded from kernel sources - * of randomness. Even though it is fast, we don't generally prefer OpenSSL over /dev/urandom - * because an RNG in user space memory is undesirable. - * - * For background, see http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ - */ + if (!is_int($length)) { + throw new InvalidParamException('First parameter ($length) must be an integer'); + } - $bytes = ''; + if ($length < 1) { + throw new InvalidParamException('First parameter ($length) must be greater than 0'); + } - // If we are on Linux or any OS that mimics the Linux /dev/urandom device, e.g. FreeBSD or OS X, - // then read from /dev/urandom. - if (@file_exists('/dev/urandom')) { - $handle = fopen('/dev/urandom', 'r'); - if ($handle !== false) { - $bytes .= fread($handle, $length); - fclose($handle); + // always use random_bytes() if it is available + if (function_exists('random_bytes')) { + return random_bytes($length); + } + + // The recent LibreSSL RNGs are faster and likely better than /dev/urandom. + // Parse OPENSSL_VERSION_TEXT because OPENSSL_VERSION_NUMBER is no use for LibreSSL. + // https://bugs.php.net/bug.php?id=71143 + if ($this->_useLibreSSL === null) { + $this->_useLibreSSL = defined('OPENSSL_VERSION_TEXT') + && preg_match('{^LibreSSL (\d\d?)\.(\d\d?)\.(\d\d?)$}', OPENSSL_VERSION_TEXT, $matches) + && (10000 * $matches[1]) + (100 * $matches[2]) + $matches[3] >= 20105; + } + + // Since 5.4.0, openssl_random_pseudo_bytes() reads from CryptGenRandom on Windows instead + // of using OpenSSL library. LibreSSL is OK everywhere but don't use OpenSSL on non-Windows. + if ($this->_useLibreSSL + || ( + DIRECTORY_SEPARATOR !== '/' + && substr_compare(PHP_OS, 'win', 0, 3, true) === 0 + && function_exists('openssl_random_pseudo_bytes') + ) + ) { + $key = openssl_random_pseudo_bytes($length, $cryptoStrong); + if ($cryptoStrong === false) { + throw new Exception( + 'openssl_random_pseudo_bytes() set $crypto_strong false. Your PHP setup is insecure.' + ); + } + if ($key !== false && StringHelper::byteLength($key) === $length) { + return $key; } } - if (StringHelper::byteLength($bytes) >= $length) { - return StringHelper::byteSubstr($bytes, 0, $length); - } - - // If we are not on Linux and there is a /dev/random device then we have a BSD or Unix device - // that won't block. It's not safe to read from /dev/random on Linux. - if (PHP_OS !== 'Linux' && @file_exists('/dev/random')) { - $handle = fopen('/dev/random', 'r'); - if ($handle !== false) { - $bytes .= fread($handle, $length); - fclose($handle); + // mcrypt_create_iv() does not use libmcrypt. Since PHP 5.3.7 it directly reads + // CryptGenRandom on Windows. Elsewhere it directly reads /dev/urandom. + if (function_exists('mcrypt_create_iv')) { + $key = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if (StringHelper::byteLength($key) === $length) { + return $key; } } - if (StringHelper::byteLength($bytes) >= $length) { - return StringHelper::byteSubstr($bytes, 0, $length); + // If not on Windows, try to open a random device. + if ($this->_randomFile === null && DIRECTORY_SEPARATOR === '/') { + // urandom is a symlink to random on FreeBSD. + $device = PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom'; + // Check random device for special character device protection mode. Use lstat() + // instead of stat() in case an attacker arranges a symlink to a fake device. + $lstat = @lstat($device); + if ($lstat !== false && ($lstat['mode'] & 0170000) === 020000) { + $this->_randomFile = fopen($device, 'rb') ?: null; + + if (is_resource($this->_randomFile)) { + // Reduce PHP stream buffer from default 8192 bytes to optimize data + // transfer from the random device for smaller values of $length. + // This also helps to keep future randoms out of user memory space. + $bufferSize = 8; + + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_randomFile, $bufferSize); + } + // stream_set_read_buffer() isn't implemented on HHVM + if (function_exists('stream_set_chunk_size')) { + stream_set_chunk_size($this->_randomFile, $bufferSize); + } + } + } } - if (!extension_loaded('openssl')) { - throw new InvalidConfigException('The OpenSSL PHP extension is not installed.'); + if (is_resource($this->_randomFile)) { + $buffer = ''; + $stillNeed = $length; + while ($stillNeed > 0) { + $someBytes = fread($this->_randomFile, $stillNeed); + if ($someBytes === false) { + break; + } + $buffer .= $someBytes; + $stillNeed -= StringHelper::byteLength($someBytes); + if ($stillNeed === 0) { + // Leaving file pointer open in order to make next generation faster by reusing it. + return $buffer; + } + } + fclose($this->_randomFile); + $this->_randomFile = null; } - $bytes .= openssl_random_pseudo_bytes($length, $cryptoStrong); - - if (StringHelper::byteLength($bytes) < $length || !$cryptoStrong) { - throw new Exception('Unable to generate random bytes.'); - } - - return StringHelper::byteSubstr($bytes, 0, $length); + throw new Exception('Unable to generate a random key'); } /** @@ -495,11 +544,18 @@ class Security extends Component * * @param integer $length the length of the key in characters * @return string the generated random key - * @throws InvalidConfigException if OpenSSL extension is needed but not installed. * @throws Exception on failure. */ public function generateRandomString($length = 32) { + if (!is_int($length)) { + throw new InvalidParamException('First parameter ($length) must be an integer'); + } + + if ($length < 1) { + throw new InvalidParamException('First parameter ($length) must be greater than 0'); + } + $bytes = $this->generateRandomKey($length); // '=' character(s) returned by base64_encode() are always discarded because // they are guaranteed to be after position $length in the base64_encode() output. @@ -513,7 +569,7 @@ class Security extends Component * Later when a password needs to be validated, the hash can be fetched and passed * to [[validatePassword()]]. For example, * - * ~~~ + * ```php * // generates the hash (usually done during user registration or when the password is changed) * $hash = Yii::$app->getSecurity()->generatePasswordHash($password); * // ...save $hash in database... @@ -524,46 +580,48 @@ class Security extends Component * } else { * // password is bad * } - * ~~~ + * ``` * * @param string $password The password to be hashed. * @param integer $cost Cost parameter used by the Blowfish hash algorithm. * The higher the value of cost, * the longer it takes to generate the hash and to verify a password against it. Higher cost +<<<<<<< HEAD <<<<<<< HEAD * therefore slows down a brute-force attack. For best protection against brute for attacks, ======= * therefore slows down a brute-force attack. For best protection against brute-force attacks, >>>>>>> yiichina/master +======= + * therefore slows down a brute-force attack. For best protection against brute-force attacks, +>>>>>>> master * set it to the highest value that is tolerable on production servers. The time taken to * compute the hash doubles for every increment by one of $cost. * @return string The password hash string. When [[passwordHashStrategy]] is set to 'crypt', * the output is always 60 ASCII characters, when set to 'password_hash' the output length * might increase in future versions of PHP (http://php.net/manual/en/function.password-hash.php) * @throws Exception on bad password parameter or cost parameter. - * @throws InvalidConfigException when an unsupported password hash strategy is configured. * @see validatePassword() */ - public function generatePasswordHash($password, $cost = 13) + public function generatePasswordHash($password, $cost = null) { - switch ($this->passwordHashStrategy) { - case 'password_hash': - if (!function_exists('password_hash')) { - throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.'); - } - /** @noinspection PhpUndefinedConstantInspection */ - return password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]); - case 'crypt': - $salt = $this->generateSalt($cost); - $hash = crypt($password, $salt); - // strlen() is safe since crypt() returns only ascii - if (!is_string($hash) || strlen($hash) !== 60) { - throw new Exception('Unknown error occurred while generating hash.'); - } - return $hash; - default: - throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'"); + if ($cost === null) { + $cost = $this->passwordHashCost; } + + if (function_exists('password_hash')) { + /** @noinspection PhpUndefinedConstantInspection */ + return password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]); + } + + $salt = $this->generateSalt($cost); + $hash = crypt($password, $salt); + // strlen() is safe since crypt() returns only ascii + if (!is_string($hash) || strlen($hash) !== 60) { + throw new Exception('Unknown error occurred while generating hash.'); + } + + return $hash; } /** @@ -572,7 +630,6 @@ class Security extends Component * @param string $hash The hash to verify the password against. * @return boolean whether the password is correct. * @throws InvalidParamException on bad password/hash parameters or if crypt() with Blowfish hash is not available. - * @throws InvalidConfigException when an unsupported password hash strategy is configured. * @see generatePasswordHash() */ public function validatePassword($password, $hash) @@ -581,26 +638,24 @@ class Security extends Component throw new InvalidParamException('Password must be a string and cannot be empty.'); } - if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) { + if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) + || $matches[1] < 4 + || $matches[1] > 30 + ) { throw new InvalidParamException('Hash is invalid.'); } - switch ($this->passwordHashStrategy) { - case 'password_hash': - if (!function_exists('password_verify')) { - throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.'); - } - return password_verify($password, $hash); - case 'crypt': - $test = crypt($password, $hash); - $n = strlen($test); - if ($n !== 60) { - return false; - } - return $this->compareString($test, $hash); - default: - throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'"); + if (function_exists('password_verify')) { + return password_verify($password, $hash); } + + $test = crypt($password, $hash); + $n = strlen($test); + if ($n !== 60) { + return false; + } + + return $this->compareString($test, $hash); } /** diff --git a/framework/base/Theme.php b/framework/base/Theme.php index d079812d5c..f2ebb8f62b 100644 --- a/framework/base/Theme.php +++ b/framework/base/Theme.php @@ -33,14 +33,14 @@ use yii\helpers\FileHelper; * * It is possible to map a single path to multiple paths. For example, * - * ~~~ + * ```php * 'pathMap' => [ * '@app/views' => [ * '@app/themes/christmas', * '@app/themes/basic', * ], * ] - * ~~~ + * ``` * * In this case, the themed version could be either `@app/themes/christmas/site/index.php` or * `@app/themes/basic/site/index.php`. The former has precedence over the latter if both files exist. @@ -48,14 +48,14 @@ use yii\helpers\FileHelper; * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application * component like the following: * - * ~~~ + * ```php * 'view' => [ * 'theme' => [ * 'basePath' => '@app/themes/basic', * 'baseUrl' => '@web/themes/basic', * ], * ], - * ~~~ + * ``` * * The above configuration specifies a theme located under the "themes/basic" directory of the Web folder * that contains the entry script of the application. If your theme is designed to handle modules, @@ -64,7 +64,7 @@ use yii\helpers\FileHelper; * @property string $basePath The root path of this theme. All resources of this theme are located under this * directory. * @property string $baseUrl The base URL (without ending slash) for this theme. All resources of this theme - * are considered to be under this base URL. This property is read-only. + * are considered to be under this base URL. * * @author Qiang Xue * @since 2.0 @@ -81,6 +81,7 @@ class Theme extends Component private $_baseUrl; + /** * @return string the base URL (without ending slash) for this theme. All resources of this theme are considered * to be under this base URL. @@ -91,7 +92,7 @@ class Theme extends Component } /** - * @param $url string the base URL or path alias for this theme. All resources of this theme are considered + * @param string $url the base URL or path alias for this theme. All resources of this theme are considered * to be under this base URL. */ public function setBaseUrl($url) diff --git a/framework/base/View.php b/framework/base/View.php index 74b0515ab9..53c0eab5f4 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -56,12 +56,12 @@ class View extends Component * Each renderer may be a view renderer object or the configuration for creating the renderer object. * For example, the following configuration enables both Smarty and Twig view renderers: * - * ~~~ + * ```php * [ * 'tpl' => ['class' => 'yii\smarty\ViewRenderer'], * 'twig' => ['class' => 'yii\twig\ViewRenderer'], * ] - * ~~~ + * ``` * * If no renderer is available for the given view file, the view file will be treated as a normal PHP * and rendered via [[renderPhpFile()]]. @@ -404,11 +404,11 @@ class View extends Component * This method can be used to implement nested layout. For example, a layout can be embedded * in another layout file specified as '@app/views/layouts/base.php' like the following: * - * ~~~ + * ```php * beginContent('@app/views/layouts/base.php'); ?> - * ...layout content here... + * //...layout content here... * endContent(); ?> - * ~~~ + * ``` * * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget. * This can be specified as either the view file path or path alias. @@ -440,12 +440,12 @@ class View extends Component * call to end the cache and save the content into cache. * A typical usage of fragment caching is as follows, * - * ~~~ + * ```php * if ($this->beginCache($id)) { * // ...generate content here * $this->endCache(); * } - * ~~~ + * ``` * * @param string $id a unique ID identifying the fragment to be cached. * @param array $properties initial property values for [[FragmentCache]] diff --git a/framework/base/Widget.php b/framework/base/Widget.php index 3d41c13df9..b5575bd4e0 100644 --- a/framework/base/Widget.php +++ b/framework/base/Widget.php @@ -73,10 +73,10 @@ class Widget extends Component implements ViewContextInterface echo $widget->run(); return $widget; } else { - throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class()); + throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class()); } } else { - throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.'); + throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.'); } } @@ -85,27 +85,41 @@ class Widget extends Component implements ViewContextInterface * The widget rendering result is returned by this method. * @param array $config name-value pairs that will be used to initialize the object properties * @return string the rendering result of the widget. + * @throws \Exception */ public static function widget($config = []) { ob_start(); ob_implicit_flush(false); +<<<<<<< HEAD <<<<<<< HEAD /* @var $widget Widget */ $config['class'] = get_called_class(); $widget = Yii::createObject($config); $out = $widget->run(); ======= +======= +>>>>>>> master try { /* @var $widget Widget */ $config['class'] = get_called_class(); $widget = Yii::createObject($config); $out = $widget->run(); +<<<<<<< HEAD } catch(\Exception $e) { ob_end_clean(); throw $e; } >>>>>>> yiichina/master +======= + } catch (\Exception $e) { + // close the output buffer opened above if it has not been closed already + if (ob_get_level() > 0) { + ob_end_clean(); + } + throw $e; + } +>>>>>>> master return ob_get_clean() . $out; } diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php index b6a3baf2b6..d92b33d084 100644 --- a/framework/behaviors/AttributeBehavior.php +++ b/framework/behaviors/AttributeBehavior.php @@ -11,8 +11,10 @@ use Yii; use Closure; use yii\base\Behavior; use yii\base\Event; +use yii\db\ActiveRecord; /** +<<<<<<< HEAD <<<<<<< HEAD * AttributeBehavior automatically assigns a specified value to one or multiple attributes of an ActiveRecord object when certain events happen. * @@ -29,8 +31,17 @@ use yii\base\Event; * [[value]] property with a PHP callable whose return value will be used to assign to the current attribute(s). * For example, >>>>>>> yiichina/master +======= + * AttributeBehavior automatically assigns a specified value to one or multiple attributes of an ActiveRecord + * object when certain events happen. * - * ~~~ + * To use AttributeBehavior, configure the [[attributes]] property which should specify the list of attributes + * that need to be updated and the corresponding events that should trigger the update. Then configure the + * [[value]] property with a PHP callable whose return value will be used to assign to the current attribute(s). + * For example, +>>>>>>> master + * + * ```php * use yii\behaviors\AttributeBehavior; * * public function behaviors() @@ -48,7 +59,10 @@ use yii\base\Event; * ], * ]; * } - * ~~~ + * ``` + * + * Because attribute values will be set automatically, it's a good idea to make sure attribute names aren't + * in `rules()` method of the model. * * @author Luciano Baraglia * @author Qiang Xue @@ -71,8 +85,10 @@ class AttributeBehavior extends Behavior */ public $attributes = []; /** - * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function - * or an arbitrary value. If the former, the return value of the function will be assigned to the attributes. + * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function, + * callable in array format (e.g. `[$this, 'methodName']`), an [[Expression]] object representing a DB expression + * (e.g. `new Expression('NOW()')`), scalar, string or an arbitrary value. If the former, the return value of the + * function will be assigned to the attributes. * The signature of the function should be as follows, * * ```php @@ -83,6 +99,12 @@ class AttributeBehavior extends Behavior * ``` */ public $value; + /** + * @var boolean whether to skip this behavior when the `$owner` has not been + * modified + * @since 2.0.8 + */ + public $skipUpdateOnClean = true; /** @@ -90,7 +112,10 @@ class AttributeBehavior extends Behavior */ public function events() { - return array_fill_keys(array_keys($this->attributes), 'evaluateAttributes'); + return array_fill_keys( + array_keys($this->attributes), + 'evaluateAttributes' + ); } /** @@ -99,6 +124,13 @@ class AttributeBehavior extends Behavior */ public function evaluateAttributes($event) { + if ($this->skipUpdateOnClean + && $event->name == ActiveRecord::EVENT_BEFORE_UPDATE + && empty($this->owner->dirtyAttributes) + ) { + return; + } + if (!empty($this->attributes[$event->name])) { $attributes = (array) $this->attributes[$event->name]; $value = $this->getValue($event); @@ -112,7 +144,7 @@ class AttributeBehavior extends Behavior } /** - * Returns the value of the current attributes. + * Returns the value for the current attributes. * This method is called by [[evaluateAttributes()]]. Its return value will be assigned * to the attributes corresponding to the triggering event. * @param Event $event the event that triggers the current attribute updating. @@ -120,6 +152,10 @@ class AttributeBehavior extends Behavior */ protected function getValue($event) { - return $this->value instanceof Closure ? call_user_func($this->value, $event) : $this->value; + if ($this->value instanceof Closure || is_array($this->value) && is_callable($this->value)) { + return call_user_func($this->value, $event); + } + + return $this->value; } } diff --git a/framework/behaviors/BlameableBehavior.php b/framework/behaviors/BlameableBehavior.php index dbf670a4c0..0fb5a110a2 100644 --- a/framework/behaviors/BlameableBehavior.php +++ b/framework/behaviors/BlameableBehavior.php @@ -8,7 +8,6 @@ namespace yii\behaviors; use Yii; -use yii\base\Event; use yii\db\BaseActiveRecord; /** @@ -29,8 +28,13 @@ use yii\db\BaseActiveRecord; * * By default, BlameableBehavior will fill the `created_by` and `updated_by` attributes with the current user ID * when the associated AR object is being inserted; it will fill the `updated_by` attribute - * with the current user ID when the AR object is being updated. If your attribute names are different, you may configure - * the [[createdByAttribute]] and [[updatedByAttribute]] properties like the following: + * with the current user ID when the AR object is being updated. + * + * Because attribute values will be set automatically, it's a good idea to make sure `created_by` and `updated_by` aren't + * in `rules()` method of the model. + * + * If your attribute names are different, you may configure the [[createdByAttribute]] and [[updatedByAttribute]] + * properties like the following: * * ```php * public function behaviors() @@ -63,17 +67,9 @@ class BlameableBehavior extends AttributeBehavior */ public $updatedByAttribute = 'updated_by'; /** - * @var callable the value that will be assigned to the attributes. This should be a valid - * PHP callable whose return value will be assigned to the current attribute(s). - * The signature of the callable should be: + * @inheritdoc * - * ```php - * function ($event) { - * // return value will be assigned to the attribute(s) - * } - * ``` - * - * If this property is not set, the value of `Yii::$app->user->id` will be assigned to the attribute(s). + * In case, when the property is `null`, the value of `Yii::$app->user->id` will be used as the value. */ public $value; @@ -94,18 +90,17 @@ class BlameableBehavior extends AttributeBehavior } /** - * Evaluates the value of the user. - * The return result of this method will be assigned to the current attribute(s). - * @param Event $event - * @return mixed the value of the user. + * @inheritdoc + * + * In case, when the [[value]] property is `null`, the value of `Yii::$app->user->id` will be used as the value. */ protected function getValue($event) { if ($this->value === null) { $user = Yii::$app->get('user', false); return $user && !$user->isGuest ? $user->id : null; - } else { - return call_user_func($this->value, $event); } + + return parent::getValue($event); } } diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 79d38cf893..fa1ad9c519 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -34,8 +34,12 @@ use Yii; * ``` * * By default, SluggableBehavior will fill the `slug` attribute with a value that can be used a slug in a URL - * when the associated AR object is being validated. If your attribute name is different, you may configure - * the [[slugAttribute]] property like the following: + * when the associated AR object is being validated. + * + * Because attribute value will be set automatically, it's a good idea to make sure `slug` isn't + * in `rules()` method of the model. + * + * If your attribute name is different, you may configure the [[slugAttribute]] property like the following: * * ```php * public function behaviors() @@ -131,46 +135,81 @@ class SluggableBehavior extends AttributeBehavior */ protected function getValue($event) { - $isNewSlug = true; - if ($this->attribute !== null) { - $attributes = (array) $this->attribute; - /* @var $owner BaseActiveRecord */ - $owner = $this->owner; - if (!empty($owner->{$this->slugAttribute})) { - $isNewSlug = false; - if (!$this->immutable) { - foreach ($attributes as $attribute) { - if ($owner->isAttributeChanged($attribute)) { - $isNewSlug = true; - break; - } - } - } - } - - if ($isNewSlug) { + if ($this->isNewSlugNeeded()) { $slugParts = []; - foreach ($attributes as $attribute) { - $slugParts[] = $owner->{$attribute}; + foreach ((array) $this->attribute as $attribute) { + $slugParts[] = $this->owner->{$attribute}; } - $slug = Inflector::slug(implode('-', $slugParts)); + + $slug = $this->generateSlug($slugParts); } else { - $slug = $owner->{$this->slugAttribute}; + return $this->owner->{$this->slugAttribute}; } } else { $slug = parent::getValue($event); } - if ($this->ensureUnique && $isNewSlug) { - $baseSlug = $slug; - $iteration = 0; - while (!$this->validateSlug($slug)) { - $iteration++; - $slug = $this->generateUniqueSlug($baseSlug, $iteration); + return $this->ensureUnique ? $this->makeUnique($slug) : $slug; + } + + /** + * Checks whether the new slug generation is needed + * This method is called by [[getValue]] to check whether the new slug generation is needed. + * You may override it to customize checking. + * @return boolean + * @since 2.0.7 + */ + protected function isNewSlugNeeded() + { + if (empty($this->owner->{$this->slugAttribute})) { + return true; + } + + if ($this->immutable) { + return false; + } + + foreach ((array)$this->attribute as $attribute) { + if ($this->owner->isAttributeChanged($attribute)) { + return true; } } - return $slug; + + return false; + } + + /** + * This method is called by [[getValue]] to generate the slug. + * You may override it to customize slug generation. + * The default implementation calls [[\yii\helpers\Inflector::slug()]] on the input strings + * concatenated by dashes (`-`). + * @param array $slugParts an array of strings that should be concatenated and converted to generate the slug value. + * @return string the conversion result. + */ + protected function generateSlug($slugParts) + { + return Inflector::slug(implode('-', $slugParts)); + } + + /** + * This method is called by [[getValue]] when [[ensureUnique]] is true to generate the unique slug. + * Calls [[generateUniqueSlug]] until generated slug is unique and returns it. + * @param string $slug basic slug value + * @return string unique slug + * @see getValue + * @see generateUniqueSlug + * @since 2.0.7 + */ + protected function makeUnique($slug) + { + $uniqueSlug = $slug; + $iteration = 0; + while (!$this->validateSlug($uniqueSlug)) { + $iteration++; + $uniqueSlug = $this->generateUniqueSlug($slug, $iteration); + } + return $uniqueSlug; } /** @@ -178,13 +217,13 @@ class SluggableBehavior extends AttributeBehavior * @param string $slug slug value * @return boolean whether slug is unique. */ - private function validateSlug($slug) + protected function validateSlug($slug) { /* @var $validator UniqueValidator */ /* @var $model BaseActiveRecord */ $validator = Yii::createObject(array_merge( [ - 'class' => UniqueValidator::className() + 'class' => UniqueValidator::className(), ], $this->uniqueValidator )); @@ -204,12 +243,11 @@ class SluggableBehavior extends AttributeBehavior * @return string new slug value * @throws \yii\base\InvalidConfigException */ - private function generateUniqueSlug($baseSlug, $iteration) + protected function generateUniqueSlug($baseSlug, $iteration) { if (is_callable($this->uniqueSlugGenerator)) { return call_user_func($this->uniqueSlugGenerator, $baseSlug, $iteration, $this->owner); - } else { - return $baseSlug . '-' . ($iteration + 1); } + return $baseSlug . '-' . ($iteration + 1); } } diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php index b3e2866a5f..5d745186dd 100644 --- a/framework/behaviors/TimestampBehavior.php +++ b/framework/behaviors/TimestampBehavior.php @@ -7,8 +7,8 @@ namespace yii\behaviors; +use yii\base\InvalidCallException; use yii\db\BaseActiveRecord; -use yii\db\Expression; /** * TimestampBehavior automatically fills the specified attributes with the current timestamp. @@ -30,6 +30,11 @@ use yii\db\Expression; * when the associated AR object is being inserted; it will fill the `updated_at` attribute * with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`. * + * Because attribute values will be set automatically, it's a good idea to make sure `created_at` and `updated_at` aren't + * in `rules()` method of the model. + * + * For the above implementation to work with MySQL database, please declare the columns(`created_at`, `updated_at`) as int(11) for being UNIX timestamp. + * * If your attribute names are different or you want to use a different way of calculating the timestamp, * you may configure the [[createdAtAttribute]], [[updatedAtAttribute]] and [[value]] properties like the following: * @@ -49,7 +54,7 @@ use yii\db\Expression; * } * ``` * - * In case you use an [[Expression]] object as in the example above, the attribute will not hold the timestamp value, but + * In case you use an [[\yii\db\Expression]] object as in the example above, the attribute will not hold the timestamp value, but * the Expression object itself after the record has been saved. If you need the value from DB afterwards you should call * the [[\yii\db\ActiveRecord::refresh()|refresh()]] method of the record. * @@ -77,10 +82,10 @@ class TimestampBehavior extends AttributeBehavior */ public $updatedAtAttribute = 'updated_at'; /** - * @var callable|Expression The expression that will be used for generating the timestamp. - * This can be either an anonymous function that returns the timestamp value, - * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`). - * If not set, it will use the value of `time()` to set the attributes. + * @inheritdoc + * + * In case, when the value is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) + * will be used as value. */ public $value; @@ -102,14 +107,16 @@ class TimestampBehavior extends AttributeBehavior /** * @inheritdoc + * + * In case, when the [[value]] is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) + * will be used as value. */ protected function getValue($event) { - if ($this->value instanceof Expression) { - return $this->value; - } else { - return $this->value !== null ? call_user_func($this->value, $event) : time(); + if ($this->value === null) { + return time(); } + return parent::getValue($event); } /** @@ -119,9 +126,15 @@ class TimestampBehavior extends AttributeBehavior * $model->touch('lastVisit'); * ``` * @param string $attribute the name of the attribute to update. + * @throws InvalidCallException if owner is a new record (since version 2.0.6). */ public function touch($attribute) { - $this->owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); + /* @var $owner BaseActiveRecord */ + $owner = $this->owner; + if ($owner->getIsNewRecord()) { + throw new InvalidCallException('Updating the timestamp is not possible on a new record.'); + } + $owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); } } diff --git a/framework/caching/ApcCache.php b/framework/caching/ApcCache.php index c7d1e3599a..60a342edb5 100644 --- a/framework/caching/ApcCache.php +++ b/framework/caching/ApcCache.php @@ -7,11 +7,14 @@ namespace yii\caching; +use yii\base\InvalidConfigException; + /** * ApcCache provides APC caching in terms of an application component. * * To use this application component, the [APC PHP extension](http://www.php.net/apc) must be loaded. - * In order to enable APC for CLI you should add "apc.enable_cli = 1" to your php.ini. + * Alternatively [APCu PHP extension](http://www.php.net/apcu) could be used via setting `useApcu` to `true`. + * In order to enable APC or APCu for CLI you should add "apc.enable_cli = 1" to your php.ini. * * See [[Cache]] for common cache operations that ApcCache supports. * @@ -20,6 +23,29 @@ namespace yii\caching; */ class ApcCache extends Cache { + /** + * @var boolean whether to use apcu or apc as the underlying caching extension. + * If true, [apcu](http://pecl.php.net/package/apcu) will be used. + * If false, [apc](http://pecl.php.net/package/apc) will be used. + * Defaults to false. + * @since 2.0.7 + */ + public $useApcu = false; + + + /** + * Initializes this application component. + * It checks if extension required is loaded. + */ + public function init() + { + parent::init(); + $extension = $this->useApcu ? 'apcu' : 'apc'; + if (!extension_loaded($extension)) { + throw new InvalidConfigException("ApcCache requires PHP $extension extension to be loaded."); + } + } + /** * Checks whether a specified key exists in the cache. * This can be faster than getting the value from the cache if the data is big. @@ -34,7 +60,7 @@ class ApcCache extends Cache { $key = $this->buildKey($key); - return apc_exists($key); + return $this->useApcu ? apcu_exists($key) : apc_exists($key); } /** @@ -45,7 +71,7 @@ class ApcCache extends Cache */ protected function getValue($key) { - return apc_fetch($key); + return $this->useApcu ? apcu_fetch($key) : apc_fetch($key); } /** @@ -55,7 +81,7 @@ class ApcCache extends Cache */ protected function getValues($keys) { - $values = apc_fetch($keys); + $values = $this->useApcu ? apcu_fetch($keys) : apc_fetch($keys); return is_array($values) ? $values : []; } @@ -70,7 +96,7 @@ class ApcCache extends Cache */ protected function setValue($key, $value, $duration) { - return apc_store($key, $value, $duration); + return $this->useApcu ? apcu_store($key, $value, $duration) : apc_store($key, $value, $duration); } /** @@ -81,7 +107,7 @@ class ApcCache extends Cache */ protected function setValues($data, $duration) { - $result = apc_store($data, null, $duration); + $result = $this->useApcu ? apcu_store($data, null, $duration) : apc_store($data, null, $duration); return is_array($result) ? array_keys($result) : []; } @@ -95,7 +121,7 @@ class ApcCache extends Cache */ protected function addValue($key, $value, $duration) { - return apc_add($key, $value, $duration); + return $this->useApcu ? apcu_add($key, $value, $duration) : apc_add($key, $value, $duration); } /** @@ -106,7 +132,7 @@ class ApcCache extends Cache */ protected function addValues($data, $duration) { - $result = apc_add($data, null, $duration); + $result = $this->useApcu ? apcu_add($data, null, $duration) : apc_add($data, null, $duration); return is_array($result) ? array_keys($result) : []; } @@ -118,7 +144,7 @@ class ApcCache extends Cache */ protected function deleteValue($key) { - return apc_delete($key); + return $this->useApcu ? apcu_delete($key) : apc_delete($key); } /** @@ -128,10 +154,6 @@ class ApcCache extends Cache */ protected function flushValues() { - if (extension_loaded('apcu')) { - return apc_clear_cache(); - } else { - return apc_clear_cache('user'); - } + return $this->useApcu ? apcu_clear_cache() : apc_clear_cache('user'); } } diff --git a/framework/caching/ArrayCache.php b/framework/caching/ArrayCache.php index 7295fc06e2..fc255a8eb9 100644 --- a/framework/caching/ArrayCache.php +++ b/framework/caching/ArrayCache.php @@ -12,7 +12,7 @@ namespace yii\caching; * * See [[Cache]] for common cache operations that ArrayCache supports. * - * Unlike the [[Cache]], ArrayCache allows the expire parameter of [[set]], [[add]], [[mset]] and [[madd]] to + * Unlike the [[Cache]], ArrayCache allows the expire parameter of [[set]], [[add]], [[multiSet]] and [[multiAdd]] to * be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds). * * @author Carsten Brandt diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index dce5ccc9ff..2b23b9a0bc 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -30,7 +30,7 @@ use yii\helpers\StringHelper; * } * ``` * - * Because Cache implements the ArrayAccess interface, it can be used like an array. For example, + * Because Cache implements the [[\ArrayAccess]] interface, it can be used like an array. For example, * * ```php * $cache['foo'] = 'some data'; @@ -141,12 +141,30 @@ abstract class Cache extends Component implements \ArrayAccess * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time, * which may improve the performance. In case a cache does not support this feature natively, * this method will try to simulate it. + * * @param string[] $keys list of string keys identifying the cached values * @return array list of cached values corresponding to the specified keys. The array * is returned in terms of (key, value) pairs. * If a value is not cached or expired, the corresponding array value will be false. + * @deprecated This method is an alias for [[multiGet()]] and will be removed in 2.1.0. */ public function mget($keys) + { + return $this->multiGet($keys); + } + + /** + * Retrieves multiple values from cache with the specified keys. + * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time, + * which may improve the performance. In case a cache does not support this feature natively, + * this method will try to simulate it. + * @param string[] $keys list of string keys identifying the cached values + * @return array list of cached values corresponding to the specified keys. The array + * is returned in terms of (key, value) pairs. + * If a value is not cached or expired, the corresponding array value will be false. + * @since 2.0.7 + */ + public function multiGet($keys) { $keyMap = []; foreach ($keys as $key) { @@ -213,8 +231,27 @@ abstract class Cache extends Component implements \ArrayAccess * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. * This parameter is ignored if [[serializer]] is false. * @return boolean whether the items are successfully stored into cache + * @deprecated This method is an alias for [[multiSet()]] and will be removed in 2.1.0. */ public function mset($items, $duration = 0, $dependency = null) + { + return $this->multiSet($items, $duration, $dependency); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and + * expiration time will be replaced with the new ones, respectively. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $duration default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + * @since 2.0.7 + */ + public function multiSet($items, $duration = 0, $dependency = null) { if ($dependency !== null && $this->serializer !== false) { $dependency->evaluateDependency($this); @@ -245,8 +282,26 @@ abstract class Cache extends Component implements \ArrayAccess * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. * This parameter is ignored if [[serializer]] is false. * @return boolean whether the items are successfully stored into cache + * @deprecated This method is an alias for [[multiAdd()]] and will be removed in 2.1.0. */ public function madd($items, $duration = 0, $dependency = null) + { + return $this->multiAdd($items, $duration, $dependency); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and expiration time will be preserved. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $duration default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + * @since 2.0.7 + */ + public function multiAdd($items, $duration = 0, $dependency = null) { if ($dependency !== null && $this->serializer !== false) { $dependency->evaluateDependency($this); @@ -423,7 +478,7 @@ abstract class Cache extends Component implements \ArrayAccess /** * Returns whether there is a cache entry with a specified key. - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param string $key a key identifying the cached value * @return boolean */ @@ -434,7 +489,7 @@ abstract class Cache extends Component implements \ArrayAccess /** * Retrieves the value from cache with a specified key. - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param string $key a key identifying the cached value * @return mixed the value stored in cache, false if the value is not in the cache or expired. */ @@ -447,7 +502,7 @@ abstract class Cache extends Component implements \ArrayAccess * Stores the value identified by a key into cache. * If the cache already contains such a key, the existing value will be * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method. - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param string $key the key identifying the value to be cached * @param mixed $value the value to be cached */ @@ -458,7 +513,7 @@ abstract class Cache extends Component implements \ArrayAccess /** * Deletes the value with the specified key from cache - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param string $key the key of the value to be deleted */ public function offsetUnset($key) diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index b27a82cee9..8c1fcbbcea 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -47,13 +47,13 @@ class DbCache extends Cache * @var string name of the DB table to store cache content. * The table should be pre-created as follows: * - * ~~~ + * ```php * CREATE TABLE cache ( * id char(128) NOT NULL PRIMARY KEY, * expire int(11), * data BLOB * ); - * ~~~ + * ``` * * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type * that can be used for some popular DBMS: diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php index e5c16768bd..3488b21075 100644 --- a/framework/caching/DbDependency.php +++ b/framework/caching/DbDependency.php @@ -49,7 +49,7 @@ class DbDependency extends Dependency { $db = Instance::ensure($this->db, Connection::className()); if ($this->sql === null) { - throw new InvalidConfigException("DbDependency::sql must be set."); + throw new InvalidConfigException('DbDependency::sql must be set.'); } if ($db->enableQueryCache) { diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php index deae61cd3b..a26d97bed2 100644 --- a/framework/caching/FileCache.php +++ b/framework/caching/FileCache.php @@ -107,11 +107,19 @@ class FileCache extends Cache protected function getValue($key) { $cacheFile = $this->getCacheFile($key); + if (@filemtime($cacheFile) > time()) { - return @file_get_contents($cacheFile); - } else { - return false; + $fp = @fopen($cacheFile, 'r'); + if ($fp !== false) { + @flock($fp, LOCK_SH); + $cacheValue = @stream_get_contents($fp); + @flock($fp, LOCK_UN); + @fclose($fp); + return $cacheValue; + } } + + return false; } /** @@ -125,6 +133,7 @@ class FileCache extends Cache */ protected function setValue($key, $value, $duration) { + $this->gc(); $cacheFile = $this->getCacheFile($key); if ($this->directoryLevel > 0) { @FileHelper::createDirectory(dirname($cacheFile), $this->dirMode, true); @@ -139,6 +148,8 @@ class FileCache extends Cache return @touch($cacheFile, $duration + time()); } else { + $error = error_get_last(); + Yii::warning("Unable to write cache file '{$cacheFile}': {$error['message']}", __METHOD__); return false; } } diff --git a/framework/caching/FileDependency.php b/framework/caching/FileDependency.php index 743aa85cfb..dc77464668 100644 --- a/framework/caching/FileDependency.php +++ b/framework/caching/FileDependency.php @@ -13,7 +13,7 @@ use yii\base\InvalidConfigException; /** * FileDependency represents a dependency based on a file's last modification time. * - * If th last modification time of the file specified via [[fileName]] is changed, + * If the last modification time of the file specified via [[fileName]] is changed, * the dependency is considered as changed. * * @author Qiang Xue diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index b46be1204c..6a1207ae99 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -7,6 +7,7 @@ namespace yii\caching; +use Yii; use yii\base\InvalidConfigException; /** @@ -27,7 +28,7 @@ use yii\base\InvalidConfigException; * * To use MemCache as the cache application component, configure the application as follows, * - * ~~~ + * ```php * [ * 'components' => [ * 'cache' => [ @@ -47,7 +48,7 @@ use yii\base\InvalidConfigException; * ], * ], * ] - * ~~~ + * ``` * * In the above, two memcache servers are used: server1 and server2. You can configure more properties of * each server, such as `persistent`, `weight`, `timeout`. Please see [[MemCacheServer]] for available options. @@ -96,7 +97,7 @@ class MemCache extends Cache /** * @var \Memcache|\Memcached the Memcache instance */ - private $_cache = null; + private $_cache; /** * @var array list of memcache server configurations */ @@ -114,8 +115,10 @@ class MemCache extends Cache } /** + * Add servers to the server pool of the cache specified + * * @param \Memcache|\Memcached $cache - * @param array $servers + * @param MemCacheServer[] $servers * @throws InvalidConfigException */ protected function addServers($cache, $servers) @@ -140,8 +143,11 @@ class MemCache extends Cache } /** + * Add servers to the server pool of the cache specified + * Used for memcached PECL extension. + * * @param \Memcached $cache - * @param array $servers + * @param MemCacheServer[] $servers */ protected function addMemcachedServers($cache, $servers) { @@ -159,8 +165,11 @@ class MemCache extends Cache } /** + * Add servers to the server pool of the cache specified + * Used for memcache PECL extension. + * * @param \Memcache $cache - * @param array $servers + * @param MemCacheServer[] $servers */ protected function addMemcacheServers($cache, $servers) { @@ -170,7 +179,7 @@ class MemCache extends Cache // $timeout is used for memcache versions that do not have $timeoutms parameter $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0); if ($paramCount === 9) { - $cache->addServer( + $cache->addserver( $server->host, $server->port, $server->persistent, @@ -182,7 +191,7 @@ class MemCache extends Cache $server->timeout ); } else { - $cache->addServer( + $cache->addserver( $server->host, $server->port, $server->persistent, @@ -280,6 +289,7 @@ class MemCache extends Cache */ protected function setValue($key, $value, $duration) { + $duration = $this->trimDuration($duration); $expire = $duration > 0 ? $duration + time() : 0; return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire); @@ -293,6 +303,8 @@ class MemCache extends Cache */ protected function setValues($data, $duration) { + $duration = $this->trimDuration($duration); + if ($this->useMemcached) { $this->_cache->setMulti($data, $duration > 0 ? $duration + time() : 0); @@ -313,6 +325,7 @@ class MemCache extends Cache */ protected function addValue($key, $value, $duration) { + $duration = $this->trimDuration($duration); $expire = $duration > 0 ? $duration + time() : 0; return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire); @@ -338,4 +351,21 @@ class MemCache extends Cache { return $this->_cache->flush(); } + + /** + * Trims duration to 30 days (2592000 seconds). + * @param integer $duration the number of seconds + * @return integer the duration + * @since 2.0.7 + * @see http://php.net/manual/en/memcache.set.php + * @see http://php.net/manual/en/memcached.expiration.php + */ + protected function trimDuration($duration) + { + if ($duration > 2592000) { + Yii::warning('Duration has been truncated to 30 days due to Memcache/Memcached limitation.', __METHOD__); + return 2592000; + } + return $duration; + } } diff --git a/framework/caching/TagDependency.php b/framework/caching/TagDependency.php index 5caa8fa91c..40315e98c7 100644 --- a/framework/caching/TagDependency.php +++ b/framework/caching/TagDependency.php @@ -12,6 +12,15 @@ namespace yii\caching; * * By calling [[invalidate()]], you can invalidate all cached data items that are associated with the specified tag name(s). * + * ```php + * // setting multiple cache keys to store data forever and tagging them with "user-123" + * Yii::$app->cache->set('user_42_profile', '', 0, new TagDependency(['tags' => 'user-123'])); + * Yii::$app->cache->set('user_42_stats', '', 0, new TagDependency(['tags' => 'user-123'])); + * + * // invalidating all keys tagged with "user-123" + * TagDependency::invalidate(Yii::$app->cache, 'user-123'); + * ``` + * * @author Qiang Xue * @since 2.0 */ @@ -40,7 +49,7 @@ class TagDependency extends Dependency } } if (!empty($newKeys)) { - $timestamps = array_merge($timestamps, $this->touchKeys($cache, $newKeys)); + $timestamps = array_merge($timestamps, static::touchKeys($cache, $newKeys)); } return $timestamps; @@ -84,7 +93,7 @@ class TagDependency extends Dependency foreach ($keys as $key) { $items[$key] = $time; } - $cache->mset($items); + $cache->multiSet($items); return $items; } @@ -105,6 +114,6 @@ class TagDependency extends Dependency $keys[] = $cache->buildKey([__CLASS__, $tag]); } - return $cache->mget($keys); + return $cache->multiGet($keys); } } diff --git a/framework/caching/migrations/m150909_153426_cache_init.php b/framework/caching/migrations/m150909_153426_cache_init.php new file mode 100644 index 0000000000..4be6cbeaff --- /dev/null +++ b/framework/caching/migrations/m150909_153426_cache_init.php @@ -0,0 +1,66 @@ + + * @since 2.0.7 + */ +class m150909_153426_cache_init extends Migration +{ + + /** + * @throws yii\base\InvalidConfigException + * @return DbCache + */ + protected function getCache() + { + $cache = Yii::$app->getCache(); + if (!$cache instanceof DbCache) { + throw new InvalidConfigException('You should configure "cache" component to use database before executing this migration.'); + } + return $cache; + } + + /** + * @inheritdoc + */ + public function up() + { + $cache = $this->getCache(); + $this->db = $cache->db; + + $tableOptions = null; + if ($this->db->driverName === 'mysql') { + // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; + } + + $this->createTable($cache->cacheTable, [ + 'id' => $this->string(128)->notNull(), + 'expire' => $this->integer(), + 'data' => $this->binary(), + 'PRIMARY KEY ([[id]])', + ], $tableOptions); + } + + /** + * @inheritdoc + */ + public function down() + { + $cache = $this->getCache(); + $this->db = $cache->db; + + $this->dropTable($cache->cacheTable); + } +} diff --git a/framework/caching/migrations/schema-mssql.sql b/framework/caching/migrations/schema-mssql.sql new file mode 100644 index 0000000000..62bb95ea7e --- /dev/null +++ b/framework/caching/migrations/schema-mssql.sql @@ -0,0 +1,20 @@ +/** + * Database schema required by \yii\caching\DbCache. + * + * @author Qiang Xue + * @author Misbahul D Munir + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists [cache]; + +create table [cache] +( + [id] varchar(128) not null, + [expire] integer, + [data] BLOB, + primary key ([id]) +); diff --git a/framework/caching/migrations/schema-mysql.sql b/framework/caching/migrations/schema-mysql.sql new file mode 100644 index 0000000000..b1ba454a03 --- /dev/null +++ b/framework/caching/migrations/schema-mysql.sql @@ -0,0 +1,20 @@ +/** + * Database schema required by \yii\caching\DbCache. + * + * @author Qiang Xue + * @author Misbahul D Munir + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists `cache`; + +create table `cache` +( + `id` varchar(128) not null, + `expire` integer, + `data` LONGBLOB, + primary key (`id`) +) engine InnoDB; diff --git a/framework/caching/migrations/schema-oci.sql b/framework/caching/migrations/schema-oci.sql new file mode 100644 index 0000000000..bc5178008a --- /dev/null +++ b/framework/caching/migrations/schema-oci.sql @@ -0,0 +1,20 @@ +/** + * Database schema required by \yii\caching\DbCache. + * + * @author Qiang Xue + * @author Misbahul D Munir + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists "cache"; + +create table "cache" +( + "id" varchar(128) not null, + "expire" integer, + "data" BYTEA, + primary key ("id") +); diff --git a/framework/caching/migrations/schema-pgsql.sql b/framework/caching/migrations/schema-pgsql.sql new file mode 100644 index 0000000000..8d510b9af0 --- /dev/null +++ b/framework/caching/migrations/schema-pgsql.sql @@ -0,0 +1,20 @@ +/** + * Database schema required by \yii\caching\DbCache. + * + * @author Qiang Xue + * @author Misbahul D Munir + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists "cache"; + +create table "cache" +( + "id" varchar(128) not null, + "expire" integer, + "data" bytea, + primary key ("id") +); diff --git a/framework/caching/migrations/schema-sqlite.sql b/framework/caching/migrations/schema-sqlite.sql new file mode 100644 index 0000000000..63349d3d02 --- /dev/null +++ b/framework/caching/migrations/schema-sqlite.sql @@ -0,0 +1,20 @@ +/** + * Database schema required by \yii\caching\DbCache. + * + * @author Qiang Xue + * @author Misbahul D Munir + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists "cache"; + +create table "cache" +( + "id" varchar(128) not null, + "expire" integer, + "data" BLOB, + primary key ("id") +); diff --git a/framework/captcha/Captcha.php b/framework/captcha/Captcha.php index 450018730b..0875614927 100644 --- a/framework/captcha/Captcha.php +++ b/framework/captcha/Captcha.php @@ -50,7 +50,7 @@ use yii\widgets\InputWidget; * method, for example like this: * * ```php - * field($model, 'captcha')->widget(\yii\widgets\Captcha::classname(), [ + * field($model, 'captcha')->widget(\yii\captcha\Captcha::classname(), [ * // configure additional widget properties here * ]) ?> * ``` @@ -91,7 +91,7 @@ class Captcha extends InputWidget { parent::init(); - $this->checkRequirements(); + static::checkRequirements(); if (!isset($this->imageOptions['id'])) { $this->imageOptions['id'] = $this->options['id'] . '-image'; @@ -128,11 +128,15 @@ class Captcha extends InputWidget public function registerClientScript() { $options = $this->getClientOptions(); +<<<<<<< HEAD <<<<<<< HEAD $options = empty($options) ? '' : Json::encode($options); ======= $options = empty($options) ? '' : Json::htmlEncode($options); >>>>>>> yiichina/master +======= + $options = empty($options) ? '' : Json::htmlEncode($options); +>>>>>>> master $id = $this->imageOptions['id']; $view = $this->getView(); CaptchaAsset::register($view); @@ -154,7 +158,7 @@ class Captcha extends InputWidget $options = [ 'refreshUrl' => Url::toRoute($route), - 'hashKey' => "yiiCaptcha/{$route[0]}", + 'hashKey' => 'yiiCaptcha/' . trim($route[0], '/'), ]; return $options; @@ -169,9 +173,8 @@ class Captcha extends InputWidget public static function checkRequirements() { if (extension_loaded('imagick')) { - $imagick = new \Imagick(); - $imagickFormats = $imagick->queryFormats('PNG'); - if (in_array('PNG', $imagickFormats)) { + $imagickFormats = (new \Imagick())->queryFormats('PNG'); + if (in_array('PNG', $imagickFormats, true)) { return 'imagick'; } } diff --git a/framework/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php index 93b1dcb604..20586cbda9 100644 --- a/framework/captcha/CaptchaAction.php +++ b/framework/captcha/CaptchaAction.php @@ -98,6 +98,12 @@ class CaptchaAction extends Action * If not set, it means the verification code will be randomly generated. */ public $fixedVerifyCode; + /** + * @var string the rendering library to use. Currently supported only 'gd' and 'imagick'. + * If not set, library will be determined automatically. + * @since 2.0.7 + */ + public $imageLibrary; /** @@ -236,13 +242,21 @@ class CaptchaAction extends Action * Renders the CAPTCHA image. * @param string $code the verification code * @return string image contents + * @throws InvalidConfigException if imageLibrary is not supported */ protected function renderImage($code) { - if (Captcha::checkRequirements() === 'gd') { - return $this->renderImageByGD($code); + if (isset($this->imageLibrary)) { + $imageLibrary = $this->imageLibrary; } else { + $imageLibrary = Captcha::checkRequirements(); + } + if ($imageLibrary === 'gd') { + return $this->renderImageByGD($code); + } elseif ($imageLibrary === 'imagick') { return $this->renderImageByImagick($code); + } else { + throw new InvalidConfigException("Defined library '{$imageLibrary}' is not supported"); } } @@ -261,7 +275,7 @@ class CaptchaAction extends Action (int) ($this->backColor % 0x10000 / 0x100), $this->backColor % 0x100 ); - imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor); + imagefilledrectangle($image, 0, 0, $this->width - 1, $this->height - 1, $backColor); imagecolordeallocate($image, $backColor); if ($this->transparent) { @@ -306,8 +320,8 @@ class CaptchaAction extends Action */ protected function renderImageByImagick($code) { - $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor)); - $foreColor = new \ImagickPixel('#' . dechex($this->foreColor)); + $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . str_pad(dechex($this->backColor), 6, 0, STR_PAD_LEFT)); + $foreColor = new \ImagickPixel('#' . str_pad(dechex($this->foreColor), 6, 0, STR_PAD_LEFT)); $image = new \Imagick(); $image->newImage($this->width, $this->height, $backColor); @@ -318,8 +332,8 @@ class CaptchaAction extends Action $fontMetrics = $image->queryFontMetrics($draw, $code); $length = strlen($code); - $w = (int) ($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1); - $h = (int) ($fontMetrics['textHeight']) - 8; + $w = (int) $fontMetrics['textWidth'] - 8 + $this->offset * ($length - 1); + $h = (int) $fontMetrics['textHeight'] - 8; $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h); $x = 10; $y = round($this->height * 27 / 40); @@ -330,7 +344,7 @@ class CaptchaAction extends Action $draw->setFillColor($foreColor); $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]); $fontMetrics = $image->queryFontMetrics($draw, $code[$i]); - $x += (int) ($fontMetrics['textWidth']) + $this->offset; + $x += (int) $fontMetrics['textWidth'] + $this->offset; } $image->setImageFormat('png'); diff --git a/framework/captcha/CaptchaValidator.php b/framework/captcha/CaptchaValidator.php index 14a14abe65..068ae0505f 100644 --- a/framework/captcha/CaptchaValidator.php +++ b/framework/captcha/CaptchaValidator.php @@ -91,7 +91,7 @@ class CaptchaValidator extends Validator $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code)); $options = [ 'hash' => $hash, - 'hashKey' => 'yiiCaptcha/' . $this->captchaAction, + 'hashKey' => 'yiiCaptcha/' . $captcha->getUniqueId(), 'caseSensitive' => $this->caseSensitive, 'message' => Yii::$app->getI18n()->format($this->message, [ 'attribute' => $object->getAttributeLabel($attribute), diff --git a/framework/classes.php b/framework/classes.php index c654fee953..8ed79d750d 100644 --- a/framework/classes.php +++ b/framework/classes.php @@ -95,6 +95,7 @@ return [ 'yii\db\BaseActiveRecord' => YII2_PATH . '/db/BaseActiveRecord.php', 'yii\db\BatchQueryResult' => YII2_PATH . '/db/BatchQueryResult.php', 'yii\db\ColumnSchema' => YII2_PATH . '/db/ColumnSchema.php', + 'yii\db\ColumnSchemaBuilder' => YII2_PATH . '/db/ColumnSchemaBuilder.php', 'yii\db\Command' => YII2_PATH . '/db/Command.php', 'yii\db\Connection' => YII2_PATH . '/db/Connection.php', 'yii\db\DataReader' => YII2_PATH . '/db/DataReader.php', @@ -108,9 +109,11 @@ return [ 'yii\db\QueryInterface' => YII2_PATH . '/db/QueryInterface.php', 'yii\db\QueryTrait' => YII2_PATH . '/db/QueryTrait.php', 'yii\db\Schema' => YII2_PATH . '/db/Schema.php', + 'yii\db\SchemaBuilderTrait' => YII2_PATH . '/db/SchemaBuilderTrait.php', 'yii\db\StaleObjectException' => YII2_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII2_PATH . '/db/TableSchema.php', 'yii\db\Transaction' => YII2_PATH . '/db/Transaction.php', + 'yii\db\cubrid\ColumnSchemaBuilder' => YII2_PATH . '/db/cubrid/ColumnSchemaBuilder.php', 'yii\db\cubrid\QueryBuilder' => YII2_PATH . '/db/cubrid/QueryBuilder.php', 'yii\db\cubrid\Schema' => YII2_PATH . '/db/cubrid/Schema.php', 'yii\db\mssql\PDO' => YII2_PATH . '/db/mssql/PDO.php', @@ -118,12 +121,15 @@ return [ 'yii\db\mssql\Schema' => YII2_PATH . '/db/mssql/Schema.php', 'yii\db\mssql\SqlsrvPDO' => YII2_PATH . '/db/mssql/SqlsrvPDO.php', 'yii\db\mssql\TableSchema' => YII2_PATH . '/db/mssql/TableSchema.php', + 'yii\db\mysql\ColumnSchemaBuilder' => YII2_PATH . '/db/mysql/ColumnSchemaBuilder.php', 'yii\db\mysql\QueryBuilder' => YII2_PATH . '/db/mysql/QueryBuilder.php', 'yii\db\mysql\Schema' => YII2_PATH . '/db/mysql/Schema.php', + 'yii\db\oci\ColumnSchemaBuilder' => YII2_PATH . '/db/oci/ColumnSchemaBuilder.php', 'yii\db\oci\QueryBuilder' => YII2_PATH . '/db/oci/QueryBuilder.php', 'yii\db\oci\Schema' => YII2_PATH . '/db/oci/Schema.php', 'yii\db\pgsql\QueryBuilder' => YII2_PATH . '/db/pgsql/QueryBuilder.php', 'yii\db\pgsql\Schema' => YII2_PATH . '/db/pgsql/Schema.php', + 'yii\db\sqlite\ColumnSchemaBuilder' => YII2_PATH . '/db/sqlite/ColumnSchemaBuilder.php', 'yii\db\sqlite\QueryBuilder' => YII2_PATH . '/db/sqlite/QueryBuilder.php', 'yii\db\sqlite\Schema' => YII2_PATH . '/db/sqlite/Schema.php', 'yii\di\Container' => YII2_PATH . '/di/Container.php', @@ -202,6 +208,7 @@ return [ 'yii\mutex\FileMutex' => YII2_PATH . '/mutex/FileMutex.php', 'yii\mutex\Mutex' => YII2_PATH . '/mutex/Mutex.php', 'yii\mutex\MysqlMutex' => YII2_PATH . '/mutex/MysqlMutex.php', + 'yii\mutex\PgsqlMutex' => YII2_PATH . '/mutex/PgsqlMutex.php', 'yii\rbac\Assignment' => YII2_PATH . '/rbac/Assignment.php', 'yii\rbac\BaseManager' => YII2_PATH . '/rbac/BaseManager.php', 'yii\rbac\DbManager' => YII2_PATH . '/rbac/DbManager.php', @@ -234,15 +241,20 @@ return [ 'yii\validators\DateValidator' => YII2_PATH . '/validators/DateValidator.php', 'yii\validators\DefaultValueValidator' => YII2_PATH . '/validators/DefaultValueValidator.php', <<<<<<< HEAD +<<<<<<< HEAD ======= 'yii\validators\EachValidator' => YII2_PATH . '/validators/EachValidator.php', >>>>>>> yiichina/master +======= + 'yii\validators\EachValidator' => YII2_PATH . '/validators/EachValidator.php', +>>>>>>> master 'yii\validators\EmailValidator' => YII2_PATH . '/validators/EmailValidator.php', 'yii\validators\ExistValidator' => YII2_PATH . '/validators/ExistValidator.php', 'yii\validators\FileValidator' => YII2_PATH . '/validators/FileValidator.php', 'yii\validators\FilterValidator' => YII2_PATH . '/validators/FilterValidator.php', 'yii\validators\ImageValidator' => YII2_PATH . '/validators/ImageValidator.php', 'yii\validators\InlineValidator' => YII2_PATH . '/validators/InlineValidator.php', + 'yii\validators\IpValidator' => YII2_PATH . '/validators/IpValidator.php', 'yii\validators\NumberValidator' => YII2_PATH . '/validators/NumberValidator.php', 'yii\validators\PunycodeAsset' => YII2_PATH . '/validators/PunycodeAsset.php', 'yii\validators\RangeValidator' => YII2_PATH . '/validators/RangeValidator.php', @@ -283,6 +295,7 @@ return [ 'yii\web\Link' => YII2_PATH . '/web/Link.php', 'yii\web\Linkable' => YII2_PATH . '/web/Linkable.php', 'yii\web\MethodNotAllowedHttpException' => YII2_PATH . '/web/MethodNotAllowedHttpException.php', + 'yii\web\MultiFieldSession' => YII2_PATH . '/web/MultiFieldSession.php', 'yii\web\NotAcceptableHttpException' => YII2_PATH . '/web/NotAcceptableHttpException.php', 'yii\web\NotFoundHttpException' => YII2_PATH . '/web/NotFoundHttpException.php', 'yii\web\Request' => YII2_PATH . '/web/Request.php', @@ -294,6 +307,7 @@ return [ 'yii\web\SessionIterator' => YII2_PATH . '/web/SessionIterator.php', 'yii\web\TooManyRequestsHttpException' => YII2_PATH . '/web/TooManyRequestsHttpException.php', 'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php', + 'yii\web\UnprocessableEntityHttpException' => YII2_PATH . '/web/UnprocessableEntityHttpException.php', 'yii\web\UnsupportedMediaTypeHttpException' => YII2_PATH . '/web/UnsupportedMediaTypeHttpException.php', 'yii\web\UploadedFile' => YII2_PATH . '/web/UploadedFile.php', 'yii\web\UrlManager' => YII2_PATH . '/web/UrlManager.php', diff --git a/framework/composer.json b/framework/composer.json index 87834f010d..e4ada6dc75 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -42,6 +42,11 @@ "name": "Paul Klimov", "email": "klimov.paul@gmail.com", "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" } ], "support": { @@ -54,14 +59,15 @@ "require": { "php": ">=5.4.0", "ext-mbstring": "*", + "ext-ctype": "*", "lib-pcre": "*", - "yiisoft/yii2-composer": "*", - "ezyang/htmlpurifier": "4.6.*", + "yiisoft/yii2-composer": "~2.0.4", + "ezyang/htmlpurifier": "~4.6", "cebe/markdown": "~1.0.0 | ~1.1.0", - "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", - "bower-asset/jquery.inputmask": "3.1.*", + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2", "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": ">=2.0.1" + "bower-asset/yii2-pjax": "~2.0.1" }, "autoload": { "psr-4": {"yii\\": ""} diff --git a/framework/console/Application.php b/framework/console/Application.php index 1314eae6e3..496b1e523e 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -10,6 +10,12 @@ namespace yii\console; use Yii; use yii\base\InvalidRouteException; +// define STDIN, STDOUT and STDERR if the PHP SAPI did not define them (e.g. creating console application in web env) +// http://php.net/manual/en/features.commandline.io-streams.php +defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); +defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); +defined('STDERR') or define('STDERR', fopen('php://stderr', 'w')); + /** * Application represents a console application. * @@ -28,9 +34,9 @@ use yii\base\InvalidRouteException; * * To run the console application, enter the following on the command line: * - * ~~~ + * ``` * yii [--param1=value1 --param2 ...] - * ~~~ + * ``` * * where `` refers to a controller route in the form of `ModuleID/ControllerID/ActionID` * (e.g. `sitemap/create`), and `param1`, `param2` refers to a set of named parameters that @@ -40,9 +46,9 @@ use yii\base\InvalidRouteException; * A `help` command is provided by default, which lists available commands and shows their usage. * To use this command, simply type: * - * ~~~ + * ``` * yii help - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -97,7 +103,7 @@ class Application extends \yii\base\Application if (!empty($path) && is_file($file = Yii::getAlias($path))) { return require($file); } else { - die("The configuration file does not exist: $path\n"); + exit("The configuration file does not exist: $path\n"); } } } @@ -150,15 +156,25 @@ class Application extends \yii\base\Application * This method parses the specified route and creates the corresponding child module(s), controller and action * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. * If the route is empty, the method will use [[defaultRoute]]. + * + * For example, to run `public function actionTest($a, $b)` assuming that the controller has options the following + * code should be used: + * + * ```php + * \Yii::$app->runAction('controller/test', ['option' => 'value', $a, $b]); + * ``` + * * @param string $route the route that specifies the action. * @param array $params the parameters to be passed to the action - * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. + * @return integer|Response the result of the action. This can be either an exit code or Response object. + * Exit code 0 means normal, and other values mean abnormal. Exit code of `null` is treaded as `0` as well. * @throws Exception if the route is invalid */ public function runAction($route, $params = []) { try { - return (int)parent::runAction($route, $params); + $res = parent::runAction($route, $params); + return is_object($res) ? $res : (int)$res; } catch (InvalidRouteException $e) { throw new Exception("Unknown command \"$route\".", 0, $e); } @@ -171,12 +187,13 @@ class Application extends \yii\base\Application public function coreCommands() { return [ - 'message' => 'yii\console\controllers\MessageController', - 'help' => 'yii\console\controllers\HelpController', - 'migrate' => 'yii\console\controllers\MigrateController', - 'cache' => 'yii\console\controllers\CacheController', 'asset' => 'yii\console\controllers\AssetController', + 'cache' => 'yii\console\controllers\CacheController', 'fixture' => 'yii\console\controllers\FixtureController', + 'help' => 'yii\console\controllers\HelpController', + 'message' => 'yii\console\controllers\MessageController', + 'migrate' => 'yii\console\controllers\MigrateController', + 'serve' => 'yii\console\controllers\ServeController', ]; } diff --git a/framework/console/Controller.php b/framework/console/Controller.php index 8446914138..2d4c312dee 100644 --- a/framework/console/Controller.php +++ b/framework/console/Controller.php @@ -20,15 +20,19 @@ use yii\helpers\Console; * Users call a console command by specifying the corresponding route which identifies a controller action. * The `yii` program is used when calling a console command, like the following: * - * ~~~ + * ``` * yii [--param1=value1 --param2 ...] - * ~~~ + * ``` * * where `` is a route to a controller action and the params will be populated as properties of a command. * See [[options()]] for details. * * @property string $help This property is read-only. * @property string $helpSummary This property is read-only. + * @property array $passedOptionValues The properties corresponding to the passed options. This property is + * read-only. + * @property array $passedOptions The names of the options passed during execution. This property is + * read-only. * * @author Qiang Xue * @since 2.0 @@ -48,6 +52,11 @@ class Controller extends \yii\base\Controller */ public $color; + /** + * @var array the options passed during execution. + */ + private $_passedOptions = []; + /** * Returns a value indicating whether ANSI color is enabled. @@ -77,11 +86,30 @@ class Controller extends \yii\base\Controller { if (!empty($params)) { // populate options here so that they are available in beforeAction(). - $options = $this->options($id); + $options = $this->options($id === '' ? $this->defaultAction : $id); + if (isset($params['_aliases'])) { + $optionAliases = $this->optionAliases(); + foreach ($params['_aliases'] as $name => $value) { + if (array_key_exists($name, $optionAliases)) { + $params[$optionAliases[$name]] = $value; + } else { + throw new Exception(Yii::t('yii', 'Unknown alias: -{name}', ['name' => $name])); + } + } + unset($params['_aliases']); + } foreach ($params as $name => $value) { if (in_array($name, $options, true)) { $default = $this->$name; - $this->$name = is_array($default) ? preg_split('/\s*,\s*/', $value) : $value; + if (is_array($default)) { + $this->$name = preg_split('/(?!\(\d+)\s*,\s*(?!\d+\))/', $value); + } elseif ($default !== null) { + settype($value, gettype($default)); + $this->$name = $value; + } else { + $this->$name = $value; + } + $this->_passedOptions[] = $name; unset($params[$name]); } elseif (!is_int($name)) { throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name])); @@ -139,9 +167,9 @@ class Controller extends \yii\base\Controller * * Example: * - * ~~~ + * ``` * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); - * ~~~ + * ``` * * @param string $string the string to be formatted * @return string @@ -164,12 +192,12 @@ class Controller extends \yii\base\Controller * * Example: * - * ~~~ + * ``` * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); - * ~~~ + * ``` * * @param string $string the string to print - * @return int|boolean Number of bytes printed or false on error + * @return integer|boolean Number of bytes printed or false on error */ public function stdout($string) { @@ -189,12 +217,12 @@ class Controller extends \yii\base\Controller * * Example: * - * ~~~ + * ``` * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); - * ~~~ + * ``` * * @param string $string the string to print - * @return int|boolean Number of bytes printed or false on error + * @return integer|boolean Number of bytes printed or false on error */ public function stderr($string) { @@ -278,6 +306,62 @@ class Controller extends \yii\base\Controller return ['color', 'interactive']; } + /** + * Returns option alias names. + * Child classes may override this method to specify alias options. + * + * @return array the options alias names valid for the action + * where the keys is alias name for option and value is option name. + * + * @since 2.0.8 + * @see options($actionID) + */ + public function optionAliases() + { + return []; + } + + /** + * Returns properties corresponding to the options for the action id + * Child classes may override this method to specify possible properties. + * + * @param string $actionID the action id of the current request + * @return array properties corresponding to the options for the action + */ + public function getOptionValues($actionID) + { + // $actionId might be used in subclasses to provide properties specific to action id + $properties = []; + foreach ($this->options($this->action->id) as $property) { + $properties[$property] = $this->$property; + } + return $properties; + } + + /** + * Returns the names of valid options passed during execution. + * + * @return array the names of the options passed during execution + */ + public function getPassedOptions() + { + return $this->_passedOptions; + } + + /** + * Returns the properties corresponding to the passed options + * + * @return array the properties corresponding to the passed options + */ + public function getPassedOptionValues() + { + $properties = []; + foreach ($this->_passedOptions as $property) { + $properties[$property] = $this->$property; + } + return $properties; + } + /** * Returns one-line short summary describing this controller. * @@ -346,10 +430,12 @@ class Controller extends \yii\base\Controller $params = isset($tags['param']) ? (array) $tags['param'] : []; $args = []; + + /** @var \ReflectionParameter $reflection */ foreach ($method->getParameters() as $i => $reflection) { $name = $reflection->getName(); $tag = isset($params[$i]) ? $params[$i] : ''; - if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) { + if (preg_match('/^(\S+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) { $type = $matches[1]; $comment = $matches[3]; } else { @@ -411,7 +497,7 @@ class Controller extends \yii\base\Controller if (is_array($doc)) { $doc = reset($doc); } - if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) { + if (preg_match('/^(\S+)(.*)/s', $doc, $matches)) { $type = $matches[1]; $comment = $matches[2]; } else { diff --git a/framework/console/ErrorHandler.php b/framework/console/ErrorHandler.php index 9fe3a97b11..c2ef846e9a 100644 --- a/framework/console/ErrorHandler.php +++ b/framework/console/ErrorHandler.php @@ -40,7 +40,7 @@ class ErrorHandler extends \yii\base\ErrorHandler $message = $this->formatMessage('Exception'); } $message .= $this->formatMessage(" '" . get_class($exception) . "'", [Console::BOLD, Console::FG_BLUE]) - . " with message " . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n" + . ' with message ' . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n" . "\n\nin " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . $this->formatMessage(basename($exception->getFile()), [Console::BOLD]) . ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n"; if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) { @@ -75,4 +75,4 @@ class ErrorHandler extends \yii\base\ErrorHandler } return $message; } -} \ No newline at end of file +} diff --git a/framework/console/Markdown.php b/framework/console/Markdown.php index a1a5c5a24f..f538266fad 100644 --- a/framework/console/Markdown.php +++ b/framework/console/Markdown.php @@ -48,10 +48,10 @@ class Markdown extends \cebe\markdown\Parser * @param array $block * @return string */ - protected function renderCode($block) - { + protected function renderCode($block) + { return Console::ansiFormat($block['content'], [Console::NEGATIVE]) . "\n\n"; - } + } /** * @inheritdoc @@ -78,7 +78,7 @@ class Markdown extends \cebe\markdown\Parser */ protected function renderEmph($element) { - return Console::ansiFormat($this->renderAbsy($element[1]), Console::ITALIC); + return Console::ansiFormat($this->renderAbsy($element[1]), [Console::ITALIC]); } /** @@ -88,7 +88,7 @@ class Markdown extends \cebe\markdown\Parser */ protected function renderStrong($element) { - return Console::ansiFormat($this->renderAbsy($element[1]), Console::BOLD); + return Console::ansiFormat($this->renderAbsy($element[1]), [Console::BOLD]); } /** diff --git a/framework/console/Request.php b/framework/console/Request.php index 5a594b71a9..8377d3a15d 100644 --- a/framework/console/Request.php +++ b/framework/console/Request.php @@ -29,7 +29,7 @@ class Request extends \yii\base\Request */ public function getParams() { - if (!isset($this->_params)) { + if ($this->_params === null) { if (isset($_SERVER['argv'])) { $this->_params = $_SERVER['argv']; array_shift($this->_params); @@ -66,11 +66,14 @@ class Request extends \yii\base\Request $params = []; foreach ($rawParams as $param) { - if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + if (preg_match('/^--(\w+)(?:=(.*))?$/', $param, $matches)) { $name = $matches[1]; if ($name !== Application::OPTION_APPCONFIG) { - $params[$name] = isset($matches[3]) ? $matches[3] : true; + $params[$name] = isset($matches[2]) ? $matches[2] : true; } + } elseif (preg_match('/^-(\w+)(?:=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true; } else { $params[] = $param; } diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php index 6215e1a42a..e0a0ffa3bf 100644 --- a/framework/console/controllers/AssetController.php +++ b/framework/console/controllers/AssetController.php @@ -12,9 +12,13 @@ use yii\console\Exception; use yii\console\Controller; use yii\helpers\Console; <<<<<<< HEAD +<<<<<<< HEAD ======= use yii\helpers\FileHelper; >>>>>>> yiichina/master +======= +use yii\helpers\FileHelper; +>>>>>>> master use yii\helpers\VarDumper; use yii\web\AssetBundle; @@ -61,13 +65,13 @@ class AssetController extends Controller * You can specify the name of the output compressed file using 'css' and 'js' keys: * For example: * - * ~~~ + * ```php * 'app\config\AllAsset' => [ * 'js' => 'js/all-{hash}.js', * 'css' => 'css/all-{hash}.css', * 'depends' => [ ... ], * ] - * ~~~ + * ``` * * File names can contain placeholder "{hash}", which will be filled by the hash of the resulting file. * @@ -77,7 +81,7 @@ class AssetController extends Controller * bundles in this case. * For example: * - * ~~~ + * ```php * 'allShared' => [ * 'js' => 'js/all-shared-{hash}.js', * 'css' => 'css/all-shared-{hash}.css', @@ -100,7 +104,7 @@ class AssetController extends Controller * 'css' => 'css/all-{hash}.css', * 'depends' => [], // Include all remaining assets * ], - * ~~~ + * ``` */ public $targets = []; /** @@ -251,11 +255,15 @@ class AssetController extends Controller $this->loadDependency($dependencyBundle, $result); $result[$name] = $dependencyBundle; } elseif ($result[$name] === false) { +<<<<<<< HEAD <<<<<<< HEAD throw new Exception("A circular dependency is detected for bundle '$name'."); ======= throw new Exception("A circular dependency is detected for bundle '{$name}': " . $this->composeCircularDependencyTrace($name, $result) . "."); >>>>>>> yiichina/master +======= + throw new Exception("A circular dependency is detected for bundle '{$name}': " . $this->composeCircularDependencyTrace($name, $result) . '.'); +>>>>>>> master } } } @@ -333,6 +341,7 @@ class AssetController extends Controller */ protected function buildTarget($target, $type, $bundles) { +<<<<<<< HEAD <<<<<<< HEAD $tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']); $inputFiles = []; @@ -340,24 +349,38 @@ class AssetController extends Controller ======= $inputFiles = []; >>>>>>> yiichina/master +======= + $inputFiles = []; +>>>>>>> master foreach ($target->depends as $name) { if (isset($bundles[$name])) { if (!$this->isBundleExternal($bundles[$name])) { foreach ($bundles[$name]->$type as $file) { - $inputFiles[] = $bundles[$name]->basePath . '/' . $file; + if (is_array($file)) { + $inputFiles[] = $bundles[$name]->basePath . '/' . $file[0]; + } else { + $inputFiles[] = $bundles[$name]->basePath . '/' . $file; + } } } } else { throw new Exception("Unknown bundle: '{$name}'"); } } +<<<<<<< HEAD <<<<<<< HEAD if ($type === 'js') { $this->compressJsFiles($inputFiles, $tempFile); - } else { - $this->compressCssFiles($inputFiles, $tempFile); - } +======= + if (empty($inputFiles)) { + $target->$type = []; +>>>>>>> master + } else { + FileHelper::createDirectory($target->basePath, $this->getAssetManager()->dirMode); + $tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']); + +<<<<<<< HEAD $targetFile = strtr($target->$type, ['{hash}' => md5_file($tempFile)]); $outputFile = $target->basePath . '/' . $targetFile; rename($tempFile, $outputFile); @@ -370,6 +393,8 @@ class AssetController extends Controller FileHelper::createDirectory($target->basePath, $this->getAssetManager()->dirMode); $tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']); +======= +>>>>>>> master if ($type === 'js') { $this->compressJsFiles($inputFiles, $tempFile); } else { @@ -381,7 +406,10 @@ class AssetController extends Controller rename($tempFile, $outputFile); $target->$type = [$targetFile]; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -449,6 +477,7 @@ class AssetController extends Controller $this->registerBundle($bundles, $depend, $registered); } unset($registered[$name]); +<<<<<<< HEAD <<<<<<< HEAD $registered[$name] = true; } elseif ($registered[$name] === false) { @@ -458,6 +487,11 @@ class AssetController extends Controller } elseif ($registered[$name] === false) { throw new Exception("A circular dependency is detected for target '{$name}': " . $this->composeCircularDependencyTrace($name, $registered) . "."); >>>>>>> yiichina/master +======= + $registered[$name] = $bundle; + } elseif ($registered[$name] === false) { + throw new Exception("A circular dependency is detected for target '{$name}': " . $this->composeCircularDependencyTrace($name, $registered) . '.'); +>>>>>>> master } } @@ -649,7 +683,7 @@ EOD; $fullMatch = $matches[0]; $inputUrl = $matches[1]; - if (strpos($inputUrl, '/') === 0 || preg_match('/^https?:\/\//is', $inputUrl) || preg_match('/^data:/is', $inputUrl)) { + if (strpos($inputUrl, '/') === 0 || preg_match('/^https?:\/\//i', $inputUrl) || preg_match('/^data:/i', $inputUrl)) { return $fullMatch; } if ($inputFileRelativePathParts === $outputFileRelativePathParts) { @@ -666,7 +700,7 @@ EOD; if (strpos($inputUrl, '/') !== false) { $inputUrlParts = explode('/', $inputUrl); foreach ($inputUrlParts as $key => $inputUrlPart) { - if ($inputUrlPart == '..') { + if ($inputUrlPart === '..') { array_pop($outputUrlParts); unset($inputUrlParts[$key]); } @@ -680,7 +714,7 @@ EOD; return str_replace($inputUrl, $outputUrl, $fullMatch); }; - $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/is', $callback, $cssContent); + $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/i', $callback, $cssContent); return $cssContent; } @@ -763,7 +797,7 @@ EOD; if ($pathPart === '..') { array_pop($realPathParts); } else { - array_push($realPathParts, $pathPart); + $realPathParts[] = $pathPart; } } return implode(DIRECTORY_SEPARATOR, $realPathParts); @@ -789,7 +823,10 @@ EOD; return $config; } <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master /** * Composes trace info for bundle circular dependency. @@ -812,5 +849,8 @@ EOD; $dependencyTrace[] = $circularDependencyName; return implode(' -> ', $dependencyTrace); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } diff --git a/framework/console/controllers/BaseMigrateController.php b/framework/console/controllers/BaseMigrateController.php index 48b4172d63..adad1dd7c5 100644 --- a/framework/console/controllers/BaseMigrateController.php +++ b/framework/console/controllers/BaseMigrateController.php @@ -51,7 +51,7 @@ abstract class BaseMigrateController extends Controller return array_merge( parent::options($actionID), ['migrationPath'], // global for all actions - ($actionID == 'create') ? ['templateFile'] : [] // action create + $actionID === 'create' ? ['templateFile'] : [] // action create ); } @@ -68,7 +68,7 @@ abstract class BaseMigrateController extends Controller $path = Yii::getAlias($this->migrationPath); if (!is_dir($path)) { if ($action->id !== 'create') { - throw new Exception('Migration failed. Directory specified in migrationPath doesn\'t exist.'); + throw new Exception("Migration failed. Directory specified in migrationPath doesn't exist: {$this->migrationPath}"); } FileHelper::createDirectory($path); } @@ -87,10 +87,10 @@ abstract class BaseMigrateController extends Controller * Upgrades the application by applying new migrations. * For example, * - * ~~~ + * ``` * yii migrate # apply all new migrations * yii migrate 3 # apply the first 3 new migrations - * ~~~ + * ``` * * @param integer $limit the number of new migrations to be applied. If 0, it means * applying all available new migrations. @@ -101,7 +101,7 @@ abstract class BaseMigrateController extends Controller { $migrations = $this->getNewMigrations(); if (empty($migrations)) { - $this->stdout("No new migration found. Your system is up-to-date.\n", Console::FG_GREEN); + $this->stdout("No new migrations found. Your system is up-to-date.\n", Console::FG_GREEN); return self::EXIT_CODE_NORMAL; } @@ -124,14 +124,19 @@ abstract class BaseMigrateController extends Controller } $this->stdout("\n"); - if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + $applied = 0; + if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { foreach ($migrations as $migration) { if (!$this->migrateUp($migration)) { + $this->stdout("\n$applied from $n " . ($applied === 1 ? 'migration was' : 'migrations were') ." applied.\n", Console::FG_RED); $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); return self::EXIT_CODE_ERROR; } + $applied++; } + + $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') ." applied.\n", Console::FG_GREEN); $this->stdout("\nMigrated up successfully.\n", Console::FG_GREEN); } } @@ -140,11 +145,11 @@ abstract class BaseMigrateController extends Controller * Downgrades the application by reverting old migrations. * For example, * - * ~~~ + * ``` * yii migrate/down # revert the last migration * yii migrate/down 3 # revert the last 3 migrations * yii migrate/down all # revert all migrations - * ~~~ + * ``` * * @param integer $limit the number of migrations to be reverted. Defaults to 1, * meaning the last applied migration will be reverted. @@ -159,7 +164,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); + throw new Exception('The step argument must be greater than 0.'); } } @@ -180,14 +185,18 @@ abstract class BaseMigrateController extends Controller } $this->stdout("\n"); - if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + $reverted = 0; + if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { foreach ($migrations as $migration) { if (!$this->migrateDown($migration)) { + $this->stdout("\n$reverted from $n " . ($reverted === 1 ? 'migration was' : 'migrations were') ." reverted.\n", Console::FG_RED); $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); return self::EXIT_CODE_ERROR; } + $reverted++; } + $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') ." reverted.\n", Console::FG_GREEN); $this->stdout("\nMigrated down successfully.\n", Console::FG_GREEN); } } @@ -198,11 +207,11 @@ abstract class BaseMigrateController extends Controller * This command will first revert the specified migrations, and then apply * them again. For example, * - * ~~~ + * ``` * yii migrate/redo # redo the last applied migration * yii migrate/redo 3 # redo the last 3 applied migrations * yii migrate/redo all # redo all migrations - * ~~~ + * ``` * * @param integer $limit the number of migrations to be redone. Defaults to 1, * meaning the last applied migration will be redone. @@ -217,7 +226,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); + throw new Exception('The step argument must be greater than 0.'); } } @@ -238,7 +247,7 @@ abstract class BaseMigrateController extends Controller } $this->stdout("\n"); - if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { foreach ($migrations as $migration) { if (!$this->migrateDown($migration)) { $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); @@ -253,6 +262,7 @@ abstract class BaseMigrateController extends Controller return self::EXIT_CODE_ERROR; } } + $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') ." redone.\n", Console::FG_GREEN); $this->stdout("\nMigration redone successfully.\n", Console::FG_GREEN); } } @@ -267,12 +277,12 @@ abstract class BaseMigrateController extends Controller * This command will first revert the specified migrations, and then apply * them again. For example, * - * ~~~ + * ``` * yii migrate/to 101129_185401 # using timestamp * yii migrate/to m101129_185401_create_user_table # using full name * yii migrate/to 1392853618 # using UNIX timestamp * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string - * ~~~ + * ``` * * @param string $version either the version name or the certain time value in the past * that the application should be migrated to. This can be either the timestamp, @@ -298,10 +308,10 @@ abstract class BaseMigrateController extends Controller * * No actual migration will be performed. * - * ~~~ + * ``` * yii migrate/mark 101129_185401 # using timestamp * yii migrate/mark m101129_185401_create_user_table # using full name - * ~~~ + * ``` * * @param string $version the version at which the migration history should be marked. * This can be either the timestamp or the full name of the migration. @@ -360,14 +370,14 @@ abstract class BaseMigrateController extends Controller * This command will show the list of migrations that have been applied * so far. For example, * - * ~~~ + * ``` * yii migrate/history # showing the last 10 migrations * yii migrate/history 5 # showing the last 5 migrations * yii migrate/history all # showing the whole history - * ~~~ + * ``` * * @param integer $limit the maximum number of migrations to be displayed. - * If it is 0, the whole migration history will be displayed. + * If it is "all", the whole migration history will be displayed. * @throws \yii\console\Exception if invalid limit value passed */ public function actionHistory($limit = 10) @@ -377,7 +387,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The limit must be greater than 0."); + throw new Exception('The limit must be greater than 0.'); } } @@ -404,11 +414,11 @@ abstract class BaseMigrateController extends Controller * This command will show the new migrations that have not been applied. * For example, * - * ~~~ + * ``` * yii migrate/new # showing the first 10 new migrations * yii migrate/new 5 # showing the first 5 new migrations * yii migrate/new all # showing all new migrations - * ~~~ + * ``` * * @param integer $limit the maximum number of new migrations to be displayed. * If it is `all`, all available new migrations will be displayed. @@ -421,7 +431,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The limit must be greater than 0."); + throw new Exception('The limit must be greater than 0.'); } } @@ -451,25 +461,32 @@ abstract class BaseMigrateController extends Controller * After using this command, developers should modify the created migration * skeleton by filling up the actual migration logic. * - * ~~~ + * ``` * yii migrate/create create_user_table - * ~~~ + * ``` * * @param string $name the name of the new migration. This should only contain * letters, digits and/or underscores. + * + * Note: If the migration name is of a special form, for example create_xxx or + * drop_xxx then the generated migration file will contain extra code, + * in this case for creating/dropping tables. + * * @throws Exception if the name argument is invalid. */ public function actionCreate($name) { if (!preg_match('/^\w+$/', $name)) { - throw new Exception("The migration name should contain letters, digits and/or underscore characters only."); + throw new Exception('The migration name should contain letters, digits and/or underscore characters only.'); } - $name = 'm' . gmdate('ymd_His') . '_' . $name; - $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php'; - + $className = 'm' . gmdate('ymd_His') . '_' . $name; + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $className . '.php'; if ($this->confirm("Create new migration '$file'?")) { - $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]); + $content = $this->generateMigrationSourceCode([ + 'name' => $name, + 'className' => $className, + ]); file_put_contents($file, $content); $this->stdout("New migration created successfully.\n", Console::FG_GREEN); } @@ -492,12 +509,12 @@ abstract class BaseMigrateController extends Controller if ($migration->up() !== false) { $this->addMigrationHistory($class); $time = microtime(true) - $start; - $this->stdout("*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n", Console::FG_GREEN); + $this->stdout("*** applied $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_GREEN); return true; } else { $time = microtime(true) - $start; - $this->stdout("*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n", Console::FG_RED); + $this->stdout("*** failed to apply $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_RED); return false; } @@ -520,13 +537,13 @@ abstract class BaseMigrateController extends Controller if ($migration->down() !== false) { $this->removeMigrationHistory($class); $time = microtime(true) - $start; - $this->stdout("*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n", Console::FG_GREEN); + $this->stdout("*** reverted $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_GREEN); return true; } else { $time = microtime(true) - $start; - $this->stdout("*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n", Console::FG_RED); + $this->stdout("*** failed to revert $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_RED); return false; } @@ -628,6 +645,22 @@ abstract class BaseMigrateController extends Controller return $migrations; } + /** + * Generates new migration source PHP code. + * Child class may override this method, adding extra logic or variation to the process. + * @param array $params generation parameters, usually following parameters are present: + * + * - name: string migration base name + * - className: string migration class name + * + * @return string generated PHP code. + * @since 2.0.8 + */ + protected function generateMigrationSourceCode($params) + { + return $this->renderFile(Yii::getAlias($this->templateFile), $params); + } + /** * Returns the migration history. * @param integer $limit the maximum number of records in the history to be returned. `null` for "no limit". diff --git a/framework/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php index 782a99c6fc..a2f6fe4df2 100644 --- a/framework/console/controllers/CacheController.php +++ b/framework/console/controllers/CacheController.php @@ -56,18 +56,18 @@ class CacheController extends Controller * Flushes given cache components. * For example, * - * ~~~ + * ``` * # flushes caches specified by their id: "first", "second", "third" * yii cache/flush first second third - * ~~~ - * + * ``` + * */ public function actionFlush() { $cachesInput = func_get_args(); if (empty($cachesInput)) { - throw new Exception("You should specify cache components names"); + throw new Exception('You should specify cache components names'); } $caches = $this->findCaches($cachesInput); @@ -93,7 +93,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => Yii::$app->get($name)->flush(), ]; } @@ -117,7 +117,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => Yii::$app->get($name)->flush(), ]; } @@ -127,13 +127,13 @@ class CacheController extends Controller /** * Clears DB schema cache for a given connection component. * - * ~~~ + * ``` * # clears cache schema specified by component id: "db" * yii cache/flush-schema db - * ~~~ + * ``` * * @param string $db id connection component - * @return int exit code + * @return integer exit code * @throws Exception * @throws \yii\base\InvalidConfigException * @@ -150,7 +150,7 @@ class CacheController extends Controller if (!$connection instanceof \yii\db\Connection) { $this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED); return self::EXIT_CODE_ERROR; - } else if (!$this->confirm("Flush cache schema for \"$db\" connection?")) { + } elseif (!$this->confirm("Flush cache schema for \"$db\" connection?")) { return static::EXIT_CODE_NORMAL; } @@ -202,7 +202,7 @@ class CacheController extends Controller } /** - * + * * @param array $caches */ private function notifyFlushed($caches) @@ -210,7 +210,7 @@ class CacheController extends Controller $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW); foreach ($caches as $cache) { - $this->stdout("\t* " . $cache['name'] ." (" . $cache['class'] . ")", Console::FG_GREEN); + $this->stdout("\t* " . $cache['name'] .' (' . $cache['class'] . ')', Console::FG_GREEN); if (!$cache['is_flushed']) { $this->stdout(" - not flushed\n", Console::FG_RED); @@ -247,7 +247,7 @@ class CacheController extends Controller { $caches = []; $components = Yii::$app->getComponents(); - $findAll = ($cachesNames == []); + $findAll = ($cachesNames === []); foreach ($components as $name => $component) { if (!$findAll && !in_array($name, $cachesNames)) { @@ -275,5 +275,4 @@ class CacheController extends Controller { return is_subclass_of($className, Cache::className()); } - } diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php index 7256d467a7..79b7993989 100644 --- a/framework/console/controllers/FixtureController.php +++ b/framework/console/controllers/FixtureController.php @@ -17,7 +17,7 @@ use yii\test\FixtureTrait; /** * Manages fixture data loading and unloading. * - * ~~~ + * ``` * #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures" * yii fixture/load User * @@ -32,7 +32,7 @@ use yii\test\FixtureTrait; * * #load fixtures with different namespace. * yii fixture/load User --namespace=alias\my\custom\namespace\goes\here - * ~~~ + * ``` * * The `unload` sub-command can be used similarly to unload fixtures. * @@ -70,11 +70,23 @@ class FixtureController extends Controller ]); } + /** + * @inheritdoc + * @since 2.0.8 + */ + public function optionAliases() + { + return array_merge(parent::optionAliases(), [ + 'g' => 'globalFixtures', + 'n' => 'namespace', + ]); + } + /** * Loads the specified fixture data. * For example, * - * ~~~ + * ``` * # load the fixture data specified by User and UserProfile. * # any existing fixture data will be removed first * yii fixture/load User UserProfile @@ -84,7 +96,7 @@ class FixtureController extends Controller * * # load all fixtures except User and UserProfile * yii fixture/load "*" -User -UserProfile - * ~~~ + * ``` * * @throws Exception if the specified fixture does not exist. */ @@ -94,7 +106,7 @@ class FixtureController extends Controller if ($fixturesInput === []) { $this->stdout($this->getHelpSummary() . "\n"); - $helpCommand = Console::ansiFormat("yii help fixture", [Console::FG_CYAN]); + $helpCommand = Console::ansiFormat('yii help fixture', [Console::FG_CYAN]); $this->stdout("Use $helpCommand to get usage info.\n"); return self::EXIT_CODE_NORMAL; @@ -155,7 +167,7 @@ class FixtureController extends Controller * Unloads the specified fixtures. * For example, * - * ~~~ + * ``` * # unload the fixture data specified by User and UserProfile. * yii fixture/unload User UserProfile * @@ -164,7 +176,7 @@ class FixtureController extends Controller * * # unload all fixtures except User and UserProfile * yii fixture/unload "*" -User -UserProfile - * ~~~ + * ``` * * @throws Exception if the specified fixture does not exist. */ @@ -322,6 +334,9 @@ class FixtureController extends Controller $this->outputList($except); } + $this->stdout("\nBe aware that:\n", Console::BOLD); + $this->stdout("Applying leads to purging of certain data in the database!\n", Console::FG_RED); + return $this->confirm("\nLoad above fixtures?"); } @@ -368,15 +383,15 @@ class FixtureController extends Controller /** * Checks if needed to apply all fixtures. * @param string $fixture - * @return bool + * @return boolean */ public function needToApplyAll($fixture) { - return $fixture == '*'; + return $fixture === '*'; } /** - * Finds fixtures to be loaded, for example "User", if no fixtures were specified then all of them + * Finds fixtures to be loaded, for example "User", if no fixtures were specified then all of them * will be searching by suffix "Fixture.php". * @param array $fixtures fixtures to be loaded * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists. @@ -386,7 +401,7 @@ class FixtureController extends Controller $fixturesPath = $this->getFixturePath(); $filesToSearch = ['*Fixture.php']; - $findAll = ($fixtures == []); + $findAll = ($fixtures === []); if (!$findAll) { @@ -433,8 +448,8 @@ class FixtureController extends Controller * Filters fixtures by splitting them in two categories: one that should be applied and not. * If fixture is prefixed with "-", for example "-User", that means that fixture should not be loaded, * if it is not prefixed it is considered as one to be loaded. Returns array: - * - * ~~~ + * + * ```php * [ * 'apply' => [ * 'User', @@ -445,7 +460,7 @@ class FixtureController extends Controller * ... * ], * ] - * ~~~ + * ``` * @param array $fixtures * @return array fixtures array with 'apply' and 'except' elements. */ @@ -460,7 +475,7 @@ class FixtureController extends Controller if (mb_strpos($fixture, '-') !== false) { $filtered['except'][] = str_replace('-', '', $fixture); } else { - $filtered['apply'][] = $fixture; + $filtered['apply'][] = $fixture; } } @@ -475,5 +490,4 @@ class FixtureController extends Controller { return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace)); } - } diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php index 977a5969a1..c0505ae41e 100644 --- a/framework/console/controllers/HelpController.php +++ b/framework/console/controllers/HelpController.php @@ -23,9 +23,9 @@ use yii\helpers\Inflector; * * This command can be used as follows on command line: * - * ~~~ + * ``` * yii help [command name] - * ~~~ + * ``` * * In the above, if the command name is not provided, all * available commands will be displayed. @@ -129,7 +129,7 @@ class HelpController extends Controller */ protected function getModuleCommands($module) { - $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/'; + $prefix = $module instanceof Application ? '' : $module->getUniqueId() . '/'; $commands = []; foreach (array_keys($module->controllerMap) as $id) { @@ -164,7 +164,7 @@ class HelpController extends Controller /** * Validates if the given class is a valid console controller class. * @param string $controllerClass - * @return bool + * @return boolean */ protected function validateControllerClass($controllerClass) { @@ -308,7 +308,7 @@ class HelpController extends Controller } $description = $controller->getActionHelp($action); - if ($description != '') { + if ($description !== '') { $this->stdout("\nDESCRIPTION\n", Console::BOLD); $this->stdout("\n$description\n\n"); } @@ -358,7 +358,7 @@ class HelpController extends Controller $this->stdout("\nOPTIONS\n\n", Console::BOLD); foreach ($options as $name => $option) { $this->stdout($this->formatOptionHelp( - $this->ansiFormat('--' . $name, Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD), + $this->ansiFormat('--' . $name . $this->formatOptionAliases($controller, $name), Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD), !empty($option['required']), $option['type'], $option['default'], @@ -397,7 +397,7 @@ class HelpController extends Controller } else { $defaultValue = var_export($defaultValue, true); } - $doc = "$type (defaults to " . $defaultValue . ")"; + $doc = "$type (defaults to $defaultValue)"; } else { $doc = $type; } @@ -405,7 +405,7 @@ class HelpController extends Controller if ($doc === '') { $doc = $comment; } elseif ($comment !== '') { - $doc .= "\n" . preg_replace("/^/m", " ", $comment); + $doc .= "\n" . preg_replace('/^/m', ' ', $comment); } $name = $required ? "$name (required)" : $name; @@ -413,6 +413,23 @@ class HelpController extends Controller return $doc === '' ? $name : "$name: $doc"; } + /** + * @param Controller $controller the controller instance + * @param string $option the option name + * @return string the formatted string for the alias argument or option + * @since 2.0.8 + */ + protected function formatOptionAliases($controller, $option) + { + $aliases = $controller->optionAliases(); + foreach ($aliases as $name => $value) { + if ($value === $option) { + return ', -' . $name; + } + } + return ''; + } + /** * @return string the name of the cli script currently running. */ diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index bc2db163a5..ab7b9ae40d 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -41,14 +41,155 @@ class MessageController extends Controller * @var string controller default action ID. */ public $defaultAction = 'extract'; + /** + * @var string required, root directory of all source files. + */ + public $sourcePath = '@yii'; + /** + * @var string required, root directory containing message translations. + */ + public $messagePath = '@yii/messages'; + /** + * @var array required, list of language codes that the extracted messages + * should be translated to. For example, ['zh-CN', 'de']. + */ + public $languages = []; + /** + * @var string the name of the function for translating messages. + * Defaults to 'Yii::t'. This is used as a mark to find the messages to be + * translated. You may use a string for single function name or an array for + * multiple function names. + */ + public $translator = 'Yii::t'; + /** + * @var boolean whether to sort messages by keys when merging new messages + * with the existing ones. Defaults to false, which means the new (untranslated) + * messages will be separated from the old (translated) ones. + */ + public $sort = false; + /** + * @var boolean whether the message file should be overwritten with the merged messages + */ + public $overwrite = true; + /** + * @var boolean whether to remove messages that no longer appear in the source code. + * Defaults to false, which means these messages will NOT be removed. + */ + public $removeUnused = false; + /** + * @var boolean whether to mark messages that no longer appear in the source code. + * Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks. + */ + public $markUnused = true; + /** + * @var array list of patterns that specify which files/directories should NOT be processed. + * If empty or not set, all files/directories will be processed. + * A path matches a pattern if it contains the pattern string at its end. For example, + * '/a/b' will match all files and directories ending with '/a/b'; + * the '*.svn' will match all files and directories whose name ends with '.svn'. + * and the '.svn' will match all files and directories named exactly '.svn'. + * Note, the '/' characters in a pattern matches both '/' and '\'. + * See helpers/FileHelper::findFiles() description for more details on pattern matching rules. + */ + public $except = [ + '.svn', + '.git', + '.gitignore', + '.gitkeep', + '.hgignore', + '.hgkeep', + '/messages', + '/BaseYii.php', // contains examples about Yii:t() + ]; + /** + * @var array list of patterns that specify which files (not directories) should be processed. + * If empty or not set, all files will be processed. + * Please refer to "except" for details about the patterns. + * If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. + */ + public $only = ['*.php']; + /** + * @var string generated file format. Can be "php", "db" or "po". + */ + public $format = 'php'; + /** + * @var string connection component ID for "db" format. + */ + public $db = 'db'; + /** + * @var string custom name for source message table for "db" format. + */ + public $sourceMessageTable = '{{%source_message}}'; + /** + * @var string custom name for translation message table for "db" format. + */ + public $messageTable = '{{%message}}'; + /** + * @var string name of the file that will be used for translations for "po" format. + */ + public $catalog = 'messages'; + /** + * @var array message categories to ignore. For example, 'yii', 'app*', 'widgets/menu', etc. + * @see isCategoryIgnored + */ + public $ignoreCategories = []; /** - * Creates a configuration file for the "extract" command. + * @inheritdoc + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), [ + 'sourcePath', + 'messagePath', + 'languages', + 'translator', + 'sort', + 'overwrite', + 'removeUnused', + 'markUnused', + 'except', + 'only', + 'format', + 'db', + 'sourceMessageTable', + 'messageTable', + 'catalog', + 'ignoreCategories', + ]); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function optionAliases() + { + return array_merge(parent::optionAliases(), [ + 'c' => 'catalog', + 'e' => 'except', + 'f' => 'format', + 'i' => 'ignoreCategories', + 'l' => 'languages', + 'u' => 'markUnused', + 'p' => 'messagePath', + 'o' => 'only', + 'w' => 'overwrite', + 'S' => 'sort', + 't' => 'translator', + 'm' => 'sourceMessageTable', + 's' => 'sourcePath', + 'r' => 'removeUnused', + ]); + } + + /** + * Creates a configuration file for the "extract" command using command line options specified * - * The generated configuration file contains detailed instructions on - * how to customize it to fit for your needs. After customization, - * you may use this configuration file with the "extract" command. + * The generated configuration file contains parameters required + * for source code messages extraction. + * You may use this configuration file with the "extract" command. * * @param string $filePath output file name or alias. * @return integer CLI exit code @@ -62,9 +203,59 @@ class MessageController extends Controller return self::EXIT_CODE_NORMAL; } } - copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath); - $this->stdout("Configuration file template created at '{$filePath}'.\n\n", Console::FG_GREEN); - return self::EXIT_CODE_NORMAL; + + $array = VarDumper::export($this->getOptionValues($this->action->id)); + $content = <<id}/{$this->defaultAction}' command. + * + * This file is automatically generated by 'yii {$this->id}/{$this->action->id}' command. + * It contains parameters for source code messages extraction. + * You may modify this file to suit your needs. + * + * You can use 'yii {$this->id}/{$this->action->id}-template' command to create + * template configuration file with detaild description for each parameter. + */ +return $array; + +EOD; + + if (file_put_contents($filePath, $content) !== false) { + $this->stdout("Configuration file created: '{$filePath}'.\n\n", Console::FG_GREEN); + return self::EXIT_CODE_NORMAL; + } else { + $this->stdout("Configuration file was NOT created: '{$filePath}'.\n\n", Console::FG_RED); + return self::EXIT_CODE_ERROR; + } + } + + /** + * Creates a configuration file template for the "extract" command. + * + * The created configuration file contains detailed instructions on + * how to customize it to fit for your needs. After customization, + * you may use this configuration file with the "extract" command. + * + * @param string $filePath output file name or alias. + * @return integer CLI exit code + * @throws Exception on failure. + */ + public function actionConfigTemplate($filePath) + { + $filePath = Yii::getAlias($filePath); + if (file_exists($filePath)) { + if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) { + return self::EXIT_CODE_NORMAL; + } + } + if (copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath)) { + $this->stdout("Configuration file template created at '{$filePath}'.\n\n", Console::FG_GREEN); + return self::EXIT_CODE_NORMAL; + } else { + $this->stdout("Configuration file template was NOT created at '{$filePath}'.\n\n", Console::FG_RED); + return self::EXIT_CODE_ERROR; + } } /** @@ -78,21 +269,25 @@ class MessageController extends Controller * this file and then customize it for your needs. * @throws Exception on failure. */ - public function actionExtract($configFile) + public function actionExtract($configFile = null) { - $configFile = Yii::getAlias($configFile); - if (!is_file($configFile)) { - throw new Exception("The configuration file does not exist: $configFile"); + $configFileContent = []; + if ($configFile !== null) { + $configFile = Yii::getAlias($configFile); + if (!is_file($configFile)) { + throw new Exception("The configuration file does not exist: $configFile"); + } else { + $configFileContent = require($configFile); + } } - $config = array_merge([ - 'translator' => 'Yii::t', - 'overwrite' => false, - 'removeUnused' => false, - 'sort' => false, - 'format' => 'php', - 'ignoreCategories' => [], - ], require($configFile)); + $config = array_merge( + $this->getOptionValues($this->action->id), + $configFileContent, + $this->getPassedOptionValues() + ); + $config['sourcePath'] = Yii::getAlias($config['sourcePath']); + $config['messagePath'] = Yii::getAlias($config['messagePath']); if (!isset($config['sourcePath'], $config['languages'])) { throw new Exception('The configuration file must specify "sourcePath" and "languages".'); @@ -100,10 +295,10 @@ class MessageController extends Controller if (!is_dir($config['sourcePath'])) { throw new Exception("The source path {$config['sourcePath']} is not a valid directory."); } - if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) { - throw new Exception('Format should be either "php", "po" or "db".'); + if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'pot', 'db'])) { + throw new Exception('Format should be either "php", "po", "pot" or "db".'); } - if (in_array($config['format'], ['php', 'po'])) { + if (in_array($config['format'], ['php', 'po', 'pot'])) { if (!isset($config['messagePath'])) { throw new Exception('The configuration file must specify "messagePath".'); } elseif (!is_dir($config['messagePath'])) { @@ -111,7 +306,7 @@ class MessageController extends Controller } } if (empty($config['languages'])) { - throw new Exception("Languages cannot be empty."); + throw new Exception('Languages cannot be empty.'); } $files = FileHelper::findFiles(realpath($config['sourcePath']), $config); @@ -128,9 +323,9 @@ class MessageController extends Controller } if ($config['format'] === 'po') { $catalog = isset($config['catalog']) ? $config['catalog'] : 'messages'; - $this->saveMessagesToPO($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort'], $catalog); + $this->saveMessagesToPO($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort'], $catalog, $config['markUnused']); } else { - $this->saveMessagesToPHP($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort']); + $this->saveMessagesToPHP($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort'], $config['markUnused']); } } } elseif ($config['format'] === 'db') { @@ -146,8 +341,12 @@ class MessageController extends Controller $sourceMessageTable, $messageTable, $config['removeUnused'], - $config['languages'] + $config['languages'], + $config['markUnused'] ); + } elseif ($config['format'] === 'pot') { + $catalog = isset($config['catalog']) ? $config['catalog'] : 'messages'; + $this->saveMessagesToPOT($messages, $config['messagePath'], $catalog); } } @@ -160,13 +359,14 @@ class MessageController extends Controller * @param string $messageTable * @param boolean $removeUnused * @param array $languages + * @param boolean $markUnused */ - protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages) + protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages, $markUnused) { $q = new \yii\db\Query; $current = []; - foreach ($q->select(['id', 'category', 'message'])->from($sourceMessageTable)->all() as $row) { + foreach ($q->select(['id', 'category', 'message'])->from($sourceMessageTable)->all($db) as $row) { $current[$row['category']][$row['id']] = $row['message']; } @@ -197,12 +397,13 @@ class MessageController extends Controller } $obsolete = array_keys($obsolete); - $this->stdout("Inserting new messages..."); + $this->stdout('Inserting new messages...'); $savedFlag = false; foreach ($new as $category => $msgs) { foreach ($msgs as $m) { $savedFlag = true; +<<<<<<< HEAD <<<<<<< HEAD $db->createCommand() @@ -218,26 +419,38 @@ class MessageController extends Controller ->insert($messageTable, ['id' => $lastPk['id'], 'language' => $language]) ->execute(); >>>>>>> yiichina/master +======= + $lastPk = $db->schema->insert($sourceMessageTable, ['category' => $category, 'message' => $m]); + foreach ($languages as $language) { + $db->createCommand() + ->insert($messageTable, ['id' => $lastPk['id'], 'language' => $language]) + ->execute(); +>>>>>>> master } } } $this->stdout($savedFlag ? "saved.\n" : "Nothing new...skipped.\n"); - $this->stdout($removeUnused ? "Deleting obsoleted messages..." : "Updating obsoleted messages..."); + $this->stdout($removeUnused ? 'Deleting obsoleted messages...' : 'Updating obsoleted messages...'); if (empty($obsolete)) { $this->stdout("Nothing obsoleted...skipped.\n"); } else { if ($removeUnused) { $db->createCommand() +<<<<<<< HEAD <<<<<<< HEAD ->delete($sourceMessageTable, ['in', 'id', $obsolete])->execute(); ======= ->delete($sourceMessageTable, ['in', 'id', $obsolete]) ->execute(); >>>>>>> yiichina/master +======= + ->delete($sourceMessageTable, ['in', 'id', $obsolete]) + ->execute(); +>>>>>>> master $this->stdout("deleted.\n"); - } else { + } elseif ($markUnused) { $db->createCommand() ->update( $sourceMessageTable, @@ -245,6 +458,8 @@ class MessageController extends Controller ['in', 'id', $obsolete] )->execute(); $this->stdout("updated.\n"); + } else { + $this->stdout("kept untouched.\n"); } } } @@ -254,57 +469,89 @@ class MessageController extends Controller * * @param string $fileName name of the file to extract messages from * @param string $translator name of the function used to translate messages +<<<<<<< HEAD <<<<<<< HEAD * @param array $ignoreCategories message categories to ignore ======= * @param array $ignoreCategories message categories to ignore. * This parameter is available since version 2.0.4. >>>>>>> yiichina/master +======= + * @param array $ignoreCategories message categories to ignore. + * This parameter is available since version 2.0.4. +>>>>>>> master * @return array */ protected function extractMessages($fileName, $translator, $ignoreCategories = []) { $coloredFileName = Console::ansiFormat($fileName, [Console::FG_CYAN]); $this->stdout("Extracting messages from $coloredFileName...\n"); + $subject = file_get_contents($fileName); $messages = []; - foreach ((array)$translator as $currentTranslator) { + foreach ((array) $translator as $currentTranslator) { $translatorTokens = token_get_all('tokensEqual($token, $translatorTokens[$matchedTokensCount])) { - $matchedTokensCount++; - } else { - $matchedTokensCount = 0; - } - } elseif ($matchedTokensCount === $translatorTokensCount) { - // translator found + $messages = array_merge_recursive($messages, $this->extractMessagesFromTokens($tokens, $translatorTokens, $ignoreCategories)); + } - // end of translator call or end of something that we can't extract - if ($this->tokensEqual(')', $token)) { + $this->stdout("\n"); + + return $messages; + } + + /** + * Extracts messages from a parsed PHP tokens list. + * @param array $tokens tokens to be processed. + * @param array $translatorTokens translator tokens. + * @param array $ignoreCategories message categories to ignore. + * @return array messages. + */ + private function extractMessagesFromTokens(array $tokens, array $translatorTokens, array $ignoreCategories) + { + $messages = []; + $translatorTokensCount = count($translatorTokens); + $matchedTokensCount = 0; + $buffer = []; + $pendingParenthesisCount = 0; + + foreach ($tokens as $token) { + // finding out translator call + if ($matchedTokensCount < $translatorTokensCount) { + if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) { + $matchedTokensCount++; + } else { + $matchedTokensCount = 0; + } + } elseif ($matchedTokensCount === $translatorTokensCount) { + // translator found + + // end of function call + if ($this->tokensEqual(')', $token)) { + $pendingParenthesisCount--; + + if ($pendingParenthesisCount === 0) { + // end of translator call or end of something that we can't extract if (isset($buffer[0][0], $buffer[1], $buffer[2][0]) && $buffer[0][0] === T_CONSTANT_ENCAPSED_STRING && $buffer[1] === ',' && $buffer[2][0] === T_CONSTANT_ENCAPSED_STRING) { // is valid call we can extract - $category = stripcslashes($buffer[0][1]); $category = mb_substr($category, 1, mb_strlen($category) - 2); - if (!in_array($category, $ignoreCategories, true)) { + if (!$this->isCategoryIgnored($category, $ignoreCategories)) { $message = stripcslashes($buffer[2][1]); $message = mb_substr($message, 1, mb_strlen($message) - 2); $messages[$category][] = $message; } + + $nestedTokens = array_slice($buffer, 3); + if (count($nestedTokens) > $translatorTokensCount) { + // search for possible nested translator calls + $messages = array_merge_recursive($messages, $this->extractMessagesFromTokens($nestedTokens, $translatorTokens, $ignoreCategories)); + } } else { // invalid call or dynamic call we can't extract - $line = Console::ansiFormat($this->getLine($buffer), [Console::FG_CYAN]); $skipping = Console::ansiFormat('Skipping line', [Console::FG_YELLOW]); $this->stdout("$skipping $line. Make sure both category and message are static strings.\n"); @@ -312,18 +559,57 @@ class MessageController extends Controller // prepare for the next match $matchedTokensCount = 0; + $pendingParenthesisCount = 0; $buffer = []; - } elseif ($token !== '(' && isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { - // ignore comments, whitespaces and beginning of function call + } else { $buffer[] = $token; } + } elseif ($this->tokensEqual('(', $token)) { + // count beginning of function call, skipping translator beginning + if ($pendingParenthesisCount > 0) { + $buffer[] = $token; + } + $pendingParenthesisCount++; + } elseif (isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { + // ignore comments and whitespaces + $buffer[] = $token; + } + } + } + + return $messages; + } + + /** + * The method checks, whether the $category is ignored according to $ignoreCategories array. + * Examples: + * + * - `myapp` - will be ignored only `myapp` category; + * - `myapp*` - will be ignored by all categories beginning with `myapp` (`myapp`, `myapplication`, `myapprove`, `myapp/widgets`, `myapp.widgets`, etc). + * + * @param string $category category that is checked + * @param array $ignoreCategories message categories to ignore. + * @return boolean + * @since 2.0.7 + */ + protected function isCategoryIgnored($category, array $ignoreCategories) + { + $result = false; + + if (!empty($ignoreCategories)) { + if (in_array($category, $ignoreCategories, true)) { + $result = true; + } else { + foreach ($ignoreCategories as $pattern) { + if (strpos($pattern, '*') > 0 && strpos($category, rtrim($pattern, '*')) === 0) { + $result = true; + break; + } } } } - $this->stdout("\n"); - - return $messages; + return $result; } /** @@ -348,7 +634,7 @@ class MessageController extends Controller * Finds out a line of the first non-char PHP token found * * @param array $tokens - * @return int|string + * @return integer|string * @since 2.0.1 */ protected function getLine($tokens) @@ -369,8 +655,9 @@ class MessageController extends Controller * @param boolean $overwrite if existing file should be overwritten without backup * @param boolean $removeUnused if obsolete translations should be removed * @param boolean $sort if translations should be sorted + * @param boolean $markUnused if obsolete translations should be marked */ - protected function saveMessagesToPHP($messages, $dirName, $overwrite, $removeUnused, $sort) + protected function saveMessagesToPHP($messages, $dirName, $overwrite, $removeUnused, $sort, $markUnused) { foreach ($messages as $category => $msgs) { $file = str_replace("\\", '/', "$dirName/$category.php"); @@ -379,7 +666,7 @@ class MessageController extends Controller $msgs = array_values(array_unique($msgs)); $coloredFileName = Console::ansiFormat($file, [Console::FG_CYAN]); $this->stdout("Saving messages to $coloredFileName...\n"); - $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category); + $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category, $markUnused); } } @@ -392,17 +679,20 @@ class MessageController extends Controller * @param boolean $removeUnused if obsolete translations should be removed * @param boolean $sort if translations should be sorted * @param string $category message category + * @param boolean $markUnused if obsolete translations should be marked */ - protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category) + protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category, $markUnused) { if (is_file($fileName)) { - $existingMessages = require($fileName); + $rawExistingMessages = require($fileName); + $existingMessages = $rawExistingMessages; sort($messages); ksort($existingMessages); - if (array_keys($existingMessages) == $messages) { + if (array_keys($existingMessages) === $messages && (!$sort || array_keys($rawExistingMessages) === $messages)) { $this->stdout("Nothing new in \"$category\" category... Nothing to save.\n\n", Console::FG_GREEN); return; } + unset($rawExistingMessages); $merged = []; $untranslated = []; foreach ($messages as $message) { @@ -421,7 +711,7 @@ class MessageController extends Controller ksort($existingMessages); foreach ($existingMessages as $message => $translation) { if (!$removeUnused && !isset($merged[$message]) && !isset($todo[$message])) { - if (!empty($translation) && strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0) { + if (!empty($translation) && (!$markUnused || (strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0))) { $todo[$message] = $translation; } else { $todo[$message] = '@@' . $translation . '@@'; @@ -451,7 +741,7 @@ class MessageController extends Controller /** * Message translations. * - * This file is automatically generated by 'yii {$this->id}' command. + * This file is automatically generated by 'yii {$this->id}/{$this->action->id}' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -469,8 +759,13 @@ return $array; EOD; - file_put_contents($fileName, $content); - $this->stdout("Translation saved.\n\n", Console::FG_GREEN); + if (file_put_contents($fileName, $content) !== false) { + $this->stdout("Translation saved.\n\n", Console::FG_GREEN); + return self::EXIT_CODE_NORMAL; + } else { + $this->stdout("Translation was NOT saved.\n\n", Console::FG_RED); + return self::EXIT_CODE_ERROR; + } } /** @@ -482,8 +777,9 @@ EOD; * @param boolean $removeUnused if obsolete translations should be removed * @param boolean $sort if translations should be sorted * @param string $catalog message catalog + * @param boolean $markUnused if obsolete translations should be marked */ - protected function saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog) + protected function saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog, $markUnused) { $file = str_replace("\\", '/', "$dirName/$catalog.po"); FileHelper::createDirectory(dirname($file)); @@ -535,7 +831,7 @@ EOD; // add obsolete unused messages foreach ($existingMessages as $message => $translation) { if (!$removeUnused && !isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message])) { - if (!empty($translation) && substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { + if (!empty($translation) && (!$markUnused || (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@'))) { $todos[$category . chr(4) . $message] = $translation; } else { $todos[$category . chr(4) . $message] = '@@' . $translation . '@@'; @@ -568,4 +864,42 @@ EOD; $this->stdout("Nothing to save.\n", Console::FG_GREEN); } } + + /** + * Writes messages into POT file + * + * @param array $messages + * @param string $dirName name of the directory to write to + * @param string $catalog message catalog + * @since 2.0.6 + */ + protected function saveMessagesToPOT($messages, $dirName, $catalog) + { + $file = str_replace("\\", '/', "$dirName/$catalog.pot"); + FileHelper::createDirectory(dirname($file)); + $this->stdout("Saving messages to $file...\n"); + + $poFile = new GettextPoFile(); + + $merged = []; + + $hasSomethingToWrite = false; + foreach ($messages as $category => $msgs) { + $msgs = array_values(array_unique($msgs)); + + sort($msgs); + foreach ($msgs as $message) { + $merged[$category . chr(4) . $message] = ''; + } + ksort($merged); + $this->stdout("Category \"$category\" merged.\n"); + $hasSomethingToWrite = true; + } + if ($hasSomethingToWrite) { + $poFile->save($file, $merged); + $this->stdout("Translation saved.\n", Console::FG_GREEN); + } else { + $this->stdout("Nothing to save.\n", Console::FG_GREEN); + } + } } diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index f5ef0de421..4133fa9d1d 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -30,16 +30,16 @@ use yii\helpers\Console; * this command is executed, if it does not exist. You may also manually * create it as follows: * - * ~~~ + * ```sql * CREATE TABLE migration ( * version varchar(180) PRIMARY KEY, * apply_time integer * ) - * ~~~ + * ``` * * Below are some common usages of this command: * - * ~~~ + * ``` * # creates a new migration named 'create_user_table' * yii migrate/create create_user_table * @@ -48,7 +48,7 @@ use yii\helpers\Console; * * # reverts the last applied migration * yii migrate/down - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -63,6 +63,45 @@ class MigrateController extends BaseMigrateController * @inheritdoc */ public $templateFile = '@yii/views/migration.php'; + /** + * @var array a set of template paths for generating migration code automatically. + * + * The key is the template type, the value is a path or the alias. Supported types are: + * - `create_table`: table creating template + * - `drop_table`: table dropping template + * - `add_column`: adding new column template + * - `drop_column`: dropping column template + * - `create_junction`: create junction template + * + * @since 2.0.7 + */ + public $generatorTemplateFiles = [ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createTableMigration.php', + ]; + /** + * @var boolean indicates whether the table names generated should consider + * the `tablePrefix` setting of the DB connection. For example, if the table + * name is `post` the generator wil return `{{%post}}`. + * @since 2.0.8 + */ + public $useTablePrefix = false; + /** + * @var array column definition strings used for creating migration code. + * + * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`. + * For example, `--fields="name:string(12):notNull:unique"` + * produces a string column of size 12 which is not null and unique values. + * + * Note: primary key is added automatically and is named id by default. + * If you want to use another name you may specify it explicitly like + * `--fields="id_key:primaryKey,name:string(12):notNull:unique"` + * @since 2.0.7 + */ + public $fields = []; /** * @var Connection|array|string the DB connection object or the application component ID of the DB connection to use * when applying migrations. Starting from version 2.0.3, this can also be a configuration array @@ -78,10 +117,28 @@ class MigrateController extends BaseMigrateController { return array_merge( parent::options($actionID), - ['migrationTable', 'db'] // global for all actions + ['migrationTable', 'db'], // global for all actions + $actionID === 'create' + ? ['templateFile', 'fields', 'useTablePrefix'] + : [] ); } + /** + * @inheritdoc + * @since 2.0.8 + */ + public function optionAliases() + { + return array_merge(parent::optionAliases(), [ + 'f' => 'fields', + 'p' => 'migrationPath', + 't' => 'migrationTable', + 'F' => 'templateFile', + 'P' => 'useTablePrefix', + ]); + } + /** * This method is invoked right before an action is to be executed (after all possible filters.) * It checks the existence of the [[migrationPath]]. @@ -124,11 +181,15 @@ class MigrateController extends BaseMigrateController $query = new Query; $rows = $query->select(['version', 'apply_time']) ->from($this->migrationTable) +<<<<<<< HEAD <<<<<<< HEAD ->orderBy('version DESC') ======= ->orderBy('apply_time DESC, version DESC') >>>>>>> yiichina/master +======= + ->orderBy('apply_time DESC, version DESC') +>>>>>>> master ->limit($limit) ->createCommand($this->db) ->queryAll(); @@ -178,4 +239,154 @@ class MigrateController extends BaseMigrateController 'version' => $version, ])->execute(); } + + /** + * @inheritdoc + * @since 2.0.8 + */ + protected function generateMigrationSourceCode($params) + { + $parsedFields = $this->parseFields(); + $fields = $parsedFields['fields']; + $foreignKeys = $parsedFields['foreignKeys']; + + $name = $params['name']; + + $templateFile = $this->templateFile; + $table = null; + if (preg_match('/^create_junction_(.+)_and_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['create_junction']; + $firstTable = mb_strtolower($matches[1], Yii::$app->charset); + $secondTable = mb_strtolower($matches[2], Yii::$app->charset); + + $fields = array_merge( + [ + [ + 'property' => $firstTable . '_id', + 'decorators' => 'integer()', + ], + [ + 'property' => $secondTable . '_id', + 'decorators' => 'integer()', + ], + ], + $fields, + [ + [ + 'property' => 'PRIMARY KEY(' . + $firstTable . '_id, ' . + $secondTable . '_id)', + ], + ] + ); + + $foreignKeys[$firstTable . '_id'] = $firstTable; + $foreignKeys[$secondTable . '_id'] = $secondTable; + $table = $firstTable . '_' . $secondTable; + } elseif (preg_match('/^add_(.+)_to_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['add_column']; + $table = mb_strtolower($matches[2], Yii::$app->charset); + } elseif (preg_match('/^drop_(.+)_from_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['drop_column']; + $table = mb_strtolower($matches[2], Yii::$app->charset); + } elseif (preg_match('/^create_(.+)$/', $name, $matches)) { + $this->addDefaultPrimaryKey($fields); + $templateFile = $this->generatorTemplateFiles['create_table']; + $table = mb_strtolower($matches[1], Yii::$app->charset); + } elseif (preg_match('/^drop_(.+)$/', $name, $matches)) { + $this->addDefaultPrimaryKey($fields); + $templateFile = $this->generatorTemplateFiles['drop_table']; + $table = mb_strtolower($matches[1], Yii::$app->charset); + } + + foreach ($foreignKeys as $column => $relatedTable) { + $foreignKeys[$column] = [ + 'idx' => $this->generateTableName("idx-$table-$column"), + 'fk' => $this->generateTableName("fk-$table-$column"), + 'relatedTable' => $this->generateTableName($relatedTable), + ]; + } + + return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [ + 'table' => $this->generateTableName($table), + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, + ])); + } + + /** + * If `useTablePrefix` equals true, then the table name will contain the + * prefix format. + * + * @param string $tableName the table name to generate. + * @return string + * @since 2.0.8 + */ + protected function generateTableName($tableName) + { + if (!$this->useTablePrefix) { + return $tableName; + } + return '{{%' . $tableName . '}}'; + } + + /** + * Parse the command line migration fields + * @return array parse result with following fields: + * + * - fields: array, parsed fields + * - foreignKeys: array, detected foreign keys + * + * @since 2.0.7 + */ + protected function parseFields() + { + $fields = []; + $foreignKeys = []; + + foreach ($this->fields as $index => $field) { + $chunks = preg_split('/\s?:\s?/', $field, null); + $property = array_shift($chunks); + + foreach ($chunks as $i => &$chunk) { + if (strpos($chunk, 'foreignKey') === 0) { + preg_match('/foreignKey\((\w*)\)/', $chunk, $matches); + $foreignKeys[$property] = isset($matches[1]) + ? $matches[1] + : preg_replace('/_id$/', '', $property); + + unset($chunks[$i]); + continue; + } + + if (!preg_match('/^(.+?)\(([^)]+)\)$/', $chunk)) { + $chunk .= '()'; + } + } + $fields[] = [ + 'property' => $property, + 'decorators' => implode('->', $chunks), + ]; + } + + return [ + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, + ]; + } + + /** + * Adds default primary key to fields list if there's no primary key specified + * @param array $fields parsed fields + * @since 2.0.7 + */ + protected function addDefaultPrimaryKey(&$fields) + { + foreach ($fields as $field) { + if ($field['decorators'] === 'primaryKey()') { + return; + } + } + array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']); + } } diff --git a/framework/console/controllers/ServeController.php b/framework/console/controllers/ServeController.php new file mode 100644 index 0000000000..65ca37cf27 --- /dev/null +++ b/framework/console/controllers/ServeController.php @@ -0,0 +1,124 @@ + + * @since 2.0.7 + */ +class ServeController extends Controller +{ + const EXIT_CODE_NO_DOCUMENT_ROOT = 2; + const EXIT_CODE_NO_ROUTING_FILE = 3; + const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_SERVER = 4; + const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS = 5; + + /** + * @var int port to serve on. + */ + public $port = 8080; + /** + * @var string path or path alias to directory to serve + */ + public $docroot = '@app/web'; + /** + * @var string path to router script. + * See https://secure.php.net/manual/en/features.commandline.webserver.php + */ + public $router; + + + /** + * Runs PHP built-in web server + * + * @param string $address address to serve on. Either "host" or "host:port". + * + * @return integer + */ + public function actionIndex($address = 'localhost') + { + $documentRoot = Yii::getAlias($this->docroot); + + if (strpos($address, ':') === false) { + $address = $address . ':' . $this->port; + } + + if (!is_dir($documentRoot)) { + $this->stdout("Document root \"$documentRoot\" does not exist.\n", Console::FG_RED); + return self::EXIT_CODE_NO_DOCUMENT_ROOT; + } + + if ($this->isAddressTaken($address)) { + $this->stdout("http://$address is taken by another process.\n", Console::FG_RED); + return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; + } + + if ($this->router !== null && !file_exists($this->router)) { + $this->stdout("Routing file \"$this->router\" does not exist.\n", Console::FG_RED); + return self::EXIT_CODE_NO_ROUTING_FILE; + } + + $this->stdout("Server started on http://{$address}/\n"); + $this->stdout("Document root is \"{$documentRoot}\"\n"); + if ($this->router) { + $this->stdout("Routing file is \"$this->router\"\n"); + } + $this->stdout("Quit the server with CTRL-C or COMMAND-C.\n"); + + passthru('"' . PHP_BINARY . '"' . " -S {$address} -t \"{$documentRoot}\" $this->router"); + } + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), [ + 'docroot', + 'router', + 'port', + ]); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function optionAliases() + { + return array_merge(parent::optionAliases(), [ + 't' => 'docroot', + 'p' => 'port', + 'r' => 'router', + ]); + } + + /** + * @param string $address server address + * @return boolean if address is already in use + */ + protected function isAddressTaken($address) + { + list($hostname, $port) = explode(':', $address); + $fp = @fsockopen($hostname, $port, $errno, $errstr, 3); + if ($fp === false) { + return false; + } + fclose($fp); + return true; + } +} diff --git a/framework/data/ActiveDataProvider.php b/framework/data/ActiveDataProvider.php index efde6b0b2b..aff4887629 100644 --- a/framework/data/ActiveDataProvider.php +++ b/framework/data/ActiveDataProvider.php @@ -22,7 +22,7 @@ use yii\di\Instance; * * The following is an example of using ActiveDataProvider to provide ActiveRecord instances: * - * ~~~ + * ```php * $provider = new ActiveDataProvider([ * 'query' => Post::find(), * 'pagination' => [ @@ -32,12 +32,12 @@ use yii\di\Instance; * * // get the posts in the current page * $posts = $provider->getModels(); - * ~~~ + * ``` * * And the following example shows how to use ActiveDataProvider without ActiveRecord: * - * ~~~ - * $query = new Query; + * ```php + * $query = new Query(); * $provider = new ActiveDataProvider([ * 'query' => $query->from('post'), * 'pagination' => [ @@ -47,7 +47,7 @@ use yii\di\Instance; * * // get the posts in the current page * $posts = $provider->getModels(); - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -171,6 +171,7 @@ class ActiveDataProvider extends BaseDataProvider public function setSort($value) { parent::setSort($value); +<<<<<<< HEAD <<<<<<< HEAD if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) { /* @var $model Model */ @@ -185,6 +186,11 @@ class ActiveDataProvider extends BaseDataProvider if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) { /* @var $model Model */ $model = new $this->query->modelClass; +======= + if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) { + /* @var $model Model */ + $model = new $this->query->modelClass; +>>>>>>> master if (empty($sort->attributes)) { foreach ($model->attributes() as $attribute) { $sort->attributes[$attribute] = [ @@ -194,12 +200,19 @@ class ActiveDataProvider extends BaseDataProvider ]; } } else { +<<<<<<< HEAD foreach($sort->attributes as $attribute => $config) { +======= + foreach ($sort->attributes as $attribute => $config) { +>>>>>>> master if (!isset($config['label'])) { $sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute); } } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } } } diff --git a/framework/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php index 0c65baed77..37ce892177 100644 --- a/framework/data/ArrayDataProvider.php +++ b/framework/data/ArrayDataProvider.php @@ -63,7 +63,12 @@ class ArrayDataProvider extends BaseDataProvider * The array elements must use zero-based integer keys. */ public $allModels; - + /** + * @var string the name of the [[yii\base\Model|Model]] class that will be represented. + * This property is used to get columns' names. + * @since 2.0.9 + */ + public $modelClass; /** * @inheritdoc @@ -82,11 +87,15 @@ class ArrayDataProvider extends BaseDataProvider $pagination->totalCount = $this->getTotalCount(); if ($pagination->getPageSize() > 0) { +<<<<<<< HEAD <<<<<<< HEAD $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); ======= $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit(), true); >>>>>>> yiichina/master +======= + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit(), true); +>>>>>>> master } } diff --git a/framework/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php index 5f61cc0be5..01ea42b347 100644 --- a/framework/data/BaseDataProvider.php +++ b/framework/data/BaseDataProvider.php @@ -33,7 +33,7 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa /** * @var string an ID that uniquely identifies the data provider among all data providers. * You should set this property if the same page contains two or more different data providers. - * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. + * Otherwise, the [[pagination]] and [[sort]] may not work properly. */ public $id; diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index 7262d03588..73d48f2404 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -26,7 +26,7 @@ use yii\web\Request; * * Controller action: * - * ~~~ + * ```php * function actionIndex() * { * $query = Article::find()->where(['status' => 1]); @@ -41,11 +41,11 @@ use yii\web\Request; * 'pages' => $pages, * ]); * } - * ~~~ + * ``` * * View: * - * ~~~ + * ```php * foreach ($models as $model) { * // display $model here * } @@ -54,7 +54,7 @@ use yii\web\Request; * echo LinkPager::widget([ * 'pagination' => $pages, * ]); - * ~~~ + * ``` * * @property integer $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement * for fetching the current page of data. Note that if the page size is infinite, a value -1 will be returned. @@ -131,7 +131,7 @@ class Pagination extends Object implements Linkable */ public $defaultPageSize = 20; /** - * @var array|boolean the page size limits. The first array element stands for the minimal page size, and the second + * @var array|false the page size limits. The first array element stands for the minimal page size, and the second * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]]. */ public $pageSizeLimit = [1, 50]; diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php index 125d658956..f36b8c4f64 100644 --- a/framework/data/SqlDataProvider.php +++ b/framework/data/SqlDataProvider.php @@ -25,7 +25,7 @@ use yii\di\Instance; * * SqlDataProvider may be used in the following way: * - * ~~~ + * ```php * $count = Yii::$app->db->createCommand(' * SELECT COUNT(*) FROM user WHERE status=:status * ', [':status' => 1])->queryScalar(); @@ -52,7 +52,7 @@ use yii\di\Instance; * * // get the user records in the current page * $models = $dataProvider->getModels(); - * ~~~ + * ``` * * Note: if you want to use the pagination feature, you must configure the [[totalCount]] property * to be the total number of rows (without pagination). And if you want to use the sorting feature, diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 6ad60bf140..3a53a7ae12 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -7,6 +7,8 @@ namespace yii\db; +use yii\base\InvalidConfigException; + /** * ActiveQuery represents a DB query associated with an Active Record class. * @@ -37,6 +39,7 @@ namespace yii\db; * ActiveQuery also provides the following additional query options: * * - [[with()]]: list of relations that this query should be performed with. + * - [[joinWith()]]: reuse a relation query definition to add a join to a query. * - [[indexBy()]]: the name of the column by which the query result should be indexed. * - [[asArray()]]: whether to return each record as an array. * @@ -152,19 +155,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface } if (empty($this->select) && !empty($this->join)) { - foreach ((array) $this->from as $alias => $table) { - if (is_string($alias)) { - $this->select = ["$alias.*"]; - } elseif (is_string($table)) { - if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $table, $matches)) { - $alias = $matches[2]; - } else { - $alias = $table; - } - $this->select = ["$alias.*"]; - } - break; - } + list(, $alias) = $this->getQueryTableName($this); + $this->select = ["$alias.*"]; } if ($this->primaryModel === null) { @@ -235,6 +227,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface * Removes duplicated models by checking their primary key values. * This method is mainly called when a join query is performed, which may cause duplicated rows being returned. * @param array $models the models to be checked + * @throws InvalidConfigException if model primary key is empty * @return array the distinctive models */ private function removeDuplicatedModels($models) @@ -245,9 +238,14 @@ class ActiveQuery extends Query implements ActiveQueryInterface $pks = $class::primaryKey(); if (count($pks) > 1) { + // composite primary key foreach ($models as $i => $model) { $key = []; foreach ($pks as $pk) { + if (!isset($model[$pk])) { + // do not continue if the primary key is not part of the result set + break 2; + } $key[] = $model[$pk]; } $key = serialize($key); @@ -257,9 +255,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface $hash[$key] = true; } } + } elseif (empty($pks)) { + throw new InvalidConfigException("Primary key of '{$class}' can not be empty."); } else { + // single column primary key $pk = reset($pks); foreach ($models as $i => $model) { + if (!isset($model[$pk])) { + // do not continue if the primary key is not part of the result set + break; + } $key = $model[$pk]; if (isset($hash[$key])) { unset($models[$i]); @@ -315,6 +320,26 @@ class ActiveQuery extends Query implements ActiveQueryInterface return $db->createCommand($sql, $params); } + /** + * @inheritdoc + */ + protected function queryScalar($selectExpression, $db) + { + if ($this->sql === null) { + return parent::queryScalar($selectExpression, $db); + } + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + if ($db === null) { + $db = $modelClass::getDb(); + } + return (new Query)->select([$selectExpression]) + ->from(['c' => "({$this->sql})"]) + ->params($this->params) + ->createCommand($db) + ->queryScalar(); + } + /** * Joins with the specified relations. * @@ -322,7 +347,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface * Based on the definition of the specified relation(s), the method will append one or multiple * JOIN statements to the current query. * - * If the `$eagerLoading` parameter is true, the method will also eager loading the specified relations, + * If the `$eagerLoading` parameter is true, the method will also perform eager loading for the specified relations, * which is equivalent to calling [[with()]] using the specified relations. * * Note that because a JOIN query will be performed, you are responsible to disambiguate column names. @@ -330,35 +355,72 @@ class ActiveQuery extends Query implements ActiveQueryInterface * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations. * - * @param string|array $with the relations to be joined. Each array element represents a single relation. - * The array keys are relation names, and the array values are the corresponding anonymous functions that - * can be used to modify the relation queries on-the-fly. If a relation query does not need modification, - * you may use the relation name as the array value. Sub-relations can also be specified (see [[with()]]). - * For example, + * @param string|array $with the relations to be joined. This can either be a string, representing a relation name or + * an array with the following semantics: + * + * - Each array element represents a single relation. + * - You may specify the relation name as the array key and provide an anonymous functions that + * can be used to modify the relation queries on-the-fly as the array value. + * - If a relation query does not need modification, you may use the relation name as the array value. + * + * The relation name may optionally contain an alias for the relation table (e.g. `books b`). + * + * Sub-relations can also be specified, see [[with()]] for the syntax. + * + * In the following you find some examples: * * ```php * // find all orders that contain books, and eager loading "books" * Order::find()->joinWith('books', true, 'INNER JOIN')->all(); * // find all orders, eager loading "books", and sort the orders and books by the book names. * Order::find()->joinWith([ - * 'books' => function ($query) { + * 'books' => function (\yii\db\ActiveQuery $query) { * $query->orderBy('item.name'); * } * ])->all(); + * // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table + * Order::find()->joinWith(['books b'], true, 'INNER JOIN')->where(['b.category' => 'Science fiction'])->all(); * ``` * + * The alias syntax is available since version 2.0.7. + * * @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`. * When this is a boolean, it applies to all relations specified in `$with`. Use an array - * to explicitly list which relations in `$with` need to be eagerly loaded. + * to explicitly list which relations in `$with` need to be eagerly loaded. Defaults to `true`. * @param string|array $joinType the join type of the relations specified in `$with`. * When this is a string, it applies to all relations specified in `$with`. Use an array * in the format of `relationName => joinType` to specify different join types for different relations. - * @return static the query object itself + * @return $this the query object itself */ public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN') { - $this->joinWith[] = [(array) $with, $eagerLoading, $joinType]; + $relations = []; + foreach ((array) $with as $name => $callback) { + if (is_int($name)) { + $name = $callback; + $callback = null; + } + if (preg_match('/^(.*?)(?:\s+AS\s+|\s+)(\w+)$/i', $name, $matches)) { + // relation is defined with an alias, adjust callback to apply alias + list(, $relation, $alias) = $matches; + $name = $relation; + $callback = function ($query) use ($callback, $alias) { + /** @var $query ActiveQuery */ + $query->alias($alias); + if ($callback !== null) { + call_user_func($callback, $query); + } + }; + } + + if ($callback === null) { + $relations[] = $name; + } else { + $relations[$name] = $callback; + } + } + $this->joinWith[] = [$relations, $eagerLoading, $joinType]; return $this; } @@ -367,13 +429,14 @@ class ActiveQuery extends Query implements ActiveQueryInterface $join = $this->join; $this->join = []; + $model = new $this->modelClass; foreach ($this->joinWith as $config) { list ($with, $eagerLoading, $joinType) = $config; - $this->joinWithRelations(new $this->modelClass, $with, $joinType); + $this->joinWithRelations($model, $with, $joinType); if (is_array($eagerLoading)) { foreach ($with as $name => $callback) { - if (is_integer($name)) { + if (is_int($name)) { if (!in_array($callback, $eagerLoading, true)) { unset($with[$name]); } @@ -407,9 +470,9 @@ class ActiveQuery extends Query implements ActiveQueryInterface * Inner joins with the specified relations. * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN". * Please refer to [[joinWith()]] for detailed usage of this method. - * @param string|array $with the relations to be joined with - * @param boolean|array $eagerLoading whether to eager loading the relations - * @return static the query object itself + * @param string|array $with the relations to be joined with. + * @param boolean|array $eagerLoading whether to eager loading the relations. + * @return $this the query object itself * @see joinWith() */ public function innerJoinWith($with, $eagerLoading = true) @@ -428,7 +491,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface $relations = []; foreach ($with as $name => $callback) { - if (is_integer($name)) { + if (is_int($name)) { $name = $callback; $callback = null; } @@ -597,13 +660,18 @@ class ActiveQuery extends Query implements ActiveQueryInterface * ```php * public function getActiveUsers() * { - * return $this->hasMany(User::className(), ['id' => 'user_id'])->onCondition(['active' => true]); + * return $this->hasMany(User::className(), ['id' => 'user_id']) + * ->onCondition(['active' => true]); * } * ``` * + * Note that this condition is applied in case of a join as well as when fetching the related records. + * Thus only fields of the related table can be used in the condition. Trying to access fields of the primary + * record will cause an error in a non-join-query. + * * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself */ public function onCondition($condition, $params = []) { @@ -618,7 +686,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface * @param string|array $condition the new ON condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see onCondition() * @see orOnCondition() */ @@ -639,7 +707,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface * @param string|array $condition the new ON condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see onCondition() * @see andOnCondition() */ @@ -673,7 +741,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface * in the [[primaryModel]] table. * @param callable $callable a PHP callback for customizing the relation associated with the junction table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return static + * @return $this the query object itself * @see via() */ public function viaTable($tableName, $link, callable $callable = null) @@ -691,4 +759,34 @@ class ActiveQuery extends Query implements ActiveQueryInterface return $this; } + + /** + * Define an alias for the table defined in [[modelClass]]. + * + * This method will adjust [[from]] so that an already defined alias will be overwritten. + * If none was defined, [[from]] will be populated with the given alias. + * + * @param string $alias the table alias. + * @return $this the query object itself + * @since 2.0.7 + */ + public function alias($alias) + { + if (empty($this->from) || count($this->from) < 2) { + list($tableName, ) = $this->getQueryTableName($this); + $this->from = [$alias => $tableName]; + } else { + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + $tableName = $modelClass::tableName(); + + foreach ($this->from as $key => $table) { + if ($table === $tableName) { + unset($this->from[$key]); + $this->from[$alias] = $tableName; + } + } + } + return $this; + } } diff --git a/framework/db/ActiveQueryInterface.php b/framework/db/ActiveQueryInterface.php index e9c8067363..09ffa44412 100644 --- a/framework/db/ActiveQueryInterface.php +++ b/framework/db/ActiveQueryInterface.php @@ -25,7 +25,7 @@ interface ActiveQueryInterface extends QueryInterface /** * Sets the [[asArray]] property. * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. - * @return static the query object itself + * @return $this the query object itself */ public function asArray($value = true); @@ -35,16 +35,16 @@ interface ActiveQueryInterface extends QueryInterface * This can also be a callable (e.g. anonymous function) that returns the index value based on the given * row or model data. The signature of the callable should be: * - * ~~~ + * ```php * // $model is an AR instance when `asArray` is false, * // or an array of column values when `asArray` is true. * function ($model) * { * // return the index value corresponding to $model * } - * ~~~ + * ``` * - * @return static the query object itself + * @return $this the query object itself */ public function indexBy($column); @@ -59,27 +59,31 @@ interface ActiveQueryInterface extends QueryInterface * For example, `orders.address` means the `address` relation defined * in the model class corresponding to the `orders` relation. * +<<<<<<< HEAD <<<<<<< HEAD * The followings are some usage examples: ======= * The following are some usage examples: >>>>>>> yiichina/master +======= + * The following are some usage examples: +>>>>>>> master * - * ~~~ + * ```php * // find customers together with their orders and country * Customer::find()->with('orders', 'country')->all(); * // find customers together with their orders and the orders' shipping address * Customer::find()->with('orders.address')->all(); * // find customers together with their country and orders of status 1 * Customer::find()->with([ - * 'orders' => function ($query) { + * 'orders' => function (\yii\db\ActiveQuery $query) { * $query->andWhere('status = 1'); * }, * 'country', * ])->all(); - * ~~~ + * ``` * - * @return static the query object itself + * @return $this the query object itself */ public function with(); @@ -88,7 +92,7 @@ interface ActiveQueryInterface extends QueryInterface * @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation. * @param callable $callable a PHP callback for customizing the relation associated with the junction table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return static the relation object itself. + * @return $this the relation object itself. */ public function via($relationName, callable $callable = null); diff --git a/framework/db/ActiveQueryTrait.php b/framework/db/ActiveQueryTrait.php index 7b4a6e54dd..beff69b365 100644 --- a/framework/db/ActiveQueryTrait.php +++ b/framework/db/ActiveQueryTrait.php @@ -34,7 +34,7 @@ trait ActiveQueryTrait /** * Sets the [[asArray]] property. * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. - * @return static the query object itself + * @return $this the query object itself */ public function asArray($value = true) { @@ -53,35 +53,39 @@ trait ActiveQueryTrait * For example, `orders.address` means the `address` relation defined * in the model class corresponding to the `orders` relation. * +<<<<<<< HEAD <<<<<<< HEAD * The followings are some usage examples: ======= * The following are some usage examples: >>>>>>> yiichina/master +======= + * The following are some usage examples: +>>>>>>> master * - * ~~~ + * ```php * // find customers together with their orders and country * Customer::find()->with('orders', 'country')->all(); * // find customers together with their orders and the orders' shipping address * Customer::find()->with('orders.address')->all(); * // find customers together with their country and orders of status 1 * Customer::find()->with([ - * 'orders' => function ($query) { + * 'orders' => function (\yii\db\ActiveQuery $query) { * $query->andWhere('status = 1'); * }, * 'country', * ])->all(); - * ~~~ + * ``` * * You can call `with()` multiple times. Each call will add relations to the existing ones. * For example, the following two statements are equivalent: * - * ~~~ + * ```php * Customer::find()->with('orders', 'country')->all(); * Customer::find()->with('orders')->with('country')->all(); - * ~~~ + * ``` * - * @return static the query object itself + * @return $this the query object itself */ public function with() { @@ -95,7 +99,7 @@ trait ActiveQueryTrait $this->with = $with; } elseif (!empty($with)) { foreach ($with as $name => $value) { - if (is_integer($name)) { + if (is_int($name)) { // repeating relation is fine as normalizeRelations() handle it well $this->with[] = $value; } else { @@ -163,7 +167,10 @@ trait ActiveQueryTrait */ public function findWith($with, &$models) { - $primaryModel = new $this->modelClass; + $primaryModel = reset($models); + if (!$primaryModel instanceof ActiveRecordInterface) { + $primaryModel = new $this->modelClass; + } $relations = $this->normalizeRelations($primaryModel, $with); /* @var $relation ActiveQuery */ foreach ($relations as $name => $relation) { @@ -184,7 +191,7 @@ trait ActiveQueryTrait { $relations = []; foreach ($with as $name => $callback) { - if (is_integer($name)) { + if (is_int($name)) { $name = $callback; $callback = null; } diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 45c82c00d5..b403d470a5 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -53,11 +53,15 @@ use yii\helpers\StringHelper; * * Using the `new` operator to create a new, empty object * * Using a method to fetch an existing record (or records) from the database * +<<<<<<< HEAD <<<<<<< HEAD * Here is a short teaser how working with an ActiveRecord looks like: ======= * Below is an example showing some typical usage of ActiveRecord: >>>>>>> yiichina/master +======= + * Below is an example showing some typical usage of ActiveRecord: +>>>>>>> master * * ```php * $user = new User(); @@ -73,6 +77,7 @@ use yii\helpers\StringHelper; * * For more details and usage information on ActiveRecord, see the [guide article on ActiveRecord](guide:db-active-record). * +<<<<<<< HEAD <<<<<<< HEAD * @method ActiveQuery hasMany(string $class, array $link) see BaseActiveRecord::hasMany() for more info * @method ActiveQuery hasOne(string $class, array $link) see BaseActiveRecord::hasOne() for more info @@ -80,6 +85,10 @@ use yii\helpers\StringHelper; * @method ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info * @method ActiveQuery hasOne($class, array $link) see [[BaseActiveRecord::hasOne()]] for more info >>>>>>> yiichina/master +======= + * @method ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info + * @method ActiveQuery hasOne($class, array $link) see [[BaseActiveRecord::hasOne()]] for more info +>>>>>>> master * * @author Qiang Xue * @author Carsten Brandt @@ -119,11 +128,11 @@ class ActiveRecord extends BaseActiveRecord * * @param boolean $skipIfSet whether existing value should be preserved. * This will only set defaults for attributes that are `null`. - * @return static the model instance itself. + * @return $this the model instance itself. */ public function loadDefaultValues($skipIfSet = true) { - foreach ($this->getTableSchema()->columns as $column) { + foreach (static::getTableSchema()->columns as $column) { if ($column->defaultValue !== null && (!$skipIfSet || $this->{$column->name} === null)) { $this->{$column->name} = $column->defaultValue; } @@ -152,9 +161,9 @@ class ActiveRecord extends BaseActiveRecord * * Below is an example: * - * ~~~ + * ```php * $customers = Customer::findBySql('SELECT * FROM customer')->all(); - * ~~~ + * ``` * * @param string $sql the SQL statement to be executed * @param array $params parameters to be bound to the SQL statement during execution. @@ -172,7 +181,7 @@ class ActiveRecord extends BaseActiveRecord * Finds ActiveRecord instance(s) by the given condition. * This method is internally called by [[findOne()]] and [[findAll()]]. * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. + * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. * @throws InvalidConfigException if there is no primary key defined * @internal */ @@ -201,9 +210,9 @@ class ActiveRecord extends BaseActiveRecord * Updates the whole table using the provided attribute values and conditions. * For example, to change the status to be 1 for all customers whose status is 2: * - * ~~~ + * ```php * Customer::updateAll(['status' => 1], 'status = 2'); - * ~~~ + * ``` * * @param array $attributes attribute values (name-value pairs) to be saved into the table * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. @@ -223,9 +232,9 @@ class ActiveRecord extends BaseActiveRecord * Updates the whole table using the provided counter changes and conditions. * For example, to increment all customers' age by 1, * - * ~~~ + * ```php * Customer::updateAllCounters(['age' => 1]); - * ~~~ + * ``` * * @param array $counters the counters to be updated (attribute name => increment value). * Use negative values if you want to decrement the counters. @@ -254,9 +263,9 @@ class ActiveRecord extends BaseActiveRecord * * For example, to delete all customers whose status is 3: * - * ~~~ + * ```php * Customer::deleteAll('status = 3'); - * ~~~ + * ``` * * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. * Please refer to [[Query::where()]] on how to specify this parameter. @@ -300,12 +309,15 @@ class ActiveRecord extends BaseActiveRecord */ public static function getTableSchema() { - $schema = static::getDb()->getSchema()->getTableSchema(static::tableName()); - if ($schema !== null) { - return $schema; - } else { - throw new InvalidConfigException("The table does not exist: " . static::tableName()); + $tableSchema = static::getDb() + ->getSchema() + ->getTableSchema(static::tableName()); + + if ($tableSchema === null) { + throw new InvalidConfigException('The table does not exist: ' . static::tableName()); } + + return $tableSchema; } /** @@ -346,7 +358,7 @@ class ActiveRecord extends BaseActiveRecord * in transactions. You can do so by overriding this method and returning the operations * that need to be transactional. For example, * - * ~~~ + * ```php * return [ * 'admin' => self::OP_INSERT, * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, @@ -354,7 +366,7 @@ class ActiveRecord extends BaseActiveRecord * // 'api' => self::OP_ALL, * * ]; - * ~~~ + * ``` * * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) * should be done in a transaction; and in the "api" scenario, all the operations should be done @@ -387,16 +399,17 @@ class ActiveRecord extends BaseActiveRecord * * This method performs the following steps in order: * - * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation - * fails, it will skip the rest of the steps; - * 2. call [[afterValidate()]] when `$runValidation` is true. - * 3. call [[beforeSave()]]. If the method returns false, it will skip the - * rest of the steps; + * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]] + * returns `false`, the rest of the steps will be skipped; + * 2. call [[afterValidate()]] when `$runValidation` is true. If validation + * failed, the rest of the steps will be skipped; + * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, + * the rest of the steps will be skipped; * 4. insert the record into database. If this fails, it will skip the rest of the steps; * 5. call [[afterSave()]]; * * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] + * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_INSERT]], and [[EVENT_AFTER_INSERT]] * will be raised by the corresponding methods. * * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database. @@ -406,15 +419,16 @@ class ActiveRecord extends BaseActiveRecord * * For example, to insert a customer record: * - * ~~~ + * ```php * $customer = new Customer; * $customer->name = $name; * $customer->email = $email; * $customer->insert(); - * ~~~ + * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributes list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return boolean whether the attributes are valid and the record is inserted successfully. @@ -458,6 +472,7 @@ class ActiveRecord extends BaseActiveRecord return false; } $values = $this->getDirtyAttributes($attributes); +<<<<<<< HEAD if (empty($values)) { foreach ($this->getPrimaryKey(true) as $key => $value) { $values[$key] = $value; @@ -488,6 +503,15 @@ class ActiveRecord extends BaseActiveRecord $this->setAttribute($name, $id); $values[$name] = $id; >>>>>>> yiichina/master +======= + if (($primaryKeys = static::getDb()->schema->insert(static::tableName(), $values)) === false) { + return false; + } + foreach ($primaryKeys as $name => $value) { + $id = static::getTableSchema()->columns[$name]->phpTypecast($value); + $this->setAttribute($name, $id); + $values[$name] = $id; +>>>>>>> master } $changedAttributes = array_fill_keys(array_keys($values), null); @@ -502,43 +526,45 @@ class ActiveRecord extends BaseActiveRecord * * This method performs the following steps in order: * - * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation - * fails, it will skip the rest of the steps; - * 2. call [[afterValidate()]] when `$runValidation` is true. - * 3. call [[beforeSave()]]. If the method returns false, it will skip the - * rest of the steps; + * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]] + * returns `false`, the rest of the steps will be skipped; + * 2. call [[afterValidate()]] when `$runValidation` is true. If validation + * failed, the rest of the steps will be skipped; + * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, + * the rest of the steps will be skipped; * 4. save the record into database. If this fails, it will skip the rest of the steps; * 5. call [[afterSave()]]; * * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] + * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]] * will be raised by the corresponding methods. * * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. * * For example, to update a customer record: * - * ~~~ + * ```php * $customer = Customer::findOne($id); * $customer->name = $name; * $customer->email = $email; * $customer->update(); - * ~~~ + * ``` * * Note that it is possible the update does not affect any row in the table. * In this case, this method will return 0. For this reason, you should use the following * code to check if update() is successful or not: * - * ~~~ - * if ($this->update() !== false) { + * ```php + * if ($customer->update() !== false) { * // update successful * } else { * // update failed * } - * ~~~ + * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributeNames list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return integer|boolean the number of rows affected, or false if validation fails @@ -632,7 +658,7 @@ class ActiveRecord extends BaseActiveRecord if ($lock !== null) { $condition[$lock] = $this->$lock; } - $result = $this->deleteAll($condition); + $result = static::deleteAll($condition); if ($lock !== null && !$result) { throw new StaleObjectException('The object being deleted is outdated.'); } @@ -655,7 +681,7 @@ class ActiveRecord extends BaseActiveRecord return false; } - return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); + return static::tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); } /** diff --git a/framework/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php index 9c44a716e3..4ab7fc951e 100644 --- a/framework/db/ActiveRecordInterface.php +++ b/framework/db/ActiveRecordInterface.php @@ -157,6 +157,7 @@ interface ActiveRecordInterface * * - a scalar value (integer or string): query by a single primary key value and return the * corresponding record (or null if not found). +<<<<<<< HEAD <<<<<<< HEAD * - an array of name-value pairs: query by a set of attribute values and return a single record * matching all of them (or null if not found). @@ -172,6 +173,15 @@ interface ActiveRecordInterface * That this method will automatically call the `one()` method and return an [[ActiveRecordInterface|ActiveRecord]] * instance. For example, >>>>>>> yiichina/master +======= + * - a non-associative array: query by a list of primary key values and return the + * first record (or null if not found). + * - an associative array of name-value pairs: query by a set of attribute values and return a single record + * matching all of them (or null if not found). Note that `['id' => 1, 2]` is treated as a non-associative array. + * + * That this method will automatically call the `one()` method and return an [[ActiveRecordInterface|ActiveRecord]] + * instance. For example, +>>>>>>> master * * ```php * // find a single customer whose primary key value is 10 @@ -199,14 +209,20 @@ interface ActiveRecordInterface * * - a scalar value (integer or string): query by a single primary key value and return an array containing the * corresponding record (or an empty array if not found). +<<<<<<< HEAD <<<<<<< HEAD * - an array of scalar values (integer or string): query by a list of primary key values and return the +======= + * - a non-associative array: query by a list of primary key values and return the +>>>>>>> master * corresponding records (or an empty array if none was found). * Note that an empty condition will result in an empty result as it will be interpreted as a search for * primary keys and not an empty `WHERE` condition. - * - an array of name-value pairs: query by a set of attribute values and return an array of records - * matching all of them (or an empty array if none was found). + * - an associative array of name-value pairs: query by a set of attribute values and return an array of records + * matching all of them (or an empty array if none was found). Note that `['id' => 1, 2]` is treated as + * a non-associative array. * +<<<<<<< HEAD * Note that this method will automatically call the `all()` method and return an array of * [[ActiveRecordInterface|ActiveRecord]] instances. For example, ======= @@ -221,6 +237,10 @@ interface ActiveRecordInterface * This method will automatically call the `all()` method and return an array of [[ActiveRecordInterface|ActiveRecord]] * instances. For example, >>>>>>> yiichina/master +======= + * This method will automatically call the `all()` method and return an array of [[ActiveRecordInterface|ActiveRecord]] + * instances. For example, +>>>>>>> master * * ```php * // find the customers whose primary key value is 10 @@ -251,9 +271,9 @@ interface ActiveRecordInterface * Updates records using the provided attribute values and conditions. * For example, to change the status to be 1 for all customers whose status is 2: * - * ~~~ + * ```php * Customer::updateAll(['status' => 1], ['status' => '2']); - * ~~~ + * ``` * * @param array $attributes attribute values (name-value pairs) to be saved for the record. * Unlike [[update()]] these are not going to be validated. @@ -270,9 +290,9 @@ interface ActiveRecordInterface * * For example, to delete all customers whose status is 3: * - * ~~~ + * ```php * Customer::deleteAll([status = 3]); - * ~~~ + * ``` * * @param array $condition the condition that matches the records that should get deleted. * Please refer to [[QueryInterface::where()]] on how to specify this parameter. @@ -289,19 +309,19 @@ interface ActiveRecordInterface * * For example, to save a customer record: * - * ~~~ + * ```php * $customer = new Customer; // or $customer = Customer::findOne($id); * $customer->name = $name; * $customer->email = $email; * $customer->save(); - * ~~~ + * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be saved to database. `false` will be returned - * in this case. - * @param array $attributeNames list of attributes that need to be saved. Defaults to null, + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. + * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the saving succeeds + * @return boolean whether the saving succeeded (i.e. no validation errors occurred). */ public function save($runValidation = true, $attributeNames = null); @@ -317,8 +337,9 @@ interface ActiveRecordInterface * $customer->insert(); * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributes list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return boolean whether the attributes are valid and the record is inserted successfully. @@ -337,8 +358,9 @@ interface ActiveRecordInterface * $customer->update(); * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributeNames list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return integer|boolean the number of rows affected, or false if validation fails @@ -381,6 +403,15 @@ interface ActiveRecordInterface */ public function getRelation($name, $throwException = true); + /** + * Populates the named relation with the related records. + * Note that this method does not check if the relation exists or not. + * @param string $name the relation name (case-sensitive) + * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation. + * @since 2.0.8 + */ + public function populateRelation($name, $records); + /** * Establishes the relationship between two records. * diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php index d0f8710a90..cc70db383a 100644 --- a/framework/db/ActiveRelationTrait.php +++ b/framework/db/ActiveRelationTrait.php @@ -98,7 +98,7 @@ trait ActiveRelationTrait * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. * @param callable $callable a PHP callback for customizing the relation associated with the junction table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return static the relation object itself. + * @return $this the relation object itself. */ public function via($relationName, callable $callable = null) { @@ -128,7 +128,7 @@ trait ActiveRelationTrait * ``` * * @param string $relationName the name of the relation that is the inverse of this relation. - * @return static the relation object itself. + * @return $this the relation object itself. */ public function inverseOf($relationName) { @@ -160,20 +160,26 @@ trait ActiveRelationTrait return $related; } - $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf); - if ($this->multiple) { foreach ($related as $i => $relatedModel) { if ($relatedModel instanceof ActiveRecordInterface) { + if (!isset($inverseRelation)) { + $inverseRelation = $relatedModel->getRelation($this->inverseOf); + } $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model); } else { + if (!isset($inverseRelation)) { + $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf); + } $related[$i][$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model; } } } else { if ($related instanceof ActiveRecordInterface) { + $inverseRelation = $related->getRelation($this->inverseOf); $related->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model); } else { + $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf); $related[$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model; } } @@ -249,9 +255,10 @@ trait ActiveRelationTrait $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); foreach ($primaryModels as $i => $primaryModel) { - if ($this->multiple && count($link) == 1 && is_array($keys = $primaryModel[reset($link)])) { + if ($this->multiple && count($link) === 1 && is_array($keys = $primaryModel[reset($link)])) { $value = []; foreach ($keys as $key) { +<<<<<<< HEAD <<<<<<< HEAD if (!is_scalar($key)) { $key = serialize($key); @@ -267,6 +274,13 @@ trait ActiveRelationTrait // if indexBy is set, array_merge will cause renumbering of numeric array foreach ($buckets[$key] as $bucketKey => $bucketValue) { >>>>>>> yiichina/master +======= + $key = $this->normalizeModelKey($key); + if (isset($buckets[$key])) { + if ($this->indexBy !== null) { + // if indexBy is set, array_merge will cause renumbering of numeric array + foreach ($buckets[$key] as $bucketKey => $bucketValue) { +>>>>>>> master $value[$bucketKey] = $bucketValue; } } else { @@ -375,11 +389,15 @@ trait ActiveRelationTrait $linkKeys = array_keys($link); if (isset($map)) { +<<<<<<< HEAD <<<<<<< HEAD foreach ($models as $i => $model) { ======= foreach ($models as $model) { >>>>>>> yiichina/master +======= + foreach ($models as $model) { +>>>>>>> master $key = $this->getModelKey($model, $linkKeys); if (isset($map[$key])) { foreach (array_keys($map[$key]) as $key2) { @@ -388,11 +406,15 @@ trait ActiveRelationTrait } } } else { +<<<<<<< HEAD <<<<<<< HEAD foreach ($models as $i => $model) { ======= foreach ($models as $model) { >>>>>>> yiichina/master +======= + foreach ($models as $model) { +>>>>>>> master $key = $this->getModelKey($model, $linkKeys); $buckets[$key][] = $model; } @@ -481,9 +503,15 @@ trait ActiveRelationTrait } } else { // composite keys + + // ensure keys of $this->link are prefixed the same way as $attributes + $prefixedLink = array_combine( + $attributes, + array_values($this->link) + ); foreach ($models as $model) { $v = []; - foreach ($this->link as $attribute => $link) { + foreach ($prefixedLink as $attribute => $link) { $v[$attribute] = $model[$link]; } $values[] = $v; @@ -493,12 +521,13 @@ trait ActiveRelationTrait } /** - * @param ActiveRecord|array $model + * @param ActiveRecordInterface|array $model * @param array $attributes * @return string */ private function getModelKey($model, $attributes) { +<<<<<<< HEAD <<<<<<< HEAD if (count($attributes) > 1) { $key = []; @@ -506,13 +535,30 @@ trait ActiveRelationTrait $key[] = $model[$attribute]; } - return serialize($key); - } else { - $attribute = reset($attributes); - $key = $model[$attribute]; - - return is_scalar($key) ? $key : serialize($key); +======= + $key = []; + foreach ($attributes as $attribute) { + $key[] = $this->normalizeModelKey($model[$attribute]); } + if (count($key) > 1) { +>>>>>>> master + return serialize($key); + } + $key = reset($key); + return is_scalar($key) ? $key : serialize($key); + } + + /** + * @param mixed $value raw key value. + * @return string normalized key value. + */ + private function normalizeModelKey($value) + { + if (is_object($value) && method_exists($value, '__toString')) { + // ensure matching to special objects, which are convertable to string, for cross-DBMS relations, for example: `|MongoId` + $value = $value->__toString(); + } +<<<<<<< HEAD ======= $key = []; foreach ($attributes as $attribute) { @@ -537,6 +583,9 @@ trait ActiveRelationTrait } return $value; >>>>>>> yiichina/master +======= + return $value; +>>>>>>> master } /** @@ -553,9 +602,9 @@ trait ActiveRelationTrait $primaryModel = reset($primaryModels); if (!$primaryModel instanceof ActiveRecordInterface) { // when primaryModels are array of arrays (asArray case) - $primaryModel = new $this->modelClass; + $primaryModel = $this->modelClass; } - return $this->asArray()->all($primaryModel->getDb()); + return $this->asArray()->all($primaryModel::getDb()); } } diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index f70e6fa51d..0e73d18a32 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -8,6 +8,7 @@ namespace yii\db; use yii\base\InvalidConfigException; +use yii\base\Event; use yii\base\Model; use yii\base\InvalidParamException; use yii\base\ModelEvent; @@ -55,7 +56,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface */ const EVENT_BEFORE_INSERT = 'beforeInsert'; /** - * @event Event an event that is triggered after a record is inserted. + * @event AfterSaveEvent an event that is triggered after a record is inserted. */ const EVENT_AFTER_INSERT = 'afterInsert'; /** @@ -64,7 +65,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface */ const EVENT_BEFORE_UPDATE = 'beforeUpdate'; /** - * @event Event an event that is triggered after a record is updated. + * @event AfterSaveEvent an event that is triggered after a record is updated. */ const EVENT_AFTER_UPDATE = 'afterUpdate'; /** @@ -76,6 +77,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * @event Event an event that is triggered after a record is deleted. */ const EVENT_AFTER_DELETE = 'afterDelete'; + /** + * @event Event an event that is triggered after a record is refreshed. + * @since 2.0.8 + */ + const EVENT_AFTER_REFRESH = 'afterRefresh'; /** * @var array attribute values indexed by attribute names @@ -114,7 +120,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * Finds ActiveRecord instance(s) by the given condition. * This method is internally called by [[findOne()]] and [[findAll()]]. * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. + * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. * @throws InvalidConfigException if there is no primary key defined * @internal */ @@ -139,9 +145,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * Updates the whole table using the provided attribute values and conditions. * For example, to change the status to be 1 for all customers whose status is 2: * - * ~~~ + * ```php * Customer::updateAll(['status' => 1], 'status = 2'); - * ~~~ + * ``` * * @param array $attributes attribute values (name-value pairs) to be saved into the table * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. @@ -158,9 +164,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * Updates the whole table using the provided counter changes and conditions. * For example, to increment all customers' age by 1, * - * ~~~ + * ```php * Customer::updateAllCounters(['age' => 1]); - * ~~~ + * ``` * * @param array $counters the counters to be updated (attribute name => increment value). * Use negative values if you want to decrement the counters. @@ -180,9 +186,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * * For example, to delete all customers whose status is 3: * - * ~~~ + * ```php * Customer::deleteAll('status = 3'); - * ~~~ + * ``` * * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. * Please refer to [[Query::where()]] on how to specify this parameter. @@ -310,12 +316,12 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * For example, to declare the `country` relation for `Customer` class, we can write * the following code in the `Customer` class: * - * ~~~ + * ```php * public function getCountry() * { * return $this->hasOne(Country::className(), ['id' => 'country_id']); * } - * ~~~ + * ``` * * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name * in the related class `Country`, while the 'country_id' value refers to an attribute name @@ -351,12 +357,12 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * For example, to declare the `orders` relation for `Customer` class, we can write * the following code in the `Customer` class: * - * ~~~ + * ```php * public function getOrders() * { * return $this->hasMany(Order::className(), ['customer_id' => 'id']); * } - * ~~~ + * ``` * * Note that in the above, the 'customer_id' key in the `$link` parameter refers to * an attribute name in the related class `Order`, while the 'id' value refers to @@ -513,13 +519,21 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Returns a value indicating whether the named attribute has been changed. +<<<<<<< HEAD <<<<<<< HEAD * @param string $name the name of the attribute +======= + * @param string $name the name of the attribute. + * @param boolean $identical whether the comparison of new and old value is made for + * identical values using `===`, defaults to `true`. Otherwise `==` is used for comparison. + * This parameter is available since version 2.0.4. +>>>>>>> master * @return boolean whether the attribute has been changed */ - public function isAttributeChanged($name) + public function isAttributeChanged($name, $identical = true) { if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { +<<<<<<< HEAD return $this->_attributes[$name] !== $this->_oldAttributes[$name]; ======= * @param string $name the name of the attribute. @@ -531,12 +545,17 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function isAttributeChanged($name, $identical = true) { if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { +======= +>>>>>>> master if ($identical) { return $this->_attributes[$name] !== $this->_oldAttributes[$name]; } else { return $this->_attributes[$name] != $this->_oldAttributes[$name]; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]); } @@ -544,6 +563,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Returns the attribute values that have been modified since they are loaded or saved most recently. + * + * The comparison of new and old values is made for identical values using `===`. + * * @param string[]|null $names the names of the attributes whose values may be returned if they are * changed recently. If null, [[attributes()]] will be used. * @return array the changed attribute values (name-value pairs) @@ -579,19 +601,19 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * * For example, to save a customer record: * - * ~~~ - * $customer = new Customer; // or $customer = Customer::findOne($id); + * ```php + * $customer = new Customer; // or $customer = Customer::findOne($id); * $customer->name = $name; * $customer->email = $email; * $customer->save(); - * ~~~ + * ``` * - * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be saved to database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the saving succeeds + * @return boolean whether the saving succeeded (i.e. no validation errors occurred). */ public function save($runValidation = true, $attributeNames = null) { @@ -607,43 +629,45 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * * This method performs the following steps in order: * - * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation - * fails, it will skip the rest of the steps; - * 2. call [[afterValidate()]] when `$runValidation` is true. - * 3. call [[beforeSave()]]. If the method returns false, it will skip the - * rest of the steps; + * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]] + * returns `false`, the rest of the steps will be skipped; + * 2. call [[afterValidate()]] when `$runValidation` is true. If validation + * failed, the rest of the steps will be skipped; + * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, + * the rest of the steps will be skipped; * 4. save the record into database. If this fails, it will skip the rest of the steps; * 5. call [[afterSave()]]; * * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] + * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]] * will be raised by the corresponding methods. * * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. * * For example, to update a customer record: * - * ~~~ + * ```php * $customer = Customer::findOne($id); * $customer->name = $name; * $customer->email = $email; * $customer->update(); - * ~~~ + * ``` * * Note that it is possible the update does not affect any row in the table. * In this case, this method will return 0. For this reason, you should use the following * code to check if update() is successful or not: * - * ~~~ - * if ($this->update() !== false) { + * ```php + * if ($customer->update() !== false) { * // update successful * } else { * // update failed * } - * ~~~ + * ``` * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be inserted into the database. + * @param boolean $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return integer|boolean the number of rows affected, or false if validation fails @@ -679,7 +703,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface { $attrs = []; foreach ($attributes as $name => $value) { - if (is_integer($name)) { + if (is_int($name)) { $attrs[] = $value; } else { $this->$name = $value; @@ -692,7 +716,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface return 0; } - $rows = $this->updateAll($values, $this->getOldPrimaryKey(true)); + $rows = static::updateAll($values, $this->getOldPrimaryKey(true)); foreach ($values as $name => $value) { $this->_oldAttributes[$name] = $this->_attributes[$name]; @@ -725,12 +749,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } // We do not check the return value of updateAll() because it's possible // that the UPDATE statement doesn't change anything and thus returns 0. - $rows = $this->updateAll($values, $condition); + $rows = static::updateAll($values, $condition); if ($lock !== null && !$rows) { throw new StaleObjectException('The object being updated is outdated.'); } + if (isset($values[$lock])) { + $this->$lock = $values[$lock]; + } + $changedAttributes = []; foreach ($values as $name => $value) { $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; @@ -748,10 +776,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * * An example usage is as follows: * - * ~~~ + * ```php * $post = Post::findOne($id); * $post->updateCounters(['view_count' => 1]); - * ~~~ + * ``` * * @param array $counters the counters to be updated (attribute name => increment value) * Use negative values if you want to decrement the counters. @@ -760,17 +788,23 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface */ public function updateCounters($counters) { - if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { + if (static::updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { foreach ($counters as $name => $value) { +<<<<<<< HEAD <<<<<<< HEAD $this->_attributes[$name] += $value; ======= +======= +>>>>>>> master if (!isset($this->_attributes[$name])) { $this->_attributes[$name] = $value; } else { $this->_attributes[$name] += $value; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master $this->_oldAttributes[$name] = $this->_attributes[$name]; } return true; @@ -809,7 +843,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface if ($lock !== null) { $condition[$lock] = $this->$lock; } - $result = $this->deleteAll($condition); + $result = static::deleteAll($condition); if ($lock !== null && !$result) { throw new StaleObjectException('The object being deleted is outdated.'); } @@ -869,7 +903,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false. * When overriding this method, make sure you call the parent implementation like the following: * - * ~~~ + * ```php * public function beforeSave($insert) * { * if (parent::beforeSave($insert)) { @@ -879,7 +913,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * return false; * } * } - * ~~~ + * ``` * * @param boolean $insert whether this method called while inserting a record. * If false, it means the method is called while updating a record. @@ -911,7 +945,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function afterSave($insert, $changedAttributes) { $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([ - 'changedAttributes' => $changedAttributes + 'changedAttributes' => $changedAttributes, ])); } @@ -920,7 +954,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * The default implementation raises the [[EVENT_BEFORE_DELETE]] event. * When overriding this method, make sure you call the parent implementation like the following: * - * ~~~ + * ```php * public function beforeDelete() * { * if (parent::beforeDelete()) { @@ -930,7 +964,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * return false; * } * } - * ~~~ + * ``` * * @return boolean whether the record should be deleted. Defaults to true. */ @@ -955,13 +989,17 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Repopulates this active record with the latest data. + * + * If the refresh is successful, an [[EVENT_AFTER_REFRESH]] event will be triggered. + * This event is available since version 2.0.8. + * * @return boolean whether the row still exists in the database. If true, the latest data * will be populated to this active record. Otherwise, this record will remain unchanged. */ public function refresh() { /* @var $record BaseActiveRecord */ - $record = $this->findOne($this->getPrimaryKey(true)); + $record = static::findOne($this->getPrimaryKey(true)); if ($record === null) { return false; } @@ -970,10 +1008,23 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } $this->_oldAttributes = $this->_attributes; $this->_related = []; + $this->afterRefresh(); return true; } + /** + * This method is called when the AR object is refreshed. + * The default implementation will trigger an [[EVENT_AFTER_REFRESH]] event. + * When overriding this method, make sure you call the parent implementation to ensure the + * event is triggered. + * @since 2.0.8 + */ + public function afterRefresh() + { + $this->trigger(self::EVENT_AFTER_REFRESH); + } + /** * Returns a value indicating whether the given active record is the same as the current one. * The comparison is made by comparing the table names and the primary key values of the two active records. @@ -1098,7 +1149,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Returns whether there is an element at the specified offset. - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param mixed $offset the offset to check on * @return boolean whether there is an element at the specified offset. */ @@ -1166,11 +1217,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * * Note that this method requires that the primary key value is not null. * - * @param string $name the case sensitive name of the relationship + * @param string $name the case sensitive name of the relationship. * @param ActiveRecordInterface $model the model to be linked with the current one. * @param array $extraColumns additional column values to be saved into the junction table. * This parameter is only meaningful for a relationship involving a junction table - * (i.e., a relation set with [[ActiveRelationTrait::via()]] or `[[ActiveQuery::viaTable()]]`.) + * (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].) * @throws InvalidCallException if the method is unable to link two models. */ public function link($name, $model, $extraColumns = []) @@ -1179,11 +1230,15 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface if ($relation->via !== null) { if ($this->getIsNewRecord() || $model->getIsNewRecord()) { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); ======= throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.'); >>>>>>> yiichina/master +======= + throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.'); +>>>>>>> master } if (is_array($relation->via)) { /* @var $viaRelation ActiveQuery */ @@ -1220,14 +1275,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } } else { $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = $this->isPrimaryKey(array_values($relation->link)); + $p2 = static::isPrimaryKey(array_values($relation->link)); if ($p1 && $p2) { if ($this->getIsNewRecord() && $model->getIsNewRecord()) { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidCallException('Unable to link models: both models are newly created.'); ======= throw new InvalidCallException('Unable to link models: at most one model can be newly created.'); >>>>>>> yiichina/master +======= + throw new InvalidCallException('Unable to link models: at most one model can be newly created.'); +>>>>>>> master } elseif ($this->getIsNewRecord()) { $this->bindModels(array_flip($relation->link), $this, $model); } else { @@ -1238,11 +1297,15 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } elseif ($p2) { $this->bindModels($relation->link, $model, $this); } else { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidCallException('Unable to link models: the link does not involve any primary key.'); ======= throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.'); >>>>>>> yiichina/master +======= + throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.'); +>>>>>>> master } } @@ -1251,8 +1314,12 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface $this->_related[$name] = $model; } elseif (isset($this->_related[$name])) { if ($relation->indexBy !== null) { - $indexBy = $relation->indexBy; - $this->_related[$name][$model->$indexBy] = $model; + if ($relation->indexBy instanceof \Closure) { + $index = call_user_func($relation->indexBy, $model); + } else { + $index = $model->{$relation->indexBy}; + } + $this->_related[$name][$index] = $model; } else { $this->_related[$name][] = $model; } @@ -1318,12 +1385,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } } else { $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = $this->isPrimaryKey(array_values($relation->link)); + $p2 = static::isPrimaryKey(array_values($relation->link)); if ($p2) { - foreach ($relation->link as $a => $b) { - $model->$a = null; + if ($delete) { + $model->delete(); + } else { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $model->save(false); } - $delete ? $model->delete() : $model->save(false); } elseif ($p1) { foreach ($relation->link as $a => $b) { if (is_array($this->$b)) { // relation via array valued attribute @@ -1347,7 +1418,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } elseif (isset($this->_related[$name])) { /* @var $b ActiveRecordInterface */ foreach ($this->_related[$name] as $a => $b) { - if ($model->getPrimaryKey() == $b->getPrimaryKey()) { + if ($model->getPrimaryKey() === $b->getPrimaryKey()) { unset($this->_related[$name][$a]); } } @@ -1408,7 +1479,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } else { /* @var $relatedModel ActiveRecordInterface */ $relatedModel = $relation->modelClass; - if (!$delete && count($relation->link) == 1 && is_array($this->{$b = reset($relation->link)})) { + if (!$delete && count($relation->link) === 1 && is_array($this->{$b = reset($relation->link)})) { // relation via array valued attribute $this->$b = []; $this->save(false); @@ -1482,13 +1553,14 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface { $labels = $this->attributeLabels(); if (isset($labels[$attribute])) { - return ($labels[$attribute]); + return $labels[$attribute]; } elseif (strpos($attribute, '.')) { $attributeParts = explode('.', $attribute); $neededAttribute = array_pop($attributeParts); $relatedModel = $this; foreach ($attributeParts as $relationName) { +<<<<<<< HEAD <<<<<<< HEAD if (isset($this->_related[$relationName]) && $this->_related[$relationName] instanceof self) { $relatedModel = $this->_related[$relationName]; @@ -1496,6 +1568,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) { $relatedModel = $relatedModel->$relationName; >>>>>>> yiichina/master +======= + if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) { + $relatedModel = $relatedModel->$relationName; +>>>>>>> master } else { try { $relation = $relatedModel->getRelation($relationName); @@ -1517,7 +1593,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Returns the text hint for the specified attribute. * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model. * @param string $attribute the attribute name @@ -1529,7 +1608,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface { $hints = $this->attributeHints(); if (isset($hints[$attribute])) { +<<<<<<< HEAD return ($hints[$attribute]); +======= + return $hints[$attribute]; +>>>>>>> master } elseif (strpos($attribute, '.')) { $attributeParts = explode('.', $attribute); $neededAttribute = array_pop($attributeParts); @@ -1557,7 +1640,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @inheritdoc * * The default implementation returns the names of the columns whose values have been populated into this record. @@ -1583,7 +1669,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Sets the element value at the specified offset to null. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `unset($model[$offset])`. * @param mixed $offset the offset to unset element */ diff --git a/framework/db/BatchQueryResult.php b/framework/db/BatchQueryResult.php index f1583839c7..e2f679f954 100644 --- a/framework/db/BatchQueryResult.php +++ b/framework/db/BatchQueryResult.php @@ -13,7 +13,7 @@ use yii\base\Object; * BatchQueryResult represents a batch query from which you can retrieve data in batches. * * You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by - * calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the `Iterator` interface, + * calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the [[\Iterator]] interface, * you can iterate it to obtain a batch of data in each iteration. For example, * * ```php @@ -94,7 +94,7 @@ class BatchQueryResult extends Object implements \Iterator /** * Resets the iterator to the initial state. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. */ public function rewind() { @@ -104,7 +104,7 @@ class BatchQueryResult extends Object implements \Iterator /** * Moves the internal pointer to the next dataset. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. */ public function next() { @@ -149,7 +149,7 @@ class BatchQueryResult extends Object implements \Iterator /** * Returns the index of the current dataset. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return integer the index of the current row. */ public function key() @@ -159,7 +159,7 @@ class BatchQueryResult extends Object implements \Iterator /** * Returns the current dataset. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return mixed the current dataset. */ public function current() @@ -169,7 +169,7 @@ class BatchQueryResult extends Object implements \Iterator /** * Returns whether there is a valid dataset at the current position. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return boolean whether there is a valid dataset at the current position. */ public function valid() diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php index 0927c312c1..cbbf8c1e81 100644 --- a/framework/db/ColumnSchema.php +++ b/framework/db/ColumnSchema.php @@ -27,7 +27,7 @@ class ColumnSchema extends Object public $allowNull; /** * @var string abstract type of this column. Possible abstract types include: - * string, text, boolean, smallint, integer, bigint, float, decimal, datetime, + * char, string, text, boolean, smallint, integer, bigint, float, decimal, datetime, * timestamp, time, date, binary, and money. */ public $type; @@ -113,7 +113,7 @@ class ColumnSchema extends Object */ protected function typecast($value) { - if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) { + if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY && $this->type !== Schema::TYPE_CHAR) { return null; } if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { @@ -122,22 +122,33 @@ class ColumnSchema extends Object switch ($this->phpType) { case 'resource': case 'string': +<<<<<<< HEAD <<<<<<< HEAD return is_resource($value) ? $value : (string) $value; ======= +======= +>>>>>>> master if (is_resource($value)) { return $value; } if (is_float($value)) { // ensure type cast always has . as decimal separator in all locales +<<<<<<< HEAD return str_replace(',', '.', (string)$value); } return (string)$value; >>>>>>> yiichina/master +======= + return str_replace(',', '.', (string) $value); + } + return (string) $value; +>>>>>>> master case 'integer': return (int) $value; case 'boolean': - return (bool) $value; + // treating a 0 bit value as false too + // https://github.com/yiisoft/yii2/issues/9006 + return (bool) $value && $value !== "\0"; case 'double': return (double) $value; } diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php new file mode 100644 index 0000000000..d425253dfe --- /dev/null +++ b/framework/db/ColumnSchemaBuilder.php @@ -0,0 +1,428 @@ + + * @since 2.0.6 + */ +class ColumnSchemaBuilder extends Object +{ + // Internally used constants representing categories that abstract column types fall under. + // See [[$categoryMap]] for mappings of abstract column types to category. + // @since 2.0.8 + const CATEGORY_PK = 'pk'; + const CATEGORY_STRING = 'string'; + const CATEGORY_NUMERIC = 'numeric'; + const CATEGORY_TIME = 'time'; + const CATEGORY_OTHER = 'other'; + + /** + * @var string the column type definition such as INTEGER, VARCHAR, DATETIME, etc. + */ + protected $type; + /** + * @var integer|string|array column size or precision definition. This is what goes into the parenthesis after + * the column type. This can be either a string, an integer or an array. If it is an array, the array values will + * be joined into a string separated by comma. + */ + protected $length; + /** + * @var boolean whether the column is not nullable. If this is `true`, a `NOT NULL` constraint will be added. + */ + protected $isNotNull = false; + /** + * @var boolean whether the column values should be unique. If this is `true`, a `UNIQUE` constraint will be added. + */ + protected $isUnique = false; + /** + * @var string the `CHECK` constraint for the column. + */ + protected $check; + /** + * @var mixed default value of the column. + */ + protected $default; + /** + * @var mixed SQL string to be appended to column schema definition. + * @since 2.0.9 + */ + protected $append; + /** + * @var boolean whether the column values should be unsigned. If this is `true`, an `UNSIGNED` keyword will be added. + * @since 2.0.7 + */ + protected $isUnsigned = false; + /** + * @var string the column after which this column will be added. + * @since 2.0.8 + */ + protected $after; + /** + * @var boolean whether this column is to be inserted at the beginning of the table. + * @since 2.0.8 + */ + protected $isFirst; + + + /** + * @var array mapping of abstract column types (keys) to type categories (values). + * @since 2.0.8 + */ + public $categoryMap = [ + Schema::TYPE_PK => self::CATEGORY_PK, + Schema::TYPE_UPK => self::CATEGORY_PK, + Schema::TYPE_BIGPK => self::CATEGORY_PK, + Schema::TYPE_UBIGPK => self::CATEGORY_PK, + Schema::TYPE_CHAR => self::CATEGORY_STRING, + Schema::TYPE_STRING => self::CATEGORY_STRING, + Schema::TYPE_TEXT => self::CATEGORY_STRING, + Schema::TYPE_SMALLINT => self::CATEGORY_NUMERIC, + Schema::TYPE_INTEGER => self::CATEGORY_NUMERIC, + Schema::TYPE_BIGINT => self::CATEGORY_NUMERIC, + Schema::TYPE_FLOAT => self::CATEGORY_NUMERIC, + Schema::TYPE_DOUBLE => self::CATEGORY_NUMERIC, + Schema::TYPE_DECIMAL => self::CATEGORY_NUMERIC, + Schema::TYPE_DATETIME => self::CATEGORY_TIME, + Schema::TYPE_TIMESTAMP => self::CATEGORY_TIME, + Schema::TYPE_TIME => self::CATEGORY_TIME, + Schema::TYPE_DATE => self::CATEGORY_TIME, + Schema::TYPE_BINARY => self::CATEGORY_OTHER, + Schema::TYPE_BOOLEAN => self::CATEGORY_NUMERIC, + Schema::TYPE_MONEY => self::CATEGORY_NUMERIC, + ]; + /** + * @var \yii\db\Connection the current database connection. It is used mainly to escape strings + * safely when building the final column schema string. + * @since 2.0.8 + */ + public $db; + /** + * @var string comment value of the column. + * @since 2.0.8 + */ + public $comment; + + /** + * Create a column schema builder instance giving the type and value precision. + * + * @param string $type type of the column. See [[$type]]. + * @param integer|string|array $length length or precision of the column. See [[$length]]. + * @param \yii\db\Connection $db the current database connection. See [[$db]]. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($type, $length = null, $db = null, $config = []) + { + $this->type = $type; + $this->length = $length; + $this->db = $db; + parent::__construct($config); + } + + /** + * Adds a `NOT NULL` constraint to the column. + * @return $this + */ + public function notNull() + { + $this->isNotNull = true; + return $this; + } + + /** + * Adds a `UNIQUE` constraint to the column. + * @return $this + */ + public function unique() + { + $this->isUnique = true; + return $this; + } + + /** + * Sets a `CHECK` constraint for the column. + * @param string $check the SQL of the `CHECK` constraint to be added. + * @return $this + */ + public function check($check) + { + $this->check = $check; + return $this; + } + + /** + * Specify the default value for the column. + * @param mixed $default the default value. + * @return $this + */ + public function defaultValue($default) + { + $this->default = $default; + return $this; + } + + /** + * Specifies the comment for column. + * @param string $comment the comment + * @return $this + * @since 2.0.8 + */ + public function comment($comment) + { + $this->comment = $comment; + return $this; + } + + /** + * Marks column as unsigned. + * @return $this + * @since 2.0.7 + */ + public function unsigned() + { + switch ($this->type) { + case Schema::TYPE_PK: + $this->type = Schema::TYPE_UPK; + break; + case Schema::TYPE_BIGPK: + $this->type = Schema::TYPE_UBIGPK; + break; + } + $this->isUnsigned = true; + return $this; + } + + /** + * Adds an `AFTER` constraint to the column. + * Note: MySQL, Oracle and Cubrid support only. + * @param string $after the column after which $this column will be added. + * @return $this + * @since 2.0.8 + */ + public function after($after) + { + $this->after = $after; + return $this; + } + + /** + * Adds an `FIRST` constraint to the column. + * Note: MySQL, Oracle and Cubrid support only. + * @return $this + * @since 2.0.8 + */ + public function first() + { + $this->isFirst = true; + return $this; + } + + /** + * Specify the default SQL expression for the column. + * @param string $default the default value expression. + * @return $this + * @since 2.0.7 + */ + public function defaultExpression($default) + { + $this->default = new Expression($default); + return $this; + } + + /** + * Specify additional SQL to be appended to schema string. + * @param string $sql the SQL string to be appended. + * @return $this + * @since 2.0.9 + */ + public function append($sql) + { + $this->append = $sql; + return $this; + } + + /** + * Builds the full string for the column's schema + * @return string + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_PK: + $format = '{type}{check}{comment}{append}'; + break; + default: + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}'; + } + return $this->buildCompleteString($format); + } + + /** + * Builds the length/precision part of the column. + * @return string + */ + protected function buildLengthString() + { + if ($this->length === null || $this->length === []) { + return ''; + } + if (is_array($this->length)) { + $this->length = implode(',', $this->length); + } + return "({$this->length})"; + } + + /** + * Builds the not null constraint for the column. + * @return string returns 'NOT NULL' if [[isNotNull]] is true, otherwise it returns an empty string. + */ + protected function buildNotNullString() + { + return $this->isNotNull ? ' NOT NULL' : ''; + } + + /** + * Builds the unique constraint for the column. + * @return string returns string 'UNIQUE' if [[isUnique]] is true, otherwise it returns an empty string. + */ + protected function buildUniqueString() + { + return $this->isUnique ? ' UNIQUE' : ''; + } + + /** + * Builds the default value specification for the column. + * @return string string with default value of column. + */ + protected function buildDefaultString() + { + if ($this->default === null) { + return ''; + } + + $string = ' DEFAULT '; + switch (gettype($this->default)) { + case 'integer': + $string .= (string) $this->default; + break; + case 'double': + // ensure type cast always has . as decimal separator in all locales + $string .= str_replace(',', '.', (string) $this->default); + break; + case 'boolean': + $string .= $this->default ? 'TRUE' : 'FALSE'; + break; + case 'object': + $string .= (string) $this->default; + break; + default: + $string .= "'{$this->default}'"; + } + + return $string; + } + + /** + * Builds the check constraint for the column. + * @return string a string containing the CHECK constraint. + */ + protected function buildCheckString() + { + return $this->check !== null ? " CHECK ({$this->check})" : ''; + } + + /** + * Builds the unsigned string for column. Defaults to unsupported. + * @return string a string containing UNSIGNED keyword. + * @since 2.0.7 + */ + protected function buildUnsignedString() + { + return ''; + } + + /** + * Builds the after constraint for the column. Defaults to unsupported. + * @return string a string containing the AFTER constraint. + * @since 2.0.8 + */ + protected function buildAfterString() + { + return ''; + } + + /** + * Builds the first constraint for the column. Defaults to unsupported. + * @return string a string containing the FIRST constraint. + * @since 2.0.8 + */ + protected function buildFirstString() + { + return ''; + } + + /** + * Builds the custom string that's appended to column definition. + * @return string custom string to append. + * @since 2.0.9 + */ + protected function buildAppendString() + { + return $this->append !== null ? ' ' . $this->append : ''; + } + + /** + * Returns the category of the column type. + * @return string a string containing the column type category name. + * @since 2.0.8 + */ + protected function getTypeCategory() + { + return isset($this->categoryMap[$this->type]) ? $this->categoryMap[$this->type] : null; + } + + /** + * Builds the comment specification for the column. + * @return string a string containing the COMMENT keyword and the comment itself + * @since 2.0.8 + */ + protected function buildCommentString() + { + return ''; + } + + /** + * Returns the complete column definition from input format + * @param string $format the format of the definition. + * @return string a string containing the complete column definition. + * @since 2.0.8 + */ + protected function buildCompleteString($format) + { + $placeholderValues = [ + '{type}' => $this->type, + '{length}' => $this->buildLengthString(), + '{unsigned}' => $this->buildUnsignedString(), + '{notnull}' => $this->buildNotNullString(), + '{unique}' => $this->buildUniqueString(), + '{default}' => $this->buildDefaultString(), + '{check}' => $this->buildCheckString(), + '{comment}' => $this->buildCommentString(), + '{pos}' => $this->isFirst ? $this->buildFirstString() : $this->buildAfterString(), + '{append}' => $this->buildAppendString(), + ]; + return strtr($format, $placeholderValues); + } +} diff --git a/framework/db/Command.php b/framework/db/Command.php index d234c97847..e581f9060a 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -18,13 +18,14 @@ use yii\base\NotSupportedException; * The SQL statement it represents can be set via the [[sql]] property. * * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call [[execute()]]. - * To execute a SQL statement that returns result data set (such as SELECT), + * To execute a SQL statement that returns a result data set (such as SELECT), * use [[queryAll()]], [[queryOne()]], [[queryColumn()]], [[queryScalar()]], or [[query()]]. + * * For example, * - * ~~~ + * ```php * $users = $connection->createCommand('SELECT * FROM user')->queryAll(); - * ~~~ + * ``` * * Command supports SQL statement preparation and parameter binding. * Call [[bindValue()]] to bind a value to a SQL parameter; @@ -33,16 +34,18 @@ use yii\base\NotSupportedException; * You may also call [[prepare()]] explicitly to prepare a SQL statement. * * Command also supports building SQL statements by providing methods such as [[insert()]], - * [[update()]], etc. For example, + * [[update()]], etc. For example, the following code will create and execute an INSERT SQL statement: * - * ~~~ + * ```php * $connection->createCommand()->insert('user', [ * 'name' => 'Sam', * 'age' => 30, * ])->execute(); - * ~~~ + * ``` * - * To build SELECT SQL statements, please use [[QueryBuilder]] instead. + * To build SELECT SQL statements, please use [[Query]] instead. + * + * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao). * * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in * [[sql]]. This property is read-only. @@ -93,6 +96,10 @@ class Command extends Component * @var string the SQL statement that this command represents */ private $_sql; + /** + * @var string name of the table, which schema, should be refreshed after command execution. + */ + private $_refreshTableName; /** @@ -101,7 +108,7 @@ class Command extends Component * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead. * Use 0 to indicate that the cached data will never expire. * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query result. - * @return static the command object itself + * @return $this the command object itself */ public function cache($duration = null, $dependency = null) { @@ -112,7 +119,7 @@ class Command extends Component /** * Disables query cache for this command. - * @return static the command object itself + * @return $this the command object itself */ public function noCache() { @@ -133,7 +140,7 @@ class Command extends Component * Specifies the SQL statement to be executed. * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. * @param string $sql the SQL statement to be set. - * @return static this command instance + * @return $this this command instance */ public function setSql($sql) { @@ -142,6 +149,7 @@ class Command extends Component $this->_sql = $this->db->quoteSql($sql); $this->_pendingParams = []; $this->params = []; + $this->_refreshTableName = null; } return $this; @@ -157,6 +165,7 @@ class Command extends Component { if (empty($this->params)) { return $this->_sql; +<<<<<<< HEAD <<<<<<< HEAD } else { $params = []; @@ -168,18 +177,25 @@ class Command extends Component } else { $params[$name] = $value; } +======= + } + $params = []; + foreach ($this->params as $name => $value) { + if (is_string($name) && strncmp(':', $name, 1)) { + $name = ':' . $name; +>>>>>>> master } - if (isset($params[1])) { - $sql = ''; - foreach (explode('?', $this->_sql) as $i => $part) { - $sql .= (isset($params[$i]) ? $params[$i] : '') . $part; - } - - return $sql; - } else { - return strtr($this->_sql, $params); + if (is_string($value)) { + $params[$name] = $this->db->quoteValue($value); + } elseif (is_bool($value)) { + $params[$name] = ($value ? 'TRUE' : 'FALSE'); + } elseif ($value === null) { + $params[$name] = 'NULL'; + } elseif (!is_object($value) && !is_resource($value)) { + $params[$name] = $value; } } +<<<<<<< HEAD ======= } $params = []; @@ -192,6 +208,8 @@ class Command extends Component $params[$name] = $value; } } +======= +>>>>>>> master if (!isset($params[1])) { return strtr($this->_sql, $params); } @@ -201,7 +219,10 @@ class Command extends Component } return $sql; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -258,11 +279,11 @@ class Command extends Component * using named placeholders, this will be a parameter name of * the form `:name`. For a prepared statement using question mark * placeholders, this will be the 1-indexed position of the parameter. - * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter + * @param mixed $value the PHP variable to bind to the SQL statement parameter (passed by reference) * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. * @param integer $length length of the data type * @param mixed $driverOptions the driver-specific options - * @return static the current command being executed + * @return $this the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php */ public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) @@ -304,7 +325,7 @@ class Command extends Component * placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @return static the current command being executed + * @return $this the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php */ public function bindValue($name, $value, $dataType = null) @@ -327,7 +348,7 @@ class Command extends Component * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`, * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`. - * @return static the current command being executed + * @return $this the current command being executed */ public function bindValues($values) { @@ -335,20 +356,28 @@ class Command extends Component return $this; } +<<<<<<< HEAD <<<<<<< HEAD ======= $schema = $this->db->getSchema(); >>>>>>> yiichina/master +======= + $schema = $this->db->getSchema(); +>>>>>>> master foreach ($values as $name => $value) { if (is_array($value)) { $this->_pendingParams[$name] = $value; $this->params[$name] = $value[0]; } else { +<<<<<<< HEAD <<<<<<< HEAD $type = $this->db->getSchema()->getPdoType($value); ======= $type = $schema->getPdoType($value); >>>>>>> yiichina/master +======= + $type = $schema->getPdoType($value); +>>>>>>> master $this->_pendingParams[$name] = [$value, $type]; $this->params[$name] = $value; } @@ -386,7 +415,7 @@ class Command extends Component * This method is best used when only the first row of result is needed for a query. * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * @return array|false the first row (in terms of an array) of the query result. False is returned if the query * results in nothing. * @throws Exception execution failed */ @@ -398,7 +427,7 @@ class Command extends Component /** * Executes the SQL statement and returns the value of the first column in the first row of data. * This method is best used when only a single value is needed for a query. - * @return string|null|boolean the value of the first column in the first row of the query result. + * @return string|null|false the value of the first column in the first row of the query result. * False is returned if there is no value. * @throws Exception execution failed */ @@ -428,12 +457,12 @@ class Command extends Component * Creates an INSERT command. * For example, * - * ~~~ + * ```php * $connection->createCommand()->insert('user', [ * 'name' => 'Sam', * 'age' => 30, * ])->execute(); - * ~~~ + * ``` * * The method will properly escape the column names, and bind the values to be inserted. * @@ -441,7 +470,7 @@ class Command extends Component * * @param string $table the table that new rows will be inserted into. * @param array $columns the column data (name => value) to be inserted into the table. - * @return Command the command object itself + * @return $this the command object itself */ public function insert($table, $columns) { @@ -455,17 +484,20 @@ class Command extends Component * Creates a batch INSERT command. * For example, * - * ~~~ + * ```php * $connection->createCommand()->batchInsert('user', ['name', 'age'], [ * ['Tom', 30], * ['Jane', 20], * ['Linda', 25], * ])->execute(); - * ~~~ + * ``` + * + * The method will properly escape the column names, and quote the values to be inserted. * <<<<<<< HEAD * Note that the values in each row must match the corresponding column names. * +<<<<<<< HEAD ======= * The method will properly escape the column names, and quote the values to be inserted. * @@ -474,10 +506,14 @@ class Command extends Component * Also note that the created command is not executed until [[execute()]] is called. * >>>>>>> yiichina/master +======= + * Also note that the created command is not executed until [[execute()]] is called. + * +>>>>>>> master * @param string $table the table that new rows will be inserted into. * @param array $columns the column names * @param array $rows the rows to be batch inserted into the table - * @return Command the command object itself + * @return $this the command object itself */ public function batchInsert($table, $columns, $rows) { @@ -490,9 +526,9 @@ class Command extends Component * Creates an UPDATE command. * For example, * - * ~~~ + * ```php * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); - * ~~~ + * ``` * * The method will properly escape the column names and bind the values to be updated. * @@ -503,7 +539,7 @@ class Command extends Component * @param string|array $condition the condition that will be put in the WHERE part. Please * refer to [[Query::where()]] on how to specify condition. * @param array $params the parameters to be bound to the command - * @return Command the command object itself + * @return $this the command object itself */ public function update($table, $columns, $condition = '', $params = []) { @@ -516,9 +552,9 @@ class Command extends Component * Creates a DELETE command. * For example, * - * ~~~ + * ```php * $connection->createCommand()->delete('user', 'status = 0')->execute(); - * ~~~ + * ``` * * The method will properly escape the table and column names. * @@ -528,7 +564,7 @@ class Command extends Component * @param string|array $condition the condition that will be put in the WHERE part. Please * refer to [[Query::where()]] on how to specify condition. * @param array $params the parameters to be bound to the command - * @return Command the command object itself + * @return $this the command object itself */ public function delete($table, $condition = '', $params = []) { @@ -553,7 +589,7 @@ class Command extends Component * @param string $table the name of the table to be created. The name will be properly quoted by the method. * @param array $columns the columns (name => definition) in the new table. * @param string $options additional SQL fragment that will be appended to the generated SQL. - * @return Command the command object itself + * @return $this the command object itself */ public function createTable($table, $columns, $options = null) { @@ -566,31 +602,31 @@ class Command extends Component * Creates a SQL command for renaming a DB table. * @param string $table the table to be renamed. The name will be properly quoted by the method. * @param string $newName the new table name. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function renameTable($table, $newName) { $sql = $this->db->getQueryBuilder()->renameTable($table, $newName); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** * Creates a SQL command for dropping a DB table. * @param string $table the table to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function dropTable($table) { $sql = $this->db->getQueryBuilder()->dropTable($table); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** * Creates a SQL command for truncating a DB table. * @param string $table the table to be truncated. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function truncateTable($table) { @@ -606,26 +642,26 @@ class Command extends Component * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called * to convert the give column type to the physical one. For example, `string` will be converted * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Command the command object itself + * @return $this the command object itself */ public function addColumn($table, $column, $type) { $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** * Creates a SQL command for dropping a DB column. * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function dropColumn($table, $column) { $sql = $this->db->getQueryBuilder()->dropColumn($table, $column); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** @@ -633,13 +669,13 @@ class Command extends Component * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. * @param string $oldName the old name of the column. The name will be properly quoted by the method. * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function renameColumn($table, $oldName, $newName) { $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** @@ -649,13 +685,13 @@ class Command extends Component * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called * to convert the give column type to the physical one. For example, `string` will be converted * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Command the command object itself + * @return $this the command object itself */ public function alterColumn($table, $column, $type) { $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** @@ -664,26 +700,26 @@ class Command extends Component * @param string $name the name of the primary key constraint. * @param string $table the table that the primary key constraint will be added to. * @param string|array $columns comma separated string or array of columns that the primary key will consist of. - * @return Command the command object itself. + * @return $this the command object itself. */ public function addPrimaryKey($name, $table, $columns) { $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** * Creates a SQL command for removing a primary key constraint to an existing table. * @param string $name the name of the primary key constraint to be removed. * @param string $table the table that the primary key constraint will be removed from. - * @return Command the command object itself + * @return $this the command object itself */ public function dropPrimaryKey($name, $table) { $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table); - return $this->setSql($sql); + return $this->setSql($sql)->requireTableSchemaRefresh($table); } /** @@ -696,7 +732,7 @@ class Command extends Component * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @return Command the command object itself + * @return $this the command object itself */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) { @@ -709,7 +745,7 @@ class Command extends Component * Creates a SQL command for dropping a foreign key constraint. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function dropForeignKey($name, $table) { @@ -725,7 +761,7 @@ class Command extends Component * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them * by commas. The column names will be properly quoted by the method. * @param boolean $unique whether to add UNIQUE constraint on the created index. - * @return Command the command object itself + * @return $this the command object itself */ public function createIndex($name, $table, $columns, $unique = false) { @@ -738,7 +774,7 @@ class Command extends Component * Creates a SQL command for dropping an index. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself + * @return $this the command object itself */ public function dropIndex($name, $table) { @@ -754,7 +790,7 @@ class Command extends Component * @param string $table the name of the table whose primary key sequence will be reset * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, * the next new row's primary key will have a value 1. - * @return Command the command object itself + * @return $this the command object itself * @throws NotSupportedException if this is not supported by the underlying DBMS */ public function resetSequence($table, $value = null) @@ -770,7 +806,7 @@ class Command extends Component * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current * or default schema. * @param string $table the table name. - * @return Command the command object itself + * @return $this the command object itself * @throws NotSupportedException if this is not supported by the underlying DBMS */ public function checkIntegrity($check = true, $schema = '', $table = '') @@ -780,6 +816,66 @@ class Command extends Component return $this->setSql($sql); } + /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for dropping comment from column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for dropping comment from table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table); + + return $this->setSql($sql); + } + /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. @@ -810,6 +906,8 @@ class Command extends Component Yii::endProfile($token, __METHOD__); + $this->refreshTableSchema(); + return $n; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); @@ -884,4 +982,27 @@ class Command extends Component return $result; } + + /** + * Marks a specified table schema to be refreshed after command execution. + * @param string $name name of the table, which schema should be refreshed. + * @return $this this command instance + * @since 2.0.6 + */ + protected function requireTableSchemaRefresh($name) + { + $this->_refreshTableName = $name; + return $this; + } + + /** + * Refreshes table schema, which was marked by [[requireTableSchemaRefresh()]] + * @since 2.0.6 + */ + protected function refreshTableSchema() + { + if ($this->_refreshTableName !== null) { + $this->db->getSchema()->refreshTableSchema($this->_refreshTableName); + } + } } diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 5cf61a9309..efa1e6384f 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -32,40 +32,40 @@ use yii\caching\Cache; * The following example shows how to create a Connection instance and establish * the DB connection: * - * ~~~ + * ```php * $connection = new \yii\db\Connection([ * 'dsn' => $dsn, * 'username' => $username, * 'password' => $password, * ]); * $connection->open(); - * ~~~ + * ``` * * After the DB connection is established, one can execute SQL statements like the following: * - * ~~~ + * ```php * $command = $connection->createCommand('SELECT * FROM post'); * $posts = $command->queryAll(); * $command = $connection->createCommand('UPDATE post SET status=1'); * $command->execute(); - * ~~~ + * ``` * * One can also do prepared SQL execution and bind parameters to the prepared SQL. * When the parameters are coming from user input, you should use this approach * to prevent SQL injection attacks. The following is an example: * - * ~~~ + * ```php * $command = $connection->createCommand('SELECT * FROM post WHERE id=:id'); * $command->bindValue(':id', $_GET['id']); * $post = $command->query(); - * ~~~ + * ``` * * For more information about how to perform various DB queries, please refer to [[Command]]. * * If the underlying DBMS supports transactions, you can perform transactional SQL queries * like the following: * - * ~~~ + * ```php * $transaction = $connection->beginTransaction(); * try { * $connection->createCommand($sql1)->execute(); @@ -75,30 +75,30 @@ use yii\caching\Cache; * } catch (Exception $e) { * $transaction->rollBack(); * } - * ~~~ + * ``` * * You also can use shortcut for the above like the following: * - * ~~~ - * $connection->transaction(function() { + * ```php + * $connection->transaction(function () { * $order = new Order($customer); * $order->save(); * $order->addItems($items); * }); - * ~~~ + * ``` * * If needed you can pass transaction isolation level as a second parameter: * - * ~~~ - * $connection->transaction(function(Connection $db) { + * ```php + * $connection->transaction(function (Connection $db) { * //return $db->... * }, Transaction::READ_UNCOMMITTED); - * ~~~ + * ``` * * Connection is often used as an application component and configured in the application * configuration like the following: * - * ~~~ + * ```php * 'components' => [ * 'db' => [ * 'class' => '\yii\db\Connection', @@ -108,7 +108,7 @@ use yii\caching\Cache; * 'charset' => 'utf8', * ], * ], - * ~~~ + * ``` * * @property string $driverName Name of the DB driver. * @property boolean $isActive Whether the DB connection is established. This property is read-only. @@ -153,6 +153,10 @@ class Connection extends Component * @var string the Data Source Name, or DSN, contains the information required to connect to the database. * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on * the format of the DSN string. + * + * For [SQLite](http://php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a path alias + * for specifying the database path, e.g. `sqlite:@app/data/db.sql`. + * * @see charset */ public $dsn; @@ -176,6 +180,7 @@ class Connection extends Component * This property is mainly managed by [[open()]] and [[close()]] methods. * When a DB connection is active, this property will represent a PDO instance; * otherwise, it will be null. + * @see pdoClass */ public $pdo; /** @@ -276,9 +281,17 @@ class Connection extends Component 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID ]; /** - * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used. + * @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[yii\db\mssql\PDO]] when MSSQL is used. + * @see pdo */ public $pdoClass; + /** + * @var string the class used to create new database [[Command]] objects. If you want to extend the [[Command]] class, + * you may configure this property to use your extended version of the class. + * @see createCommand + * @since 2.0.7 + */ + public $commandClass = 'yii\db\Command'; /** * @var boolean whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint). * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect. @@ -571,12 +584,20 @@ class Connection extends Component } elseif (($pos = strpos($this->dsn, ':')) !== false) { $driver = strtolower(substr($this->dsn, 0, $pos)); } - if (isset($driver) && ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv')) { - $pdoClass = 'yii\db\mssql\PDO'; + if (isset($driver)) { + if ($driver === 'mssql' || $driver === 'dblib') { + $pdoClass = 'yii\db\mssql\PDO'; + } elseif ($driver === 'sqlsrv') { + $pdoClass = 'yii\db\mssql\SqlsrvPDO'; + } } } - return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); + $dsn = $this->dsn; + if (strncmp('sqlite:@', $dsn, 8) === 0) { + $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7)); + } + return new $pdoClass($dsn, $this->username, $this->password, $this->attributes); } /** @@ -606,7 +627,8 @@ class Connection extends Component */ public function createCommand($sql = null, $params = []) { - $command = new Command([ + /** @var Command $command */ + $command = new $this->commandClass([ 'db' => $this, 'sql' => $sql, ]); @@ -653,14 +675,17 @@ class Connection extends Component public function transaction(callable $callback, $isolationLevel = null) { $transaction = $this->beginTransaction($isolationLevel); + $level = $transaction->level; try { $result = call_user_func($callback, $this); - if ($transaction->isActive) { + if ($transaction->isActive && $transaction->level === $level) { $transaction->commit(); } } catch (\Exception $e) { - $transaction->rollBack(); + if ($transaction->isActive && $transaction->level === $level) { + $transaction->rollBack(); + } throw $e; } @@ -933,4 +958,14 @@ class Connection extends Component return null; } + + /** + * Close the connection before serializing. + * @return array + */ + public function __sleep() + { + $this->close(); + return array_keys((array) $this); + } } diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php index ebe835183e..35d57a9e9e 100644 --- a/framework/db/DataReader.php +++ b/framework/db/DataReader.php @@ -16,7 +16,7 @@ use yii\base\InvalidCallException; * returns all the rows in a single array. Rows of data can also be read by * iterating through the reader. For example, * - * ~~~ + * ```php * $command = $connection->createCommand('SELECT * FROM post'); * $reader = $command->query(); * @@ -31,7 +31,7 @@ use yii\base\InvalidCallException; * * // equivalent to: * $rows = $reader->readAll(); - * ~~~ + * ``` * * Note that since DataReader is a forward-only stream, you can only traverse it once. * Doing it the second time will throw an exception. @@ -212,7 +212,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Resets the iterator to the initial state. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @throws InvalidCallException if this method is invoked twice */ public function rewind() @@ -227,7 +227,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Returns the index of the current row. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return integer the index of the current row. */ public function key() @@ -237,7 +237,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Returns the current row. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return mixed the current row. */ public function current() @@ -247,7 +247,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Moves the internal pointer to the next row. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. */ public function next() { @@ -257,7 +257,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Returns whether there is a row of data at current position. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return boolean whether there is a row of data at current position. */ public function valid() diff --git a/framework/db/Expression.php b/framework/db/Expression.php index 8e725ecda0..c40c4f97b0 100644 --- a/framework/db/Expression.php +++ b/framework/db/Expression.php @@ -9,14 +9,19 @@ namespace yii\db; /** * Expression represents a DB expression that does not need escaping or quoting. + * * When an Expression object is embedded within a SQL statement or fragment, * it will be replaced with the [[expression]] property value without any * DB escaping or quoting. For example, * - * ~~~ + * ```php * $expression = new Expression('NOW()'); - * $sql = 'SELECT ' . $expression; // SELECT NOW() - * ~~~ + * $now = (new \yii\db\Query)->select($expression)->scalar(); // SELECT NOW(); + * echo $now; // prints the current date + * ``` + * + * Expression objects are mainly created for passing raw SQL expressions to methods of + * [[Query]], [[ActiveQuery]], and related classes. * * An expression can also be bound with parameters specified via [[params]]. * diff --git a/framework/db/Migration.php b/framework/db/Migration.php index 7ec8abc5bb..5d17489f94 100644 --- a/framework/db/Migration.php +++ b/framework/db/Migration.php @@ -38,6 +38,8 @@ use yii\di\Instance; */ class Migration extends Component implements MigrationInterface { + use SchemaBuilderTrait; + /** * @var Connection|array|string the DB connection object or the application component ID of the DB connection * that this migration should work with. Starting from version 2.0.2, this can also be a configuration array @@ -60,12 +62,22 @@ class Migration extends Component implements MigrationInterface /** * Initializes the migration. - * This method will set [[db]] to be the 'db' application component, if it is null. + * This method will set [[db]] to be the 'db' application component, if it is `null`. */ public function init() { parent::init(); $this->db = Instance::ensure($this->db, Connection::className()); + $this->db->getSchema()->refresh(); + } + + /** + * @inheritdoc + * @since 2.0.6 + */ + protected function getDb() + { + return $this->db; } /** @@ -85,7 +97,7 @@ class Migration extends Component implements MigrationInterface } $transaction->commit(); } catch (\Exception $e) { - echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + echo 'Exception: ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; echo $e->getTraceAsString() . "\n"; $transaction->rollBack(); @@ -113,7 +125,7 @@ class Migration extends Component implements MigrationInterface } $transaction->commit(); } catch (\Exception $e) { - echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + echo 'Exception: ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; echo $e->getTraceAsString() . "\n"; $transaction->rollBack(); @@ -140,7 +152,7 @@ class Migration extends Component implements MigrationInterface * This method contains the logic to be executed when removing this migration. * This method differs from [[down()]] in that the DB logic implemented here will * be enclosed within a DB transaction. - * Child classes may implement this method instead of [[up()]] if the DB logic + * Child classes may implement this method instead of [[down()]] if the DB logic * needs to be within a transaction. * @return boolean return a false value to indicate the migration fails * and should not proceed further. All other return values mean the migration succeeds. @@ -161,7 +173,7 @@ class Migration extends Component implements MigrationInterface echo " > execute SQL: $sql ..."; $time = microtime(true); $this->db->createCommand($sql)->bindValues($params)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -175,7 +187,7 @@ class Migration extends Component implements MigrationInterface echo " > insert into $table ..."; $time = microtime(true); $this->db->createCommand()->insert($table, $columns)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -190,7 +202,7 @@ class Migration extends Component implements MigrationInterface echo " > insert into $table ..."; $time = microtime(true); $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -207,7 +219,7 @@ class Migration extends Component implements MigrationInterface echo " > update $table ..."; $time = microtime(true); $this->db->createCommand()->update($table, $columns, $condition, $params)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -222,7 +234,7 @@ class Migration extends Component implements MigrationInterface echo " > delete from $table ..."; $time = microtime(true); $this->db->createCommand()->delete($table, $condition, $params)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -246,6 +258,11 @@ class Migration extends Component implements MigrationInterface echo " > create table $table ..."; $time = microtime(true); $this->db->createCommand()->createTable($table, $columns, $options)->execute(); + foreach ($columns as $column => $type) { + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } + } echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } @@ -259,7 +276,7 @@ class Migration extends Component implements MigrationInterface echo " > rename table $table to $newName ..."; $time = microtime(true); $this->db->createCommand()->renameTable($table, $newName)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -271,7 +288,7 @@ class Migration extends Component implements MigrationInterface echo " > drop table $table ..."; $time = microtime(true); $this->db->createCommand()->dropTable($table)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -283,7 +300,7 @@ class Migration extends Component implements MigrationInterface echo " > truncate table $table ..."; $time = microtime(true); $this->db->createCommand()->truncateTable($table)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -299,7 +316,10 @@ class Migration extends Component implements MigrationInterface echo " > add column $column $type to table $table ..."; $time = microtime(true); $this->db->createCommand()->addColumn($table, $column, $type)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -312,7 +332,7 @@ class Migration extends Component implements MigrationInterface echo " > drop column $column from table $table ..."; $time = microtime(true); $this->db->createCommand()->dropColumn($table, $column)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -326,7 +346,7 @@ class Migration extends Component implements MigrationInterface echo " > rename column $name in table $table to $newName ..."; $time = microtime(true); $this->db->createCommand()->renameColumn($table, $name, $newName)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -342,7 +362,10 @@ class Migration extends Component implements MigrationInterface echo " > alter column $column in table $table to $type ..."; $time = microtime(true); $this->db->createCommand()->alterColumn($table, $column, $type)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -354,10 +377,10 @@ class Migration extends Component implements MigrationInterface */ public function addPrimaryKey($name, $table, $columns) { - echo " > add primary key $name on $table (" . (is_array($columns) ? implode(',', $columns) : $columns).") ..."; + echo " > add primary key $name on $table (" . (is_array($columns) ? implode(',', $columns) : $columns) . ') ...'; $time = microtime(true); $this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -370,7 +393,7 @@ class Migration extends Component implements MigrationInterface echo " > drop primary key $name ..."; $time = microtime(true); $this->db->createCommand()->dropPrimaryKey($name, $table)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -386,10 +409,10 @@ class Migration extends Component implements MigrationInterface */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) { - echo " > add foreign key $name: $table (" . implode(',', (array) $columns) . ") references $refTable (" . implode(',', (array) $refColumns) . ") ..."; + echo " > add foreign key $name: $table (" . implode(',', (array) $columns) . ") references $refTable (" . implode(',', (array) $refColumns) . ') ...'; $time = microtime(true); $this->db->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -402,7 +425,7 @@ class Migration extends Component implements MigrationInterface echo " > drop foreign key $name from table $table ..."; $time = microtime(true); $this->db->createCommand()->dropForeignKey($name, $table)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -410,15 +433,16 @@ class Migration extends Component implements MigrationInterface * @param string $name the name of the index. The name will be properly quoted by the method. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas or use an array. The column names will be properly quoted by the method. + * by commas or use an array. Each column name will be properly quoted by the method. Quoting will be skipped for column names that + * include a left parenthesis "(". * @param boolean $unique whether to add UNIQUE constraint on the created index. */ public function createIndex($name, $table, $columns, $unique = false) { - echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array) $columns) . ") ..."; + echo ' > create' . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array) $columns) . ') ...'; $time = microtime(true); $this->db->createCommand()->createIndex($name, $table, $columns, $unique)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -431,6 +455,70 @@ class Migration extends Component implements MigrationInterface echo " > drop index $name ..."; $time = microtime(true); $this->db->createCommand()->dropIndex($name, $table)->execute(); - echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and execute a SQL statement for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + echo " > add comment on column $column ..."; + $time = microtime(true); + $this->db->createCommand()->addCommentOnColumn($table, $column, $comment)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + echo " > add comment on table $table ..."; + $time = microtime(true); + $this->db->createCommand()->addCommentOnTable($table, $comment)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and execute a SQL statement for dropping comment from column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + echo " > drop comment from column $column ..."; + $time = microtime(true); + $this->db->createCommand()->dropCommentFromColumn($table, $column)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for dropping comment from table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + echo " > drop comment from table $table ..."; + $time = microtime(true); + $this->db->createCommand()->dropCommentFromTable($table)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } } diff --git a/framework/db/Query.php b/framework/db/Query.php index b371b72ccb..c33afc672c 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -35,6 +35,10 @@ use yii\base\Component; * $rows = $command->queryAll(); * ``` * + * Query internally uses the [[QueryBuilder]] class to generate the SQL statement. + * + * A more detailed usage guide on how to work with Query can be found in the [guide article on Query Builder](guide:db-query-builder). + * * @author Qiang Xue * @author Carsten Brandt * @since 2.0 @@ -74,18 +78,18 @@ class Query extends Component implements QueryInterface * @var array how to join with other tables. Each array element represents the specification * of one join which has the following structure: * - * ~~~ + * ```php * [$joinType, $tableName, $joinCondition] - * ~~~ + * ``` * * For example, * - * ~~~ + * ```php * [ * ['INNER JOIN', 'user', 'user.id = author_id'], * ['LEFT JOIN', 'team', 'team.id = team_id'], * ] - * ~~~ + * ``` */ public $join; /** @@ -129,7 +133,7 @@ class Query extends Component implements QueryInterface * This method is called by [[QueryBuilder]] when it starts to build SQL from a query object. * You may override this method to do some final preparation work when converting a query into a SQL statement. * @param QueryBuilder $builder - * @return Query a prepared query instance which will be used by [[QueryBuilder]] to build the SQL + * @return $this a prepared query instance which will be used by [[QueryBuilder]] to build the SQL */ public function prepare($builder) { @@ -140,7 +144,7 @@ class Query extends Component implements QueryInterface * Starts a batch query. * * A batch query supports fetching data in batches, which can keep the memory usage under a limit. - * This method will return a [[BatchQueryResult]] object which implements the `Iterator` interface + * This method will return a [[BatchQueryResult]] object which implements the [[\Iterator]] interface * and can be traversed to retrieve the data in batches. * * For example, @@ -154,7 +158,7 @@ class Query extends Component implements QueryInterface * * @param integer $batchSize the number of records to be fetched in each batch. * @param Connection $db the database connection. If not set, the "db" application component will be used. - * @return BatchQueryResult the batch query result. It implements the `Iterator` interface + * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface * and can be traversed to retrieve the data in batches. */ public function batch($batchSize = 100, $db = null) @@ -181,7 +185,7 @@ class Query extends Component implements QueryInterface * * @param integer $batchSize the number of records to be fetched in each batch. * @param Connection $db the database connection. If not set, the "db" application component will be used. - * @return BatchQueryResult the batch query result. It implements the `Iterator` interface + * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface * and can be traversed to retrieve the data in batches. */ public function each($batchSize = 100, $db = null) @@ -248,7 +252,7 @@ class Query extends Component implements QueryInterface * The value returned will be the first column in the first row of the query results. * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. - * @return string|boolean the value of the first column in the first row of the query result. + * @return string|null|false the value of the first column in the first row of the query result. * False is returned if the query result is empty. */ public function scalar($db = null) @@ -264,9 +268,12 @@ class Query extends Component implements QueryInterface */ public function column($db = null) { +<<<<<<< HEAD <<<<<<< HEAD return $this->createCommand($db)->queryColumn(); ======= +======= +>>>>>>> master if (!is_string($this->indexBy)) { return $this->createCommand($db)->queryColumn(); } @@ -283,17 +290,24 @@ class Query extends Component implements QueryInterface } } return $results; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** * Returns the number of records. * @param string $q the COUNT expression. Defaults to '*'. +<<<<<<< HEAD <<<<<<< HEAD * Make sure you properly quote column names in the expression. ======= * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. >>>>>>> yiichina/master +======= + * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. +>>>>>>> master * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given (or null), the `db` application component will be used. * @return integer|string number of records. The result may be a string depending on the @@ -307,11 +321,15 @@ class Query extends Component implements QueryInterface /** * Returns the sum of the specified column values. * @param string $q the column name or expression. +<<<<<<< HEAD <<<<<<< HEAD * Make sure you properly quote column names in the expression. ======= * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. >>>>>>> yiichina/master +======= + * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. +>>>>>>> master * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. * @return mixed the sum of the specified column values. @@ -324,11 +342,15 @@ class Query extends Component implements QueryInterface /** * Returns the average of the specified column values. * @param string $q the column name or expression. +<<<<<<< HEAD <<<<<<< HEAD * Make sure you properly quote column names in the expression. ======= * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. >>>>>>> yiichina/master +======= + * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. +>>>>>>> master * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. * @return mixed the average of the specified column values. @@ -341,11 +363,15 @@ class Query extends Component implements QueryInterface /** * Returns the minimum of the specified column values. * @param string $q the column name or expression. +<<<<<<< HEAD <<<<<<< HEAD * Make sure you properly quote column names in the expression. ======= * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. >>>>>>> yiichina/master +======= + * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. +>>>>>>> master * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. * @return mixed the minimum of the specified column values. @@ -358,11 +384,15 @@ class Query extends Component implements QueryInterface /** * Returns the maximum of the specified column values. * @param string $q the column name or expression. +<<<<<<< HEAD <<<<<<< HEAD * Make sure you properly quote column names in the expression. ======= * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. >>>>>>> yiichina/master +======= + * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression. +>>>>>>> master * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. * @return mixed the maximum of the specified column values. @@ -380,11 +410,11 @@ class Query extends Component implements QueryInterface */ public function exists($db = null) { - $select = $this->select; - $this->select = [new Expression('1')]; $command = $this->createCommand($db); - $this->select = $select; - return $command->queryScalar() !== false; + $params = $command->params; + $command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql())); + $command->bindValues($params); + return (boolean)$command->queryScalar(); } /** @@ -392,7 +422,7 @@ class Query extends Component implements QueryInterface * Restores the value of select to make this query reusable. * @param string|Expression $selectExpression * @param Connection|null $db - * @return bool|string + * @return boolean|string */ protected function queryScalar($selectExpression, $db) { @@ -409,11 +439,15 @@ class Query extends Component implements QueryInterface $this->limit = $limit; $this->offset = $offset; +<<<<<<< HEAD <<<<<<< HEAD if (empty($this->groupBy) && empty($this->union) && !$this->distinct) { ======= if (empty($this->groupBy) && empty($this->having) && empty($this->union) && !$this->distinct) { >>>>>>> yiichina/master +======= + if (empty($this->groupBy) && empty($this->having) && empty($this->union) && !$this->distinct) { +>>>>>>> master return $command->queryScalar(); } else { return (new Query)->select([$selectExpression]) @@ -425,11 +459,12 @@ class Query extends Component implements QueryInterface /** * Sets the SELECT part of the query. - * @param string|array $columns the columns to be selected. + * @param string|array|Expression $columns the columns to be selected. * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases (e.g. "user.id AS user_id"). * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). + * (which means the column contains a DB expression). A DB expression may also be passed in form of + * an [[Expression]] object. * * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts. @@ -442,11 +477,13 @@ class Query extends Component implements QueryInterface * * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. - * @return static the query object itself + * @return $this the query object itself */ public function select($columns, $option = null) { - if (!is_array($columns)) { + if ($columns instanceof Expression) { + $columns = [$columns]; + } elseif (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } $this->select = $columns; @@ -457,7 +494,10 @@ class Query extends Component implements QueryInterface /** * Add more columns to the SELECT part of the query. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * * Note, that if [[select]] has not been specified before, you should include `*` explicitly * if you want to select all remaining columns too: @@ -466,14 +506,22 @@ class Query extends Component implements QueryInterface * $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one(); * ``` * +<<<<<<< HEAD >>>>>>> yiichina/master * @param string|array $columns the columns to add to the select. * @return static the query object itself +======= + * @param string|array|Expression $columns the columns to add to the select. See [[select()]] for more + * details about the format of this parameter. + * @return $this the query object itself +>>>>>>> master * @see select() */ public function addSelect($columns) { - if (!is_array($columns)) { + if ($columns instanceof Expression) { + $columns = [$columns]; + } elseif (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } if ($this->select === null) { @@ -487,7 +535,7 @@ class Query extends Component implements QueryInterface /** * Sets the value indicating whether to SELECT DISTINCT or not. * @param boolean $value whether to SELECT DISTINCT or not. - * @return static the query object itself + * @return $this the query object itself */ public function distinct($value = true) { @@ -509,7 +557,23 @@ class Query extends Component implements QueryInterface * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used * as the alias for the sub-query. * - * @return static the query object itself + * Here are some examples: + * + * ```php + * // SELECT * FROM `user` `u`, `profile`; + * $query = (new \yii\db\Query)->from(['u' => 'user', 'profile']); + * + * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`; + * $subquery = (new \yii\db\Query)->from('user')->where(['active' => true]) + * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]); + * + * // subquery can also be a string with plain SQL wrapped in parenthesis + * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`; + * $subquery = "(SELECT * FROM `user` WHERE `active` = 1)"; + * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]); + * ``` + * + * @return $this the query object itself */ public function from($tables) { @@ -530,9 +594,9 @@ class Query extends Component implements QueryInterface * * @inheritdoc * - * @param string|array $condition the conditions that should be put in the WHERE part. + * @param string|array|Expression $condition the conditions that should be put in the WHERE part. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see andWhere() * @see orWhere() * @see QueryInterface::where() @@ -547,10 +611,10 @@ class Query extends Component implements QueryInterface /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see where() * @see orWhere() */ @@ -568,10 +632,10 @@ class Query extends Component implements QueryInterface /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see where() * @see andWhere() */ @@ -586,25 +650,62 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Adds a filtering condition for a specific column and allow the user to choose a filter operator. + * + * It adds an additional WHERE condition for the given field and determines the comparison operator + * based on the first few characters of the given value. + * The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * The comparison operator is intelligently determined based on the first few characters in the given value. + * In particular, it recognizes the following operators if they appear as the leading characters in the given value: + * + * - `<`: the column must be less than the given value. + * - `>`: the column must be greater than the given value. + * - `<=`: the column must be less than or equal to the given value. + * - `>=`: the column must be greater than or equal to the given value. + * - `<>`: the column must not be the same as the given value. + * - `=`: the column must be equal to the given value. + * - If none of the above operators is detected, the `$defaultOperator` will be used. + * + * @param string $name the column name. + * @param string $value the column value optionally prepended with the comparison operator. + * @param string $defaultOperator The operator to use, when no operator is given in `$value`. + * Defaults to `=`, performing an exact match. + * @return $this The query object itself + * @since 2.0.8 + */ + public function andFilterCompare($name, $value, $defaultOperator = '=') + { + if (preg_match("/^(<>|>=|>|<=|<|=)/", $value, $matches)) { + $operator = $matches[1]; + $value = substr($value, strlen($operator)); + } else { + $operator = $defaultOperator; + } + return $this->andFilterWhere([$operator, $name, $value]); + } + /** * Appends a JOIN part to the query. * The first parameter specifies what type of join it is. * @param string $type the type of join, such as INNER JOIN, LEFT JOIN. * @param string|array $table the table to be joined. * - * Use string to represent the name of the table to be joined. - * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * Use a string to represent the name of the table to be joined. + * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). * The method will automatically quote the table name unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). * - * Use array to represent joining with a sub-query. The array must contain only one element. - * The value must be a Query object representing the sub-query while the corresponding key + * Use an array to represent joining with a sub-query. The array must contain only one element. + * The value must be a [[Query]] object representing the sub-query while the corresponding key * represents the alias for the sub-query. * * @param string|array $on the join condition that should appear in the ON part. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return $this the query object itself */ public function join($type, $table, $on = '', $params = []) { @@ -616,19 +717,19 @@ class Query extends Component implements QueryInterface * Appends an INNER JOIN part to the query. * @param string|array $table the table to be joined. * - * Use string to represent the name of the table to be joined. - * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * Use a string to represent the name of the table to be joined. + * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). * The method will automatically quote the table name unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). * - * Use array to represent joining with a sub-query. The array must contain only one element. - * The value must be a Query object representing the sub-query while the corresponding key + * Use an array to represent joining with a sub-query. The array must contain only one element. + * The value must be a [[Query]] object representing the sub-query while the corresponding key * represents the alias for the sub-query. * * @param string|array $on the join condition that should appear in the ON part. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return $this the query object itself */ public function innerJoin($table, $on = '', $params = []) { @@ -640,19 +741,19 @@ class Query extends Component implements QueryInterface * Appends a LEFT OUTER JOIN part to the query. * @param string|array $table the table to be joined. * - * Use string to represent the name of the table to be joined. - * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * Use a string to represent the name of the table to be joined. + * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). * The method will automatically quote the table name unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). * - * Use array to represent joining with a sub-query. The array must contain only one element. - * The value must be a Query object representing the sub-query while the corresponding key + * Use an array to represent joining with a sub-query. The array must contain only one element. + * The value must be a [[Query]] object representing the sub-query while the corresponding key * represents the alias for the sub-query. * * @param string|array $on the join condition that should appear in the ON part. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query - * @return Query the query object itself + * @return $this the query object itself */ public function leftJoin($table, $on = '', $params = []) { @@ -664,19 +765,19 @@ class Query extends Component implements QueryInterface * Appends a RIGHT OUTER JOIN part to the query. * @param string|array $table the table to be joined. * - * Use string to represent the name of the table to be joined. - * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * Use a string to represent the name of the table to be joined. + * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). * The method will automatically quote the table name unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). * - * Use array to represent joining with a sub-query. The array must contain only one element. - * The value must be a Query object representing the sub-query while the corresponding key + * Use an array to represent joining with a sub-query. The array must contain only one element. + * The value must be a [[Query]] object representing the sub-query while the corresponding key * represents the alias for the sub-query. * * @param string|array $on the join condition that should appear in the ON part. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query - * @return Query the query object itself + * @return $this the query object itself */ public function rightJoin($table, $on = '', $params = []) { @@ -686,16 +787,24 @@ class Query extends Component implements QueryInterface /** * Sets the GROUP BY part of the query. - * @param string|array $columns the columns to be grouped by. + * @param string|array|Expression $columns the columns to be grouped by. * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return static the query object itself + * + * Note that if your group-by is an expression containing commas, you should always use an array + * to represent the group-by information. Otherwise, the method will not be able to correctly determine + * the group-by columns. + * + * Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL. + * @return $this the query object itself * @see addGroupBy() */ public function groupBy($columns) { - if (!is_array($columns)) { + if ($columns instanceof Expression) { + $columns = [$columns]; + } elseif (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } $this->groupBy = $columns; @@ -708,12 +817,20 @@ class Query extends Component implements QueryInterface * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return static the query object itself + * + * Note that if your group-by is an expression containing commas, you should always use an array + * to represent the group-by information. Otherwise, the method will not be able to correctly determine + * the group-by columns. + * + * Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL. + * @return $this the query object itself * @see groupBy() */ public function addGroupBy($columns) { - if (!is_array($columns)) { + if ($columns instanceof Expression) { + $columns = [$columns]; + } elseif (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } if ($this->groupBy === null) { @@ -726,10 +843,10 @@ class Query extends Component implements QueryInterface /** * Sets the HAVING part of the query. - * @param string|array $condition the conditions to be put after HAVING. + * @param string|array|Expression $condition the conditions to be put after HAVING. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see andHaving() * @see orHaving() */ @@ -743,10 +860,10 @@ class Query extends Component implements QueryInterface /** * Adds an additional HAVING condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. - * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see having() * @see orHaving() */ @@ -764,10 +881,10 @@ class Query extends Component implements QueryInterface /** * Adds an additional HAVING condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. - * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself + * @return $this the query object itself * @see having() * @see andHaving() */ @@ -786,11 +903,11 @@ class Query extends Component implements QueryInterface * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION * @param boolean $all TRUE if using UNION ALL and FALSE if using UNION - * @return static the query object itself + * @return $this the query object itself */ public function union($sql, $all = false) { - $this->union[] = [ 'query' => $sql, 'all' => $all ]; + $this->union[] = ['query' => $sql, 'all' => $all]; return $this; } @@ -798,7 +915,7 @@ class Query extends Component implements QueryInterface * Sets the parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. * For example, `[':name' => 'Dan', ':age' => 31]`. - * @return static the query object itself + * @return $this the query object itself * @see addParams() */ public function params($params) @@ -811,7 +928,7 @@ class Query extends Component implements QueryInterface * Adds additional parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. * For example, `[':name' => 'Dan', ':age' => 31]`. - * @return static the query object itself + * @return $this the query object itself * @see params() */ public function addParams($params) @@ -821,7 +938,7 @@ class Query extends Component implements QueryInterface $this->params = $params; } else { foreach ($params as $name => $value) { - if (is_integer($name)) { + if (is_int($name)) { $this->params[] = $value; } else { $this->params[$name] = $value; diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 9f9a998dae..af024c1581 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -9,12 +9,14 @@ namespace yii\db; use yii\base\InvalidParamException; use yii\base\NotSupportedException; +use yii\helpers\ArrayHelper; /** * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object. * - * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE, - * from a [[Query]] object. + * SQL statements are created from [[Query]] objects using the [[build()]]-method. + * + * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE. * * @author Qiang Xue * @since 2.0 @@ -34,7 +36,7 @@ class QueryBuilder extends \yii\base\Object * @var string the separator between different fragments of a SQL statement. * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. */ - public $separator = " "; + public $separator = ' '; /** * @var array the abstract column types mapped to physical column types. * This is mainly used to support creating/modifying tables using DB-independent data type specifications. @@ -101,6 +103,21 @@ class QueryBuilder extends \yii\base\Object $sql = implode($this->separator, array_filter($clauses)); $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset); + if (!empty($query->orderBy)) { + foreach ($query->orderBy as $expression) { + if ($expression instanceof Expression) { + $params = array_merge($params, $expression->params); + } + } + } + if (!empty($query->groupBy)) { + foreach ($query->groupBy as $expression) { + if ($expression instanceof Expression) { + $params = array_merge($params, $expression->params); + } + } + } + $union = $this->buildUnion($query->union, $params); if ($union !== '') { $sql = "($sql){$this->separator}$union"; @@ -113,12 +130,12 @@ class QueryBuilder extends \yii\base\Object * Creates an INSERT SQL statement. * For example, * - * ~~~ + * ```php * $sql = $queryBuilder->insert('user', [ - * 'name' => 'Sam', - * 'age' => 30, + * 'name' => 'Sam', + * 'age' => 30, * ], $params); - * ~~~ + * ``` * * The method will properly escape the table and column names. * @@ -153,29 +170,34 @@ class QueryBuilder extends \yii\base\Object } return 'INSERT INTO ' . $schema->quoteTableName($table) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); } /** * Generates a batch INSERT SQL statement. * For example, * - * ~~~ + * ```php * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [ * ['Tom', 30], * ['Jane', 20], * ['Linda', 25], * ]); - * ~~~ + * ``` * * Note that the values in each row must match the corresponding column names. * <<<<<<< HEAD +<<<<<<< HEAD ======= * The method will properly escape the column names, and quote the values to be inserted. * >>>>>>> yiichina/master +======= + * The method will properly escape the column names, and quote the values to be inserted. + * +>>>>>>> master * @param string $table the table that new rows will be inserted into. * @param array $columns the column names * @param array $rows the rows to be batch inserted into the table @@ -221,10 +243,10 @@ class QueryBuilder extends \yii\base\Object * Creates an UPDATE SQL statement. * For example, * - * ~~~ + * ```php * $params = []; * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params); - * ~~~ + * ``` * * The method will properly escape the table and column names. * @@ -268,9 +290,9 @@ class QueryBuilder extends \yii\base\Object * Creates a DELETE SQL statement. * For example, * - * ~~~ + * ```php * $sql = $queryBuilder->delete('user', 'status = 0'); - * ~~~ + * ``` * * The method will properly escape the table and column names. * @@ -302,13 +324,13 @@ class QueryBuilder extends \yii\base\Object * * For example, * - * ~~~ + * ```php * $sql = $queryBuilder->createTable('user', [ * 'id' => 'pk', * 'name' => 'string', * 'age' => 'integer', * ]); - * ~~~ + * ``` * * @param string $table the name of the table to be created. The name will be properly quoted by the method. * @param array $columns the columns (name => definition) in the new table. @@ -325,7 +347,7 @@ class QueryBuilder extends \yii\base\Object $cols[] = "\t" . $type; } } - $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; + $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; return $options === null ? $sql : $sql . ' ' . $options; } @@ -348,7 +370,7 @@ class QueryBuilder extends \yii\base\Object */ public function dropTable($table) { - return "DROP TABLE " . $this->db->quoteTableName($table); + return 'DROP TABLE ' . $this->db->quoteTableName($table); } /** @@ -392,7 +414,7 @@ class QueryBuilder extends \yii\base\Object */ public function truncateTable($table) { - return "TRUNCATE TABLE " . $this->db->quoteTableName($table); + return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table); } /** @@ -419,8 +441,8 @@ class QueryBuilder extends \yii\base\Object */ public function dropColumn($table, $column) { - return "ALTER TABLE " . $this->db->quoteTableName($table) - . " DROP COLUMN " . $this->db->quoteColumnName($column); + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' DROP COLUMN ' . $this->db->quoteColumnName($column); } /** @@ -432,9 +454,9 @@ class QueryBuilder extends \yii\base\Object */ public function renameColumn($table, $oldName, $newName) { - return "ALTER TABLE " . $this->db->quoteTableName($table) - . " RENAME COLUMN " . $this->db->quoteColumnName($oldName) - . " TO " . $this->db->quoteColumnName($newName); + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName) + . ' TO ' . $this->db->quoteColumnName($newName); } /** @@ -555,6 +577,59 @@ class QueryBuilder extends \yii\base\Object throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.'); } + /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment); + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return string the SQL statement for adding comment on table + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment); + } + + /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL'; + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL'; + } + /** * Converts an abstract column type into a physical column type. * The conversion is done using the type map specified in [[typeMap]]. @@ -563,6 +638,8 @@ class QueryBuilder extends \yii\base\Object * * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `unsignedpk`: an unsigned auto-incremental primary key type, will be converted into "int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `char`: char type, will be converted into "char(1)" * - `string`: string type, will be converted into "varchar(255)" * - `text`: a long string type, will be converted into "text" * - `smallint`: a small integer type, will be converted into "smallint(6)" @@ -589,11 +666,15 @@ class QueryBuilder extends \yii\base\Object * be ignored. * * If a type cannot be found in [[typeMap]], it will be returned without any change. - * @param string $type abstract column type + * @param string|ColumnSchemaBuilder $type abstract column type * @return string physical column type. */ public function getColumnType($type) { + if ($type instanceof ColumnSchemaBuilder) { + $type = $type->__toString(); + } + if (isset($this->typeMap[$type])) { return $this->typeMap[$type]; } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) { @@ -750,7 +831,17 @@ class QueryBuilder extends \yii\base\Object */ public function buildGroupBy($columns) { - return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns); + if (empty($columns)) { + return ''; + } + foreach ($columns as $i => $column) { + if ($column instanceof Expression) { + $columns[$i] = $column->expression; + } elseif (strpos($column, '(') === false) { + $columns[$i] = $this->db->quoteColumnName($column); + } + } + return 'GROUP BY ' . implode(', ', $columns); } /** @@ -832,11 +923,15 @@ class QueryBuilder extends \yii\base\Object */ protected function hasLimit($limit) { +<<<<<<< HEAD <<<<<<< HEAD return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0; ======= return ctype_digit((string) $limit); >>>>>>> yiichina/master +======= + return ctype_digit((string) $limit); +>>>>>>> master } /** @@ -846,12 +941,17 @@ class QueryBuilder extends \yii\base\Object */ protected function hasOffset($offset) { +<<<<<<< HEAD <<<<<<< HEAD return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0'; ======= $offset = (string) $offset; return ctype_digit($offset) && $offset !== '0'; >>>>>>> yiichina/master +======= + $offset = (string) $offset; + return ctype_digit($offset) && $offset !== '0'; +>>>>>>> master } /** @@ -880,11 +980,15 @@ class QueryBuilder extends \yii\base\Object } /** +<<<<<<< HEAD <<<<<<< HEAD * Processes columns and properly quote them if necessary. ======= * Processes columns and properly quotes them if necessary. >>>>>>> yiichina/master +======= + * Processes columns and properly quotes them if necessary. +>>>>>>> master * It will join all columns into a string with comma as separators. * @param string|array $columns the columns to be processed * @return string the processing result @@ -911,14 +1015,19 @@ class QueryBuilder extends \yii\base\Object /** * Parses the condition specification and generates the corresponding SQL expression. - * @param string|array $condition the condition specification. Please refer to [[Query::where()]] + * @param string|array|Expression $condition the condition specification. Please refer to [[Query::where()]] * on how to specify a condition. * @param array $params the binding parameters to be populated * @return string the generated SQL expression */ public function buildCondition($condition, &$params) { - if (!is_array($condition)) { + if ($condition instanceof Expression) { + foreach ($condition->params as $n => $v) { + $params[$n] = $v; + } + return $condition->expression; + } elseif (!is_array($condition)) { return (string) $condition; } elseif (empty($condition)) { return ''; @@ -948,7 +1057,7 @@ class QueryBuilder extends \yii\base\Object { $parts = []; foreach ($condition as $column => $value) { - if (is_array($value) || $value instanceof Query) { + if (ArrayHelper::isTraversable($value) || $value instanceof Query) { // IN condition $parts[] = $this->buildInCondition('IN', [$column, $value], $params); } else { @@ -986,6 +1095,12 @@ class QueryBuilder extends \yii\base\Object if (is_array($operand)) { $operand = $this->buildCondition($operand, $params); } + if ($operand instanceof Expression) { + foreach ($operand->params as $n => $v) { + $params[$n] = $v; + } + $operand = $operand->expression; + } if ($operand !== '') { $parts[] = $operand; } @@ -1007,7 +1122,7 @@ class QueryBuilder extends \yii\base\Object */ public function buildNotCondition($operator, $operands, &$params) { - if (count($operands) != 1) { + if (count($operands) !== 1) { throw new InvalidParamException("Operator '$operator' requires exactly one operand."); } @@ -1084,7 +1199,7 @@ class QueryBuilder extends \yii\base\Object list($column, $values) = $operands; - if ($values === [] || $column === []) { + if ($column === []) { return $operator === 'IN' ? '0=1' : ''; } @@ -1092,41 +1207,46 @@ class QueryBuilder extends \yii\base\Object return $this->buildSubqueryInCondition($operator, $column, $values, $params); } - $values = (array) $values; - - if (count($column) > 1) { + if ($column instanceof \Traversable || count($column) > 1) { return $this->buildCompositeInCondition($operator, $column, $values, $params); } if (is_array($column)) { $column = reset($column); } + + $sqlValues = []; foreach ($values as $i => $value) { - if (is_array($value)) { + if (is_array($value) || $value instanceof \ArrayAccess) { $value = isset($value[$column]) ? $value[$column] : null; } if ($value === null) { - $values[$i] = 'NULL'; + $sqlValues[$i] = 'NULL'; } elseif ($value instanceof Expression) { - $values[$i] = $value->expression; + $sqlValues[$i] = $value->expression; foreach ($value->params as $n => $v) { $params[$n] = $v; } } else { $phName = self::PARAM_PREFIX . count($params); $params[$phName] = $value; - $values[$i] = $phName; + $sqlValues[$i] = $phName; } } + + if (empty($sqlValues)) { + return $operator === 'IN' ? '0=1' : ''; + } + if (strpos($column, '(') === false) { $column = $this->db->quoteColumnName($column); } - if (count($values) > 1) { - return "$column $operator (" . implode(', ', $values) . ')'; + if (count($sqlValues) > 1) { + return "$column $operator (" . implode(', ', $sqlValues) . ')'; } else { $operator = $operator === 'IN' ? '=' : '<>'; - return $column . $operator . reset($values); + return $column . $operator . reset($sqlValues); } } @@ -1161,7 +1281,7 @@ class QueryBuilder extends \yii\base\Object * Builds SQL for IN condition * * @param string $operator - * @param array $columns + * @param array|\Traversable $columns * @param array $values * @param array $params * @return string SQL @@ -1182,13 +1302,17 @@ class QueryBuilder extends \yii\base\Object } $vss[] = '(' . implode(', ', $vs) . ')'; } + + if (empty($vss)) { + return $operator === 'IN' ? '0=1' : ''; + }; + + $sqlColumns = []; foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } + $sqlColumns[] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column; } - return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; + return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')'; } /** @@ -1217,7 +1341,7 @@ class QueryBuilder extends \yii\base\Object throw new InvalidParamException("Operator '$operator' requires two operands."); } - $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']; + $escape = isset($operands[2]) ? $operands[2] : ['%' => '\%', '_' => '\_', '\\' => '\\\\']; unset($operands[2]); if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) { @@ -1304,15 +1428,32 @@ class QueryBuilder extends \yii\base\Object } return "$column $operator {$value->expression}"; <<<<<<< HEAD +<<<<<<< HEAD ======= } elseif ($value instanceof Query) { list($sql, $params) = $this->build($value, $params); return "$column $operator ($sql)"; >>>>>>> yiichina/master +======= + } elseif ($value instanceof Query) { + list($sql, $params) = $this->build($value, $params); + return "$column $operator ($sql)"; +>>>>>>> master } else { $phName = self::PARAM_PREFIX . count($params); $params[$phName] = $value; return "$column $operator $phName"; } } + + /** + * Creates a SELECT EXISTS() SQL statement. + * @param string $rawSql the subquery in a raw form to select from. + * @return string the SELECT EXISTS() SQL statement. + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT EXISTS(' . $rawSql . ')'; + } } diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php index 48c912ee24..ab355be26a 100644 --- a/framework/db/QueryInterface.php +++ b/framework/db/QueryInterface.php @@ -62,14 +62,14 @@ interface QueryInterface * This can also be a callable (e.g. anonymous function) that returns the index value based on the given * row data. The signature of the callable should be: * - * ~~~ + * ```php * function ($row) * { * // return the index value corresponding to $row * } - * ~~~ + * ``` * - * @return static the query object itself + * @return $this the query object itself */ public function indexBy($column); @@ -91,11 +91,15 @@ interface QueryInterface * - `['status' => null]` generates `status IS NULL`. * * A condition in operator format generates the SQL expression according to the specified operator, which +<<<<<<< HEAD <<<<<<< HEAD * can be one of the followings: ======= * can be one of the following: >>>>>>> yiichina/master +======= + * can be one of the following: +>>>>>>> master * * - **and**: the operands should be concatenated together using `AND`. For example, * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, @@ -104,7 +108,7 @@ interface QueryInterface * The method will *not* do any quoting or escaping. * * - **or**: similar to the `and` operator except that the operands are concatenated using `OR`. For example, - * `['or', ['type' => [7, 8, 9]], ['id' => [1, 2, 3]]` will generate `(type IN (7, 8, 9) OR (id IN (1, 2, 3)))`. + * `['or', ['type' => [7, 8, 9]], ['id' => [1, 2, 3]]]` will generate `(type IN (7, 8, 9) OR (id IN (1, 2, 3)))`. * * - **not**: this will take only one operand and build the negation of it by prefixing the query string with `NOT`. * For example `['not', ['attribute' => null]]` will result in the condition `NOT (attribute IS NULL)`. @@ -158,7 +162,7 @@ interface QueryInterface * following SQL expression: `id >= 10`. * * @param string|array $condition the conditions that should be put in the WHERE part. - * @return static the query object itself + * @return $this the query object itself * @see andWhere() * @see orWhere() */ @@ -169,7 +173,7 @@ interface QueryInterface * The new condition and the existing one will be joined using the 'AND' operator. * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself + * @return $this the query object itself * @see where() * @see orWhere() */ @@ -180,7 +184,7 @@ interface QueryInterface * The new condition and the existing one will be joined using the 'OR' operator. * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself + * @return $this the query object itself * @see where() * @see andWhere() */ @@ -191,7 +195,7 @@ interface QueryInterface * * @param array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself + * @return $this the query object itself * @see andFilterWhere() * @see orFilterWhere() */ @@ -202,7 +206,7 @@ interface QueryInterface * The new condition and the existing one will be joined using the 'AND' operator. * @param array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself + * @return $this the query object itself * @see filterWhere() * @see orFilterWhere() */ @@ -213,7 +217,7 @@ interface QueryInterface * The new condition and the existing one will be joined using the 'OR' operator. * @param array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself + * @return $this the query object itself * @see filterWhere() * @see andFilterWhere() */ @@ -226,7 +230,7 @@ interface QueryInterface * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return static the query object itself + * @return $this the query object itself * @see addOrderBy() */ public function orderBy($columns); @@ -238,7 +242,7 @@ interface QueryInterface * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return static the query object itself + * @return $this the query object itself * @see orderBy() */ public function addOrderBy($columns); @@ -246,14 +250,14 @@ interface QueryInterface /** * Sets the LIMIT part of the query. * @param integer $limit the limit. Use null or negative value to disable limit. - * @return static the query object itself + * @return $this the query object itself */ public function limit($limit); /** * Sets the OFFSET part of the query. * @param integer $offset the offset. Use null or negative value to disable offset. - * @return static the query object itself + * @return $this the query object itself */ public function offset($offset); } diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 40d17f11cc..85d0108b5f 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -58,14 +58,14 @@ trait QueryTrait * This can also be a callable (e.g. anonymous function) that returns the index value based on the given * row data. The signature of the callable should be: * - * ~~~ + * ```php * function ($row) * { * // return the index value corresponding to $row * } - * ~~~ + * ``` * - * @return static the query object itself. + * @return $this the query object itself */ public function indexBy($column) { @@ -79,7 +79,7 @@ trait QueryTrait * See [[QueryInterface::where()]] for detailed documentation. * * @param string|array $condition the conditions that should be put in the WHERE part. - * @return static the query object itself. + * @return $this the query object itself * @see andWhere() * @see orWhere() */ @@ -94,7 +94,7 @@ trait QueryTrait * The new condition and the existing one will be joined using the 'AND' operator. * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself. + * @return $this the query object itself * @see where() * @see orWhere() */ @@ -113,7 +113,7 @@ trait QueryTrait * The new condition and the existing one will be joined using the 'OR' operator. * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself. + * @return $this the query object itself * @see where() * @see andWhere() */ @@ -149,7 +149,7 @@ trait QueryTrait * * @param array $condition the conditions that should be put in the WHERE part. * See [[where()]] on how to specify this parameter. - * @return static the query object itself. + * @return $this the query object itself * @see where() * @see andFilterWhere() * @see orFilterWhere() @@ -173,7 +173,7 @@ trait QueryTrait * * @param array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself. + * @return $this the query object itself * @see filterWhere() * @see orFilterWhere() */ @@ -196,7 +196,7 @@ trait QueryTrait * * @param array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. - * @return static the query object itself. + * @return $this the query object itself * @see filterWhere() * @see andFilterWhere() */ @@ -292,15 +292,19 @@ trait QueryTrait /** * Sets the ORDER BY part of the query. - * @param string|array $columns the columns (and the directions) to be ordered by. + * @param string|array|Expression $columns the columns (and the directions) to be ordered by. * Columns can be specified in either a string (e.g. `"id ASC, name DESC"`) or an array * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). + * * Note that if your order-by is an expression containing commas, you should always use an array * to represent the order-by information. Otherwise, the method will not be able to correctly determine * the order-by columns. - * @return static the query object itself. + * + * Since version 2.0.7, an [[Expression]] object can be passed to specify the ORDER BY part explicitly in plain SQL. + * @return $this the query object itself * @see addOrderBy() */ public function orderBy($columns) @@ -311,12 +315,19 @@ trait QueryTrait /** * Adds additional ORDER BY columns to the query. - * @param string|array $columns the columns (and the directions) to be ordered by. + * @param string|array|Expression $columns the columns (and the directions) to be ordered by. * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return static the query object itself. + * + * Note that if your order-by is an expression containing commas, you should always use an array + * to represent the order-by information. Otherwise, the method will not be able to correctly determine + * the order-by columns. + * + * Since version 2.0.7, an [[Expression]] object can be passed to specify the ORDER BY part explicitly in plain SQL. + * @return $this the query object itself * @see orderBy() */ public function addOrderBy($columns) @@ -333,12 +344,14 @@ trait QueryTrait /** * Normalizes format of ORDER BY data * - * @param array|string $columns + * @param array|string|Expression $columns the columns value to normalize. See [[orderBy]] and [[addOrderBy]]. * @return array */ protected function normalizeOrderBy($columns) { - if (is_array($columns)) { + if ($columns instanceof Expression) { + return [$columns]; + } elseif (is_array($columns)) { return $columns; } else { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); @@ -357,7 +370,7 @@ trait QueryTrait /** * Sets the LIMIT part of the query. * @param integer $limit the limit. Use null or negative value to disable limit. - * @return static the query object itself. + * @return $this the query object itself */ public function limit($limit) { @@ -368,7 +381,7 @@ trait QueryTrait /** * Sets the OFFSET part of the query. * @param integer $offset the offset. Use null or negative value to disable offset. - * @return static the query object itself. + * @return $this the query object itself */ public function offset($offset) { diff --git a/framework/db/Schema.php b/framework/db/Schema.php index 53455d6489..c3b605010e 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -23,10 +23,15 @@ use yii\caching\TagDependency; * sequence object. This property is read-only. * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only. <<<<<<< HEAD +<<<<<<< HEAD ======= * @property string[] $schemaNames All schema names in the database, except system schemas. This property is * read-only. >>>>>>> yiichina/master +======= + * @property string[] $schemaNames All schema names in the database, except system schemas. This property is + * read-only. +>>>>>>> master * @property string[] $tableNames All table names in the database. This property is read-only. * @property TableSchema[] $tableSchemas The metadata for all tables in the database. Each array element is an * instance of [[TableSchema]] or its child class. This property is read-only. @@ -41,14 +46,21 @@ use yii\caching\TagDependency; abstract class Schema extends Object { /** +<<<<<<< HEAD <<<<<<< HEAD * The followings are the supported abstract column data types. ======= * The following are the supported abstract column data types. >>>>>>> yiichina/master +======= + * The following are the supported abstract column data types. +>>>>>>> master */ const TYPE_PK = 'pk'; + const TYPE_UPK = 'upk'; const TYPE_BIGPK = 'bigpk'; + const TYPE_UBIGPK = 'ubigpk'; + const TYPE_CHAR = 'char'; const TYPE_STRING = 'string'; const TYPE_TEXT = 'text'; const TYPE_SMALLINT = 'smallint'; @@ -83,12 +95,18 @@ abstract class Schema extends Object /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * @var array list of ALL schema names in the database, except system schemas */ private $_schemaNames; /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @var array list of ALL table names in the database */ private $_tableNames = []; @@ -114,7 +132,7 @@ abstract class Schema extends Object /** * Loads the metadata for the specified table. * @param string $name table name - * @return TableSchema DBMS-dependent table metadata, null if the table does not exist. + * @return null|TableSchema DBMS-dependent table metadata, null if the table does not exist. */ abstract protected function loadTableSchema($name); @@ -122,7 +140,7 @@ abstract class Schema extends Object * Obtains the metadata for the named table. * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. * @param boolean $refresh whether to reload the table schema even if it is found in the cache. - * @return TableSchema table metadata. Null if the named table does not exist. + * @return null|TableSchema table metadata. Null if the named table does not exist. */ public function getTableSchema($name, $refresh = false) { @@ -210,7 +228,10 @@ abstract class Schema extends Object /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Returns all schema names in the database, except system schemas. * @param boolean $refresh whether to fetch the latest available schema names. If this is false, * schema names fetched previously (if available) will be returned. @@ -227,7 +248,10 @@ abstract class Schema extends Object } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Returns all table names in the database. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name. * If not empty, the returned table names will be prefixed with the schema name. @@ -293,6 +317,24 @@ abstract class Schema extends Object $this->_tables = []; } + /** + * Refreshes the particular table schema. + * This method cleans up cached table schema so that it can be re-created later + * to reflect the database schema change. + * @param string $name table name. + * @since 2.0.6 + */ + public function refreshTableSchema($name) + { + unset($this->_tables[$name]); + $this->_tableNames = []; + /* @var $cache Cache */ + $cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache; + if ($this->db->enableSchemaCache && $cache instanceof Cache) { + $cache->delete($this->getCacheKey($name)); + } + } + /** * Creates a query builder for the database. * This method may be overridden by child classes to create a DBMS-specific query builder. @@ -305,7 +347,25 @@ abstract class Schema extends Object /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= + * Create a column schema builder instance giving the type and value precision. + * + * This method may be overridden by child classes to create a DBMS-specific column schema builder. + * + * @param string $type type of the column. See [[ColumnSchemaBuilder::$type]]. + * @param integer|string|array $length length or precision of the column. See [[ColumnSchemaBuilder::$length]]. + * @return ColumnSchemaBuilder column schema builder instance + * @since 2.0.6 + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length); + } + + /** +>>>>>>> master * Returns all schema names in the database, including the default one but not system schemas. * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception. @@ -319,7 +379,10 @@ abstract class Schema extends Object } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Returns all table names in the database. * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception. @@ -336,12 +399,12 @@ abstract class Schema extends Object * Returns all unique indexes for the given table. * Each array element is of the following structure: * - * ~~~ + * ```php * [ * 'IndexName1' => ['col1' [, ...]], * 'IndexName2' => ['col2' [, ...]], * ] - * ~~~ + * ``` * * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception @@ -364,11 +427,15 @@ abstract class Schema extends Object public function getLastInsertID($sequenceName = '') { if ($this->db->isActive) { +<<<<<<< HEAD <<<<<<< HEAD return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $sequenceName); ======= return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $this->quoteSimpleTableName($sequenceName)); >>>>>>> yiichina/master +======= + return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $this->quoteTableName($sequenceName)); +>>>>>>> master } else { throw new InvalidCallException('DB Connection is not active.'); } @@ -424,7 +491,10 @@ abstract class Schema extends Object /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Executes the INSERT command, returning primary key values. * @param string $table the table that new rows will be inserted into. * @param array $columns the column data (name => value) to be inserted into the table. @@ -451,7 +521,10 @@ abstract class Schema extends Object } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Quotes a string value for use in a query. * Note that if the parameter is not a string, it will be returned without change. * @param string $str string to be quoted @@ -509,7 +582,7 @@ abstract class Schema extends Object */ public function quoteColumnName($name) { - if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { + if (strpos($name, '(') !== false || strpos($name, '[[') !== false) { return $name; } if (($pos = strrpos($name, '.')) !== false) { @@ -518,7 +591,9 @@ abstract class Schema extends Object } else { $prefix = ''; } - + if (strpos($name, '{{') !== false) { + return $name; + } return $prefix . $this->quoteSimpleColumnName($name); } @@ -583,9 +658,9 @@ abstract class Schema extends Object ]; if (isset($typeMap[$column->type])) { if ($column->type === 'bigint') { - return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; + return PHP_INT_SIZE === 8 && !$column->unsigned ? 'integer' : 'string'; } elseif ($column->type === 'integer') { - return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer'; + return PHP_INT_SIZE === 4 && $column->unsigned ? 'string' : 'integer'; } else { return $typeMap[$column->type]; } diff --git a/framework/db/SchemaBuilderTrait.php b/framework/db/SchemaBuilderTrait.php new file mode 100644 index 0000000000..89e587f7df --- /dev/null +++ b/framework/db/SchemaBuilderTrait.php @@ -0,0 +1,273 @@ +createTable('example_table', [ + * 'id' => $this->primaryKey(), + * 'name' => $this->string(64)->notNull(), + * 'type' => $this->integer()->notNull()->defaultValue(10), + * 'description' => $this->text(), + * 'rule_name' => $this->string(64), + * 'data' => $this->text(), + * 'created_at' => $this->datetime()->notNull(), + * 'updated_at' => $this->datetime(), + * ]); + * ``` + * + * @author Vasenin Matvey + * @since 2.0.6 + */ +trait SchemaBuilderTrait +{ + /** + * @return Connection the database connection to be used for schema building. + */ + protected abstract function getDb(); + + /** + * Creates a primary key column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function primaryKey($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_PK, $length); + } + + /** + * Creates a big primary key column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function bigPrimaryKey($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_BIGPK, $length); + } + + /** + * Creates a char column. + * @param integer $length column size definition i.e. the maximum string length. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.8 + */ + public function char($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_CHAR, $length); + } + + /** + * Creates a string column. + * @param integer $length column size definition i.e. the maximum string length. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function string($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_STRING, $length); + } + + /** + * Creates a text column. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function text() + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_TEXT); + } + + /** + * Creates a smallint column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function smallInteger($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_SMALLINT, $length); + } + + /** + * Creates an integer column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function integer($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_INTEGER, $length); + } + + /** + * Creates a bigint column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function bigInteger($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_BIGINT, $length); + } + + /** + * Creates a float column. + * @param integer $precision column value precision. First parameter passed to the column type, e.g. FLOAT(precision). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function float($precision = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_FLOAT, $precision); + } + + /** + * Creates a double column. + * @param integer $precision column value precision. First parameter passed to the column type, e.g. DOUBLE(precision). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function double($precision = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_DOUBLE, $precision); + } + + /** + * Creates a decimal column. + * @param integer $precision column value precision, which is usually the total number of digits. + * First parameter passed to the column type, e.g. DECIMAL(precision, scale). + * This parameter will be ignored if not supported by the DBMS. + * @param integer $scale column value scale, which is usually the number of digits after the decimal point. + * Second parameter passed to the column type, e.g. DECIMAL(precision, scale). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function decimal($precision = null, $scale = null) + { + $length = []; + if ($precision !== null) { + $length[] = $precision; + } + if ($scale !== null) { + $length[] = $scale; + } + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_DECIMAL, $length); + } + + /** + * Creates a datetime column. + * @param integer $precision column value precision. First parameter passed to the column type, e.g. DATETIME(precision). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function dateTime($precision = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_DATETIME, $precision); + } + + /** + * Creates a timestamp column. + * @param integer $precision column value precision. First parameter passed to the column type, e.g. TIMESTAMP(precision). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function timestamp($precision = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_TIMESTAMP, $precision); + } + + /** + * Creates a time column. + * @param integer $precision column value precision. First parameter passed to the column type, e.g. TIME(precision). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function time($precision = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_TIME, $precision); + } + + /** + * Creates a date column. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function date() + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_DATE); + } + + /** + * Creates a binary column. + * @param integer $length column size or precision definition. + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function binary($length = null) + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_BINARY, $length); + } + + /** + * Creates a boolean column. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function boolean() + { + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN); + } + + /** + * Creates a money column. + * @param integer $precision column value precision, which is usually the total number of digits. + * First parameter passed to the column type, e.g. DECIMAL(precision, scale). + * This parameter will be ignored if not supported by the DBMS. + * @param integer $scale column value scale, which is usually the number of digits after the decimal point. + * Second parameter passed to the column type, e.g. DECIMAL(precision, scale). + * This parameter will be ignored if not supported by the DBMS. + * @return ColumnSchemaBuilder the column instance which can be further customized. + * @since 2.0.6 + */ + public function money($precision = null, $scale = null) + { + $length = []; + if ($precision !== null) { + $length[] = $precision; + } + if ($scale !== null) { + $length[] = $scale; + } + return $this->getDb()->getSchema()->createColumnSchemaBuilder(Schema::TYPE_MONEY, $length); + } +} diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php index d83bc6b0c4..e13daabef8 100644 --- a/framework/db/TableSchema.php +++ b/framework/db/TableSchema.php @@ -45,13 +45,13 @@ class TableSchema extends Object /** * @var array foreign keys of this table. Each array element is of the following structure: * - * ~~~ + * ```php * [ * 'ForeignTableName', * 'fk1' => 'pk1', // pk1 is in foreign table * 'fk2' => 'pk2', // if composite foreign key * ] - * ~~~ + * ``` */ public $foreignKeys = []; /** @@ -87,7 +87,7 @@ class TableSchema extends Object */ public function fixPrimaryKey($keys) { - $keys = (array)$keys; + $keys = (array) $keys; $this->primaryKey = $keys; foreach ($this->columns as $column) { $column->isPrimaryKey = false; diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php index ebba0ed1b5..63595ed42a 100644 --- a/framework/db/Transaction.php +++ b/framework/db/Transaction.php @@ -18,17 +18,18 @@ use yii\base\InvalidConfigException; * The following code is a typical example of using transactions (note that some * DBMS may not support transactions): * - * ~~~ + * ```php * $transaction = $connection->beginTransaction(); * try { * $connection->createCommand($sql1)->execute(); * $connection->createCommand($sql2)->execute(); * //.... other SQL executions * $transaction->commit(); - * } catch (Exception $e) { + * } catch (\Exception $e) { * $transaction->rollBack(); + * throw $e; * } - * ~~~ + * ``` * * @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]] * or [[rollBack()]]. This property is read-only. @@ -36,6 +37,7 @@ use yii\base\InvalidConfigException; * one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a string * containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is * write-only. + * @property integer $level The current nesting level of the transaction. This property is read-only. * * @author Qiang Xue * @since 2.0 @@ -109,7 +111,7 @@ class Transaction extends \yii\base\Object } $this->db->open(); - if ($this->_level == 0) { + if ($this->_level === 0) { if ($isolationLevel !== null) { $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel); } @@ -143,7 +145,7 @@ class Transaction extends \yii\base\Object } $this->_level--; - if ($this->_level == 0) { + if ($this->_level === 0) { Yii::trace('Commit transaction', __METHOD__); $this->db->pdo->commit(); $this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); @@ -172,7 +174,7 @@ class Transaction extends \yii\base\Object } $this->_level--; - if ($this->_level == 0) { + if ($this->_level === 0) { Yii::trace('Roll back transaction', __METHOD__); $this->db->pdo->rollBack(); $this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); @@ -210,4 +212,13 @@ class Transaction extends \yii\base\Object Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__); $this->db->getSchema()->setTransactionIsolationLevel($level); } + + /** + * @return integer The current nesting level of the transaction. + * @since 2.0.8 + */ + public function getLevel() + { + return $this->_level; + } } diff --git a/framework/db/cubrid/ColumnSchemaBuilder.php b/framework/db/cubrid/ColumnSchemaBuilder.php new file mode 100644 index 0000000000..d418e80858 --- /dev/null +++ b/framework/db/cubrid/ColumnSchemaBuilder.php @@ -0,0 +1,71 @@ + + * @since 2.0.8 + */ +class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder +{ + /** + * @inheritdoc + */ + protected function buildUnsignedString() + { + return $this->isUnsigned ? ' UNSIGNED' : ''; + } + + /** + * @inheritdoc + */ + protected function buildAfterString() + { + return $this->after !== null ? + ' AFTER ' . $this->db->quoteColumnName($this->after) : + ''; + } + + /** + * @inheritdoc + */ + protected function buildFirstString() + { + return $this->isFirst ? ' FIRST' : ''; + } + + /** + * @inheritdoc + */ + protected function buildCommentString() + { + return $this->comment !== null ? " COMMENT " . $this->db->quoteValue($this->comment) : ''; + } + + /** + * @inheritdoc + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_PK: + $format = '{type}{check}{pos}{comment}{append}'; + break; + case self::CATEGORY_NUMERIC: + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}'; + break; + default: + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}'; + } + return $this->buildCompleteString($format); + } +} diff --git a/framework/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php index 5633fb2422..3ccee2443a 100644 --- a/framework/db/cubrid/QueryBuilder.php +++ b/framework/db/cubrid/QueryBuilder.php @@ -8,6 +8,7 @@ namespace yii\db\cubrid; use yii\base\InvalidParamException; +use yii\db\Exception; /** * QueryBuilder is the query builder for CUBRID databases (version 9.3.x and higher). @@ -22,7 +23,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = [ Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_UPK => 'int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_UBIGPK => 'bigint UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'varchar', Schema::TYPE_SMALLINT => 'smallint', @@ -63,7 +67,7 @@ class QueryBuilder extends \yii\db\QueryBuilder $value = (int) $value; } - return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;"; + return 'ALTER TABLE ' . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;"; } elseif ($table === null) { throw new InvalidParamException("Table not found: $tableName"); } else { @@ -91,4 +95,90 @@ class QueryBuilder extends \yii\db\QueryBuilder return $sql; } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END'; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $definition = $this->getColumnDefinition($table, $column); + $definition = trim(preg_replace("/COMMENT '(.*?)'/i", '', $definition)); + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' CHANGE ' . $this->db->quoteColumnName($column) + . ' ' . $this->db->quoteColumnName($column) + . (empty($definition) ? '' : ' ' . $definition) + . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return $this->addCommentOnColumn($table, $column, ''); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return $this->addCommentOnTable($table, ''); + } + + + /** + * Gets column definition. + * + * @param string $table table name + * @param string $column column name + * @return null|string the column definition + * @throws Exception in case when table does not contain column + * @since 2.0.8 + */ + private function getColumnDefinition($table, $column) + { + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->db->quoteTableName($table))->queryOne(); + if ($row === false) { + throw new Exception("Unable to find column '$column' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + $sql = preg_replace('/^[^(]+\((.*)\).*$/', '\1', $sql); + $sql = str_replace(', [', ",\n[", $sql); + if (preg_match_all('/^\s*\[(.*?)\]\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $column) { + return $matches[2][$i]; + } + } + } + return null; + } } diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php index 939063b696..f70063c229 100644 --- a/framework/db/cubrid/Schema.php +++ b/framework/db/cubrid/Schema.php @@ -45,10 +45,10 @@ class Schema extends \yii\db\Schema 'timestamp' => self::TYPE_TIMESTAMP, 'datetime' => self::TYPE_DATETIME, // String data types - 'char' => self::TYPE_STRING, + 'char' => self::TYPE_CHAR, 'varchar' => self::TYPE_STRING, 'char varying' => self::TYPE_STRING, - 'nchar' => self::TYPE_STRING, + 'nchar' => self::TYPE_CHAR, 'nchar varying' => self::TYPE_STRING, 'string' => self::TYPE_STRING, // BLOB/CLOB data types @@ -155,7 +155,7 @@ class Schema extends \yii\db\Schema } else { $table->foreignKeys[$key['FK_NAME']] = [ $key['PKTABLE_NAME'], - $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'], ]; } } @@ -227,7 +227,7 @@ class Schema extends \yii\db\Schema ) { $column->defaultValue = new Expression($info['Default']); } elseif (isset($type) && $type === 'bit') { - $column->defaultValue = hexdec(trim($info['Default'],'X\'')); + $column->defaultValue = hexdec(trim($info['Default'], 'X\'')); } else { $column->defaultValue = $column->phpTypecast($info['Default']); } @@ -243,7 +243,7 @@ class Schema extends \yii\db\Schema protected function findTableNames($schema = '') { $pdo = $this->db->getSlavePdo(); - $tables =$pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); + $tables = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); $tableNames = []; foreach ($tables as $table) { // do not list system tables @@ -299,4 +299,12 @@ class Schema extends \yii\db\Schema } parent::setTransactionIsolationLevel($level); } + + /** + * @inheritdoc + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length, $this->db); + } } diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 534d7649de..d6332faf0e 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -8,6 +8,7 @@ namespace yii\db\mssql; use yii\base\InvalidParamException; +use yii\base\NotSupportedException; /** * QueryBuilder is the query builder for MS SQL Server databases (version 2008 and above). @@ -22,9 +23,12 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = [ Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_UPK => 'int IDENTITY PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', - Schema::TYPE_STRING => 'varchar(255)', - Schema::TYPE_TEXT => 'text', + Schema::TYPE_UBIGPK => 'bigint IDENTITY PRIMARY KEY', + Schema::TYPE_CHAR => 'nchar(1)', + Schema::TYPE_STRING => 'nvarchar(255)', + Schema::TYPE_TEXT => 'ntext', Schema::TYPE_SMALLINT => 'smallint', Schema::TYPE_INTEGER => 'int', Schema::TYPE_BIGINT => 'bigint', @@ -52,7 +56,7 @@ class QueryBuilder extends \yii\db\QueryBuilder } if ($this->isOldMssql()) { - return $this->oldbuildOrderByAndLimit($sql, $orderBy, $limit, $offset); + return $this->oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset); } else { return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset); } @@ -117,25 +121,28 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * Builds a SQL statement for renaming a DB table. - * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $oldName the table to be renamed. The name will be properly quoted by the method. * @param string $newName the new table name. The name will be properly quoted by the method. * @return string the SQL statement for renaming a DB table. */ - public function renameTable($table, $newName) + public function renameTable($oldName, $newName) { - return "sp_rename '$table', '$newName'"; + return 'sp_rename ' . $this->db->quoteTableName($oldName) . ', ' . $this->db->quoteTableName($newName); } /** * Builds a SQL statement for renaming a column. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $name the old name of the column. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. * @param string $newName the new name of the column. The name will be properly quoted by the method. * @return string the SQL statement for renaming a DB column. */ - public function renameColumn($table, $name, $newName) + public function renameColumn($table, $oldName, $newName) { - return "sp_rename '$table.$name', '$newName', 'COLUMN'"; + $table = $this->db->quoteTableName($table); + $oldName = $this->db->quoteColumnName($oldName); + $newName = $this->db->quoteColumnName($newName); + return "sp_rename '{$table}.{$oldName}', {$newName}, 'COLUMN'"; } /** @@ -179,6 +186,42 @@ class QueryBuilder extends \yii\db\QueryBuilder return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL"; } + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; + } + /** * Returns an array of column names given model name * @@ -211,20 +254,15 @@ class QueryBuilder extends \yii\db\QueryBuilder { if ($this->_oldMssql === null) { $pdo = $this->db->getSlavePdo(); - $version = preg_split("/\./", $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); + $version = explode('.', $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); $this->_oldMssql = $version[0] < 11; } return $this->_oldMssql; } /** - * Builds SQL for IN condition - * - * @param string $operator - * @param array $columns - * @param array $values - * @param array $params - * @return string SQL + * @inheritdoc + * @throws NotSupportedException if `$columns` is an array */ protected function buildSubqueryInCondition($operator, $columns, $values, &$params) { @@ -266,4 +304,13 @@ class QueryBuilder extends \yii\db\QueryBuilder return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')'; } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END'; + } } diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php index 15d3138cfb..9fd4bac2e6 100644 --- a/framework/db/mssql/Schema.php +++ b/framework/db/mssql/Schema.php @@ -47,11 +47,11 @@ class Schema extends \yii\db\Schema 'datetime' => self::TYPE_DATETIME, 'time' => self::TYPE_TIME, // character strings - 'char' => self::TYPE_STRING, + 'char' => self::TYPE_CHAR, 'varchar' => self::TYPE_STRING, 'text' => self::TYPE_TEXT, // unicode character strings - 'nchar' => self::TYPE_STRING, + 'nchar' => self::TYPE_CHAR, 'nvarchar' => self::TYPE_STRING, 'ntext' => self::TYPE_TEXT, // binary strings @@ -152,13 +152,13 @@ class Schema extends \yii\db\Schema { $parts = explode('.', str_replace(['[', ']'], '', $name)); $partCount = count($parts); - if ($partCount == 3) { + if ($partCount === 3) { // catalog name, schema name and table name passed $table->catalogName = $parts[0]; $table->schemaName = $parts[1]; $table->name = $parts[2]; $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name; - } elseif ($partCount == 2) { + } elseif ($partCount === 2) { // only schema name and table name passed $table->schemaName = $parts[0]; $table->name = $parts[1]; @@ -180,7 +180,7 @@ class Schema extends \yii\db\Schema $column = $this->createColumnSchema(); $column->name = $info['column_name']; - $column->allowNull = $info['is_nullable'] == 'YES'; + $column->allowNull = $info['is_nullable'] === 'YES'; $column->dbType = $info['data_type']; $column->enumValues = []; // mssql has only vague equivalents to enum $column->isPrimaryKey = null; // primary key will be determined in findColumns() method @@ -214,7 +214,7 @@ class Schema extends \yii\db\Schema $column->phpType = $this->getColumnPhpType($column); - if ($info['column_default'] == '(NULL)') { + if ($info['column_default'] === '(NULL)') { $info['column_default'] = null; } if (!$column->isPrimaryKey && ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP')) { @@ -283,6 +283,7 @@ SQL; } /** +<<<<<<< HEAD <<<<<<< HEAD * Collects the primary key column details for the given table. * @param TableSchema $table the table metadata @@ -297,6 +298,15 @@ SQL; */ protected function findTableConstraints($table, $type) >>>>>>> yiichina/master +======= + * Collects the constraint details for the given table and constraint type. + * @param TableSchema $table + * @param string $type either PRIMARY KEY or UNIQUE + * @return array each entry contains index_name and field_name + * @since 2.0.4 + */ + protected function findTableConstraints($table, $type) +>>>>>>> master { $keyColumnUsageTableName = 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE'; $tableConstraintsTableName = 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS'; @@ -310,12 +320,18 @@ SQL; $sql = <<>>>>>> master [kcu].[column_name] AS [field_name] FROM {$keyColumnUsageTableName} AS [kcu] LEFT JOIN {$tableConstraintsTableName} AS [tc] ON + [kcu].[table_schema] = [tc].[table_schema] AND [kcu].[table_name] = [tc].[table_name] AND [kcu].[constraint_name] = [tc].[constraint_name] WHERE +<<<<<<< HEAD [tc].[constraint_type] = 'PRIMARY KEY' AND ======= [kcu].[constraint_name] AS [index_name], @@ -328,15 +344,21 @@ LEFT JOIN {$tableConstraintsTableName} AS [tc] ON WHERE [tc].[constraint_type] = :type AND >>>>>>> yiichina/master +======= + [tc].[constraint_type] = :type AND +>>>>>>> master [kcu].[table_name] = :tableName AND [kcu].[table_schema] = :schemaName SQL; +<<<<<<< HEAD <<<<<<< HEAD $table->primaryKey = $this->db ->createCommand($sql, [':tableName' => $table->name, ':schemaName' => $table->schemaName]) ->queryColumn(); ======= +======= +>>>>>>> master return $this->db ->createCommand($sql, [ ':tableName' => $table->name, @@ -357,7 +379,10 @@ SQL; $result[] = $row['field_name']; } $table->primaryKey = $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -393,6 +418,7 @@ JOIN {$keyColumnUsageTableName} AS [kcu2] ON [kcu2].[constraint_name] = [rc].[unique_constraint_name] AND [kcu2].[ordinal_position] = [kcu1].[ordinal_position] <<<<<<< HEAD +<<<<<<< HEAD WHERE [kcu1].[table_name] = :tableName SQL; @@ -401,11 +427,19 @@ SQL; WHERE [kcu1].[table_name] = :tableName AND [kcu1].[table_schema] = :schemaName SQL; +======= +WHERE [kcu1].[table_name] = :tableName AND [kcu1].[table_schema] = :schemaName +SQL; + +>>>>>>> master $rows = $this->db->createCommand($sql, [ ':tableName' => $table->name, ':schemaName' => $table->schemaName, ])->queryAll(); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master $table->foreignKeys = []; foreach ($rows as $row) { $table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']]; @@ -427,28 +461,45 @@ SQL; SELECT [t].[table_name] FROM [INFORMATION_SCHEMA].[TABLES] AS [t] <<<<<<< HEAD +<<<<<<< HEAD WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'BASE TABLE' ======= WHERE [t].[table_schema] = :schema AND [t].[table_type] IN ('BASE TABLE', 'VIEW') ORDER BY [t].[table_name] >>>>>>> yiichina/master +======= +WHERE [t].[table_schema] = :schema AND [t].[table_type] IN ('BASE TABLE', 'VIEW') +ORDER BY [t].[table_name] +>>>>>>> master SQL; return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn(); } <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master /** * Returns all unique indexes for the given table. * Each array element is of the following structure: * +<<<<<<< HEAD * ~~~ * [ * 'IndexName1' => ['col1' [, ...]], * 'IndexName2' => ['col2' [, ...]], * ] * ~~~ +======= + * ```php + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ``` +>>>>>>> master * * @param TableSchema $table the table metadata * @return array all unique indexes for the given table. @@ -462,5 +513,8 @@ SQL; } return $result; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } diff --git a/framework/db/mysql/ColumnSchemaBuilder.php b/framework/db/mysql/ColumnSchemaBuilder.php new file mode 100644 index 0000000000..e740c782b0 --- /dev/null +++ b/framework/db/mysql/ColumnSchemaBuilder.php @@ -0,0 +1,71 @@ + + * @since 2.0.8 + */ +class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder +{ + /** + * @inheritdoc + */ + protected function buildUnsignedString() + { + return $this->isUnsigned ? ' UNSIGNED' : ''; + } + + /** + * @inheritdoc + */ + protected function buildAfterString() + { + return $this->after !== null ? + ' AFTER ' . $this->db->quoteColumnName($this->after) : + ''; + } + + /** + * @inheritdoc + */ + protected function buildFirstString() + { + return $this->isFirst ? ' FIRST' : ''; + } + + /** + * @inheritdoc + */ + protected function buildCommentString() + { + return $this->comment !== null ? " COMMENT " . $this->db->quoteValue($this->comment) : ''; + } + + /** + * @inheritdoc + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_PK: + $format = '{type}{length}{check}{comment}{pos}{append}'; + break; + case self::CATEGORY_NUMERIC: + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}'; + break; + default: + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}'; + } + return $this->buildCompleteString($format); + } +} diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index f69c5a4a2b..065c461f89 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -7,8 +7,9 @@ namespace yii\db\mysql; -use yii\db\Exception; use yii\base\InvalidParamException; +use yii\db\Exception; +use yii\db\Expression; /** * QueryBuilder is the query builder for MySQL databases. @@ -23,7 +24,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = [ Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_UPK => 'int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_UBIGPK => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', @@ -79,6 +83,19 @@ class QueryBuilder extends \yii\db\QueryBuilder . $this->db->quoteColumnName($newName); } + /** + * @inheritdoc + * @see https://bugs.mysql.com/bug.php?id=48875 + */ + public function createIndex($name, $table, $columns, $unique = false) + { + return 'ALTER TABLE ' + . $this->db->quoteTableName($table) + . ($unique ? ' ADD UNIQUE INDEX ' : ' ADD INDEX ') + . $this->db->quoteTableName($name) + . ' (' . $this->buildColumns($columns) . ')'; + } + /** * Builds a SQL statement for dropping a foreign key constraint. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. @@ -135,8 +152,8 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * Builds a SQL statement for enabling or disabling integrity check. * @param boolean $check whether to turn on or off the integrity check. - * @param string $table the table name. Meaningless for MySQL. * @param string $schema the schema of the tables. Meaningless for MySQL. + * @param string $table the table name. Meaningless for MySQL. * @return string the SQL statement for checking integrity */ public function checkIntegrity($check = true, $schema = '', $table = '') @@ -164,4 +181,118 @@ class QueryBuilder extends \yii\db\QueryBuilder return $sql; } + + /** + * @inheritdoc + */ + public function insert($table, $columns, &$params) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + $names = []; + $placeholders = []; + foreach ($columns as $name => $value) { + $names[] = $schema->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $placeholders[] = $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + if (empty($names) && $tableSchema !== null) { + $columns = !empty($tableSchema->primaryKey) ? $tableSchema->primaryKey : reset($tableSchema->columns)->name; + foreach ($columns as $name) { + $names[] = $schema->quoteColumnName($name); + $placeholders[] = 'DEFAULT'; + } + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $definition = $this->getColumnDefinition($table, $column); + $definition = trim(preg_replace("/COMMENT '(.*?)'/i", '', $definition)); + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' CHANGE ' . $this->db->quoteColumnName($column) + . ' ' . $this->db->quoteColumnName($column) + . (empty($definition) ? '' : ' ' . $definition) + . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return $this->addCommentOnColumn($table, $column, ''); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return $this->addCommentOnTable($table, ''); + } + + + /** + * Gets column definition. + * + * @param string $table table name + * @param string $column column name + * @return null|string the column definition + * @throws Exception in case when table does not contain column + */ + private function getColumnDefinition($table, $column) + { + $quotedTable = $this->db->quoteTableName($table); + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne(); + if ($row === false) { + throw new Exception("Unable to find column '$column' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $column) { + return $matches[2][$i]; + } + } + } + return null; + } } diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index c5e6679570..90aad9106a 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -43,7 +43,7 @@ class Schema extends \yii\db\Schema 'text' => self::TYPE_TEXT, 'varchar' => self::TYPE_STRING, 'string' => self::TYPE_STRING, - 'char' => self::TYPE_STRING, + 'char' => self::TYPE_CHAR, 'datetime' => self::TYPE_DATETIME, 'year' => self::TYPE_DATE, 'date' => self::TYPE_DATE, @@ -61,7 +61,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleTableName($name) { - return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + return strpos($name, '`') !== false ? $name : "`$name`"; } /** @@ -72,7 +72,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleColumnName($name) { - return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + return strpos($name, '`') !== false || $name === '*' ? $name : "`$name`"; } /** @@ -129,6 +129,7 @@ class Schema extends \yii\db\Schema { $column = $this->createColumnSchema(); +<<<<<<< HEAD <<<<<<< HEAD $column->name = $info['Field']; $column->allowNull = $info['Null'] === 'YES'; @@ -146,6 +147,15 @@ class Schema extends \yii\db\Schema $column->dbType = $info['type']; >>>>>>> yiichina/master +======= + $column->name = $info['field']; + $column->allowNull = $info['null'] === 'YES'; + $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false; + $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false; + $column->comment = $info['comment']; + + $column->dbType = $info['type']; +>>>>>>> master $column->unsigned = stripos($column->dbType, 'unsigned') !== false; $column->type = self::TYPE_STRING; @@ -183,12 +193,17 @@ class Schema extends \yii\db\Schema $column->phpType = $this->getColumnPhpType($column); if (!$column->isPrimaryKey) { +<<<<<<< HEAD <<<<<<< HEAD if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP') { +======= + if ($column->type === 'timestamp' && $info['default'] === 'CURRENT_TIMESTAMP') { +>>>>>>> master $column->defaultValue = new Expression('CURRENT_TIMESTAMP'); } elseif (isset($type) && $type === 'bit') { - $column->defaultValue = bindec(trim($info['Default'],'b\'')); + $column->defaultValue = bindec(trim($info['default'], 'b\'')); } else { +<<<<<<< HEAD $column->defaultValue = $column->phpTypecast($info['Default']); ======= if ($column->type === 'timestamp' && $info['default'] === 'CURRENT_TIMESTAMP') { @@ -198,6 +213,9 @@ class Schema extends \yii\db\Schema } else { $column->defaultValue = $column->phpTypecast($info['default']); >>>>>>> yiichina/master +======= + $column->defaultValue = $column->phpTypecast($info['default']); +>>>>>>> master } } @@ -226,11 +244,17 @@ class Schema extends \yii\db\Schema } foreach ($columns as $info) { <<<<<<< HEAD +<<<<<<< HEAD ======= if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) { $info = array_change_key_case($info, CASE_LOWER); } >>>>>>> yiichina/master +======= + if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) { + $info = array_change_key_case($info, CASE_LOWER); + } +>>>>>>> master $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; if ($column->isPrimaryKey) { @@ -268,18 +292,58 @@ class Schema extends \yii\db\Schema */ protected function findConstraints($table) { - $sql = $this->getCreateTableSql($table); + $sql = << $name) { - $constraint[$name] = $pks[$k]; + try { + $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll(); + $constraints = []; + foreach ($rows as $row) { + $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name']; + $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name']; + } + $table->foreignKeys = []; + foreach ($constraints as $constraint) { + $table->foreignKeys[] = array_merge( + [$constraint['referenced_table_name']], + $constraint['columns'] + ); + } + } catch (\Exception $e) { + $previous = $e->getPrevious(); + if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) { + throw $e; + } + + // table does not exist, try to determine the foreign keys using the table creation sql + $sql = $this->getCreateTableSql($table); + $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; + if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); + $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); + $constraint = [str_replace('`', '', $match[2])]; + foreach ($fks as $k => $name) { + $constraint[$name] = $pks[$k]; + } + $table->foreignKeys[md5(serialize($constraint))] = $constraint; } - $table->foreignKeys[] = $constraint; + $table->foreignKeys = array_values($table->foreignKeys); } } } @@ -288,12 +352,12 @@ class Schema extends \yii\db\Schema * Returns all unique indexes for the given table. * Each array element is of the following structure: * - * ~~~ + * ```php * [ - * 'IndexName1' => ['col1' [, ...]], - * 'IndexName2' => ['col2' [, ...]], + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], * ] - * ~~~ + * ``` * * @param TableSchema $table the table metadata * @return array all unique indexes for the given table. @@ -329,4 +393,12 @@ class Schema extends \yii\db\Schema return $this->db->createCommand($sql)->queryColumn(); } + + /** + * @inheritdoc + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length, $this->db); + } } diff --git a/framework/db/oci/ColumnSchemaBuilder.php b/framework/db/oci/ColumnSchemaBuilder.php new file mode 100644 index 0000000000..6893d4c0b7 --- /dev/null +++ b/framework/db/oci/ColumnSchemaBuilder.php @@ -0,0 +1,64 @@ + + * @author Chris Harris + * @since 2.0.6 + */ +class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder +{ + /** + * @inheritdoc + */ + protected function buildUnsignedString() + { + return $this->isUnsigned ? ' UNSIGNED' : ''; + } + + /** + * @inheritdoc + */ + protected function buildAfterString() + { + return $this->after !== null ? + ' AFTER ' . $this->db->quoteColumnName($this->after) : + ''; + } + + /** + * @inheritdoc + */ + protected function buildFirstString() + { + return $this->isFirst ? ' FIRST' : ''; + } + + /** + * @inheritdoc + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_PK: + $format = '{type}{length}{check}{pos}{append}'; + break; + case self::CATEGORY_NUMERIC: + $format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}{append}'; + break; + default: + $format = '{type}{length}{default}{notnull}{check}{pos}{append}'; + } + return $this->buildCompleteString($format); + } +} diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index 4554bad334..2f6014b076 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -10,6 +10,7 @@ namespace yii\db\oci; use yii\base\InvalidParamException; use yii\db\Connection; use yii\db\Exception; +use yii\db\Expression; /** * QueryBuilder is the query builder for Oracle databases. @@ -24,7 +25,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = [ Schema::TYPE_PK => 'NUMBER(10) NOT NULL PRIMARY KEY', + Schema::TYPE_UPK => 'NUMBER(10) UNSIGNED NOT NULL PRIMARY KEY', Schema::TYPE_BIGPK => 'NUMBER(20) NOT NULL PRIMARY KEY', + Schema::TYPE_UBIGPK => 'NUMBER(20) UNSIGNED NOT NULL PRIMARY KEY', + Schema::TYPE_CHAR => 'CHAR(1)', Schema::TYPE_STRING => 'VARCHAR2(255)', Schema::TYPE_TEXT => 'CLOB', Schema::TYPE_SMALLINT => 'NUMBER(5)', @@ -161,19 +165,69 @@ EOD; return $sql; } <<<<<<< HEAD +<<<<<<< HEAD ======= +======= + + /** + * @inheritdoc + */ + public function insert($table, $columns, &$params) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + $names = []; + $placeholders = []; + foreach ($columns as $name => $value) { + $names[] = $schema->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $placeholders[] = $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + if (empty($names) && $tableSchema !== null) { + $columns = !empty($tableSchema->primaryKey) ? $tableSchema->primaryKey : reset($tableSchema->columns)->name; + foreach ($columns as $name) { + $names[] = $schema->quoteColumnName($name); + $placeholders[] = 'DEFAULT'; + } + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); + } +>>>>>>> master /** * Generates a batch INSERT SQL statement. * For example, * +<<<<<<< HEAD * ~~~ +======= + * ```php +>>>>>>> master * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [ * ['Tom', 30], * ['Jane', 20], * ['Linda', 25], * ]); +<<<<<<< HEAD * ~~~ +======= + * ``` +>>>>>>> master * * Note that the values in each row must match the corresponding column names. * @@ -219,5 +273,35 @@ EOD; return 'INSERT ALL ' . $tableAndColumns . implode($tableAndColumns, $values) . ' SELECT 1 FROM SYS.DUAL'; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM DUAL'; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . " IS ''"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . " IS ''"; + } +>>>>>>> master } diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index 90d6fc1169..1d9e7f9f15 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -8,9 +8,10 @@ namespace yii\db\oci; use yii\base\InvalidCallException; -use yii\db\Connection; -use yii\db\TableSchema; use yii\db\ColumnSchema; +use yii\db\Connection; +use yii\db\Expression; +use yii\db\TableSchema; /** * Schema is the class for retrieving metadata from an Oracle database @@ -25,7 +26,10 @@ class Schema extends \yii\db\Schema { /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * @var array map of DB errors and corresponding exceptions * If left part is found in DB error message exception class from the right part is used. */ @@ -33,8 +37,13 @@ class Schema extends \yii\db\Schema 'ORA-00001: unique constraint' => 'yii\db\IntegrityException', ]; +<<<<<<< HEAD /** >>>>>>> yiichina/master +======= + + /** +>>>>>>> master * @inheritdoc */ public function init() @@ -69,6 +78,14 @@ class Schema extends \yii\db\Schema return new QueryBuilder($this->db); } + /** + * @inheritdoc + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length, $this->db); + } + /** * @inheritdoc */ @@ -113,6 +130,7 @@ class Schema extends \yii\db\Schema */ protected function findColumns($table) { +<<<<<<< HEAD <<<<<<< HEAD $schemaName = $table->schemaName; $tableName = $table->name; @@ -132,27 +150,31 @@ SELECT a.column_name, a.data_type || $sql = <<>>>>>> yiichina/master +======= + $sql = <<>>>>>> master a.nullable, a.data_default, - ( SELECT D.constraint_type - FROM ALL_CONS_COLUMNS C - inner join ALL_constraints D on D.OWNER = C.OWNER and D.constraint_name = C.constraint_name - WHERE C.OWNER = B.OWNER - and C.table_name = B.object_name - and C.column_name = A.column_name - and D.constraint_type = 'P') as Key, com.comments as column_comment FROM ALL_TAB_COLUMNS A inner join ALL_OBJECTS B ON b.owner = a.owner and ltrim(B.OBJECT_NAME) = ltrim(A.TABLE_NAME) LEFT JOIN all_col_comments com ON (A.owner = com.owner AND A.table_name = com.table_name AND A.column_name = com.column_name) WHERE +<<<<<<< HEAD <<<<<<< HEAD a.owner = '{$schemaName}' and (b.object_type = 'TABLE' or b.object_type = 'VIEW') and b.object_name = '{$tableName}' +======= + a.owner = :schemaName + and b.object_type IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') + and b.object_name = :tableName +>>>>>>> master ORDER by a.column_id -EOD; +SQL; try { +<<<<<<< HEAD $columns = $this->db->createCommand($sql)->queryAll(); ======= a.owner = :schemaName @@ -162,11 +184,16 @@ ORDER by a.column_id SQL; try { +======= +>>>>>>> master $columns = $this->db->createCommand($sql, [ ':tableName' => $table->name, ':schemaName' => $table->schemaName, ])->queryAll(); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } catch (\Exception $e) { return false; } @@ -177,6 +204,7 @@ SQL; foreach ($columns as $column) { <<<<<<< HEAD +<<<<<<< HEAD ======= if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { $column = array_change_key_case($column, CASE_UPPER); @@ -192,6 +220,13 @@ SQL; ======= >>>>>>> yiichina/master } +======= + if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { + $column = array_change_key_case($column, CASE_UPPER); + } + $c = $this->createColumn($column); + $table->columns[$c->name] = $c; +>>>>>>> master } return true; } @@ -199,6 +234,7 @@ SQL; /** * Sequence name of table * +<<<<<<< HEAD <<<<<<< HEAD * @param $tablename * @internal param \yii\db\TableSchema $table ->name the table schema @@ -218,6 +254,12 @@ SQL; * @internal param \yii\db\TableSchema $table ->name the table schema * @return string whether the sequence exists */ +======= + * @param string $tableName + * @internal param \yii\db\TableSchema $table->name the table schema + * @return string|null whether the sequence exists + */ +>>>>>>> master protected function getTableSequenceName($tableName) { @@ -231,7 +273,10 @@ AND ud.referenced_type='SEQUENCE' SQL; $sequenceName = $this->db->createCommand($seq_name_sql, [':tableName' => $tableName])->queryScalar(); return $sequenceName === false ? null : $sequenceName; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -248,9 +293,13 @@ SQL; if ($this->db->isActive) { // get the last insert id from the master connection <<<<<<< HEAD +<<<<<<< HEAD ======= $sequenceName = $this->quoteSimpleTableName($sequenceName); >>>>>>> yiichina/master +======= + $sequenceName = $this->quoteSimpleTableName($sequenceName); +>>>>>>> master return $this->db->useMaster(function (Connection $db) use ($sequenceName) { return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar(); }); @@ -270,8 +319,8 @@ SQL; $c = $this->createColumnSchema(); $c->name = $column['COLUMN_NAME']; $c->allowNull = $column['NULLABLE'] === 'Y'; - $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false; $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']; +<<<<<<< HEAD <<<<<<< HEAD $this->extractColumnType($c, $column['DATA_TYPE']); @@ -280,6 +329,11 @@ SQL; $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); >>>>>>> yiichina/master +======= + $c->isPrimaryKey = false; + $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); + $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); +>>>>>>> master $c->phpType = $this->getColumnPhpType($c); @@ -287,9 +341,12 @@ SQL; if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) { $c->defaultValue = null; } else { +<<<<<<< HEAD <<<<<<< HEAD $c->defaultValue = $c->phpTypecast($column['DATA_DEFAULT']); ======= +======= +>>>>>>> master $defaultValue = $column['DATA_DEFAULT']; if ($c->type === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') { $c->defaultValue = new Expression('CURRENT_TIMESTAMP'); @@ -305,7 +362,10 @@ SQL; } $c->defaultValue = $c->phpTypecast($defaultValue); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } } @@ -318,6 +378,7 @@ SQL; */ protected function findConstraints($table) { +<<<<<<< HEAD <<<<<<< HEAD $sql = <<db->createCommand($sql); +======= + $sql = <<db->createCommand($sql, [ + ':tableName' => $table->name, + ':schemaName' => $table->schemaName, + ]); + $constraints = []; +>>>>>>> master foreach ($command->queryAll() as $row) { - if ($row['CONSTRAINT_TYPE'] === 'R') { - $name = $row["COLUMN_NAME"]; - $table->foreignKeys[$name] = [$row["TABLE_REF"], $row["COLUMN_REF"]]; + if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { + $row = array_change_key_case($row, CASE_UPPER); } + + if ($row['CONSTRAINT_TYPE'] === 'P') { + $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true; + $table->primaryKey[] = $row['COLUMN_NAME']; + if (empty($table->sequenceName)) { + $table->sequenceName = $this->getTableSequenceName($table->name); + } + } + + if ($row['CONSTRAINT_TYPE'] !== 'R') { + // this condition is not checked in SQL WHERE because of an Oracle Bug: + // see https://github.com/yiisoft/yii2/pull/8844 + continue; + } +<<<<<<< HEAD ======= $sql = <<foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']); >>>>>>> yiichina/master +======= + + $name = $row['CONSTRAINT_NAME']; + if (!isset($constraints[$name])) { + $constraints[$name] = [ + 'tableName' => $row['TABLE_REF'], + 'columns' => [], + ]; + } + $constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF']; +>>>>>>> master } + foreach ($constraints as $constraint) { + $table->foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']); + } + } + + /** + * @inheritdoc + */ + protected function findSchemaNames() + { + $sql = <<db->createCommand($sql)->queryColumn(); } /** @@ -383,11 +508,17 @@ SQL; protected function findTableNames($schema = '') { if ($schema === '') { - $sql = <<db->createCommand($sql); } else { +<<<<<<< HEAD $sql = <<db->createCommand($sql); } else { +======= +>>>>>>> master $sql = <<db->createCommand($sql, [':schema' => $schema]); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } $rows = $command->queryAll(); $names = []; foreach ($rows as $row) { +<<<<<<< HEAD <<<<<<< HEAD $names[] = $row['TABLE_NAME']; } @@ -449,30 +586,79 @@ SQL; $names[] = $row['TABLE_NAME']; } >>>>>>> yiichina/master +======= + if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { + $row = array_change_key_case($row, CASE_UPPER); + } + $names[] = $row['TABLE_NAME']; + } +>>>>>>> master return $names; } /** <<<<<<< HEAD +<<<<<<< HEAD +======= + * Returns all unique indexes for the given table. + * Each array element is of the following structure: + * + * ```php + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ``` + * + * @param TableSchema $table the table metadata + * @return array all unique indexes for the given table. + * @since 2.0.4 + */ + public function findUniqueIndexes($table) + { + $query = <<db->createCommand($query, [ + ':tableName' => $table->name, + ':schemaName' => $table->schemaName, + ]); + foreach ($command->queryAll() as $row) { + $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME']; + } + return $result; + } + + /** +>>>>>>> master * Extracts the data types for the given column * @param ColumnSchema $column * @param string $dbType DB type + * @param string $precision total number of digits. + * This parameter is available since version 2.0.4. + * @param string $scale number of digits on the right of the decimal separator. + * This parameter is available since version 2.0.4. + * @param string $length length for character types. + * This parameter is available since version 2.0.4. */ - protected function extractColumnType($column, $dbType) + protected function extractColumnType($column, $dbType, $precision, $scale, $length) { $column->dbType = $dbType; - if (strpos($dbType, 'FLOAT') !== false) { + if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) { $column->type = 'double'; - } elseif (strpos($dbType, 'NUMBER') !== false || strpos($dbType, 'INTEGER') !== false) { - if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) { - $values = explode(',', $matches[1]); - if (isset($values[1]) && (((int) $values[1]) > 0)) { - $column->type = 'double'; - } else { - $column->type = 'integer'; - } + } elseif (strpos($dbType, 'NUMBER') !== false) { + if ($scale === null || $scale > 0) { + $column->type = 'decimal'; } else { +<<<<<<< HEAD $column->type = 'double'; ======= * Returns all unique indexes for the given table. @@ -534,16 +720,26 @@ SQL; } else { $column->type = 'integer'; >>>>>>> yiichina/master +======= + $column->type = 'integer'; +>>>>>>> master } + } elseif (strpos($dbType, 'INTEGER') !== false) { + $column->type = 'integer'; } elseif (strpos($dbType, 'BLOB') !== false) { $column->type = 'binary'; } elseif (strpos($dbType, 'CLOB') !== false) { $column->type = 'text'; <<<<<<< HEAD +<<<<<<< HEAD ======= } elseif (strpos($dbType, 'TIMESTAMP') !== false) { $column->type = 'timestamp'; >>>>>>> yiichina/master +======= + } elseif (strpos($dbType, 'TIMESTAMP') !== false) { + $column->type = 'timestamp'; +>>>>>>> master } else { $column->type = 'string'; } @@ -554,16 +750,69 @@ SQL; * @param ColumnSchema $column * @param string $dbType the column's DB type <<<<<<< HEAD +<<<<<<< HEAD +======= + * @param string $precision total number of digits. + * This parameter is available since version 2.0.4. + * @param string $scale number of digits on the right of the decimal separator. + * This parameter is available since version 2.0.4. + * @param string $length length for character types. + * This parameter is available since version 2.0.4. */ - protected function extractColumnSize($column, $dbType) + protected function extractColumnSize($column, $dbType, $precision, $scale, $length) { - if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) { - $values = explode(',', $matches[1]); - $column->size = $column->precision = (int) $values[0]; - if (isset($values[1])) { - $column->scale = (int) $values[1]; + $column->size = trim($length) === '' ? null : (int)$length; + $column->precision = trim($precision) === '' ? null : (int)$precision; + $column->scale = trim($scale) === '' ? null : (int)$scale; + } + + /** + * @inheritdoc +>>>>>>> master + */ + public function insert($table, $columns) + { + $params = []; + $returnParams = []; + $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); + $tableSchema = $this->getTableSchema($table); + $returnColumns = $tableSchema->primaryKey; + if (!empty($returnColumns)) { + $columnSchemas = $tableSchema->columns; + $returning = []; + foreach ((array)$returnColumns as $name) { + $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams)); + $returnParams[$phName] = [ + 'column' => $name, + 'value' => null, + ]; + if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->phpType !== 'integer') { + $returnParams[$phName]['dataType'] = \PDO::PARAM_STR; + } else { + $returnParams[$phName]['dataType'] = \PDO::PARAM_INT; + } + $returnParams[$phName]['size'] = isset($columnSchemas[$name]) && isset($columnSchemas[$name]->size) ? $columnSchemas[$name]->size : -1; + $returning[] = $this->quoteColumnName($name); } + $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams)); } + + $command = $this->db->createCommand($sql, $params); + $command->prepare(false); + + foreach ($returnParams as $name => &$value) { + $command->pdoStatement->bindParam($name, $value['value'], $value['dataType'], $value['size']); + } + + if (!$command->execute()) { + return false; + } + + $result = []; + foreach ($returnParams as $value) { + $result[$value['column']] = $value['value']; + } +<<<<<<< HEAD ======= * @param string $precision total number of digits. * This parameter is available since version 2.0.4. @@ -627,5 +876,9 @@ SQL; return $result; >>>>>>> yiichina/master +======= + + return $result; +>>>>>>> master } } diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php index e07c187ca1..7498ee2928 100644 --- a/framework/db/pgsql/QueryBuilder.php +++ b/framework/db/pgsql/QueryBuilder.php @@ -17,12 +17,41 @@ use yii\base\InvalidParamException; */ class QueryBuilder extends \yii\db\QueryBuilder { + /** + * Defines a UNIQUE index for [[createIndex()]]. + * @since 2.0.6 + */ + const INDEX_UNIQUE = 'unique'; + /** + * Defines a B-tree index for [[createIndex()]]. + * @since 2.0.6 + */ + const INDEX_B_TREE = 'btree'; + /** + * Defines a hash index for [[createIndex()]]. + * @since 2.0.6 + */ + const INDEX_HASH = 'hash'; + /** + * Defines a GiST index for [[createIndex()]]. + * @since 2.0.6 + */ + const INDEX_GIST = 'gist'; + /** + * Defines a GIN index for [[createIndex()]]. + * @since 2.0.6 + */ + const INDEX_GIN = 'gin'; + /** * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = [ Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', + Schema::TYPE_UPK => 'serial NOT NULL PRIMARY KEY', Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY', + Schema::TYPE_UBIGPK => 'bigserial NOT NULL PRIMARY KEY', + Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', @@ -65,6 +94,36 @@ class QueryBuilder extends \yii\db\QueryBuilder ]; + /** + * Builds a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, + * separate them with commas or use an array to represent them. Each column name will be properly quoted + * by the method, unless a parenthesis is found in the name. + * @param boolean|string $unique whether to make this a UNIQUE index constraint. You can pass `true` or [[INDEX_UNIQUE]] to create + * a unique index, `false` to make a non-unique index using the default index type, or one of the following constants to specify + * the index method to use: [[INDEX_B_TREE]], [[INDEX_HASH]], [[INDEX_GIST]], [[INDEX_GIN]]. + * @return string the SQL statement for creating a new index. + * @see http://www.postgresql.org/docs/8.2/static/sql-createindex.html + */ + public function createIndex($name, $table, $columns, $unique = false) + { + if ($unique === self::INDEX_UNIQUE || $unique === true) { + $index = false; + $unique = true; + } else { + $index = $unique; + $unique = false; + } + + return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') . + $this->db->quoteTableName($name) . ' ON ' . + $this->db->quoteTableName($table) . + ($index !== false ? " USING $index" : '') . + ' (' . $this->buildColumns($columns) . ')'; + } + /** * Builds a SQL statement for dropping an index. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. @@ -165,6 +224,42 @@ class QueryBuilder extends \yii\db\QueryBuilder . $this->db->quoteColumnName($column) . ' ' . $type; } + /** + * @inheritdoc + */ + public function insert($table, $columns, &$params) + { + return parent::insert($table, $this->normalizeTableRowData($table, $columns), $params); + } + + /** + * @inheritdoc + */ + public function update($table, $columns, $condition, &$params) + { + return parent::update($table, $this->normalizeTableRowData($table, $columns), $condition, $params); + } + + /** + * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary. + * @param string $table the table that data will be saved into. + * @param array $columns the column data (name => value) to be saved into the table. + * @return array normalized columns + * @since 2.0.9 + */ + private function normalizeTableRowData($table, $columns) + { + if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + foreach ($columns as $name => $value) { + if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) { + $columns[$name] = [$value, \PDO::PARAM_LOB]; // explicitly setup PDO param type for binary column + } + } + } + return $columns; + } + /** * @inheritdoc */ @@ -181,7 +276,7 @@ class QueryBuilder extends \yii\db\QueryBuilder foreach ($rows as $row) { $vs = []; foreach ($row as $i => $value) { - if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) { $value = $columnSchemas[$columns[$i]]->dbTypecast($value); } if (is_string($value)) { diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 85a36c415f..2c6e54f4c8 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -45,8 +45,9 @@ class Schema extends \yii\db\Schema 'polygon' => self::TYPE_STRING, 'path' => self::TYPE_STRING, - 'character' => self::TYPE_STRING, - 'char' => self::TYPE_STRING, + 'character' => self::TYPE_CHAR, + 'char' => self::TYPE_CHAR, + 'bpchar' => self::TYPE_CHAR, 'character varying' => self::TYPE_STRING, 'varchar' => self::TYPE_STRING, 'text' => self::TYPE_TEXT, @@ -104,7 +105,7 @@ class Schema extends \yii\db\Schema 'uuid' => self::TYPE_STRING, 'json' => self::TYPE_STRING, 'jsonb' => self::TYPE_STRING, - 'xml' => self::TYPE_STRING + 'xml' => self::TYPE_STRING, ]; @@ -168,7 +169,10 @@ class Schema extends \yii\db\Schema /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Returns all schema names in the database, including the default one but not system schemas. * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception. @@ -187,7 +191,10 @@ SQL; } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Returns all table names in the database. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. * @return array all table names in the database. The names have NO schema name prefix. @@ -197,6 +204,7 @@ SQL; if ($schema === '') { $schema = $this->defaultSchema; } +<<<<<<< HEAD <<<<<<< HEAD $sql = <<db->createCommand($sql); $command->bindParam(':schema', $schema); ======= +======= +>>>>>>> master $sql = <<db->createCommand($sql, [':schemaName' => $schema]); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master $rows = $command->queryAll(); $names = []; foreach ($rows as $row) { @@ -238,17 +251,26 @@ SQL; $sql = <<>>>>>> master fc.relname as foreign_table_name, fns.nspname as foreign_table_schema, - (select string_agg(attname,',') attname from pg_attribute where attrelid=ct.confrelid and attnum = any(ct.confkey)) as foreign_columns + fa.attname as foreign_column_name from - pg_constraint ct + (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s + FROM pg_constraint ct + ) AS ct inner join pg_class c on c.oid=ct.conrelid inner join pg_namespace ns on c.relnamespace=ns.oid + inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s] left join pg_class fc on fc.oid=ct.confrelid left join pg_namespace fns on fc.relnamespace=fns.oid +<<<<<<< HEAD ======= ct.conname as constraint_name, @@ -267,11 +289,15 @@ from left join pg_namespace fns on fc.relnamespace=fns.oid left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s] >>>>>>> yiichina/master +======= + left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s] +>>>>>>> master where ct.contype='f' and c.relname={$tableName} and ns.nspname={$tableSchema} <<<<<<< HEAD +<<<<<<< HEAD SQL; $constraints = $this->db->createCommand($sql)->queryAll(); @@ -286,11 +312,20 @@ SQL; $constraints = []; foreach ($this->db->createCommand($sql)->queryAll() as $constraint) { >>>>>>> yiichina/master +======= +order by + fns.nspname, fc.relname, a.attnum +SQL; + + $constraints = []; + foreach ($this->db->createCommand($sql)->queryAll() as $constraint) { +>>>>>>> master if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; } else { $foreignTable = $constraint['foreign_table_name']; } +<<<<<<< HEAD <<<<<<< HEAD $citem = [$foreignTable]; foreach ($columns as $idx => $column) { @@ -305,45 +340,55 @@ SQL; 'columns' => [], ]; } +======= + $name = $constraint['constraint_name']; + if (!isset($constraints[$name])) { + $constraints[$name] = [ + 'tableName' => $foreignTable, + 'columns' => [], + ]; + } +>>>>>>> master $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name']; } foreach ($constraints as $constraint) { $table->foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } } /** * Gets information about given table unique indexes. * @param TableSchema $table the table metadata +<<<<<<< HEAD <<<<<<< HEAD * @return array with index names, columns and if it is an expression tree +======= + * @return array with index and column names +>>>>>>> master */ protected function getUniqueIndexInformation($table) { - $tableName = $this->quoteValue($table->name); - $tableSchema = $this->quoteValue($table->schemaName); - $sql = <<db->createCommand($sql)->queryAll(); ======= * @return array with index and column names @@ -366,29 +411,35 @@ AND c.relname = :tableName AND ns.nspname = :schemaName ORDER BY i.relname, k SQL; +======= +>>>>>>> master return $this->db->createCommand($sql, [ ':schemaName' => $table->schemaName, ':tableName' => $table->name, ])->queryAll(); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** * Returns all unique indexes for the given table. * Each array element is of the following structure: * - * ~~~ + * ```php * [ - * 'IndexName1' => ['col1' [, ...]], - * 'IndexName2' => ['col2' [, ...]], + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], * ] - * ~~~ + * ``` * * @param TableSchema $table the table metadata * @return array all unique indexes for the given table. */ public function findUniqueIndexes($table) { +<<<<<<< HEAD <<<<<<< HEAD $indexes = $this->getUniqueIndexInformation($table); $uniqueIndexes = []; @@ -412,6 +463,13 @@ SQL; foreach ($rows as $row) { $uniqueIndexes[$row['indexname']][] = $row['columnname']; >>>>>>> yiichina/master +======= + $uniqueIndexes = []; + + $rows = $this->getUniqueIndexInformation($table); + foreach ($rows as $row) { + $uniqueIndexes[$row['indexname']][] = $row['columnname']; +>>>>>>> master } return $uniqueIndexes; @@ -503,8 +561,12 @@ SQL; $column->defaultValue = bindec(trim($column->defaultValue, 'B\'')); } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) { $column->defaultValue = $matches[1]; - } elseif (preg_match("/^(.*?)::/", $column->defaultValue, $matches)) { - $column->defaultValue = $column->phpTypecast($matches[1]); + } elseif (preg_match('/^(.*?)::/', $column->defaultValue, $matches)) { + if ($matches[1] === 'NULL') { + $column->defaultValue = null; + } else { + $column->defaultValue = $column->phpTypecast($matches[1]); + } } else { $column->defaultValue = $column->phpTypecast($column->defaultValue); } @@ -544,7 +606,10 @@ SQL; return $column; } <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master /** * @inheritdoc @@ -556,7 +621,11 @@ SQL; $returnColumns = $this->getTableSchema($table)->primaryKey; if (!empty($returnColumns)) { $returning = []; +<<<<<<< HEAD foreach ((array)$returnColumns as $name) { +======= + foreach ((array) $returnColumns as $name) { +>>>>>>> master $returning[] = $this->quoteColumnName($name); } $sql .= ' RETURNING ' . implode(', ', $returning); @@ -568,5 +637,8 @@ SQL; return !$command->pdoStatement->rowCount() ? false : $result; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } diff --git a/framework/db/sqlite/ColumnSchemaBuilder.php b/framework/db/sqlite/ColumnSchemaBuilder.php new file mode 100644 index 0000000000..8ba74568c4 --- /dev/null +++ b/framework/db/sqlite/ColumnSchemaBuilder.php @@ -0,0 +1,46 @@ + + * @since 2.0.8 + */ +class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder +{ + /** + * @inheritdoc + */ + protected function buildUnsignedString() + { + return $this->isUnsigned ? ' UNSIGNED' : ''; + } + + /** + * @inheritdoc + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_PK: + $format = '{type}{check}{append}'; + break; + case self::CATEGORY_NUMERIC: + $format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}{append}'; + break; + default: + $format = '{type}{length}{notnull}{unique}{check}{default}{append}'; + } + + return $this->buildCompleteString($format); + } +} diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 0581371a63..405601f92b 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -11,6 +11,8 @@ use yii\db\Connection; use yii\db\Exception; use yii\base\InvalidParamException; use yii\base\NotSupportedException; +use yii\db\Expression; +use yii\db\Query; /** * QueryBuilder is the query builder for SQLite databases. @@ -25,7 +27,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = [ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_UPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_UBIGPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', @@ -48,13 +53,13 @@ class QueryBuilder extends \yii\db\QueryBuilder * Generates a batch INSERT SQL statement. * For example, * - * ~~~ + * ```php * $connection->createCommand()->batchInsert('user', ['name', 'age'], [ * ['Tom', 30], * ['Jane', 20], * ['Linda', 25], * ])->execute(); - * ~~~ + * ``` * * Note that the values in each row must match the corresponding column names. * @@ -162,7 +167,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function truncateTable($table) { - return "DELETE FROM " . $this->db->quoteTableName($table); + return 'DELETE FROM ' . $this->db->quoteTableName($table); } /** @@ -233,6 +238,18 @@ class QueryBuilder extends \yii\db\QueryBuilder throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); } + /** + * Builds a SQL statement for renaming a DB table. + * + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($table, $newName) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName); + } + /** * Builds a SQL statement for changing the definition of a column. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. @@ -274,6 +291,46 @@ class QueryBuilder extends \yii\db\QueryBuilder throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); } + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + /** * @inheritdoc */ @@ -295,13 +352,8 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** - * Builds SQL for IN condition - * - * @param string $operator - * @param array $columns - * @param array $values - * @param array $params - * @return string SQL + * @inheritdoc + * @throws NotSupportedException if `$columns` is an array */ protected function buildSubqueryInCondition($operator, $columns, $values, &$params) { @@ -343,4 +395,71 @@ class QueryBuilder extends \yii\db\QueryBuilder return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')'; } + + /** + * @inheritdoc + */ + public function build($query, $params = []) + { + $query = $query->prepare($this); + + $params = empty($params) ? $query->params : array_merge($params, $query->params); + + $clauses = [ + $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), + $this->buildFrom($query->from, $params), + $this->buildJoin($query->join, $params), + $this->buildWhere($query->where, $params), + $this->buildGroupBy($query->groupBy), + $this->buildHaving($query->having, $params), + ]; + + $sql = implode($this->separator, array_filter($clauses)); + $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset); + + if (!empty($query->orderBy)) { + foreach ($query->orderBy as $expression) { + if ($expression instanceof Expression) { + $params = array_merge($params, $expression->params); + } + } + } + if (!empty($query->groupBy)) { + foreach ($query->groupBy as $expression) { + if ($expression instanceof Expression) { + $params = array_merge($params, $expression->params); + } + } + } + + $union = $this->buildUnion($query->union, $params); + if ($union !== '') { + $sql = "$sql{$this->separator}$union"; + } + + return [$sql, $params]; + } + + /** + * @inheritdoc + */ + public function buildUnion($unions, &$params) + { + if (empty($unions)) { + return ''; + } + + $result = ''; + + foreach ($unions as $i => $union) { + $query = $union['query']; + if ($query instanceof Query) { + list($unions[$i]['query'], $params) = $this->build($query, $params); + } + + $result .= ' UNION ' . ($union['all'] ? 'ALL ' : '') . ' ' . $unions[$i]['query']; + } + + return trim($result); + } } diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php index d466413764..bd50b4cd51 100644 --- a/framework/db/sqlite/Schema.php +++ b/framework/db/sqlite/Schema.php @@ -48,7 +48,7 @@ class Schema extends \yii\db\Schema 'text' => self::TYPE_TEXT, 'varchar' => self::TYPE_STRING, 'string' => self::TYPE_STRING, - 'char' => self::TYPE_STRING, + 'char' => self::TYPE_CHAR, 'blob' => self::TYPE_BINARY, 'datetime' => self::TYPE_DATETIME, 'year' => self::TYPE_DATE, @@ -67,7 +67,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleTableName($name) { - return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + return strpos($name, '`') !== false ? $name : "`$name`"; } /** @@ -78,7 +78,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleColumnName($name) { - return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + return strpos($name, '`') !== false || $name === '*' ? $name : "`$name`"; } /** @@ -91,6 +91,15 @@ class Schema extends \yii\db\Schema return new QueryBuilder($this->db); } + /** + * @inheritdoc + * @return ColumnSchemaBuilder column schema builder instance + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length); + } + /** * Returns all table names in the database. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. @@ -98,11 +107,15 @@ class Schema extends \yii\db\Schema */ protected function findTableNames($schema = '') { +<<<<<<< HEAD <<<<<<< HEAD $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; ======= $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name"; >>>>>>> yiichina/master +======= + $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name"; +>>>>>>> master return $this->db->createCommand($sql)->queryColumn(); } @@ -134,7 +147,7 @@ class Schema extends \yii\db\Schema */ protected function findColumns($table) { - $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')'; + $sql = 'PRAGMA table_info(' . $this->quoteSimpleTableName($table->name) . ')'; $columns = $this->db->createCommand($sql)->queryAll(); if (empty($columns)) { return false; @@ -161,7 +174,7 @@ class Schema extends \yii\db\Schema */ protected function findConstraints($table) { - $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')'; + $sql = 'PRAGMA foreign_key_list(' . $this->quoteSimpleTableName($table->name) . ')'; $keys = $this->db->createCommand($sql)->queryAll(); foreach ($keys as $key) { $id = (int) $key['id']; @@ -178,25 +191,25 @@ class Schema extends \yii\db\Schema * Returns all unique indexes for the given table. * Each array element is of the following structure: * - * ~~~ + * ```php * [ - * 'IndexName1' => ['col1' [, ...]], - * 'IndexName2' => ['col2' [, ...]], + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], * ] - * ~~~ + * ``` * * @param TableSchema $table the table metadata * @return array all unique indexes for the given table. */ public function findUniqueIndexes($table) { - $sql = "PRAGMA index_list(" . $this->quoteSimpleTableName($table->name) . ')'; + $sql = 'PRAGMA index_list(' . $this->quoteSimpleTableName($table->name) . ')'; $indexes = $this->db->createCommand($sql)->queryAll(); $uniqueIndexes = []; foreach ($indexes as $index) { $indexName = $index['name']; - $indexInfo = $this->db->createCommand("PRAGMA index_info(" . $this->quoteValue($index['name']) . ")")->queryAll(); + $indexInfo = $this->db->createCommand('PRAGMA index_info(' . $this->quoteValue($index['name']) . ')')->queryAll(); if ($index['unique']) { $uniqueIndexes[$indexName] = []; @@ -274,14 +287,13 @@ class Schema extends \yii\db\Schema */ public function setTransactionIsolationLevel($level) { - switch($level) - { + switch ($level) { case Transaction::SERIALIZABLE: - $this->db->createCommand("PRAGMA read_uncommitted = False;")->execute(); - break; + $this->db->createCommand('PRAGMA read_uncommitted = False;')->execute(); + break; case Transaction::READ_UNCOMMITTED: - $this->db->createCommand("PRAGMA read_uncommitted = True;")->execute(); - break; + $this->db->createCommand('PRAGMA read_uncommitted = True;')->execute(); + break; default: throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.'); } diff --git a/framework/di/Container.php b/framework/di/Container.php index 04287df646..31a8c1e193 100644 --- a/framework/di/Container.php +++ b/framework/di/Container.php @@ -8,8 +8,10 @@ namespace yii\di; use ReflectionClass; +use Yii; use yii\base\Component; use yii\base\InvalidConfigException; +use yii\helpers\ArrayHelper; /** * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container. @@ -171,7 +173,7 @@ class Container extends Component } elseif (is_object($definition)) { return $this->_singletons[$class] = $definition; } else { - throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition)); + throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition)); } if (array_key_exists($class, $this->_singletons)) { @@ -230,11 +232,15 @@ class Container extends Component * You may use [[has()]] to check if a class definition already exists. * * @param string $class class name, interface name or alias name +<<<<<<< HEAD <<<<<<< HEAD * @param mixed $definition the definition associated with `$class`. It can be one of the followings: ======= * @param mixed $definition the definition associated with `$class`. It can be one of the following: >>>>>>> yiichina/master +======= + * @param mixed $definition the definition associated with `$class`. It can be one of the following: +>>>>>>> master * * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable * should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor @@ -246,7 +252,7 @@ class Container extends Component * - a string: a class name, an interface name or an alias name. * @param array $params the list of constructor parameters. The parameters will be passed to the class * constructor when [[get()]] is called. - * @return static the container itself + * @return $this the container itself */ public function set($class, $definition = [], array $params = []) { @@ -266,7 +272,7 @@ class Container extends Component * @param mixed $definition the definition associated with `$class`. See [[set()]] for more details. * @param array $params the list of constructor parameters. The parameters will be passed to the class * constructor when [[get()]] is called. - * @return static the container itself + * @return $this the container itself * @see set() */ public function setSingleton($class, $definition = [], array $params = []) @@ -458,4 +464,95 @@ class Container extends Component } return $dependencies; } + + /** + * Invoke a callback with resolving dependencies in parameters. + * + * This methods allows invoking a callback and let type hinted parameter names to be + * resolved as objects of the Container. It additionally allow calling function using named parameters. + * + * For example, the following callback may be invoked using the Container to resolve the formatter dependency: + * + * ```php + * $formatString = function($string, \yii\i18n\Formatter $formatter) { + * // ... + * } + * Yii::$container->invoke($formatString, ['string' => 'Hello World!']); + * ``` + * + * This will pass the string `'Hello World!'` as the first param, and a formatter instance created + * by the DI container as the second param to the callable. + * + * @param callable $callback callable to be invoked. + * @param array $params The array of parameters for the function. + * This can be either a list of parameters, or an associative array representing named function parameters. + * @return mixed the callback return value. + * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. + * @since 2.0.7 + */ + public function invoke(callable $callback, $params = []) + { + if (is_callable($callback)) { + return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params)); + } else { + return call_user_func_array($callback, $params); + } + } + + /** + * Resolve dependencies for a function. + * + * This method can be used to implement similar functionality as provided by [[invoke()]] in other + * components. + * + * @param callable $callback callable to be invoked. + * @param array $params The array of parameters for the function, can be either numeric or associative. + * @return array The resolved dependencies. + * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. + * @since 2.0.7 + */ + public function resolveCallableDependencies(callable $callback, $params = []) + { + if (is_array($callback)) { + $reflection = new \ReflectionMethod($callback[0], $callback[1]); + } else { + $reflection = new \ReflectionFunction($callback); + } + + $args = []; + + $associative = ArrayHelper::isAssociative($params); + + foreach ($reflection->getParameters() as $param) { + $name = $param->getName(); + if (($class = $param->getClass()) !== null) { + $className = $class->getName(); + if ($associative && isset($params[$name]) && $params[$name] instanceof $className) { + $args[] = $params[$name]; + unset($params[$name]); + } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) { + $args[] = array_shift($params); + } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { + $args[] = $obj; + } else { + $args[] = $this->get($className); + } + } elseif ($associative && isset($params[$name])) { + $args[] = $params[$name]; + unset($params[$name]); + } elseif (!$associative && count($params)) { + $args[] = array_shift($params); + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } elseif (!$param->isOptional()) { + $funcName = $reflection->getName(); + throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\"."); + } + } + + foreach ($params as $value) { + $args[] = $value; + } + return $args; + } } diff --git a/framework/di/Instance.php b/framework/di/Instance.php index 4e335db7ec..f51e0f24f8 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -107,9 +107,7 @@ class Instance */ public static function ensure($reference, $type = null, $container = null) { - if ($reference instanceof $type) { - return $reference; - } elseif (is_array($reference)) { + if (is_array($reference)) { $class = isset($reference['class']) ? $reference['class'] : $type; if (!$container instanceof Container) { $container = Yii::$container; @@ -122,11 +120,13 @@ class Instance if (is_string($reference)) { $reference = new static($reference); + } elseif ($type === null || $reference instanceof $type) { + return $reference; } if ($reference instanceof self) { $component = $reference->get($container); - if ($component instanceof $type || $type === null) { + if ($type === null || $component instanceof $type) { return $component; } else { throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected."); diff --git a/framework/di/ServiceLocator.php b/framework/di/ServiceLocator.php index 622278c2ef..b3bc0755e6 100644 --- a/framework/di/ServiceLocator.php +++ b/framework/di/ServiceLocator.php @@ -170,11 +170,15 @@ class ServiceLocator extends Component * * @param string $id component ID (e.g. `db`). * @param mixed $definition the component definition to be registered with this locator. +<<<<<<< HEAD <<<<<<< HEAD * It can be one of the followings: ======= * It can be one of the following: >>>>>>> yiichina/master +======= + * It can be one of the following: +>>>>>>> master * * - a class name * - a configuration array: the array contains name-value pairs that will be used to diff --git a/framework/filters/AccessControl.php b/framework/filters/AccessControl.php index e60c13178c..5c1ce9ad30 100644 --- a/framework/filters/AccessControl.php +++ b/framework/filters/AccessControl.php @@ -26,7 +26,7 @@ use yii\web\ForbiddenHttpException; * For example, the following declarations will allow authenticated users to access the "create" * and "update" actions and deny all other users from accessing these two actions. * - * ~~~ + * ```php * public function behaviors() * { * return [ @@ -49,7 +49,7 @@ use yii\web\ForbiddenHttpException; * ], * ]; * } - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -67,9 +67,9 @@ class AccessControl extends ActionFilter * * The signature of the callback should be as follows: * - * ~~~ + * ```php * function ($rule, $action) - * ~~~ + * ``` * * where `$rule` is the rule that denies the user, and `$action` is the current [[Action|action]] object. * `$rule` can be `null` if access is denied because none of the rules matched. @@ -120,7 +120,7 @@ class AccessControl extends ActionFilter } elseif ($allow === false) { if (isset($rule->denyCallback)) { call_user_func($rule->denyCallback, $rule, $action); - } elseif (isset($this->denyCallback)) { + } elseif ($this->denyCallback !== null) { call_user_func($this->denyCallback, $rule, $action); } else { $this->denyAccess($user); @@ -128,7 +128,7 @@ class AccessControl extends ActionFilter return false; } } - if (isset($this->denyCallback)) { + if ($this->denyCallback !== null) { call_user_func($this->denyCallback, null, $action); } else { $this->denyAccess($user); diff --git a/framework/filters/AccessRule.php b/framework/filters/AccessRule.php index cd68359ba3..e60f95af12 100644 --- a/framework/filters/AccessRule.php +++ b/framework/filters/AccessRule.php @@ -31,8 +31,8 @@ class AccessRule extends Component */ public $actions; /** - * @var array list of controller IDs that this rule applies to. The comparison is case-sensitive. - * If not set or empty, it means this rule applies to all controllers. + * @var array list of the controller IDs that this rule applies to. Each controller ID is prefixed with the module ID (if any). + * The comparison is case-sensitive. If not set or empty, it means this rule applies to all controllers. */ public $controllers; /** @@ -67,9 +67,9 @@ class AccessRule extends Component * @var callable a callback that will be called to determine if the rule should be applied. * The signature of the callback should be as follows: * - * ~~~ + * ```php * function ($rule, $action) - * ~~~ + * ``` * * where `$rule` is this rule, and `$action` is the current [[Action|action]] object. * The callback should return a boolean value indicating whether this rule should be applied. @@ -82,9 +82,9 @@ class AccessRule extends Component * * The signature of the callback should be as follows: * - * ~~~ + * ```php * function ($rule, $action) - * ~~~ + * ``` * * where `$rule` is this rule, and `$action` is the current [[Action|action]] object. */ diff --git a/framework/filters/ContentNegotiator.php b/framework/filters/ContentNegotiator.php index 9a34b0f7eb..7d2bfc5d21 100644 --- a/framework/filters/ContentNegotiator.php +++ b/framework/filters/ContentNegotiator.php @@ -220,7 +220,7 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface return $this->languages[$language]; } foreach ($this->languages as $key => $supported) { - if (is_integer($key) && $this->isLanguageSupported($language, $supported)) { + if (is_int($key) && $this->isLanguageSupported($language, $supported)) { return $supported; } } @@ -232,7 +232,7 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface return $this->languages[$language]; } foreach ($this->languages as $key => $supported) { - if (is_integer($key) && $this->isLanguageSupported($language, $supported)) { + if (is_int($key) && $this->isLanguageSupported($language, $supported)) { return $supported; } } diff --git a/framework/filters/HttpCache.php b/framework/filters/HttpCache.php index 86f5a84f50..8df842f78b 100644 --- a/framework/filters/HttpCache.php +++ b/framework/filters/HttpCache.php @@ -12,7 +12,7 @@ use yii\base\ActionFilter; use yii\base\Action; /** - * HttpCache implements client-side caching by utilizing the `Last-Modified` and `Etag` HTTP headers. + * HttpCache implements client-side caching by utilizing the `Last-Modified` and `ETag` HTTP headers. * * It is an action filter that can be added to a controller and handles the `beforeAction` event. * @@ -20,7 +20,7 @@ use yii\base\Action; * In the following example the filter will be applied to the `list`-action and * the Last-Modified header will contain the date of the last update to the user table in the database. * - * ~~~ + * ```php * public function behaviors() * { * return [ @@ -32,12 +32,12 @@ use yii\base\Action; * return $q->from('user')->max('updated_at'); * }, * // 'etagSeed' => function ($action, $params) { - * // return // generate etag seed here + * // return // generate ETag seed here * // } * ], * ]; * } - * ~~~ + * ``` * * @author Da:Sourcerer * @author Qiang Xue @@ -49,33 +49,45 @@ class HttpCache extends ActionFilter * @var callable a PHP callback that returns the UNIX timestamp of the last modification time. * The callback's signature should be: * - * ~~~ + * ```php * function ($action, $params) - * ~~~ + * ``` * * where `$action` is the [[Action]] object that this filter is currently handling; * `$params` takes the value of [[params]]. The callback should return a UNIX timestamp. + * + * @see http://tools.ietf.org/html/rfc7232#section-2.2 */ public $lastModified; /** - * @var callable a PHP callback that generates the Etag seed string. + * @var callable a PHP callback that generates the ETag seed string. * The callback's signature should be: * - * ~~~ + * ```php * function ($action, $params) - * ~~~ + * ``` * * where `$action` is the [[Action]] object that this filter is currently handling; * `$params` takes the value of [[params]]. The callback should return a string serving - * as the seed for generating an Etag. + * as the seed for generating an ETag. */ public $etagSeed; + /** + * @var bool whether to generate weak ETags. + * + * Weak ETags should be used if the content should be considered semantically equivalent, but not byte-equal. + * + * @since 2.0.8 + * @see http://tools.ietf.org/html/rfc7232#section-2.3 + */ + public $weakEtag = false; /** * @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks. */ public $params; /** * @var string the value of the `Cache-Control` HTTP header. If null, the header will not be sent. + * @see http://tools.ietf.org/html/rfc2616#section-14.9 */ public $cacheControlHeader = 'public, max-age=3600'; /** @@ -152,7 +164,7 @@ class HttpCache extends ActionFilter if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { // HTTP_IF_NONE_MATCH takes precedence over HTTP_IF_MODIFIED_SINCE // http://tools.ietf.org/html/rfc7232#section-3.3 - return $etag !== null && in_array($etag, Yii::$app->request->getEtags(), true); + return $etag !== null && in_array($etag, Yii::$app->request->getETags(), true); } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { return $lastModified !== null && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified; } else { @@ -185,12 +197,13 @@ class HttpCache extends ActionFilter } /** - * Generates an Etag from the given seed string. + * Generates an ETag from the given seed string. * @param string $seed Seed for the ETag - * @return string the generated Etag + * @return string the generated ETag */ protected function generateEtag($seed) { - return '"' . rtrim(base64_encode(sha1($seed, true)), '=') . '"'; + $etag = '"' . rtrim(base64_encode(sha1($seed, true)), '=') . '"'; + return $this->weakEtag ? 'W/' . $etag : $etag; } } diff --git a/framework/filters/PageCache.php b/framework/filters/PageCache.php index 1b2630cc85..2631b482cb 100644 --- a/framework/filters/PageCache.php +++ b/framework/filters/PageCache.php @@ -25,7 +25,7 @@ use yii\web\Response; * cache the whole page for maximum 60 seconds or until the count of entries in the post table changes. * It also stores different versions of the page depending on the application language. * - * ~~~ + * ```php * public function behaviors() * { * return [ @@ -43,7 +43,7 @@ use yii\web\Response; * ], * ]; * } - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -72,15 +72,18 @@ class PageCache extends ActionFilter * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. * For example, * - * ~~~ + * ```php * [ * 'class' => 'yii\caching\DbDependency', * 'sql' => 'SELECT MAX(updated_at) FROM post', * ] - * ~~~ + * ``` * - * would make the output cache depends on the last modified time of all posts. + * would make the output cache depend on the last modified time of all posts. * If any post has its modification time changed, the cached content would be invalidated. + * + * If [[cacheCookies]] or [[cacheHeaders]] is enabled, then [[\yii\caching\Dependency::reusable]] should be enabled as well to save performance. + * This is because the cookies and headers are currently stored separately from the actual page content, causing the dependency to be evaluated twice. */ public $dependency; /** @@ -89,11 +92,11 @@ class PageCache extends ActionFilter * The following variation setting will cause the content to be cached in different versions * according to the current application language: * - * ~~~ + * ```php * [ * Yii::$app->language, * ] - * ~~~ + * ``` */ public $variations; /** @@ -107,7 +110,10 @@ class PageCache extends ActionFilter */ public $view; <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master /** * @var boolean|array a boolean value indicating whether to cache all cookies, or an array of * cookie names indicating which cookies can be cached. Be very careful with caching cookies, because @@ -122,7 +128,10 @@ class PageCache extends ActionFilter * @since 2.0.4 */ public $cacheHeaders = true; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master /** @@ -150,6 +159,10 @@ class PageCache extends ActionFilter $this->cache = Instance::ensure($this->cache, Cache::className()); + if (is_array($this->dependency)) { + $this->dependency = Yii::createObject($this->dependency); + } + $properties = []; foreach (['cache', 'duration', 'dependency', 'variations'] as $name) { $properties[$name] = $this->$name; @@ -192,6 +205,7 @@ class PageCache extends ActionFilter $response->statusText = $data['statusText']; } if (isset($data['headers']) && is_array($data['headers'])) { +<<<<<<< HEAD <<<<<<< HEAD $response->getHeaders()->fromArray($data['headers']); } @@ -205,6 +219,14 @@ class PageCache extends ActionFilter $cookies = $response->getCookies()->toArray(); $response->getCookies()->fromArray(array_merge($data['cookies'], $cookies)); >>>>>>> yiichina/master +======= + $headers = $response->getHeaders()->toArray(); + $response->getHeaders()->fromArray(array_merge($data['headers'], $headers)); + } + if (isset($data['cookies']) && is_array($data['cookies'])) { + $cookies = $response->getCookies()->toArray(); + $response->getCookies()->fromArray(array_merge($data['cookies'], $cookies)); +>>>>>>> master } } @@ -221,12 +243,16 @@ class PageCache extends ActionFilter 'version' => $response->version, 'statusCode' => $response->statusCode, 'statusText' => $response->statusText, +<<<<<<< HEAD <<<<<<< HEAD 'headers' => $response->getHeaders()->toArray(), 'cookies' => $response->getCookies()->toArray(), ]; ======= ]; +======= + ]; +>>>>>>> master if (!empty($this->cacheHeaders)) { $headers = $response->getHeaders()->toArray(); if (is_array($this->cacheHeaders)) { @@ -254,8 +280,12 @@ class PageCache extends ActionFilter } $data['cookies'] = $cookies; } +<<<<<<< HEAD >>>>>>> yiichina/master $this->cache->set($this->calculateCacheKey(), $data); +======= + $this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency); +>>>>>>> master echo ob_get_clean(); } diff --git a/framework/filters/RateLimitInterface.php b/framework/filters/RateLimitInterface.php index 1236a43d0e..ebcce73086 100644 --- a/framework/filters/RateLimitInterface.php +++ b/framework/filters/RateLimitInterface.php @@ -23,6 +23,7 @@ interface RateLimitInterface * and the second element is the size of the window in seconds. */ public function getRateLimit($request, $action); + /** * Loads the number of allowed requests and the corresponding timestamp from a persistent storage. * @param \yii\web\Request $request the current request @@ -31,6 +32,7 @@ interface RateLimitInterface * and the second element is the corresponding UNIX timestamp. */ public function loadAllowance($request, $action); + /** * Saves the number of allowed requests and the corresponding timestamp to a persistent storage. * @param \yii\web\Request $request the current request diff --git a/framework/filters/RateLimiter.php b/framework/filters/RateLimiter.php index 82ac9de51a..d55ca78767 100644 --- a/framework/filters/RateLimiter.php +++ b/framework/filters/RateLimiter.php @@ -67,7 +67,7 @@ class RateLimiter extends ActionFilter */ public function beforeAction($action) { - $user = $this->user ? : Yii::$app->getUser()->getIdentity(false); + $user = $this->user ? : (Yii::$app->getUser() ? Yii::$app->getUser()->getIdentity(false) : null); if ($user instanceof RateLimitInterface) { Yii::trace('Check rate limit', __METHOD__); $this->checkRateLimit( diff --git a/framework/filters/VerbFilter.php b/framework/filters/VerbFilter.php index b2cdb1077a..4acda1e970 100644 --- a/framework/filters/VerbFilter.php +++ b/framework/filters/VerbFilter.php @@ -23,7 +23,7 @@ use yii\web\MethodNotAllowedHttpException; * For example, the following declarations will define a typical set of allowed * request methods for REST CRUD actions. * - * ~~~ + * ```php * public function behaviors() * { * return [ @@ -39,7 +39,7 @@ use yii\web\MethodNotAllowedHttpException; * ], * ]; * } - * ~~~ + * ``` * * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 * @author Carsten Brandt @@ -54,19 +54,19 @@ class VerbFilter extends Behavior * allowed methods (e.g. GET, HEAD, PUT) as the value. * If an action is not listed all request methods are considered allowed. * - * You can use '*' to stand for all actions. When an action is explicitly - * specified, it takes precedence over the specification given by '*'. + * You can use `'*'` to stand for all actions. When an action is explicitly + * specified, it takes precedence over the specification given by `'*'`. * * For example, * - * ~~~ + * ```php * [ * 'create' => ['get', 'post'], * 'update' => ['get', 'put', 'post'], * 'delete' => ['post', 'delete'], * '*' => ['get'], * ] - * ~~~ + * ``` */ public $actions = []; diff --git a/framework/filters/auth/AuthInterface.php b/framework/filters/auth/AuthInterface.php index 66235a4cd1..f12518cb3d 100644 --- a/framework/filters/auth/AuthInterface.php +++ b/framework/filters/auth/AuthInterface.php @@ -30,12 +30,14 @@ interface AuthInterface * @throws UnauthorizedHttpException if authentication information is provided but is invalid. */ public function authenticate($user, $request, $response); + /** * Generates challenges upon authentication failure. * For example, some appropriate HTTP headers may be generated. * @param Response $response */ public function challenge($response); + /** * Handles authentication failure. * The implementation should normally throw UnauthorizedHttpException to indicate authentication failure. diff --git a/framework/filters/auth/AuthMethod.php b/framework/filters/auth/AuthMethod.php index 1ec8a60cde..798c2a4660 100644 --- a/framework/filters/auth/AuthMethod.php +++ b/framework/filters/auth/AuthMethod.php @@ -8,6 +8,7 @@ namespace yii\filters\auth; use Yii; +use yii\base\Action; use yii\base\ActionFilter; use yii\web\UnauthorizedHttpException; use yii\web\User; @@ -34,6 +35,14 @@ abstract class AuthMethod extends ActionFilter implements AuthInterface * @var Response the response to be sent. If not set, the `response` application component will be used. */ public $response; + /** + * @var array list of action IDs that this filter will be applied to, but auth failure will not lead to error. + * It may be used for actions, that are allowed for public, but return some additional data for authenticated users. + * Defaults to empty, meaning authentication is not optional for any action. + * @see isOptional + * @since 2.0.7 + */ + public $optional = []; /** @@ -43,13 +52,21 @@ abstract class AuthMethod extends ActionFilter implements AuthInterface { $response = $this->response ? : Yii::$app->getResponse(); - $identity = $this->authenticate( - $this->user ? : Yii::$app->getUser(), - $this->request ? : Yii::$app->getRequest(), - $response - ); + try { + $identity = $this->authenticate( + $this->user ? : Yii::$app->getUser(), + $this->request ? : Yii::$app->getRequest(), + $response + ); + } catch (UnauthorizedHttpException $e) { + if ($this->isOptional($action)) { + return true; + } - if ($identity !== null) { + throw $e; + } + + if ($identity !== null || $this->isOptional($action)) { return true; } else { $this->challenge($response); @@ -72,4 +89,18 @@ abstract class AuthMethod extends ActionFilter implements AuthInterface { throw new UnauthorizedHttpException('You are requesting with an invalid credential.'); } + + /** + * Checks, whether authentication is optional for the given action. + * + * @param Action $action + * @return boolean + * @see optional + * @since 2.0.7 + */ + protected function isOptional($action) + { + $id = $this->getActionId($action); + return in_array($id, $this->optional, true); + } } diff --git a/framework/filters/auth/CompositeAuth.php b/framework/filters/auth/CompositeAuth.php index faf6802189..7fc39609df 100644 --- a/framework/filters/auth/CompositeAuth.php +++ b/framework/filters/auth/CompositeAuth.php @@ -63,17 +63,24 @@ class CompositeAuth extends AuthMethod public function authenticate($user, $request, $response) { foreach ($this->authMethods as $i => $auth) { +<<<<<<< HEAD <<<<<<< HEAD $this->authMethods[$i] = $auth = Yii::createObject($auth); if (!$auth instanceof AuthInterface) { throw new InvalidConfigException(get_class($auth) . ' must implement yii\filters\auth\AuthInterface'); ======= if (!$auth instanceof AuthInterface) { +======= + if (!$auth instanceof AuthInterface) { +>>>>>>> master $this->authMethods[$i] = $auth = Yii::createObject($auth); if (!$auth instanceof AuthInterface) { throw new InvalidConfigException(get_class($auth) . ' must implement yii\filters\auth\AuthInterface'); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } $identity = $auth->authenticate($user, $request, $response); diff --git a/framework/filters/auth/HttpBearerAuth.php b/framework/filters/auth/HttpBearerAuth.php index 0aece20ae5..9155c94596 100644 --- a/framework/filters/auth/HttpBearerAuth.php +++ b/framework/filters/auth/HttpBearerAuth.php @@ -40,7 +40,7 @@ class HttpBearerAuth extends AuthMethod public function authenticate($user, $request, $response) { $authHeader = $request->getHeaders()->get('Authorization'); - if ($authHeader !== null && preg_match("/^Bearer\\s+(.*?)$/", $authHeader, $matches)) { + if ($authHeader !== null && preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) { $identity = $user->loginByAccessToken($matches[1], get_class($this)); if ($identity === null) { $this->handleFailure($response); diff --git a/framework/grid/ActionColumn.php b/framework/grid/ActionColumn.php index 462503bddc..389134ee27 100644 --- a/framework/grid/ActionColumn.php +++ b/framework/grid/ActionColumn.php @@ -32,6 +32,10 @@ use yii\helpers\Url; */ class ActionColumn extends Column { + /** + * @inheritdoc + */ + public $headerOptions = ['class' => 'action-column']; /** * @var string the ID of the controller that should handle the actions specified here. * If not set, it will use the currently active controller. This property is mainly used by @@ -48,7 +52,7 @@ class ActionColumn extends Column * * As an example, to only have the view, and update button you can add the ActionColumn to your GridView columns as follows: * - * ``` + * ```php * ['class' => 'yii\grid\ActionColumn', 'template' => '{view} {update}'], * ``` * @@ -76,11 +80,32 @@ class ActionColumn extends Column * [ * 'update' => function ($url, $model, $key) { * return $model->status === 'editable' ? Html::a('Update', $url) : ''; - * }; + * }, * ], * ``` */ public $buttons = []; + /** @var array visibility conditions for each button. The array keys are the button names (without curly brackets), + * and the values are the boolean true/false or the anonymous function. When the button name is not specified in + * this array it will be shown by default. + * The callbacks must use the following signature: + * + * ```php + * function ($model, $key, $index) { + * return $model->status === 'editable'; + * } + * ``` + * + * Or you can pass a boolean value: + * + * ```php + * [ + * 'update' => \Yii::$app->user->can('update'), + * ], + * ``` + * @since 2.0.7 + */ + public $visibleButtons = []; /** * @var callable a callback that creates a button URL using the specified model information. * The signature of the callback should be the same as that of [[createUrl()]]. @@ -110,10 +135,16 @@ class ActionColumn extends Column { if (!isset($this->buttons['view'])) { $this->buttons['view'] = function ($url, $model, $key) { +<<<<<<< HEAD <<<<<<< HEAD return Html::a('', $url, array_merge([ +======= + $options = array_merge([ +>>>>>>> master 'title' => Yii::t('yii', 'View'), + 'aria-label' => Yii::t('yii', 'View'), 'data-pjax' => '0', +<<<<<<< HEAD ], $this->buttonOptions)); ======= $options = array_merge([ @@ -123,14 +154,24 @@ class ActionColumn extends Column ], $this->buttonOptions); return Html::a('', $url, $options); >>>>>>> yiichina/master +======= + ], $this->buttonOptions); + return Html::a('', $url, $options); +>>>>>>> master }; } if (!isset($this->buttons['update'])) { $this->buttons['update'] = function ($url, $model, $key) { +<<<<<<< HEAD <<<<<<< HEAD return Html::a('', $url, array_merge([ +======= + $options = array_merge([ +>>>>>>> master 'title' => Yii::t('yii', 'Update'), + 'aria-label' => Yii::t('yii', 'Update'), 'data-pjax' => '0', +<<<<<<< HEAD ], $this->buttonOptions)); ======= $options = array_merge([ @@ -140,16 +181,26 @@ class ActionColumn extends Column ], $this->buttonOptions); return Html::a('', $url, $options); >>>>>>> yiichina/master +======= + ], $this->buttonOptions); + return Html::a('', $url, $options); +>>>>>>> master }; } if (!isset($this->buttons['delete'])) { $this->buttons['delete'] = function ($url, $model, $key) { +<<<<<<< HEAD <<<<<<< HEAD return Html::a('', $url, array_merge([ +======= + $options = array_merge([ +>>>>>>> master 'title' => Yii::t('yii', 'Delete'), + 'aria-label' => Yii::t('yii', 'Delete'), 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'), 'data-method' => 'post', 'data-pjax' => '0', +<<<<<<< HEAD ], $this->buttonOptions)); ======= $options = array_merge([ @@ -161,6 +212,10 @@ class ActionColumn extends Column ], $this->buttonOptions); return Html::a('', $url, $options); >>>>>>> yiichina/master +======= + ], $this->buttonOptions); + return Html::a('', $url, $options); +>>>>>>> master }; } } @@ -176,7 +231,7 @@ class ActionColumn extends Column */ public function createUrl($action, $model, $key, $index) { - if ($this->urlCreator instanceof Closure) { + if (is_callable($this->urlCreator)) { return call_user_func($this->urlCreator, $action, $model, $key, $index); } else { $params = is_array($key) ? $key : ['id' => (string) $key]; @@ -193,9 +248,17 @@ class ActionColumn extends Column { return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($model, $key, $index) { $name = $matches[1]; - if (isset($this->buttons[$name])) { - $url = $this->createUrl($name, $model, $key, $index); + if (isset($this->visibleButtons[$name])) { + $isVisible = $this->visibleButtons[$name] instanceof \Closure + ? call_user_func($this->visibleButtons[$name], $model, $key, $index) + : $this->visibleButtons[$name]; + } else { + $isVisible = true; + } + + if ($isVisible && isset($this->buttons[$name])) { + $url = $this->createUrl($name, $model, $key, $index); return call_user_func($this->buttons[$name], $url, $model, $key); } else { return ''; diff --git a/framework/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php index d5ee00eb57..9b5a17d38d 100644 --- a/framework/grid/CheckboxColumn.php +++ b/framework/grid/CheckboxColumn.php @@ -10,6 +10,7 @@ namespace yii\grid; use Closure; use yii\base\InvalidConfigException; use yii\helpers\Html; +use yii\helpers\Json; /** * CheckboxColumn displays a column of checkboxes in a grid view. @@ -54,7 +55,7 @@ class CheckboxColumn extends Column * you can use this option in the following way (in this example using the `name` attribute of the model): * * ```php - * 'checkboxOptions' => function($model, $key, $index, $column) { + * 'checkboxOptions' => function ($model, $key, $index, $column) { * return ['value' => $model->name]; * } * ``` @@ -81,6 +82,8 @@ class CheckboxColumn extends Column if (substr_compare($this->name, '[]', -2, 2)) { $this->name .= '[]'; } + + $this->registerClientScript(); } /** @@ -91,19 +94,10 @@ class CheckboxColumn extends Column */ protected function renderHeaderCellContent() { - $name = rtrim($this->name, '[]') . '_all'; - $id = $this->grid->options['id']; - $options = json_encode([ - 'name' => $this->name, - 'multiple' => $this->multiple, - 'checkAll' => $name, - ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); - if ($this->header !== null || !$this->multiple) { return parent::renderHeaderCellContent(); } else { - return Html::checkBox($name, false, ['class' => 'select-on-check-all']); + return Html::checkbox($this->getHeaderCheckBoxName(), false, ['class' => 'select-on-check-all']); } } @@ -116,11 +110,47 @@ class CheckboxColumn extends Column $options = call_user_func($this->checkboxOptions, $model, $key, $index, $this); } else { $options = $this->checkboxOptions; - if (!isset($options['value'])) { - $options['value'] = is_array($key) ? json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $key; - } + } + + if (!isset($options['value'])) { + $options['value'] = is_array($key) ? Json::encode($key) : $key; } return Html::checkbox($this->name, !empty($options['checked']), $options); } + + /** + * Returns header checkbox name + * @return string header checkbox name + * @since 2.0.8 + */ + protected function getHeaderCheckBoxName() + { + $name = $this->name; + if (substr_compare($name, '[]', -2, 2) === 0) { + $name = substr($name, 0, -2); + } + if (substr_compare($name, ']', -1, 1) === 0) { + $name = substr($name, 0, -1) . '_all]'; + } else { + $name .= '_all'; + } + + return $name; + } + + /** + * Registers the needed JavaScript + * @since 2.0.8 + */ + public function registerClientScript() + { + $id = $this->grid->options['id']; + $options = Json::encode([ + 'name' => $this->name, + 'multiple' => $this->multiple, + 'checkAll' => $this->grid->showHeader ? $this->getHeaderCheckBoxName() : null, + ]); + $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); + } } diff --git a/framework/grid/Column.php b/framework/grid/Column.php index 4c6ef3b13b..603d656b02 100644 --- a/framework/grid/Column.php +++ b/framework/grid/Column.php @@ -32,11 +32,15 @@ class Column extends Object */ public $footer; /** +<<<<<<< HEAD <<<<<<< HEAD * @var callable This is a callable that will be used to generated the content of each cell. ======= * @var callable This is a callable that will be used to generate the content of each cell. >>>>>>> yiichina/master +======= + * @var callable This is a callable that will be used to generate the content of each cell. +>>>>>>> master * The signature of the function should be the following: `function ($model, $key, $index, $column)`. * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered * and `$column` is a reference to the [[Column]] object. @@ -128,7 +132,18 @@ class Column extends Object */ protected function renderHeaderCellContent() { - return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell; + return trim($this->header) !== '' ? $this->header : $this->getHeaderCellLabel(); + } + + /** + * Returns header cell label. + * This method may be overridden to customize the label of the header cell. + * @return string label + * @since 2.0.8 + */ + protected function getHeaderCellLabel() + { + return $this->grid->emptyCell; } /** diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index 255717ff9f..c1f51da510 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -9,6 +9,7 @@ namespace yii\grid; use yii\base\Model; use yii\data\ActiveDataProvider; +use yii\data\ArrayDataProvider; use yii\db\ActiveQueryInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -90,7 +91,7 @@ class DataColumn extends Column */ public $sortLinkOptions = []; /** - * @var string|array|boolean the HTML code representing a filter input (e.g. a text field, a dropdown list) + * @var string|array|null|false the HTML code representing a filter input (e.g. a text field, a dropdown list) * that is used for this data column. This property is effective only when [[GridView::filterModel]] is set. * * - If this property is not set, a text field will be generated as the filter input; @@ -117,6 +118,25 @@ class DataColumn extends Column return parent::renderHeaderCellContent(); } + $label = $this->getHeaderCellLabel(); + if ($this->encodeLabel) { + $label = Html::encode($label); + } + + if ($this->attribute !== null && $this->enableSorting && + ($sort = $this->grid->dataProvider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { + return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => $label])); + } else { + return $label; + } + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + protected function getHeaderCellLabel() + { $provider = $this->grid->dataProvider; if ($this->label === null) { @@ -124,6 +144,12 @@ class DataColumn extends Column /* @var $model Model */ $model = new $provider->query->modelClass; $label = $model->getAttributeLabel($this->attribute); + } elseif ($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { + /* @var $model Model */ + $model = new $provider->modelClass; + $label = $model->getAttributeLabel($this->attribute); + } elseif ($this->grid->filterModel !== null && $this->grid->filterModel instanceof Model) { + $label = $this->grid->filterModel->getAttributeLabel($this->attribute); } else { $models = $provider->getModels(); if (($model = reset($models)) instanceof Model) { @@ -137,12 +163,7 @@ class DataColumn extends Column $label = $this->label; } - if ($this->attribute !== null && $this->enableSorting && - ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { - return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => ($this->encodeLabel ? Html::encode($label) : $label)])); - } else { - return $this->encodeLabel ? Html::encode($label) : $label; - } + return $label; } /** diff --git a/framework/grid/GridView.php b/framework/grid/GridView.php index 3460a2c082..75708d6e21 100644 --- a/framework/grid/GridView.php +++ b/framework/grid/GridView.php @@ -185,7 +185,13 @@ class GridView extends BaseListView */ public $columns = []; /** - * @var string the HTML display when the content of a cell is empty + * @var string the HTML display when the content of a cell is empty. + * This property is used to render cells that have no defined content, + * e.g. empty footer or filter cells. + * + * Note that this is not used by the [[DataColumn]] if a data item is `null`. In that case + * the [[\yii\i18n\Formatter::nullDisplay|nullDisplay]] property of the [[formatter]] will + * be used to indicate an empty data value. */ public $emptyCell = ' '; /** @@ -254,7 +260,7 @@ class GridView extends BaseListView public function init() { parent::init(); - if ($this->formatter == null) { + if ($this->formatter === null) { $this->formatter = Yii::$app->getFormatter(); } elseif (is_array($this->formatter)) { $this->formatter = Yii::createObject($this->formatter); @@ -275,11 +281,15 @@ class GridView extends BaseListView public function run() { $id = $this->options['id']; +<<<<<<< HEAD <<<<<<< HEAD $options = Json::encode($this->getClientOptions()); ======= $options = Json::htmlEncode($this->getClientOptions()); >>>>>>> yiichina/master +======= + $options = Json::htmlEncode($this->getClientOptions()); +>>>>>>> master $view = $this->getView(); GridViewAsset::register($view); $view->registerJs("jQuery('#$id').yiiGridView($options);"); @@ -305,7 +315,7 @@ class GridView extends BaseListView public function renderSection($name) { switch ($name) { - case "{errors}": + case '{errors}': return $this->renderErrors(); default: return parent::renderSection($name); @@ -354,7 +364,7 @@ class GridView extends BaseListView /** * Renders the caption element. - * @return bool|string the rendered caption element or `false` if no caption element should be rendered. + * @return boolean|string the rendered caption element or `false` if no caption element should be rendered. */ public function renderCaption() { @@ -367,7 +377,7 @@ class GridView extends BaseListView /** * Renders the column group HTML. - * @return bool|string the column group HTML or `false` if no column group should be rendered. + * @return boolean|string the column group HTML or `false` if no column group should be rendered. */ public function renderColumnGroup() { @@ -403,9 +413,9 @@ class GridView extends BaseListView $cells[] = $column->renderHeaderCell(); } $content = Html::tag('tr', implode('', $cells), $this->headerRowOptions); - if ($this->filterPosition == self::FILTER_POS_HEADER) { + if ($this->filterPosition === self::FILTER_POS_HEADER) { $content = $this->renderFilters() . $content; - } elseif ($this->filterPosition == self::FILTER_POS_BODY) { + } elseif ($this->filterPosition === self::FILTER_POS_BODY) { $content .= $this->renderFilters(); } @@ -424,7 +434,7 @@ class GridView extends BaseListView $cells[] = $column->renderFooterCell(); } $content = Html::tag('tr', implode('', $cells), $this->footerRowOptions); - if ($this->filterPosition == self::FILTER_POS_FOOTER) { + if ($this->filterPosition === self::FILTER_POS_FOOTER) { $content .= $this->renderFilters(); } @@ -567,7 +577,7 @@ class GridView extends BaseListView $model = reset($models); if (is_array($model) || is_object($model)) { foreach ($model as $name => $value) { - $this->columns[] = $name; + $this->columns[] = (string) $name; } } } diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index d24bb6451a..5a8ee0c7e3 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -23,11 +23,11 @@ class BaseArrayHelper { /** * Converts an object or an array of objects into an array. - * @param object|array $object the object to be converted into an array + * @param object|array|string $object the object to be converted into an array * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. * The properties specified for each class is an array of the following format: * - * ~~~ + * ```php * [ * 'app\models\Post' => [ * 'id', @@ -40,18 +40,18 @@ class BaseArrayHelper * }, * ], * ] - * ~~~ + * ``` * * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: * - * ~~~ + * ```php * [ * 'id' => 123, * 'title' => 'test', * 'createTime' => '2013-01-01 12:00AM', * 'length' => 301, * ] - * ~~~ + * ``` * * @param boolean $recursive whether to recursively converts properties which are objects into arrays. * @return array the array representation of the object @@ -85,7 +85,7 @@ class BaseArrayHelper } } if ($object instanceof Arrayable) { - $result = $object->toArray(); + $result = $object->toArray([], [], $recursive); } else { $result = []; foreach ($object as $key => $value) { @@ -93,7 +93,7 @@ class BaseArrayHelper } } - return $recursive ? static::toArray($result) : $result; + return $recursive ? static::toArray($result, $properties) : $result; } else { return [$object]; } @@ -119,7 +119,7 @@ class BaseArrayHelper while (!empty($args)) { $next = array_shift($args); foreach ($next as $k => $v) { - if (is_integer($k)) { + if (is_int($k)) { if (isset($res[$k])) { $res[] = $v; } else { @@ -145,16 +145,21 @@ class BaseArrayHelper * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']` * or `$array->x` is neither an array nor an object, the default value will be returned. * Note that if the array already has an element `x.y.z`, then its value will be returned +<<<<<<< HEAD <<<<<<< HEAD * instead of going through the sub-arrays. ======= * instead of going through the sub-arrays. So it is better to be done specifying an array of key names * like `['x', 'y', 'z']`. >>>>>>> yiichina/master +======= + * instead of going through the sub-arrays. So it is better to be done specifying an array of key names + * like `['x', 'y', 'z']`. +>>>>>>> master * * Below are some usage examples, * - * ~~~ + * ```php * // working with array * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); * // working with object @@ -165,13 +170,20 @@ class BaseArrayHelper * }); * // using dot format to retrieve the property of embedded object * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street'); +<<<<<<< HEAD <<<<<<< HEAD * ~~~ +======= + * // using an array of keys to retrieve the value + * $value = \yii\helpers\ArrayHelper::getValue($versions, ['1.0', 'date']); + * ``` +>>>>>>> master * * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, + * @param string|\Closure|array $key key name of the array element, an array of keys or property name of the object, * or an anonymous function returning the value. The anonymous function signature should be: * `function($array, $defaultValue)`. +<<<<<<< HEAD ======= * // using an array of keys to retrieve the value * $value = \yii\helpers\ArrayHelper::getValue($versions, ['1.0', 'date']); @@ -183,6 +195,9 @@ class BaseArrayHelper * `function($array, $defaultValue)`. * The possibility to pass an array of keys is available since version 2.0.4. >>>>>>> yiichina/master +======= + * The possibility to pass an array of keys is available since version 2.0.4. +>>>>>>> master * @param mixed $default the default value to be returned if the specified array key does not exist. Not used when * getting value from an object. * @return mixed the value of the element if found, default value otherwise @@ -194,8 +209,11 @@ class BaseArrayHelper return $key($array, $default); } +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> master if (is_array($key)) { $lastKey = array_pop($key); foreach ($key as $keyPart) { @@ -204,7 +222,10 @@ class BaseArrayHelper $key = $lastKey; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master if (is_array($array) && array_key_exists($key, $array)) { return $array[$key]; } @@ -215,6 +236,8 @@ class BaseArrayHelper } if (is_object($array)) { + // this is expected to fail if the property does not exist, or __get() is not implemented + // it is not reliably possible to check whether a property is accessable beforehand return $array->$key; } elseif (is_array($array)) { return array_key_exists($key, $array) ? $array[$key] : $default; @@ -229,13 +252,13 @@ class BaseArrayHelper * * Usage examples, * - * ~~~ + * ```php * // $array = ['type' => 'A', 'options' => [1, 2]]; * // working with array * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); * // $array content * // $array = ['options' => [1, 2]]; - * ~~~ + * ``` * * @param array $array the array to extract value from * @param string $key key name of the array element @@ -255,44 +278,132 @@ class BaseArrayHelper } /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. + * Indexes and/or groups the array according to a specified key. + * The input should be either multidimensional array or an array of objects. * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. + * The $key can be either a key name of the sub-array, a property name of object, or an anonymous + * function that must return the value that will be used as a key. * - * If a key value is null, the corresponding array element will be discarded and not put in the result. + * $groups is an array of keys, that will be used to group the input array into one or more sub-arrays based + * on keys specified. * - * For example, + * If the `$key` is specified as `null` or a value of an element corresponding to the key is `null` in addition + * to `$groups` not specified then the element is discarded. * - * ~~~ + * For example: + * + * ```php * $array = [ - * ['id' => '123', 'data' => 'abc'], - * ['id' => '345', 'data' => 'def'], + * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + * ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], * ]; * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // [ - * // '123' => ['id' => '123', 'data' => 'abc'], - * // '345' => ['id' => '345', 'data' => 'def'], - * // ] + * ``` * - * // using anonymous function + * The result will be an associative array, where the key is the value of `id` attribute + * + * ```php + * [ + * '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + * '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + * // The second element of an original array is overwritten by the last element because of the same id + * ] + * ``` + * + * An anonymous function can be used in the grouping array as well. + * + * ```php * $result = ArrayHelper::index($array, function ($element) { * return $element['id']; * }); - * ~~~ + * ``` * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array + * Passing `id` as a third argument will group `$array` by `id`: + * + * ```php + * $result = ArrayHelper::index($array, null, 'id'); + * ``` + * + * The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level + * and indexed by `data` on the third level: + * + * ```php + * [ + * '123' => [ + * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + * ], + * '345' => [ // all elements with this index are present in the result array + * ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + * ] + * ] + * ``` + * + * The anonymous function can be used in the array of grouping keys as well: + * + * ```php + * $result = ArrayHelper::index($array, 'data', [function ($element) { + * return $element['id']; + * }, 'device']); + * ``` + * + * The result will be a multidimensional array grouped by `id` on the first level, by the `device` on the second one + * and indexed by the `data` on the third level: + * + * ```php + * [ + * '123' => [ + * 'laptop' => [ + * 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + * ] + * ], + * '345' => [ + * 'tablet' => [ + * 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + * ], + * 'smartphone' => [ + * 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + * ] + * ] + * ] + * ``` + * + * @param array $array the array that needs to be indexed or grouped + * @param string|\Closure|null $key the column name or anonymous function which result will be used to index the array + * @param string|string[]|\Closure[]|null $groups the array of keys, that will be used to group the input array + * by one or more keys. If the $key attribute or its value for the particular element is null and $groups is not + * defined, the array element will be discarded. Otherwise, if $groups is specified, array element will be added + * to the result array without any key. This parameter is available since version 2.0.8. + * @return array the indexed and/or grouped array */ - public static function index($array, $key) + public static function index($array, $key, $groups = []) { $result = []; + $groups = (array)$groups; + foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; + $lastArray = &$result; + + foreach ($groups as $group) { + $value = static::getValue($element, $group); + if (!array_key_exists($value, $lastArray)) { + $lastArray[$value] = []; + } + $lastArray = &$lastArray[$value]; + } + + if ($key === null) { + if (!empty($groups)) { + $lastArray[] = $element; + } + } else { + $value = static::getValue($element, $key); + if ($value !== null) { + $lastArray[$value] = $element; + } + } + unset($lastArray); } return $result; @@ -304,7 +415,7 @@ class BaseArrayHelper * * For example, * - * ~~~ + * ```php * $array = [ * ['id' => '123', 'data' => 'abc'], * ['id' => '345', 'data' => 'def'], @@ -316,7 +427,7 @@ class BaseArrayHelper * $result = ArrayHelper::getColumn($array, function ($element) { * return $element['id']; * }); - * ~~~ + * ``` * * @param array $array * @param string|\Closure $name @@ -347,7 +458,7 @@ class BaseArrayHelper * * For example, * - * ~~~ + * ```php * $array = [ * ['id' => '123', 'name' => 'aaa', 'class' => 'x'], * ['id' => '124', 'name' => 'bbb', 'class' => 'x'], @@ -373,7 +484,7 @@ class BaseArrayHelper * // '345' => 'ccc', * // ], * // ] - * ~~~ + * ``` * * @param array $array * @param string|\Closure $from @@ -461,6 +572,13 @@ class BaseArrayHelper $args[] = $direction[$i]; $args[] = $flag; } + + // This fix is used for cases when main sorting specified by columns has equal values + // Without it it will lead to Fatal Error: Nesting level too deep - recursive dependency? + $args[] = range(1, count($array)); + $args[] = SORT_ASC; + $args[] = SORT_NUMERIC; + $args[] = &$array; call_user_func_array('array_multisort', $args); } @@ -481,15 +599,15 @@ class BaseArrayHelper public static function htmlEncode($data, $valuesOnly = true, $charset = null) { if ($charset === null) { - $charset = Yii::$app->charset; + $charset = Yii::$app ? Yii::$app->charset : 'UTF-8'; } $d = []; foreach ($data as $key => $value) { if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); + $key = htmlspecialchars($key, ENT_QUOTES | ENT_SUBSTITUTE, $charset); } if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + $d[$key] = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset); } elseif (is_array($value)) { $d[$key] = static::htmlEncode($value, $valuesOnly, $charset); } else { @@ -593,11 +711,82 @@ class BaseArrayHelper return array_keys($array) === range(0, count($array) - 1); } else { foreach ($array as $key => $value) { - if (!is_integer($key)) { + if (!is_int($key)) { return false; } } return true; } } + + /** + * Check whether an array or [[\Traversable]] contains an element. + * + * This method does the same as the PHP function [in_array()](http://php.net/manual/en/function.in-array.php) + * but additionally works for objects that implement the [[\Traversable]] interface. + * @param mixed $needle The value to look for. + * @param array|\Traversable $haystack The set of values to search. + * @param boolean $strict Whether to enable strict (`===`) comparison. + * @return boolean `true` if `$needle` was found in `$haystack`, `false` otherwise. + * @throws InvalidParamException if `$haystack` is neither traversable nor an array. + * @see http://php.net/manual/en/function.in-array.php + * @since 2.0.7 + */ + public static function isIn($needle, $haystack, $strict = false) + { + if ($haystack instanceof \Traversable) { + foreach ($haystack as $value) { + if ($needle == $value && (!$strict || $needle === $value)) { + return true; + } + } + } elseif (is_array($haystack)) { + return in_array($needle, $haystack, $strict); + } else { + throw new InvalidParamException('Argument $haystack must be an array or implement Traversable'); + } + + return false; + } + + /** + * Checks whether a variable is an array or [[\Traversable]]. + * + * This method does the same as the PHP function [is_array()](http://php.net/manual/en/function.is-array.php) + * but additionally works on objects that implement the [[\Traversable]] interface. + * @param mixed $var The variable being evaluated. + * @return boolean whether $var is array-like + * @see http://php.net/manual/en/function.is_array.php + * @since 2.0.8 + */ + public static function isTraversable($var) + { + return is_array($var) || $var instanceof \Traversable; + } + + /** + * Checks whether an array or [[\Traversable]] is a subset of another array or [[\Traversable]]. + * + * This method will return `true`, if all elements of `$needles` are contained in + * `$haystack`. If at least one element is missing, `false` will be returned. + * @param array|\Traversable $needles The values that must **all** be in `$haystack`. + * @param array|\Traversable $haystack The set of value to search. + * @param boolean $strict Whether to enable strict (`===`) comparison. + * @throws InvalidParamException if `$haystack` or `$needles` is neither traversable nor an array. + * @return boolean `true` if `$needles` is a subset of `$haystack`, `false` otherwise. + * @since 2.0.7 + */ + public static function isSubset($needles, $haystack, $strict = false) + { + if (is_array($needles) || $needles instanceof \Traversable) { + foreach ($needles as $needle) { + if (!static::isIn($needle, $haystack, $strict)) { + return false; + } + } + return true; + } else { + throw new InvalidParamException('Argument $needles must be an array or implement Traversable'); + } + } } diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php index 80a154d5ef..7e65bae584 100644 --- a/framework/helpers/BaseConsole.php +++ b/framework/helpers/BaseConsole.php @@ -7,7 +7,7 @@ namespace yii\helpers; -use yii\console\Markdown; +use yii\console\Markdown as ConsoleMarkdown; /** * BaseConsole provides concrete implementation for [[Console]]. @@ -19,6 +19,7 @@ use yii\console\Markdown; */ class BaseConsole { + // foreground color control codes const FG_BLACK = 30; const FG_RED = 31; const FG_GREEN = 32; @@ -27,7 +28,7 @@ class BaseConsole const FG_PURPLE = 35; const FG_CYAN = 36; const FG_GREY = 37; - + // background color control codes const BG_BLACK = 40; const BG_RED = 41; const BG_GREEN = 42; @@ -36,7 +37,7 @@ class BaseConsole const BG_PURPLE = 45; const BG_CYAN = 46; const BG_GREY = 47; - + // fonts style control codes const RESET = 0; const NORMAL = 0; const BOLD = 1; @@ -130,7 +131,7 @@ class BaseConsole */ public static function scrollUp($lines = 1) { - echo "\033[" . (int) $lines . "S"; + echo "\033[" . (int) $lines . 'S'; } /** @@ -140,7 +141,7 @@ class BaseConsole */ public static function scrollDown($lines = 1) { - echo "\033[" . (int) $lines . "T"; + echo "\033[" . (int) $lines . 'T'; } /** @@ -287,7 +288,7 @@ class BaseConsole { $code = implode(';', $format); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + return "\033[0m" . ($code !== '' ? "\033[" . $code . 'm' : '') . $string . "\033[0m"; } /** @@ -334,7 +335,8 @@ class BaseConsole * @param string $string the string to measure * @return integer the length of the string not counting ANSI format characters */ - public static function ansiStrlen($string) { + public static function ansiStrlen($string) + { return mb_strlen(static::stripAnsiFormat($string)); } @@ -401,7 +403,7 @@ class BaseConsole } $return = ''; - while($reset && $tags > 0) { + while ($reset && $tags > 0) { $return .= ''; $tags--; } @@ -433,7 +435,7 @@ class BaseConsole } $styleString = ''; - foreach($currentStyle as $name => $value) { + foreach ($currentStyle as $name => $value) { if (is_array($value)) { $value = implode(' ', $value); } @@ -444,7 +446,7 @@ class BaseConsole }, $string ); - while($tags > 0) { + while ($tags > 0) { $result .= ''; $tags--; } @@ -453,12 +455,12 @@ class BaseConsole /** * Converts Markdown to be better readable in console environments by applying some ANSI format - * @param string $markdown - * @return string + * @param string $markdown the markdown string. + * @return string the parsed result as ANSI formatted string. */ public static function markdownToAnsi($markdown) { - $parser = new Markdown(); + $parser = new ConsoleMarkdown(); return $parser->parse($markdown); } @@ -579,18 +581,18 @@ class BaseConsole */ public static function streamSupportsAnsiColors($stream) { - return DIRECTORY_SEPARATOR == '\\' + return DIRECTORY_SEPARATOR === '\\' ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON' : function_exists('posix_isatty') && @posix_isatty($stream); } /** * Returns true if the console is running on windows - * @return bool + * @return boolean */ public static function isRunningOnWindows() { - return DIRECTORY_SEPARATOR == '\\'; + return DIRECTORY_SEPARATOR === '\\'; } /** @@ -611,12 +613,17 @@ class BaseConsole if (static::isRunningOnWindows()) { $output = []; exec('mode con', $output); +<<<<<<< HEAD <<<<<<< HEAD if (isset($output) && strpos($output[1], 'CON') !== false) { ======= if (isset($output, $output[1]) && strpos($output[1], 'CON') !== false) { >>>>>>> yiichina/master return $size = [(int) preg_replace('~[^0-9]~', '', $output[3]), (int) preg_replace('~[^0-9]~', '', $output[4])]; +======= + if (isset($output, $output[1]) && strpos($output[1], 'CON') !== false) { + return $size = [(int) preg_replace('~\D~', '', $output[3]), (int) preg_replace('~\D~', '', $output[4])]; +>>>>>>> master } } else { // try stty if available @@ -658,11 +665,15 @@ class BaseConsole * @param boolean $refresh whether to force refresh of screen size. * This will be passed to [[getScreenSize()]]. * @return string the wrapped text. +<<<<<<< HEAD <<<<<<< HEAD * @since 2.0.3 ======= * @since 2.0.4 >>>>>>> yiichina/master +======= + * @since 2.0.4 +>>>>>>> master */ public static function wrapText($text, $indent = 0, $refresh = false) { @@ -673,7 +684,7 @@ class BaseConsole $pad = str_repeat(' ', $indent); $lines = explode("\n", wordwrap($text, $size[0] - $indent, "\n", true)); $first = true; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { if ($first) { $first = false; continue; @@ -698,7 +709,7 @@ class BaseConsole * Prints a string to STDOUT. * * @param string $string the string to print - * @return int|boolean Number of bytes printed or false on error + * @return integer|boolean Number of bytes printed or false on error */ public static function stdout($string) { @@ -709,7 +720,7 @@ class BaseConsole * Prints a string to STDERR. * * @param string $string the string to print - * @return int|boolean Number of bytes printed or false on error + * @return integer|boolean Number of bytes printed or false on error */ public static function stderr($string) { @@ -788,7 +799,7 @@ class BaseConsole ? static::input("$text [" . $options['default'] . '] ') : static::input("$text "); - if (!strlen($input)) { + if ($input === '') { if (isset($options['default'])) { $input = $options['default']; } elseif ($options['required']) { @@ -825,11 +836,11 @@ class BaseConsole return $default; } - if (!strcasecmp ($input, 'y') || !strcasecmp ($input, 'yes') ) { + if (!strcasecmp($input, 'y') || !strcasecmp($input, 'yes')) { return true; } - if (!strcasecmp ($input, 'n') || !strcasecmp ($input, 'no') ) { + if (!strcasecmp($input, 'n') || !strcasecmp($input, 'no')) { return false; } } @@ -847,13 +858,13 @@ class BaseConsole public static function select($prompt, $options = []) { top: - static::stdout("$prompt [" . implode(',', array_keys($options)) . ",?]: "); + static::stdout("$prompt [" . implode(',', array_keys($options)) . ',?]: '); $input = static::stdin(); if ($input === '?') { foreach ($options as $key => $value) { static::output(" $key - $value"); } - static::output(" ? - Show help"); + static::output(' ? - Show help'); goto top; } elseif (!array_key_exists($input, $options)) { goto top; @@ -865,6 +876,9 @@ class BaseConsole private static $_progressStart; private static $_progressWidth; private static $_progressPrefix; + private static $_progressEta; + private static $_progressEtaLastDone = 0; + private static $_progressEtaLastUpdate; /** * Starts display of a progress bar on screen. @@ -910,6 +924,9 @@ class BaseConsole self::$_progressStart = time(); self::$_progressWidth = $width; self::$_progressPrefix = $prefix; + self::$_progressEta = null; + self::$_progressEtaLastDone = 0; + self::$_progressEtaLastUpdate = time(); static::updateProgress($done, $total); } @@ -948,13 +965,24 @@ class BaseConsole $width -= static::ansiStrlen($prefix); $percent = ($total == 0) ? 1 : $done / $total; - $info = sprintf("%d%% (%d/%d)", $percent * 100, $done, $total); + $info = sprintf('%d%% (%d/%d)', $percent * 100, $done, $total); if ($done > $total || $done == 0) { - $info .= ' ETA: n/a'; + self::$_progressEta = null; + self::$_progressEtaLastUpdate = time(); } elseif ($done < $total) { - $rate = (time() - self::$_progressStart) / $done; - $info .= sprintf(' ETA: %d sec.', $rate * ($total - $done)); + // update ETA once per second to avoid flapping + if (time() - self::$_progressEtaLastUpdate > 1 && $done > self::$_progressEtaLastDone) { + $rate = (time() - (self::$_progressEtaLastUpdate ?: self::$_progressStart)) / ($done - self::$_progressEtaLastDone); + self::$_progressEta = $rate * ($total - $done); + self::$_progressEtaLastUpdate = time(); + self::$_progressEtaLastDone = $done; + } + } + if (self::$_progressEta === null) { + $info .= ' ETA: n/a'; + } else { + $info .= sprintf(' ETA: %d sec.', self::$_progressEta); } $width -= 3 + static::ansiStrlen($info); @@ -968,10 +996,10 @@ class BaseConsole $percent = 1; } $bar = floor($percent * $width); - $status = str_repeat("=", $bar); + $status = str_repeat('=', $bar); if ($bar < $width) { - $status .= ">"; - $status .= str_repeat(" ", $width - $bar - 1); + $status .= '>'; + $status .= str_repeat(' ', $width - $bar - 1); } static::stdout("\r$prefix" . "[$status] $info"); } @@ -1004,5 +1032,8 @@ class BaseConsole self::$_progressStart = null; self::$_progressWidth = null; self::$_progressPrefix = ''; + self::$_progressEta = null; + self::$_progressEtaLastDone = 0; + self::$_progressEtaLastUpdate = null; } } diff --git a/framework/helpers/BaseFileHelper.php b/framework/helpers/BaseFileHelper.php index d2166601b6..5b48c91510 100644 --- a/framework/helpers/BaseFileHelper.php +++ b/framework/helpers/BaseFileHelper.php @@ -8,6 +8,7 @@ namespace yii\helpers; use Yii; +use yii\base\ErrorException; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; @@ -188,7 +189,7 @@ class BaseFileHelper public static function getExtensionsByMimeType($mimeType, $magicFile = null) { $mimeTypes = static::loadMimeTypes($magicFile); - return array_keys($mimeTypes, mb_strtolower($mimeType, 'utf-8'), true); + return array_keys($mimeTypes, mb_strtolower($mimeType, 'UTF-8'), true); } private static $_mimeTypes = []; @@ -252,17 +253,24 @@ class BaseFileHelper */ public static function copyDirectory($src, $dst, $options = []) { + if ($src === $dst || strpos($dst, $src) === 0) { + throw new InvalidParamException('Trying to copy a directory to itself or a subdirectory.'); + } if (!is_dir($dst)) { static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); } $handle = opendir($src); if ($handle === false) { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidParamException('Unable to open directory: ' . $src); ======= throw new InvalidParamException("Unable to open directory: $src"); >>>>>>> yiichina/master +======= + throw new InvalidParamException("Unable to open directory: $src"); +>>>>>>> master } if (!isset($options['basePath'])) { // this should be done only once @@ -285,7 +293,10 @@ class BaseFileHelper @chmod($to, $options['fileMode']); } } else { - static::copyDirectory($from, $to, $options); + // recursive copy, defaults to true + if (!isset($options['recursive']) || $options['recursive']) { + static::copyDirectory($from, $to, $options); + } } if (isset($options['afterCopy'])) { call_user_func($options['afterCopy'], $from, $to); @@ -297,12 +308,15 @@ class BaseFileHelper /** * Removes a directory (and all its content) recursively. + * * @param string $dir the directory to be deleted recursively. * @param array $options options for directory remove. Valid options are: * * - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too. * Defaults to `false`, meaning the content of the symlinked directory would not be deleted. * Only symlink would be removed in that default case. + * + * @throws ErrorException in case of failure */ public static function removeDirectory($dir, $options = []) { @@ -321,7 +335,17 @@ class BaseFileHelper if (is_dir($path)) { static::removeDirectory($path, $options); } else { - unlink($path); + try { + unlink($path); + } catch (ErrorException $e) { + if (DIRECTORY_SEPARATOR === '\\') { + // last resort measure for Windows + $lines = []; + exec("DEL /F/Q \"$path\"", $lines, $deleteError); + } else { + throw $e; + } + } } } closedir($handle); @@ -369,11 +393,15 @@ class BaseFileHelper public static function findFiles($dir, $options = []) { if (!is_dir($dir)) { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidParamException('The dir argument must be a directory.'); ======= throw new InvalidParamException("The dir argument must be a directory: $dir"); >>>>>>> yiichina/master +======= + throw new InvalidParamException("The dir argument must be a directory: $dir"); +>>>>>>> master } $dir = rtrim($dir, DIRECTORY_SEPARATOR); if (!isset($options['basePath'])) { @@ -384,11 +412,15 @@ class BaseFileHelper $list = []; $handle = opendir($dir); if ($handle === false) { +<<<<<<< HEAD <<<<<<< HEAD throw new InvalidParamException('Unable to open directory: ' . $dir); ======= throw new InvalidParamException("Unable to open directory: $dir"); >>>>>>> yiichina/master +======= + throw new InvalidParamException("Unable to open directory: $dir"); +>>>>>>> master } while (($file = readdir($handle)) !== false) { if ($file === '.' || $file === '..') { @@ -460,9 +492,13 @@ class BaseFileHelper * @param boolean $recursive whether to create parent directories if they do not exist. * @return boolean whether the directory is created successfully <<<<<<< HEAD +<<<<<<< HEAD ======= * @throws \yii\base\Exception if the directory could not be created. >>>>>>> yiichina/master +======= + * @throws \yii\base\Exception if the directory could not be created (i.e. php error due to parallel changes) +>>>>>>> master */ public static function createDirectory($path, $mode = 0775, $recursive = true) { @@ -470,9 +506,11 @@ class BaseFileHelper return true; } $parentDir = dirname($path); - if ($recursive && !is_dir($parentDir)) { + // recurse if parent dir does not exist and we are not at the root of the file system. + if ($recursive && !is_dir($parentDir) && $parentDir !== $path) { static::createDirectory($parentDir, $mode, true); } +<<<<<<< HEAD <<<<<<< HEAD $result = mkdir($path, $mode); chmod($path, $mode); @@ -486,6 +524,22 @@ class BaseFileHelper >>>>>>> yiichina/master return $result; +======= + try { + if (!mkdir($path, $mode)) { + return false; + } + } catch (\Exception $e) { + if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288 + throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); + } + } + try { + return chmod($path, $mode); + } catch (\Exception $e) { + throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); + } +>>>>>>> master } /** @@ -497,7 +551,7 @@ class BaseFileHelper * @param string $pattern the pattern that $baseName will be compared against * @param integer|boolean $firstWildcard location of first wildcard character in the $pattern * @param integer $flags pattern flags - * @return boolean wheter the name matches against pattern + * @return boolean whether the name matches against pattern */ private static function matchBasename($baseName, $pattern, $firstWildcard, $flags) { @@ -531,12 +585,12 @@ class BaseFileHelper * @param string $pattern the pattern that path part will be compared against * @param integer|boolean $firstWildcard location of first wildcard character in the $pattern * @param integer $flags pattern flags - * @return boolean wheter the path part matches against pattern + * @return boolean whether the path part matches against pattern */ private static function matchPathname($path, $basePath, $pattern, $firstWildcard, $flags) { // match with FNM_PATHNAME; the pattern has base implicitly in front of it. - if (isset($pattern[0]) && $pattern[0] == '/') { + if (isset($pattern[0]) && $pattern[0] === '/') { $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); if ($firstWildcard !== false && $firstWildcard !== 0) { $firstWildcard--; @@ -622,7 +676,7 @@ class BaseFileHelper * @param string $pattern * @param boolean $caseSensitive * @throws \yii\base\InvalidParamException - * @return array with keys: (string) pattern, (int) flags, (int|boolean)firstWildcard + * @return array with keys: (string) pattern, (int) flags, (int|boolean) firstWildcard */ private static function parseExcludePattern($pattern, $caseSensitive) { @@ -644,11 +698,11 @@ class BaseFileHelper return $result; } - if ($pattern[0] == '!') { + if ($pattern[0] === '!') { $result['flags'] |= self::PATTERN_NEGATIVE; $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); } - if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) == '/') { + if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) === '/') { $pattern = StringHelper::byteSubstr($pattern, 0, -1); $result['flags'] |= self::PATTERN_MUSTBEDIR; } @@ -656,7 +710,7 @@ class BaseFileHelper $result['flags'] |= self::PATTERN_NODIR; } $result['firstWildcard'] = self::firstWildcardInPattern($pattern); - if ($pattern[0] == '*' && self::firstWildcardInPattern(StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern))) === false) { + if ($pattern[0] === '*' && self::firstWildcardInPattern(StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern))) === false) { $result['flags'] |= self::PATTERN_ENDSWITH; } $result['pattern'] = $pattern; @@ -675,7 +729,7 @@ class BaseFileHelper $wildcardSearch = function ($r, $c) use ($pattern) { $p = strpos($pattern, $c); - return $r===false ? $p : ($p===false ? $r : min($r, $p)); + return $r === false ? $p : ($p === false ? $r : min($r, $p)); }; return array_reduce($wildcards, $wildcardSearch, false); diff --git a/framework/helpers/BaseFormatConverter.php b/framework/helpers/BaseFormatConverter.php index 83a2dca69b..aec0033e86 100644 --- a/framework/helpers/BaseFormatConverter.php +++ b/framework/helpers/BaseFormatConverter.php @@ -153,11 +153,15 @@ class BaseFormatConverter 'MMM' => 'M', // A short textual representation of a month, three letters 'MMMM' => 'F', // A full textual representation of a month, such as January or March 'MMMMM' => '', // +<<<<<<< HEAD <<<<<<< HEAD 'L' => 'm', // Stand alone month in year ======= 'L' => 'n', // Stand alone month in year >>>>>>> yiichina/master +======= + 'L' => 'n', // Stand alone month in year +>>>>>>> master 'LL' => 'm', // Stand alone month in year 'LLL' => 'M', // Stand alone month in year 'LLLL' => 'F', // Stand alone month in year @@ -367,11 +371,15 @@ class BaseFormatConverter 'MMM' => 'M', // A short textual representation of a month, three letters 'MMMM' => 'MM', // A full textual representation of a month, such as January or March 'MMMMM' => '', // +<<<<<<< HEAD <<<<<<< HEAD 'L' => 'mm', // Stand alone month in year ======= 'L' => 'm', // Stand alone month in year >>>>>>> yiichina/master +======= + 'L' => 'm', // Stand alone month in year +>>>>>>> master 'LL' => 'mm', // Stand alone month in year 'LLL' => 'M', // Stand alone month in year 'LLLL' => 'MM', // Stand alone month in year diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 71348c8814..d959627401 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -80,7 +80,6 @@ class BaseHtml 'rel', 'media', ]; - /** * @var array list of tag attributes that should be specially handled when their values are of array type. * In particular, if the value of the `data` attribute is `['name' => 'xyz', 'age' => 13]`, two attributes @@ -120,7 +119,7 @@ class BaseHtml /** * Generates a complete HTML tag. - * @param string $name the tag name + * @param string|boolean|null $name the tag name. If $name is `null` or `false`, the corresponding content will be rendered without any tag. * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. * @param array $options the HTML tag attributes (HTML options) in terms of name-value pairs. @@ -138,13 +137,16 @@ class BaseHtml */ public static function tag($name, $content = '', $options = []) { + if ($name === null || $name === false) { + return $content; + } $html = "<$name" . static::renderTagAttributes($options) . '>'; return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; } /** * Generates a start tag. - * @param string $name the tag name + * @param string|boolean|null $name the tag name. If $name is `null` or `false`, the corresponding content will be rendered without any tag. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. @@ -155,18 +157,24 @@ class BaseHtml */ public static function beginTag($name, $options = []) { + if ($name === null || $name === false) { + return ''; + } return "<$name" . static::renderTagAttributes($options) . '>'; } /** * Generates an end tag. - * @param string $name the tag name + * @param string|boolean|null $name the tag name. If $name is `null` or `false`, the corresponding content will be rendered without any tag. * @return string the generated end tag * @see beginTag() * @see tag() */ public static function endTag($name) { + if ($name === null || $name === false) { + return ''; + } return ""; } @@ -176,10 +184,13 @@ class BaseHtml * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. +<<<<<<< HEAD <<<<<<< HEAD * If the options does not contain "type", a "type" attribute with value "text/css" will be used. ======= >>>>>>> yiichina/master +======= +>>>>>>> master * See [[renderTagAttributes()]] for details on how attributes are being rendered. * @return string the generated style tag */ @@ -194,10 +205,13 @@ class BaseHtml * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. +<<<<<<< HEAD <<<<<<< HEAD * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. ======= >>>>>>> yiichina/master +======= +>>>>>>> master * See [[renderTagAttributes()]] for details on how attributes are being rendered. * @return string the generated script tag */ @@ -232,10 +246,10 @@ class BaseHtml if (isset($options['condition'])) { $condition = $options['condition']; unset($options['condition']); - return ""; + return self::wrapIntoCondition(static::tag('link', '', $options), $condition); } elseif (isset($options['noscript']) && $options['noscript'] === true) { unset($options['noscript']); - return ""; + return ''; } else { return static::tag('link', '', $options); } @@ -262,12 +276,26 @@ class BaseHtml if (isset($options['condition'])) { $condition = $options['condition']; unset($options['condition']); - return ""; + return self::wrapIntoCondition(static::tag('script', '', $options), $condition); } else { return static::tag('script', '', $options); } } + /** + * Wraps given content into conditional comments for IE, e.g., `lt IE 9`. + * @param string $content raw HTML content. + * @param string $condition condition string. + * @return string generated HTML. + */ + private static function wrapIntoCondition($content, $condition) + { + if (strpos($condition, '!IE') !== false) { + return "\n" . $content . "\n"; + } + return ""; + } + /** * Generates the meta tags containing CSRF token information. * @return string the generated meta tags @@ -295,6 +323,11 @@ class BaseHtml * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * Special options: + * + * - `csrf`: whether to generate the CSRF hidden input. Defaults to true. + * * @return string the generated form start tag. * @see endForm() */ @@ -311,7 +344,9 @@ class BaseHtml $hiddenInputs[] = static::hiddenInput($request->methodParam, $method); $method = 'post'; } - if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) { + $csrf = ArrayHelper::remove($options, 'csrf', true); + + if ($csrf && $request->enableCsrfValidation && strcasecmp($method, 'post') === 0) { $hiddenInputs[] = static::hiddenInput($request->csrfParam, $request->getCsrfToken()); } } @@ -360,6 +395,14 @@ class BaseHtml * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[Url::to()]] * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute * will not be generated. + * + * If you want to use an absolute url you can call [[Url::to()]] yourself, before passing the URL to this method, + * like this: + * + * ```php + * Html::a('link text', Url::to($url, true)) + * ``` + * * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. @@ -452,6 +495,10 @@ class BaseHtml /** * Generates a submit button tag. + * + * Be careful when naming form elements such as submit buttons. According to the [jQuery documentation](https://api.jquery.com/submit/) there + * are some reserved names that can cause conflicts, e.g. `submit`, `length`, or `method`. + * * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, * you should consider [[encode()]] it to prevent XSS attacks. @@ -523,6 +570,10 @@ class BaseHtml /** * Generates a submit input button. + * + * Be careful when naming form elements such as submit buttons. According to the [jQuery documentation](https://api.jquery.com/submit/) there + * are some reserved names that can cause conflicts, e.g. `submit`, `length`, or `method`. + * * @param string $label the value attribute. If it is null, the value attribute will not be generated. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. @@ -733,12 +784,12 @@ class BaseHtml * - options: array, the attributes for the select option tags. The array keys must be valid option values, * and the array values are the extra attributes for the corresponding option tags. For example, * - * ~~~ + * ```php * [ * 'value1' => ['disabled' => true], * 'value2' => ['label' => 'value 2'], * ]; - * ~~~ + * ``` * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', * except that the array keys represent the optgroup labels specified in $items. @@ -782,12 +833,12 @@ class BaseHtml * - options: array, the attributes for the select option tags. The array keys must be valid option values, * and the array values are the extra attributes for the corresponding option tags. For example, * - * ~~~ + * ```php * [ * 'value1' => ['disabled' => true], * 'value2' => ['label' => 'value 2'], * ]; - * ~~~ + * ``` * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', * except that the array keys represent the optgroup labels specified in $items. @@ -839,7 +890,7 @@ class BaseHtml * @param array $options options (name => config) for the checkbox list container tag. * The following options are specially handled: * - * - tag: string, the tag name of the container element. + * - tag: string|false, the tag name of the container element. False to render radio buttons without container. * - unselect: string, the value that should be submitted when none of the checkboxes is selected. * By setting this option, a hidden input will be generated. * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. @@ -849,9 +900,9 @@ class BaseHtml * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * - * ~~~ + * ```php * function ($index, $label, $name, $checked, $value) - * ~~~ + * ``` * * where $index is the zero-based index of the checkbox in the whole list; $label * is the label for the checkbox; and $name, $value and $checked represent the name, @@ -867,15 +918,18 @@ class BaseHtml $name .= '[]'; } - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; - $encode = !isset($options['encode']) || $options['encode']; + $formatter = ArrayHelper::remove($options, 'item'); + $itemOptions = ArrayHelper::remove($options, 'itemOptions', []); + $encode = ArrayHelper::remove($options, 'encode', true); + $separator = ArrayHelper::remove($options, 'separator', "\n"); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + $lines = []; $index = 0; foreach ($items as $value => $label) { $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); + (!ArrayHelper::isTraversable($selection) && !strcmp($value, $selection) + || ArrayHelper::isTraversable($selection) && ArrayHelper::isIn($value, $selection)); if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { @@ -891,15 +945,18 @@ class BaseHtml // add a hidden field so that if the list box has no option being selected, it still submits a value $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; $hidden = static::hiddenInput($name2, $options['unselect']); + unset($options['unselect']); } else { $hidden = ''; } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); + $visibleContent = implode($separator, $lines); - return $hidden . static::tag($tag, implode($separator, $lines), $options); + if ($tag === false) { + return $hidden . $visibleContent; + } + + return $hidden . static::tag($tag, $visibleContent, $options); } /** @@ -912,7 +969,7 @@ class BaseHtml * @param array $options options (name => config) for the radio button list container tag. * The following options are specially handled: * - * - tag: string, the tag name of the container element. + * - tag: string|false, the tag name of the container element. False to render radio buttons without container. * - unselect: string, the value that should be submitted when none of the radio buttons is selected. * By setting this option, a hidden input will be generated. * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. @@ -922,9 +979,9 @@ class BaseHtml * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * - * ~~~ + * ```php * function ($index, $label, $name, $checked, $value) - * ~~~ + * ``` * * where $index is the zero-based index of the radio button in the whole list; $label * is the label for the radio button; and $name, $value and $checked represent the name, @@ -936,15 +993,21 @@ class BaseHtml */ public static function radioList($name, $selection = null, $items = [], $options = []) { - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; + $formatter = ArrayHelper::remove($options, 'item'); + $itemOptions = ArrayHelper::remove($options, 'itemOptions', []); + $encode = ArrayHelper::remove($options, 'encode', true); + $separator = ArrayHelper::remove($options, 'separator', "\n"); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = isset($options['unselect']) ? static::hiddenInput($name, $options['unselect']) : ''; + unset($options['unselect']); + $lines = []; $index = 0; foreach ($items as $value => $label) { $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); + (!ArrayHelper::isTraversable($selection) && !strcmp($value, $selection) + || ArrayHelper::isTraversable($selection) && ArrayHelper::isIn($value, $selection)); if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { @@ -955,19 +1018,13 @@ class BaseHtml } $index++; } + $visibleContent = implode($separator, $lines); - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; + if ($tag === false) { + return $hidden . $visibleContent; } - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); + return $hidden . static::tag($tag, $visibleContent, $options); } /** @@ -978,13 +1035,15 @@ class BaseHtml * * - encode: boolean, whether to HTML-encode the items. Defaults to true. * This option is ignored if the `item` option is specified. + * - separator: string, the HTML code that separates items. Defaults to a simple newline (`"\n"`). + * This option is available since version 2.0.7. * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. * - item: callable, a callback that is used to generate each individual list item. * The signature of this callback must be: * - * ~~~ + * ```php * function ($item, $index) - * ~~~ + * ``` * * where $index is the array key corresponding to `$item` in `$items`. The callback should return * the whole list item tag. @@ -995,11 +1054,11 @@ class BaseHtml */ public static function ul($items, $options = []) { - $tag = isset($options['tag']) ? $options['tag'] : 'ul'; - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; - unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + $encode = ArrayHelper::remove($options, 'encode', true); + $formatter = ArrayHelper::remove($options, 'item'); + $separator = ArrayHelper::remove($options, 'separator', "\n"); + $itemOptions = ArrayHelper::remove($options, 'itemOptions', []); if (empty($items)) { return static::tag($tag, '', $options); @@ -1013,7 +1072,12 @@ class BaseHtml $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); } } - return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + + return static::tag( + $tag, + $separator . implode($separator, $results) . $separator, + $options + ); } /** @@ -1028,9 +1092,9 @@ class BaseHtml * - item: callable, a callback that is used to generate each individual list item. * The signature of this callback must be: * - * ~~~ + * ```php * function ($item, $index) - * ~~~ + * ``` * * where $index is the array key corresponding to `$item` in `$items`. The callback should return * the whole list item tag. @@ -1066,16 +1130,18 @@ class BaseHtml */ public static function activeLabel($model, $attribute, $options = []) { - $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + $for = ArrayHelper::remove($options, 'for', static::getInputId($model, $attribute)); $attribute = static::getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); - unset($options['label'], $options['for']); + $label = ArrayHelper::remove($options, 'label', static::encode($model->getAttributeLabel($attribute))); return static::label($label, $for, $options); } /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * Generates a hint tag for the given model attribute. * The hint text is the hint associated with the attribute, obtained via [[Model::getAttributeHint()]]. * If no hint content can be obtained, method will return an empty string. @@ -1109,7 +1175,10 @@ class BaseHtml } /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * Generates a summary of the validation errors. * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden. * @param Model|Model[] $models the model(s) whose validation errors are to be displayed @@ -1117,11 +1186,15 @@ class BaseHtml * * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used. * - footer: string, the footer HTML for the error summary. +<<<<<<< HEAD <<<<<<< HEAD * - encode: boolean, if set to false then value won't be encoded. ======= * - encode: boolean, if set to false then the error messages won't be encoded. >>>>>>> yiichina/master +======= + * - encode: boolean, if set to false then the error messages won't be encoded. +>>>>>>> master * * The rest of the options will be rendered as the attributes of the container tag. The values will * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. @@ -1130,9 +1203,9 @@ class BaseHtml public static function errorSummary($models, $options = []) { $header = isset($options['header']) ? $options['header'] : '

    ' . Yii::t('yii', 'Please fix the following errors:') . '

    '; - $footer = isset($options['footer']) ? $options['footer'] : ''; - $encode = !isset($options['encode']) || $options['encode'] !== false; - unset($options['header'], $options['footer'], $options['encode']); + $footer = ArrayHelper::remove($options, 'footer', ''); + $encode = ArrayHelper::remove($options, 'encode', true); + unset($options['header']); $lines = []; if (!is_array($models)) { @@ -1147,10 +1220,10 @@ class BaseHtml if (empty($lines)) { // still render the placeholder for client-side validation use - $content = "
      "; + $content = '
        '; $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none'; } else { - $content = "
        • " . implode("
        • \n
        • ", $lines) . "
        "; + $content = '
        • ' . implode("
        • \n
        • ", $lines) . '
        '; } return Html::tag('div', $header . $content . $footer, $options); } @@ -1167,11 +1240,15 @@ class BaseHtml * The following options are specially handled: * * - tag: this specifies the tag name. If not set, "div" will be used. +<<<<<<< HEAD <<<<<<< HEAD * - encode: boolean, if set to false then value won't be encoded. ======= * - encode: boolean, if set to false then the error message won't be encoded. >>>>>>> yiichina/master +======= + * - encode: boolean, if set to false then the error message won't be encoded. +>>>>>>> master * * See [[renderTagAttributes()]] for details on how attributes are being rendered. * @@ -1181,9 +1258,8 @@ class BaseHtml { $attribute = static::getAttributeName($attribute); $error = $model->getFirstError($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - $encode = !isset($options['encode']) || $options['encode'] !== false; - unset($options['tag'], $options['encode']); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + $encode = ArrayHelper::remove($options, 'encode', true); return Html::tag($tag, $encode ? Html::encode($error) : $error, $options); } @@ -1210,6 +1286,27 @@ class BaseHtml return static::input($type, $name, $value, $options); } + /** + * If `maxlength` option is set true and the model attribute is validated by a string validator, + * the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. + * @param array $options the tag options in terms of name-value pairs. + */ + private static function normalizeMaxLength($model, $attribute, &$options) + { + if (isset($options['maxlength']) && $options['maxlength'] === true) { + unset($options['maxlength']); + $attrName = static::getAttributeName($attribute); + foreach ($model->getActiveValidators($attrName) as $validator) { + if ($validator instanceof StringValidator && $validator->max !== null) { + $options['maxlength'] = $validator->max; + break; + } + } + } + } + /** * Generates a text input tag for the given model attribute. * This method will generate the "name" and "value" tag attributes automatically for the model attribute @@ -1230,16 +1327,7 @@ class BaseHtml */ public static function activeTextInput($model, $attribute, $options = []) { - if (isset($options['maxlength']) && $options['maxlength'] === true) { - unset($options['maxlength']); - $attrName = static::getAttributeName($attribute); - foreach ($model->getActiveValidators($attrName) as $validator) { - if ($validator instanceof StringValidator && $validator->max !== null) { - $options['maxlength'] = $validator->max; - break; - } - } - } + self::normalizeMaxLength($model, $attribute, $options); return static::activeInput('text', $model, $attribute, $options); } @@ -1270,10 +1358,17 @@ class BaseHtml * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * The following special options are recognized: + * + * - maxlength: integer|boolean, when `maxlength` is set true and the model attribute is validated + * by a string validator, the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]]. + * This option is available since version 2.0.6. + * * @return string the generated input tag */ public static function activePasswordInput($model, $attribute, $options = []) { + self::normalizeMaxLength($model, $attribute, $options); return static::activeInput('password', $model, $attribute, $options); } @@ -1293,7 +1388,11 @@ class BaseHtml { // add a hidden field so that if a model only has a file field, we can // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, ['id' => null, 'value' => '']) + $hiddenOptions = ['id' => null, 'value' => '']; + if (isset($options['name'])) { + $hiddenOptions['name'] = $options['name']; + } + return static::activeHiddenInput($model, $attribute, $hiddenOptions) . static::activeInput('file', $model, $attribute, $options); } @@ -1306,15 +1405,27 @@ class BaseHtml * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * The following special options are recognized: + * + * - maxlength: integer|boolean, when `maxlength` is set true and the model attribute is validated + * by a string validator, the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]]. + * This option is available since version 2.0.6. + * * @return string the generated textarea tag */ public static function activeTextarea($model, $attribute, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); + if (isset($options['value'])) { + $value = $options['value']; + unset($options['value']); + } else { + $value = static::getAttributeValue($model, $attribute); + } if (!array_key_exists('id', $options)) { $options['id'] = static::getInputId($model, $attribute); } + self::normalizeMaxLength($model, $attribute, $options); return static::textarea($name, $value, $options); } @@ -1436,12 +1547,12 @@ class BaseHtml * - options: array, the attributes for the select option tags. The array keys must be valid option values, * and the array values are the extra attributes for the corresponding option tags. For example, * - * ~~~ + * ```php * [ * 'value1' => ['disabled' => true], * 'value2' => ['label' => 'value 2'], * ]; - * ~~~ + * ``` * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', * except that the array keys represent the optgroup labels specified in $items. @@ -1485,12 +1596,12 @@ class BaseHtml * - options: array, the attributes for the select option tags. The array keys must be valid option values, * and the array values are the extra attributes for the corresponding option tags. For example, * - * ~~~ + * ```php * [ * 'value1' => ['disabled' => true], * 'value2' => ['label' => 'value 2'], * ]; - * ~~~ + * ``` * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', * except that the array keys represent the optgroup labels specified in $items. @@ -1538,9 +1649,9 @@ class BaseHtml * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * - * ~~~ + * ```php * function ($index, $label, $name, $checked, $value) - * ~~~ + * ``` * * where $index is the zero-based index of the checkbox in the whole list; $label * is the label for the checkbox; and $name, $value and $checked represent the name, @@ -1579,9 +1690,9 @@ class BaseHtml * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * - * ~~~ + * ```php * function ($index, $label, $name, $checked, $value) - * ~~~ + * ``` * * where $index is the zero-based index of the radio button in the whole list; $label * is the label for the radio button; and $name, $value and $checked represent the name, @@ -1663,6 +1774,7 @@ class BaseHtml foreach ($items as $key => $value) { if (is_array($value)) { $groupAttrs = isset($groups[$key]) ? $groups[$key] : []; +<<<<<<< HEAD <<<<<<< HEAD $groupAttrs['label'] = $key; ======= @@ -1670,6 +1782,11 @@ class BaseHtml $groupAttrs['label'] = $key; } >>>>>>> yiichina/master +======= + if (!isset($groupAttrs['label'])) { + $groupAttrs['label'] = $key; + } +>>>>>>> master $attrs = ['options' => $options, 'groups' => $groups, 'encodeSpaces' => $encodeSpaces, 'encode' => $encode]; $content = static::renderSelectOptions($selection, $value, $attrs); $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); @@ -1677,8 +1794,8 @@ class BaseHtml $attrs = isset($options[$key]) ? $options[$key] : []; $attrs['value'] = (string) $key; $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); + (!ArrayHelper::isTraversable($selection) && !strcmp($key, $selection) + || ArrayHelper::isTraversable($selection) && ArrayHelper::isIn($key, $selection)); $text = $encode ? static::encode($value) : $value; if ($encodeSpaces) { $text = str_replace(' ', ' ', $text); @@ -1734,21 +1851,39 @@ class BaseHtml if (in_array($name, static::$dataAttributes)) { foreach ($value as $n => $v) { if (is_array($v)) { +<<<<<<< HEAD <<<<<<< HEAD $html .= " $name-$n='" . Json::encode($v, JSON_HEX_APOS) . "'"; ======= $html .= " $name-$n='" . Json::htmlEncode($v) . "'"; >>>>>>> yiichina/master +======= + $html .= " $name-$n='" . Json::htmlEncode($v) . "'"; +>>>>>>> master } else { $html .= " $name-$n=\"" . static::encode($v) . '"'; } } + } elseif ($name === 'class') { + if (empty($value)) { + continue; + } + $html .= " $name=\"" . static::encode(implode(' ', $value)) . '"'; + } elseif ($name === 'style') { + if (empty($value)) { + continue; + } + $html .= " $name=\"" . static::encode(static::cssStyleFromArray($value)) . '"'; } else { +<<<<<<< HEAD <<<<<<< HEAD $html .= " $name='" . Json::encode($value, JSON_HEX_APOS) . "'"; ======= $html .= " $name='" . Json::htmlEncode($value) . "'"; >>>>>>> yiichina/master +======= + $html .= " $name='" . Json::htmlEncode($value) . "'"; +>>>>>>> master } } elseif ($value !== null) { $html .= " $name=\"" . static::encode($value) . '"'; @@ -1759,39 +1894,76 @@ class BaseHtml } /** - * Adds a CSS class to the specified options. + * Adds a CSS class (or several classes) to the specified options. * If the CSS class is already in the options, it will not be added again. + * If class specification at given options is an array, and some class placed there with the named (string) key, + * overriding of such key will have no effect. For example: + * + * ```php + * $options = ['class' => ['persistent' => 'initial']]; + * Html::addCssClass($options, ['persistent' => 'override']); + * var_dump($options['class']); // outputs: array('persistent' => 'initial'); + * ``` + * * @param array $options the options to be modified. - * @param string $class the CSS class to be added + * @param string|array $class the CSS class(es) to be added */ public static function addCssClass(&$options, $class) { if (isset($options['class'])) { - $classes = ' ' . $options['class'] . ' '; - if (strpos($classes, ' ' . $class . ' ') === false) { - $options['class'] .= ' ' . $class; + if (is_array($options['class'])) { + $options['class'] = self::mergeCssClasses($options['class'], (array) $class); + } else { + $classes = preg_split('/\s+/', $options['class'], -1, PREG_SPLIT_NO_EMPTY); + $options['class'] = implode(' ', self::mergeCssClasses($classes, (array) $class)); } } else { $options['class'] = $class; } } + /** + * Merges already existing CSS classes with new one. + * This method provides the priority for named existing classes over additional. + * @param array $existingClasses already existing CSS classes. + * @param array $additionalClasses CSS classes to be added. + * @return array merge result. + */ + private static function mergeCssClasses(array $existingClasses, array $additionalClasses) + { + foreach ($additionalClasses as $key => $class) { + if (is_int($key) && !in_array($class, $existingClasses)) { + $existingClasses[] = $class; + } elseif (!isset($existingClasses[$key])) { + $existingClasses[$key] = $class; + } + } + return array_unique($existingClasses); + } + /** * Removes a CSS class from the specified options. * @param array $options the options to be modified. - * @param string $class the CSS class to be removed + * @param string|array $class the CSS class(es) to be removed */ public static function removeCssClass(&$options, $class) { if (isset($options['class'])) { - $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); - if (($index = array_search($class, $classes)) !== false) { - unset($classes[$index]); - } - if (empty($classes)) { - unset($options['class']); + if (is_array($options['class'])) { + $classes = array_diff($options['class'], (array) $class); + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = $classes; + } } else { - $options['class'] = implode(' ', $classes); + $classes = preg_split('/\s+/', $options['class'], -1, PREG_SPLIT_NO_EMPTY); + $classes = array_diff($classes, (array) $class); + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } } } } @@ -1821,7 +1993,7 @@ class BaseHtml public static function addCssStyle(&$options, $style, $overwrite = true) { if (!empty($options['style'])) { - $oldStyle = static::cssStyleToArray($options['style']); + $oldStyle = is_array($options['style']) ? $options['style'] : static::cssStyleToArray($options['style']); $newStyle = is_array($style) ? $style : static::cssStyleToArray($style); if (!$overwrite) { foreach ($newStyle as $property => $value) { @@ -1852,7 +2024,7 @@ class BaseHtml public static function removeCssStyle(&$options, $properties) { if (!empty($options['style'])) { - $style = static::cssStyleToArray($options['style']); + $style = is_array($options['style']) ? $options['style'] : static::cssStyleToArray($options['style']); foreach ((array) $properties as $property) { unset($style[$property]); } @@ -2034,4 +2206,28 @@ class BaseHtml $name = strtolower(static::getInputName($model, $attribute)); return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name); } + + /** + * Escapes regular expression to use in JavaScript + * @param string $regexp the regular expression to be escaped. + * @return string the escaped result. + * @since 2.0.6 + */ + public static function escapeJsRegularExpression($regexp) + { + $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $regexp); + $deliminator = substr($pattern, 0, 1); + $pos = strrpos($pattern, $deliminator, 1); + $flag = substr($pattern, $pos + 1); + if ($deliminator !== '/') { + $pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/'; + } else { + $pattern = substr($pattern, 0, $pos + 1); + } + if (!empty($flag)) { + $pattern .= preg_replace('/[^igm]/', '', $flag); + } + + return $pattern; + } } diff --git a/framework/helpers/BaseHtmlPurifier.php b/framework/helpers/BaseHtmlPurifier.php index db528d6775..bc6d5af4c7 100644 --- a/framework/helpers/BaseHtmlPurifier.php +++ b/framework/helpers/BaseHtmlPurifier.php @@ -19,7 +19,7 @@ class BaseHtmlPurifier { /** * Passes markup through HTMLPurifier making it safe to output to end user - * + * * @param string $content The HTML content to purify * @param array|\Closure|null $config The config to use for HtmlPurifier. * If not specified or `null` the default config will be used. @@ -32,13 +32,13 @@ class BaseHtmlPurifier * * Here is a usage example of such a function: * - * ~~~ + * ```php * // Allow the HTML5 data attribute `data-type` on `img` elements. - * $content = HtmlPurifier::process($content, function($config) { + * $content = HtmlPurifier::process($content, function ($config) { * $config->getHTMLDefinition(true) * ->addAttribute('img', 'data-type', 'Text'); * }); - * ~~~ + * ``` * * @return string the purified HTML content. */ @@ -46,13 +46,14 @@ class BaseHtmlPurifier { $configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config); $configInstance->autoFinalize = false; - $purifier=\HTMLPurifier::instance($configInstance); + $purifier = \HTMLPurifier::instance($configInstance); $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); - + $purifier->config->set('Cache.SerializerPermissions', 0775); + + static::configure($configInstance); if ($config instanceof \Closure) { call_user_func($config, $configInstance); } - static::configure($configInstance); return $purifier->purify($content); } diff --git a/framework/helpers/BaseInflector.php b/framework/helpers/BaseInflector.php index 74eb685541..1e19d38b2a 100644 --- a/framework/helpers/BaseInflector.php +++ b/framework/helpers/BaseInflector.php @@ -95,6 +95,7 @@ class BaseInflector '/(m)en$/i' => '\1an', '/(c)hildren$/i' => '\1\2hild', '/(n)ews$/i' => '\1\2ews', + '/(n)etherlands$/i' => '\1\2etherlands', '/eaus$/' => 'eau', '/^(.*us)$/' => '\\1', '/s$/i' => '', @@ -217,7 +218,7 @@ class BaseInflector 'Yengeese' => 'Yengeese', ]; /** - * @var array fallback map for transliteration used by [[slug()]] when intl isn't available. + * @var array fallback map for transliteration used by [[transliterate()]] when intl isn't available. */ public static $transliteration = [ 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C', @@ -232,11 +233,52 @@ class BaseInflector 'ÿ' => 'y', ]; /** - * @var mixed Either a [[Transliterator]] or a string from which a [[Transliterator]] - * can be built for transliteration used by [[slug()]] when intl is available. + * Shortcut for `Any-Latin; NFKD` transliteration rule. The rule is strict, letters will be transliterated with + * the closest sound-representation chars. The result may contain any UTF-8 chars. For example: + * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to + * `huò qǔ dào dochira Ukraí̈nsʹka: g̀,ê, Srpska: đ, n̂, d̂! ¿Español?` + * + * Used in [[transliterate()]]. + * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table) + * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table + * @see transliterate() + * @since 2.0.7 + */ + const TRANSLITERATE_STRICT = 'Any-Latin; NFKD'; + /** + * Shortcut for `Any-Latin; Latin-ASCII` transliteration rule. The rule is medium, letters will be + * transliterated to characters of Latin-1 (ISO 8859-1) ASCII table. For example: + * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to + * `huo qu dao dochira Ukrainsʹka: g,e, Srpska: d, n, d! ¿Espanol?` + * + * Used in [[transliterate()]]. + * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table) + * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table + * @see transliterate() + * @since 2.0.7 + */ + const TRANSLITERATE_MEDIUM = 'Any-Latin; Latin-ASCII'; + /** + * Shortcut for `Any-Latin; Latin-ASCII; [\u0080-\uffff] remove` transliteration rule. The rule is loose, + * letters will be transliterated with the characters of Basic Latin Unicode Block. + * For example: + * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to + * `huo qu dao dochira Ukrainska: g,e, Srpska: d, n, d! Espanol?` + * + * Used in [[transliterate()]]. + * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table) + * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table + * @see transliterate() + * @since 2.0.7 + */ + const TRANSLITERATE_LOOSE = 'Any-Latin; Latin-ASCII; [\u0080-\uffff] remove'; + + /** + * @var mixed Either a [[\Transliterator]], or a string from which a [[\Transliterator]] can be built + * for transliteration. Used by [[transliterate()]] when intl is available. Defaults to [[TRANSLITERATE_LOOSE]] * @see http://php.net/manual/en/transliterator.transliterate.php */ - public static $transliterator = 'Any-Latin; NFKD'; + public static $transliterator = self::TRANSLITERATE_LOOSE; /** @@ -320,7 +362,7 @@ class BaseInflector $label = trim(strtolower(str_replace([ '-', '_', - '.' + '.', ], ' ', preg_replace('/(? 'The maximum stack depth has been exceeded.', + 'JSON_ERROR_STATE_MISMATCH' => 'Invalid or malformed JSON.', + 'JSON_ERROR_CTRL_CHAR' => 'Control character error, possibly incorrectly encoded.', + 'JSON_ERROR_SYNTAX' => 'Syntax error.', + 'JSON_ERROR_UTF8' => 'Malformed UTF-8 characters, possibly incorrectly encoded.', // PHP 5.3.3 + 'JSON_ERROR_RECURSION' => 'One or more recursive references in the value to be encoded.', // PHP 5.5.0 + 'JSON_ERROR_INF_OR_NAN' => 'One or more NAN or INF values in the value to be encoded', // PHP 5.5.0 + 'JSON_ERROR_UNSUPPORTED_TYPE' => 'A value of a type that cannot be encoded was given', // PHP 5.5.0 + ]; + + /** * Encodes the given value into a JSON string. * The method enhances `json_encode()` by supporting JavaScript expressions. * In particular, the method will not encode a JavaScript expression that is * represented in terms of a [[JsExpression]] object. - * @param mixed $value the data to be encoded + * @param mixed $value the data to be encoded. * @param integer $options the encoding options. For more details please refer to * . Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`. - * @return string the encoding result + * @return string the encoding result. + * @throws InvalidParamException if there is any encoding error. */ public static function encode($value, $options = 320) { $expressions = []; - $value = static::processData($value, $expressions, uniqid()); + $value = static::processData($value, $expressions, uniqid('', true)); + set_error_handler(function () { + static::handleJsonError(JSON_ERROR_SYNTAX); + }, E_WARNING); $json = json_encode($value, $options); + restore_error_handler(); + static::handleJsonError(json_last_error()); - return empty($expressions) ? $json : strtr($json, $expressions); + return $expressions === [] ? $json : strtr($json, $expressions); + } + + /** + * Encodes the given value into a JSON string HTML-escaping entities so it is safe to be embedded in HTML code. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * + * @param mixed $value the data to be encoded + * @return string the encoding result + * @since 2.0.4 + * @throws InvalidParamException if there is any encoding error + */ + public static function htmlEncode($value) + { + return static::encode($value, JSON_UNESCAPED_UNICODE | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS); } /** @@ -69,28 +108,42 @@ class BaseJson { if (is_array($json)) { throw new InvalidParamException('Invalid JSON data.'); + } elseif ($json === null || $json === '') { + return null; } $decode = json_decode((string) $json, $asArray); - switch (json_last_error()) { - case JSON_ERROR_NONE: - break; - case JSON_ERROR_DEPTH: - throw new InvalidParamException('The maximum stack depth has been exceeded.'); - case JSON_ERROR_CTRL_CHAR: - throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); - case JSON_ERROR_SYNTAX: - throw new InvalidParamException('Syntax error.'); - case JSON_ERROR_STATE_MISMATCH: - throw new InvalidParamException('Invalid or malformed JSON.'); - case JSON_ERROR_UTF8: - throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); - default: - throw new InvalidParamException('Unknown JSON decoding error.'); - } + static::handleJsonError(json_last_error()); return $decode; } + /** + * Handles [[encode()]] and [[decode()]] errors by throwing exceptions with the respective error message. + * + * @param integer $lastError error code from [json_last_error()](http://php.net/manual/en/function.json-last-error.php). + * @throws \yii\base\InvalidParamException if there is any encoding/decoding error. + * @since 2.0.6 + */ + protected static function handleJsonError($lastError) + { + if ($lastError === JSON_ERROR_NONE) { + return; + } + + $availableErrors = []; + foreach (static::$jsonErrorMessages as $const => $message) { + if (defined($const)) { + $availableErrors[constant($const)] = $message; + } + } + + if (isset($availableErrors[$lastError])) { + throw new InvalidParamException($availableErrors[$lastError], $lastError); + } + + throw new InvalidParamException('Unknown JSON encoding/decoding error.'); + } + /** * Pre-processes the data before sending it to `json_encode()`. * @param mixed $data the data to be processed @@ -110,6 +163,8 @@ class BaseJson $data = $data->jsonSerialize(); } elseif ($data instanceof Arrayable) { $data = $data->toArray(); + } elseif ($data instanceof \SimpleXMLElement) { + $data = (array) $data; } else { $result = []; foreach ($data as $name => $value) { diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index 6f3fa6c8f6..0c50213bb4 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -55,10 +55,11 @@ class BaseMarkdown * * @param string $markdown the markdown text to parse * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return string the parsed HTML output * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ - public static function process($markdown, $flavor = 'original') + public static function process($markdown, $flavor = null) { $parser = static::getParser($flavor); @@ -72,10 +73,11 @@ class BaseMarkdown * * @param string $markdown the markdown text to parse * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return string the parsed HTML output * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ - public static function processParagraph($markdown, $flavor = 'original') + public static function processParagraph($markdown, $flavor = null) { $parser = static::getParser($flavor); @@ -83,23 +85,21 @@ class BaseMarkdown } /** - * @param string $flavor + * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return \cebe\markdown\Parser * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ protected static function getParser($flavor) { + if ($flavor === null) { + $flavor = static::$defaultFlavor; + } /* @var $parser \cebe\markdown\Markdown */ if (!isset(static::$flavors[$flavor])) { throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'"); } elseif (!is_object($config = static::$flavors[$flavor])) { - $parser = Yii::createObject($config); - if (is_array($config)) { - foreach ($config as $name => $value) { - $parser->{$name} = $value; - } - } - static::$flavors[$flavor] = $parser; + static::$flavors[$flavor] = Yii::createObject($config); } return static::$flavors[$flavor]; diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index eac76b3620..7d468b2acb 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -61,7 +61,7 @@ class BaseStringHelper */ public static function basename($path, $suffix = '') { - if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) === $suffix) { $path = mb_substr($path, 0, -$len); } $path = rtrim(str_replace('\\', '/', $path), '/\\'); @@ -105,7 +105,7 @@ class BaseStringHelper public static function truncate($string, $length, $suffix = '...', $encoding = null, $asHtml = false) { if ($asHtml) { - return self::truncateHtml($string, $length, $suffix, $encoding ?: Yii::$app->charset); + return static::truncateHtml($string, $length, $suffix, $encoding ?: Yii::$app->charset); } if (mb_strlen($string, $encoding ?: Yii::$app->charset) > $length) { @@ -128,7 +128,7 @@ class BaseStringHelper public static function truncateWords($string, $count, $suffix = '...', $asHtml = false) { if ($asHtml) { - return self::truncateHtml($string, $count, $suffix); + return static::truncateHtml($string, $count, $suffix); } $words = preg_split('/(\s+)/u', trim($string), null, PREG_SPLIT_DELIM_CAPTURE); @@ -141,7 +141,7 @@ class BaseStringHelper /** * Truncate a string while preserving the HTML. - * + * * @param string $string The string to truncate * @param integer $count * @param string $suffix String to append to the end of the truncated string. @@ -152,6 +152,7 @@ class BaseStringHelper protected static function truncateHtml($string, $count, $suffix, $encoding = false) { $config = \HTMLPurifier_Config::create(null); + $config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); $lexer = \HTMLPurifier_Lexer::create($config); $tokens = $lexer->tokenizeHTML($string, $config, null); $openTokens = 0; @@ -161,10 +162,10 @@ class BaseStringHelper if ($token instanceof \HTMLPurifier_Token_Start) { //Tag begins $openTokens++; $truncated[] = $token; - } else if ($token instanceof \HTMLPurifier_Token_Text && $totalCount <= $count) { //Text + } elseif ($token instanceof \HTMLPurifier_Token_Text && $totalCount <= $count) { //Text if (false === $encoding) { $token->data = self::truncateWords($token->data, $count - $totalCount, ''); - $currentCount = str_word_count($token->data); + $currentCount = self::countWords($token->data); } else { $token->data = self::truncate($token->data, $count - $totalCount, '', $encoding) . ' '; $currentCount = mb_strlen($token->data, $encoding); @@ -174,10 +175,10 @@ class BaseStringHelper $token->data = ' ' . $token->data; } $truncated[] = $token; - } else if ($token instanceof \HTMLPurifier_Token_End) { //Tag ends + } elseif ($token instanceof \HTMLPurifier_Token_End) { //Tag ends $openTokens--; $truncated[] = $token; - } else if ($token instanceof \HTMLPurifier_Token_Empty) { //Self contained tags, i.e. etc. + } elseif ($token instanceof \HTMLPurifier_Token_Empty) { //Self contained tags, i.e. etc. $truncated[] = $token; } if (0 === $openTokens && $totalCount >= $count) { @@ -186,7 +187,7 @@ class BaseStringHelper } $context = new \HTMLPurifier_Context(); $generator = new \HTMLPurifier_Generator($config, $context); - return $generator->generateFromTokens($truncated) . $suffix; + return $generator->generateFromTokens($truncated) . ($totalCount >= $count ? $suffix : ''); } /** @@ -247,17 +248,22 @@ class BaseStringHelper * @param boolean $skipEmpty Whether to skip empty strings between delimiters. Default is false. * @return array <<<<<<< HEAD +<<<<<<< HEAD ======= * @since 2.0.4 >>>>>>> yiichina/master +======= + * @since 2.0.4 +>>>>>>> master */ - public static function explode($string, $delimiter = ',', $trim = true, $skipEmpty = false) { + public static function explode($string, $delimiter = ',', $trim = true, $skipEmpty = false) + { $result = explode($delimiter, $string); if ($trim) { if ($trim === true) { $trim = 'trim'; } elseif (!is_callable($trim)) { - $trim = function($v) use ($trim) { + $trim = function ($v) use ($trim) { return trim($v, $trim); }; } @@ -265,8 +271,22 @@ class BaseStringHelper } if ($skipEmpty) { // Wrapped with array_values to make array keys sequential after empty values removing - $result = array_values(array_filter($result)); + $result = array_values(array_filter($result, function ($value) { + return $value !== ''; + })); } return $result; } + + /** + * Counts words in a string + * @since 2.0.8 + * + * @param string $string + * @return integer + */ + public static function countWords($string) + { + return count(preg_split('/\s+/u', $string, null, PREG_SPLIT_NO_EMPTY)); + } } diff --git a/framework/helpers/BaseUrl.php b/framework/helpers/BaseUrl.php index 4f740f76e2..929c137979 100644 --- a/framework/helpers/BaseUrl.php +++ b/framework/helpers/BaseUrl.php @@ -20,6 +20,13 @@ use yii\base\InvalidParamException; */ class BaseUrl { + /** + * @var \yii\web\UrlManager URL manager to use for creating URLs + * @since 2.0.8 + */ + public static $urlManager; + + /** * Creates a URL for the given route. * @@ -58,19 +65,19 @@ class BaseUrl * Below are some examples of using this method: * * ```php - * // /index.php?r=site/index + * // /index.php?r=site%2Findex * echo Url::toRoute('site/index'); * - * // /index.php?r=site/index&src=ref1#name + * // /index.php?r=site%2Findex&src=ref1#name * echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); * - * // http://www.example.com/index.php?r=site/index + * // http://www.example.com/index.php?r=site%2Findex * echo Url::toRoute('site/index', true); * - * // https://www.example.com/index.php?r=site/index + * // https://www.example.com/index.php?r=site%2Findex * echo Url::toRoute('site/index', 'https'); * - * // /index.php?r=post/index assume the alias "@posts" is defined as "post/index" + * // /index.php?r=post%2Findex assume the alias "@posts" is defined as "post/index" * echo Url::toRoute('@posts'); * ``` * @@ -91,9 +98,9 @@ class BaseUrl $route[0] = static::normalizeRoute($route[0]); if ($scheme) { - return Yii::$app->getUrlManager()->createAbsoluteUrl($route, is_string($scheme) ? $scheme : null); + return static::getUrlManager()->createAbsoluteUrl($route, is_string($scheme) ? $scheme : null); } else { - return Yii::$app->getUrlManager()->createUrl($route); + return static::getUrlManager()->createUrl($route); } } @@ -160,13 +167,13 @@ class BaseUrl * Below are some examples of using this method: * * ```php - * // /index.php?r=site/index + * // /index.php?r=site%2Findex * echo Url::to(['site/index']); * - * // /index.php?r=site/index&src=ref1#name + * // /index.php?r=site%2Findex&src=ref1#name * echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); * - * // /index.php?r=post/index assume the alias "@posts" is defined as "/post/index" + * // /index.php?r=post%2Findex assume the alias "@posts" is defined as "/post/index" * echo Url::to(['@posts']); * * // the currently requested URL @@ -216,9 +223,9 @@ class BaseUrl return is_string($scheme) ? "$scheme:$url" : $url; } - if (($pos = strpos($url, ':')) == false || !ctype_alpha(substr($url, 0, $pos))) { + if (($pos = strpos($url, ':')) === false || !ctype_alpha(substr($url, 0, $pos))) { // turn relative URL into absolute - $url = Yii::$app->getUrlManager()->getHostInfo() . '/' . ltrim($url, '/'); + $url = static::getUrlManager()->getHostInfo() . '/' . ltrim($url, '/'); } if (is_string($scheme) && ($pos = strpos($url, ':')) !== false) { @@ -240,9 +247,9 @@ class BaseUrl */ public static function base($scheme = false) { - $url = Yii::$app->getUrlManager()->getBaseUrl(); + $url = static::getUrlManager()->getBaseUrl(); if ($scheme) { - $url = Yii::$app->getUrlManager()->getHostInfo() . $url; + $url = static::getUrlManager()->getHostInfo() . $url; if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) { $url = $scheme . substr($url, $pos); } @@ -304,7 +311,7 @@ class BaseUrl $params = Yii::$app->controller->actionParams; $params[0] = Yii::$app->controller->getRoute(); - return Yii::$app->getUrlManager()->createAbsoluteUrl($params); + return static::getUrlManager()->createAbsoluteUrl($params); } /** @@ -323,7 +330,7 @@ class BaseUrl $url = Yii::$app->getHomeUrl(); if ($scheme) { - $url = Yii::$app->getUrlManager()->getHostInfo() . $url; + $url = static::getUrlManager()->getHostInfo() . $url; if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) { $url = $scheme . substr($url, $pos); } @@ -354,13 +361,13 @@ class BaseUrl * ```php * // assume $_GET = ['id' => 123, 'src' => 'google'], current route is "post/view" * - * // /index.php?r=post/view&id=123&src=google + * // /index.php?r=post%2Fview&id=123&src=google * echo Url::current(); * - * // /index.php?r=post/view&id=123 + * // /index.php?r=post%2Fview&id=123 * echo Url::current(['src' => null]); * - * // /index.php?r=post/view&id=100&src=google + * // /index.php?r=post%2Fview&id=100&src=google * echo Url::current(['id' => 100]); * ``` * @@ -382,4 +389,13 @@ class BaseUrl $route = ArrayHelper::merge($currentParams, $params); return static::toRoute($route, $scheme); } + + /** + * @return \yii\web\UrlManager URL manager used to create URLs + * @since 2.0.8 + */ + protected static function getUrlManager() + { + return static::$urlManager ?: Yii::$app->getUrlManager(); + } } diff --git a/framework/helpers/BaseVarDumper.php b/framework/helpers/BaseVarDumper.php index a7fa370da2..8292fcd329 100644 --- a/framework/helpers/BaseVarDumper.php +++ b/framework/helpers/BaseVarDumper.php @@ -7,6 +7,9 @@ namespace yii\helpers; +use yii\base\Arrayable; +use yii\base\InvalidValueException; + /** * BaseVarDumper provides concrete implementation for [[VarDumper]]. * @@ -81,7 +84,7 @@ class BaseVarDumper self::$_output .= '{resource}'; break; case 'NULL': - self::$_output .= "null"; + self::$_output .= 'null'; break; case 'unknown type': self::$_output .= '{unknown}'; @@ -114,7 +117,15 @@ class BaseVarDumper $className = get_class($var); $spaces = str_repeat(' ', $level * 4); self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ((array) $var as $key => $value) { + if ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__debugInfo')) { + $dumpValues = $var->__debugInfo(); + if (!is_array($dumpValues)) { + throw new InvalidValueException('__debuginfo() must return an array'); + } + } else { + $dumpValues = (array) $var; + } + foreach ($dumpValues as $key => $value) { $keyDisplay = strtr(trim($key), "\0", ':'); self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; self::dumpInternal($value, $level + 1); @@ -163,7 +174,7 @@ class BaseVarDumper self::$_output .= '[]'; } else { $keys = array_keys($var); - $outputKeys = ($keys !== range(0, sizeof($var) - 1)); + $outputKeys = ($keys !== range(0, count($var) - 1)); $spaces = str_repeat(' ', $level * 4); self::$_output .= '['; foreach ($keys as $key) { @@ -179,6 +190,7 @@ class BaseVarDumper } break; case 'object': +<<<<<<< HEAD <<<<<<< HEAD self::$_output .= 'unserialize(' . var_export(serialize($var), true) . ')'; ======= @@ -191,9 +203,85 @@ class BaseVarDumper } self::$_output .= $output; >>>>>>> yiichina/master +======= + if ($var instanceof \Closure) { + self::$_output .= self::exportClosure($var); + } else { + try { + $output = 'unserialize(' . var_export(serialize($var), true) . ')'; + } catch (\Exception $e) { + // serialize may fail, for example: if object contains a `\Closure` instance + // so we use a fallback + if ($var instanceof Arrayable) { + self::exportInternal($var->toArray(), $level); + return; + } elseif ($var instanceof \IteratorAggregate) { + $varAsArray = []; + foreach ($var as $key => $value) { + $varAsArray[$key] = $value; + } + self::exportInternal($varAsArray, $level); + return; + } elseif ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__toString')) { + $output = var_export($var->__toString(), true); + } else { + $outputBackup = self::$_output; + $output = var_export(self::dumpAsString($var), true); + self::$_output = $outputBackup; + } + } + self::$_output .= $output; + } +>>>>>>> master break; default: self::$_output .= var_export($var, true); } } + + /** + * Exports a [[Closure]] instance. + * @param \Closure $closure closure instance. + * @return string + */ + private static function exportClosure(\Closure $closure) + { + $reflection = new \ReflectionFunction($closure); + + $fileName = $reflection->getFileName(); + $start = $reflection->getStartLine(); + $end = $reflection->getEndLine(); + + if ($fileName === false || $start === false || $end === false) { + return 'function() {/* Error: unable to determine Closure source */}'; + } + + --$start; + + $source = implode("\n", array_slice(file($fileName), $start, $end - $start)); + $tokens = token_get_all(' * @since 2.0 diff --git a/framework/helpers/mimeTypes.php b/framework/helpers/mimeTypes.php index 46ede3d7db..5da7ee30bd 100644 --- a/framework/helpers/mimeTypes.php +++ b/framework/helpers/mimeTypes.php @@ -414,6 +414,7 @@ return [ 'm3a' => 'audio/mpeg', 'm3u' => 'audio/x-mpegurl', 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', 'm4u' => 'video/vnd.mpegurl', 'm4v' => 'video/x-m4v', 'ma' => 'application/mathematica', diff --git a/framework/i18n/DbMessageSource.php b/framework/i18n/DbMessageSource.php index d8d4c7eb05..3ad1b4bcdd 100644 --- a/framework/i18n/DbMessageSource.php +++ b/framework/i18n/DbMessageSource.php @@ -9,6 +9,7 @@ namespace yii\i18n; use Yii; use yii\base\InvalidConfigException; +use yii\db\Expression; use yii\di\Instance; use yii\helpers\ArrayHelper; use yii\caching\Cache; @@ -19,29 +20,20 @@ use yii\db\Query; * DbMessageSource extends [[MessageSource]] and represents a message source that stores translated * messages in database. * - * The database must contain the following two tables: - * - * ~~~ - * CREATE TABLE source_message ( - * id INTEGER PRIMARY KEY AUTO_INCREMENT, - * category VARCHAR(32), - * message TEXT - * ); - * - * CREATE TABLE message ( - * id INTEGER, - * language VARCHAR(16), - * translation TEXT, - * PRIMARY KEY (id, language), - * CONSTRAINT fk_message_source_message FOREIGN KEY (id) - * REFERENCES source_message (id) ON DELETE CASCADE ON UPDATE RESTRICT - * ); - * ~~~ + * The database must contain the following two tables: source_message and message. * * The `source_message` table stores the messages to be translated, and the `message` table stores * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]] * and [[messageTable]], respectively. * + * The database connection is specified by [[db]]. Database schema could be initialized by applying migration: + * + * ``` + * yii migrate --migrationPath=@yii/i18n/migrations/ + * ``` + * + * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory. + * * @author resurtm * @since 2.0 */ @@ -49,23 +41,30 @@ class DbMessageSource extends MessageSource { /** * Prefix which would be used when generating cache key. + * @deprecated This constant has never been used and will be removed in 2.1.0. */ const CACHE_KEY_PREFIX = 'DbMessageSource'; /** * @var Connection|array|string the DB connection object or the application component ID of the DB connection. + * * After the DbMessageSource object is created, if you want to change this property, you should only assign * it with a DB connection object. + * * Starting from version 2.0.2, this can also be a configuration array for creating the object. */ public $db = 'db'; /** * @var Cache|array|string the cache object or the application component ID of the cache object. - * The messages data will be cached using this cache object. Note, this property has meaning only - * in case [[cachingDuration]] set to non-zero value. + * The messages data will be cached using this cache object. + * Note, that to enable caching you have to set [[enableCaching]] to `true`, otherwise setting this property has no effect. + * * After the DbMessageSource object is created, if you want to change this property, you should only assign * it with a cache object. + * * Starting from version 2.0.2, this can also be a configuration array for creating the object. + * @see cachingDuration + * @see enableCaching */ public $cache = 'cache'; /** @@ -142,26 +141,49 @@ class DbMessageSource extends MessageSource */ protected function loadMessagesFromDb($category, $language) { - $mainQuery = new Query(); - $mainQuery->select(['t1.message message', 't2.translation translation']) - ->from(["$this->sourceMessageTable t1", "$this->messageTable t2"]) - ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :language') - ->params([':category' => $category, ':language' => $language]); + $mainQuery = (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation']) + ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable]) + ->where([ + 't1.id' => new Expression('[[t2.id]]'), + 't1.category' => $category, + 't2.language' => $language, + ]); $fallbackLanguage = substr($language, 0, 2); - if ($fallbackLanguage != $language) { - $fallbackQuery = new Query(); - $fallbackQuery->select(['t1.message message', 't2.translation translation']) - ->from(["$this->sourceMessageTable t1", "$this->messageTable t2"]) - ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :fallbackLanguage') - ->andWhere("t2.id NOT IN (SELECT id FROM $this->messageTable WHERE language = :language)") - ->params([':category' => $category, ':language' => $language, ':fallbackLanguage' => $fallbackLanguage]); + $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2); - $mainQuery->union($fallbackQuery, true); + if ($fallbackLanguage !== $language) { + $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackLanguage), true); + } elseif ($language === $fallbackSourceLanguage) { + $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackSourceLanguage), true); } $messages = $mainQuery->createCommand($this->db)->queryAll(); return ArrayHelper::map($messages, 'message', 'translation'); } + + /** + * The method builds the [[Query]] object for the fallback language messages search. + * Normally is called from [[loadMessagesFromDb]]. + * + * @param string $category the message category + * @param string $language the originally requested language + * @param string $fallbackLanguage the target fallback language + * @return Query + * @see loadMessagesFromDb + * @since 2.0.7 + */ + protected function createFallbackQuery($category, $language, $fallbackLanguage) + { + return (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation']) + ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable]) + ->where([ + 't1.id' => new Expression('[[t2.id]]'), + 't1.category' => $category, + 't2.language' => $fallbackLanguage, + ])->andWhere([ + 'NOT IN', 't2.id', (new Query())->select('[[id]]')->from($this->messageTable)->where(['language' => $language]) + ]); + } } diff --git a/framework/i18n/Formatter.php b/framework/i18n/Formatter.php index 15857c5f0d..8a47eeb976 100644 --- a/framework/i18n/Formatter.php +++ b/framework/i18n/Formatter.php @@ -95,7 +95,7 @@ class Formatter extends Component * * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * For example: * @@ -111,7 +111,7 @@ class Formatter extends Component * * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * For example: * @@ -128,7 +128,7 @@ class Formatter extends Component * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). * * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * For example: * @@ -138,6 +138,37 @@ class Formatter extends Component * ``` */ public $datetimeFormat = 'medium'; + /** + * @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly + * passed to the [constructor of the `IntlDateFormatter` class](http://php.net/manual/en/intldateformatter.create.php). + * + * Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant + * `\IntlDateFormatter::GREGORIAN` for Gregorian calendar. + * + * To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar), + * set this property to `\IntlDateFormatter::TRADITIONAL`. + * The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be: + * + * ```php + * 'formatter' => [ + * 'locale' => 'fa_IR@calendar=persian', + * 'calendar' => \IntlDateFormatter::TRADITIONAL, + * ], + * ``` + * + * Available calendar names can be found in the [ICU manual](http://userguide.icu-project.org/datetime/calendar). + * + * Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class. + * Check the [PHP manual](http://php.net/manual/en/intldateformatter.create.php) for more details. + * + * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, setting this property will have no effect. + * + * @see http://php.net/manual/en/intldateformatter.create.php + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes + * @see http://php.net/manual/en/class.intlcalendar.php + * @since 2.0.7 + */ + public $calendar; /** * @var string the character displayed as the decimal point when formatting a number. * If not set, the decimal separator corresponding to [[locale]] will be used. @@ -189,7 +220,10 @@ class Formatter extends Component public $numberFormatterTextOptions = []; /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * @var array a list of name value pairs that are passed to the * intl [Numberformatter::setSymbol()](http://php.net/manual/en/numberformatter.setsymbol.php) method of all * the number formatter objects created by [[createNumberFormatter()]]. @@ -198,7 +232,11 @@ class Formatter extends Component * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol) * for the possible options. * +<<<<<<< HEAD * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/de/20BD/) instead of `руб.` for Russian Ruble: +======= + * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble: +>>>>>>> master * * ```php * [ @@ -210,7 +248,10 @@ class Formatter extends Component */ public $numberFormatterSymbols = []; /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]]. * If not set, the currency code corresponding to [[locale]] will be used. * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it @@ -218,7 +259,7 @@ class Formatter extends Component */ public $currencyCode; /** - * @var array the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]]. + * @var integer the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]]. * Defaults to 1024. */ public $sizeFormatBase = 1024; @@ -450,7 +491,7 @@ class Formatter extends Component * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). * * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * @return string the formatted result. * @throws InvalidParamException if the input value can not be evaluated as a date value. @@ -482,7 +523,7 @@ class Formatter extends Component * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). * * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * @return string the formatted result. * @throws InvalidParamException if the input value can not be evaluated as a date value. @@ -514,7 +555,7 @@ class Formatter extends Component * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). * * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the - * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * PHP [date()](http://php.net/manual/en/function.date.php)-function. * * @return string the formatted result. * @throws InvalidParamException if the input value can not be evaluated as a date value. @@ -571,20 +612,20 @@ class Formatter extends Component // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP $year = $timestamp->format('Y'); - if ($this->_intlLoaded && !(PHP_INT_SIZE == 4 && ($year <= 1901 || $year >= 2038))) { + if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) { if (strncmp($format, 'php:', 4) === 0) { $format = FormatConverter::convertDatePhpToIcu(substr($format, 4)); } if (isset($this->_dateFormats[$format])) { if ($type === 'date') { - $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone); + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar); } elseif ($type === 'time') { - $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone); + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar); } else { - $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone); + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar); } } else { - $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, null, $format); + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format); } if ($formatter === null) { throw new InvalidConfigException(intl_get_error_message()); @@ -644,9 +685,9 @@ class Formatter extends Component } try { if (is_numeric($value)) { // process as unix timestamp, which is always in UTC - if (($timestamp = DateTime::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) { - throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp."); - } + $timestamp = new DateTime(); + $timestamp->setTimezone(new DateTimeZone('UTC')); + $timestamp->setTimestamp($value); return $checkTimeInfo ? [$timestamp, true] : $timestamp; } elseif (($timestamp = DateTime::createFromFormat('Y-m-d', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01) return $checkTimeInfo ? [$timestamp, false] : $timestamp; @@ -661,7 +702,7 @@ class Formatter extends Component } else { return new DateTime($value, new DateTimeZone($this->defaultTimeZone)); } - } catch(\Exception $e) { + } catch (\Exception $e) { throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage() . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e); } @@ -790,6 +831,72 @@ class Formatter extends Component } } + /** + * Represents the value as duration in human readable format. + * + * @param DateInterval|string|integer $value the value to be formatted. Acceptable formats: + * - [DateInterval object](http://php.net/manual/ru/class.dateinterval.php) + * - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds` + * - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration: + * `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values + * `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value + * `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value + * `P1D2H30M` - simply a date interval + * `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`) + * + * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `. + * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`. + * @return string the formatted duration. + * @since 2.0.7 + */ + public function asDuration($value, $implodeString = ', ', $negativeSign = '-') + { + if ($value === null) { + return $this->nullDisplay; + } + + if ($value instanceof DateInterval) { + $isNegative = $value->invert; + $interval = $value; + } elseif (is_numeric($value)) { + $isNegative = $value < 0; + $zeroDateTime = (new DateTime())->setTimestamp(0); + $valueDateTime = (new DateTime())->setTimestamp(abs($value)); + $interval = $valueDateTime->diff($zeroDateTime); + } elseif (strpos($value, 'P-') === 0) { + $interval = new DateInterval('P'.substr($value, 2)); + $isNegative = true; + } else { + $interval = new DateInterval($value); + $isNegative = $interval->invert; + } + + if ($interval->y > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->locale); + } + if ($interval->m > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->locale); + } + if ($interval->d > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale); + } + if ($interval->h > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale); + } + if ($interval->i > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale); + } + if ($interval->s > 0) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale); + } + if ($interval->s === 0 && empty($parts)) { + $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale); + $isNegative = false; + } + + return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts)); + } + // number formats @@ -801,11 +908,15 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master */ public function asInteger($value, $options = [], $textOptions = []) { @@ -816,14 +927,20 @@ class Formatter extends Component if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions); $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value, NumberFormatter::TYPE_INT64); ======= +======= +>>>>>>> master if (($result = $f->format($value, NumberFormatter::TYPE_INT64)) === false) { throw new InvalidParamException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator); } @@ -841,11 +958,15 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @see decimalSeparator * @see thousandSeparator */ @@ -858,16 +979,22 @@ class Formatter extends Component if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value); ======= +======= +>>>>>>> master if (($result = $f->format($value)) === false) { throw new InvalidParamException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { - if ($decimals === null){ + if ($decimals === null) { $decimals = 2; } return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); @@ -883,11 +1010,15 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master */ public function asPercent($value, $decimals = null, $options = [], $textOptions = []) { @@ -898,19 +1029,25 @@ class Formatter extends Component if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value); ======= +======= +>>>>>>> master if (($result = $f->format($value)) === false) { throw new InvalidParamException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { - if ($decimals === null){ + if ($decimals === null) { $decimals = 0; } - $value = $value * 100; + $value *= 100; return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%'; } } @@ -923,11 +1060,15 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master */ public function asScientific($value, $decimals = null, $options = [], $textOptions = []) { @@ -936,21 +1077,27 @@ class Formatter extends Component } $value = $this->normalizeNumericValue($value); - if ($this->_intlLoaded){ + if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value); ======= +======= +>>>>>>> master if (($result = $f->format($value)) === false) { throw new InvalidParamException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { if ($decimals !== null) { return sprintf("%.{$decimals}E", $value); } else { - return sprintf("%.E", $value); + return sprintf('%.E', $value); } } } @@ -958,6 +1105,7 @@ class Formatter extends Component /** * Formats the value as a currency number. * +<<<<<<< HEAD <<<<<<< HEAD * This function does not requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed * to work but it is highly recommended to install it to get good formatting results. @@ -965,6 +1113,10 @@ class Formatter extends Component * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed * to work, but it is highly recommended to install it to get good formatting results. >>>>>>> yiichina/master +======= + * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed + * to work, but it is highly recommended to install it to get good formatting results. +>>>>>>> master * * @param mixed $value the value to be formatted. * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use. @@ -972,11 +1124,15 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined. */ public function asCurrency($value, $currency = null, $options = [], $textOptions = []) @@ -990,16 +1146,28 @@ class Formatter extends Component $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions); if ($currency === null) { if ($this->currencyCode === null) { +<<<<<<< HEAD <<<<<<< HEAD $currency = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL); } else { $currency = $this->currencyCode; +======= + if (($result = $formatter->format($value)) === false) { + throw new InvalidParamException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage()); + } + return $result; +>>>>>>> master } + $currency = $this->currencyCode; } - return $formatter->formatCurrency($value, $currency); + if (($result = $formatter->formatCurrency($value, $currency)) === false) { + throw new InvalidParamException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage()); + } + return $result; } else { if ($currency === null) { if ($this->currencyCode === null) { +<<<<<<< HEAD throw new InvalidConfigException('The default currency code for the formatter is not defined.'); ======= if (($result = $formatter->format($value)) === false) { @@ -1018,6 +1186,9 @@ class Formatter extends Component if ($this->currencyCode === null) { throw new InvalidConfigException('The default currency code for the formatter is not defined and the php intl extension is not installed which could take the default currency from the locale.'); >>>>>>> yiichina/master +======= + throw new InvalidConfigException('The default currency code for the formatter is not defined and the php intl extension is not installed which could take the default currency from the locale.'); +>>>>>>> master } $currency = $this->currencyCode; } @@ -1032,11 +1203,15 @@ class Formatter extends Component * * @param mixed $value the value to be formatted * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available. */ public function asSpellout($value) @@ -1045,16 +1220,22 @@ class Formatter extends Component return $this->nullDisplay; } $value = $this->normalizeNumericValue($value); - if ($this->_intlLoaded){ + if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value); ======= +======= +>>>>>>> master if (($result = $f->format($value)) === false) { throw new InvalidParamException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.'); } @@ -1067,11 +1248,15 @@ class Formatter extends Component * * @param mixed $value the value to be formatted * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available. */ public function asOrdinal($value) @@ -1080,16 +1265,22 @@ class Formatter extends Component return $this->nullDisplay; } $value = $this->normalizeNumericValue($value); - if ($this->_intlLoaded){ + if ($this->_intlLoaded) { $f = $this->createNumberFormatter(NumberFormatter::ORDINAL); +<<<<<<< HEAD <<<<<<< HEAD return $f->format($value); ======= +======= +>>>>>>> master if (($result = $f->format($value)) === false) { throw new InvalidParamException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage()); } return $result; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } else { throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.'); } @@ -1103,16 +1294,20 @@ class Formatter extends Component * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...) * are used in the formatting result. * - * @param integer $value value in bytes to be formatted. + * @param string|integer|float $value value in bytes to be formatted. * @param integer $decimals the number of digits after the decimal point. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @see sizeFormat * @see asSize */ @@ -1126,21 +1321,33 @@ class Formatter extends Component if ($this->sizeFormatBase == 1024) { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale); } } else { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} KB', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} MB', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} GB', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} TB', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} PB', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} KB', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} MB', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} GB', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} TB', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} PB', $params, $this->locale); } } } @@ -1151,16 +1358,20 @@ class Formatter extends Component * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...) * are used in the formatting result. * - * @param integer $value value in bytes to be formatted. + * @param string|integer|float $value value in bytes to be formatted. * @param integer $decimals the number of digits after the decimal point. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return string the formatted result. +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master * @see sizeFormat * @see asShortSize */ @@ -1174,21 +1385,33 @@ class Formatter extends Component if ($this->sizeFormatBase == 1024) { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale); } } else { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale); } } } @@ -1202,27 +1425,26 @@ class Formatter extends Component * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. * @return array [parameters for Yii::t containing formatted number, internal position of size unit] +<<<<<<< HEAD <<<<<<< HEAD * @throws InvalidParamException if the input value is not numeric. ======= * @throws InvalidParamException if the input value is not numeric or the formatting failed. >>>>>>> yiichina/master +======= + * @throws InvalidParamException if the input value is not numeric or the formatting failed. +>>>>>>> master */ private function formatSizeNumber($value, $decimals, $options, $textOptions) { - if (is_string($value) && is_numeric($value)) { - $value = (int) $value; - } - if (!is_numeric($value)) { - throw new InvalidParamException("'$value' is not a numeric value."); - } + $value = $this->normalizeNumericValue($value); $position = 0; do { - if ($value < $this->sizeFormatBase) { + if (abs($value) < $this->sizeFormatBase) { break; } - $value = $value / $this->sizeFormatBase; + $value /= $this->sizeFormatBase; $position++; } while ($position < 5); @@ -1241,7 +1463,9 @@ class Formatter extends Component // format the size value $params = [ // this is the unformatted number used for the plural rule - 'n' => $value, + // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this + // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural + 'n' => abs($value), // this is the formatted number used for display 'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions), ]; @@ -1251,6 +1475,7 @@ class Formatter extends Component } /** +<<<<<<< HEAD <<<<<<< HEAD * @param $value * @return float @@ -1261,12 +1486,22 @@ class Formatter extends Component * - everything [empty](http://php.net/manual/de/function.empty.php) will result in `0` * - a [numeric](http://php.net/manual/de/function.is-numeric.php) string will be casted to float * - everything else will be returned if it is [numeric](http://php.net/manual/de/function.is-numeric.php), +======= + * Normalizes a numeric input value + * + * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0` + * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float + * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php), +>>>>>>> master * otherwise an exception is thrown. * * @param mixed $value the input value * @return float|integer the normalized number value * @throws InvalidParamException if the input value is not numeric. +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master */ protected function normalizeNumericValue($value) { @@ -1289,7 +1524,7 @@ class Formatter extends Component * * @param integer $style the type of the number formatter. * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL - * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE + * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE * @param integer $decimals the number of digits after the decimal point. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. @@ -1299,24 +1534,15 @@ class Formatter extends Component { $formatter = new NumberFormatter($this->locale, $style); - if ($this->decimalSeparator !== null) { - $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator); - } - if ($this->thousandSeparator !== null) { - $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator); - } - - if ($decimals !== null) { - $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals); - $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals); - } - + // set text attributes foreach ($this->numberFormatterTextOptions as $name => $attribute) { $formatter->setTextAttribute($name, $attribute); } foreach ($textOptions as $name => $attribute) { $formatter->setTextAttribute($name, $attribute); } + + // set attributes foreach ($this->numberFormatterOptions as $name => $value) { $formatter->setAttribute($name, $value); } @@ -1324,11 +1550,30 @@ class Formatter extends Component $formatter->setAttribute($name, $value); } <<<<<<< HEAD +<<<<<<< HEAD ======= foreach ($this->numberFormatterSymbols as $name => $symbol) { $formatter->setSymbol($name, $symbol); } >>>>>>> yiichina/master +======= + if ($decimals !== null) { + $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals); + $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals); + } + + // set symbols + if ($this->decimalSeparator !== null) { + $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator); + } + if ($this->thousandSeparator !== null) { + $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator); + } + foreach ($this->numberFormatterSymbols as $name => $symbol) { + $formatter->setSymbol($name, $symbol); + } + +>>>>>>> master return $formatter; } } diff --git a/framework/i18n/GettextMessageSource.php b/framework/i18n/GettextMessageSource.php index 2d9e26967d..18981a6c8c 100644 --- a/framework/i18n/GettextMessageSource.php +++ b/framework/i18n/GettextMessageSource.php @@ -50,14 +50,19 @@ class GettextMessageSource extends MessageSource /** - * Loads the message translation for the specified language and category. + * Loads the message translation for the specified $language and $category. * If translation for specific locale code such as `en-US` isn't found it - * tries more generic `en`. + * tries more generic `en`. When both are present, the `en-US` messages will be merged + * over `en`. See [[loadFallbackMessages]] for details. + * If the $language is less specific than [[sourceLanguage]], the method will try to + * load the messages for [[sourceLanguage]]. For example: [[sourceLanguage]] is `en-GB`, + * $language is `en`. The method will load the messages for `en` and merge them over `en-GB`. * * @param string $category the message category * @param string $language the target language - * @return array the loaded messages. The keys are original messages, and the values - * are translated messages. + * @return array the loaded messages. The keys are original messages, and the values are translated messages. + * @see loadFallbackMessages + * @see sourceLanguage */ protected function loadMessages($category, $language) { @@ -65,21 +70,12 @@ class GettextMessageSource extends MessageSource $messages = $this->loadMessagesFromFile($messageFile, $category); $fallbackLanguage = substr($language, 0, 2); - if ($fallbackLanguage != $language) { - $fallbackMessageFile = $this->getMessageFilePath($fallbackLanguage); - $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile, $category); + $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2); - if ($messages === null && $fallbackMessages === null && $fallbackLanguage != $this->sourceLanguage) { - Yii::error("The message file for category '$category' does not exist: $messageFile Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); - } elseif (empty($messages)) { - return $fallbackMessages; - } elseif (!empty($fallbackMessages)) { - foreach ($fallbackMessages as $key => $value) { - if (!empty($value) && empty($messages[$key])) { - $messages[$key] = $fallbackMessages[$key]; - } - } - } + if ($fallbackLanguage !== $language) { + $this->loadFallbackMessages($category, $fallbackLanguage, $messages, $messageFile); + } elseif ($language === $fallbackSourceLanguage) { + $messages = $this->loadFallbackMessages($category, $this->sourceLanguage, $messages, $messageFile); } else { if ($messages === null) { Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); @@ -89,6 +85,44 @@ class GettextMessageSource extends MessageSource return (array) $messages; } + /** + * The method is normally called by [[loadMessages]] to load the fallback messages for the language. + * Method tries to load the $category messages for the $fallbackLanguage and adds them to the $messages array. + * + * @param string $category the message category + * @param string $fallbackLanguage the target fallback language + * @param array $messages the array of previously loaded translation messages. + * The keys are original messages, and the values are the translated messages. + * @param string $originalMessageFile the path to the file with messages. Used to log an error message + * in case when no translations were found. + * @return array the loaded messages. The keys are original messages, and the values are the translated messages. + * @since 2.0.7 + */ + protected function loadFallbackMessages($category, $fallbackLanguage, $messages, $originalMessageFile) + { + $fallbackMessageFile = $this->getMessageFilePath($fallbackLanguage); + $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile, $category); + + if ( + $messages === null && $fallbackMessages === null + && $fallbackLanguage !== $this->sourceLanguage + && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2) + ) { + Yii::error("The message file for category '$category' does not exist: $originalMessageFile " + . "Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); + } elseif (empty($messages)) { + return $fallbackMessages; + } elseif (!empty($fallbackMessages)) { + foreach ($fallbackMessages as $key => $value) { + if (!empty($value) && empty($messages[$key])) { + $messages[$key] = $fallbackMessages[$key]; + } + } + } + + return (array) $messages; + } + /** * Returns message file path for the specified language and category. * diff --git a/framework/i18n/GettextMoFile.php b/framework/i18n/GettextMoFile.php index 6946425150..27d958b6d6 100644 --- a/framework/i18n/GettextMoFile.php +++ b/framework/i18n/GettextMoFile.php @@ -79,7 +79,7 @@ class GettextMoFile extends GettextFile // revision $revision = $this->readInteger($fileHandle); - if ($revision != 0) { + if ($revision !== 0) { throw new Exception('Invalid MO file revision: ' . $revision . '.'); } diff --git a/framework/i18n/GettextPoFile.php b/framework/i18n/GettextPoFile.php index 7dd29253c6..3f61141be1 100644 --- a/framework/i18n/GettextPoFile.php +++ b/framework/i18n/GettextPoFile.php @@ -35,7 +35,7 @@ class GettextPoFile extends GettextFile $messages = []; for ($i = 0; $i < $matchCount; ++$i) { - if ($matches[2][$i] == $context) { + if ($matches[2][$i] === $context) { $id = $this->decode($matches[3][$i]); $message = $this->decode($matches[4][$i]); $messages[$id] = $message; diff --git a/framework/i18n/MessageFormatter.php b/framework/i18n/MessageFormatter.php index 65285fa1b0..2d68a391d3 100644 --- a/framework/i18n/MessageFormatter.php +++ b/framework/i18n/MessageFormatter.php @@ -7,6 +7,7 @@ namespace yii\i18n; +use Yii; use yii\base\Component; use yii\base\NotSupportedException; @@ -93,16 +94,15 @@ class MessageFormatter extends Component return $this->fallbackFormat($pattern, $params, $language); } - if (version_compare(PHP_VERSION, '5.5.0', '<') || version_compare(INTL_ICU_VERSION, '4.8', '<')) { - // replace named arguments - $pattern = $this->replaceNamedArguments($pattern, $params, $newParams); - $params = $newParams; - } + // replace named arguments (https://github.com/yiisoft/yii2/issues/9678) + $newParams = []; + $pattern = $this->replaceNamedArguments($pattern, $params, $newParams); + $params = $newParams; $formatter = new \MessageFormatter($language, $pattern); if ($formatter === null) { $this->_errorCode = intl_get_error_code(); - $this->_errorMessage = "Message pattern is invalid: " . intl_get_error_message(); + $this->_errorMessage = 'Message pattern is invalid: ' . intl_get_error_message(); return false; } @@ -142,7 +142,7 @@ class MessageFormatter extends Component // replace named arguments if (($tokens = self::tokenizePattern($pattern)) === false) { $this->_errorCode = -1; - $this->_errorMessage = "Message pattern is invalid."; + $this->_errorMessage = 'Message pattern is invalid.'; return false; } @@ -163,7 +163,7 @@ class MessageFormatter extends Component $formatter = new \MessageFormatter($language, $pattern); if ($formatter === null) { $this->_errorCode = -1; - $this->_errorMessage = "Message pattern is invalid."; + $this->_errorMessage = 'Message pattern is invalid.'; return false; } @@ -192,7 +192,7 @@ class MessageFormatter extends Component * @param array $map * @return string The pattern string with placeholders replaced. */ - private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = []) + private function replaceNamedArguments($pattern, $givenParams, &$resultingParams = [], &$map = []) { if (($tokens = self::tokenizePattern($pattern)) === false) { return false; @@ -210,14 +210,14 @@ class MessageFormatter extends Component $resultingParams[$map[$param]] = $givenParams[$param]; } $token[0] = $map[$param]; - $quote = ""; + $quote = ''; } else { // quote unused token $quote = "'"; } $type = isset($token[1]) ? trim($token[1]) : 'none'; // replace plural and select format recursively - if ($type == 'plural' || $type == 'select') { + if ($type === 'plural' || $type === 'select') { if (!isset($token[2])) { return false; } @@ -251,7 +251,7 @@ class MessageFormatter extends Component { if (($tokens = self::tokenizePattern($pattern)) === false) { $this->_errorCode = -1; - $this->_errorMessage = "Message pattern is invalid."; + $this->_errorMessage = 'Message pattern is invalid.'; return false; } @@ -259,7 +259,7 @@ class MessageFormatter extends Component if (is_array($token)) { if (($tokens[$i] = $this->parseToken($token, $args, $locale)) === false) { $this->_errorCode = -1; - $this->_errorMessage = "Message pattern is invalid."; + $this->_errorMessage = 'Message pattern is invalid.'; return false; } @@ -276,19 +276,20 @@ class MessageFormatter extends Component */ private static function tokenizePattern($pattern) { + $charset = Yii::$app ? Yii::$app->charset : 'UTF-8'; $depth = 1; - if (($start = $pos = mb_strpos($pattern, '{')) === false) { + if (($start = $pos = mb_strpos($pattern, '{', 0, $charset)) === false) { return [$pattern]; } - $tokens = [mb_substr($pattern, 0, $pos)]; + $tokens = [mb_substr($pattern, 0, $pos, $charset)]; while (true) { - $open = mb_strpos($pattern, '{', $pos + 1); - $close = mb_strpos($pattern, '}', $pos + 1); + $open = mb_strpos($pattern, '{', $pos + 1, $charset); + $close = mb_strpos($pattern, '}', $pos + 1, $charset); if ($open === false && $close === false) { break; } if ($open === false) { - $open = mb_strlen($pattern); + $open = mb_strlen($pattern, $charset); } if ($close > $open) { $depth++; @@ -297,14 +298,14 @@ class MessageFormatter extends Component $depth--; $pos = $close; } - if ($depth == 0) { - $tokens[] = explode(',', mb_substr($pattern, $start + 1, $pos - $start - 1), 3); + if ($depth === 0) { + $tokens[] = explode(',', mb_substr($pattern, $start + 1, $pos - $start - 1, $charset), 3); $start = $pos + 1; - $tokens[] = mb_substr($pattern, $start, $open - $start); + $tokens[] = mb_substr($pattern, $start, $open - $start, $charset); $start = $open; } } - if ($depth != 0) { + if ($depth !== 0) { return false; } @@ -316,14 +317,14 @@ class MessageFormatter extends Component * @param array $token the token to parse * @param array $args arguments to replace * @param string $locale the locale - * @return bool|string parsed token or false on failure + * @return boolean|string parsed token or false on failure * @throws \yii\base\NotSupportedException when unsupported formatting is used. */ private function parseToken($token, $args, $locale) { // parsing pattern based on ICU grammar: // http://icu-project.org/apiref/icu4c/classMessageFormat.html#details - + $charset = Yii::$app ? Yii::$app->charset : 'UTF-8'; $param = trim($token[0]); if (isset($args[$param])) { $arg = $args[$param]; @@ -341,8 +342,14 @@ class MessageFormatter extends Component case 'selectordinal': throw new NotSupportedException("Message format '$type' is not supported. You have to install PHP intl extension to use this feature."); case 'number': - if (is_int($arg) && (!isset($token[2]) || trim($token[2]) == 'integer')) { - return $arg; + $format = isset($token[2]) ? trim($token[2]) : null; + if (is_numeric($arg) && ($format === null || $format === 'integer')) { + $number = number_format($arg); + if ($format === null && ($pos = strpos($arg, '.')) !== false) { + // add decimals with unknown length + $number .= '.' . substr($arg, $pos + 1); + } + return $number; } throw new NotSupportedException("Message format 'number' is only supported for integer values. You have to install PHP intl extension to use this feature."); case 'none': @@ -362,14 +369,14 @@ class MessageFormatter extends Component return false; } $selector = trim($select[$i++]); - if ($message === false && $selector == 'other' || $selector == $arg) { + if ($message === false && $selector === 'other' || $selector == $arg) { $message = implode(',', $select[$i]); } } if ($message !== false) { return $this->fallbackFormat($message, $args, $locale); } - break; + break; case 'plural': /* http://icu-project.org/apiref/icu4c/classicu_1_1PluralFormat.html pluralStyle = [offsetValue] (selector '{' message '}')+ @@ -393,12 +400,12 @@ class MessageFormatter extends Component $selector = trim($plural[$i++]); if ($i == 1 && strncmp($selector, 'offset:', 7) === 0) { - $offset = (int) trim(mb_substr($selector, 7, ($pos = mb_strpos(str_replace(["\n", "\r", "\t"], ' ', $selector), ' ', 7)) - 7)); - $selector = trim(mb_substr($selector, $pos + 1)); + $offset = (int) trim(mb_substr($selector, 7, ($pos = mb_strpos(str_replace(["\n", "\r", "\t"], ' ', $selector), ' ', 7, $charset)) - 7, $charset)); + $selector = trim(mb_substr($selector, $pos + 1, null, $charset)); } - if ($message === false && $selector == 'other' || - $selector[0] == '=' && (int) mb_substr($selector, 1) == $arg || - $selector == 'one' && $arg - $offset == 1 + if ($message === false && $selector === 'other' || + $selector[0] === '=' && (int) mb_substr($selector, 1, null, $charset) === $arg || + $selector === 'one' && $arg - $offset == 1 ) { $message = implode(',', str_replace('#', $arg - $offset, $plural[$i])); } diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php index 05f7d77a3d..0becc55966 100644 --- a/framework/i18n/PhpMessageSource.php +++ b/framework/i18n/PhpMessageSource.php @@ -19,12 +19,12 @@ use Yii; * - Each PHP script is saved as a file named as `[[basePath]]/LanguageID/CategoryName.php`; * - Within each PHP script, the message translations are returned as an array like the following: * - * ~~~ + * ```php * return [ * 'original message 1' => 'translated message 1', * 'original message 2' => 'translated message 2', * ]; - * ~~~ + * ``` * * You may use [[fileMap]] to customize the association between category names and the file names. * @@ -41,25 +41,30 @@ class PhpMessageSource extends MessageSource * @var array mapping between message categories and the corresponding message file paths. * The file paths are relative to [[basePath]]. For example, * - * ~~~ + * ```php * [ * 'core' => 'core.php', * 'ext' => 'extensions.php', * ] - * ~~~ + * ``` */ public $fileMap; /** - * Loads the message translation for the specified language and category. + * Loads the message translation for the specified $language and $category. * If translation for specific locale code such as `en-US` isn't found it - * tries more generic `en`. + * tries more generic `en`. When both are present, the `en-US` messages will be merged + * over `en`. See [[loadFallbackMessages]] for details. + * If the $language is less specific than [[sourceLanguage]], the method will try to + * load the messages for [[sourceLanguage]]. For example: [[sourceLanguage]] is `en-GB`, + * $language is `en`. The method will load the messages for `en` and merge them over `en-GB`. * * @param string $category the message category * @param string $language the target language - * @return array the loaded messages. The keys are original messages, and the values - * are translated messages. + * @return array the loaded messages. The keys are original messages, and the values are the translated messages. + * @see loadFallbackMessages + * @see sourceLanguage */ protected function loadMessages($category, $language) { @@ -67,21 +72,12 @@ class PhpMessageSource extends MessageSource $messages = $this->loadMessagesFromFile($messageFile); $fallbackLanguage = substr($language, 0, 2); - if ($fallbackLanguage != $language) { - $fallbackMessageFile = $this->getMessageFilePath($category, $fallbackLanguage); - $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile); + $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2); - if ($messages === null && $fallbackMessages === null && $fallbackLanguage != $this->sourceLanguage) { - Yii::error("The message file for category '$category' does not exist: $messageFile Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); - } elseif (empty($messages)) { - return $fallbackMessages; - } elseif (!empty($fallbackMessages)) { - foreach ($fallbackMessages as $key => $value) { - if (!empty($value) && empty($messages[$key])) { - $messages[$key] = $fallbackMessages[$key]; - } - } - } + if ($language !== $fallbackLanguage) { + $messages = $this->loadFallbackMessages($category, $fallbackLanguage, $messages, $messageFile); + } elseif ($language === $fallbackSourceLanguage) { + $messages = $this->loadFallbackMessages($category, $this->sourceLanguage, $messages, $messageFile); } else { if ($messages === null) { Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); @@ -91,6 +87,44 @@ class PhpMessageSource extends MessageSource return (array) $messages; } + /** + * The method is normally called by [[loadMessages]] to load the fallback messages for the language. + * Method tries to load the $category messages for the $fallbackLanguage and adds them to the $messages array. + * + * @param string $category the message category + * @param string $fallbackLanguage the target fallback language + * @param array $messages the array of previously loaded translation messages. + * The keys are original messages, and the values are the translated messages. + * @param string $originalMessageFile the path to the file with messages. Used to log an error message + * in case when no translations were found. + * @return array the loaded messages. The keys are original messages, and the values are the translated messages. + * @since 2.0.7 + */ + protected function loadFallbackMessages($category, $fallbackLanguage, $messages, $originalMessageFile) + { + $fallbackMessageFile = $this->getMessageFilePath($category, $fallbackLanguage); + $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile); + + if ( + $messages === null && $fallbackMessages === null + && $fallbackLanguage !== $this->sourceLanguage + && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2) + ) { + Yii::error("The message file for category '$category' does not exist: $originalMessageFile " + . "Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); + } elseif (empty($messages)) { + return $fallbackMessages; + } elseif (!empty($fallbackMessages)) { + foreach ($fallbackMessages as $key => $value) { + if (!empty($value) && empty($messages[$key])) { + $messages[$key] = $fallbackMessages[$key]; + } + } + } + + return (array) $messages; + } + /** * Returns message file path for the specified language and category. * @@ -113,7 +147,7 @@ class PhpMessageSource extends MessageSource /** * Loads the message translation for the specified language and category or returns null if file doesn't exist. * - * @param $messageFile string path to message file + * @param string $messageFile path to message file * @return array|null array of messages or null if file not found */ protected function loadMessagesFromFile($messageFile) diff --git a/framework/i18n/migrations/m150207_210500_i18n_init.php b/framework/i18n/migrations/m150207_210500_i18n_init.php new file mode 100644 index 0000000000..630f771bd0 --- /dev/null +++ b/framework/i18n/migrations/m150207_210500_i18n_init.php @@ -0,0 +1,50 @@ + + * @since 2.0.7 + */ +class m150207_210500_i18n_init extends Migration +{ + public function up() + { + $tableOptions = null; + if ($this->db->driverName === 'mysql') { + // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; + } + + $this->createTable('source_message', [ + 'id' => $this->primaryKey(), + 'category' => $this->string(), + 'message' => $this->text(), + ], $tableOptions); + + $this->createTable('message', [ + 'id' => $this->integer()->notNull(), + 'language' => $this->string(16)->notNull(), + 'translation' => $this->text(), + ], $tableOptions); + + $this->addPrimaryKey('pk_message_id_language', 'message', ['id', 'language']); + $this->addForeignKey('fk_message_source_message', 'message', 'id', 'source_message', 'id', 'CASCADE', 'RESTRICT'); + } + + public function down() + { + $this->dropForeignKey('fk_message_source_message', 'message'); + $this->dropTable('message'); + $this->dropTable('source_message'); + } +} diff --git a/framework/i18n/migrations/schema-mssql.sql b/framework/i18n/migrations/schema-mssql.sql new file mode 100644 index 0000000000..e4f9715ffa --- /dev/null +++ b/framework/i18n/migrations/schema-mssql.sql @@ -0,0 +1,29 @@ +/** + * Database schema required by \yii\i18n\DbMessageSource. + * + * @author Dmitry Naumenko + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists [source_message]; +drop table if exists [message]; + +CREATE TABLE [source_message] +( + [id] integer IDENTITY PRIMARY KEY, + [category] varchar(255), + [message] text +); + +CREATE TABLE [message] +( + [id] integer NOT NULL, + [language] varchar(16) NOT NULL, + [translation] text +); + +ALTER TABLE [message] ADD CONSTRAINT [pk_message_id_language] PRIMARY KEY ([id], [language]); +ALTER TABLE [message] ADD CONSTRAINT [fk_message_source_message] FOREIGN KEY ([id]) REFERENCES [source_message] ([id]) ON UPDATE CASCADE ON DELETE NO ACTION; diff --git a/framework/i18n/migrations/schema-mysql.sql b/framework/i18n/migrations/schema-mysql.sql new file mode 100644 index 0000000000..2fc2d9a45f --- /dev/null +++ b/framework/i18n/migrations/schema-mysql.sql @@ -0,0 +1,30 @@ +/** + * Database schema required by \yii\i18n\DbMessageSource. + * + * @author Dmitry Naumenko + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + + +drop table if exists `source_message`; +drop table if exists `message`; + +CREATE TABLE `source_message` +( + `id` integer NOT NULL AUTO_INCREMENT PRIMARY KEY, + `category` varchar(255), + `message` text +); + +CREATE TABLE `message` +( + `id` integer NOT NULL, + `language` varchar(16) NOT NULL, + `translation` text +); + +ALTER TABLE `message` ADD CONSTRAINT `pk_message_id_language` PRIMARY KEY (`id`, `language`); +ALTER TABLE `message` ADD CONSTRAINT `fk_message_source_message` FOREIGN KEY (`id`) REFERENCES `source_message` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT; diff --git a/framework/i18n/migrations/schema-oci.sql b/framework/i18n/migrations/schema-oci.sql new file mode 100644 index 0000000000..f0b526c62d --- /dev/null +++ b/framework/i18n/migrations/schema-oci.sql @@ -0,0 +1,30 @@ +/** + * Database schema required by \yii\i18n\DbMessageSource. + * + * @author Dmitry Naumenko + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + + +drop table if exists "source_message"; +drop table if exists "message"; + +CREATE TABLE "source_message" +( + "id" integer NOT NULL PRIMARY KEY, + "category" varchar(255), + "message" clob +); +CREATE SEQUENCE "source_message_SEQ"; + +CREATE TABLE "message" +( + "id" integer NOT NULL, + "language" varchar(16) NOT NULL, + "translation" clob, + primary key ("id", "language"), + foreign key ("id") references "source_message" ("id") on delete cascade +); diff --git a/framework/i18n/migrations/schema-pgsql.sql b/framework/i18n/migrations/schema-pgsql.sql new file mode 100644 index 0000000000..838aca9e86 --- /dev/null +++ b/framework/i18n/migrations/schema-pgsql.sql @@ -0,0 +1,32 @@ +/** + * Database schema required by \yii\i18n\DbMessageSource. + * + * @author Dmitry Naumenko + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + + +drop table if exists "source_message"; +drop table if exists "message"; + +CREATE SEQUENCE source_message_seq; + +CREATE TABLE "source_message" +( + "id" integer NOT NULL PRIMARY KEY DEFAULT nextval('source_message_seq'), + "category" varchar(255), + "message" text +); + +CREATE TABLE "message" +( + "id" integer NOT NULL, + "language" varchar(16) NOT NULL, + "translation" text +); + +ALTER TABLE "message" ADD CONSTRAINT "pk_message_id_language" PRIMARY KEY ("id", "language"); +ALTER TABLE "message" ADD CONSTRAINT "fk_message_source_message" FOREIGN KEY ("id") REFERENCES "source_message" ("id") ON UPDATE CASCADE ON DELETE RESTRICT; diff --git a/framework/i18n/migrations/schema-sqlite.sql b/framework/i18n/migrations/schema-sqlite.sql new file mode 100644 index 0000000000..fcc4fbea03 --- /dev/null +++ b/framework/i18n/migrations/schema-sqlite.sql @@ -0,0 +1,28 @@ +/** + * Database schema required by \yii\i18n\DbMessageSource. + * + * @author Dmitry Naumenko + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0.7 + */ + +drop table if exists `source_message`; +drop table if exists `message`; + +CREATE TABLE `source_message` +( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `category` varchar(255), + `message` text +); + +CREATE TABLE `message` +( + `id` integer NOT NULL REFERENCES `source_message` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION, + `language` varchar(16) NOT NULL, + `translation` text, + PRIMARY KEY (`id`, `language`) +); + diff --git a/framework/log/DbTarget.php b/framework/log/DbTarget.php index c1e7e4dff8..d77f6e9369 100644 --- a/framework/log/DbTarget.php +++ b/framework/log/DbTarget.php @@ -67,7 +67,12 @@ class DbTarget extends Target foreach ($this->messages as $message) { list($text, $level, $category, $timestamp) = $message; if (!is_string($text)) { - $text = VarDumper::export($text); + // exceptions may not be serializable if in the call stack somewhere is a Closure + if ($text instanceof \Throwable || $text instanceof \Exception) { + $text = (string) $text; + } else { + $text = VarDumper::export($text); + } } $command->bindValues([ ':level' => $level, diff --git a/framework/log/Dispatcher.php b/framework/log/Dispatcher.php index 81752532a0..3caf8f570f 100644 --- a/framework/log/Dispatcher.php +++ b/framework/log/Dispatcher.php @@ -52,7 +52,8 @@ use yii\base\ErrorHandler; * * @property integer $flushInterval How many messages should be logged before they are sent to targets. This * method returns the value of [[Logger::flushInterval]]. - * @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. + * @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. Note that the type of + * this property differs in getter and setter. See [[getLogger()]] and [[setLogger()]] for details. * @property integer $traceLevel How many application call stacks should be logged together with each message. * This method returns the value of [[Logger::traceLevel]]. Defaults to 0. * @@ -119,10 +120,14 @@ class Dispatcher extends Component /** * Sets the connected logger. - * @param Logger $value the logger. + * @param Logger|string|array $value the logger to be used. This can either be a logger instance + * or a configuration that will be used to create one using [[Yii::createObject()]]. */ public function setLogger($value) { + if (is_string($value) || is_array($value)) { + $value = Yii::createObject($value); + } $this->_logger = $value; $this->_logger->dispatcher = $this; } diff --git a/framework/log/EmailTarget.php b/framework/log/EmailTarget.php index 1e449bbd92..659c645f75 100644 --- a/framework/log/EmailTarget.php +++ b/framework/log/EmailTarget.php @@ -24,7 +24,7 @@ use yii\mail\MailerInterface; * 'targets' => [ * [ * 'class' => 'yii\log\EmailTarget', - * 'mailer' =>'mailer', + * 'mailer' => 'mailer', * 'levels' => ['error', 'warning'], * 'message' => [ * 'from' => ['log@example.com'], diff --git a/framework/log/Logger.php b/framework/log/Logger.php index 63cecb0400..43bee36da2 100644 --- a/framework/log/Logger.php +++ b/framework/log/Logger.php @@ -78,7 +78,7 @@ class Logger extends Component * @var array logged messages. This property is managed by [[log()]] and [[flush()]]. * Each log message is of the following structure: * - * ~~~ + * ``` * [ * [0] => message (mixed, can be a string or some complex data, such as an exception object) * [1] => level (integer) @@ -86,7 +86,7 @@ class Logger extends Component * [3] => timestamp (float, obtained by microtime(true)) * [4] => traces (array, debug backtrace, contains the application code call stacks) * ] - * ~~~ + * ``` */ public $messages = []; /** @@ -116,14 +116,20 @@ class Logger extends Component { parent::init(); register_shutdown_function(function () { +<<<<<<< HEAD <<<<<<< HEAD // make sure "flush()" is called last when there are multiple shutdown functions ======= +======= +>>>>>>> master // make regular flush before other shutdown functions, which allows session data collection and so on $this->flush(); // make sure log entries written by shutdown functions are also flushed // ensure "flush()" is called last when there are multiple shutdown functions +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master register_shutdown_function([$this, 'flush'], true); }); } diff --git a/framework/log/SyslogTarget.php b/framework/log/SyslogTarget.php index 9704e0b02d..e5998cef46 100644 --- a/framework/log/SyslogTarget.php +++ b/framework/log/SyslogTarget.php @@ -60,7 +60,12 @@ class SyslogTarget extends Target list($text, $level, $category, $timestamp) = $message; $level = Logger::getLevelName($level); if (!is_string($text)) { - $text = VarDumper::export($text); + // exceptions may not be serializable if in the call stack somewhere is a Closure + if ($text instanceof \Throwable || $text instanceof \Exception) { + $text = (string) $text; + } else { + $text = VarDumper::export($text); + } } $prefix = $this->getMessagePrefix($message); diff --git a/framework/log/Target.php b/framework/log/Target.php index 75179b0468..afb5153cac 100644 --- a/framework/log/Target.php +++ b/framework/log/Target.php @@ -99,7 +99,7 @@ abstract class Target extends Component */ public function collect($messages, $final) { - $this->messages = array_merge($this->messages, $this->filterMessages($messages, $this->getLevels(), $this->categories, $this->except)); + $this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except)); $count = count($this->messages); if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { if (($context = $this->getContextMessage()) !== '') { @@ -152,11 +152,11 @@ abstract class Target extends Component * * For example, * - * ~~~ + * ```php * ['error', 'warning'] * // which is equivalent to: * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING - * ~~~ + * ``` * * @param array|integer $levels message levels that this target is interested in. * @throws InvalidConfigException if an unknown level name is given @@ -238,11 +238,16 @@ abstract class Target extends Component list($text, $level, $category, $timestamp) = $message; $level = Logger::getLevelName($level); if (!is_string($text)) { - $text = VarDumper::export($text); + // exceptions may not be serializable if in the call stack somewhere is a Closure + if ($text instanceof \Throwable || $text instanceof \Exception) { + $text = (string) $text; + } else { + $text = VarDumper::export($text); + } } $traces = []; if (isset($message[4])) { - foreach($message[4] as $trace) { + foreach ($message[4] as $trace) { $traces[] = "in {$trace['file']}:{$trace['line']}"; } } diff --git a/framework/log/migrations/m141106_185632_log_init.php b/framework/log/migrations/m141106_185632_log_init.php index 98b1ce8b2e..3cef167df2 100644 --- a/framework/log/migrations/m141106_185632_log_init.php +++ b/framework/log/migrations/m141106_185632_log_init.php @@ -6,7 +6,6 @@ */ use yii\base\InvalidConfigException; -use yii\db\Schema; use yii\db\Migration; use yii\log\DbTarget; @@ -62,12 +61,12 @@ class m141106_185632_log_init extends Migration } $this->createTable($target->logTable, [ - 'id' => Schema::TYPE_BIGPK, - 'level' => Schema::TYPE_INTEGER, - 'category' => Schema::TYPE_STRING, - 'log_time' => Schema::TYPE_DOUBLE, - 'prefix' => Schema::TYPE_TEXT, - 'message' => Schema::TYPE_TEXT, + 'id' => $this->bigPrimaryKey(), + 'level' => $this->integer(), + 'category' => $this->string(), + 'log_time' => $this->double(), + 'prefix' => $this->text(), + 'message' => $this->text(), ], $tableOptions); $this->createIndex('idx_log_level', $target->logTable, 'level'); diff --git a/framework/mail/BaseMailer.php b/framework/mail/BaseMailer.php index 0451d11a53..913a97b7c0 100644 --- a/framework/mail/BaseMailer.php +++ b/framework/mail/BaseMailer.php @@ -61,13 +61,13 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont * * For example: * - * ~~~ + * ```php * [ * 'charset' => 'UTF-8', * 'from' => 'noreply@mydomain.com', * 'bcc' => 'developer@mydomain.com', * ] - * ~~~ + * ``` */ public $messageConfig = []; /** @@ -91,9 +91,9 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont * * The signature of the callback is: * - * ~~~ + * ```php * function ($mailer, $message) - * ~~~ + * ``` */ public $fileTransportCallback; @@ -151,7 +151,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont /** * Creates a new message instance and optionally composes its body content via view rendering. * - * @param string|array $view the view to be used for rendering the message body. This can be: + * @param string|array|null $view the view to be used for rendering the message body. This can be: * * - a string, which represents the view name or path alias for rendering the HTML body of the email. * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. @@ -320,7 +320,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont protected function saveMessage($message) { $path = Yii::getAlias($this->fileTransportPath); - if (!is_dir(($path))) { + if (!is_dir($path)) { mkdir($path, 0777, true); } if ($this->fileTransportCallback !== null) { diff --git a/framework/mail/MailerInterface.php b/framework/mail/MailerInterface.php index 22231c0371..d730f8739c 100644 --- a/framework/mail/MailerInterface.php +++ b/framework/mail/MailerInterface.php @@ -13,13 +13,13 @@ namespace yii\mail; * A mailer should mainly support creating and sending [[MessageInterface|mail messages]]. It should * also support composition of the message body through the view rendering mechanism. For example, * - * ~~~ + * ```php * Yii::$app->mailer->compose('contact/html', ['contactForm' => $form]) * ->setFrom('from@domain.com') * ->setTo($form->email) * ->setSubject($form->subject) * ->send(); - * ~~~ + * ``` * * @see MessageInterface * @@ -31,7 +31,7 @@ interface MailerInterface /** * Creates a new message instance and optionally composes its body content via view rendering. * - * @param string|array $view the view to be used for rendering the message body. This can be: + * @param string|array|null $view the view to be used for rendering the message body. This can be: * * - a string, which represents the view name or path alias for rendering the HTML body of the email. * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. diff --git a/framework/mail/MessageInterface.php b/framework/mail/MessageInterface.php index aef63deb1f..57128fde7d 100644 --- a/framework/mail/MessageInterface.php +++ b/framework/mail/MessageInterface.php @@ -15,7 +15,7 @@ namespace yii\mail; * * Messages are sent by a [[\yii\mail\MailerInterface|mailer]], like the following, * - * ~~~ + * ```php * Yii::$app->mailer->compose() * ->setFrom('from@domain.com') * ->setTo($form->email) @@ -23,7 +23,7 @@ namespace yii\mail; * ->setTextBody('Plain text content') * ->setHtmlBody('HTML content') * ->send(); - * ~~~ + * ``` * * @see MailerInterface * @@ -41,7 +41,7 @@ interface MessageInterface /** * Sets the character set of this message. * @param string $charset character set name. - * @return static self reference. + * @return $this self reference. */ public function setCharset($charset); @@ -57,7 +57,7 @@ interface MessageInterface * You may pass an array of addresses if this message is from multiple people. * You may also specify sender name in addition to email address using format: * `[email => name]`. - * @return static self reference. + * @return $this self reference. */ public function setFrom($from); @@ -73,7 +73,7 @@ interface MessageInterface * You may pass an array of addresses if multiple recipients should receive this message. * You may also specify receiver name in addition to email address using format: * `[email => name]`. - * @return static self reference. + * @return $this self reference. */ public function setTo($to); @@ -89,7 +89,7 @@ interface MessageInterface * You may pass an array of addresses if this message should be replied to multiple people. * You may also specify reply-to name in addition to email address using format: * `[email => name]`. - * @return static self reference. + * @return $this self reference. */ public function setReplyTo($replyTo); @@ -105,7 +105,7 @@ interface MessageInterface * You may pass an array of addresses if multiple recipients should receive this message. * You may also specify receiver name in addition to email address using format: * `[email => name]`. - * @return static self reference. + * @return $this self reference. */ public function setCc($cc); @@ -121,7 +121,7 @@ interface MessageInterface * You may pass an array of addresses if multiple recipients should receive this message. * You may also specify receiver name in addition to email address using format: * `[email => name]`. - * @return static self reference. + * @return $this self reference. */ public function setBcc($bcc); @@ -134,21 +134,21 @@ interface MessageInterface /** * Sets the message subject. * @param string $subject message subject - * @return static self reference. + * @return $this self reference. */ public function setSubject($subject); /** * Sets message plain text content. * @param string $text message plain text content. - * @return static self reference. + * @return $this self reference. */ public function setTextBody($text); /** * Sets message HTML content. * @param string $html message HTML content. - * @return static self reference. + * @return $this self reference. */ public function setHtmlBody($html); @@ -160,7 +160,7 @@ interface MessageInterface * - fileName: name, which should be used to attach file. * - contentType: attached file MIME type. * - * @return static self reference. + * @return $this self reference. */ public function attach($fileName, array $options = []); @@ -172,7 +172,7 @@ interface MessageInterface * - fileName: name, which should be used to attach file. * - contentType: attached file MIME type. * - * @return static self reference. + * @return $this self reference. */ public function attachContent($content, array $options = []); diff --git a/framework/messages/ar/yii.php b/framework/messages/ar/yii.php index 9c63fece17..847f709e82 100644 --- a/framework/messages/ar/yii.php +++ b/framework/messages/ar/yii.php @@ -38,8 +38,8 @@ return [ 'Please upload a file.' => 'الرجاء تحميل ملف.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'عرض {begin, number}-{end, number} من أصل {totalCount, number} {totalCount, plural, one{مُدخل} few{مُدخلات} many{مُدخل} other{مُدخلات}}.', 'The file "{file}" is not an image.' => 'الملف "{file}" ليس صورة.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'الملف "{file}" كبير الحجم. حجمه لا يجب أن يتخطى {limit, number} {limit, plural, one{بايت} other{بايت}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'الملف "{file}" صغير جداً. حجمه لا يجب أن يكون أصغر من {limit, number} {limit, plural, other{بايت}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'الملف "{file}" كبير الحجم. حجمه لا يجب أن يتخطى {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'الملف "{file}" صغير جداً. حجمه لا يجب أن يكون أصغر من {formattedLimit}.', 'The format of {attribute} is invalid.' => 'شكل {attribute} غير صالح', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" كبيرة جداً. ارتفاعها لا يمكن أن يتخطى {limit, number} {limit, plural, other{بكسل}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" كبيرة جداً. عرضها لا يمكن أن يتخطى {limit, number} {limit, plural, other{بكسل}}.', @@ -66,15 +66,38 @@ return [ '{attribute} must be a string.' => '{attribute} يجب أن يكون كلمات', '{attribute} must be an integer.' => '{attribute} يجب أن يكون رقمًا صحيحًا', '{attribute} must be either "{true}" or "{false}".' => '{attribute} يجب أن يكن إما "{true}" أو "{false}".', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} يجب أن يساوي "{compareValueOrAttribute}".', '{attribute} must be greater than "{compareValue}".' => '{attribute} يجب أن يكون أكبر من "{compareValue}".', '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} يجب أن يكون أكبر من أو يساوي "{compareValue}".', '{attribute} must be less than "{compareValue}".' => '{attribute} يجب أن يكون أصغر من "{compareValue}".', '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} يجب أن يكون أصغر من أو يساوي "{compareValue}".', - '{attribute} must be no greater than {max}.' => '{attribute} يجب أن لا يكون أكبر من "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} يجب أن لا يكون أكبر من "{max}".', '{attribute} must be no less than {min}.' => '{attribute} يجب أن لا يكون أصغر من "{min}".', '{attribute} must be repeated exactly.' => '{attribute} يجب أن يكون متطابق.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} يجب ان لا يساوي "{compareValue}"', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على أكثر من {min, number} {min, plural, one{حرف} few{حروف} many{حرف}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} يجب أن لا يحتوي على أكثر من {max, number} {max, plural, one{حرف} few{حروف} many{حرف}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على {length, number} {length, plural, one{حرف} few{حروف} many{حرف}}.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على أكثر من {min, number} {min, plural, one{حرف} few{حروف} other{حرف}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} يجب أن لا يحتوي على أكثر من {max, number} {max, plural, one{حرف} few{حروف} other{حرف}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على {length, number} {length, plural, one{حرف} few{حروف} other{حرف}}.', + '{nFormatted} B' => '{nFormatted} بايت', + '{nFormatted} GB' => '{nFormatted} جيجابايت', + '{nFormatted} GiB' => '{nFormatted} جيبيبايت', + '{nFormatted} KB' => '{nFormatted} كيلوبايت', + '{nFormatted} KiB' => '{nFormatted} كيبيبايت', + '{nFormatted} MB' => '{nFormatted} ميجابايت', + '{nFormatted} MiB' => '{nFormatted} ميبيبايت', + '{nFormatted} PB' => '{nFormatted} بيتابايت', + '{nFormatted} PiB' => '{nFormatted} بيبيبايت', + '{nFormatted} TB' => '{nFormatted} تيرابايت', + '{nFormatted} TiB' => '{nFormatted} تيبيبايت', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} بايت', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} جيبيبايت', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} جيجابايت', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} كيبيبايت', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} كيلوبايت', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} ميبيبايت', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} ميجابايت', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} بيبيبايت', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} بيتابايت', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} تيبيبايت', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} تيرابايت', ]; diff --git a/framework/messages/az/yii.php b/framework/messages/az/yii.php index 1338797215..be787c74d4 100644 --- a/framework/messages/az/yii.php +++ b/framework/messages/az/yii.php @@ -39,8 +39,8 @@ return [ 'Please upload a file.' => 'Xahiş olunur bir fayl yükləyin.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, number} {totalCount, plural, one{elementdən} other{elementdən}} {begin, number}-{end, number} arası göstərilir.', 'The file "{file}" is not an image.' => '"{file}" təsvir faylı deyil.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" faylı çox böyükdür. Həcmi {limit, number} {limit, plural, one{byte} other{bytes}} qiymətindən böyük ola bilməz.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" faylı çox kiçikdir. Həcmi {limit, number} {limit, plural, one{byte} other{bytes}} qiymətindən kiçik ola bilməz.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => '"{file}" faylı çox böyükdür. Həcmi {formattedLimit} qiymətindən böyük ola bilməz.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => '"{file}" faylı çox kiçikdir. Həcmi {formattedLimit} qiymətindən kiçik ola bilməz.', 'The format of {attribute} is invalid.' => '{attribute} formatı düzgün deyil.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çox böyükdür. Uzunluq {limit, plural, one{pixel} other{pixels}} qiymətindən böyük ola bilməz.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çox böyükdür. Eni {limit, number} {limit, plural, one{pixel} other{pixel}} qiymətindən böyük ola bilməz.', diff --git a/framework/messages/bg/yii.php b/framework/messages/bg/yii.php index 9a711f4e39..9206c9bff3 100644 --- a/framework/messages/bg/yii.php +++ b/framework/messages/bg/yii.php @@ -39,8 +39,8 @@ return array ( 'Please upload a file.' => 'Моля, прикачете файл.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показване на {begin, number}-{end, number} от {totalCount, number} {totalCount, plural, one{запис} other{записа}}.', 'The file "{file}" is not an image.' => 'Файлът "{file}" не е изображение.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фйлът "{file}" е твърде голям. Размерът на файла не трябва да превишава {limit, number} {limit, plural, one{байт} other{байта}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файлът "{file}" е твърде малък. Размерът на файла трябва да е поне {limit, number} {limit, plural, one{байт} other{байта}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Фйлът "{file}" е твърде голям. Размерът на файла не трябва да превишава {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Файлът "{file}" е твърде малък. Размерът на файла трябва да е поне {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Невалиден формат за "{attribute}".', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде голямо. Височината не трябва да е по-голяма от {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде голямо. Широчината не трябва да е по-голяма от {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', diff --git a/framework/messages/bs/yii.php b/framework/messages/bs/yii.php new file mode 100644 index 0000000000..695a595b05 --- /dev/null +++ b/framework/messages/bs/yii.php @@ -0,0 +1,114 @@ + '(bez vrijednosti)', + 'An internal server error occurred.' => 'Došlo je do interne greške na serveru.', + 'Are you sure you want to delete this item?' => 'Jeste li sigurni da želite obrisati ovu stavku?', + 'Delete' => 'Obriši', + 'Error' => 'Greška', + 'File upload failed.' => 'Slanje datoteke nije uspjelo.', + 'Home' => 'Početna', + 'Invalid data received for parameter "{param}".' => 'Neispravan podatak dobijen u parametru "{param}"', + 'Login Required' => 'Prijava je obavezna', + 'Missing required arguments: {params}' => 'Nedostaju obavezni argumenti: {params}', + 'Missing required parameters: {params}' => 'Nedostaju obavezni parametri: {params}', + 'No' => 'Ne', + 'No results found.' => 'Nema rezulatata.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Samo datoteke sa sljedećim MIME tipovima su dozvoljeni: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Samo datoteke sa sljedećim ekstenzijama su dozvoljeni: {extensions}.', + 'Page not found.' => 'Stranica nije pronađena.', + 'Please fix the following errors:' => 'Molimo ispravite sljedeće greške:', + 'Please upload a file.' => 'Molimo da pošaljete datoteku.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, one{stavke} other{stavki}}.', + 'The file "{file}" is not an image.' => 'Datoteka "{file}" nije slika.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Datoteka "{file}" je prevelika. Veličina ne smije biti veća od {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Datoteka "{file}" је premala. Veličina ne smije biti manja od {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Format atributa "{attribute}" je neispravan.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Visina ne smije biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Širina ne smije biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premala. Visina ne smije biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premala. Širina ne smije biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The requested view "{name}" was not found.' => 'Stranica "{name} nije pronađena."', + 'The verification code is incorrect.' => 'Potvrdni kod nije ispravan.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ukupno {count, number} {count, plural, one{stavka} other{stavki}}.', + 'Unable to verify your data submission.' => 'Nije moguće provjeriti poslane podatke.', + 'Unknown option: --{name}' => 'Nepoznata opcija: --{name}', + 'Update' => 'Ažurirati', + 'View' => 'Pregled', + 'Yes' => 'Da', + 'You are not allowed to perform this action.' => 'Nemate prava da izvršite ovu akciju.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Možete poslati najviše {limit, number} {limit, plural, one{datoteku} other{datoteka}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'za {delta, plural, =1{dan} one{# dan} few{# dana} many{# dana} other{# dana}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'za {delta, plural, =1{minut} one{# minut} few{# minuta} many{# minuta} other{# minuta}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'za {delta, plural, =1{mjesec} one{# mjesec} few{# mjeseci} many{# mjeseci} other{# mjeseci}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'za {delta, plural, =1{sekundu} one{# sekundu} few{# sekundi} many{# sekundi} other{# sekundi}', + 'in {delta, plural, =1{a year} other{# years}}' => 'za {delta, plural, =1{godinu} one{# godinu} few{# godini} many{# godina} other{# godina}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'za {delta, plural, =1{sat} one{# sat} few{# sati} many{# sati} other{# sati}}', + 'just now' => 'upravo sada', + 'the input value' => 'ulazna vrijednost', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" je već zauzet.', + '{attribute} cannot be blank.' => '{attribute} ne smije biti prazan.', + '{attribute} is invalid.' => '{attribute} je neispravan.', + '{attribute} is not a valid URL.' => '{attribute} ne sadrži ispravan URL.', + '{attribute} is not a valid email address.' => '{attribute} ne sadrži ispravnu email adresu.', + '{attribute} must be "{requiredValue}".' => '{attribute} mora biti "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} mora biti broj.', + '{attribute} must be a string.' => '{attribute} mora biti tekst.', + '{attribute} must be an integer.' => '{attribute} mora biti cijeli broj.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} mora biti "{true}" ili "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} mora biti veći od "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} mora biti veći ili jednak od "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} mora biti manji od "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} mora biti manji ili jednak od "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} ne smije biti veći od "{max}"', + '{attribute} must be no less than {min}.' => '{attribute} ne smije biti manji od {min}.', + '{attribute} must be repeated exactly.' => '{attribute} mora biti ponovljen ispravno.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ne smije biti jednak"{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} treba sadržavati najmanje {min, number} {min, plural, one{znak} other{znakova}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} treba sadržavati najviše {max, number} {max, plural, one{znak} other{znakova}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} treba sadržavati {length, number} {length, plural, one{znak} other{znakova}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'prije {delta, plural, =1{dan} one{# dan} few{# dana} many{# dana} other{# dana}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'prije {delta, plural, =1{minut} one{# minut} few{# minuta} many{# minuta} other{# minuta}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'prije {delta, plural, =1{mjesec} one{# mjesec} few{# mjeseci} many{# mjeseci} other{# mjeseci}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'prije {delta, plural, =1{sekundu} one{# sekundu} few{# sekundi} many{# sekundi} other{# sekundi}', + '{delta, plural, =1{a year} other{# years}} ago' => 'prije {delta, plural, =1{godinu} one{# godinu} few{# godina} many{# godina} other{# godina}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'prije {delta, plural, =1{sat} one{# sat} few{# sati} many{# sati} other{# sati}}', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{bajt} other{bajtova}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibajt} other{gibibajta}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabajt} other{gigabajta}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibajt} other{kibibajta}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobajt} other{kilobajta}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibajt} other{mebibajta}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabajt} other{megabajta}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibajt} other{pebibajta}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabajt} other{petabajta}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibajt} other{tebibajta}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabajt} other{terabajta}}', +]; \ No newline at end of file diff --git a/framework/messages/ca/yii.php b/framework/messages/ca/yii.php index c41e36340f..ba12d3b4a1 100644 --- a/framework/messages/ca/yii.php +++ b/framework/messages/ca/yii.php @@ -39,8 +39,8 @@ return [ 'Please upload a file.' => 'Si us plau puja un arxiu.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Mostrant {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{element} other{elements}}.', 'The file "{file}" is not an image.' => 'L\'arxiu "{file}" no és una imatge.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'L\'arxiu "{file}" és massa gran. El seu tamany no pot excedir {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'L\'arxiu "{file}" és massa petit. El seu tamany no pot ser menor que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'L\'arxiu "{file}" és massa gran. El seu tamany no pot excedir {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'L\'arxiu "{file}" és massa petit. El seu tamany no pot ser menor que {formattedLimit}.', 'The format of {attribute} is invalid.' => 'El format de {attribute} és invalid.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa gran. L\'altura no pot ser major que {limit, number} {limit, plural, one{píxel} other{píxels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa gran. L\'amplada no pot ser major que {limit, number} {limit, plural, one{píxel} other{píxels}}.', diff --git a/framework/messages/config.php b/framework/messages/config.php index 27de1e3b94..4efd6f8669 100644 --- a/framework/messages/config.php +++ b/framework/messages/config.php @@ -7,7 +7,7 @@ return [ 'messagePath' => __DIR__, // array, required, list of language codes that the extracted messages // should be translated to. For example, ['zh-CN', 'de']. - 'languages' => ['ar', 'az', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'ms', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'th', 'tj', 'uk', 'vi', 'zh-CN','zh-TW'], + 'languages' => ['ar', 'az', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'lt', 'lv', 'ms', 'nb-NO', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'th', 'tj', 'uk', 'vi', 'zh-CN', 'zh-TW'], // string, the name of the function for translating messages. // Defaults to 'Yii::t'. This is used as a mark to find the messages to be // translated. You may use a string for single function name or an array for @@ -22,6 +22,9 @@ return [ // boolean, whether to remove messages that no longer appear in the source code. // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. 'removeUnused' => false, + // boolean, whether to mark messages that no longer appear in the source code. + // Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks. + 'markUnused' => true, // array, list of patterns that specify which files/directories should NOT be processed. // If empty or not set, all files/directories will be processed. // A path matches a pattern if it contains the pattern string at its end. For example, @@ -38,7 +41,6 @@ return [ '.hgignore', '.hgkeep', '/messages', - '/BaseYii.php', // contains examples about Yii:t() ], // array, list of patterns that specify which files (not directories) should be processed. // If empty or not set, all files will be processed. diff --git a/framework/messages/cs/yii.php b/framework/messages/cs/yii.php index 7cac15c7c5..0925d23821 100644 --- a/framework/messages/cs/yii.php +++ b/framework/messages/cs/yii.php @@ -39,8 +39,8 @@ return [ 'Please upload a file.' => 'Nahrajte prosím soubor.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, plural, one{Zobrazen} few{Zobrazeny} other{Zobrazeno}} {totalCount, plural, one{{begin, number}} other{{begin, number}-{end, number}}} z {totalCount, number} {totalCount, plural, one{záznamu} other{záznamů}}.', 'The file "{file}" is not an image.' => 'Soubor "{file}" není obrázek.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Soubor "{file}" je příliš velký. Velikost souboru nesmí přesáhnout {limit, number} {limit, plural, one{byte} few{byty} other{bytů}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Soubor "{file}" je příliš malý. Velikost souboru nesmí být méně než {limit, number} {limit, plural, one{byte} few{byty} other{bytů}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Soubor "{file}" je příliš velký. Velikost souboru nesmí přesáhnout {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Soubor "{file}" je příliš malý. Velikost souboru nesmí být méně než {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Formát údaje {attribute} je neplatný.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš velký. Výška nesmí přesáhnout {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš velký. Šířka nesmí přesáhnout {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', diff --git a/framework/messages/da/yii.php b/framework/messages/da/yii.php index e5a03611e2..fe3c8d68e9 100644 --- a/framework/messages/da/yii.php +++ b/framework/messages/da/yii.php @@ -39,8 +39,8 @@ return array ( 'Please upload a file.' => 'Venligst upload en fil.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Viser {begin, number}-{end, number} af {totalCount, number} {totalCount, plural, one{element} other{elementer}}.', 'The file "{file}" is not an image.' => 'Filen "{file}" er ikke et billede.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" er for stor. Størrelsen må ikke overstige {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" er for lille. Størrelsen må ikke være mindre end {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Filen "{file}" er for stor. Størrelsen må ikke overstige {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Filen "{file}" er for lille. Størrelsen må ikke være mindre end {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Formatet af {attribute} er ugyldigt.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for stort. Højden må ikke være større end {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for stort. Bredden må ikke være større end {limit, number} {limit, plural, one{pixel} other{pixels}}.', @@ -83,7 +83,7 @@ return array ( '{attribute} must be repeated exactly.' => '{attribute} skal være gentaget præcist.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} må ikke være lig med "{compareValue}".', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} skal mindst indeholde {min, number} {min, plural, one{tegn} other{tegn}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} skal højst indeholde {min, number} {min, plural, one{tegn} other{tegn}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} skal højst indeholde {max, number} {max, plural, one{tegn} other{tegn}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} skal indeholde {length, number} {length, plural, one{tegn} other{tegn}}.', '{delta, plural, =1{a day} other{# days}} ago' => 'for {delta, plural, =1{en dag} other{# dage}} siden', '{delta, plural, =1{a minute} other{# minutes}} ago' => 'for {delta, plural, =1{et minut} other{# minutter}} siden', diff --git a/framework/messages/de/yii.php b/framework/messages/de/yii.php index 58a9e84f2a..1d928c0c42 100644 --- a/framework/messages/de/yii.php +++ b/framework/messages/de/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,6 +17,7 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + 'Unknown alias: -{name}' => 'Unbekannter Alias: -{name}', '(not set)' => '(nicht gesetzt)', 'An internal server error occurred.' => 'Es ist ein interner Serverfehler aufgetreten.', 'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?', @@ -35,10 +36,11 @@ return [ 'Page not found.' => 'Seite nicht gefunden.', 'Please fix the following errors:' => 'Bitte korrigieren Sie die folgenden Fehler:', 'Please upload a file.' => 'Bitte laden Sie eine Datei hoch.', + 'Powered by {yii}' => 'Basiert auf {yii}', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Zeige {begin, number}-{end, number} von {totalCount, number} {totalCount, plural, one{Eintrag} other{Einträgen}}.', 'The file "{file}" is not an image.' => 'Die Datei "{file}" ist kein Bild.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Die Datei "{file}" ist zu groß. Es {limit, plural, one{ist} other{sind}} maximal {limit, number} {limit, plural, one{Byte} other{Bytes}} erlaubt.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Die Datei "{file}" ist zu klein. Es {limit, plural, one{ist} other{sind}} mindestens {limit, number} {limit, plural, one{Byte} other{Bytes}} erforderlich.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Die Datei "{file}" ist zu groß. Es sind maximal {formattedLimit} erlaubt.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Die Datei "{file}" ist zu klein. Es sind mindestens {formattedLimit} erforderlich.', 'The format of {attribute} is invalid.' => 'Das Format von {attribute} ist ungültig.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel hoch sein.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel breit sein.', @@ -52,6 +54,7 @@ return [ 'Update' => 'Bearbeiten', 'View' => 'Anzeigen', 'Yes' => 'Ja', + 'Yii Framework' => 'Yii Framework', 'You are not allowed to perform this action.' => 'Sie dürfen diese Aktion nicht durchführen.', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sie können maximal {limit, number} {limit, plural, one{eine Datei} other{# Dateien}} hochladen.', 'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{einem Tag} other{# Tagen}}', @@ -64,25 +67,38 @@ return [ 'the input value' => 'der eingegebene Wert', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" wird bereits verwendet.', '{attribute} cannot be blank.' => '{attribute} darf nicht leer sein.', + '{attribute} contains wrong subnet mask.' => '{attribute} enthält ungültige Subnetz-Maske.', '{attribute} is invalid.' => '{attribute} ist ungültig.', '{attribute} is not a valid URL.' => '{attribute} ist keine gültige URL.', '{attribute} is not a valid email address.' => '{attribute} ist keine gültige Emailadresse.', + '{attribute} is not in the allowed range.' => '{attribute} ist außerhalb des gültigen Bereichs.', '{attribute} must be "{requiredValue}".' => '{attribute} muss den Wert {requiredValue} haben.', '{attribute} must be a number.' => '{attribute} muss eine Zahl sein.', '{attribute} must be a string.' => '{attribute} muss eine Zeichenkette sein.', + '{attribute} must be a valid IP address.' => '{attribute} muss eine gültige IP-Adresse sein.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} muss eine gültige IP-Adresse inklusive Subnetz-Maske sein.', '{attribute} must be an integer.' => '{attribute} muss eine Ganzzahl sein.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} muss entweder "{true}" oder "{false}" sein.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} muss größer als "{compareValue}" sein.', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} muss größer oder gleich "{compareValue}" sein.', - '{attribute} must be less than "{compareValue}".' => '{attribute} muss kleiner als "{compareValue}" sein.', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} muss kleiner oder gleich "{compareValue}" sein.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} muss gleich mit "{compareValueOrAttribute}" sein.', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} muss größer als "{compareValueOrAttribute}" sein.', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} muss größer oder gleich "{compareValueOrAttribute}" sein.', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} muss kleiner als "{compareValueOrAttribute}" sein.', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} muss kleiner oder gleich "{compareValueOrAttribute}" sein.', '{attribute} must be no greater than {max}.' => '{attribute} darf nicht größer als {max} sein.', '{attribute} must be no less than {min}.' => '{attribute} darf nicht kleiner als {min} sein.', - '{attribute} must be repeated exactly.' => '{attribute} muss genau wiederholt werden.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} darf nicht "{compareValue}" sein.', + '{attribute} must not be a subnet.' => '{attribute} darf kein Subnetz sein.', + '{attribute} must not be an IPv4 address.' => '{attribute} darf keine IPv4-Adresse sein.', + '{attribute} must not be an IPv6 address.' => '{attribute} darf keine IPv6-Adresse sein.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} darf nicht "{compareValueOrAttribute}" sein.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} muss mindestens {min, number} Zeichen enthalten.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} darf maximal {max, number} Zeichen enthalten.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} muss aus genau {length, number} Zeichen bestehen.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 Tag} other{# Tage}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 Stunde} other{# Stunden}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 Minute} other{# Minuten}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 Monat} other{# Monate}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 Sekunde} other{# Sekunden}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 Jahr} other{# Jahre}}', '{delta, plural, =1{a day} other{# days}} ago' => 'vor {delta, plural, =1{einem Tag} other{# Tagen}}', '{delta, plural, =1{a minute} other{# minutes}} ago' => 'vor {delta, plural, =1{einer Minute} other{# Minuten}}', '{delta, plural, =1{a month} other{# months}} ago' => 'vor {delta, plural, =1{einem Monat} other{# Monaten}}', diff --git a/framework/messages/el/yii.php b/framework/messages/el/yii.php index 5edd1522ab..b765469c71 100644 --- a/framework/messages/el/yii.php +++ b/framework/messages/el/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,14 +17,33 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '{attribute} contains wrong subnet mask.' => 'Το {attribute} περιέχει λάθος μάσκα υποδικτύου.', + '{attribute} is not in the allowed range.' => 'Το {attribute} δεν είναι στο επιτρεπόμενο εύρος.', + '{attribute} must be a valid IP address.' => 'Το {attribute} πρέπει να είναι έγκυρη διεύθυνση IP.', + '{attribute} must be an IP address with specified subnet.' => 'Το {attribute} πρέπει να είναι διεύθυνση IP με καθορισμένη μάσκα.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => 'Το {attribute} πρέπει να είναι ίδιο με το "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => 'Το {attribute} πρέπει να είναι μεγαλύτερο από το "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => 'Το {attribute} πρέπει να είναι μεγαλύτερο ή ίσο από το "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => 'Το {attribute} πρέπει να είναι μικρότερο από το "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => 'Το {attribute} πρέπει να είναι μικρότερο ή ίσο από το "{compareValueOrAttribute}".', + '{attribute} must not be a subnet.' => 'Το {attribute} δεν πρέπει να είναι υποδίκτυο.', + '{attribute} must not be an IPv4 address.' => 'Το {attribute} δεν πρέπει να είναι διεύθυνση IPv4.', + '{attribute} must not be an IPv6 address.' => 'Το {attribute} δεν πρέπει να είναι διεύθυνση IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Το {attribute} δεν πρέπει να είναι ίδιο με το "{compareValueOrAttribute}".', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 ημέρα} other{# ημέρες}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 ώρα} other{# ώρες}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 λεπτό} other{# λεπτά}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 μήνας} other{# μήνες}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 δευτερόλεπτο} other{# δευτερόλεπτα}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 έτος} other{# έτη}}', '(not set)' => '(μη ορισμένο)', 'An internal server error occurred.' => 'Υπήρξε ένα εσωτερικό σφάλμα του διακομιστή.', 'Are you sure you want to delete this item?' => 'Είστε σίγουροι για τη διαγραφή του αντικειμένου;', 'Delete' => 'Διαγραφή', 'Error' => 'Σφάλμα', - 'File upload failed.' => 'Η μεταφόρτωση απέτυχε.', + 'File upload failed.' => 'Το ανέβασμα του αρχείου απέτυχε.', 'Home' => 'Αρχική', - 'Invalid data received for parameter "{param}".' => 'Μη έγκυρα δεδομένα για την παράμετρο "{param}".', + 'Invalid data received for parameter "{param}".' => 'Λήφθησαν μη έγκυρα δεδομένα για την παράμετρο "{param}".', 'Login Required' => 'Απαιτείται είσοδος', 'Missing required arguments: {params}' => 'Απουσιάζουν απαραίτητα ορίσματα: {params}', 'Missing required parameters: {params}' => 'Απουσιάζουν απαραίτητες παράμετροι: {params}', @@ -34,16 +53,16 @@ return [ 'Only files with these extensions are allowed: {extensions}.' => 'Επιτρέπονται αρχεία μόνο με καταλήξεις: {extensions}.', 'Page not found.' => 'Η σελίδα δε βρέθηκε.', 'Please fix the following errors:' => 'Παρακαλώ διορθώστε τα παρακάτω σφάλματα:', - 'Please upload a file.' => 'Παρακαλώ μεταφορτώστε ένα αρχείο.', + 'Please upload a file.' => 'Παρακαλώ ανεβάστε ένα αρχείο.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Εμφανίζονται {begin, number}-{end, number} από {totalCount, number}.', 'The file "{file}" is not an image.' => 'Το αρχείο «{file}» δεν είναι εικόνα.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Το αρχείο «{file}» είναι πολύ μεγάλο . Το μέγεθός του δεν μπορεί να είναι πάνω από {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Το αρχείο «{file}» είναι πολύ μικρό. Το μέγεθός του δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{byte} few{bytes} many{bytes} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Το αρχείο «{file}» είναι πολύ μεγάλο. Το μέγεθός του δεν μπορεί να είναι πάνω από {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Το αρχείο «{file}» είναι πολύ μικρό. Το μέγεθός του δεν μπορεί να είναι μικρότερο από {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Η μορφή του «{attribute}» δεν είναι έγκυρη.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μεγάλη. Το ύψος δεν μπορεί να είναι μεγαλύτερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μεγάλη. Το πλάτος δεν μπορεί να είναι μεγαλύτερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. To ύψος δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. Το πλάτος του δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. Το πλάτος δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', 'The requested view "{name}" was not found.' => 'Δε βρέθηκε η αιτούμενη όψη "{name}".', 'The verification code is incorrect.' => 'Ο κωδικός επαλήθευσης είναι εσφαλμένος.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Συνολικά {count, number} {count, plural, one{αντικείμενο} few{αντικείμενα} many{αντικείμενα} other{αντικείμενα}}.', @@ -66,21 +85,15 @@ return [ '{attribute} cannot be blank.' => 'Το «{attribute}» δεν μπορεί να είναι κενό.', '{attribute} is invalid.' => 'Το «{attribute}» δεν είναι έγκυρο.', '{attribute} is not a valid URL.' => 'Το «{attribute}» δεν είναι έγκυρη διεύθυνση URL.', - '{attribute} is not a valid email address.' => 'Η διεύθυνση email «{attribute}» δεν είναι έγκυρη.', + '{attribute} is not a valid email address.' => 'Το «{attribute}» δεν είναι έγκυρη διεύθυνση e-mail.', '{attribute} must be "{requiredValue}".' => 'Το «{attribute}» πρέπει να είναι «{requiredValue}».', '{attribute} must be a number.' => 'Το «{attribute}» πρέπει να είναι αριθμός.', '{attribute} must be a string.' => 'Το «{attribute}» πρέπει να είναι συμβολοσειρά.', '{attribute} must be an integer.' => 'Το «{attribute}» πρέπει να είναι ακέραιος.', '{attribute} must be either "{true}" or "{false}".' => 'Το «{attribute}» πρέπει να είναι «{true}» ή «{false}».', - '{attribute} must be greater than "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μεγαλύτερο από «{compareValue}».', - '{attribute} must be greater than or equal to "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μεγαλύτερο ή ίσο με «{compareValue}».', - '{attribute} must be less than "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μικρότερο από «{compareValue}».', - '{attribute} must be less than or equal to "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μικρότερο ή ίσο με «{compareValue}».', - '{attribute} must be no greater than {max}.' => 'Το «{attribute}» δεν πρέπει να ξεπερνά το {max}.', - '{attribute} must be no less than {min}.' => 'Το «{attribute}» δεν πρέπει να είναι λιγότερο από {min}.', - '{attribute} must be repeated exactly.' => 'Το «{attribute}» πρέπει να επαναληφθεί ακριβώς.', - '{attribute} must not be equal to "{compareValue}".' => 'Το «{attribute}» δεν πρέπει να είναι ίσο με «{compareValue}».', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το λιγότερο {min, number} {min, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', + '{attribute} must be no greater than {max}.' => 'Το «{attribute}» δεν πρέπει να είναι μεγαλύτερο από {max}.', + '{attribute} must be no less than {min}.' => 'Το «{attribute}» δεν πρέπει να είναι μικρότερο από {min}.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει τουλάχιστον {min, number} {min, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το πολύ {max, number} {max, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει {length, number} {length, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', '{delta, plural, =1{a day} other{# days}} ago' => 'πριν {delta, plural, =1{μία ημέρα} other{# ημέρες}}', diff --git a/framework/messages/es/yii.php b/framework/messages/es/yii.php index ceac8380b9..a9c9729984 100644 --- a/framework/messages/es/yii.php +++ b/framework/messages/es/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,6 +17,51 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + 'Powered by {yii}' => 'Desarrollado con {yii}', + 'The requested view "{name}" was not found.' => 'La vista solicitada "{name}" no fue encontrada', + 'Yii Framework' => 'Yii Framework', + 'just now' => 'justo ahora', + '{attribute} contains wrong subnet mask.' => '{attribute} contiene la máscara de subred incorrecta', + '{attribute} is not in the allowed range.' => '{attribute} no está en el rango permitido.', + '{attribute} must be a valid IP address.' => '{attribute} debe ser una dirección IP válida ', + '{attribute} must be an IP address with specified subnet.' => '{attribute} debe ser una dirección IP con subred especificada.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} debe ser igual a "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} debe ser mayor a "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} debe ser mayor o igual a "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} debe ser menor a "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} debe ser menor o igual a "{compareValueOrAttribute}".', + '{attribute} must not be a subnet.' => '{attribute} no debe ser una subnet.', + '{attribute} must not be an IPv4 address.' => '{attribute} no debe ser una dirección IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} no debe ser una dirección IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} no debe ser igual a "{compareValueOrAttribute}".', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 día} other{# días}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 hora} other{# horas}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuto} other{# minutos}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 mes} other{# meses}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 segundo} other{# segundos}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 año} other{# años}}', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', '(not set)' => '(no definido)', 'An internal server error occurred.' => 'Hubo un error interno del servidor.', 'Are you sure you want to delete this item?' => '¿Está seguro de eliminar este elemento?', @@ -29,8 +74,6 @@ return [ 'Missing required arguments: {params}' => 'Argumentos requeridos ausentes: {params}', 'Missing required parameters: {params}' => 'Parámetros requeridos ausentes: {params}', 'No' => 'No', - 'No help for unknown command "{command}".' => 'No existe ayuda para el comando desconocido "{command}"', - 'No help for unknown sub-command "{command}".' => 'No existe ayuda para el sub-comando desconocido "{command}"', 'No results found.' => 'No se encontraron resultados.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Sólo se aceptan archivos con los siguientes tipos MIME: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Sólo se aceptan archivos con las siguientes extensiones: {extensions}', @@ -39,8 +82,8 @@ return [ 'Please upload a file.' => 'Por favor suba un archivo.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Mostrando {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{elemento} other{elementos}}.', 'The file "{file}" is not an image.' => 'El archivo "{file}" no es una imagen.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'El archivo "{file}" es demasiado grande. Su tamaño no puede exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'El archivo "{file}" es demasiado pequeño. Su tamaño no puede ser menor a {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'El archivo "{file}" es demasiado grande. Su tamaño no puede exceder {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'El archivo "{file}" es demasiado pequeño. Su tamaño no puede ser menor a {formattedLimit}.', 'The format of {attribute} is invalid.' => 'El formato de {attribute} es inválido.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado grande. La altura no puede ser mayor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado grande. La anchura no puede ser mayor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', @@ -49,7 +92,7 @@ return [ 'The verification code is incorrect.' => 'El código de verificación es incorrecto.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{elemento} other{elementos}}.', 'Unable to verify your data submission.' => 'Incapaz de verificar los datos enviados.', - 'Unknown command "{command}".' => 'Comando desconocido "{command}".', + 'Unknown alias: -{name}' => 'Alias desconocido: -{name}', 'Unknown option: --{name}' => 'Opción desconocida: --{name}', 'Update' => 'Actualizar', 'View' => 'Ver', @@ -73,14 +116,8 @@ return [ '{attribute} must be a string.' => '{attribute} debe ser una cadena de caracteres.', '{attribute} must be an integer.' => '{attribute} debe ser un número entero.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} debe ser "{true}" o "{false}".', - '{attribute} must be greater than "{compareValue}".' => '{attribute} debe ser mayor a "{compareValue}', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} debe ser mayor o igual a "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} debe ser menor a "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} debe ser menor o igual a "{compareValue}".', '{attribute} must be no greater than {max}.' => '{attribute} no debe ser mayor a {max}.', '{attribute} must be no less than {min}.' => '{attribute} no debe ser menor a {min}.', - '{attribute} must be repeated exactly.' => '{attribute} debe ser repetido exactamente igual.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} no debe ser igual a "{compareValue}".', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} debería contener al menos {min, number} {min, plural, one{letra} other{letras}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} debería contener como máximo {max, number} {max, plural, one{letra} other{letras}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} debería contener {length, number} {length, plural, one{letra} other{letras}}.', @@ -90,16 +127,4 @@ return [ '{delta, plural, =1{a second} other{# seconds}} ago' => 'hace {delta, plural, =1{un segundo} other{# segundos}}', '{delta, plural, =1{a year} other{# years}} ago' => 'hace {delta, plural, =1{un año} other{# años}}', '{delta, plural, =1{an hour} other{# hours}} ago' => 'hace {delta, plural, =1{una hora} other{# horas}}', - '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', - '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', - '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', - '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', - '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', - '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', - '{n} B' => '{n} B', - '{n} GB' => '{n} GB', - '{n} KB' => '{n} KB', - '{n} MB' => '{n} MB', - '{n} PB' => '{n} PB', - '{n} TB' => '{n} TB', ]; diff --git a/framework/messages/et/yii.php b/framework/messages/et/yii.php index a29a03440e..cf362f62d8 100644 --- a/framework/messages/et/yii.php +++ b/framework/messages/et/yii.php @@ -24,13 +24,13 @@ return array ( 'Error' => 'Viga', 'File upload failed.' => 'Faili üleslaadimine ebaõnnestus.', 'Home' => 'Avaleht', - 'Invalid data received for parameter "{param}".' => 'Vastu võeti vigased andmed parameetri "{param}" jaoks.', - 'Login Required' => 'Vajab sisselogimist', + 'Invalid data received for parameter "{param}".' => 'Parameetri "{param}" jaoks võeti vastu vigased andmed.', + 'Login Required' => 'Vajalik on sisse logimine', 'Missing required arguments: {params}' => 'Puuduvad nõutud argumendid: {params}', 'Missing required parameters: {params}' => 'Puuduvad nõutud parameetrid: {params}', 'No' => 'Ei', - 'No help for unknown command "{command}".' => 'Abi puudub tundmatu käsu "{command}" jaoks.', - 'No help for unknown sub-command "{command}".' => 'Abi puudub tundmatu alamkäsu "{command}" jaoks.', + 'No help for unknown command "{command}".' => 'Tundmatu käsu "{command}" jaoks puudub abi.', + 'No help for unknown sub-command "{command}".' => 'Tundmatu alamkäsu "{command}" jaoks puudub abi.', 'No results found.' => 'Ei leitud ühtegi tulemust.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Lubatud on ainult nende MIME tüüpidega failid: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Lubatud on ainult nende faililaienditega failid: {extensions}.', @@ -38,18 +38,18 @@ return array ( 'Please fix the following errors:' => 'Palun parandage järgnevad vead:', 'Please upload a file.' => 'Palun laadige fail üles.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Näitan {totalCount, number} {totalCount, plural, one{üksusest} other{üksusest}} {begin, number}-{end, number}.', - 'The file "{file}" is not an image.' => 'Fail "{file}" ei ole pilt.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fail "{file}" on liiga suur. Suurus ei tohi ületada {limit, number} {limit, plural, one{baiti} other{baiti}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fail "{file}" on liiga väike. Suurus ei tohi olla väiksem kui {limit, number} {limit, plural, one{baiti} other{baiti}}.', + 'The file "{file}" is not an image.' => 'See fail "{file}" ei ole pilt.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'See fail "{file}" on liiga suur. Suurus ei tohi ületada {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'See fail "{file}" on liiga väike. Suurus ei tohi olla väiksem kui {formattedLimit}.', 'The format of {attribute} is invalid.' => '{attribute} on sobimatus vormingus.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Kõrgus ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Laius ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Kõrgus ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Laius ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The requested view "{name}" was not found.' => 'Soovitud vaadet "{name}" ei leitud.', - 'The verification code is incorrect.' => 'Kontrollkood ei ole õige.', + 'The verification code is incorrect.' => 'Kontrollkood on vale.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Kokku {count, number} {count, plural, one{üksus} other{üksust}}.', - 'Unable to verify your data submission.' => 'Ei suuda teie andmesööte õigsuses veenduda.', + 'Unable to verify your data submission.' => 'Ei suuda edastatud andmete õigsuses veenduda.', 'Unknown command "{command}".' => 'Tundmatu käsklus "{command}".', 'Unknown option: --{name}' => 'Tundmatu valik: --{name}', 'Update' => 'Muuda', @@ -71,7 +71,7 @@ return array ( '{attribute} is not a valid email address.' => '{attribute} ei ole korrektne e-posti aadress.', '{attribute} must be "{requiredValue}".' => '{attribute} peab olema "{requiredValue}".', '{attribute} must be a number.' => '{attribute} peab olema number.', - '{attribute} must be a string.' => '{attribute} peab olema sõne.', + '{attribute} must be a string.' => '{attribute} peab olema tekst.', '{attribute} must be an integer.' => '{attribute} peab olema täisarv.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} peab olema kas "{true}" või "{false}".', '{attribute} must be greater than "{compareValue}".' => '{attribute} peab olema suurem kui "{compareValue}".', @@ -82,9 +82,9 @@ return array ( '{attribute} must be no less than {min}.' => '{attribute} ei tohi olla väiksem kui {min}.', '{attribute} must be repeated exactly.' => '{attribute} peab täpselt kattuma.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} ei tohi olla "{compareValue}".', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama vähemalt {min, number} {min, plural, one{märki} other{märki}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tohib sisaldada maksimaalselt {max, number} {max, plural, one{märki} other{märki}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama {length, number} {length, plural, one{märki} other{märki}}.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama vähemalt {min, number} {min, plural, one{tähemärki} other{tähemärki}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tohib sisaldada maksimaalselt {max, number} {max, plural, one{tähemärki} other{tähemärki}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama {length, number} {length, plural, one{tähemärki} other{tähemärki}}.', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{üks päev} other{# päeva}} tagasi', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{üks minut} other{# minutit}} tagasi', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{kuu aega} other{# kuud}} tagasi', diff --git a/framework/messages/fa/yii.php b/framework/messages/fa/yii.php index 3b97690ff2..8ce9af5d99 100644 --- a/framework/messages/fa/yii.php +++ b/framework/messages/fa/yii.php @@ -20,11 +20,109 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '(not set)' => '(تنظیم نشده)', + 'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.', 'Are you sure you want to delete this item?' => 'آیا اطمینان به حذف این مورد دارید؟', + 'Delete' => 'حذف', + 'Error' => 'خطا', + 'File upload failed.' => 'آپلود فایل ناموفق بود.', + 'Home' => 'صفحه‌اصلی', + 'Invalid data received for parameter "{param}".' => 'برای پارامتر "{param}" اطلاعات نادرستی دریافت شده است.', + 'Login Required' => 'ورود اجباری', + 'Missing required arguments: {params}' => 'فاقد آرگومان‌های مورد نیاز: {params}', + 'Missing required parameters: {params}' => 'فاقد پارامترهای مورد نیاز: {params}', + 'No' => 'خیر', + 'No results found.' => 'نتیجه‌ای یافت نشد.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'فقط این نوع فایل‌ها مجاز می‌باشند: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'فقط فایل‌های با این پسوندها مجاز هستند: {extensions}.', + 'Page not found.' => 'صفحه‌ای یافت نشد.', + 'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:', + 'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.', + 'Powered by {yii}' => 'طراحی شده توسط {yii}', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', + 'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'حجم فایل "{file}" بسیار بیشتر می باشد. حجم آن نمی تواند از {formattedLimit} بیشتر باشد.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'حجم فایل "{file}" بسیار کم می باشد. حجم آن نمی تواند از {formattedLimit} کمتر باشد.', + 'The format of {attribute} is invalid.' => 'قالب {attribute} نامعتبر است.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. ارتفاع نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. عرض نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. ارتفاع نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. عرض نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', 'The requested view "{name}" was not found.' => 'نمای درخواستی "{name}" یافت نشد.', + 'The verification code is incorrect.' => 'کد تائید اشتباه است.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} مورد.', + 'Unable to verify your data submission.' => 'قادر به تائید اطلاعات ارسالی شما نمی‌باشد.', + 'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}', + 'Update' => 'بروزرسانی', + 'View' => 'نما', + 'Yes' => 'بله', + 'Yii Framework' => 'فریم ورک یی', + 'You are not allowed to perform this action.' => 'شما برای انجام این عملیات، دسترسی ندارید.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'شما حداکثر {limit, number} فایل را می‌توانید آپلود کنید.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta} روز دیگر', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} دقیقه دیگر', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta} ماه دیگر', 'in {delta, plural, =1{a second} other{# seconds}}' => 'در {delta} ثانیه', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta} سال دیگر', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} ساعت دیگر', + 'just now' => 'هم اکنون', + 'the input value' => 'مقدار ورودی', + '{attribute} "{value}" has already been taken.' => '{attribute} با مقدار "{value}" در حال حاضر گرفته‌شده است.', + '{attribute} cannot be blank.' => '{attribute} نمی‌تواند خالی باشد.', + '{attribute} contains wrong subnet mask.' => '{attribute} شامل فرمت زیرشبکه اشتباه است.', + '{attribute} is invalid.' => '{attribute} معتبر نیست.', + '{attribute} is not a valid URL.' => '{attribute} یک URL معتبر نیست.', + '{attribute} is not a valid email address.' => '{attribute} یک آدرس ایمیل معتبر نیست.', + '{attribute} is not in the allowed range.' => '{attribute} در محدوده مجاز نمی باشد.', + '{attribute} must be "{requiredValue}".' => '{attribute} باید "{requiredValue}" باشد.', + '{attribute} must be a number.' => '{attribute} باید یک عدد باشد.', + '{attribute} must be a string.' => '{attribute} باید یک رشته باشد.', + '{attribute} must be a valid IP address.' => '{attribute} باید IP صحیح باشد.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} باید یک IP آدرسی با زیرشبکه بخصوص باشد.', + '{attribute} must be an integer.' => '{attribute} باید یک عدد صحیح باشد.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} باید "{true}" و یا "{false}" باشد.', +<<<<<<< HEAD + '{attribute} must be greater than "{compareValue}".' => '{attribute} باید بزرگتر از "{compareValue}" باشد.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} باید بزرگتر و یا مساوی "{compareValue}" باشد.', + '{attribute} must be less than "{compareValue}".' => '{attribute} باید کوچکتر از "{compareValue}" باشد.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} باید کوچکتر و یا مساوی "{compareValue}" باشد.', +<<<<<<< HEAD + '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{compareValue}" باشد.', + '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{compareValue}" باشد.', +======= + '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{max}" باشد.', + '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{min}" باشد.', +>>>>>>> yiichina/master + '{attribute} must be repeated exactly.' => '{attribute} عیناً باید تکرار شود.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} نباید برابر با "{compareValue}" باشد.', +======= + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} باید با "{compareValueOrAttribute}" برابر باشد.', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} باید بزرگتر از "{compareValueOrAttribute}" باشد.', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} باید بزرکتر یا برابر با "{compareValueOrAttribute}" باشد.', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} باید کمتر از "{compareValueOrAttribute}" باشد.', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} باید کمتر یا برابر با "{compareValueOrAttribute}" باشد.', + '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{max}" باشد.', + '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{min}" باشد.', + '{attribute} must not be a subnet.' => '{attribute} باید کمتر یا برابر "{compareValueOrAttribute}" باشد.', + '{attribute} must not be an IPv4 address.' => '{attribute} باید آدرس IPv4 نباشد.', + '{attribute} must not be an IPv6 address.' => '{attribute} باید آدرس IPv6 نباشد.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} باید مانند "{compareValueOrAttribute}" تکرار نشود.', +>>>>>>> master + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} حداقل باید شامل {min, number} کارکتر باشد.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} حداکثر باید شامل {max, number} کارکتر باشد.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} باید شامل {length, number} کارکتر باشد.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta} روز', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta} ساعت', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta} دقیقه', + '{delta, plural, =1{1 month} other{# months}}' => '{delta} ماه', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta} ثانیه', + '{delta, plural, =1{1 year} other{# years}}' => '{delta} سال', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} روز قبل', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} دقیقه قبل', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} ماه قبل', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} ثانیه قبل', '{delta, plural, =1{a year} other{# years}} ago' => '{delta} سال پیش', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} ساعت قبل', '{nFormatted} B' => '{nFormatted} B', '{nFormatted} GB' => '{nFormatted} GB', '{nFormatted} GiB' => '{nFormatted} GiB', @@ -47,76 +145,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} پتابایت', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} تبی‌بایت', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} ترابایت', - '(not set)' => '(تنظیم نشده)', - 'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.', - 'Delete' => 'حذف', - 'Error' => 'خطا', - 'File upload failed.' => 'آپلود فایل ناموفق بود.', - 'Home' => 'صفحه‌اصلی', - 'Invalid data received for parameter "{param}".' => 'برای پارامتر "{param}" اطلاعات نادرستی دریافت شده است.', - 'Login Required' => 'ورود اجباری', - 'Missing required arguments: {params}' => 'فاقد آرگومان‌های مورد نیاز: {params}', - 'Missing required parameters: {params}' => 'فاقد پارامترهای مورد نیاز: {params}', - 'No' => 'خیر', - 'No results found.' => 'نتیجه‌ای یافت نشد.', - 'Only files with these extensions are allowed: {extensions}.' => 'فقط فایل‌های با این پسوندها مجاز هستند: {extensions}.', - 'Page not found.' => 'صفحه‌ای یافت نشد.', - 'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:', - 'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', - 'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'حجم فایل "{file}" بیش از حد زیاد است. مقدار آن نمی‌تواند بیشتر از {limit, number} بایت باشد.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'حجم فایل "{file}" بیش از حد کم است. مقدار آن نمی‌تواند کمتر از {limit, number} بایت باشد.', - 'The format of {attribute} is invalid.' => 'قالب {attribute} نامعتبر است.', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. ارتفاع نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. عرض نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. ارتفاع نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. عرض نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', - 'The verification code is incorrect.' => 'کد تائید اشتباه است.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} مورد.', - 'Unable to verify your data submission.' => 'قادر به تائید اطلاعات ارسالی شما نمی‌باشد.', - 'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}', - 'Update' => 'بروزرسانی', - 'View' => 'نما', - 'Yes' => 'بله', - 'You are not allowed to perform this action.' => 'شما برای انجام این عملیات، دسترسی ندارید.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'شما حداکثر {limit, number} فایل را می‌توانید آپلود کنید.', - 'in {delta, plural, =1{a day} other{# days}}' => '{delta} روز دیگر', - 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} دقیقه دیگر', - 'in {delta, plural, =1{a month} other{# months}}' => '{delta} ماه دیگر', - 'in {delta, plural, =1{a year} other{# years}}' => '{delta} سال دیگر', - 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} ساعت دیگر', - 'just now' => 'هم اکنون', - 'the input value' => 'مقدار ورودی', - '{attribute} "{value}" has already been taken.' => '{attribute} با مقدار "{value}" در حال حاضر گرفته‌شده است.', - '{attribute} cannot be blank.' => '{attribute} نمی‌تواند خالی باشد.', - '{attribute} is invalid.' => '{attribute} معتبر نیست.', - '{attribute} is not a valid URL.' => '{attribute} یک URL معتبر نیست.', - '{attribute} is not a valid email address.' => '{attribute} یک آدرس ایمیل معتبر نیست.', - '{attribute} must be "{requiredValue}".' => '{attribute} باید "{requiredValue}" باشد.', - '{attribute} must be a number.' => '{attribute} باید یک عدد باشد.', - '{attribute} must be a string.' => '{attribute} باید یک رشته باشد.', - '{attribute} must be an integer.' => '{attribute} باید یک عدد صحیح باشد.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} باید "{true}" و یا "{false}" باشد.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} باید بزرگتر از "{compareValue}" باشد.', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} باید بزرگتر و یا مساوی "{compareValue}" باشد.', - '{attribute} must be less than "{compareValue}".' => '{attribute} باید کوچکتر از "{compareValue}" باشد.', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} باید کوچکتر و یا مساوی "{compareValue}" باشد.', -<<<<<<< HEAD - '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{compareValue}" باشد.', - '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{compareValue}" باشد.', -======= - '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{max}" باشد.', - '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{min}" باشد.', ->>>>>>> yiichina/master - '{attribute} must be repeated exactly.' => '{attribute} عیناً باید تکرار شود.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} نباید برابر با "{compareValue}" باشد.', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} حداقل باید شامل {min, number} کارکتر باشد.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} حداکثر باید شامل {max, number} کارکتر باشد.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} باید شامل {length, number} کارکتر باشد.', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta} روز قبل', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} دقیقه قبل', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta} ماه قبل', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} ثانیه قبل', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} ساعت قبل', ]; diff --git a/framework/messages/fi/yii.php b/framework/messages/fi/yii.php index 6310954b93..71cccd899f 100644 --- a/framework/messages/fi/yii.php +++ b/framework/messages/fi/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,6 +17,14 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + 'Powered by {yii}' => 'Powered by {yii}', + 'Yii Framework' => 'Yii Framework', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} täytyy olla yhtä suuri kuin "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} täytyy olla suurempi kuin "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} täytyy olla suurempi tai yhtä suuri kuin "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} täytyy olla pienempi kuin "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} täytyy olla pienempi tai yhtä suuri kuin "{compareValueOrAttribute}".', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} ei saa olla yhtä suuri kuin "{compareValueOrAttribute}".', '(not set)' => '(ei asetettu)', 'An internal server error occurred.' => 'Sisäinen palvelinvirhe.', 'Are you sure you want to delete this item?' => 'Haluatko varmasti poistaa tämän?', @@ -37,8 +45,8 @@ return [ 'Please upload a file.' => 'Lähetä tiedosto.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Näytetään {begin, number}-{end, number} kaikkiaan {totalCount, number} {totalCount, plural, one{tuloksesta} other{tuloksesta}}.', 'The file "{file}" is not an image.' => 'Tiedosto "{file}" ei ole kuva.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Tiedosto "{file}" on liian iso. Sen koko ei voi olla suurempi kuin {limit, number} {limit, plural, one{tavu} other{tavua}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Tiedosto "{file}" on liian pieni. Sen koko ei voi olla pienempi kuin {limit, number} {limit, plural, one{tavu} other{tavua}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Tiedosto "{file}" on liian iso. Sen koko ei voi olla suurempi kuin {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Tiedosto "{file}" on liian pieni. Sen koko ei voi olla pienempi kuin {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Attribuutin {attribute} formaatti on virheellinen.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian suuri. Korkeus ei voi olla suurempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian suuri. Leveys ei voi olla suurempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', @@ -64,25 +72,32 @@ return [ 'the input value' => 'syötetty arvo', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" on jo käytössä.', '{attribute} cannot be blank.' => '{attribute} ei voi olla tyhjä.', + '{attribute} contains wrong subnet mask.' => '{attribute} sisältää väärän aliverkkopeitteen.', '{attribute} is invalid.' => '{attribute} on virheellinen.', '{attribute} is not a valid URL.' => '{attribute} on virheellinen URL.', '{attribute} is not a valid email address.' => '{attribute} on virheellinen sähköpostiosoite.', + '{attribute} is not in the allowed range.' => '{attribute} ei ole sallitulla alueella.', '{attribute} must be "{requiredValue}".' => '{attribute} täytyy olla "{requiredValue}".', '{attribute} must be a number.' => '{attribute} täytyy olla luku.', '{attribute} must be a string.' => '{attribute} täytyy olla merkkijono.', + '{attribute} must be a valid IP address.' => '{attribute} täytyy olla kelvollinen IP-osoite.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} täytyy olla määritetyllä aliverkolla oleva IP-osoite.', '{attribute} must be an integer.' => '{attribute} täytyy olla kokonaisluku.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} täytyy olla joko {true} tai {false}.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} täytyy olla suurempi kuin "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} täytyy olla suurempi tai yhtä suuri kuin "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} täytyy olla pienempi kuin "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} täytyy olla pienempi tai yhtä suuri kuin "{compareValue}".', '{attribute} must be no greater than {max}.' => '{attribute} ei saa olla suurempi kuin "{max}".', '{attribute} must be no less than {min}.' => '{attribute} ei saa olla pienempi kuin "{min}".', - '{attribute} must be repeated exactly.' => '{attribute} täytyy toistaa täsmälleen.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} ei saa olla yhtä suuri kuin "{compareValue}".', + '{attribute} must not be a subnet.' => '{attribute} ei saa olla aliverkko.', + '{attribute} must not be an IPv4 address.' => '{attribute} ei saa olla IPv4-osoite.', + '{attribute} must not be an IPv6 address.' => '{attribute} ei saa olla IPv6-osoite.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää vähintään {min, number} {min, plural, one{merkki} other{merkkiä}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää enintään {max, number} {max, plural, one{merkki} other{merkkiä}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää {length, number} {length, plural, one{merkki} other{merkkiä}}.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 päivä} other{# päivää}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 tunti} other{# tuntia}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuutti} other{# minuuttia}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 kuukausi} other{# kuukautta}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 sekunti} other{# sekuntia}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 vuosi} other{# vuotta}}', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{päivä} other{# päivää}} sitten', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minuutti} other{# minuuttia}} sitten', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{kuukausi} other{# kuukautta}} sitten', diff --git a/framework/messages/fr/yii.php b/framework/messages/fr/yii.php index 5ff893174f..99b4be776d 100644 --- a/framework/messages/fr/yii.php +++ b/framework/messages/fr/yii.php @@ -17,6 +17,12 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 jour} other{# jours}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 heure} other{# heures}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minute} other{# minutes}}', + '{delta, plural, =1{1 month} other{# months}}' => 'mois', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 seconde} other{# secondes}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 année} other{# années}}', '(not set)' => '(non défini)', 'An internal server error occurred.' => 'Une erreur de serveur interne s\'est produite.', 'Are you sure you want to delete this item?' => 'Êtes-vous sûr de vouloir supprimer cet élément ?', @@ -29,27 +35,25 @@ return [ 'Missing required arguments: {params}' => 'Arguments manquants requis : {params}', 'Missing required parameters: {params}' => 'Paramètres manquants requis : {params}', 'No' => 'Non', - 'No help for unknown command "{command}".' => 'Aucune aide pour la commande inconnue « {command} ».', - 'No help for unknown sub-command "{command}".' => 'Aucune aide pour la sous-commande inconnue « {command} ».', 'No results found.' => 'Aucun résultat trouvé.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Seulement les fichiers ayant ces types MIME sont autorisés : {mimeTypes}.', - 'Only files with these extensions are allowed: {extensions}.' => 'Les extensions de fichier autorisées sont : {extensions}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Les extensions de fichiers autorisées sont : {extensions}.', 'Page not found.' => 'Page non trouvée.', 'Please fix the following errors:' => 'Veuillez vérifier les erreurs suivantes :', 'Please upload a file.' => 'Veuillez télécharger un fichier.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Affichage de {begin, number}-{end, number} sur {totalCount, number} {totalCount, plural, one{élément} other{éléments}}.', 'The file "{file}" is not an image.' => 'Le fichier « {file} » n\'est pas une image.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Le fichier « {file} » est trop gros. Sa taille ne peut dépasser {limit, number} {limit, plural, one{octet} other{octets}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Le fichier « {file} » est trop petit. Sa taille ne peut être inférieure à {limit, number} {limit, plural, one{octet} other{octets}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Le fichier « {file} » est trop gros. Sa taille ne peut dépasser {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Le fichier « {file} » est trop petit. Sa taille ne peut être inférieure à {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Le format de {attribute} est invalide', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop grande. La hauteur ne peut être supérieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop large. La largeur ne peut être supérieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop petite. La hauteur ne peut être inférieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop petite. La largeur ne peut être inférieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The requested view "{name}" was not found.' => 'La vue demandée « {name} » n\'a pas été trouvée.', 'The verification code is incorrect.' => 'Le code de vérification est incorrect.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{élément} other{éléments}}.', 'Unable to verify your data submission.' => 'Impossible de vérifier votre envoi de données.', - 'Unknown command "{command}".' => 'Commande inconnue : « {command} ».', 'Unknown option: --{name}' => 'Option inconnue : --{name}', 'Update' => 'Modifier', 'View' => 'Voir', @@ -62,25 +66,33 @@ return [ 'in {delta, plural, =1{a second} other{# seconds}}' => 'dans {delta, plural, =1{une seconde} other{# secondes}}', 'in {delta, plural, =1{a year} other{# years}}' => 'dans {delta, plural, =1{un an} other{# ans}}', 'in {delta, plural, =1{an hour} other{# hours}}' => 'dans {delta, plural, =1{une heure} other{# heures}}', + 'just now' => 'à l\'instant', 'the input value' => 'la valeur d\'entrée', '{attribute} "{value}" has already been taken.' => '{attribute} « {value} » a déjà été pris.', '{attribute} cannot be blank.' => '{attribute} ne peut être vide.', + '{attribute} contains wrong subnet mask.' => '{attribute} contient un masque de sous-réseau non valide.', '{attribute} is invalid.' => '{attribute} est invalide.', '{attribute} is not a valid URL.' => '{attribute} n\'est pas une URL valide.', '{attribute} is not a valid email address.' => '{attribute} n\'est pas une adresse email valide.', + '{attribute} is not in the allowed range.' => '{attribute} n\'est pas dans la plage autorisée.', '{attribute} must be "{requiredValue}".' => '{attribute} doit êre « {requiredValue} ».', '{attribute} must be a number.' => '{attribute} doit être un nombre.', - '{attribute} must be a string.' => '{attribute} doit être une chaîne.', + '{attribute} must be a string.' => '{attribute} doit être au format texte.', + '{attribute} must be a valid IP address.' => '{attribute} doit être une adresse IP valide.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} doit être une adresse IP avec un sous-réseau.', '{attribute} must be an integer.' => '{attribute} doit être un entier.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} doit être soit {true} soit {false}.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} doit être supérieur à « {compareValue} ».', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} doit être supérieur ou égal à « {compareValue} ».', - '{attribute} must be less than "{compareValue}".' => '{attribute} doit être inférieur à « {compareValue} ».', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} doit être inférieur ou égal à « {compareValue} ».', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} doit être soit « {true} » soit « {false} ».', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} doit être supérieur à « {compareValueOrAttribute} ».', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} doit être supérieur ou égal à « {compareValueOrAttribute} ».', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} doit être inférieur à « {compareValueOrAttribute} ».', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} doit être inférieur ou égal à « {compareValueOrAttribute} ».', '{attribute} must be no greater than {max}.' => '{attribute} ne doit pas être supérieur à {max}.', '{attribute} must be no less than {min}.' => '{attribute} ne doit pas être inférieur à {min}.', '{attribute} must be repeated exactly.' => '{attribute} doit être identique.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} ne doit pas être égal à « {compareValue} ».', + '{attribute} must not be a subnet.' => '{attribute} ne doit pas être un sous-réseau.', + '{attribute} must not be an IPv4 address.' => '{attribute} ne doit pas être une adresse IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} ne doit pas être une adresse IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} ne doit pas être égal à « {compareValueOrAttribute} ».', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} doit comporter au moins {min, number} {min, plural, one{caractère} other{caractères}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} doit comporter au plus {max, number} {max, plural, one{caractère} other{caractères}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} doit comporter {length, number} {length, plural, one{caractère} other{caractères}}.', @@ -90,16 +102,26 @@ return [ '{delta, plural, =1{a second} other{# seconds}} ago' => 'il y a {delta, plural, =1{une seconde} other{# secondes}}', '{delta, plural, =1{a year} other{# years}} ago' => 'il y a {delta, plural, =1{un an} other{# ans}}', '{delta, plural, =1{an hour} other{# hours}} ago' => 'il y a {delta, plural, =1{une heure} other{# heures}}', - '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# octet} other{# octets}}', - '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigaoctet} other{# gigaoctets}}', - '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilooctet} other{# kilooctets}}', - '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megaoctet} other{# megaoctets}}', - '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petaoctet} other{# petaoctets}}', - '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# teraoctet} other{# teraoctets}}', - '{n} B' => '{n} o', - '{n} GB' => '{n} Go', - '{n} KB' => '{n} Ko', - '{n} MB' => '{n} Mo', - '{n} PB' => '{n} Po', - '{n} TB' => '{n} To', + '{nFormatted} B' => '{nFormatted} o', + '{nFormatted} GB' => '{nFormatted} Go', + '{nFormatted} GiB' => '{nFormatted} Gio', + '{nFormatted} KB' => '{nFormatted} Ko', + '{nFormatted} KiB' => '{nFormatted} Kio', + '{nFormatted} MB' => '{nFormatted} Mo', + '{nFormatted} MiB' => '{nFormatted} Mio', + '{nFormatted} PB' => '{nFormatted} Po', + '{nFormatted} PiB' => '{nFormatted} Pio', + '{nFormatted} TB' => '{nFormatted} To', + '{nFormatted} TiB' => '{nFormatted} Tio', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{octet} other{octets}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{# gigaoctet} other{# gigaoctets}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gibioctet} other{gibioctets}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibioctet} other{kibioctets}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{# kilooctet} other{# kilooctets}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebioctet} other{mebioctets}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{# megaoctet} other{# megaoctets}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebioctet} other{pebioctets}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{# petaoctet} other{# petaoctets}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{# teraoctet} other{# teraoctets}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{# teraoctet} other{# teraoctets}}', ]; diff --git a/framework/messages/he/yii.php b/framework/messages/he/yii.php index 2b0c1cd70e..7a6bec8edf 100644 --- a/framework/messages/he/yii.php +++ b/framework/messages/he/yii.php @@ -37,8 +37,8 @@ return [ 'Please upload a file.' => 'נא העלה קובץ.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'מציג {begin, number}-{end, number} מתוך {totalCount, number} {totalCount, plural, one{רשומה} other{רשומות}}.', 'The file "{file}" is not an image.' => 'הקובץ "{file}" אינו קובץ תמונה.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'הקובץ "{file}" גדול מדי. גודל זה אינו מצליח {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'הקובץ "{file}" קטן מדי. הקובץ אינו יכול להיות קטן מ {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'הקובץ "{file}" גדול מדי. גודל זה אינו מצליח {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'הקובץ "{file}" קטן מדי. הקובץ אינו יכול להיות קטן מ {formattedLimit}.', 'The format of {attribute} is invalid.' => 'הפורמט של {attribute} אינו חוקי.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'התמונה "{file}" גדולה מדי. הגובה לא יכול להיות גדול מ {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'התמונה "{file}" גדולה מדי. הרוחב לא יכול להיות גדול מ {limit, number} {limit, plural, one{pixel} other{pixels}}.', @@ -74,6 +74,6 @@ return [ '{attribute} must be repeated exactly.' => '{attribute} חייב להיות מוחזר בדיוק.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} חייב להיות שווה ל "{compareValue}"', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} אמור לכלול לפחות {min, number} {min, plural, one{תו} few{תוים} many{תוים}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} אמור לא לכלול יותר מ {max, number} {max, plural, one{תו} few{תוים} many{תוים}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} אמור לא לכלול יותר מ{max, number} {max, plural, one{תו} other{תוים}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} אמור לכלול {length, number} {length, plural, one{תו} few{תוים} many{תוים}}.', ]; diff --git a/framework/messages/hr/yii.php b/framework/messages/hr/yii.php new file mode 100644 index 0000000000..23ae4cdf70 --- /dev/null +++ b/framework/messages/hr/yii.php @@ -0,0 +1,105 @@ + '(nije postavljeno)', + 'An internal server error occurred.' => 'Došlo je do interne pogreške servera.', + 'Are you sure you want to delete this item' => 'Želiš li to obrisati?', + 'Delete' => 'Obrisati', + 'Error' => 'Pogreška', + 'File upload failed.' => 'Upload podatka nije uspio.', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Nevažeći podaci primljeni za parametar "{param}"', + 'Login Required' => 'Prijava potrebna', + 'Missing required arguments: {params}' => 'Nedostaju potrebni argunenti: {params}', + 'Missing required parameters: {params}' => 'Nedostaju potrebni parametri: {params}', + 'No' => 'Ne', + 'No help for unknown command "{command}".' => 'Nema pomoći za nepoznatu naredbu "{command}"', + 'No help for unknown sub-command "{command}".' => 'Nema pomoći za nepoznatu pod-naredbu "{command}"', + 'No results found.' => 'Nema rezultata.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Samo datoteke s ovim MIME vrstama su dopuštene: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Samo datoteke s ovim eksentinzijama su dopuštene:: {extensions}', + 'Page not found.' => 'Stranica nije pronađena.', + 'Please fix the following errors:' => 'Molimo vas ispravite pogreške:', + 'Please upload a file.' => 'Molimo vas da uploadate datoteku.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazuj {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, =1{stavka} one{# stavka} few{# stavke} many{# stavki} other{# stavki}}.', + 'The file "{file}" is not an image.' => 'Podatak "{file}" nije slika.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Podatak "{file}" je prevelik. Ne smije biti veći od {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Podatak "{file}" je premalen. Ne smije biti manji od {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Format od {attribute} je nevažeći.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Visina slike ne smije biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Širina slike ne smije biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premalena. Visina slike ne smije biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premalena. Širina slike ne smije biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The verification code is incorrect.' => 'Kod za provjeru nije točan.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ukupno {count, number} {count, plural, =1{stavka} one{# stavka} few{# stavke} many{# stavki} other{# stavki}}.', + 'Unable to verify your data submission.' => 'Nije moguće provjeriti podnesene podatke.', + 'Unknown command "{command}".' => 'Nepoznata naredba "{command}".', + 'Unknown option: --{name}' => 'Nepoznata opcija: --{name}', + 'Update' => 'Uredi', + 'View' => 'Pregled', + 'Yes' => 'Da', + 'You are not allowed to perform this action.' => 'Nije vam dopušteno obavljati tu radnju.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Najviše možete uploadat {limit, number} {limit, plural, one{fajl} other{fajlova}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'u {delta, plural, =1{dan} one{# dan} few{# dana} many{# dana} other{# dana}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'u {delta, plural, =1{minuta} one{# minuta} few{# minute} many{# minuta} other{# minuta}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'u {delta, plural, =1{mjesec} one{# mjesec} few{# mjeseca} many{# mjeseci} other{# mjeseci}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'u {delta, plural, =1{sekunda} one{# sekunda} few{# sekunde} many{# sekundi} other{# sekundi}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'u {delta, plural, =1{godina} one{# godine} few{# godine} many{# godina} other{# godina}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'u {delta, plural, =1{sat} one{# sat} few{# sata} many{# sati} other{# sati}}', + 'the input value' => 'ulazna vrijednost', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" već se koristi.', + '{attribute} cannot be blank.' => '{attribute} ne smije biti prazan.', + '{attribute} is invalid.' => 'Atribut "{attribute}" je neispravan.', + '{attribute} is not a valid URL.' => '{attribute} nije valjan URL.', + '{attribute} is not a valid email address.' => '{attribute} nije valjana email adresa.', + '{attribute} must be "{requiredValue}".' => '{attribute} mora biti "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} mora biti broj.', + '{attribute} must be a string.' => '{attribute} mora biti string(riječ,tekst).', + '{attribute} must be an integer.' => '{attribute} mora biti cijeli broj.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} mora biti "{true}" ili "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} mora biti veći od "{compareValue}', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} mora biti veći ili jednak "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} mora biti manji od "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} mora biti jednak "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} ne smije biti veći od {max}.', + '{attribute} must be no less than {min}.' => '{attribute} ne smije biti manji od {min}.', + '{attribute} must be repeated exactly.' => '{attribute} mora biti točno ponovljeno.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ne smije biti jednak "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} mora najmanje sadržavati {min, number} {min, plural, =1{znak} one{# znak} few{# znaka} many{# znakova} other{# znakova}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} moze sadržavati najviše do {max, number} {max, plural, =1{znak} one{# znak} few{# znaka} many{# znakova} other{# znakova}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} mora sadržavati {length, number} {length, plural, =1{znak} one{# znak} few{# znaka} many{# znakova} other{# znakova}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{dan} one{# dan} few{# dana} many{# dana} other{# dana}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minuta} one{# minuta} few{# minute} many{# minuta} other{# minuta}}', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{mjesec} one{# mjesec} few{# mjeseca} many{# mjeseci} other{# mjeseci}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{sekunda} one{# sekunda} few{# sekunde} many{# sekundi} other{# sekundi}}', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{godina} one{# godine} few{# godine} many{# godina} other{# godina}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => ' {delta, plural, =1{sat} one{# sat} few{# sata} many{# sati} other{# sati}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# bajt} other{# bajta}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabajt} other{# gigabajta}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobajt} other{# kilobajta}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabajt} other{# megabajta}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabajt} other{# petabajta}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabajt} other{# terabajta}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/framework/messages/hu/yii.php b/framework/messages/hu/yii.php index b5dcb7863f..1877a4a642 100644 --- a/framework/messages/hu/yii.php +++ b/framework/messages/hu/yii.php @@ -39,8 +39,8 @@ return array ( 'Please upload a file.' => 'Kérjük töltsön fel egy fájlt.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{begin, number}-{end, number} megjelenítése a(z) {totalCount, number} elemből.', 'The file "{file}" is not an image.' => '"{file}" nem egy kép.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" túl nagy. A mérete nem lehet nagyobb {limit, number} bájtnál.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" túl kicsi. A mérete nem lehet kisebb {limit, number} bájtnál.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => '"{file}" túl nagy. A mérete nem lehet nagyobb {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => '"{file}" túl kicsi. A mérete nem lehet kisebb {formattedLimit}.', 'The format of {attribute} is invalid.' => 'A(z) {attribute} formátuma érvénytelen.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl nagy. A magassága nem lehet nagyobb {limit, number} pixelnél.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl nagy. A szélessége nem lehet nagyobb {limit, number} pixelnél.', @@ -82,6 +82,7 @@ return array ( '{attribute} must be no less than {min}.' => '{attribute} nem lehet kisebb, mint {min}.', '{attribute} must be repeated exactly.' => 'Ismételje meg pontosan a(z) {attribute} mezőbe írtakat.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} nem lehet egyenlő ezzel: "{compareValue}".', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '"{attribute}" mezőnek azonosnak kell lennie a "{compareValueOrAttribute}" mezőbe írtakkal.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} minimum {min, number} karakter kell, hogy legyen.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} maximum {max, number} karakter lehet.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} pontosan {length, number} kell, hogy legyen.', diff --git a/framework/messages/id/yii.php b/framework/messages/id/yii.php index 21c7b5c79c..0d0d3d477a 100644 --- a/framework/messages/id/yii.php +++ b/framework/messages/id/yii.php @@ -18,22 +18,22 @@ */ return [ 'The requested view "{name}" was not found.' => 'View "{name}" yang diminta tidak ditemukan.', - 'You are requesting with an invalid access token.' => 'Anda melakukan permintaan dengan akses token yang invalid.', + 'You are requesting with an invalid access token.' => 'Anda melakukan permintaan dengan akses token yang tidak valid.', '(not set)' => '(belum diset)', 'An internal server error occurred.' => 'Terjadi kesalahan internal server.', - 'Are you sure you want to delete this item?' => 'Apakah Anda yakin ingin menghapus ini?', + 'Are you sure you want to delete this item?' => 'Apakah Anda yakin ingin menghapus item ini?', 'Delete' => 'Hapus', 'Error' => 'Kesalahan', 'File upload failed.' => 'Mengunggah berkas gagal.', 'Home' => 'Beranda', - 'Invalid data received for parameter "{param}".' => 'Data yang diterima invalid untuk parameter "{param}"', + 'Invalid data received for parameter "{param}".' => 'Data yang diterima tidak valid untuk parameter "{param}"', 'Login Required' => 'Diperlukan login', 'Missing required arguments: {params}' => 'Argumen yang diperlukan tidak ada: {params}', 'Missing required parameters: {params}' => 'Parameter yang diperlukan tidak ada: {params}', 'No' => 'Tidak', 'No help for unknown command "{command}".' => 'Tidak ada bantuan untuk perintah yang tidak diketahui "{command}".', 'No help for unknown sub-command "{command}".' => 'Tidak ada bantuan untuk sub perintah yang tidak diketahui "{command}".', - 'No results found.' => 'Tidak ada hasil yang ditemukan.', + 'No results found.' => 'Tidak ada data yang ditemukan.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Hanya berkas dengan tipe MIME ini yang diperbolehkan: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Hanya berkas dengan ekstensi ini yang diperbolehkan: {extensions}.', 'Page not found.' => 'Halaman tidak ditemukan.', @@ -41,8 +41,8 @@ return [ 'Please upload a file.' => 'Silahkan mengunggah berkas.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Menampilkan {begin, number}-{end, number} dari {totalCount, number} {totalCount, plural, one{item} other{item}}.', 'The file "{file}" is not an image.' => 'File bukan berupa gambar.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Berkas "{file}" terlalu besar. Ukurannya tidak boleh lebih besar dari {limit, number} {limit, plural, one{bita} other{bita}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Berkas "{file}" terlalu kecil. Ukurannya tidak boleh lebih kecil dari {limit, number} {limit, plural, one{bita} other{bita}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Berkas "{file}" terlalu besar. Ukurannya tidak boleh lebih besar dari {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Berkas "{file}" terlalu kecil. Ukurannya tidak boleh lebih kecil dari {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Format dari {attribute} tidak valid.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu besar. Tingginya tidak boleh lebih besar dari {limit, number} {limit, plural, one{piksel} other{piksel}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu besar. Lebarnya tidak boleh lebih besar dari {limit, number} {limit, plural, one{piksel} other{piksel}}.', @@ -50,7 +50,7 @@ return [ 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu kecil. Lebarnya tidak boleh lebih kecil dari {limit, number} {limit, plural, one{piksel} other{piksel}}.', 'The verification code is incorrect.' => 'Kode verifikasi tidak benar.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{item} other{item}}.', - 'Unable to verify your data submission.' => 'Tidak dapat memverifikasi pengiriman data Anda.', + 'Unable to verify your data submission.' => 'Tidak dapat mem-verifikasi pengiriman data Anda.', 'Unknown command "{command}".' => 'Perintah tidak dikenal "{command}".', 'Unknown option: --{name}' => 'Opsi tidak dikenal: --{name}', 'Update' => 'Ubah', diff --git a/framework/messages/it/yii.php b/framework/messages/it/yii.php index 07bdf58266..524a539caa 100644 --- a/framework/messages/it/yii.php +++ b/framework/messages/it/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,8 +17,69 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + 'Unknown alias: -{name}' => 'Alias sconosciuto: -{name}', + '{attribute} contains wrong subnet mask.' => '{attribute} contiene una subnet mask errata.', + '{attribute} is not in the allowed range.' => '{attribute} non rientra nell\'intervallo permesso', + '{attribute} must be a valid IP address.' => '{attribute} deve essere un indirizzo IP valido.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} deve essere un indirizzo IP valido con subnet specificata.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} deve essere uguale a "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} deve essere maggiore di "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} deve essere maggiore o uguale a "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} deve essere minore di "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} deve essere minore o uguale a "{compareValueOrAttribute}".', + '{attribute} must not be a subnet.' => '{attribute} non deve essere una subnet.', + '{attribute} must not be an IPv4 address.' => '{attribute} non deve essere un indirizzo IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} non deve essere un indirizzo IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} non deve essere uguale a "{compareValueOrAttribute}".', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 giorno} other{# giorni}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 ora} other{# ore}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuto} other{# minuti}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 mese} other{# mesi}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 secondo} other{# secondi}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 anno} other{# anni}}', + '{attribute} must be greater than "{compareValue}".' => '@@{attribute} deve essere maggiore di "{compareValue}".@@', + '{attribute} must be greater than or equal to "{compareValue}".' => '@@{attribute} deve essere maggiore o uguale a "{compareValue}".@@', + '{attribute} must be less than "{compareValue}".' => '@@{attribute} deve essere minore di "{compareValue}".@@', + '{attribute} must be less than or equal to "{compareValue}".' => '@@{attribute} deve essere minore o uguale a "{compareValue}".@@', + '{attribute} must be repeated exactly.' => '@@{attribute} deve essere ripetuto esattamente.@@', + '{attribute} must not be equal to "{compareValue}".' => '@@{attribute} non deve essere uguale a "{compareValue}".@@', + '(not set)' => '(nessun valore)', + 'An internal server error occurred.' => 'Si è verificato un errore interno', + 'Are you sure you want to delete this item?' => 'Sei sicuro di voler eliminare questo elemento?', + 'Delete' => 'Elimina', + 'Error' => 'Errore', + 'File upload failed.' => 'Upload file fallito.', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Dati ricevuti non corretti per il parametro "{param}".', + 'Login Required' => 'Login richiesto', + 'Missing required arguments: {params}' => 'Il seguente argomento è mancante: {params}', + 'Missing required parameters: {params}' => 'Il seguente parametro è mancante: {params}', + 'No' => 'No', + 'No results found.' => 'Nessun risultato trovato', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Solo i file con questi tipi MIME sono consentiti: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Solo i file con queste estensioni sono permessi: {extensions}.', + 'Page not found.' => 'Pagina non trovata.', + 'Please fix the following errors:' => 'Per favore correggi i seguenti errori:', + 'Please upload a file.' => 'Per favore carica un file.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Visualizzo {begin, number}-{end, number} di {totalCount, number} {totalCount, plural, one{elemento} other{elementi}}.', + 'The file "{file}" is not an image.' => 'Il file "{file}" non è una immagine.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Il file "{file}" è troppo grande. La dimensione non può superare i {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Il file "{file}" è troppo piccolo. La dimensione non può essere inferiore a {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Il formato di {attribute} non è valido.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'immagine "{file}" è troppo grande. La sua altezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo grande. La sua larghezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua altezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua larghezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', 'The requested view "{name}" was not found.' => 'La vista "{name}" richiesta non è stata trovata.', + 'The verification code is incorrect.' => 'Il codice di verifica non è corretto.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '{count, plural, one{Elementi} other{Elementi}} totali {count, number}.', + 'Unable to verify your data submission.' => 'Impossibile verificare i dati inviati.', + 'Unknown option: --{name}' => 'Opzione Sconosciuta: --{name}', + 'Update' => 'Aggiorna', + 'View' => 'Visualizza', + 'Yes' => 'Si', + 'You are not allowed to perform this action.' => 'Non sei autorizzato ad eseguire questa operazione.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puoi caricare al massimo {limit, number} {limit, plural, one{file} other{file}}.', 'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{un giorno} other{# giorni}}', 'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{un minuto} other{# minuti}}', 'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{un mese} other{# mesi}}', @@ -26,6 +87,22 @@ return [ 'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{un anno} other{# anni}}', 'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{un\'ora} other{# ore}}', 'just now' => 'proprio ora', + 'the input value' => 'il valore del campo', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" è già presente.', + '{attribute} cannot be blank.' => '{attribute} non può essere vuoto.', + '{attribute} is invalid.' => '{attribute} non è valido.', + '{attribute} is not a valid URL.' => '{attribute} non è un URL valido.', + '{attribute} is not a valid email address.' => '{attribute} non è un indirizzo email valido.', + '{attribute} must be "{requiredValue}".' => '{attribute} deve essere "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} deve essere un numero.', + '{attribute} must be a string.' => '{attribute} deve essere una stringa.', + '{attribute} must be an integer.' => '{attribute} deve essere un numero intero.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} deve essere "{true}" oppure "{false}".', + '{attribute} must be no greater than {max}.' => '{attribute} non deve essere maggiore di {max}.', + '{attribute} must be no less than {min}.' => '{attribute} non deve essere minore di {min}.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere almeno {min, number} {min, plural, one{carattere} other{caratteri}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere al massimo {max, number} {max, plural, one{carattere} other{caratteri}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere {length, number} {length, plural, one{carattere} other{caratteri}}.', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{un giorno} other{# giorni}} fa', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{un minuto} other{# minuti}} fa', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{un mese} other{# mesi}} fa', @@ -54,61 +131,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabyte}}', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibyte}}', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabyte}}', - '(not set)' => '(nessun valore)', - 'An internal server error occurred.' => 'Si è verificato un errore interno', - 'Are you sure you want to delete this item?' => 'Sei sicuro di voler eliminare questo elemento?', - 'Delete' => 'Elimina', - 'Error' => 'Errore', - 'File upload failed.' => 'Upload file fallito.', - 'Home' => 'Home', - 'Invalid data received for parameter "{param}".' => 'Dati ricevuti non corretti per il parametro "{param}".', - 'Login Required' => 'Login richiesto', - 'Missing required arguments: {params}' => 'Il seguente argomento è mancante: {params}', - 'Missing required parameters: {params}' => 'Il seguente parametro è mancante: {params}', - 'No' => 'No', - 'No results found.' => 'Nessun risultato trovato', - 'Only files with these extensions are allowed: {extensions}.' => 'Solo i file con queste estensioni sono permessi: {extensions}.', - 'Page not found.' => 'Pagina non trovata.', - 'Please fix the following errors:' => 'Per favore correggi i seguenti errori:', - 'Please upload a file.' => 'Per favore carica un file.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Visualizzo {begin, number}-{end, number} di {totalCount, number} {totalCount, plural, one{elemento} other{elementi}}.', - 'The file "{file}" is not an image.' => 'Il file "{file}" non è una immagine.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Il file "{file}" è troppo grande. La dimensione non può superare i {limit, number} {limit, plural, one{byte} other{byte}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Il file "{file}" è troppo piccolo. La dimensione non può essere inferiore a {limit, number} {limit, plural, one{byte} other{byte}}.', - 'The format of {attribute} is invalid.' => 'Il formato di {attribute} non è valido.', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'immagine "{file}" è troppo grande. La sua altezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo grande. La sua larghezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua altezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua larghezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', - 'The verification code is incorrect.' => 'Il codice di verifica non è corretto.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => '{count, plural, one{Elementi} other{Elementi}} totali {count, number}.', - 'Unable to verify your data submission.' => 'Impossibile verificare i dati inviati.', - 'Unknown option: --{name}' => 'Opzione Sconosciuta: --{name}', - 'Update' => 'Aggiorna', - 'View' => 'Visualizza', - 'Yes' => 'Si', - 'You are not allowed to perform this action.' => 'Non sei autorizzato ad eseguire questa operazione.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puoi caricare al massimo {limit, number} {limit, plural, one{file} other{file}}.', - 'the input value' => 'il valore del campo', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" è già presente.', - '{attribute} cannot be blank.' => '{attribute} non può essere vuoto.', - '{attribute} is invalid.' => '{attribute} non è valido.', - '{attribute} is not a valid URL.' => '{attribute} non è un URL valido.', - '{attribute} is not a valid email address.' => '{attribute} non è un indirizzo email valido.', - '{attribute} must be "{requiredValue}".' => '{attribute} deve essere "{requiredValue}".', - '{attribute} must be a number.' => '{attribute} deve essere un numero.', - '{attribute} must be a string.' => '{attribute} deve essere una stringa.', - '{attribute} must be an integer.' => '{attribute} deve essere un numero intero.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} deve essere "{true}" oppure "{false}".', - '{attribute} must be greater than "{compareValue}".' => '{attribute} deve essere maggiore di "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} deve essere maggiore o uguale a "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} deve essere minore di "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} deve essere minore o uguale a "{compareValue}".', - '{attribute} must be no greater than {max}.' => '{attribute} non deve essere maggiore di {max}.', - '{attribute} must be no less than {min}.' => '{attribute} non deve essere minore di {min}.', - '{attribute} must be repeated exactly.' => '{attribute} deve essere ripetuto esattamente.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} non deve essere uguale a "{compareValue}".', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere almeno {min, number} {min, plural, one{carattere} other{caratteri}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere al massimo {max, number} {max, plural, one{carattere} other{caratteri}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere {length, number} {length, plural, one{carattere} other{caratteri}}.', ]; diff --git a/framework/messages/ja/yii.php b/framework/messages/ja/yii.php index 1cc32d112c..5551c9015b 100644 --- a/framework/messages/ja/yii.php +++ b/framework/messages/ja/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,9 +17,43 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'Are you sure you want to delete this item?' => 'このアイテムを削除しても本当にかまいませんか?', + '(not set)' => '(未設定)', + 'An internal server error occurred.' => '内部サーバーエラーが発生しました。', + 'Are you sure you want to delete this item?' => 'このアイテムを削除したいというのは本当ですか?', + 'Delete' => '削除', + 'Error' => 'エラー', + 'File upload failed.' => 'ファイルアップロードに失敗しました。', + 'Home' => 'ホーム', + 'Invalid data received for parameter "{param}".' => 'パラメータ "{param}" に不正なデータを受け取りました。', + 'Login Required' => 'ログインが必要です', + 'Missing required arguments: {params}' => '必要な引数がありません: {params}', + 'Missing required parameters: {params}' => '必要なパラメータがありません: {params}', + 'No' => 'いいえ', + 'No results found.' => '結果が得られませんでした。', 'Only files with these MIME types are allowed: {mimeTypes}.' => '以下の MIME タイプのファイルだけが許可されています: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => '次の拡張子を持つファイルだけが許可されています : {extensions}', + 'Page not found.' => 'ページが見つかりません。', + 'Please fix the following errors:' => '次のエラーを修正してください :', + 'Please upload a file.' => 'ファイルをアップロードしてください。', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, number} 件中 {begin, number} から {end, number} までを表示しています。', + 'The file "{file}" is not an image.' => 'ファイル "{file}" は画像ではありません。', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'ファイル "{file}" は大きすぎます。サイズが {formattedLimit} を超えてはいけません。', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'ファイル "{file}" は小さすぎます。サイズが {formattedLimit} より小さくてはいけません。', + 'The format of {attribute} is invalid.' => '{attribute} の書式が正しくありません。', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。高さが {limit} ピクセルより大きくてはいけません。', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。幅が {limit} ピクセルより大きくてはいけません。', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。高さが {limit} ピクセルより小さくてはいけません。', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。幅が {limit} ピクセルより小さくてはいけません。', 'The requested view "{name}" was not found.' => 'リクエストされたビュー "{name}" が見つかりませんでした。', + 'The verification code is incorrect.' => '検証コードが正しくありません。', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '合計 {count} 件。', + 'Unable to verify your data submission.' => 'データ送信を検証できませんでした。', + 'Unknown option: --{name}' => '不明なオプション: --{name}', + 'Update' => '更新', + 'View' => '閲覧', + 'Yes' => 'はい', + 'You are not allowed to perform this action.' => 'このアクションの実行は許可されていません。', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '最大で {limit, number} 個のファイルをアップロードできます。', 'in {delta, plural, =1{a day} other{# days}}' => '{delta} 日後', 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} 分後', 'in {delta, plural, =1{a month} other{# months}}' => '{delta} ヶ月後', @@ -27,6 +61,41 @@ return [ 'in {delta, plural, =1{a year} other{# years}}' => '{delta} 年後', 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} 時間後', 'just now' => '現在', + 'the input value' => '入力値', + '{attribute} "{value}" has already been taken.' => '{attribute} で "{value}" は既に使われています。', + '{attribute} cannot be blank.' => '{attribute} は空白ではいけません。', + '{attribute} contains wrong subnet mask.' => '{attribute} は無効なサブネットマスクを含んでいます。', + '{attribute} is invalid.' => '{attribute} は無効です。', + '{attribute} is not a valid URL.' => '{attribute} は有効な URL 書式ではありません。', + '{attribute} is not a valid email address.' => '{attribute} は有効なメールアドレス書式ではありません。', + '{attribute} is not in the allowed range.' => '{attribute} は許容される範囲内にありません。', + '{attribute} must be "{requiredValue}".' => '{attribute} は "{requiredValue}" である必要があります。', + '{attribute} must be a number.' => '{attribute} は数字でなければいけません。', + '{attribute} must be a string.' => '{attribute} は文字列でなければいけません。', + '{attribute} must be a valid IP address.' => '{attribute} は有効な IP アドレスでなければいけません。', + '{attribute} must be an IP address with specified subnet.' => '{attribute} は指定されたサブネットを持つ IP アドレスでなければいけません。', + '{attribute} must be an integer.' => '{attribute} は整数でなければいけません。', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} は "{true}" か "{false}" のいずれかでなければいけません。', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" と等しくなければいけません。', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" より大きくなければいけません。', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" と等しいか、または、大きくなければいけません。', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" より小さくなければいけません。', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" と等しいか、または、小さくなければいけません。', + '{attribute} must be no greater than {max}.' => '{attribute} は {max} より大きくてはいけません。', + '{attribute} must be no less than {min}.' => '{attribute} は {min} より小さくてはいけません。', + '{attribute} must not be a subnet.' => '{attribute} はサブネットであってはいけません。', + '{attribute} must not be an IPv4 address.' => '{attribute} は IPv4 アドレスであってはいけません。', + '{attribute} must not be an IPv6 address.' => '{attribute} は IPv6 アドレスであってはいけません。', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} は "{compareValueOrAttribute}" と等しくてはいけません。', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} は {min} 文字以上でなければいけません。', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} は {max} 文字以下でなければいけません。', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} は {length} 文字でなければいけません。', + '{delta, plural, =1{1 day} other{# days}}' => '{delta} 日間', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta} 時間', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta} 分間', + '{delta, plural, =1{1 month} other{# months}}' => '{delta} ヶ月間', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta} 秒間', + '{delta, plural, =1{1 year} other{# years}}' => '{delta} 年間', '{delta, plural, =1{a day} other{# days}} ago' => '{delta} 日前', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} 分前', '{delta, plural, =1{a month} other{# months}} ago' => '{delta} ヶ月前', @@ -55,60 +124,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} ペタバイト', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} テビバイト', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} テラバイト', - '(not set)' => '(セットされていません)', - 'An internal server error occurred.' => 'サーバー内部エラーが発生しました。', - 'Delete' => '削除', - 'Error' => 'エラー', - 'File upload failed.' => 'ファイルアップロードに失敗しました。', - 'Home' => 'ホーム', - 'Invalid data received for parameter "{param}".' => 'パラメータ"{param}"に不正なデータを受け取りました。', - 'Login Required' => 'ログインが必要です', - 'Missing required arguments: {params}' => '必要な引数がありません: {params}', - 'Missing required parameters: {params}' => '必要なパラメータがありません: {params}', - 'No' => 'いいえ', - 'No results found.' => '結果が得られませんでした。', - 'Only files with these extensions are allowed: {extensions}.' => '次の拡張子を持つファイルだけが許可されています : {extensions}', - 'Page not found.' => 'ページが見つかりません。', - 'Please fix the following errors:' => '次のエラーを修正してください :', - 'Please upload a file.' => 'ファイルをアップロードしてください。', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount} 件中 {begin} から {end} までを表示しています。', - 'The file "{file}" is not an image.' => 'ファイル "{file}" は画像ではありません。', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ファイル "{file}" が大きすぎます。サイズは {limit} バイトを超えることができません。', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ファイル "{file}" が小さすぎます。サイズは {limit} バイトを下回ることができません。', - 'The format of {attribute} is invalid.' => '{attribute}の書式が正しくありません。', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。高さが {limit} より大きくてはいけません。', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。幅が {limit} より大きくてはいけません。', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。高さが {limit} より小さくてはいけません。', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。幅が {limit} より小さくてはいけません。', - 'The verification code is incorrect.' => '検証コードが正しくありません。', - 'Total {count, number} {count, plural, one{item} other{items}}.' => '合計 {count} 件。', - 'Unable to verify your data submission.' => 'データ送信を検証できませんでした。', - 'Unknown option: --{name}' => '不明なオプション: --{name}', - 'Update' => '更新', - 'View' => '閲覧', - 'Yes' => 'はい', - 'You are not allowed to perform this action.' => 'このアクションの実行は許可されていません。', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '最大で {limit} 個のファイルをアップロードできます。', - 'the input value' => '入力値', - '{attribute} "{value}" has already been taken.' => '{attribute}で "{value}" は既に使われています。', - '{attribute} cannot be blank.' => '{attribute}は空白ではいけません。', - '{attribute} is invalid.' => '{attribute}は無効です。', - '{attribute} is not a valid URL.' => '{attribute}は有効な URL 書式ではありません。', - '{attribute} is not a valid email address.' => '{attribute}は有効なメールアドレス書式ではありません。', - '{attribute} must be "{requiredValue}".' => '{attribute}は{value}である必要があります。', - '{attribute} must be a number.' => '{attribute}は数字にしてください。', - '{attribute} must be a string.' => '{attribute}は文字列にしてください。', - '{attribute} must be an integer.' => '{attribute}は整数にしてください。', - '{attribute} must be either "{true}" or "{false}".' => '{attribute}は{true}か{false}のいずれかである必要があります。', - '{attribute} must be greater than "{compareValue}".' => '{attribute}は"{compareValue}"より大きい必要があります。', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}は"{compareValue}"以上である必要があります。', - '{attribute} must be less than "{compareValue}".' => '{attribute}は"{compareValue}"より小さい必要があります。', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}は"{compareValue}"以下である必要があります。', - '{attribute} must be no greater than {max}.' => '{attribute}は"{max}"より大きくてはいけません。', - '{attribute} must be no less than {min}.' => '{attribute}は"{min}"より小さくてはいけません。', - '{attribute} must be repeated exactly.' => '{attribute}は正確に繰り返してください。', - '{attribute} must not be equal to "{compareValue}".' => '{attribute}は"{compareValue}"ではいけません。', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}は{min}文字以上でなければなりません。', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}は{max}文字以下でなければなりません。', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}は{length}文字でなければなりません。', ]; diff --git a/framework/messages/ka/yii.php b/framework/messages/ka/yii.php new file mode 100644 index 0000000000..cdc16acc39 --- /dev/null +++ b/framework/messages/ka/yii.php @@ -0,0 +1,114 @@ + '(არ არის მითითებული)', + 'An internal server error occurred.' => 'წარმოიშვა სერვერის შიდა შეცდომა.', + 'Are you sure you want to delete this item?' => 'დარწმუნებული ხართ, რომ გინდათ ამ ელემენტის წაშლა?', + 'Delete' => 'წაშლა', + 'Error' => 'შეცდომა', + 'File upload failed.' => 'ფაილის ჩამოტვირთვა ვერ მოხერხდა.', + 'Home' => 'მთავარი', + 'Invalid data received for parameter "{param}".' => 'პარამეტრის "{param}" არასწორი მნიშვნელობა.', + 'Login Required' => 'საჭიროა შესვლა.', + 'Missing required arguments: {params}' => 'არ არსებობენ აუცილებელი პარამეტრები: {params}', + 'Missing required parameters: {params}' => 'არ არსებობენ აუცილებელი პარამეტრები: {params}', + 'No' => 'არა', + 'No results found.' => 'არაფერი მოიძებნა.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'ნებადართულია ფაილების ჩამოტვირთვა მხოლოდ შემდეგი MIME-ტიპებით: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'ნებადართულია ფაილების ჩამოტვირთვა მხოლოდ შემდეგი გაფართოებებით: {extensions}.', + 'Page not found.' => 'გვერდი ვერ მოიძებნა.', + 'Please fix the following errors:' => 'შეასწორეთ შემდეგი შეცდომები:', + 'Please upload a file.' => 'ჩამოტვირთეთ ფაილი.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'ნაჩვენებია ჩანაწერები {begin, number}-{end, number} из {totalCount, number}.', + 'The file "{file}" is not an image.' => 'ფაილი «{file}» არ წარმოადგენს გამოსახულებას.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'ფაილი «{file}» ძალიან დიდია. ზომა არ უნდა აღემატებოდეს {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'ფაილი «{file}» ძალიან პატარაა. ზომა უნდა აღემატებოდეს {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'მნიშვნელობის «{attribute}» არასწორი ფორმატი.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'ფაილი «{file}» ძალიან დიდია. სიმაღლე არ უნდა აღემატებოდეს {limit, number} {limit, plural, one{პიქსელს} few{პიქსელს} many{პიქსელს} other{პიქსელს}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'ფაილი «{file}» ძალიან დიდია. სიგანე არ უნდა აღემატებოდეს {limit, number} {limit, plural, one{პიქსელს} few{პიქსელს} many{პიქსელს} other{პიქსელს}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'ფაილი «{file}» ძალიან პატარაა. სიმაღლე უნდა აღემატებოდეს {limit, number} {limit, plural, one{პიქსელს} few{პიქსელს} many{პიქსელს} other{პიქსელს}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'ფაილი «{file}» ძალიან პატარაა. სიგანე უნდა აღემატებოდეს {limit, number} {limit, plural, one{პიქსელს} few{პიქსელს} many{პიქსელს} other{პიქსელს}}.', + 'The requested view "{name}" was not found.' => 'მოთხოვნილი წარმოდგენის "{name}" ფაილი ვერ მოიძებნა.', + 'The verification code is incorrect.' => 'არასწორი შემამოწმებელი კოდი.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'სულ {count, number} {count, plural, one{ჩანაწერი} few{ჩანაწერი} many{ჩანაწერი}} other{ჩანაწერი}}.', + 'Unable to verify your data submission.' => 'ვერ მოხერხდა გადაცემული მონაცემების შემოწმება.', + 'Unknown option: --{name}' => 'უცნობი ოფცია: --{name}', + 'Update' => 'რედაქტირება', + 'View' => 'ნახვა', + 'Yes' => 'დიახ', + 'You are not allowed to perform this action.' => 'თქვენ არ გაქვთ მოცემული ქმედების შესრულების ნებართვა.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'თქვენ არ შეგიძლიათ ჩამოტვირთოთ {limit, number}-ზე მეტი {limit, plural, one{ფაილი} few{ფაილი} many{ფაილი} other{ფაილი}}.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta, plural, =1{დღის} one{# დღის} few{# დღის} many{# დღის} other{# დღის}} შემდეგ', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta, plural, =1{წუთის} one{# წუთის} few{# წუთის} many{# წუთის} other{# წუთის}} შემდეგ', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta, plural, =1{თვის} one{# თვის} few{# თვის} many{# თვის} other{# თვის}} შემდეგ', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta, plural, =1{წამის} one{# წამის} few{# წამის} many{# წამის} other{# წამის}} შემდეგ', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta, plural, =1{წლის} one{# წლის} few{# წლის} many{# წლის} other{# წლის}} შემდეგ', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta, plural, =1{საათის} one{# საათის} few{# საათის} many{# საათის} other{# საათის}} შემდეგ', + 'just now' => 'ახლავე', + 'the input value' => 'შეყვანილი მნიშვნელობა', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» უკვე დაკავებულია.', + '{attribute} cannot be blank.' => 'საჭიროა შევსება «{attribute}».', + '{attribute} is invalid.' => 'მნიშვნელობა «{attribute}» არასწორია.', + '{attribute} is not a valid URL.' => 'მნიშვნელობა «{attribute}» არ წარმოადგენს სწორ URL-ს.', + '{attribute} is not a valid email address.' => 'მნიშვნელობა «{attribute}» არ წარმოადგენს სწორ email მისამართს.', + '{attribute} must be "{requiredValue}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{requiredValue}-ს ტოლი».', + '{attribute} must be a number.' => 'მნიშვნელობა «{attribute}» უნდა იყოს რიცხვი.', + '{attribute} must be a string.' => 'მნიშვნელობა «{attribute}» უნდა იყოს სტრიქონი.', + '{attribute} must be an integer.' => 'მნიშვნელობა «{attribute}» უნდა იყოს მთელი რიცხვი.', + '{attribute} must be either "{true}" or "{false}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{true}»-ს ან «{false}»-ს ტოლი.', + '{attribute} must be greater than "{compareValue}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{compareValue}» მნიშვნელობაზე მეტი.', + '{attribute} must be greater than or equal to "{compareValue}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{compareValue}» მნიშვნელობაზე მეტი ან ტოლი.', + '{attribute} must be less than "{compareValue}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{compareValue}» მნიშვნელობაზე ნაკლები.', + '{attribute} must be less than or equal to "{compareValue}".' => 'მნიშვნელობა «{attribute}» უნდა იყოს «{compareValue}» მნიშვნელობაზე ნაკლები ან ტოლი.', + '{attribute} must be no greater than {max}.' => 'მნიშვნელობა «{attribute}» არ უნდა აღემატებოდეს {max}.', + '{attribute} must be no less than {min}.' => 'მნიშვნელობა «{attribute}» უნდა იყოს არანაკლები {min}.', + '{attribute} must be repeated exactly.' => 'მნიშვნელობა «{attribute}» უნდა განმეორდეს ზუსტად.', + '{attribute} must not be equal to "{compareValue}".' => 'მნიშვნელობა «{attribute}» არ უნდა იყოს «{compareValue}»-ს ტოლი.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'მნიშვნელობა «{attribute}» უნდა შეიცავდეს მინიმუმ {min, number} {min, plural, one{სიმბოლს} few{სიმბოლს} many{სიმბოლს} other{სიმბოლს}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'მნიშვნელობა «{attribute}» უნდა შეიცავდეს მაქსიმუმ {max, number} {max, plural, one{სიმბოლს} few{სიმბოლს} many{სიმბოლს} other{სიმბოლს}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'მნიშვნელობა «{attribute}» უნდა შეიცავდეს {length, number} {length, plural, one{სიმბოლს} few{სიმბოლს} many{სიმბოლს} other{სიმბოლს}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{დღის} one{# დღის} few{# დღის} many{# დღის} other{# დღის}} უკან', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{წუთის} one{# წუთის} few{# წუთის} many{# წუთის} other{# წუთის}} უკან', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{თვის} one{# თვის} few{# თვის} many{# თვის} other{# თვის}} უკან', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{წამის} one{# წამის} few{# წამის} many{# წამის} other{# წამის}} უკან', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{წლის} one{# წლის} few{# წლის} many{# წლის} other{# წლის}} უკან', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{საათის} one{# საათის} few{# საათის} many{# საათის} other{# საათის}} უკან', + '{nFormatted} B' => '{nFormatted} ბ', + '{nFormatted} GB' => '{nFormatted} გბ', + '{nFormatted} GiB' => '{nFormatted} გიბ', + '{nFormatted} KB' => '{nFormatted} კბ', + '{nFormatted} KiB' => '{nFormatted} კიბ', + '{nFormatted} MB' => '{nFormatted} მბ', + '{nFormatted} MiB' => '{nFormatted} მიბ', + '{nFormatted} PB' => '{nFormatted} პბ', + '{nFormatted} PiB' => '{nFormatted} პიბ', + '{nFormatted} TB' => '{nFormatted} ტბ', + '{nFormatted} TiB' => '{nFormatted} ტიბ', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{ბაიტი} few{ბაიტს} many{ბაიტი} other{ბაიტს}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{გიბიბაიტი} few{გიბიბაიტს} many{გიბიბაიტი} other{გიბიბაიტს}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{გიგაბაიტი} few{გიგაბაიტს} many{გიგაბაიტი} other{გიგაბაიტს}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{კიბიბაიტი} few{კიბიბაიტს} many{კიბიბაიტი} other{კიბიბაიტს}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{კილობაიტი} few{კილობაიტს} many{კილობაიტი} other{კილობაიტს}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, one{მებიბაიტი} few{მებიბაიტს} many{მებიბაიტი} other{მებიბაიტს}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, one{მეგაბაიტი} few{მეგაბაიტს} many{მეგაბაიტი} other{მეგაბაიტს}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, one{პებიბაიტი} few{პებიბაიტს} many{პებიბაიტი} other{პებიბაიტს}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{პეტაბაიტი} few{პეტაბაიტს} many{პეტაბაიტი} other{პეტაბაიტს}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{ტებიბაიტი} few{ტებიბაიტს} many{ტებიბაიტი} other{ტებიბაიტს}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{ტერაბაიტი} few{ტერაბაიტს} many{ტერაბაიტი} other{ტერაბაიტს}}', +]; diff --git a/framework/messages/kk/yii.php b/framework/messages/kk/yii.php index d865224ac9..acfb1a820c 100644 --- a/framework/messages/kk/yii.php +++ b/framework/messages/kk/yii.php @@ -17,63 +17,64 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - '(not set)' => '(тапсырылған жок)', + '(not set)' => '(берілмеген)', 'An internal server error occurred.' => 'Сервердің ішкі қатесі туды.', + 'Are you sure you want to delete this item?' => 'Бұл элементті жоюға сенімдісіз бе?', 'Delete' => 'Жою', 'Error' => 'Қате', - 'File upload failed.' => 'Файлды жүктеу сәті болмады', + 'File upload failed.' => 'Файлды жүктеу сәтті болмады', 'Home' => 'Басы', - 'Invalid data received for parameter "{param}".' => 'Параметрдің мағынасы дұрыс емес"{param}".', + 'Invalid data received for parameter "{param}".' => '"{param}" параметріне дұрыс емес мән берілген.', 'Login Required' => 'Кіруді сұрайды.', - 'Missing required arguments: {params}' => 'Қажетті дәлелдер жоқ: {params}', + 'Missing required arguments: {params}' => 'Қажетті аргументтер жоқ: {params}', 'Missing required parameters: {params}' => 'Қажетті параметрлер жоқ: {params}', 'No' => 'Жоқ', - 'No help for unknown command "{command}".' => 'Анықтама белгісіз команда үшін ақиық "{command}".', - 'No help for unknown sub-command "{command}".' => 'Анықтама белгісіз субкоманда үшін ақиық "{command}".', - 'No results found.' => 'Ештене табылған жок.', - 'Only files with these extensions are allowed: {extensions}.' => 'Файлды жүктеу тек қана осы аумақтармен: {extensions}.', - 'Page not found.' => 'Парақ табылған жок.', - 'Please fix the following errors:' => 'Мына қателерді түзеніз:', - 'Please upload a file.' => 'Файлды жүктеу.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Жазбалар көрсетілген {begin, number}-{end, number} дан {totalCount, number}.', - 'The file "{file}" is not an image.' => 'Файл «{file}» сурет емес.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте үлкен. Өлшемі осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте кіші. Өлшемі осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.', - 'The format of {attribute} is invalid.' => 'Форматың мағынасы дұрыс емес «{attribute}».', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ұзындығы осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ені осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ұзындығы осыдан астам болу керек,неғұрлым limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ені осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', + 'No help for unknown command "{command}".' => 'Белгісіз "{command}" командасы үшін көмек табылмады.', + 'No help for unknown sub-command "{command}".' => 'Белгісіз "{command}" ішкі командасы үшін көмек табылмады.', + 'No results found.' => 'Нәтиже табылған жок.', + 'Only files with these extensions are allowed: {extensions}.' => 'Тек мына кеңейтімдегі файлдарға ғана рұқсат етілген: {extensions}.', + 'Page not found.' => 'Бет табылған жок.', + 'Please fix the following errors:' => 'Өтінеміз, мына қателерді түзеңіз:', + 'Please upload a file.' => 'Файлды жүктеңіз.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, number} жазбадан {begin, number}-{end, number} аралығы көрсетілген.', + 'The file "{file}" is not an image.' => '«{file}» файлы сурет емес.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => '«{file}» файлының өлшемі тым үлкен. Көлемі {formattedLimit} аспау керек.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => '«{file}» файлының өлшемі тым кіші. Көлемі {formattedLimit} кем болмауы керек.', + 'The format of {attribute} is invalid.' => '«{attribute}» аттрибутының форматы дұрыс емес.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» суретінің өлшемі тым үлкен. Биіктігі {limit, number} пиксельден аспауы қажет.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» суретінің өлшемі тым үлкен. Ені {limit, number} пиксельден аспауы қажет.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» суретінің өлшемі тым кіші. Биіктігі {limit, number} пиксельден кем болмауы қажет.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» суретінің өлшемі тым кіші. Ені {limit, number} пиксельден кем болмауы қажет.', 'The verification code is incorrect.' => 'Тексеріс коды қате.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Барі {count, number} {count, plural, one{жазба} few{жазбалар} many{жазбалар} other{жазбалар}}.', - 'Unable to verify your data submission.' => 'Берілген мәліметердің тексеру сәті болмады.', - 'Unknown command "{command}".' => 'Белгісіз команда "{command}".', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Барлығы {count, number} жазба.', + 'Unable to verify your data submission.' => 'Жіберілген мәліметерді тексеру мүмкін емес.', + 'Unknown command "{command}".' => '"{command}" командасы белгісіз.', 'Unknown option: --{name}' => 'Белгісіз опция: --{name}', - 'Update' => 'Жаңалау', + 'Update' => 'Жаңарту', 'View' => 'Көру', - 'Yes' => 'Я', - 'You are not allowed to perform this action.' => 'Сізге адал әрекет жасауға болмайды', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Сіз осыдан жүктеуге астам {limit, number} {limit, plural, one{файла} few{файлдар} many{файлдар} other{файлдар}}.', - 'the input value' => 'кіргізілген мағыналар', - '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес.', - '{attribute} cannot be blank.' => 'Толтыруға қажет «{attribute}».', - '{attribute} is invalid.' => 'Мағына «{attribute}» дүрыс емес.', - '{attribute} is not a valid URL.' => 'Мағына «{attribute}» дұрыс URL емес.', - '{attribute} is not a valid email address.' => 'Мағына «{attribute}» дұрыс email адрес емес.', - '{attribute} must be "{requiredValue}".' => 'Мағына «{attribute}» тең болу керек «{requiredValue}».', - '{attribute} must be a number.' => 'Мағына «{attribute}» сан болу керек.', - '{attribute} must be a string.' => 'Мағына «{attribute}» әріп болу керек.', - '{attribute} must be an integer.' => 'Мағына «{attribute}» бүтін сан болу керек.', - '{attribute} must be either "{true}" or "{false}".' => 'Мағына «{attribute}» тең болу керек «{true}» немесе «{false}».', - '{attribute} must be greater than "{compareValue}".' => 'Мағына «{attribute}» мағынасынан үлкен болу керек «{compareValue}».', - '{attribute} must be greater than or equal to "{compareValue}".' => 'Мағына «{attribute}» үлкен болу керек немесе мағынасынан тең болу керек «{compareValue}».', - '{attribute} must be less than "{compareValue}".' => 'Мағына «{attribute}» мағынасынан кіші болу керек «{compareValue}».', - '{attribute} must be less than or equal to "{compareValue}".' => 'Мағына «{attribute}» кіші болу керек немесе мағынасынан тең болу керек «{compareValue}».', - '{attribute} must be no greater than {max}.' => 'Мағына «{attribute}» аспау керек {max}.', - '{attribute} must be no less than {min}.' => 'Мағына «{attribute}» көп болу керек {min}.', - '{attribute} must be repeated exactly.' => 'Мағына «{attribute}» дәлме-дәл қайталану керек.', - '{attribute} must not be equal to "{compareValue}".' => 'Мағына «{attribute}» тең болмау керек «{compareValue}».', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» минимум болу керек {min, number} {min, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» өте үлкен болу керек {max, number} {max, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Мағынада «{attribute}» болу керек {length, number} {length, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', + 'Yes' => 'Иә', + 'You are not allowed to perform this action.' => 'Сізге бұл әрекетті жасауға рұқсат жоқ', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Сіз {limit} файлдан көп жүктей алмайсыз.', + 'the input value' => 'енгізілген мән', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес мән.', + '{attribute} cannot be blank.' => '«{attribute}» толтыруды қажет етеді.', + '{attribute} is invalid.' => '«{attribute}» мәні жарамсыз.', + '{attribute} is not a valid URL.' => '«{attribute}» жарамды URL емес.', + '{attribute} is not a valid email address.' => '«{attribute}» жарамды email адрес емес.', + '{attribute} must be "{requiredValue}".' => '«{attribute}» мәні «{requiredValue}» болу керек.', + '{attribute} must be a number.' => '«{attribute}» мәні сан болуы керек.', + '{attribute} must be a string.' => '«{attribute}» мәні мәтін болуы керек.', + '{attribute} must be an integer.' => '«{attribute}» мәні бүтін сан болу керек.', + '{attribute} must be either "{true}" or "{false}".' => '«{attribute}» мәні «{true}» немесе «{false}» болуы керек.', + '{attribute} must be greater than "{compareValue}".' => '«{attribute}» мәні мынадан үлкен болуы керек: «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => '«{attribute}» мәні мынадан үлкен немесе тең болуы керек: «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => '«{attribute}» мәні мынадан кіші болуы керек: «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => '«{attribute}» мәні мынадан кіші немесе тең болуы керек: «{compareValue}».', + '{attribute} must be no greater than {max}.' => '«{attribute}» мәні мынадан аспауы керек: {max}.', + '{attribute} must be no less than {min}.' => '«{attribute}» мәні мынадан кем болмауы керек: {min}.', + '{attribute} must be repeated exactly.' => '«{attribute}» мәні дәлме-дәл қайталану керек.', + '{attribute} must not be equal to "{compareValue}".' => '«{attribute}» мәні мынаған тең болмауы керек: «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '«{attribute}» мәні кемінде {min} таңбадан тұруы керек.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '«{attribute}» мәні {max} таңбадан аспауы қажет.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '«{attribute}» {length} таңбадан тұруы керек.', ]; diff --git a/framework/messages/lt/yii.php b/framework/messages/lt/yii.php index 1fda404ef1..06bee3724a 100644 --- a/framework/messages/lt/yii.php +++ b/framework/messages/lt/yii.php @@ -17,6 +17,7 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ +<<<<<<< HEAD <<<<<<< HEAD '(not set)' => '(nenustatyta)', 'An internal server error occurred.' => 'Įvyko vidinė serverio klaida', @@ -105,6 +106,8 @@ return [ '{n} PB' => '{n} PB', '{n} TB' => '{n} TB', ======= +======= +>>>>>>> master 'just now' => 'dabar', '{nFormatted} B' => '{nFormatted} B', '{nFormatted} GB' => '{nFormatted} GB', @@ -148,8 +151,13 @@ return [ 'Please upload a file.' => 'Prašome įkelti failą.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Rodomi rezultatai {begin, number}-{end, number}{totalCount, number}.', 'The file "{file}" is not an image.' => 'Failas „{file}“ nėra paveikslėlis.', +<<<<<<< HEAD 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Failas „{file}“ yra per didelis. Dydis negali viršyti {limit, number} {limit, plural, one{baito} few{baitų} other{baitų}}.', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Failas „{file}“ yra per mažas. Dydis negali būti mažesnis už {limit, number} {limit, plural, one{baitą} few{baitus} other{baitų}}.', +======= + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Failas „{file}“ yra per didelis. Dydis negali viršyti {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Failas „{file}“ yra per mažas. Dydis negali būti mažesnis už {formattedLimit}.', +>>>>>>> master 'The format of {attribute} is invalid.' => 'Atributo „{attribute}“ formatas yra netinkamas.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per didelis. Aukštis negali viršyti {limit, number} {limit, plural, one{taško} few{taškų} other{taškų}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per didelis. Plotis negali viršyti {limit, number} {limit, plural, one{taško} few{taškų} other{taškų}}.', @@ -199,5 +207,8 @@ return [ '{delta, plural, =1{a second} other{# seconds}} ago' => 'prieš {delta, plural, =1{sekundę} one{# sekundę} few{# sekundes} other{# sekundžių}}', '{delta, plural, =1{a year} other{# years}} ago' => 'prieš {delta, plural, =1{metus} one{# metus} few{# metus} other{# metų}}', '{delta, plural, =1{an hour} other{# hours}} ago' => 'prieš {delta, plural, =1{valandą} one{# valandą} few{# valandas} other{# valandų}}', +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master ]; diff --git a/framework/messages/lv/yii.php b/framework/messages/lv/yii.php index fba82ef93a..f7e3060359 100644 --- a/framework/messages/lv/yii.php +++ b/framework/messages/lv/yii.php @@ -61,8 +61,8 @@ return [ 'Please upload a file.' => 'Lūdzu, augšupielādiet failu.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Tiek rādīti ieraksti {begin, number}-{end, number} no {totalCount, number}.', 'The file "{file}" is not an image.' => 'Fails „{file}” nav uzskatīts par attēlu.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fails „{file}” pārsniedz pieļaujamo ierobežojumu. Izmēram nedrīkst pārsniegt {limit, number} {limit, plural, one{baitu} other{baitus}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fails „{file}” ir pārāk mazs. Izmēram ir jābūt vairāk par {limit, number} {limit, plural, one{baitu} other{baitiem}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Fails „{file}” pārsniedz pieļaujamo ierobežojumu. Izmēram nedrīkst pārsniegt {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Fails „{file}” ir pārāk mazs. Izmēram ir jābūt vairāk par {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Vērtībai „{attribute}” ir nepareizs formāts.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk liels. Augstumam ir jābūt mazākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk liels. Platumam ir jābūt mazākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', @@ -96,14 +96,14 @@ return [ '{attribute} must be a string.' => '„{attribute}” vērtībai ir jābūt virknei.', '{attribute} must be an integer.' => '„{attribute}” vērtībai ir jābūt veselam skaitlim.', '{attribute} must be either "{true}" or "{false}".' => '„{attribute}” vērtībai ir jābūt „{true}” vai „{false}”.', - '{attribute} must be greater than "{compareValue}".' => '„{attribute}” vērtībai ir jābūt lielākai par „{compareValue}” vērību.', - '{attribute} must be greater than or equal to "{compareValue}".' => '„{attribute}” vērtībai ir jābūt lielākai vai vienādai ar „{compareValue}” vērtību.', - '{attribute} must be less than "{compareValue}".' => '„{attribute}” vērtībai ir jābūt mazākai par „{compareValue}” vērtību.', - '{attribute} must be less than or equal to "{compareValue}".' => '„{attribute}” vērtībai ir jābūt mazākai vai vienādai ar „{compareValue}” vērtību.', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '„{attribute}” vērtībai ir jābūt lielākai par „{compareValueOrAttribute}” vērtību.', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '„{attribute}” vērtībai ir jābūt lielākai vai vienādai ar „{compareValueOrAttribute}” vērtību.', + '{attribute} must be less than "{compareValueOrAttribute}".' => '„{attribute}” vērtībai ir jābūt mazākai par „{compareValueOrAttribute}” vērtību.', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '„{attribute}” vērtībai ir jābūt mazākai vai vienādai ar „{compareValueOrAttribute}” vērtību.', '{attribute} must be no greater than {max}.' => '„{attribute}” vērtībai nedrīkst pārsniegt {max}.', '{attribute} must be no less than {min}.' => '„{attribute}” vērtībai ir jāpārsniedz {min}.', - '{attribute} must be repeated exactly.' => '„{attribute}” vērtībai ir precīzi jāatkārto.', - '{attribute} must not be equal to "{compareValue}".' => '„{attribute}” vērtībai nedrīkst būt vienādai ar „{compareValue}”.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '„{attribute}” vērtībai jābūt vienādai ar „{compareValueOrAttribute}”.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '„{attribute}” vērtībai nedrīkst būt vienādai ar „{compareValueOrAttribute}”.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver vismaz {min, number} {min, plural, one{simbolu} other{simbolus}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver ne vairāk par {max, number} {max, plural, one{simbolu} other{simbolus}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver {length, number} {length, plural, one{simbolu} other{simbolus}}.', diff --git a/framework/messages/ms/yii.php b/framework/messages/ms/yii.php index 1f2ad87946..078ea60307 100644 --- a/framework/messages/ms/yii.php +++ b/framework/messages/ms/yii.php @@ -38,10 +38,10 @@ return [ 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Memaparkan {begin, number}-{end, number} daripada {totalCount, number} {totalCount, plural, one{item} other{items}}.', 'The file "{file}" is not an image.' => 'Fail ini "{file}" bukan bejenis gambar.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => - 'Fail ini "{file}" terlalu besar. Saiz tidak boleh lebih besar daripada {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => - 'Fail ini "{file}" terlalu kecil. Saiznya tidak boleh lebih kecil daripada {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => + 'Fail ini "{file}" terlalu besar. Saiz tidak boleh lebih besar daripada {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => + 'Fail ini "{file}" terlalu kecil. Saiznya tidak boleh lebih kecil daripada {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Format untuk atribut ini {attribute} tidak sah.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu panjang. Panjang gambar tidak boleh lebih besar daripada {limit, number} {limit, plural, one{pixel} other{pixels}}.', diff --git a/framework/messages/nb-NO/yii.php b/framework/messages/nb-NO/yii.php new file mode 100644 index 0000000000..dce67da33f --- /dev/null +++ b/framework/messages/nb-NO/yii.php @@ -0,0 +1,114 @@ + '(ikke angitt)', + 'An internal server error occurred.' => 'En intern serverfeil oppstod.', + 'Are you sure you want to delete this item?' => 'Er du sikker på at du vil slette dette elementet?', + 'Delete' => 'Slett', + 'Error' => 'Feil', + 'File upload failed.' => 'Filopplasting feilet.', + 'Home' => 'Hjem', + 'Invalid data received for parameter "{param}".' => 'Ugyldig data mottatt for parameter "{param}".', + 'Login Required' => 'Innlogging påkrevet', + 'Missing required arguments: {params}' => 'Mangler obligatoriske argumenter: {params}', + 'Missing required parameters: {params}' => 'Mangler obligatoriske parametere: {params}', + 'No' => 'Nei', + 'No results found.' => 'Ingen resultater funnet.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Bare filer med disse MIME-typene er tillatt: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Bare filer med disse filendelsene er tillatt: {mimeTypes}.', + 'Page not found.' => 'Siden finnes ikke.', + 'Please fix the following errors:' => 'Vennligs fiks følgende feil:', + 'Please upload a file.' => 'Vennligs last opp en fil.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Viser {begin, number}-{end, number} av {totalCount, number} {totalCount, plural, one{element} other{elementer}}.', + 'The file "{file}" is not an image.' => 'Filen "{file}" er ikke et bilde.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Filen "{file}" er for stor. Størrelsen kan ikke overskride {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Filen "{file}" er for liten. Størrelsen kan ikke være mindre enn {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Formatet til {attribute} er ugyldig.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bildet "{file}" er for stort. Høyden kan ikke overskride {limit, number} {limit, plural, one{piksel} other{piksler}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bildet "{file}" er for stort. Bredden kan ikke overskride {limit, number} {limit, plural, one{piksel} other{piksler}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bildet "{file}" er for lite. Høyden kan ikke være mindre enn {limit, number} {limit, plural, one{piksel} other{piksler}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bildet "{file}" er for lite. Bredden kan ikke være mindre enn {limit, number} {limit, plural, one{piksel} other{piksler}}.', + 'The requested view "{name}" was not found.' => 'Den forespurte visningen "{name}" ble ikke funnet.', + 'The verification code is incorrect.' => 'Verifiseringskoden er feil.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Totalt {count, number} {count, plural, one{element} other{elementer}}.', + 'Unable to verify your data submission.' => 'Kunne ikke verifisere innsendt data.', + 'Unknown option: --{name}' => 'Ukjent alternativ: --{name}', + 'Update' => 'Oppdater', + 'View' => 'Vis', + 'Yes' => 'Ja', + 'You are not allowed to perform this action.' => 'Du har ikke tilatelse til å gjennomføre denne handlingen.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Du kan laste opp maks {limit, number} {limit, plural, one{fil} other{filer}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'om {delta, plural, =1{en dag} other{# dager}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'om {delta, plural, =1{ett minutt} other{# minutter}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'om {delta, plural, =1{en måned} other{# måneder}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'om {delta, plural, =1{ett sekund} other{# sekunder}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'om {delta, plural, =1{ett år} other{# år}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'om {delta, plural, =1{en time} other{# timer}}', + 'just now' => 'akkurat nå', + 'the input value' => 'inndataverdien', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" er allerede tatt i bruk.', + '{attribute} cannot be blank.' => '{attribute} kan ikke være tomt.', + '{attribute} is invalid.' => '{attribute} er ugyldig.', + '{attribute} is not a valid URL.' => '{attribute} er ikke en gyldig URL.', + '{attribute} is not a valid email address.' => '{attribute} er ikke en gyldig e-postadresse.', + '{attribute} must be "{requiredValue}".' => '{attribute} må være "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} må være et nummer.', + '{attribute} must be a string.' => '{attribute} må være en tekststreng.', + '{attribute} must be an integer.' => '{attribute} må være et heltall.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} må være enten "{true}" eller "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} må være større enn "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} må være større enn eller lik "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} må være mindre enn "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} må være mindre enn eller lik "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} kan ikke være større enn {max}.', + '{attribute} must be no less than {min}.' => '{attribute} kan ikke være mindre enn {min}.', + '{attribute} must be repeated exactly.' => '{attribute} må gjentas nøyaktig.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} kan ikke være lik "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} må inneholde minst {min, number} {min, plural, one{tegn} other{tegn}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} kan inneholde maks {max, number} {max, plural, one{tegn} other{tegn}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} må inneholde {length, number} {length, plural, one{tegn} other{tegn}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{en dag} other{# dager}} siden', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{ett minutt} other{# minutter}} siden', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{en måned} other{# måneder}} siden', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{ett sekund} other{# sekunder}} siden', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{ett år} other{# år}} siden', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{en time} other{# timer}} siden', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{byte}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibyte}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibyte}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabyte}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabyte}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibyte}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibyte}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobyte}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobyte}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibyte}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibyte}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabyte}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabyte}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibyte}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibyte}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabyte}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabyte}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibyte}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibyte}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabyte}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabyte}}', +]; diff --git a/framework/messages/nl/yii.php b/framework/messages/nl/yii.php index b9f51cd77d..acd5e30403 100644 --- a/framework/messages/nl/yii.php +++ b/framework/messages/nl/yii.php @@ -17,7 +17,86 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '(not set)' => '(niet ingesteld)', + 'An internal server error occurred.' => 'Er is een interne serverfout opgetreden.', + 'Are you sure you want to delete this item?' => 'Weet je zeker dat je dit item wilt verwijderen?', + 'Delete' => 'Verwijderen', + 'Error' => 'Fout', + 'File upload failed.' => 'Bestand uploaden mislukt.', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Ongeldige gegevens ontvangen voor parameter "{param}".', + 'Login Required' => 'Inloggen verplicht', + 'Missing required arguments: {params}' => 'Ontbrekende vereiste argumenten: {params}', + 'Missing required parameters: {params}' => 'Ontbrekende vereiste parameters: {params}', + 'No' => 'Nee', + 'No results found.' => 'Geen resultaten gevonden', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Alleen bestanden met de volgende MIME types zijn toegestaan: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => 'Alleen bestanden met de volgende extensies zijn toegestaan: {extensions}.', + 'Page not found.' => 'Pagina niet gevonden.', + 'Please fix the following errors:' => 'Corrigeer de volgende fouten:', + 'Please upload a file.' => 'Upload een bestand.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Resultaat {begin, number}-{end, number} van {totalCount, number} {totalCount, plural, one{item} other{items}}.', + 'The file "{file}" is not an image.' => 'Het bestand "{file}" is geen afbeelding.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Het bestand "{file}" is te groot. Het kan niet groter zijn dan {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Het bestand "{file}" is te klein. Het kan niet kleiner zijn dan {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Het formaat van {attribute} is ongeldig', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', + 'The requested view "{name}" was not found.' => 'De gevraagde view "{view}" werd niet gevonden.', + 'The verification code is incorrect.' => 'De verificatiecode is onjuist.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Totaal {count, number} {count, plural, one{item} other{items}}.', + 'Unable to verify your data submission.' => 'Het is niet mogelijk uw verstrekte gegevens te verifiëren.', + 'Unknown option: --{name}' => 'Onbekende optie: --{name}', + 'Update' => 'Bewerk', + 'View' => 'Bekijk', + 'Yes' => 'Ja', + 'You are not allowed to perform this action.' => 'U bent niet gemachtigd om deze actie uit te voeren.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'U kunt maximaal {limit, number} {limit, plural, one{ander bestand} other{andere bestander}} uploaden.', + 'in {delta, plural, =1{a day} other{# days}}' => 'binnen {delta, plural, =1{een dag} other{# dagen}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'binnen {delta, plural, =1{een minuut} other{# minuten}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'binnen {delta, plural, =1{een maand} other{# maanden}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'binnen {delta, plural, =1{een seconde} other{# seconden}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'binnen {delta, plural, =1{een jaar} other{# jaren}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'binnen {delta, plural, =1{een uur} other{# uur}}', 'just now' => 'zojuist', + 'the input value' => 'de invoerwaarde', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" is reeds in gebruik.', + '{attribute} cannot be blank.' => '{attribute} mag niet leeg zijn.', + '{attribute} is invalid.' => '{attribute} is ongeldig.', + '{attribute} is not a valid URL.' => '{attribute} is geen geldige URL.', + '{attribute} is not a valid email address.' => '{attribute} is geen geldig emailadres.', + '{attribute} must be "{requiredValue}".' => '{attribute} moet "{requiredValue}" zijn.', + '{attribute} must be a number.' => '{attribute} moet een getal zijn.', + '{attribute} must be a string.' => '{attribute} moet een string zijn.', + '{attribute} must be an integer.' => '{attribute} moet een geheel getal zijn.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} moet "{true}" of "{false}" zijn.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} moet groter zijn dan "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} moet groter dan of gelijk aan "{compareValue}" zijn.', + '{attribute} must be less than "{compareValue}".' => '{attribute} moet minder zijn dan "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} moet minder dan of gelijk aan "{compareValue}" zijn.', + '{attribute} must be no greater than {max}.' => '{attribute} mag niet groter zijn dan {max}.', + '{attribute} must be no less than {min}.' => '{attribute} mag niet kleiner zijn dan {min}.', + '{attribute} must be repeated exactly.' => '{attribute} moet exact herhaald worden.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} mag niet gelijk zijn aan "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} moet minstens {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} mag maximaal {max, number} {max, plural, one{karakter} other{karakters}} bevatten.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} moet precies {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{een dag} other{# dagen}} geleden', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{een minuut} other{# minuten}} geleden', +<<<<<<< HEAD +<<<<<<< HEAD + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{a month} other{# months}} geleden', +======= + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{een maand} other{# maanden}} geleden', +>>>>>>> yiichina/master +======= + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{een maand} other{# maanden}} geleden', +>>>>>>> master + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{een seconde} other{# seconden}} geleden', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{een jaar} other{# jaren}} geleden', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{een uur} other{# uren}} geleden', '{nFormatted} B' => '{nFormatted} B', '{nFormatted} GB' => '{nFormatted} GB', '{nFormatted} GiB' => '{nFormatted} GiB', @@ -40,82 +119,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', - '(not set)' => '(niet ingesteld)', - 'An internal server error occurred.' => 'Er is een interne serverfout opgetreden.', - 'Are you sure you want to delete this item?' => 'Ben je zeker dat je dit item wilt verwijderen?', - 'Delete' => 'Verwijderen', - 'Error' => 'Fout', - 'File upload failed.' => 'Bestand uploaden mislukt.', - 'Home' => 'Home', - 'Invalid data received for parameter "{param}".' => 'Ongeldige gegevens ontvangen voor parameter "{param}".', - 'Login Required' => 'Inloggen verplicht', - 'Missing required arguments: {params}' => 'Ontbrekende vereiste argumenten: {params}', - 'Missing required parameters: {params}' => 'Ontbrekende vereiste parameters: {params}', - 'No' => 'Nee', - 'No help for unknown command "{command}".' => 'Geen hulp voor onbekend commando "{command}".', - 'No help for unknown sub-command "{command}".' => 'Geen hulp voor onbekend sub-commando "{command}".', - 'No results found.' => 'Geen resultaten gevonden', - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Alleen bestanden met de volgende MIME types zijn toegelaten: {mimeTypes}', - 'Only files with these extensions are allowed: {extensions}.' => 'Alleen bestanden met de volgende extensies zijn toegestaan: {extensions}.', - 'Page not found.' => 'Pagina niet gevonden.', - 'Please fix the following errors:' => 'Corrigeer de volgende fouten:', - 'Please upload a file.' => 'Upload een bestand.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Resultaat {begin, number}-{end, number} van {totalCount, number} {totalCount, plural, one{item} other{items}}.', - 'The file "{file}" is not an image.' => 'Het bestand "{file}" is geen afbeelding.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Het bestand "{file}" is te groot. Het kan niet groter zijn dan {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Het bestand "{file}" is te klein. Het kan niet kleiner zijn dan {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The format of {attribute} is invalid.' => 'Het formaat van {attribute} is ongeldig', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', - 'The requested view "{name}" was not found.' => 'De gevraagde view "{view}" werd niet gevonden.', - 'The verification code is incorrect.' => 'De verificatiecode is onjuist.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Totaal {count, number} {count, plural, one{item} other{items}}.', - 'Unable to verify your data submission.' => 'Het is niet mogelijk uw verstrekte gegevens te verifiëren.', - 'Unknown command "{command}".' => 'Onbekend commando "{command}".', - 'Unknown option: --{name}' => 'Onbekende optie: --{name}', - 'Update' => 'Update', - 'View' => 'Bekijk', - 'Yes' => 'Ja', - 'You are not allowed to perform this action.' => 'U bent niet gemachtigd om deze actie uit te voeren.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'U kunt maximaal {limit, number} {limit, plural, one{ander bestand} other{andere bestander}} uploaden.', - 'in {delta, plural, =1{a day} other{# days}}' => 'binnen {delta, plural, =1{een dag} other{# dagen}}', - 'in {delta, plural, =1{a minute} other{# minutes}}' => 'binnen {delta, plural, =1{een minuut} other{# minuten}}', - 'in {delta, plural, =1{a month} other{# months}}' => 'binnen {delta, plural, =1{een maand} other{# maanden}}', - 'in {delta, plural, =1{a second} other{# seconds}}' => 'binnen {delta, plural, =1{een seconde} other{# seconden}}', - 'in {delta, plural, =1{a year} other{# years}}' => 'binnen {delta, plural, =1{een jaar} other{# jaren}}', - 'in {delta, plural, =1{an hour} other{# hours}}' => 'binnen {delta, plural, =1{een uur} other{# uren}}', - 'the input value' => 'de invoerwaarde', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" is reeds in gebruik.', - '{attribute} cannot be blank.' => '{attribute} mag niet leeg zijn.', - '{attribute} is invalid.' => '{attribute} is ongeldig.', - '{attribute} is not a valid URL.' => '{attribute} is geen geldige URL.', - '{attribute} is not a valid email address.' => '{attribute} is geen geldig emailadres.', - '{attribute} must be "{requiredValue}".' => '{attribute} moet "{requiredValue}" zijn.', - '{attribute} must be a number.' => '{attribute} moet een getal zijn.', - '{attribute} must be a string.' => '{attribute} moet een string zijn.', - '{attribute} must be an integer.' => '{attribute} moet een geheel getal zijn.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} moet "{true}" of "{false}" zijn.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} moet groter zijn dan "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} moet groter dan of gelijk aan "{compareValue}" zijn.', - '{attribute} must be less than "{compareValue}".' => '{attribute} moet minder zijn dan "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} moet minder dan of gelijk aan "{compareValue}" zijn.', - '{attribute} must be no greater than {max}.' => '{attribute} mag niet groter zijn dan {max}.', - '{attribute} must be no less than {min}.' => '{attribute} mag niet kleiner zijn dan {min}.', - '{attribute} must be repeated exactly.' => '{attribute} moet exact herhaald worden.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} mag niet gelijk zijn aan "{compareValue}".', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} moet minstens {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} mag maximaal {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} moet precies {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{een dag} other{# dagen}} geleden', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{een minuut} other{# minuten}} geleden', -<<<<<<< HEAD - '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{a month} other{# months}} geleden', -======= - '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{een maand} other{# maanden}} geleden', ->>>>>>> yiichina/master - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{een seconde} other{# seconden}} geleden', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{een jaar} other{# jaren}} geleden', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{een uur} other{# uren}} geleden', ]; diff --git a/framework/messages/pl/yii.php b/framework/messages/pl/yii.php index c2820c1c16..9abe5441e9 100644 --- a/framework/messages/pl/yii.php +++ b/framework/messages/pl/yii.php @@ -17,7 +17,8 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'just now' => 'przed chwilą', + 'Powered by {yii}' => 'Powered by {yii}', + 'Yii Framework' => 'Yii Framework', '(not set)' => '(brak wartości)', 'An internal server error occurred.' => 'Wystąpił wewnętrzny błąd serwera.', 'Are you sure you want to delete this item?' => 'Czy na pewno usunąć ten element?', @@ -30,8 +31,6 @@ return [ 'Missing required arguments: {params}' => 'Brak wymaganych argumentów: {params}', 'Missing required parameters: {params}' => 'Brak wymaganych parametrów: {params}', 'No' => 'Nie', - 'No help for unknown command "{command}".' => 'Brak pomocy dla nieznanego polecenia "{command}".', - 'No help for unknown sub-command "{command}".' => 'Brak pomocy dla nieznanego pod-polecenia "{command}".', 'No results found.' => 'Brak wyników.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Dozwolone są tylko pliki z następującymi typami MIME: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Dozwolone są tylko pliki z następującymi rozszerzeniami: {extensions}.', @@ -40,8 +39,8 @@ return [ 'Please upload a file.' => 'Proszę wgrać plik.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Wyświetlone {begin, number}-{end, number} z {totalCount, number}.', 'The file "{file}" is not an image.' => 'Plik "{file}" nie jest obrazem.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Plik "{file}" jest zbyt duży. Jego rozmiar nie może przekraczać {limit, number} {limit, plural, one{bajtu} few{bajtów} many{bajtów} other{bajta}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Plik "{file}" jest za mały. Jego rozmiar nie może być mniejszy niż {limit, number} {limit, plural, one{bajtu} few{bajtów} many{bajtów} other{bajta}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Plik "{file}" jest zbyt duży. Jego rozmiar nie może przekraczać {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Plik "{file}" jest za mały. Jego rozmiar nie może być mniejszy niż {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Format {attribute} jest nieprawidłowy.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest zbyt duży. Wysokość nie może być większa niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest zbyt duży. Szerokość nie może być większa niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', @@ -51,7 +50,6 @@ return [ 'The verification code is incorrect.' => 'Kod weryfikacyjny jest nieprawidłowy.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Razem {count, number} {count, plural, one{rekord} few{rekordy} many{rekordów} other{rekordu}}.', 'Unable to verify your data submission.' => 'Nie udało się zweryfikować przesłanych danych.', - 'Unknown command "{command}".' => 'Nieznane polecenie "{command}".', 'Unknown option: --{name}' => 'Nieznana opcja: --{name}', 'Update' => 'Aktualizuj', 'View' => 'Zobacz szczegóły', @@ -64,28 +62,42 @@ return [ 'in {delta, plural, =1{a second} other{# seconds}}' => 'za {delta, plural, =1{sekundę} few{# sekundy} many{# sekund} other{# sekundy}}', 'in {delta, plural, =1{a year} other{# years}}' => 'za {delta, plural, =1{rok} few{# lata} many{# lat} other{# dni}}', 'in {delta, plural, =1{an hour} other{# hours}}' => 'za {delta, plural, =1{godzinę} few{# godziny} many{# godzin} other{# godziny}}', + 'just now' => 'przed chwilą', 'the input value' => 'wartość wejściowa', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" jest już w użyciu.', '{attribute} cannot be blank.' => '{attribute} nie może pozostać bez wartości.', + '{attribute} contains wrong subnet mask.' => '{attribute} posiada złą maskę podsieci.', '{attribute} is invalid.' => '{attribute} zawiera nieprawidłową wartość.', '{attribute} is not a valid URL.' => '{attribute} nie zawiera prawidłowego adresu URL.', '{attribute} is not a valid email address.' => '{attribute} nie zawiera prawidłowego adresu email.', + '{attribute} is not in the allowed range.' => '{attribute} nie jest w dozwolonym zakresie.', '{attribute} must be "{requiredValue}".' => '{attribute} musi mieć wartość "{requiredValue}".', '{attribute} must be a number.' => '{attribute} musi być liczbą.', '{attribute} must be a string.' => '{attribute} musi być tekstem.', + '{attribute} must be a valid IP address.' => '{attribute} musi być poprawnym adresem IP.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} musi być adresem IP w określonej podsieci.', '{attribute} must be an integer.' => '{attribute} musi być liczbą całkowitą.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} musi mieć wartość "{true}" lub "{false}".', - '{attribute} must be greater than "{compareValue}".' => '{attribute} musi mieć wartość większą od "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} musi mieć wartość większą lub równą "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} musi mieć wartość mniejszą od "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} musi mieć wartość mniejszą lub równą "{compareValue}".', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} musi mieć tę samą wartość co "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość większą od "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość większą lub równą "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość mniejszą od "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość mniejszą lub równą "{compareValueOrAttribute}".', '{attribute} must be no greater than {max}.' => '{attribute} musi wynosić nie więcej niż {max}.', '{attribute} must be no less than {min}.' => '{attribute} musi wynosić nie mniej niż {min}.', - '{attribute} must be repeated exactly.' => 'Wartość {attribute} musi być dokładnie powtórzona.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} musi mieć wartość różną od "{compareValue}".', + '{attribute} must not be a subnet.' => '{attribute} nie może być podsiecią.', + '{attribute} must not be an IPv4 address.' => '{attribute} nie może być adresem IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} nie może być adresem IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość różną od "{compareValueOrAttribute}".', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać co najmniej {min, number} {min, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać nie więcej niż {max, number} {max, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać dokładnie {length, number} {length, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 dzień} other{# dni} other{# dnia}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 godzina} few{# godziny} many{# godzin} other{# godziny}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuta} few{# minuty} many{# minut} other{# minuty}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 miesiąc} few{# miesiące} many{# miesięcy} other{# miesiąca}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 sekunda} few{# sekundy} many{# sekund} other{# sekundy}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 rok} few{# lata} many{# lat} other{# roku}}', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{jeden dzień} other{# dni} other{# dnia}} temu', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minutę} few{# minuty} many{# minut} other{# minuty}} temu', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{miesiąc} few{# miesiące} many{# miesięcy} other{# miesiąca}} temu', diff --git a/framework/messages/pt-BR/yii.php b/framework/messages/pt-BR/yii.php index 05e396abe6..bbb6947566 100644 --- a/framework/messages/pt-BR/yii.php +++ b/framework/messages/pt-BR/yii.php @@ -17,8 +17,6 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'São permitidos somente arquivos com os seguintes tipos MIME: {mimeTypes}.', - 'The requested view "{name}" was not found.' => 'A visão "{name}" solicitada não foi encontrada.', 'in {delta, plural, =1{a day} other{# days}}' => 'em {delta, plural, =1{1 dia} other{# dias}}', 'in {delta, plural, =1{a minute} other{# minutes}}' => 'em {delta, plural, =1{1 minuto} other{# minutos}}', 'in {delta, plural, =1{a month} other{# months}}' => 'em {delta, plural, =1{1 mês} other{# meses}}', @@ -31,19 +29,30 @@ return [ '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{há 1 segundo} other{há # segundos}}', '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{há 1 ano} other{há # anos}}', '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{há 1 hora} other{há # horas}}', - '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', - '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', - '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', - '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', - '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', - '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', - '{n} B' => '{n} B', - '{n} GB' => '{n} GB', - '{n} KB' => '{n} KB', - '{n} MB' => '{n} MB', - '{n} PB' => '{n} PB', - '{n} TB' => '{n} TB', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', '(not set)' => '(não definido)', + 'just now' => 'agora mesmo', 'An internal server error occurred.' => 'Ocorreu um erro interno do servidor.', 'Are you sure you want to delete this item?' => 'Confirma a exclusão deste item?', 'Delete' => 'Excluir', @@ -55,26 +64,25 @@ return [ 'Missing required arguments: {params}' => 'Argumentos obrigatórios ausentes: {params}', 'Missing required parameters: {params}' => 'Parâmetros obrigatórios ausentes: {params}', 'No' => 'Não', - 'No help for unknown command "{command}".' => 'Não há ajuda para o comando desconhecido "{command}".', - 'No help for unknown sub-command "{command}".' => 'Não há ajuda para o sub-comando desconhecido "{command}".', 'No results found.' => 'Nenhum resultado foi encontrado.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'São permitidos somente arquivos com os seguintes tipos MIME: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'São permitidos somente arquivos com as seguintes extensões: {extensions}.', 'Page not found.' => 'Página não encontrada.', 'Please fix the following errors:' => 'Por favor, corrija os seguintes erros:', 'Please upload a file.' => 'Por favor, faça upload de um arquivo.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Exibindo {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{item} other{itens}}.', 'The file "{file}" is not an image.' => 'O arquivo "{file}" não é uma imagem.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O arquivo "{file}" é grande demais. Seu tamanho não pode exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O arquivo "{file}" é pequeno demais. Seu tamanho não pode ser menor que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'O arquivo "{file}" é grande demais. Seu tamanho não pode exceder {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'O arquivo "{file}" é pequeno demais. Seu tamanho não pode ser menor que {formattedLimit}.', 'The format of {attribute} is invalid.' => 'O formato de "{attribute}" é inválido.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é grande demais. A altura não pode ser maior que {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é grande demais. A largura não pode ser maior que {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é pequeno demais. A altura não pode ser menor que {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é pequeno demais. A largura não pode ser menor que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The requested view "{name}" was not found.' => 'A visão "{name}" solicitada não foi encontrada.', 'The verification code is incorrect.' => 'O código de verificação está incorreto.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{item} other{itens}}.', 'Unable to verify your data submission.' => 'Não foi possível verificar o seu envio de dados.', - 'Unknown command "{command}".' => 'Comando desconhecido "{command}".', 'Unknown option: --{name}' => 'Opção desconhecida : --{name}', 'Update' => 'Alterar', 'View' => 'Exibir', @@ -84,23 +92,30 @@ return [ 'the input value' => 'o valor de entrada', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" já foi utilizado.', '{attribute} cannot be blank.' => '"{attribute}" não pode ficar em branco.', + '{attribute} contains wrong subnet mask.' => '{attribute} contém a máscara de sub-rede errado.', '{attribute} is invalid.' => '"{attribute}" é inválido.', '{attribute} is not a valid URL.' => '"{attribute}" não é uma URL válida.', '{attribute} is not a valid email address.' => '"{attribute}" não é um endereço de e-mail válido.', + '{attribute} is not in the allowed range.' => '{attribute} intervalo não permitido.', '{attribute} must be "{requiredValue}".' => '"{attribute}" deve ser "{requiredValue}".', '{attribute} must be a number.' => '"{attribute}" deve ser um número.', '{attribute} must be a string.' => '"{attribute}" deve ser um texto.', + '{attribute} must be a valid IP address.' => '{attribute} deve ser um endereço IP válido.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} deve ser um endereço IP com sub-rede especificada.', + '{attribute} must not be a subnet.' => '{attribute} não deve ser uma sub-rede.', + '{attribute} must not be an IPv4 address.' => '{attribute} não deve ser um endereço IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} não deve ser um endereço IPv6.', '{attribute} must be an integer.' => '"{attribute}" deve ser um número inteiro.', '{attribute} must be either "{true}" or "{false}".' => '"{attribute}" deve ser "{true}" ou "{false}".', - '{attribute} must be greater than "{compareValue}".' => '"{attribute}" deve ser maior que "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '"{attribute}" deve ser maior ou igual a "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '"{attribute}" deve ser menor que "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '"{attribute}" deve ser menor ou igual a "{compareValue}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '"{attribute}" deve ser maior que "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '"{attribute}" deve ser maior ou igual a "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '"{attribute}" deve ser menor que "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '"{attribute}" deve ser menor ou igual a "{compareValueOrAttribute}".', '{attribute} must be no greater than {max}.' => '"{attribute}" não pode ser maior que {max}.', '{attribute} must be no less than {min}.' => '"{attribute}" não pode ser menor que {min}.', - '{attribute} must be repeated exactly.' => '"{attribute}" deve ser repetido exatamente.', - '{attribute} must not be equal to "{compareValue}".' => '"{attribute}" não deve ser igual a "{compareValue}".', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '"{attribute}" não deve ser igual a "{compareValueOrAttribute}".', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '"{attribute}" deve conter pelo menos {min, number} {min, plural, one{caractere} other{caracteres}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '"{attribute}" deve conter no máximo {max, number} {max, plural, one{caractere} other{caracteres}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '"{attribute}" deve conter {length, number} {length, plural, one{caractere} other{caracteres}}.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} deve ser igual a "{compareValueOrAttribute}".', ]; diff --git a/framework/messages/pt/yii.php b/framework/messages/pt/yii.php index 4ff048f61e..886358b635 100644 --- a/framework/messages/pt/yii.php +++ b/framework/messages/pt/yii.php @@ -37,8 +37,8 @@ return [ 'Please upload a file.' => 'Por favor faça upload de um ficheiro.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'A exibir {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{item} other{itens}}.', 'The file "{file}" is not an image.' => 'O ficheiro “{file}” não é uma imagem.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O ficheiro “{file}” é grande demais. O tamanho não pode exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O ficheiro “{file}” é pequeno demais. O tamanho não pode ser menor do que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'O ficheiro “{file}” é grande demais. O tamanho não pode exceder {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'O ficheiro “{file}” é pequeno demais. O tamanho não pode ser menor do que {formattedLimit}.', 'The format of {attribute} is invalid.' => 'O formato de “{attribute}” é inválido.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é grande demais. A altura não pode ser maior do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é grande demais. A largura não pode ser maior do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', diff --git a/framework/messages/ro/yii.php b/framework/messages/ro/yii.php index 5143e03d8b..4b2e446b0c 100644 --- a/framework/messages/ro/yii.php +++ b/framework/messages/ro/yii.php @@ -37,8 +37,8 @@ return [ 'Please upload a file.' => 'Vă rugăm sa încărcați un fișier.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Sunt afișați itemii {begin, number}-{end, number} din {totalCount, number}.', 'The file "{file}" is not an image.' => 'Fișierul «{file}» nu este o imagine.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fișierul «{file}» este prea mare. Dimensiunea acestuia nu trebuie să fie mai mare de {limit, number} {limit, plural, one{byte} other{bytes}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fișierul «{file}» este prea mic. Dimensiunea acestuia nu trebuie sa fie mai mică de {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Fișierul «{file}» este prea mare. Dimensiunea acestuia nu trebuie să fie mai mare de {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Fișierul «{file}» este prea mic. Dimensiunea acestuia nu trebuie sa fie mai mică de {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Formatul «{attribute}» nu este valid.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mare. Înălțimea nu trebuie să fie mai mare de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mare. Lățimea nu trebuie să fie mai mare de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', diff --git a/framework/messages/ru/yii.php b/framework/messages/ru/yii.php index bbf6a63ada..6d08adaea4 100644 --- a/framework/messages/ru/yii.php +++ b/framework/messages/ru/yii.php @@ -17,6 +17,91 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '{attribute} must be equal to "{compareValueOrAttribute}".' => 'Значение «{attribute}» должно быть равно «{compareValueOrAttribute}».', + '{attribute} must be greater than "{compareValueOrAttribute}".' => 'Значение «{attribute}» должно быть больше значения «{compareValueOrAttribute}».', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => 'Значение «{attribute}» должно быть больше или равно значения «{compareValueOrAttribute}».', + '{attribute} must be less than "{compareValueOrAttribute}".' => 'Значение «{attribute}» должно быть меньше значения «{compareValueOrAttribute}».', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => 'Значение «{attribute}» должно быть меньше или равно значения «{compareValueOrAttribute}».', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Значение «{attribute}» не должно быть равно «{compareValueOrAttribute}».', + '(not set)' => '(не задано)', + 'An internal server error occurred.' => 'Возникла внутренняя ошибка сервера.', + 'Are you sure you want to delete this item?' => 'Вы уверены, что хотите удалить этот элемент?', + 'Delete' => 'Удалить', + 'Error' => 'Ошибка', + 'File upload failed.' => 'Загрузка файла не удалась.', + 'Home' => 'Главная', + 'Invalid data received for parameter "{param}".' => 'Неправильное значение параметра "{param}".', + 'Login Required' => 'Требуется вход.', + 'Missing required arguments: {params}' => 'Отсутствуют обязательные аргументы: {params}', + 'Missing required parameters: {params}' => 'Отсутствуют обязательные параметры: {params}', + 'No' => 'Нет', + 'No results found.' => 'Ничего не найдено.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Разрешена загрузка файлов только со следующими MIME-типами: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Разрешена загрузка файлов только со следующими расширениями: {extensions}.', + 'Page not found.' => 'Страница не найдена.', + 'Please fix the following errors:' => 'Исправьте следующие ошибки:', + 'Please upload a file.' => 'Загрузите файл.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показаны записи {begin, number}-{end, number} из {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Файл «{file}» не является изображением.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Файл «{file}» слишком большой. Размер не должен превышать {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Файл «{file}» слишком маленький. Размер должен быть более {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Неверный формат значения «{attribute}».', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Высота не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Ширина не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Высота должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Ширина должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The requested view "{name}" was not found.' => 'Запрашиваемый файл представления "{name}" не найден.', + 'The verification code is incorrect.' => 'Неправильный проверочный код.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Всего {count, number} {count, plural, one{запись} few{записи} many{записей} other{записи}}.', + 'Unable to verify your data submission.' => 'Не удалось проверить переданные данные.', + 'Unknown option: --{name}' => 'Неизвестная опция: --{name}', + 'Update' => 'Редактировать', + 'View' => 'Просмотр', + 'Yes' => 'Да', + 'You are not allowed to perform this action.' => 'Вам не разрешено производить данное действие.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Вы не можете загружать более {limit, number} {limit, plural, one{файла} few{файлов} many{файлов} other{файла}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'через {delta, plural, =1{день} one{# день} few{# дня} many{# дней} other{# дня}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'через {delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'через {delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'через {delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'через {delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'через {delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}}', + 'just now' => 'прямо сейчас', + 'the input value' => 'введённое значение', + '{attribute} "{value}" has already been taken.' => 'Значение «{value}» для «{attribute}» уже занято.', + '{attribute} cannot be blank.' => 'Необходимо заполнить «{attribute}».', + '{attribute} contains wrong subnet mask.' => 'Значение «{attribute}» содержит неверную маску подсети.', + '{attribute} is invalid.' => 'Значение «{attribute}» неверно.', + '{attribute} is not a valid URL.' => 'Значение «{attribute}» не является правильным URL.', + '{attribute} is not a valid email address.' => 'Значение «{attribute}» не является правильным email адресом.', + '{attribute} is not in the allowed range.' => 'Значение «{attribute}» не входит в список разрешенных диапазонов адресов.', + '{attribute} must be "{requiredValue}".' => 'Значение «{attribute}» должно быть равно «{requiredValue}».', + '{attribute} must be a number.' => 'Значение «{attribute}» должно быть числом.', + '{attribute} must be a string.' => 'Значение «{attribute}» должно быть строкой.', + '{attribute} must be a valid IP address.' => 'Значение «{attribute}» должно быть правильным IP адресом.', + '{attribute} must be an IP address with specified subnet.' => 'Значение «{attribute}» должно быть IP адресом с подсетью.', + '{attribute} must be an integer.' => 'Значение «{attribute}» должно быть целым числом.', + '{attribute} must be either "{true}" or "{false}".' => 'Значение «{attribute}» должно быть равно «{true}» или «{false}».', + '{attribute} must be no greater than {max}.' => 'Значение «{attribute}» не должно превышать {max}.', + '{attribute} must be no less than {min}.' => 'Значение «{attribute}» должно быть не меньше {min}.', + '{attribute} must not be a subnet.' => 'Значение «{attribute}» не должно быть подсетью.', + '{attribute} must not be an IPv4 address.' => 'Значение «{attribute}» не должно быть IPv4 адресом.', + '{attribute} must not be an IPv6 address.' => 'Значение «{attribute}» не должно быть IPv6 адресом.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать минимум {min, number} {min, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать максимум {max, number} {max, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать {length, number} {length, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# день} few{# дня} many{# дней} other{# дней}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# час} few{# часа} many{# часов} other{# часов}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# минута} few{# минуты} many{# минут} other{# минут}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# месяц} few{# месяца} many{# месяцев} other{# месяцев}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# секунда} few{# секунды} many{# секунд} other{# секунд}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# год} few{# года} many{# лет} other{# лет}}', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{# день} few{# дня} many{# дней} other{# дня}} назад', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}} назад', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}} назад', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}} назад', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}} назад', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}} назад', '{nFormatted} B' => '{nFormatted} Б', '{nFormatted} GB' => '{nFormatted} ГБ', '{nFormatted} GiB' => '{nFormatted} ГиБ', @@ -39,79 +124,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{петабайт} few{петабайта} many{петабайтов} other{петабайта}}', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{тебибайт} few{тебибайта} many{тебибайтов} other{тебибайта}}', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{терабайт} few{терабайта} many{терабайтов} other{терабайта}}', - '(not set)' => '(не задано)', - 'An internal server error occurred.' => 'Возникла внутренняя ошибка сервера.', - 'Are you sure you want to delete this item?' => 'Вы уверены, что хотите удалить этот элемент?', - 'Delete' => 'Удалить', - 'Error' => 'Ошибка', - 'File upload failed.' => 'Загрузка файла не удалась.', - 'Home' => 'Главная', - 'Invalid data received for parameter "{param}".' => 'Неправильное значение параметра "{param}".', - 'Login Required' => 'Требуется вход.', - 'Missing required arguments: {params}' => 'Отсутствуют обязательные аргументы: {params}', - 'Missing required parameters: {params}' => 'Отсутствуют обязательные параметры: {params}', - 'No' => 'Нет', - 'No help for unknown command "{command}".' => 'Справка недоступна для неизвестной команды "{command}".', - 'No help for unknown sub-command "{command}".' => 'Справка недоступна для неизвестной субкоманды "{command}".', - 'No results found.' => 'Ничего не найдено.', - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Разрешена загрузка файлов только со следующими MIME-типами: {mimeTypes}.', - 'Only files with these extensions are allowed: {extensions}.' => 'Разрешена загрузка файлов только со следующими расширениями: {extensions}.', - 'Page not found.' => 'Страница не найдена.', - 'Please fix the following errors:' => 'Исправьте следующие ошибки:', - 'Please upload a file.' => 'Загрузите файл.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показаны записи {begin, number}-{end, number} из {totalCount, number}.', - 'The file "{file}" is not an image.' => 'Файл «{file}» не является изображением.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» слишком большой. Размер не должен превышать {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» слишком маленький. Размер должен быть более {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', - 'The format of {attribute} is invalid.' => 'Неверный формат значения «{attribute}».', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Высота не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Ширина не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Высота должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Ширина должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', - 'The requested view "{name}" was not found.' => 'Запрашиваемый файл представления "{name}" не найден.', - 'The verification code is incorrect.' => 'Неправильный проверочный код.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Всего {count, number} {count, plural, one{запись} few{записи} many{записей} other{записи}}.', - 'Unable to verify your data submission.' => 'Не удалось проверить переданные данные.', - 'Unknown command "{command}".' => 'Неизвестная команда "{command}".', - 'Unknown option: --{name}' => 'Неизвестная опция: --{name}', - 'Update' => 'Редактировать', - 'View' => 'Просмотр', - 'Yes' => 'Да', - 'You are not allowed to perform this action.' => 'Вам не разрешено производить данное действие.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Вы не можете загружать более {limit, number} {limit, plural, one{файла} few{файлов} many{файлов} other{файла}}.', - 'in {delta, plural, =1{a day} other{# days}}' => 'через {delta, plural, =1{день} one{# день} few{# дня} many{# дней} other{# дня}}', - 'in {delta, plural, =1{a minute} other{# minutes}}' => 'через {delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}}', - 'in {delta, plural, =1{a month} other{# months}}' => 'через {delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}}', - 'in {delta, plural, =1{a second} other{# seconds}}' => 'через {delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}}', - 'in {delta, plural, =1{a year} other{# years}}' => 'через {delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}}', - 'in {delta, plural, =1{an hour} other{# hours}}' => 'через {delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}}', - 'just now' => 'прямо сейчас', - 'the input value' => 'введённое значение', - '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» уже занят.', - '{attribute} cannot be blank.' => 'Необходимо заполнить «{attribute}».', - '{attribute} is invalid.' => 'Значение «{attribute}» неверно.', - '{attribute} is not a valid URL.' => 'Значение «{attribute}» не является правильным URL.', - '{attribute} is not a valid email address.' => 'Значение «{attribute}» не является правильным email адресом.', - '{attribute} must be "{requiredValue}".' => 'Значение «{attribute}» должно быть равно «{requiredValue}».', - '{attribute} must be a number.' => 'Значение «{attribute}» должно быть числом.', - '{attribute} must be a string.' => 'Значение «{attribute}» должно быть строкой.', - '{attribute} must be an integer.' => 'Значение «{attribute}» должно быть целым числом.', - '{attribute} must be either "{true}" or "{false}".' => 'Значение «{attribute}» должно быть равно «{true}» или «{false}».', - '{attribute} must be greater than "{compareValue}".' => 'Значение «{attribute}» должно быть больше значения «{compareValue}».', - '{attribute} must be greater than or equal to "{compareValue}".' => 'Значение «{attribute}» должно быть больше или равно значения «{compareValue}».', - '{attribute} must be less than "{compareValue}".' => 'Значение «{attribute}» должно быть меньше значения «{compareValue}».', - '{attribute} must be less than or equal to "{compareValue}".' => 'Значение «{attribute}» должно быть меньше или равно значения «{compareValue}».', - '{attribute} must be no greater than {max}.' => 'Значение «{attribute}» не должно превышать {max}.', - '{attribute} must be no less than {min}.' => 'Значение «{attribute}» должно быть не меньше {min}.', - '{attribute} must be repeated exactly.' => 'Значение «{attribute}» должно быть повторено в точности.', - '{attribute} must not be equal to "{compareValue}".' => 'Значение «{attribute}» не должно быть равно «{compareValue}».', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать минимум {min, number} {min, plural, one{символ} few{символа} many{символов} other{символа}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать максимум {max, number} {max, plural, one{символ} few{символа} many{символов} other{символа}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать {length, number} {length, plural, one{символ} few{символа} many{символов} other{символа}}.', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{день} few{# дня} many{# дней} other{# дня}} назад', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}} назад', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}} назад', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}} назад', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}} назад', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}} назад', ]; diff --git a/framework/messages/sk/yii.php b/framework/messages/sk/yii.php index 6057d43cc8..a44b185c40 100644 --- a/framework/messages/sk/yii.php +++ b/framework/messages/sk/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,8 +17,91 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + '{attribute} contains wrong subnet mask.' => '{attribute} obsahuje neplatnú masku podsiete.', + '(not set)' => '(nie je nastavené)', + 'An internal server error occurred.' => 'Vyskytla sa interná chyba servera.', + 'Are you sure you want to delete this item?' => 'Skutočne chcete odstrániť tento záznam?', + 'Delete' => 'Zmazať', + 'Error' => 'Chyba', + 'File upload failed.' => 'Súbor sa nepodarilo nahrať.', + 'Home' => 'Úvod', + 'Invalid data received for parameter "{param}".' => 'Neplatné údaje pre parameter "{param}".', + 'Login Required' => 'Je potrebné sa prihlásiť', + 'Missing required arguments: {params}' => 'Chýbajú povinné argumenty: {params}', + 'Missing required parameters: {params}' => 'Chýbajú povinné parametre: {params}', + 'No' => 'Nie', + 'No results found.' => 'Neboli nájdené žiadne záznamy.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Povolené sú len súbory nasledovných MIME typov: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Povolené sú len súbory s nasledovnými príponami: {extensions}.', + 'Page not found.' => 'Stránka nebola nájdená.', + 'Please fix the following errors:' => 'Opravte prosím nasledujúce chyby:', + 'Please upload a file.' => 'Nahrajte prosím súbor.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Zobrazujem {begin, number}-{end, number} z {totalCount, number} {totalCount, plural, one{záznam} other{záznamov}}.', + 'The file "{file}" is not an image.' => 'Súbor "{file}" nie je obrázok.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Súbor "{file}" je príliš veľký. Veľkosť súboru nesmie byť viac ako {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Súbor "{file}" je príliš malý. Veľkosť súboru nesmie byť menej ako {formattedLimit}.', + 'The format of {attribute} is invalid.' => 'Formát atribútu {attribute} je neplatný.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Výška nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Šírka nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Výška nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Šírka nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', 'The requested view "{name}" was not found.' => 'Požadovaná stránka "{name}" nebola nájdená.', + 'The verification code is incorrect.' => 'Kód pre overenie je neplatný.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Celkovo {count, number} {count, plural, one{záznam} other{záznamov}}.', + 'Unable to verify your data submission.' => 'Nebolo možné preveriť odoslané údaje.', + 'Unknown option: --{name}' => 'Neznáme nastavenie: --{name}', + 'Update' => 'Upraviť', + 'View' => 'Náhľad', + 'Yes' => 'Áno', + 'You are not allowed to perform this action.' => 'Nemáte oprávnenie pre požadovanú akciu.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Nahrať môžete najviac {limit, number} {limit, plural, one{súbor} other{súborov}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'o {delta, plural, =1{deň} other{# dni}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'o {delta, plural, =1{minútu} other{# minút}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'o {delta, plural, =1{mesiac} other{# mesiacov}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'o {delta, plural, =1{sekundu} other{# sekúnd}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'o {delta, plural, =1{rok} other{# rokov}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'o {delta, plural, =1{hodinu} other{# hodín}}', 'just now' => 'práve teraz', + 'the input value' => 'vstupná hodnota', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" je už použité.', + '{attribute} cannot be blank.' => 'Pole {attribute} nesmie byť prázdne.', + '{attribute} is invalid.' => 'Pole {attribute} je neplatné.', + '{attribute} is not a valid URL.' => '{attribute} nie je platná URL.', + '{attribute} is not a valid email address.' => '{attribute} nie je platná emailová adresa.', + '{attribute} is not in the allowed range.' => '{attribute} je mimo povoleného rozsahu.', + '{attribute} must be "{requiredValue}".' => '{attribute} musí byť "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} musí byť číslo.', + '{attribute} must be a string.' => '{attribute} musí byť reťazec.', + '{attribute} must be a valid IP address.' => '{attribute} musí byť platná IP adresa.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} musí byť IP adresa so špecifikovanou podsieťou.', + '{attribute} must be an integer.' => '{attribute} musí byť integer.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} musí byť "{true}" alebo "{false}".', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} musí byť "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} musí byť väčšie ako "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} musí byť väčšie alebo rovné "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} musí byť menšie ako "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} musí byť menšie alebo rovné "{compareValueOrAttribute}".', + '{attribute} must be no greater than {max}.' => '{attribute} nesmie byť vyšší ako {max}.', + '{attribute} must be no less than {min}.' => '{attribute} nesmie byť nižší ako {min}.', + '{attribute} must not be a subnet.' => '{attribute} nesmie byť podsieť.', + '{attribute} must not be an IPv4 address.' => '{attribute} nesmie byť IPv4 adresa.', + '{attribute} must not be an IPv6 address.' => '{attribute} nesmie byť IPv6 adresa.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} sa nesmie rovnať "{compareValueOrAttribute}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať aspoň {min, number} {min, plural, one{znak} other{znakov}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} môže obsahovať najviac {max, number} {max, plural, one{znak} other{znakov}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať {length, number} {length, plural, one{znak} other{znakov}}.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 deň} =2{2 dni} =3{3 dni} =4{4 dni} other{# dní}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 hodina} =2{2 hodiny} =3{3 hodiny} =4{4 hodiny} other{# hodín}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minúta} =2{2 minúty} =3{3 minúty} =4{4 minúty} other{# minút}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 mesiac} =2{2 mesiace} =3{3 mesiace} =4{4 mesiace} other{# mesiacov}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 sekunda} =2{2 sekundy} =3{3 sekundy} =4{4 sekundy} other{# sekúnd}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 rok} =2{2 roky} =3{3 roky} =4{4 roky} other{# rokov}}', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{včera} other{pred # dňami}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'pred {delta, plural, =1{minútou} other{# minútami}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'pred {delta, plural, =1{mesiacom} other{# mesiacmi}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'pred {delta, plural, =1{sekundou} other{# sekundami}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'pred {delta, plural, =1{rokom} other{# rokmi}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'pred {delta, plural, =1{hodinou} other{# hodinami}}', '{nFormatted} B' => '{nFormatted} B', '{nFormatted} GB' => '{nFormatted} GB', '{nFormatted} GiB' => '{nFormatted} GiB', @@ -41,77 +124,4 @@ return [ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabajt} =2{petabajty} =3{petabajty} =4{petabajty} other{petabajtov}}', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibajt} =2{tebibajty} =3{tebibajty} =4{tebibajty} other{tebibajtov}}', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabajt} =2{terabajty} =3{terabajty} =4{terabajty} other{terabajtov}}', - '(not set)' => '(nie je nastavené)', - 'An internal server error occurred.' => 'Vyskytla sa interná chyba servera.', - 'Are you sure you want to delete this item?' => 'Skutočne chcete odstrániť tento záznam?', - 'Delete' => 'Zmazať', - 'Error' => 'Chyba', - 'File upload failed.' => 'Súbor sa nepodarilo nahrať.', - 'Home' => 'Úvod', - 'Invalid data received for parameter "{param}".' => 'Neplatné údaje pre parameter "{param}".', - 'Login Required' => 'Je potrebné sa prihlásiť', - 'Missing required arguments: {params}' => 'Chýbajú povinné argumenty: {params}', - 'Missing required parameters: {params}' => 'Chýbajú povinné parametre: {params}', - 'No' => 'Nie', - 'No help for unknown command "{command}".' => 'Pre neznámy príkaz "{command}" nie je k dispozícii žiadna nápoveda.', - 'No help for unknown sub-command "{command}".' => 'Pre neznámy podpríkaz "{command}" nie je k dispozícii žiadna nápoveda.', - 'No results found.' => 'Neboli nájdené žiadne záznamy.', - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Povolené sú len súbory nasledovných MIME typov: {mimeTypes}.', - 'Only files with these extensions are allowed: {extensions}.' => 'Povolené sú len súbory s nasledovnými príponami: {extensions}.', - 'Page not found.' => 'Stránka nebola nájdená.', - 'Please fix the following errors:' => 'Opravte prosím nasledujúce chyby:', - 'Please upload a file.' => 'Nahrajte prosím súbor.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Zobrazujem {begin, number}-{end, number} z {totalCount, number} {totalCount, plural, one{záznam} other{záznamov}}.', - 'The file "{file}" is not an image.' => 'Súbor "{file}" nie je obrázok.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Súbor "{file}" je príliš veľký. Veľkosť súboru nesmie byť viac ako {limit, number} {limit, plural, one{bajt} other{bajtov}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Súbor "{file}" je príliš malý. Veľkosť súboru nesmie byť menej ako {limit, number} {limit, plural, one{bajt} other{bajtov}}.', - 'The format of {attribute} is invalid.' => 'Formát atribútu {attribute} je neplatný.', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Výška nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Šírka nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Výška nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Šírka nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', - 'The verification code is incorrect.' => 'Kód pre overenie je neplatný.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Celkovo {count, number} {count, plural, one{záznam} other{záznamov}}.', - 'Unable to verify your data submission.' => 'Nebolo možné preveriť odoslané údaje.', - 'Unknown command "{command}".' => 'Neznámy príkaz "{command}".', - 'Unknown option: --{name}' => 'Neznáme nastavenie: --{name}', - 'Update' => 'Upraviť', - 'View' => 'Náhľad', - 'Yes' => 'Áno', - 'You are not allowed to perform this action.' => 'Nemáte oprávnenie pre požadovanú akciu.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Nahrať môžete najviac {limit, number} {limit, plural, one{súbor} other{súborov}}.', - 'in {delta, plural, =1{a day} other{# days}}' => 'o {delta, plural, =1{deň} other{# dni}}', - 'in {delta, plural, =1{a minute} other{# minutes}}' => 'o {delta, plural, =1{minútu} other{# minút}}', - 'in {delta, plural, =1{a month} other{# months}}' => 'o {delta, plural, =1{mesiac} other{# mesiacov}}', - 'in {delta, plural, =1{a second} other{# seconds}}' => 'o {delta, plural, =1{sekundu} other{# sekúnd}}', - 'in {delta, plural, =1{a year} other{# years}}' => 'o {delta, plural, =1{rok} other{# rokov}}', - 'in {delta, plural, =1{an hour} other{# hours}}' => 'o {delta, plural, =1{hodinu} other{# hodín}}', - 'the input value' => 'vstupná hodnota', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" je už použité.', - '{attribute} cannot be blank.' => '{attribute} nesmie byť prázdne.', - '{attribute} is invalid.' => '{attribute} je neplatné.', - '{attribute} is not a valid URL.' => '{attribute} nie je platná URL.', - '{attribute} is not a valid email address.' => '{attribute} nie je platná emailová adresa.', - '{attribute} must be "{requiredValue}".' => '{attribute} musí byť "{requiredValue}".', - '{attribute} must be a number.' => '{attribute} musí byť číslo.', - '{attribute} must be a string.' => '{attribute} musí byť reťazec.', - '{attribute} must be an integer.' => '{attribute} musí byť integer.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} musí byť "{true}" alebo "{false}".', - '{attribute} must be greater than "{compareValue}".' => '{attribute} musí byť vyšší ako "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} musí byť vyšší alebo rovný "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} musí byť nižší ako "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} musí byť nižší alebo rovný "{compareValue}".', - '{attribute} must be no greater than {max}.' => '{attribute} nesmie byť vyšší ako {max}.', - '{attribute} must be no less than {min}.' => '{attribute} nesmie byť nižší ako {min}.', - '{attribute} must be repeated exactly.' => '{attribute} musí byť rovnaký.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} nesmie byť "{compareValue}".', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať aspoň {min, number} {min, plural, one{znak} other{znakov}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} môže obsahovať najviac {max, number} {max, plural, one{znak} other{znakov}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať {length, number} {length, plural, one{znak} other{znakov}}.', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{včera} other{pred # dňami}}', - '{delta, plural, =1{a minute} other{# minutes}} ago' => 'pred {delta, plural, =1{minútou} other{# minútami}}', - '{delta, plural, =1{a month} other{# months}} ago' => 'pred {delta, plural, =1{mesiacom} other{# mesiacmi}}', - '{delta, plural, =1{a second} other{# seconds}} ago' => 'pred {delta, plural, =1{sekundou} other{# sekundami}}', - '{delta, plural, =1{a year} other{# years}} ago' => 'pred {delta, plural, =1{rokom} other{# rokmi}}', - '{delta, plural, =1{an hour} other{# hours}} ago' => 'pred {delta, plural, =1{hodinou} other{# hodinami}}', ]; diff --git a/framework/messages/sl/yii.php b/framework/messages/sl/yii.php index b77cf7c86f..c47971930b 100644 --- a/framework/messages/sl/yii.php +++ b/framework/messages/sl/yii.php @@ -40,8 +40,8 @@ return [ 'Please upload a file.' => 'Prosimo, naložite datoteko.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikaz {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, one{Element} two{Elementa} few{Elementi} other{Elementov}}.', 'The file "{file}" is not an image.' => 'Datoteka "{file}" ni slika.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Datoteka "{file}" je prevelika. Njena velikost {limit, number} {limit, plural, one{bajta} two{bajtov} few{bajtov} other{bajtov}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Datoteka "{file}" je premajhna. Njena velikost ne sme biti manjša od {limit, number} {limit, plural, one{bajta} two{bajtov} few{bajtov} other{bajtov}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Datoteka "{file}" je prevelika. Njena velikost {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Datoteka "{file}" je premajhna. Njena velikost ne sme biti manjša od {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Format {attribute} ni veljaven.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Višina ne sme biti večja od {limit, number} {limit, plural, one{piksla} other{pikslov}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Širina ne sme biti večja od {limit, number} {limit, plural, one{piksla} other{pikslov}}.', diff --git a/framework/messages/sr-Latn/yii.php b/framework/messages/sr-Latn/yii.php index cad9d9bce9..c2d52801a5 100644 --- a/framework/messages/sr-Latn/yii.php +++ b/framework/messages/sr-Latn/yii.php @@ -63,10 +63,10 @@ return [ 'Page not found.' => 'Stranica nije pronađena.', 'Please fix the following errors:' => 'Molimo vas ispravite sledeće greške:', 'Please upload a file.' => 'Molimo vas postavite fajl.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, plural, =1{# stavke} one{# stavke} few{# stavke} many{# stavki} other{# stavki}}.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, =1{# stavke} one{# stavke} few{# stavke} many{# stavki} other{# stavki}}.', 'The file "{file}" is not an image.' => 'Fajl "{file}" nije slika.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fajl "{file}" je prevelik. Veličina ne može biti veća od {limit, number} {limit, plural, one{bajt} few{bajta} other{bajta}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fajl "{file}" je premali. Veličina ne može biti manja od {limit, number} {limit, plural, one{bajt} few{bajta} other{bajta}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Fajl "{file}" je prevelik. Veličina ne može biti veća od {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Fajl "{file}" je premali. Veličina ne može biti manja od {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Format atributa "{attribute}" je neispravan.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Visina ne sme biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Širina ne sme biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', diff --git a/framework/messages/sr/yii.php b/framework/messages/sr/yii.php index d41ce84a15..288db916ec 100644 --- a/framework/messages/sr/yii.php +++ b/framework/messages/sr/yii.php @@ -63,10 +63,10 @@ return [ 'Page not found.' => 'Страница није пронађена.', 'Please fix the following errors:' => 'Молимо вас исправите следеће грешке:', 'Please upload a file.' => 'Молимо вас поставите фајл.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Приказано {begin, number}-{end, number} од {totalCount, plural, =1{# ставке} one{# ставкe} few{# ставке} many{# ставки} other{# ставки}}.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Приказано {begin, number}-{end, number} од {totalCount, number} {totalCount, plural, =1{# ставке} one{# ставкe} few{# ставке} many{# ставки} other{# ставки}}.', 'The file "{file}" is not an image.' => 'Фајл "{file}" није слика.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фајл "{file}" је превелик. Величина не може бити већа од {limit, number} {limit, plural, one{бајт} few{бајтa} other{бајтa}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фајл "{file}" је премали. Величина не може бити мања од {limit, number} {limit, plural, one{бајт} few{бајтa} other{бајтa}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Фајл "{file}" је превелик. Величина не може бити већа од {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Фајл "{file}" је премали. Величина не може бити мања од {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Формат атрибута "{attribute}" је неисправан.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је превелика. Висина не сме бити већа од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је превелика. Ширина не сме бити већа од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', diff --git a/framework/messages/sv/yii.php b/framework/messages/sv/yii.php index f318d70afb..4d5a43f6b7 100644 --- a/framework/messages/sv/yii.php +++ b/framework/messages/sv/yii.php @@ -37,8 +37,8 @@ return [ 'Please upload a file.' => 'Var god ladda upp en fil.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Visar {begin, number}-{end, number} av {totalCount, number} objekt.', 'The file "{file}" is not an image.' => 'Filen "{file}" är inte en bild.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" är för stor. Filstorleken får inte överskrida {limit, number} {limit, plural, one{byte} other{byte}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" är för liten. Filstorleken måste vara minst {limit, number} {limit, plural, one{byte} other{byte}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Filen "{file}" är för stor. Filstorleken får inte överskrida {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Filen "{file}" är för liten. Filstorleken måste vara minst {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Formatet för "{attribute}" är ogiltigt.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bilden "{file}" är för stor. Höjden får inte överskrida {limit, number} {limit, plural, =1{pixel} other{pixlar}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Bilden "{file}" är för stor. Bredden får inte överskrida {limit, number} {limit, plural, =1{pixel} other{pixlar}}.', diff --git a/framework/messages/th/yii.php b/framework/messages/th/yii.php index 1e09135167..ce1c2add78 100644 --- a/framework/messages/th/yii.php +++ b/framework/messages/th/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,90 +17,90 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - '(not set)' => '(ไม่ได้ตั้ง)', - 'An internal server error occurred.' => 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์', - 'Are you sure you want to delete this item?' => 'คุณแน่ใจหรือไม่ที่จะลบรายการนี้?', - 'Delete' => 'ลบ', - 'Error' => 'ผิดพลาด', - 'File upload failed.' => 'ไม่สามารถอัพโหลดไฟล์ได้', - 'Home' => 'หน้าหลัก', - 'Invalid data received for parameter "{param}".' => 'ค่าพารามิเตอร์ "{param}" ไม่ถูกต้อง', - 'Login Required' => 'จำเป็นต้องเข้าสู่ระบบ', - 'Missing required arguments: {params}' => 'อาร์กิวเมนต์ที่จำเป็นขาดหายไป: {params}', - 'Missing required parameters: {params}' => 'พารามิเตอร์ที่จำเป็นขาดหายไป: {params}', - 'No' => 'ไม่', - 'No help for unknown command "{command}".' => 'ไม่มีวิธีใช้สำหรับคำสั่ง "{command}" ที่ไม่รู้จัก', - 'No help for unknown sub-command "{command}".' => 'ไม่มีวิธีใช้สำหรับคำสั่งย่อย "{command}" ที่ไม่รู้จัก', - 'No results found.' => 'ไม่พบผลลัพธ์', - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'เฉพาะไฟล์ที่มีชนิด MIME ต่อไปนี้ที่ได้รับการอนุญาต: {mimeTypes}', - 'Only files with these extensions are allowed: {extensions}.' => 'เฉพาะไฟล์ที่มีนามสกุลต่อไปนี้ที่ได้รับอนุญาต: {extensions}', - 'Page not found.' => 'ไม่พบหน้า', - 'Please fix the following errors:' => 'โปรดแก้ไขข้อผิดพลาดต่อไปนี้:', - 'Please upload a file.' => 'กรุณาอัพโหลดไฟล์', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'แสดง {begin, number} ถึง {end, number} จาก {totalCount, number} ผลลัพธ์', - 'The file "{file}" is not an image.' => 'ไฟล์ "{file}" ไม่ใช่รูปภาพ', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ไฟล์ "{file}" มีขนาดใหญ่ไป ไฟล์จะต้องมีขนาดไม่เกิน {limit, number} ไบต์', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ไฟล์ "{file}" มีขนาดเล็กเกินไป ไฟล์จะต้องมีขนาดมากกว่า {limit, number} ไบต์', - 'The format of {attribute} is invalid.' => 'รูปแบบ {attribute} ไม่ถูกต้อง', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความสูงต้องน้อยกว่า {limit, number} พิกเซล', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความกว้างต้องน้อยกว่า {limit, number} พิกเซล', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความสูงต้องมากว่า {limit, number} พิกเซล', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความกว้างต้องมากกว่า {limit, number} พิกเซล', - 'The requested view "{name}" was not found.' => 'ไม่พบ "{name}" ในการเรียกใช้', - 'The verification code is incorrect.' => 'รหัสการยืนยันไม่ถูกต้อง', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'ทั้งหมด {count, number} ผลลัพธ์', - 'Unable to verify your data submission.' => 'ไม่สามารถตรวจสอบการส่งข้อมูลของคุณ', - 'Unknown command "{command}".' => 'ไม่รู้จักคำสั่ง "{command}"', - 'Unknown option: --{name}' => 'ไม่รู้จักตัวเลือก: --{name}', - 'Update' => 'ปรับปรุง', - 'View' => 'ดู', - 'Yes' => 'ใช่', - 'You are not allowed to perform this action.' => 'คุณไม่ได้รับอนุญาตให้ดำเนินการนี้', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'คุณสามารถอัพโหลดมากที่สุด {limit, number} ไฟล์', - 'in {delta, plural, =1{a second} other{# seconds}}' => 'ใน {delta} วินาที', - 'in {delta, plural, =1{a minute} other{# minutes}}' => 'ใน {delta} นาที', - 'in {delta, plural, =1{an hour} other{# hours}}' => 'ใน {delta} ชั่วโมง', - 'in {delta, plural, =1{a day} other{# days}}' => 'ใน {delta} วัน', - 'in {delta, plural, =1{a month} other{# months}}' => 'ใน {delta} เดือน', - 'in {delta, plural, =1{a year} other{# years}}' => 'ใน {delta} ปี', - 'the input value' => 'ค่าป้อนที่เข้ามา', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" มีอยู่แล้ว', - '{attribute} cannot be blank.' => '{attribute} ต้องไม่ว่างเปล่า', - '{attribute} is invalid.' => '{attribute} ไม่ถูกต้อง', - '{attribute} is not a valid URL.' => '{attribute} ไม่ใช่รูปแบบ URL ที่ถูกต้อง', - '{attribute} is not a valid email address.' => '{attribute} ไม่ใช่รูปแบบอีเมลที่ถูกต้อง', - '{attribute} must be "{requiredValue}".' => '{attribute} ต้องการ "{requiredValue}"', - '{attribute} must be a number.' => '{attribute} ต้องเป็นตัวเลขเท่านั้น', - '{attribute} must be a string.' => '{attribute} ต้องเป็นตัวอักขระเท่านั้น', - '{attribute} must be an integer.' => '{attribute} ต้องเป็นจำนวนเต็มเท่านั้น', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} ต้องเป็น "{true}" หรือ "{false}"', - '{attribute} must be greater than "{compareValue}".' => '{attribute} ต้องมากกว่า "{compareValue}"', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} ต้องมากกว่าหรือเท่ากับ "{compareValue}"', - '{attribute} must be less than "{compareValue}".' => '{attribute} ต้องน้อยกว่า "{compareValue}"', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} ต้องน้อยกว่าหรือเท่ากับ "{compareValue}"', - '{attribute} must be no greater than {max}.' => '{attribute} ต้องไม่มากกว่า {max}.', - '{attribute} must be no less than {min}.' => '{attribute} ต้องไม่น้อยกว่า {min}', - '{attribute} must be repeated exactly.' => '{attribute} ต้องมีค่าเหมือนกัน', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} ต้องมีค่าเหมือนกัน "{compareValue}"', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วยอักขระอย่างน้อย {min, number} อักขระ', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วยอักขระอย่างมาก {max, number} อักขระ', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วย {length, number} อักขระ', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} วินาทีที่ผ่านมา', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} นาทีที่ผ่านมา', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} ชั่วโมงที่ผ่านมา', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta} วันที่ผ่านมา', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta} เดือนที่ผ่านมา', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta} ปีที่ผ่านมา', - '{n, plural, =1{# byte} other{# bytes}}' => '{n} ไบต์', - '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} กิโลไบต์', - '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} เมกะไบต์', - '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} จิกะไบต์', - '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} เทระไบต์', - '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} เพตะไบต์', - '{n} B' => '{n} B', - '{n} GB' => '{n} GB', - '{n} KB' => '{n} KB', - '{n} MB' => '{n} MB', - '{n} PB' => '{n} PB', - '{n} TB' => '{n} TB', + '(not set)' => '(ไม่ได้ตั้ง)', + 'An internal server error occurred.' => 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์', + 'Are you sure you want to delete this item?' => 'คุณแน่ใจหรือไม่ที่จะลบรายการนี้?', + 'Delete' => 'ลบ', + 'Error' => 'ผิดพลาด', + 'File upload failed.' => 'อัพโหลดไฟล์ล้มเหลว', + 'Home' => 'หน้าหลัก', + 'Invalid data received for parameter "{param}".' => 'ค่าพารามิเตอร์ "{param}" ไม่ถูกต้อง', + 'Login Required' => 'จำเป็นต้องเข้าสู่ระบบ', + 'Missing required arguments: {params}' => 'อาร์กิวเมนต์ที่จำเป็นขาดหายไป: {params}', + 'Missing required parameters: {params}' => 'พารามิเตอร์ที่จำเป็นขาดหายไป: {params}', + 'No' => 'ไม่', + 'No results found.' => 'ไม่พบผลลัพธ์', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'เฉพาะไฟล์ที่มีชนิด MIME ต่อไปนี้ที่ได้รับการอนุญาต: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => 'เฉพาะไฟล์ที่มีนามสกุลต่อไปนี้ที่ได้รับอนุญาต: {extensions}', + 'Page not found.' => 'ไม่พบหน้า', + 'Please fix the following errors:' => 'โปรดแก้ไขข้อผิดพลาดต่อไปนี้:', + 'Please upload a file.' => 'กรุณาอัพโหลดไฟล์', + 'Powered by {yii}' => 'ขับเคลื่อนโดย {yii}', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'แสดง {begin, number} ถึง {end, number} จาก {totalCount, number} ผลลัพธ์', + 'The file "{file}" is not an image.' => 'ไฟล์ "{file}" ไม่ใช่รูปภาพ', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'ไฟล์ "{file}" มีขนาดใหญ่ไป ไฟล์จะต้องมีขนาดไม่เกิน {formattedLimit}', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'ไฟล์ "{file}" มีขนาดเล็กเกินไป ไฟล์จะต้องมีขนาดมากกว่า {formattedLimit}', + 'The format of {attribute} is invalid.' => 'รูปแบบ {attribute} ไม่ถูกต้อง', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความสูงต้องน้อยกว่า {limit, number} พิกเซล', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความกว้างต้องน้อยกว่า {limit, number} พิกเซล', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความสูงต้องมากว่า {limit, number} พิกเซล', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความกว้างต้องมากกว่า {limit, number} พิกเซล', + 'The requested view "{name}" was not found.' => 'ไม่พบ "{name}" ในการเรียกใช้', + 'The verification code is incorrect.' => 'รหัสการยืนยันไม่ถูกต้อง', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'ทั้งหมด {count, number} ผลลัพธ์', + 'Unable to verify your data submission.' => 'ไม่สามารถตรวจสอบการส่งข้อมูลของคุณ', + 'Unknown option: --{name}' => 'ไม่รู้จักตัวเลือก: --{name}', + 'Update' => 'ปรับปรุง', + 'View' => 'ดู', + 'Yes' => 'ใช่', + 'You are not allowed to perform this action.' => 'คุณไม่ได้รับอนุญาตให้ดำเนินการนี้', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'คุณสามารถอัพโหลดมากที่สุด {limit, number} ไฟล์', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'ใน {delta} วินาที', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'ใน {delta} นาที', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'ใน {delta} ชั่วโมง', + 'in {delta, plural, =1{a day} other{# days}}' => 'ใน {delta} วัน', + 'in {delta, plural, =1{a month} other{# months}}' => 'ใน {delta} เดือน', + 'in {delta, plural, =1{a year} other{# years}}' => 'ใน {delta} ปี', + 'just now' => 'เมื่อสักครู่นี้', + 'the input value' => 'ค่าป้อนที่เข้ามา', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ถูกใช้ไปแล้ว', + '{attribute} cannot be blank.' => '{attribute}ต้องไม่ว่างเปล่า', + '{attribute} is invalid.' => '{attribute}ไม่ถูกต้อง', + '{attribute} is not a valid URL.' => '{attribute}ไม่ใช่รูปแบบ URL ที่ถูกต้อง', + '{attribute} is not a valid email address.' => '{attribute}ไม่ใช่รูปแบบอีเมลที่ถูกต้อง', + '{attribute} must be "{requiredValue}".' => '{attribute}ต้องการ "{requiredValue}"', + '{attribute} must be a number.' => '{attribute}ต้องเป็นตัวเลขเท่านั้น', + '{attribute} must be a string.' => '{attribute}ต้องเป็นตัวอักขระเท่านั้น', + '{attribute} must be an integer.' => '{attribute}ต้องเป็นจำนวนเต็มเท่านั้น', + '{attribute} must be either "{true}" or "{false}".' => '{attribute}ต้องเป็น "{true}" หรือ "{false}"', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute}ต้องเหมือนกับ "{compareValueOrAttribute}"', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute}ต้องมากกว่า "{compareValueOrAttribute}"', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute}ต้องมากกว่าหรือเท่ากับ "{compareValueOrAttribute}"', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute}ต้องน้อยกว่า "{compareValueOrAttribute}"', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute}ต้องน้อยกว่าหรือเท่ากับ "{compareValueOrAttribute}"', + '{attribute} must be no greater than {max}.' => '{attribute}ต้องไม่มากกว่า {max}.', + '{attribute} must be no less than {min}.' => '{attribute}ต้องไม่น้อยกว่า {min}', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute}ต้องมีค่าเหมือนกัน "{compareValueOrAttribute}"', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}ควรประกอบด้วยอักขระอย่างน้อย {min, number} อักขระ', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}ควรประกอบด้วยอักขระอย่างมาก {max, number} อักขระ', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}ควรประกอบด้วย {length, number} อักขระ', + '{attribute} contains wrong subnet mask.' => '{attribute}ไม่ใช่ซับเน็ตที่ถูกต้อง', + '{attribute} is not in the allowed range.' => '{attribute}ไม่ได้อยู่ในช่วงที่ได้รับอนุญาต', + '{attribute} must be a valid IP address.' => '{attribute}ต้องเป็นที่อยู่ไอพีที่ถูกต้อง', + '{attribute} must be an IP address with specified subnet.' => '{attribute}ต้องเป็นที่อยู่ไอพีกับซับเน็ตที่ระบุ', + '{attribute} must not be a subnet.' => '{attribute}ต้องไม่ใช่ซับเน็ต', + '{attribute} must not be an IPv4 address.' => '{attribute}ต้องไม่ใช่ที่อยู่ไอพีรุ่น 4', + '{attribute} must not be an IPv6 address.' => '{attribute}ต้องไม่ใช่ที่อยู่ไอพีรุ่น 6', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta} วินาที', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta} ชั่วโมง', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta} นาที', + '{delta, plural, =1{1 day} other{# days}}' => '{delta} วัน', + '{delta, plural, =1{1 month} other{# months}}' => '{delta} เดือน', + '{delta, plural, =1{1 year} other{# years}}' => '{delta} ปี', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} วินาทีที่ผ่านมา', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} นาทีที่ผ่านมา', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} ชั่วโมงที่ผ่านมา', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} วันที่ผ่านมา', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} เดือนที่ผ่านมา', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} ปีที่ผ่านมา', ]; diff --git a/framework/messages/tj/yii.php b/framework/messages/tj/yii.php index d775ee9220..93c408efe3 100644 --- a/framework/messages/tj/yii.php +++ b/framework/messages/tj/yii.php @@ -17,98 +17,98 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - '{nFormatted} B' => '{nFormatted} B', - '{nFormatted} KB' => '{nFormatted} KB', - '{nFormatted} KiB' => '{nFormatted} KiB', - '{nFormatted} MB' => '{nFormatted} MB', - '{nFormatted} MiB' => '{nFormatted} MiB', - '{nFormatted} GB' => '{nFormatted} GB', - '{nFormatted} GiB' => '{nFormatted} GiB', - '{nFormatted} PB' => '{nFormatted} PB', - '{nFormatted} PiB' => '{nFormatted} PiB', - '{nFormatted} TB' => '{nFormatted} TB', - '{nFormatted} TiB' => '{nFormatted} TiB', - '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} байт', - '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} кибибайт', - '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} килобайт', - '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} мебибайт', - '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} мегабайт', - '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} гибибайт', - '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} гигабайт', - '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} пебибайт', - '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} петабайт', - '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} тебибайт', - '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} терабайт', - 'Are you sure you want to delete this item?' => 'Оё шумо дар ҳақиқат мехоҳед, ки ин нашрро нест кунед?', - 'The requested view "{name}" was not found.' => 'Файл "{name}" барои манзур ёфт нашуд', - '(not set)' => '(танзим нашуда)', - 'An internal server error occurred.' => 'Хатои дохилии сервер рух дод.', - 'Delete' => 'Нест', - 'Error' => 'Хато', - 'File upload failed.' => 'Аплоди файл шикаст хурд.', - 'Home' => 'Асосӣ', - 'Invalid data received for parameter "{param}".' => 'Маълумоти номувофиқ барои параметри "{param}" гирифта шуд.', - 'Login Required' => 'Вуруд маҷбурист', - 'Missing required arguments: {params}' => 'Аргументи лозими вуҷд надорад: {params}', - 'Missing required parameters: {params}' => 'Параметри лозими вуҷуд надорад: {params}', - 'No' => 'На', - 'No results found.' => 'Чизе ёфт нашуд.', - 'Page not found.' => 'Саҳифа ёфт нашуд.', - 'Please fix the following errors:' => 'Илтимос хатоҳои зеринро ислоҳ кунед:', - 'Only files with these extensions are allowed: {extensions}.' => 'Танҳо файлҳои бо ин пасванд иҷоза аст: {extensions}.', - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Фақат ин намуди файлҳо иҷозат аст: {mimeTypes}.', - 'The format of {attribute} is invalid.' => 'Формати {attribute} ғалат буд.', - 'Please upload a file.' => 'Илтимос файл аплод кунед.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Манзури {begin, number}-{end, number} аз {totalCount, number}.', - 'The file "{file}" is not an image.' => 'Файл "{file}" расм набуд.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл "{file}" калон аст. Аз {limit, number} набояд калонтар бошад.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл "{file}" хурд аст. Аз {limit, number} набояд хурдтар бошад.', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" баланд аст. Баландияш набояд аз {limit, number} зиёд бошад.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" паҳн аст. Паҳнияш набояд аз {limit, number} зиёд бошад.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Баландияш набояд аз {limit, number} хурд бошад.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Паҳнияш набояд аз {limit, number} хурд бошад.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ҳамаги {limit, number} аплод карда метавонед.', - 'The verification code is incorrect.' => 'Коди санҷиши ғалат аст.', - 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ҳамаги {count, number} нашр.', - 'Unable to verify your data submission.' => 'Маълумоти фиристодаи шуморо санҷиш карда натавонистам.', - 'Unknown option: --{name}' => 'Гузинаи номаълум: --{name}', - 'Update' => 'Тағир', - 'View' => 'Манзур', - 'Yes' => 'Ҳа', - 'just now' => 'ҳоло', - 'the input value' => 'маълумоти вурудбуда', - 'You are not allowed to perform this action.' => 'Шумо барои анҷоми ин амал дастнорасед.', - 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} сонияи дигар', - 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} дақиқаи дигар', - 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} соати дигар', - 'in {delta, plural, =1{a day} other{# days}}' => '{delta} рӯзи дигар', - 'in {delta, plural, =1{a month} other{# months}}' => '{delta} моҳи дигар', - 'in {delta, plural, =1{a year} other{# years}}' => '{delta} соли дигар', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} сонияи қабл', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} дақиқаи қабл', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} соати қабл', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta} рӯзи қабл', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta} моҳи қабл', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta} сол пеш', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" машғул аст.', - '{attribute} cannot be blank.' => '{attribute} набояд холи бошад.', - '{attribute} is invalid.' => '{attribute} ғалат аст.', - '{attribute} is not a valid URL.' => '{attribute} URL ғалат аст.', - '{attribute} is not a valid email address.' => '{attribute} E-mail одреси ғалат аст.', - '{attribute} must be "{requiredValue}".' => '{attribute} бояд "{requiredValue}" бошад.', - '{attribute} must be a number.' => '{attribute} бояд адад бошад.', - '{attribute} must be a string.' => '{attribute} бояд хат бошад.', - '{attribute} must be an integer.' => '{attribute} бояд адади комил бошад.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} бояд ё "{true}" ё "{false}" бошад.', - '{attribute} must be greater than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" калон бошад.', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} бояд калон ё баробари "{compareValue}" бошад.', - '{attribute} must be less than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" хурд бошад.', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} бояд хурд ё баробари "{compareValue}" бошад.', - '{attribute} must be no greater than {max}.' => '{attribute} бояд аз {max} зиёд набошад.', - '{attribute} must be no less than {min}.' => '{attribute} бояд аз {min} кам набошад.', - '{attribute} must be repeated exactly.' => '{attribute} айнан бояд такрор шавад.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} бояд баробари "{compareValue}" набошад.', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} хади ақал {min, number} рамз дошта бошад.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} хамаги {max, number} рамз дошта бошад.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} бояд {length, number} рамз дошта бошад.', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} байт', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} кибибайт', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} килобайт', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} мебибайт', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} мегабайт', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} гибибайт', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} гигабайт', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} пебибайт', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} петабайт', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} тебибайт', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} терабайт', + 'Are you sure you want to delete this item?' => 'Оё шумо дар ҳақиқат мехоҳед, ки ин нашрро нест кунед?', + 'The requested view "{name}" was not found.' => 'Файл "{name}" барои манзур ёфт нашуд', + '(not set)' => '(танзим нашуда)', + 'An internal server error occurred.' => 'Хатои дохилии сервер рух дод.', + 'Delete' => 'Нест', + 'Error' => 'Хато', + 'File upload failed.' => 'Аплоди файл шикаст хурд.', + 'Home' => 'Асосӣ', + 'Invalid data received for parameter "{param}".' => 'Маълумоти номувофиқ барои параметри "{param}" гирифта шуд.', + 'Login Required' => 'Вуруд маҷбурист', + 'Missing required arguments: {params}' => 'Аргументи лозими вуҷд надорад: {params}', + 'Missing required parameters: {params}' => 'Параметри лозими вуҷуд надорад: {params}', + 'No' => 'На', + 'No results found.' => 'Чизе ёфт нашуд.', + 'Page not found.' => 'Саҳифа ёфт нашуд.', + 'Please fix the following errors:' => 'Илтимос хатоҳои зеринро ислоҳ кунед:', + 'Only files with these extensions are allowed: {extensions}.' => 'Танҳо файлҳои бо ин пасванд иҷоза аст: {extensions}.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Фақат ин намуди файлҳо иҷозат аст: {mimeTypes}.', + 'The format of {attribute} is invalid.' => 'Формати {attribute} ғалат буд.', + 'Please upload a file.' => 'Илтимос файл аплод кунед.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Манзури {begin, number}-{end, number} аз {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Файл "{file}" расм набуд.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Файл "{file}" калон аст. Аз {formattedLimit} набояд калонтар бошад.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Файл "{file}" хурд аст. Аз {formattedLimit} набояд хурдтар бошад.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" баланд аст. Баландияш набояд аз {limit, number} зиёд бошад.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" паҳн аст. Паҳнияш набояд аз {limit, number} зиёд бошад.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Баландияш набояд аз {limit, number} хурд бошад.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Паҳнияш набояд аз {limit, number} хурд бошад.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ҳамаги {limit, number} аплод карда метавонед.', + 'The verification code is incorrect.' => 'Коди санҷиши ғалат аст.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ҳамаги {count, number} нашр.', + 'Unable to verify your data submission.' => 'Маълумоти фиристодаи шуморо санҷиш карда натавонистам.', + 'Unknown option: --{name}' => 'Гузинаи номаълум: --{name}', + 'Update' => 'Тағир', + 'View' => 'Манзур', + 'Yes' => 'Ҳа', + 'just now' => 'ҳоло', + 'the input value' => 'маълумоти вурудбуда', + 'You are not allowed to perform this action.' => 'Шумо барои анҷоми ин амал дастнорасед.', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} сонияи дигар', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} дақиқаи дигар', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} соати дигар', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta} рӯзи дигар', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta} моҳи дигар', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta} соли дигар', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} сонияи қабл', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} дақиқаи қабл', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} соати қабл', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} рӯзи қабл', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} моҳи қабл', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} сол пеш', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" машғул аст.', + '{attribute} cannot be blank.' => '{attribute} набояд холи бошад.', + '{attribute} is invalid.' => '{attribute} ғалат аст.', + '{attribute} is not a valid URL.' => '{attribute} URL ғалат аст.', + '{attribute} is not a valid email address.' => '{attribute} E-mail одреси ғалат аст.', + '{attribute} must be "{requiredValue}".' => '{attribute} бояд "{requiredValue}" бошад.', + '{attribute} must be a number.' => '{attribute} бояд адад бошад.', + '{attribute} must be a string.' => '{attribute} бояд хат бошад.', + '{attribute} must be an integer.' => '{attribute} бояд адади комил бошад.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} бояд ё "{true}" ё "{false}" бошад.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" калон бошад.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} бояд калон ё баробари "{compareValue}" бошад.', + '{attribute} must be less than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" хурд бошад.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} бояд хурд ё баробари "{compareValue}" бошад.', + '{attribute} must be no greater than {max}.' => '{attribute} бояд аз {max} зиёд набошад.', + '{attribute} must be no less than {min}.' => '{attribute} бояд аз {min} кам набошад.', + '{attribute} must be repeated exactly.' => '{attribute} айнан бояд такрор шавад.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} бояд баробари "{compareValue}" набошад.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} хади ақал {min, number} рамз дошта бошад.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} хамаги {max, number} рамз дошта бошад.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} бояд {length, number} рамз дошта бошад.', ]; diff --git a/framework/messages/tr/yii.php b/framework/messages/tr/yii.php index 2f78e65b96..d61de882e2 100644 --- a/framework/messages/tr/yii.php +++ b/framework/messages/tr/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -19,7 +19,7 @@ return [ '(not set)' => '(Veri Yok)', 'An internal server error occurred.' => 'Bir sunucu hatası oluştu.', - 'Are you sure you want to delete this item?' => 'Bu veriyi silmek istediğinizden emin misiniz??', + 'Are you sure you want to delete this item?' => 'Bu veriyi silmek istediğinizden emin misiniz?', 'Delete' => 'Sil', 'Error' => 'Hata', 'File upload failed.' => 'Dosya yükleme başarısız.', @@ -29,8 +29,6 @@ return [ 'Missing required arguments: {params}' => 'Gerekli argüman eksik: {params}', 'Missing required parameters: {params}' => 'Gerekli parametre eksik: {params}', 'No' => 'Hayır', - 'No help for unknown command "{command}".' => 'Bilinmeyen komut "{command}" için bir yardım yok.', - 'No help for unknown sub-command "{command}".' => 'Bilinmeyen alt-komut "{command}" için bir yardım yok.', 'No results found.' => 'Sonuç bulunamadı', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Sadece bu tip MIME türleri geçerlidir: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Sadece bu tip uzantıları olan dosyalar geçerlidir: {extensions}.', @@ -39,8 +37,8 @@ return [ 'Please upload a file.' => 'Lütfen bir dosya yükleyin.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, number} {totalCount, plural, one{öğenin} other{öğenin}} {begin, number}-{end, number} arası gösteriliyor.', 'The file "{file}" is not an image.' => '"{file}" bir resim dosyası değil.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" dosyası çok büyük. Boyutu {limit, number} {limit, plural, one{byte} other{bytes}} değerinden büyük olamaz.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" dosyası çok küçük. Boyutu {limit, number} {limit, plural, one{byte} other{bytes}} değerinden küçük olamaz.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => '"{file}" dosyası çok büyük. Boyutu {formattedLimit} değerinden büyük olamaz.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => '"{file}" dosyası çok küçük. Boyutu {formattedLimit} değerinden küçük olamaz.', 'The format of {attribute} is invalid.' => '{attribute} formatı geçersiz.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok büyük. Yükseklik {limit, plural, one{pixel} other{pixels}} değerinden büyük olamaz.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok büyük. Genişlik {limit, number} {limit, plural, one{pixel} other{pixels}} değerinden büyük olamaz.', @@ -49,45 +47,81 @@ return [ 'The verification code is incorrect.' => 'Doğrulama kodu yanlış.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Toplam {count, number} {count, plural, one{öğe} other{öğe}}.', 'Unable to verify your data submission.' => 'İlettiğiniz veri doğrulanamadı.', - 'Unknown command "{command}".' => 'Bilinmeyen komut "{command}".', - 'Unknown option: --{name}' => 'Bilinmeyen seçenek: --{name}', + 'Unknown alias: -{name}' => 'Bilinmeyen rumuz: -{name}', + 'Unknown option: --{name}' => 'Bilinmeyen opsiyon: --{name}', 'Update' => 'Güncelle', - 'View' => 'İncele', + 'View' => 'Görüntüle', 'Yes' => 'Evet', 'You are not allowed to perform this action.' => 'Bu işlemi yapmaya yetkiniz yok.', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sadece {limit, number} {limit, plural, one{dosya} other{# dosya}} yükleyebilirsiniz.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta, plural, =1{bir gün} other{# gün}} içerisinde', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta, plural, =1{bir dakika} other{# dakika}} içerisinde', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta, plural, =1{bir ay} other{# ay}} içerisinde', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta, plural, =1{bir saniye} other{# saniye}} içerisinde', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta, plural, =1{bir yıl} other{# yıl}} içerisinde', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta, plural, =1{bir saat} other{# saat}} içerisinde', + 'just now' => 'henüz', 'the input value' => 'veri giriş değeri', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" daha önce alınmış.', '{attribute} cannot be blank.' => '{attribute} boş bırakılamaz.', + '{attribute} contains wrong subnet mask.' => '{attribute} yanlış alt ağ maskesi içeriyor.', '{attribute} is invalid.' => '{attribute} geçersiz.', '{attribute} is not a valid URL.' => '{attribute} geçerli bir URL değil.', '{attribute} is not a valid email address.' => '{attribute} geçerli bir mail adresi değil.', + '{attribute} is not in the allowed range.' => '{attribute} izin verilen aralıkta değil.', '{attribute} must be "{requiredValue}".' => '{attribute} {requiredValue} olmalı.', '{attribute} must be a number.' => '{attribute} sayı olmalı.', '{attribute} must be a string.' => '{attribute} harf olmalı.', + '{attribute} must be a valid IP address.' => '{attribute} geçerli bir IP adresi değil.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} IP adresi belirtilen alt ağ ile birlikte olmalı.', '{attribute} must be an integer.' => '{attribute} rakam olmalı.', - '{attribute} must be either "{true}" or "{false}".' => '{attribute} yanlızca {true} yada {false} olabilir.', - '{attribute} must be greater than "{compareValue}".' => '{attribute}, "{compareValue}" den büyük olmalı.', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}, "{compareValue}" den büyük yada eşit olmalı.', - '{attribute} must be less than "{compareValue}".' => '{attribute}, "{compareValue}" den az olmalı.', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}, "{compareValue}" den az yada eşit olmalı.', - '{attribute} must be no greater than {max}.' => '{attribute} {max} den büyük olmamalı.', - '{attribute} must be no less than {min}.' => '{attribute} {min} den küçük olmamalı.', - '{attribute} must be repeated exactly.' => '{attribute} aynı şekilde tekrarlanmalıdır.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute}, "{compareValue}" ile eşit olmamalı', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} "{true}" ya da "{false}" olmalı.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerine eşit olmalı.', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerinden büyük olmalı.', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerinden büyük veya eşit olmalı.', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerinden küçük olmalı.', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerinden küçük veya eşit olmalı.', + '{attribute} must be no greater than {max}.' => '{attribute} {max} değerinden büyük olamaz.', + '{attribute} must be no less than {min}.' => '{attribute} {min} değerinden küçük olamaz.', + '{attribute} must not be a subnet.' => '{attribute} alt ağ olamaz.', + '{attribute} must not be an IPv4 address.' => '{attribute} IPv4 olamaz.', + '{attribute} must not be an IPv6 address.' => '{attribute} IPv6 olamaz.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} "{compareValueOrAttribute}" değerine eşit olamaz.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} en az {min, number} karakter içermeli.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} en fazla {max, number} karakter içermeli.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} {length, number} karakter içermeli.', - '{n, plural, =1{# byte} other{# bytes}}' => '{n} Byte', - '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} Gigabyte', - '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} Kilobyte', - '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} Megabyte', - '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} Petabyte', - '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} Terabyte', - '{n} B' => '{n} B', - '{n} GB' => '{n} GB', - '{n} KB' => '{n} KB', - '{n} MB' => '{n} MB', - '{n} PB' => '{n} PB', - '{n} TB' => '{n} TB', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 gün} other{# gün}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 saat} other{# saat}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 dakika} other{# dakika}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 ay} other{# ay}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 saniye} other{# saniye}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 yıl} other{# yıl}}', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{bir gün} other{# gün}} önce', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{bir dakika} other{# dakika}} önce', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{bir ay} other{# ay}} önce', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{bir saniye} other{# saniye}} önce', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{bir yıl} other{# yıl}} önce', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{bir saat} other{# saat}} önce', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{bayt} other{bayt}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibayt} other{gibibayt}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabayt} other{gigabayt}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibayt} other{kibibayt}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobayt} other{kilobayt}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibayt} other{mebibayt}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabayt} other{megabayt}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibayt} other{pebibayt}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabayt} other{petabayt}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibayt} other{tebibayt}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabayt} other{terabayt}}', ]; diff --git a/framework/messages/uk/yii.php b/framework/messages/uk/yii.php index fbe78e5e73..9ccbb1dc8b 100644 --- a/framework/messages/uk/yii.php +++ b/framework/messages/uk/yii.php @@ -17,9 +17,12 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'No help for unknown command "{command}".' => '@@Довідка недоступна для невідомої команди "{command}".@@', - 'No help for unknown sub-command "{command}".' => '@@Довідка недоступна для невідомої субкоманди "{command}".@@', - 'Unknown command "{command}".' => '@@Невідома команда "{command}".@@', + '{attribute} must be equal to "{compareValueOrAttribute}".' => 'Значення "{attribute}" повинно бути рівним "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => 'Значення "{attribute}" повинно бути більшим значення "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => 'Значення "{attribute}" повинно бути більшим або дорівнювати значенню "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => 'Значення "{attribute}" повинно бути меншим значення "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => 'Значення "{attribute}" повинно бути меншим або дорівнювати значенню "{compareValueOrAttribute}".', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Значення "{attribute}" не повинно бути рівним "{compareValueOrAttribute}".', '(not set)' => '(не задано)', 'An internal server error occurred.' => 'Виникла внутрішня помилка сервера.', 'Are you sure you want to delete this item?' => 'Ви впевнені, що хочете видалити цей елемент?', @@ -40,19 +43,19 @@ return [ 'Please upload a file.' => 'Будь ласка, завантажте файл.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показані {begin, number}-{end, number} із {totalCount, number} {totalCount, plural, one{запису} other{записів}}.', 'The file "{file}" is not an image.' => 'Файл "{file}" не є зображенням.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл "{file}" занадто великий. Розмір не повинен перевищувати {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл "{file}" занадто малий. Розмір повинен бути більше, ніж {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Файл "{file}" занадто великий. Розмір не повинен перевищувати {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Файл "{file}" занадто малий. Розмір повинен бути більше, ніж {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Невірний формат значення "{attribute}".', - 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл "{file}" занадто великий. Висота не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', - 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл "{file}" занадто великий. Ширина не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', - 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл "{file}" занадто малий. Висота повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', - 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл "{file}" занадто малий. Ширина повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Зображення "{file}" занадто велике. Висота не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Зображення "{file}" занадто велике. Ширина не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Зображення "{file}" занадто мале. Висота повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Зображення "{file}" занадто мале. Ширина повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', 'The requested view "{name}" was not found.' => 'Представлення "{name}" не знайдено.', 'The verification code is incorrect.' => 'Невірний код перевірки.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Всього {count, number} {count, plural, one{запис} few{записи} many{записів} other{записи}}.', 'Unable to verify your data submission.' => 'Не вдалося перевірити передані дані.', 'Unknown option: --{name}' => 'Невідома опція : --{name}', - 'Update' => 'Редагувати', + 'Update' => 'Оновити', 'View' => 'Переглянути', 'Yes' => 'Так', 'You are not allowed to perform this action.' => 'Вам не дозволено виконувати дану дію.', @@ -65,27 +68,34 @@ return [ 'in {delta, plural, =1{an hour} other{# hours}}' => 'через {delta, plural, =1{годину} one{# годину} few{# години} many{# годин} other{# години}}', 'just now' => 'саме зараз', 'the input value' => 'введене значення', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" вже зайнятий.', + '{attribute} "{value}" has already been taken.' => 'Значення «{value}» для «{attribute}» вже зайнято.', '{attribute} cannot be blank.' => 'Необхідно заповнити "{attribute}".', + '{attribute} contains wrong subnet mask.' => 'Значення «{attribute}» містить неправильну маску підмережі.', '{attribute} is invalid.' => 'Значення "{attribute}" не вірне.', '{attribute} is not a valid URL.' => 'Значення "{attribute}" не є правильним URL.', '{attribute} is not a valid email address.' => 'Значення "{attribute}" не є правильною email адресою.', + '{attribute} is not in the allowed range.' => 'Значення «{attribute}» не входить в список дозволених діапазонів адрес.', '{attribute} must be "{requiredValue}".' => 'Значення "{attribute}" має бути рівним "{requiredValue}".', '{attribute} must be a number.' => 'Значення "{attribute}" має бути числом.', - '{attribute} must be a string.' => 'Значення "{attribute}" має бути рядком.', + '{attribute} must be a string.' => 'Значення "{attribute}" має бути текстовим рядком.', + '{attribute} must be a valid IP address.' => 'Значення «{attribute}» повинно бути правильною IP адресою.', + '{attribute} must be an IP address with specified subnet.' => 'Значення «{attribute}» повинно бути IP адресою з підмережею.', '{attribute} must be an integer.' => 'Значення "{attribute}" має бути цілим числом.', '{attribute} must be either "{true}" or "{false}".' => 'Значення "{attribute}" має дорівнювати "{true}" або "{false}".', - '{attribute} must be greater than "{compareValue}".' => 'Значення "{attribute}" повинно бути більшим значення "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => 'Значення "{attribute}" повинно бути більшим або дорівнювати значенню "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => 'Значення "{attribute}" повинно бути меншим значення "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => 'Значення "{attribute}" повинно бути меншим або дорівнювати значенню "{compareValue}".', '{attribute} must be no greater than {max}.' => 'Значення "{attribute}" не повинно перевищувати {max}.', '{attribute} must be no less than {min}.' => 'Значення "{attribute}" має бути більшим {min}.', - '{attribute} must be repeated exactly.' => 'Значення "{attribute}" має бути повторене в точності.', - '{attribute} must not be equal to "{compareValue}".' => 'Значення "{attribute}" не повинно бути рівним "{compareValue}".', + '{attribute} must not be a subnet.' => 'Значення «{attribute}» не повинно бути підмережею.', + '{attribute} must not be an IPv4 address.' => 'Значення «{attribute}» не повинно бути IPv4 адресою.', + '{attribute} must not be an IPv6 address.' => 'Значення «{attribute}» не повинно бути IPv6 адресою.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значення "{attribute}" повинно містити мінімум {min, number} {min, plural, one{символ} few{символа} many{символів} other{символа}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значення "{attribute}" повинно містити максимум {max, number} {max, plural, one{символ} few{символа} many{символів} other{символа}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значення "{attribute}" повинно містити {length, number} {length, plural, one{символ} few{символа} many{символів} other{символа}}.', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# день} few{# дні} many{# днів} other{# днів}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# година} few{# години} many{# годин} other{# годин}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# хвилина} few{# хвилини} many{# хвилин} other{# хвилин}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# місяць} few{# місяця} many{# місяців} other{# місяців}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# секунда} few{# секунди} many{# секунд} other{# секунд}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# рік} few{# роки} many{# років} other{# років}}', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{день} few{# дні} many{# днів} other{# дні}} тому', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{хвилину} one{# хвилину} few{# хвилини} many{# хвилин} other{# хвилини}} тому', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{місяць} one{# місяць} few{# місяці} many{# місяців} other{# місяці}} тому', @@ -104,7 +114,7 @@ return [ '{nFormatted} TB' => '{nFormatted} Тб', '{nFormatted} TiB' => '{nFormatted} ТіБ', '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{байт} few{байта} many{байтів} other{байта}}', - '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{гібібайт} few{гібібайта} many{гібібайтів} other{гіибібайта}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{гібібайт} few{гібібайта} many{гібібайтів} other{гібібайта}}', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{гігабайт} few{гігабайта} many{гігабайтів} other{гігабайта}}', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{кібібайт} few{кібібайта} many{кібібайтів} other{кібібайта}}', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{кілобайт} few{кілобайта} many{кілобайтів} other{кілобайта}}', diff --git a/framework/messages/uz/yii.php b/framework/messages/uz/yii.php new file mode 100644 index 0000000000..81fde640a0 --- /dev/null +++ b/framework/messages/uz/yii.php @@ -0,0 +1,117 @@ + '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{bayt} few{bayt} many{baytlar} other{bayt}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{gibibayt} few{gibibayt} many{gibibayt} other{gibibayt}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{gigabayt} few{gigabayt} many{gigabayt} other{gigabayt}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{kibibayt} few{kibibayt} many{kibibayt} other{kibibayt}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{kilobayt} few{kilobayt} many{kilobayt} other{kilobayt}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, one{mebibayt} few{mebibayt} many{mebibayt} other{mebibayt}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, one{megabayt} few{megabayt} many{megabayt} other{megabayt}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, one{pebibayt} few{pebibayt} many{pebibayt} other{pebibayt}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{petabayt} few{petabayt} many{petabayt} other{petabayt}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{tebibayt} few{tebibayt} many{tebibayt} other{tebibayt}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{terabayt} few{terabayt} many{terabayt} other{terabayt}}', + '(not set)' => '(qiymatlanmagan)', + 'An internal server error occurred.' => 'Serverning ichki xatoligi yuz berdi.', + 'Are you sure you want to delete this item?' => 'Sizni ushbu elementni o`chirishga ishonchngiz komilmi?', + 'Delete' => 'O`chirish', + 'Error' => 'Xato', + 'File upload failed.' => 'Faylni yuklab bo`lmadi.', + 'Home' => 'Asosiy', + 'Invalid data received for parameter "{param}".' => '"{param}" parametr qiymati noto`g`ri.', + 'Login Required' => 'Kirish talab qilinadi.', + 'Missing required arguments: {params}' => 'Quyidagi zarur argumentlar mavjud emas: {params}', + 'Missing required parameters: {params}' => 'Quyidagi zarur parametrlar mavjud emas: {params}', + 'No' => 'Yo`q', + 'No help for unknown command "{command}".' => '"{command}" noaniq komanda uchun ma`lumotnoma mavjud emas.', + 'No help for unknown sub-command "{command}".' => '"{command}" noaniq qism komanda uchun ma`lumotnoma mavjud emas.', + 'No results found.' => 'Hech nima topilmadi.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Faqat quyidagi MIME-turidagi fayllarni yuklashga ruhsat berilgan: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Faqat quyidagi kengaytmali fayllarni yuklashga ruhsat berilgan: {extensions}.', + 'Page not found.' => 'Sahifa topilmadi.', + 'Please fix the following errors:' => 'Navbatdagi xatoliklarni to`g`rilang:', + 'Please upload a file.' => 'Faylni yuklang.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Namoyish etilayabdi {begin, number}-{end, number} ta yozuv {totalCount, number} tadan.', + 'The file "{file}" is not an image.' => '«{file}» fayl rasm emas.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => '«{file}» fayl juda katta. O`lcham {formattedLimit} oshishi kerak emas.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => '«{file}» fayl juda kichkina. O`lcham {formattedLimit} kam bo`lmasligi kerak.', + 'The format of {attribute} is invalid.' => '«{attribute}» qiymatning formati noto`g`ri.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» fayl juda katta. Balandlik {limit, number} {limit, plural, one{pikseldan} few{pikseldan} many{pikseldan} other{pikseldan}} oshmasligi kerak.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» fayl juda katta. Eni {limit, number} {limit, plural, one{pikseldan} few{pikseldan} many{pikseldan} other{pikseldan}} oshmasligi kerak.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» fayl juda kichkina. Balandlik {limit, number} {limit, plural, one{pikseldan} few{pikseldan} many{pikseldan} other{pikseldan}} kam bo`lmasligi kerak.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '«{file}» juda kichkina. Eni {limit, number} {limit, plural, one{pikseldan} few{pikseldan} many{pikseldan} other{pikseldan}} kam bo`lmasligi kerak.', + 'The requested view "{name}" was not found.' => 'So`ralayotgan "{name}" namoyish fayli topilmadi.', + 'The verification code is incorrect.' => 'Tekshiruv kodi noto`g`ri.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Jami {count, number} {count, plural, one{yozuv} few{yozuv} many{yozuv} other{yozuv}}.', + 'Unable to verify your data submission.' => 'Yuborilgan ma`lumotlarni tekshirib bo`lmadi.', + 'Unknown command "{command}".' => '"{command}" noaniq komanda.', + 'Unknown option: --{name}' => 'Noaniq opsiya: --{name}', + 'Update' => 'Tahrirlash', + 'View' => 'Ko`rish', + 'Yes' => 'Ha', + 'You are not allowed to perform this action.' => 'Sizga ushbu amalni bajarishga ruhsat berilmagan.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Siz {limit, number} {limit, plural, one{fayldan} few{fayllardan} many{fayllardan} other{fayllardan}} ko`pini yuklab ola olmaysiz.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta, plural, =1{kundan} one{# kundan} few{# kundan} many{# kunlardan} other{# kundan}} keyin', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta, plural, =1{minutdan} one{# minutdan} few{# minutlardan} many{# minutdan} other{# minutlardan}} keyin', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta, plural, =1{oydan} one{# oydan} few{# oydan} many{# oylardan} other{# oylardan}} keyin', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta, plural, =1{sekunddan} one{# sekunddan} few{# sekundlardan} many{# sekunddan} other{# sekundlardan}} keyin', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta, plural, =1{yildan} one{# yildan} few{# yillardan} many{# yillardan} other{# yillardan}} keyin', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta, plural, =1{soatdan} one{# soatdan} few{# soatlardan} many{# soatlardan} other{# soatlardan}} keyin', + 'just now' => 'xoziroq', + 'the input value' => 'kiritilgan qiymat', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» avvalroq band qilingan.', + '{attribute} cannot be blank.' => '«{attribute}» to`ldirish shart.', + '{attribute} is invalid.' => '«{attribute}» qiymati noto`g`ri.', + '{attribute} is not a valid URL.' => '«{attribute}» qiymati to`g`ri URL emas.', + '{attribute} is not a valid email address.' => '«{attribute}» qiymati to`g`ri email manzil emas.', + '{attribute} must be "{requiredValue}".' => '«{attribute}» qiymati «{requiredValue}» ga teng bo`lishi kerak.', + '{attribute} must be a number.' => '«{attribute}» qiymati son bo`lishi kerak.', + '{attribute} must be a string.' => '«{attribute}» qiymati satr bo`lishi kerak.', + '{attribute} must be an integer.' => '«{attribute}» qiymati butun son bo`lishi kerak.', + '{attribute} must be either "{true}" or "{false}".' => '«{attribute}» qiymati «{true}» yoki «{false}» bo`lishi kerak.', + '{attribute} must be greater than "{compareValue}".' => '«{attribute}» qiymati «{compareValue}» dan katta bo`lishi kerak.', + '{attribute} must be greater than or equal to "{compareValue}".' => '«{attribute}» qiymati «{compareValue}» dan katta yoki teng bo`lishi kerak.', + '{attribute} must be less than "{compareValue}".' => '«{attribute}» qiymati «{compareValue}» dan kichkina bo`lishi kerak.', + '{attribute} must be less than or equal to "{compareValue}".' => '«{attribute}» qiymati «{compareValue}» dan kichik yoki teng bo`lishi kerak.', + '{attribute} must be no greater than {max}.' => '«{attribute}» qiymati {max} dan oshmasligi kerak.', + '{attribute} must be no less than {min}.' => '«{attribute}» qiymati {min} dan kichkina bo`lishi kerak.', + '{attribute} must be repeated exactly.' => '«{attribute}» qiymati bir xil tarzda takrorlanishi kerak.', + '{attribute} must not be equal to "{compareValue}".' => '«{attribute}» qiymati «{compareValue}» ga teng bo`lmasligi kerak.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '«{attribute}» qiymati minimum {min, number} {min, plural, one{belgidan} few{belgidan} many{belgidan} other{belgidan}} tashkil topishi kerak.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '«{attribute}» qiymati maksimum {max, number} {max, plural, one{belgidan} few{belgidan} many{belgidan} other{belgidan}} oshmasligi kerak.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '«{attribute}» qiymati {length, number} {length, plural, one{belgidan} few{belgidan} many{belgidan} other{belgidan}} tashkil topishi kerak.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{kun} one{kun} few{# kun} many{# kun} other{# kun}} avval', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minut} one{# minut} few{# minutlar} many{# minutlar} other{# minutlar}} avval', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{oy} one{# oy} few{# oylar} many{# oylar} other{# oylar}} avval', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{sekund} one{# sekundlar} few{# sekundlar} many{# sekundlar} other{# sekundlar}} avval', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{yil} one{# yil} few{# yillar} many{# yillar} other{# yillar}} avval', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{soat} one{# soat} few{# soatlar} many{# soatlar} other{# soatlar}} avval', +]; diff --git a/framework/messages/vi/yii.php b/framework/messages/vi/yii.php index 94373c27ab..17d6e67cb6 100644 --- a/framework/messages/vi/yii.php +++ b/framework/messages/vi/yii.php @@ -39,8 +39,8 @@ return [ 'Please upload a file.' => 'Hãy tải file lên.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Trình bày {begin, number}-{end, number} trong số {totalCount, number} mục.', 'The file "{file}" is not an image.' => 'File "{file}" phải là một ảnh.', - 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'File "{file}" quá lớn. Kích cỡ tối đa được phép tải lên là {limit, number} byte.', - 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'File "{file}" quá nhỏ. Kích cỡ tối thiểu được phép tải lên là {limit, number} byte.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'File "{file}" quá lớn. Kích cỡ tối đa được phép tải lên là {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'File "{file}" quá nhỏ. Kích cỡ tối thiểu được phép tải lên là {formattedLimit}.', 'The format of {attribute} is invalid.' => 'Định dạng của {attribute} không hợp lệ.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'File "{file}" có kích thước quá lớn. Chiều cao tối đa được phép là {limit, number} pixel.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ảnh "{file}" có kích thước quá lớn. Chiều rộng tối đa được phép là {limit, number} pixel.', diff --git a/framework/messages/zh-CN/yii.php b/framework/messages/zh-CN/yii.php index 3200ccd33c..55ffec1f82 100644 --- a/framework/messages/zh-CN/yii.php +++ b/framework/messages/zh-CN/yii.php @@ -66,14 +66,15 @@ return [ '{attribute} must be a string.' => '{attribute}必须是一条字符串。', '{attribute} must be an integer.' => '{attribute}必须是整数。', '{attribute} must be either "{true}" or "{false}".' => '{attribute}的值必须要么为"{true}",要么为"{false}"。', - '{attribute} must be greater than "{compareValue}".' => '{attribute}的值必须大于"{compareValue}"。', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}的值必须大于或等于"{compareValue}"。', - '{attribute} must be less than "{compareValue}".' => '{attribute}的值必须小于"{compareValue}"。', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}的值必须小于或等于"{compareValue}"。', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute}的值必须等于"{compareValueOrAttribute}"。', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute}的值不得等于"{compareValueOrAttribute}"。', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute}的值必须大于"{compareValueOrAttribute}"。', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute}的值必须大于或等于"{compareValueOrAttribute}"。', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute}的值必须小于"{compareValueOrAttribute}"。', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute}的值必须小于或等于"{compareValueOrAttribute}"。', '{attribute} must be no greater than {max}.' => '{attribute}的值必须不大于{max}。', '{attribute} must be no less than {min}.' => '{attribute}的值必须不小于{min}。', '{attribute} must be repeated exactly.' => '{attribute}必须重复。', - '{attribute} must not be equal to "{compareValue}".' => '{attribute}的值不得等于"{compareValue}"。', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}应该包含至少{min, number}个字符。', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}只能包含至多{max, number}个字符。', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}应该包含{length, number}个字符。', diff --git a/framework/messages/zh-TW/yii.php b/framework/messages/zh-TW/yii.php index b4cbc5094a..36848465c4 100644 --- a/framework/messages/zh-TW/yii.php +++ b/framework/messages/zh-TW/yii.php @@ -77,4 +77,17 @@ return [ '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} 應該包含至少 {min, number} 個字符。', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} 只能包含最多 {max, number} 個字符。', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} 應該包含 {length, number} 個字符。', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta}年後', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta}個月後', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta}天後', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta}小時後', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta}分鐘後', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta}秒後', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta}年前', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta}個月前', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta}天前', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta}小時前', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta}分鐘前', + 'just now' => '剛剛', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta}秒前', ]; diff --git a/framework/mutex/FileMutex.php b/framework/mutex/FileMutex.php index b58fe344c0..7619a4ddee 100644 --- a/framework/mutex/FileMutex.php +++ b/framework/mutex/FileMutex.php @@ -20,7 +20,7 @@ use yii\helpers\FileHelper; * ``` * [ * 'components' => [ - * 'mutex'=> [ + * 'mutex' => [ * 'class' => 'yii\mutex\FileMutex' * ], * ], diff --git a/framework/mutex/Mutex.php b/framework/mutex/Mutex.php index 31e94b1b73..b868bd7e4b 100644 --- a/framework/mutex/Mutex.php +++ b/framework/mutex/Mutex.php @@ -11,9 +11,9 @@ use Yii; use yii\base\Component; /** - * Mutex component allows mutual execution of the concurrent processes, preventing "race conditions". - * This is achieved by using "lock" mechanism. Each possibly concurrent thread cooperates by acquiring - * the lock before accessing the corresponding data. + * The Mutex component allows mutual execution of concurrent processes in order to prevent "race conditions". + * This is achieved by using a "lock" mechanism. Each possibly concurrent thread cooperates by acquiring + * a lock before accessing the corresponding data. * * Usage example: * @@ -25,7 +25,7 @@ use yii\base\Component; * } * ``` * - * This class is a base one, which should be extended in order to implement actual lock mechanism. + * This is a base class, which should be extended in order to implement the actual lock mechanism. * * @author resurtm * @since 2.0 @@ -33,20 +33,20 @@ use yii\base\Component; abstract class Mutex extends Component { /** - * @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically + * @var boolean whether all locks acquired in this process (i.e. local locks) must be released automatically * before finishing script execution. Defaults to true. Setting this property to true means that all locks - * acquire in this process must be released in any case (regardless any kind of errors or exceptions). + * acquired in this process must be released (regardless of errors or exceptions). */ public $autoRelease = true; /** - * @var string[] names of the locks acquired in the current PHP process. + * @var string[] names of the locks acquired by the current PHP process. */ private $_locks = []; /** - * Initializes the mutex component. + * Initializes the Mutex component. */ public function init() { @@ -61,9 +61,9 @@ abstract class Mutex extends Component } /** - * Acquires lock by given name. + * Acquires a lock by name. * @param string $name of the lock to be acquired. Must be unique. - * @param integer $timeout to wait for lock to be released. Defaults to zero meaning that method will return + * @param integer $timeout time to wait for lock to be released. Defaults to zero meaning that method will return * false immediately in case lock was already acquired. * @return boolean lock acquiring result. */ @@ -79,8 +79,8 @@ abstract class Mutex extends Component } /** - * Release acquired lock. This method will return false in case named lock was not found. - * @param string $name of the lock to be released. This lock must be already created. + * Releases acquired lock. This method will return false in case the lock was not found. + * @param string $name of the lock to be released. This lock must already exist. * @return boolean lock release result: false in case named lock was not found.. */ public function release($name) @@ -98,15 +98,15 @@ abstract class Mutex extends Component } /** - * This method should be extended by concrete mutex implementations. Acquires lock by given name. + * This method should be extended by a concrete Mutex implementations. Acquires lock by name. * @param string $name of the lock to be acquired. - * @param integer $timeout to wait for lock to become released. + * @param integer $timeout time to wait for the lock to be released. * @return boolean acquiring result. */ abstract protected function acquireLock($name, $timeout = 0); /** - * This method should be extended by concrete mutex implementations. Releases lock by given name. + * This method should be extended by a concrete Mutex implementations. Releases lock by given name. * @param string $name of the lock to be released. * @return boolean release result. */ diff --git a/framework/mutex/MysqlMutex.php b/framework/mutex/MysqlMutex.php index 6c45e250e7..e47a71b9de 100644 --- a/framework/mutex/MysqlMutex.php +++ b/framework/mutex/MysqlMutex.php @@ -18,11 +18,11 @@ use yii\base\InvalidConfigException; * ``` * [ * 'components' => [ - * 'db'=> [ + * 'db' => [ * 'class' => 'yii\db\Connection', * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', * ] - * 'mutex'=> [ + * 'mutex' => [ * 'class' => 'yii\mutex\MysqlMutex', * ], * ], diff --git a/framework/mutex/PgsqlMutex.php b/framework/mutex/PgsqlMutex.php new file mode 100644 index 0000000000..aa0d9be572 --- /dev/null +++ b/framework/mutex/PgsqlMutex.php @@ -0,0 +1,93 @@ + [ + * 'db' => [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'pgsql:host=127.0.0.1;dbname=demo', + * ] + * 'mutex' => [ + * 'class' => 'yii\mutex\PgsqlMutex', + * ], + * ], + * ] + * ``` + * + * @see Mutex + * + * @author nineinchnick + * @since 2.0.8 + */ +class PgsqlMutex extends DbMutex +{ + /** + * Initializes PgSQL specific mutex component implementation. + * @throws InvalidConfigException if [[db]] is not PgSQL connection. + */ + public function init() + { + parent::init(); + if ($this->db->driverName !== 'pgsql') { + throw new InvalidConfigException('In order to use PgsqlMutex connection must be configured to use PgSQL database.'); + } + } + + /** + * Converts a string into two 16 bit integer keys using the SHA1 hash function. + * @param string $name + * @return array contains two 16 bit integer keys + */ + private function getKeysFromName($name) + { + return array_values(unpack('n2', sha1($name, true))); + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + * @see http://www.postgresql.org/docs/9.0/static/functions-admin.html + */ + protected function acquireLock($name, $timeout = 0) + { + if ($timeout !== 0) { + throw new InvalidParamException('PgsqlMutex does not support timeout.'); + } + list($key1, $key2) = $this->getKeysFromName($name); + return (bool) $this->db + ->createCommand('SELECT pg_try_advisory_lock(:key1, :key2)', [':key1' => $key1, ':key2' => $key2]) + ->queryScalar(); + } + + /** + * Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + * @see http://www.postgresql.org/docs/9.0/static/functions-admin.html + */ + protected function releaseLock($name) + { + list($key1, $key2) = $this->getKeysFromName($name); + return (bool) $this->db + ->createCommand('SELECT pg_advisory_unlock(:key1, :key2)', [':key1' => $key1, ':key2' => $key2]) + ->queryScalar(); + } +} diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php index 5a13547236..728aa19677 100644 --- a/framework/rbac/Assignment.php +++ b/framework/rbac/Assignment.php @@ -24,7 +24,7 @@ class Assignment extends Object */ public $userId; /** - * @return string the role name + * @var string the role name */ public $roleName; /** diff --git a/framework/rbac/BaseManager.php b/framework/rbac/BaseManager.php index 04941e17b4..e1302f0770 100644 --- a/framework/rbac/BaseManager.php +++ b/framework/rbac/BaseManager.php @@ -41,11 +41,15 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Adds an auth item to the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param Item $item ======= * @param Item $item the item to add >>>>>>> yiichina/master +======= + * @param Item $item the item to add +>>>>>>> master * @return boolean whether the auth item is successfully added to the system * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) */ @@ -53,11 +57,15 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Adds a rule to the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param Rule $rule ======= * @param Rule $rule the rule to add >>>>>>> yiichina/master +======= + * @param Rule $rule the rule to add +>>>>>>> master * @return boolean whether the rule is successfully added to the system * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) */ @@ -65,11 +73,15 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Removes an auth item from the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param Item $item ======= * @param Item $item the item to remove >>>>>>> yiichina/master +======= + * @param Item $item the item to remove +>>>>>>> master * @return boolean whether the role or permission is successfully removed * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) */ @@ -77,11 +89,15 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Removes a rule from the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param Rule $rule ======= * @param Rule $rule the rule to remove >>>>>>> yiichina/master +======= + * @param Rule $rule the rule to remove +>>>>>>> master * @return boolean whether the rule is successfully removed * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) */ @@ -89,6 +105,7 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Updates an auth item in the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param string $name the old name of the auth item * @param Item $item @@ -96,6 +113,10 @@ abstract class BaseManager extends Component implements ManagerInterface * @param string $name the name of the item being updated * @param Item $item the updated item >>>>>>> yiichina/master +======= + * @param string $name the name of the item being updated + * @param Item $item the updated item +>>>>>>> master * @return boolean whether the auth item is successfully updated * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) */ @@ -103,6 +124,7 @@ abstract class BaseManager extends Component implements ManagerInterface /** * Updates a rule to the RBAC system. +<<<<<<< HEAD <<<<<<< HEAD * @param string $name the old name of the rule * @param Rule $rule @@ -110,6 +132,10 @@ abstract class BaseManager extends Component implements ManagerInterface * @param string $name the name of the rule being updated * @param Rule $rule the updated rule >>>>>>> yiichina/master +======= + * @param string $name the name of the rule being updated + * @param Rule $rule the updated rule +>>>>>>> master * @return boolean whether the rule is successfully updated * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) */ @@ -120,7 +146,7 @@ abstract class BaseManager extends Component implements ManagerInterface */ public function createRole($name) { - $role = new Role; + $role = new Role(); $role->name = $name; return $role; } @@ -141,11 +167,16 @@ abstract class BaseManager extends Component implements ManagerInterface public function add($object) { if ($object instanceof Item) { + if ($object->ruleName && $this->getRule($object->ruleName) === null) { + $rule = \Yii::createObject($object->ruleName); + $rule->name = $object->ruleName; + $this->addRule($rule); + } return $this->addItem($object); } elseif ($object instanceof Rule) { return $this->addRule($object); } else { - throw new InvalidParamException("Adding unsupported object type."); + throw new InvalidParamException('Adding unsupported object type.'); } } @@ -159,7 +190,7 @@ abstract class BaseManager extends Component implements ManagerInterface } elseif ($object instanceof Rule) { return $this->removeRule($object); } else { - throw new InvalidParamException("Removing unsupported object type."); + throw new InvalidParamException('Removing unsupported object type.'); } } @@ -169,11 +200,16 @@ abstract class BaseManager extends Component implements ManagerInterface public function update($name, $object) { if ($object instanceof Item) { + if ($object->ruleName && $this->getRule($object->ruleName) === null) { + $rule = \Yii::createObject($object->ruleName); + $rule->name = $object->ruleName; + $this->addRule($rule); + } return $this->updateItem($name, $object); } elseif ($object instanceof Rule) { return $this->updateRule($name, $object); } else { - throw new InvalidParamException("Updating unsupported object type."); + throw new InvalidParamException('Updating unsupported object type.'); } } @@ -220,7 +256,7 @@ abstract class BaseManager extends Component implements ManagerInterface * @param string|integer $user the user ID. This should be either an integer or a string representing * the unique identifier of a user. See [[\yii\web\User::id]]. * @param Item $item the auth item that needs to execute its rule - * @param array $params parameters passed to [[ManagerInterface::checkAccess()]] and will be passed to the rule + * @param array $params parameters passed to [[CheckAccessInterface::checkAccess()]] and will be passed to the rule * @return boolean the return value of [[Rule::execute()]]. If the auth item does not specify a rule, true will be returned. * @throws InvalidConfigException if the auth item has an invalid rule. */ diff --git a/framework/rbac/CheckAccessInterface.php b/framework/rbac/CheckAccessInterface.php new file mode 100644 index 0000000000..d67c89e90c --- /dev/null +++ b/framework/rbac/CheckAccessInterface.php @@ -0,0 +1,21 @@ + + * @since 2.0.9 + */ +interface CheckAccessInterface +{ + /** + * Checks if the user has the specified permission. + * @param string|integer $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param string $permissionName the name of the permission to be checked against + * @param array $params name-value pairs that will be passed to the rules associated + * with the roles and permissions assigned to the user. + * @return boolean whether the user has the specified permission. + * @throws \yii\base\InvalidParamException if $permissionName does not refer to an existing permission + */ + public function checkAccess($userId, $permissionName, $params = []); +} diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 37ae149701..270de47789 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -27,6 +27,7 @@ use yii\di\Instance; * * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory. * +<<<<<<< HEAD <<<<<<< HEAD * You may change the names of the three tables used to store the authorization data by setting [[itemTable]], * [[itemChildTable]] and [[assignmentTable]]. @@ -34,6 +35,10 @@ use yii\di\Instance; * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]], * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]]. >>>>>>> yiichina/master +======= + * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]], + * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]]. +>>>>>>> master * * @author Qiang Xue * @author Alexander Kochetov @@ -65,15 +70,19 @@ class DbManager extends BaseManager */ public $ruleTable = '{{%auth_rule}}'; /** +<<<<<<< HEAD <<<<<<< HEAD * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the followings: ======= * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the following: >>>>>>> yiichina/master +======= + * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the following: +>>>>>>> master * * - an application component ID (e.g. `cache`) * - a configuration array - * - a [[yii\caching\Cache]] object + * - a [[\yii\caching\Cache]] object * * When this is not set, it means caching is not enabled. * @@ -94,6 +103,7 @@ class DbManager extends BaseManager * @since 2.0.3 */ public $cacheKey = 'rbac'; + /** * @var Item[] all auth items (name => Item) */ @@ -107,6 +117,7 @@ class DbManager extends BaseManager */ protected $parents; + /** * Initializes the application component. * This method overrides the parent implementation by establishing the database connection. @@ -167,7 +178,7 @@ class DbManager extends BaseManager if (!empty($this->parents[$itemName])) { foreach ($this->parents[$itemName] as $parent) { - if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) { + if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) { return true; } } @@ -223,6 +234,10 @@ class DbManager extends BaseManager */ protected function getItem($name) { + if (empty($name)) { + return null; + } + if (!empty($this->items[$name])) { return $this->items[$name]; } @@ -456,19 +471,24 @@ class DbManager extends BaseManager */ public function getRolesByUser($userId) { - if (empty($userId)) { + if (!isset($userId) || $userId === '') { return []; } $query = (new Query)->select('b.*') ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable]) ->where('{{a}}.[[item_name]]={{b}}.[[name]]') +<<<<<<< HEAD <<<<<<< HEAD ->andWhere(['a.user_id' => (string) $userId]); ======= ->andWhere(['a.user_id' => (string) $userId]) ->andWhere(['b.type' => Item::TYPE_ROLE]); >>>>>>> yiichina/master +======= + ->andWhere(['a.user_id' => (string) $userId]) + ->andWhere(['b.type' => Item::TYPE_ROLE]); +>>>>>>> master $roles = []; foreach ($query->all($this->db) as $row) { @@ -508,6 +528,41 @@ class DbManager extends BaseManager return []; } + $directPermission = $this->getDirectPermissionsByUser($userId); + $inheritedPermission = $this->getInheritedPermissionsByUser($userId); + + return array_merge($directPermission, $inheritedPermission); + } + + /** + * Returns all permissions that are directly assigned to user. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names. + * @since 2.0.7 + */ + protected function getDirectPermissionsByUser($userId) + { + $query = (new Query)->select('b.*') + ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable]) + ->where('{{a}}.[[item_name]]={{b}}.[[name]]') + ->andWhere(['a.user_id' => (string) $userId]) + ->andWhere(['b.type' => Item::TYPE_PERMISSION]); + + $permissions = []; + foreach ($query->all($this->db) as $row) { + $permissions[$row['name']] = $this->populateItem($row); + } + return $permissions; + } + + /** + * Returns all permissions that the user inherits from the roles assigned to him. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names. + * @since 2.0.7 + */ + protected function getInheritedPermissionsByUser($userId) + { $query = (new Query)->select('item_name') ->from($this->assignmentTable) ->where(['user_id' => (string) $userId]); @@ -648,6 +703,15 @@ class DbManager extends BaseManager return $assignments; } + /** + * @inheritdoc + * @since 2.0.8 + */ + public function canAddChild($parent, $child) + { + return !$this->detectLoop($parent, $child); + } + /** * @inheritdoc */ @@ -658,7 +722,7 @@ class DbManager extends BaseManager } if ($parent instanceof Permission && $child instanceof Role) { - throw new InvalidParamException("Cannot add a role as a child of a permission."); + throw new InvalidParamException('Cannot add a role as a child of a permission.'); } if ($this->detectLoop($parent, $child)) { @@ -864,7 +928,7 @@ class DbManager extends BaseManager { if (!$this->supportsCascadeUpdate()) { $this->db->createCommand() - ->update($this->itemTable, ['ruleName' => null]) + ->update($this->itemTable, ['rule_name' => null]) ->execute(); } @@ -925,4 +989,22 @@ class DbManager extends BaseManager $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]); } + + /** + * Returns all role assignment information for the specified role. + * @param string $roleName + * @return Assignment[] the assignments. An empty array will be + * returned if role is not assigned to any user. + * @since 2.0.7 + */ + public function getUserIdsByRole($roleName) + { + if (empty($roleName)) { + return []; + } + + return (new Query)->select('[[user_id]]') + ->from($this->assignmentTable) + ->where(['item_name' => $roleName])->column($this->db); + } } diff --git a/framework/rbac/ManagerInterface.php b/framework/rbac/ManagerInterface.php index 2356773e63..fa44d8957f 100644 --- a/framework/rbac/ManagerInterface.php +++ b/framework/rbac/ManagerInterface.php @@ -11,20 +11,8 @@ namespace yii\rbac; * @author Qiang Xue * @since 2.0 */ -interface ManagerInterface +interface ManagerInterface extends CheckAccessInterface { - /** - * Checks if the user has the specified permission. - * @param string|integer $userId the user ID. This should be either an integer or a string representing - * the unique identifier of a user. See [[\yii\web\User::id]]. - * @param string $permissionName the name of the permission to be checked against - * @param array $params name-value pairs that will be passed to the rules associated - * with the roles and permissions assigned to the user. - * @return boolean whether the user has the specified permission. - * @throws \yii\base\InvalidParamException if $permissionName does not refer to an existing permission - */ - public function checkAccess($userId, $permissionName, $params = []); - /** * Creates a new Role object. * Note that the newly created role is not added to the RBAC system yet. @@ -70,11 +58,15 @@ interface ManagerInterface /** * Returns the named role. * @param string $name the role name. +<<<<<<< HEAD <<<<<<< HEAD * @return Role the role corresponding to the specified name. Null is returned if no such role. ======= * @return null|Role the role corresponding to the specified name. Null is returned if no such role. >>>>>>> yiichina/master +======= + * @return null|Role the role corresponding to the specified name. Null is returned if no such role. +>>>>>>> master */ public function getRole($name); @@ -88,18 +80,22 @@ interface ManagerInterface * Returns the roles that are assigned to the user via [[assign()]]. * Note that child roles that are not assigned directly to the user will not be returned. * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) - * @return Role[] all roles directly or indirectly assigned to the user. The array is indexed by the role names. + * @return Role[] all roles directly assigned to the user. The array is indexed by the role names. */ public function getRolesByUser($userId); /** * Returns the named permission. * @param string $name the permission name. +<<<<<<< HEAD <<<<<<< HEAD * @return Permission the permission corresponding to the specified name. Null is returned if no such permission. ======= * @return null|Permission the permission corresponding to the specified name. Null is returned if no such permission. >>>>>>> yiichina/master +======= + * @return null|Permission the permission corresponding to the specified name. Null is returned if no such permission. +>>>>>>> master */ public function getPermission($name); @@ -126,11 +122,15 @@ interface ManagerInterface /** * Returns the rule of the specified name. * @param string $name the rule name +<<<<<<< HEAD <<<<<<< HEAD * @return Rule the rule object, or null if the specified name does not correspond to a rule. ======= * @return null|Rule the rule object, or null if the specified name does not correspond to a rule. >>>>>>> yiichina/master +======= + * @return null|Rule the rule object, or null if the specified name does not correspond to a rule. +>>>>>>> master */ public function getRule($name); @@ -140,10 +140,21 @@ interface ManagerInterface */ public function getRules(); + /** + * Checks the possibility of adding a child to parent + * @param Item $parent the parent item + * @param Item $child the child item to be added to the hierarchy + * @return boolean possibility of adding + * + * @since 2.0.8 + */ + public function canAddChild($parent, $child); + /** * Adds an item as a child of another item. * @param Item $parent * @param Item $child + * @return boolean whether the child successfully added * @throws \yii\base\Exception if the parent-child relationship already exists or if a loop has been detected. */ public function addChild($parent, $child); @@ -207,13 +218,17 @@ interface ManagerInterface /** * Returns the assignment information regarding a role and a user. - * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) * @param string $roleName the role name +<<<<<<< HEAD <<<<<<< HEAD * @return Assignment the assignment information. Null is returned if ======= * @return null|Assignment the assignment information. Null is returned if >>>>>>> yiichina/master +======= + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return null|Assignment the assignment information. Null is returned if +>>>>>>> master * the role is not assigned to the user. */ public function getAssignment($roleName, $userId); @@ -226,6 +241,14 @@ interface ManagerInterface */ public function getAssignments($userId); + /** + * Returns all user IDs assigned to the role specified. + * @param string $roleName + * @return array array of user ID strings + * @since 2.0.7 + */ + public function getUserIdsByRole($roleName); + /** * Removes all authorization data, including roles, permissions, rules, and assignments. */ diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 594fd7f298..167bf5031c 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -148,6 +148,15 @@ class PhpManager extends BaseManager return false; } + /** + * @inheritdoc + * @since 2.0.8 + */ + public function canAddChild($parent, $child) + { + return !$this->detectLoop($parent, $child); + } + /** * @inheritdoc */ @@ -157,11 +166,11 @@ class PhpManager extends BaseManager throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist."); } - if ($parent->name == $child->name) { + if ($parent->name === $child->name) { throw new InvalidParamException("Cannot add '{$parent->name} ' as a child of itself."); } if ($parent instanceof Permission && $child instanceof Role) { - throw new InvalidParamException("Cannot add a role as a child of a permission."); + throw new InvalidParamException('Cannot add a role as a child of a permission.'); } if ($this->detectLoop($parent, $child)) { @@ -327,6 +336,7 @@ class PhpManager extends BaseManager } unset($this->items[$item->name]); $this->saveItems(); + $this->saveAssignments(); return true; } else { return false; @@ -377,14 +387,20 @@ class PhpManager extends BaseManager { $roles = []; foreach ($this->getAssignments($userId) as $name => $assignment) { +<<<<<<< HEAD <<<<<<< HEAD $roles[$name] = $this->items[$assignment->roleName]; ======= +======= +>>>>>>> master $role = $this->items[$assignment->roleName]; if ($role->type === Item::TYPE_ROLE) { $roles[$name] = $role; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } return $roles; @@ -429,6 +445,39 @@ class PhpManager extends BaseManager * @inheritdoc */ public function getPermissionsByUser($userId) + { + $directPermission = $this->getDirectPermissionsByUser($userId); + $inheritedPermission = $this->getInheritedPermissionsByUser($userId); + + return array_merge($directPermission, $inheritedPermission); + } + + /** + * Returns all permissions that are directly assigned to user. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names. + * @since 2.0.7 + */ + protected function getDirectPermissionsByUser($userId) + { + $permissions = []; + foreach ($this->getAssignments($userId) as $name => $assignment) { + $permission = $this->items[$assignment->roleName]; + if ($permission->type === Item::TYPE_PERMISSION) { + $permissions[$name] = $permission; + } + } + + return $permissions; + } + + /** + * Returns all permissions that the user inherits from the roles assigned to him. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names. + * @since 2.0.7 + */ + protected function getInheritedPermissionsByUser($userId) { $assignments = $this->getAssignments($userId); $result = []; @@ -502,9 +551,11 @@ class PhpManager extends BaseManager return; } - foreach ($this->assignments as $i => $assignment) { - if (isset($names[$assignment->roleName])) { - unset($this->assignments[$i]); + foreach ($this->assignments as $i => $assignments) { + foreach ($assignments as $n => $assignment) { + if (isset($names[$assignment->roleName])) { + unset($this->assignments[$i][$n]); + } } } foreach ($this->children as $name => $children) { @@ -578,17 +629,21 @@ class PhpManager extends BaseManager */ protected function updateItem($name, $item) { +<<<<<<< HEAD <<<<<<< HEAD $this->items[$item->name] = $item; +======= +>>>>>>> master if ($name !== $item->name) { if (isset($this->items[$item->name])) { throw new InvalidParamException("Unable to change the item name. The name '{$item->name}' is already used by another item."); - } - if (isset($this->items[$name])) { - unset ($this->items[$name]); + } else { + // Remove old item in case of renaming + unset($this->items[$name]); if (isset($this->children[$name])) { $this->children[$item->name] = $this->children[$name]; +<<<<<<< HEAD unset ($this->children[$name]); ======= if ($name !== $item->name) { @@ -602,31 +657,46 @@ class PhpManager extends BaseManager $this->children[$item->name] = $this->children[$name]; unset($this->children[$name]); >>>>>>> yiichina/master +======= + unset($this->children[$name]); +>>>>>>> master } foreach ($this->children as &$children) { if (isset($children[$name])) { $children[$item->name] = $children[$name]; +<<<<<<< HEAD <<<<<<< HEAD unset ($children[$name]); ======= unset($children[$name]); >>>>>>> yiichina/master +======= + unset($children[$name]); +>>>>>>> master } } foreach ($this->assignments as &$assignments) { if (isset($assignments[$name])) { $assignments[$item->name] = $assignments[$name]; + $assignments[$item->name]->roleName = $item->name; unset($assignments[$name]); } } + $this->saveAssignments(); } } <<<<<<< HEAD +<<<<<<< HEAD ======= $this->items[$item->name] = $item; >>>>>>> yiichina/master +======= + + $this->items[$item->name] = $item; + +>>>>>>> master $this->saveItems(); return true; } @@ -742,6 +812,22 @@ class PhpManager extends BaseManager protected function saveToFile($data, $file) { file_put_contents($file, "invalidateScriptCache($file); + } + + /** + * Invalidates precompiled script cache (such as OPCache or APC) for the given file. + * @param string $file the file path. + * @since 2.0.9 + */ + protected function invalidateScriptCache($file) + { + if (function_exists('opcache_invalidate')) { + opcache_invalidate($file, true); + } + if (function_exists('apc_delete_file')) { + @apc_delete_file($file); + } } /** @@ -796,4 +882,21 @@ class PhpManager extends BaseManager } $this->saveToFile($rules, $this->ruleFile); } + + /** + * @inheritdoc + * @since 2.0.7 + */ + public function getUserIdsByRole($roleName) + { + $result = []; + foreach ($this->assignments as $userID => $assignments) { + foreach ($assignments as $userAssignment) { + if ($userAssignment->roleName === $roleName && $userAssignment->userId == $userID) { + $result[] = (string)$userID; + } + } + } + return $result; + } } diff --git a/framework/rbac/Rule.php b/framework/rbac/Rule.php index 55936ff94c..9fe6a33718 100644 --- a/framework/rbac/Rule.php +++ b/framework/rbac/Rule.php @@ -37,7 +37,7 @@ abstract class Rule extends Object * @param string|integer $user the user ID. This should be either an integer or a string representing * the unique identifier of a user. See [[\yii\web\User::id]]. * @param Item $item the role or permission that this rule is associated with - * @param array $params parameters passed to [[ManagerInterface::checkAccess()]]. + * @param array $params parameters passed to [[CheckAccessInterface::checkAccess()]]. * @return boolean a value indicating whether the rule permits the auth item it is associated with. */ abstract public function execute($user, $item, $params); diff --git a/framework/rbac/migrations/m140506_102106_rbac_init.php b/framework/rbac/migrations/m140506_102106_rbac_init.php index b5cda499ff..886ec098db 100644 --- a/framework/rbac/migrations/m140506_102106_rbac_init.php +++ b/framework/rbac/migrations/m140506_102106_rbac_init.php @@ -6,7 +6,6 @@ */ use yii\base\InvalidConfigException; -use yii\db\Schema; use yii\rbac\DbManager; /** @@ -30,6 +29,17 @@ class m140506_102106_rbac_init extends \yii\db\Migration return $authManager; } + /** + * @return bool + */ + protected function isMSSQL() + { + return $this->db->driverName === 'mssql' || $this->db->driverName === 'sqlsrv' || $this->db->driverName === 'dblib'; + } + + /** + * @inheritdoc + */ public function up() { $authManager = $this->getAuthManager(); @@ -42,51 +52,98 @@ class m140506_102106_rbac_init extends \yii\db\Migration } $this->createTable($authManager->ruleTable, [ - 'name' => Schema::TYPE_STRING . '(64) NOT NULL', - 'data' => Schema::TYPE_TEXT, - 'created_at' => Schema::TYPE_INTEGER, - 'updated_at' => Schema::TYPE_INTEGER, + 'name' => $this->string(64)->notNull(), + 'data' => $this->text(), + 'created_at' => $this->integer(), + 'updated_at' => $this->integer(), 'PRIMARY KEY (name)', ], $tableOptions); $this->createTable($authManager->itemTable, [ - 'name' => Schema::TYPE_STRING . '(64) NOT NULL', - 'type' => Schema::TYPE_INTEGER . ' NOT NULL', - 'description' => Schema::TYPE_TEXT, - 'rule_name' => Schema::TYPE_STRING . '(64)', - 'data' => Schema::TYPE_TEXT, - 'created_at' => Schema::TYPE_INTEGER, - 'updated_at' => Schema::TYPE_INTEGER, + 'name' => $this->string(64)->notNull(), + 'type' => $this->integer()->notNull(), + 'description' => $this->text(), + 'rule_name' => $this->string(64), + 'data' => $this->text(), + 'created_at' => $this->integer(), + 'updated_at' => $this->integer(), 'PRIMARY KEY (name)', - 'FOREIGN KEY (rule_name) REFERENCES ' . $authManager->ruleTable . ' (name) ON DELETE SET NULL ON UPDATE CASCADE', + 'FOREIGN KEY (rule_name) REFERENCES ' . $authManager->ruleTable . ' (name)'. + ($this->isMSSQL() ? '' : ' ON DELETE SET NULL ON UPDATE CASCADE'), ], $tableOptions); $this->createIndex('idx-auth_item-type', $authManager->itemTable, 'type'); $this->createTable($authManager->itemChildTable, [ - 'parent' => Schema::TYPE_STRING . '(64) NOT NULL', - 'child' => Schema::TYPE_STRING . '(64) NOT NULL', + 'parent' => $this->string(64)->notNull(), + 'child' => $this->string(64)->notNull(), 'PRIMARY KEY (parent, child)', - 'FOREIGN KEY (parent) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', - 'FOREIGN KEY (child) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + 'FOREIGN KEY (parent) REFERENCES ' . $authManager->itemTable . ' (name)'. + ($this->isMSSQL() ? '' : ' ON DELETE CASCADE ON UPDATE CASCADE'), + 'FOREIGN KEY (child) REFERENCES ' . $authManager->itemTable . ' (name)'. + ($this->isMSSQL() ? '' : ' ON DELETE CASCADE ON UPDATE CASCADE'), ], $tableOptions); $this->createTable($authManager->assignmentTable, [ - 'item_name' => Schema::TYPE_STRING . '(64) NOT NULL', - 'user_id' => Schema::TYPE_STRING . '(64) NOT NULL', - 'created_at' => Schema::TYPE_INTEGER, + 'item_name' => $this->string(64)->notNull(), + 'user_id' => $this->string(64)->notNull(), + 'created_at' => $this->integer(), 'PRIMARY KEY (item_name, user_id)', 'FOREIGN KEY (item_name) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', ], $tableOptions); + + if ($this->isMSSQL()) { + $this->execute("CREATE TRIGGER dbo.trigger_auth_item_child + ON dbo.{$authManager->itemTable} + INSTEAD OF DELETE, UPDATE + AS + DECLARE @old_name VARCHAR (64) = (SELECT name FROM deleted) + DECLARE @new_name VARCHAR (64) = (SELECT name FROM inserted) + BEGIN + IF COLUMNS_UPDATED() > 0 + BEGIN + IF @old_name <> @new_name + BEGIN + ALTER TABLE auth_item_child NOCHECK CONSTRAINT FK__auth_item__child; + UPDATE auth_item_child SET child = @new_name WHERE child = @old_name; + END + UPDATE auth_item + SET name = (SELECT name FROM inserted), + type = (SELECT type FROM inserted), + description = (SELECT description FROM inserted), + rule_name = (SELECT rule_name FROM inserted), + data = (SELECT data FROM inserted), + created_at = (SELECT created_at FROM inserted), + updated_at = (SELECT updated_at FROM inserted) + WHERE name IN (SELECT name FROM deleted) + IF @old_name <> @new_name + BEGIN + ALTER TABLE auth_item_child CHECK CONSTRAINT FK__auth_item__child; + END + END + ELSE + BEGIN + DELETE FROM dbo.{$authManager->itemChildTable} WHERE parent IN (SELECT name FROM deleted) OR child IN (SELECT name FROM deleted); + DELETE FROM dbo.{$authManager->itemTable} WHERE name IN (SELECT name FROM deleted); + END + END;"); + } } + /** + * @inheritdoc + */ public function down() { $authManager = $this->getAuthManager(); $this->db = $authManager->db; + if ($this->isMSSQL()) { + $this->execute('DROP dbo.trigger_auth_item_child;'); + } + $this->dropTable($authManager->assignmentTable); $this->dropTable($authManager->itemChildTable); $this->dropTable($authManager->itemTable); $this->dropTable($authManager->ruleTable); } -} +} \ No newline at end of file diff --git a/framework/rbac/migrations/schema-mssql.sql b/framework/rbac/migrations/schema-mssql.sql index 5e1596f8ba..4bc940d48d 100644 --- a/framework/rbac/migrations/schema-mssql.sql +++ b/framework/rbac/migrations/schema-mssql.sql @@ -33,7 +33,7 @@ create table [auth_item] [created_at] integer, [updated_at] integer, primary key ([name]), - foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade + foreign key ([rule_name]) references [auth_rule] ([name]) ); create index [idx-auth_item-type] on [auth_item] ([type]); @@ -43,8 +43,8 @@ create table [auth_item_child] [parent] varchar(64) not null, [child] varchar(64) not null, primary key ([parent],[child]), - foreign key ([parent]) references [auth_item] ([name]) on delete cascade on update cascade, - foreign key ([child]) references [auth_item] ([name]) on delete cascade on update cascade + foreign key ([parent]) references [auth_item] ([name]), + foreign key ([child]) references [auth_item] ([name]) ); create table [auth_assignment] @@ -55,3 +55,38 @@ create table [auth_assignment] primary key ([item_name], [user_id]), foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade ); + +CREATE TRIGGER dbo.trigger_auth_item_child + ON dbo.[auth_item] + INSTEAD OF DELETE, UPDATE + AS + DECLARE @old_name VARCHAR (64) = (SELECT name FROM deleted) + DECLARE @new_name VARCHAR (64) = (SELECT name FROM inserted) + BEGIN + IF COLUMNS_UPDATED() > 0 + BEGIN + IF @old_name <> @new_name + BEGIN + ALTER TABLE auth_item_child NOCHECK CONSTRAINT FK__auth_item__child; + UPDATE auth_item_child SET child = @new_name WHERE child = @old_name; + END + UPDATE auth_item + SET name = (SELECT name FROM inserted), + type = (SELECT type FROM inserted), + description = (SELECT description FROM inserted), + rule_name = (SELECT rule_name FROM inserted), + data = (SELECT data FROM inserted), + created_at = (SELECT created_at FROM inserted), + updated_at = (SELECT updated_at FROM inserted) + WHERE name IN (SELECT name FROM deleted) + IF @old_name <> @new_name + BEGIN + ALTER TABLE auth_item_child CHECK CONSTRAINT FK__auth_item__child; + END + END + ELSE + BEGIN + DELETE FROM dbo.[auth_item_child] WHERE parent IN (SELECT name FROM deleted) OR child IN (SELECT name FROM deleted); + DELETE FROM dbo.[auth_item] WHERE name IN (SELECT name FROM deleted); + END + END; \ No newline at end of file diff --git a/framework/rbac/migrations/schema-oci.sql b/framework/rbac/migrations/schema-oci.sql index 44fca9ecfb..efe3526200 100644 --- a/framework/rbac/migrations/schema-oci.sql +++ b/framework/rbac/migrations/schema-oci.sql @@ -9,41 +9,44 @@ * @since 2.0 */ -drop table if exists "auth_assignment"; -drop table if exists "auth_item_child"; -drop table if exists "auth_item"; -drop table if exists "auth_rule"; +drop table "auth_assignment"; +drop table "auth_item_child"; +drop table "auth_item"; +drop table "auth_rule"; +-- create new auth_rule table create table "auth_rule" ( "name" varchar(64) not null, - "data" text, + "data" varchar(1000), "created_at" integer, "updated_at" integer, primary key ("name") ); +-- create auth_item table create table "auth_item" ( "name" varchar(64) not null, "type" integer not null, - "description" text, + "description" varchar(1000), "rule_name" varchar(64), - "data" text, + "data" varchar(1000), "created_at" integer, "updated_at" integer, - primary key ("name"), - foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade, - key "type" ("type") + foreign key ("rule_name") references "auth_rule"("name") on delete set null, + primary key ("name") ); +-- adds oracle specific index to auth_item +CREATE INDEX auth_type_index ON "auth_item"("type"); create table "auth_item_child" ( "parent" varchar(64) not null, "child" varchar(64) not null, primary key ("parent","child"), - foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, - foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("parent") references "auth_item"("name") on delete cascade, + foreign key ("child") references "auth_item"("name") on delete cascade ); create table "auth_assignment" @@ -52,5 +55,5 @@ create table "auth_assignment" "user_id" varchar(64) not null, "created_at" integer, primary key ("item_name","user_id"), - foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("item_name") references "auth_item" ("name") on delete cascade ); diff --git a/framework/requirements/YiiRequirementChecker.php b/framework/requirements/YiiRequirementChecker.php index 131e26e1ab..ef902f1b5d 100644 --- a/framework/requirements/YiiRequirementChecker.php +++ b/framework/requirements/YiiRequirementChecker.php @@ -16,7 +16,7 @@ if (version_compare(PHP_VERSION, '4.3', '<')) { * * Example: * - * ~~~php + * ```php * require_once('path/to/YiiRequirementChecker.php'); * $requirementsChecker = new YiiRequirementChecker(); * $requirements = array( @@ -29,7 +29,7 @@ if (version_compare(PHP_VERSION, '4.3', '<')) { * ), * ); * $requirementsChecker->checkYii()->check($requirements)->render(); - * ~~~ + * ``` * * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]] * @@ -37,14 +37,14 @@ if (version_compare(PHP_VERSION, '4.3', '<')) { * In this case specified PHP expression will be evaluated in the context of this class instance. * For example: * - * ~~~ + * ```php * $requirements = array( * array( * 'name' => 'Upload max file size', * 'condition' => 'eval:$this->checkUploadMaxFileSize("5M")', * ), * ); - * ~~~ + * ``` * * Note: this class definition does not match ordinary Yii style, because it should match PHP 4.3 * and should not use features from newer PHP versions! @@ -63,7 +63,7 @@ class YiiRequirementChecker * @param array|string $requirements requirements to be checked. * If an array, it is treated as the set of requirements; * If a string, it is treated as the path of the file, which contains the requirements; - * @return static self instance. + * @return $this self instance. */ function check($requirements) { @@ -180,7 +180,7 @@ class YiiRequirementChecker if (empty($extensionVersion)) { return false; } - if (strncasecmp($extensionVersion, 'PECL-', 5) == 0) { + if (strncasecmp($extensionVersion, 'PECL-', 5) === 0) { $extensionVersion = substr($extensionVersion, 5); } @@ -199,7 +199,7 @@ class YiiRequirementChecker return false; } - return ((int) $value == 1 || strtolower($value) == 'on'); + return ((int) $value === 1 || strtolower($value) === 'on'); } /** @@ -214,7 +214,7 @@ class YiiRequirementChecker return true; } - return (strtolower($value) == 'off'); + return (strtolower($value) === 'off'); } /** @@ -254,20 +254,16 @@ class YiiRequirementChecker } switch (strtolower($sizeUnit)) { case 'kb': - case 'k': { + case 'k': return $size * 1024; - } case 'mb': - case 'm': { + case 'm': return $size * 1024 * 1024; - } case 'gb': - case 'g': { + case 'g': return $size * 1024 * 1024 * 1024; - } - default: { + default: return 0; - } } } diff --git a/framework/requirements/requirements.php b/framework/requirements/requirements.php index 3225c5195d..2e7282529f 100644 --- a/framework/requirements/requirements.php +++ b/framework/requirements/requirements.php @@ -31,6 +31,12 @@ return array( 'condition' => extension_loaded('SPL'), 'by' => 'Yii Framework', ), + array( + 'name' => 'Ctype extension', + 'mandatory' => true, + 'condition' => extension_loaded('ctype'), + 'by' => 'Yii Framework' + ), array( 'name' => 'MBString extension', 'mandatory' => true, @@ -58,13 +64,24 @@ return array( array( 'name' => 'ICU version', 'mandatory' => false, - 'condition' => version_compare(INTL_ICU_VERSION, '49', '>='), + 'condition' => defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '49', '>='), 'by' => 'Internationalization support', 'memo' => 'ICU 49.0 or higher is required when you want to use # placeholder in plural rules (for example, plural in Formatter::asRelativeTime()) in the yii\i18n\Formatter class. Your current ICU version is ' . - INTL_ICU_VERSION . '.' + (defined('INTL_ICU_VERSION') ? INTL_ICU_VERSION : '(ICU is missing)') . '.' + ), + array( + 'name' => 'ICU Data version', + 'mandatory' => false, + 'condition' => defined('INTL_ICU_DATA_VERSION') && version_compare(INTL_ICU_DATA_VERSION, '49.1', '>='), + 'by' => 'Internationalization support', + 'memo' => 'ICU Data 49.1 or higher is required when you want to use # placeholder in plural rules + (for example, plural in + + Formatter::asRelativeTime()) in the yii\i18n\Formatter class. Your current ICU Data version is ' . + (defined('INTL_ICU_DATA_VERSION') ? INTL_ICU_DATA_VERSION : '(ICU Data is missing)') . '.' ), array( 'name' => 'Fileinfo extension', diff --git a/framework/requirements/views/web/css.php b/framework/requirements/views/web/css.php index 2946a4debd..e3f2a41999 100644 --- a/framework/requirements/views/web/css.php +++ b/framework/requirements/views/web/css.php @@ -1,6807 +1,63 @@ diff --git a/framework/requirements/views/web/index.php b/framework/requirements/views/web/index.php index af0d839bee..08d15798bb 100644 --- a/framework/requirements/views/web/index.php +++ b/framework/requirements/views/web/index.php @@ -11,12 +11,11 @@
        -
        +

        Yii Application Requirement Checker

        -
        +
        - -
        +

        Description

        This script checks if your server configuration meets the requirements @@ -67,15 +66,12 @@ - -

        - +
        - - +
        diff --git a/framework/rest/Serializer.php b/framework/rest/Serializer.php index 30ff767ef7..899f6f0e80 100644 --- a/framework/rest/Serializer.php +++ b/framework/rest/Serializer.php @@ -91,7 +91,10 @@ class Serializer extends Component public $collectionEnvelope; /** <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * @var string the name of the envelope (e.g. `_links`) for returning the links objects. * It takes effect only, if `collectionEnvelope` is set. * @since 2.0.4 @@ -104,7 +107,10 @@ class Serializer extends Component */ public $metaEnvelope = '_meta'; /** +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @var Request the current request. If not set, the `request` application component will be used. */ public $request; @@ -205,6 +211,7 @@ class Serializer extends Component protected function serializePagination($pagination) { return [ +<<<<<<< HEAD <<<<<<< HEAD '_links' => Link::serialize($pagination->getLinks(true)), '_meta' => [ @@ -212,6 +219,10 @@ class Serializer extends Component $this->linksEnvelope => Link::serialize($pagination->getLinks(true)), $this->metaEnvelope => [ >>>>>>> yiichina/master +======= + $this->linksEnvelope => Link::serialize($pagination->getLinks(true)), + $this->metaEnvelope => [ +>>>>>>> master 'totalCount' => $pagination->totalCount, 'pageCount' => $pagination->getPageCount(), 'currentPage' => $pagination->getPage() + 1, diff --git a/framework/rest/UrlRule.php b/framework/rest/UrlRule.php index 9b3aa00fc3..7b9fd269cd 100644 --- a/framework/rest/UrlRule.php +++ b/framework/rest/UrlRule.php @@ -147,7 +147,7 @@ class UrlRule extends CompositeUrlRule $controllers = []; foreach ((array) $this->controller as $urlName => $controller) { - if (is_integer($urlName)) { + if (is_int($urlName)) { $urlName = $this->pluralize ? Inflector::pluralize($controller) : $controller; } $controllers[$urlName] = $controller; @@ -166,11 +166,15 @@ class UrlRule extends CompositeUrlRule { $only = array_flip($this->only); $except = array_flip($this->except); +<<<<<<< HEAD <<<<<<< HEAD $patterns = array_merge($this->patterns, $this->extraPatterns); ======= $patterns = $this->extraPatterns + $this->patterns; >>>>>>> yiichina/master +======= + $patterns = $this->extraPatterns + $this->patterns; +>>>>>>> master $rules = []; foreach ($this->controller as $urlName => $controller) { $prefix = trim($this->prefix . '/' . $urlName, '/'); diff --git a/framework/test/ActiveFixture.php b/framework/test/ActiveFixture.php index 442c33a749..00670dd946 100644 --- a/framework/test/ActiveFixture.php +++ b/framework/test/ActiveFixture.php @@ -57,7 +57,7 @@ class ActiveFixture extends BaseActiveFixture public function init() { parent::init(); - if (!isset($this->modelClass) && !isset($this->tableName)) { + if ($this->modelClass === null && $this->tableName === null) { throw new InvalidConfigException('Either "modelClass" or "tableName" must be set.'); } } @@ -77,6 +77,7 @@ class ActiveFixture extends BaseActiveFixture $this->data = []; $table = $this->getTableSchema(); foreach ($this->getData() as $alias => $row) { +<<<<<<< HEAD <<<<<<< HEAD $this->db->createCommand()->insert($table->fullName, $row)->execute(); if ($table->sequenceName !== null) { @@ -92,6 +93,10 @@ class ActiveFixture extends BaseActiveFixture $primaryKeys = $this->db->schema->insert($table->fullName, $row); $this->data[$alias] = array_merge($row, $primaryKeys); >>>>>>> yiichina/master +======= + $primaryKeys = $this->db->schema->insert($table->fullName, $row); + $this->data[$alias] = array_merge($row, $primaryKeys); +>>>>>>> master } } diff --git a/framework/test/ArrayFixture.php b/framework/test/ArrayFixture.php index 6160571989..09a65716fd 100644 --- a/framework/test/ArrayFixture.php +++ b/framework/test/ArrayFixture.php @@ -31,6 +31,7 @@ class ArrayFixture extends Fixture implements \IteratorAggregate, \ArrayAccess, */ public $dataFile; + /** * Loads the fixture. * @@ -72,5 +73,4 @@ class ArrayFixture extends Fixture implements \IteratorAggregate, \ArrayAccess, parent::unload(); $this->data = []; } - } diff --git a/framework/test/FixtureTrait.php b/framework/test/FixtureTrait.php index e67d12d684..463cc565f7 100644 --- a/framework/test/FixtureTrait.php +++ b/framework/test/FixtureTrait.php @@ -168,7 +168,7 @@ trait FixtureTrait if (!is_array($fixture)) { $class = ltrim($fixture, '\\'); $fixtures[$name] = ['class' => $class]; - $aliases[$class] = is_integer($name) ? $class : $name; + $aliases[$class] = is_int($name) ? $class : $name; } elseif (isset($fixture['class'])) { $class = ltrim($fixture['class'], '\\'); $config[$class] = $fixture; diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php index 91e534a08e..348d9dd58e 100644 --- a/framework/validators/BooleanValidator.php +++ b/framework/validators/BooleanValidator.php @@ -53,15 +53,16 @@ class BooleanValidator extends Validator protected function validateValue($value) { $valid = !$this->strict && ($value == $this->trueValue || $value == $this->falseValue) - || $this->strict && ($value === $this->trueValue || $value === $this->falseValue); + || $this->strict && ($value === $this->trueValue || $value === $this->falseValue); + if (!$valid) { return [$this->message, [ - 'true' => $this->trueValue, - 'false' => $this->falseValue, + 'true' => $this->trueValue === true ? 'true' : $this->trueValue, + 'false' => $this->falseValue === false ? 'false' : $this->falseValue, ]]; - } else { - return null; } + + return null; } /** @@ -74,8 +75,8 @@ class BooleanValidator extends Validator 'falseValue' => $this->falseValue, 'message' => Yii::$app->getI18n()->format($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - 'true' => $this->trueValue, - 'false' => $this->falseValue, + 'true' => $this->trueValue === true ? 'true' : $this->trueValue, + 'false' => $this->falseValue === false ? 'false' : $this->falseValue, ], Yii::$app->language), ]; if ($this->skipOnEmpty) { diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php index 9a33c089a5..5ed17341ff 100644 --- a/framework/validators/CompareValidator.php +++ b/framework/validators/CompareValidator.php @@ -62,6 +62,8 @@ class CompareValidator extends Validator * - `>=`: check if value being validated is greater than or equal to the value being compared with. * - `<`: check if value being validated is less than the value being compared with. * - `<=`: check if value being validated is less than or equal to the value being compared with. + * + * When you want to compare numbers, make sure to also set [[type]] to `number`. */ public $operator = '=='; /** @@ -72,6 +74,7 @@ class CompareValidator extends Validator * - `{value}`: the value of the attribute being validated * - `{compareValue}`: the value or the attribute label to be compared with * - `{compareAttribute}`: the label of the attribute to be compared with + * - `{compareValueOrAttribute}`: the value or the attribute label to be compared with */ public $message; @@ -85,28 +88,28 @@ class CompareValidator extends Validator if ($this->message === null) { switch ($this->operator) { case '==': - $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); + $this->message = Yii::t('yii', '{attribute} must be equal to "{compareValueOrAttribute}".'); break; case '===': - $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); + $this->message = Yii::t('yii', '{attribute} must be equal to "{compareValueOrAttribute}".'); break; case '!=': - $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValueOrAttribute}".'); break; case '!==': - $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValueOrAttribute}".'); break; case '>': - $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValueOrAttribute}".'); break; case '>=': - $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValueOrAttribute}".'); break; case '<': - $this->message = Yii::t('yii', '{attribute} must be less than "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be less than "{compareValueOrAttribute}".'); break; case '<=': - $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValueOrAttribute}".'); break; default: throw new InvalidConfigException("Unknown operator: {$this->operator}"); @@ -126,17 +129,18 @@ class CompareValidator extends Validator return; } if ($this->compareValue !== null) { - $compareLabel = $compareValue = $this->compareValue; + $compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue; } else { $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute; $compareValue = $model->$compareAttribute; - $compareLabel = $model->getAttributeLabel($compareAttribute); + $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute); } if (!$this->compareValues($this->operator, $this->type, $value, $compareValue)) { $this->addError($model, $attribute, $this->message, [ 'compareAttribute' => $compareLabel, 'compareValue' => $compareValue, + 'compareValueOrAttribute' => $compareValueOrAttribute, ]); } } @@ -153,6 +157,7 @@ class CompareValidator extends Validator return [$this->message, [ 'compareAttribute' => $this->compareValue, 'compareValue' => $this->compareValue, + 'compareValueOrAttribute' => $this->compareValue, ]]; } else { return null; @@ -210,11 +215,12 @@ class CompareValidator extends Validator if ($this->compareValue !== null) { $options['compareValue'] = $this->compareValue; - $compareValue = $this->compareValue; + $compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue; } else { $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute; $compareValue = $model->getAttributeLabel($compareAttribute); $options['compareAttribute'] = Html::getInputId($model, $compareAttribute); + $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute); } if ($this->skipOnEmpty) { @@ -223,8 +229,9 @@ class CompareValidator extends Validator $options['message'] = Yii::$app->getI18n()->format($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - 'compareAttribute' => $compareValue, + 'compareAttribute' => $compareLabel, 'compareValue' => $compareValue, + 'compareValueOrAttribute' => $compareValueOrAttribute, ], Yii::$app->language); ValidationAsset::register($view); diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php index 7965151001..7404126cdf 100644 --- a/framework/validators/DateValidator.php +++ b/framework/validators/DateValidator.php @@ -8,12 +8,17 @@ namespace yii\validators; <<<<<<< HEAD +<<<<<<< HEAD +======= +use DateTime; +>>>>>>> master use IntlDateFormatter; use Yii; -use DateTime; +use yii\base\InvalidConfigException; use yii\helpers\FormatConverter; /** +<<<<<<< HEAD * DateValidator verifies if the attribute represents a date, time or datetime in a proper format. ======= use DateTime; @@ -24,6 +29,8 @@ use yii\base\InvalidConfigException; use yii\helpers\FormatConverter; /** +======= +>>>>>>> master * DateValidator verifies if the attribute represents a date, time or datetime in a proper [[format]]. * * It can also parse internationalized dates in a specific [[locale]] like e.g. `12 мая 2014` when [[format]] @@ -32,8 +39,16 @@ use yii\helpers\FormatConverter; * It is further possible to limit the date within a certain range using [[min]] and [[max]]. * * Additional to validating the date it can also export the parsed timestamp as a machine readable format +<<<<<<< HEAD * which can be configured using [[timestampAttribute]]. >>>>>>> yiichina/master +======= + * which can be configured using [[timestampAttribute]]. For values that include time information (not date-only values) + * also the time zone will be adjusted. The time zone of the input value is assumed to be the one specified by the [[timeZone]] + * property and the target timeZone will be UTC when [[timestampAttributeFormat]] is `null` (exporting as UNIX timestamp) + * or [[timestampAttributeTimeZone]] otherwise. If you want to avoid the time zone conversion, make sure that [[timeZone]] and + * [[timestampAttributeTimeZone]] are the same. +>>>>>>> master * * @author Qiang Xue * @author Carsten Brandt @@ -41,6 +56,41 @@ use yii\helpers\FormatConverter; */ class DateValidator extends Validator { + /** + * Constant for specifying the validation [[type]] as a date value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_DATE = 'date'; + /** + * Constant for specifying the validation [[type]] as a datetime value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_DATETIME = 'datetime'; + /** + * Constant for specifying the validation [[type]] as a time value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_TIME = 'time'; + + /** + * @var string the type of the validator. Indicates, whether a date, time or datetime value should be validated. + * This property influences the default value of [[format]] and also sets the correct behavior when [[format]] is one of the intl + * short formats, `short`, `medium`, `long`, or `full`. + * + * This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * + * This property can be set to the following values: + * + * - [[TYPE_DATE]] - (default) for validating date values only, that means only values that do not include a time range are valid. + * - [[TYPE_DATETIME]] - for validating datetime values, that contain a date part as well as a time part. + * - [[TYPE_TIME]] - for validating time values, that contain no date information. + * + * @since 2.0.8 + */ + public $type = self::TYPE_DATE; /** * @var string the date format that the value being validated should follow. * This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). @@ -49,26 +99,42 @@ class DateValidator extends Validator * Please refer to on supported formats. * * If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details. + * Since version 2.0.8 the default value will be determined from different formats of the formatter class, + * dependent on the value of [[type]]: + * + * - if type is [[TYPE_DATE]], the default value will be taken from [[\yii\i18n\Formatter::dateFormat]], + * - if type is [[TYPE_DATETIME]], it will be taken from [[\yii\i18n\Formatter::datetimeFormat]], + * - and if type is [[TYPE_TIME]], it will be [[\yii\i18n\Formatter::timeFormat]]. * * Here are some example values: * * ```php * 'MM/dd/yyyy' // date in ICU format * 'php:m/d/Y' // the same date in PHP format +<<<<<<< HEAD <<<<<<< HEAD * ``` ======= * 'MM/dd/yyyy HH:mm' // not only dates but also times can be validated * ``` +======= + * 'MM/dd/yyyy HH:mm' // not only dates but also times can be validated + * ``` +>>>>>>> master * * **Note:** the underlying date parsers being used vary dependent on the format. If you use the ICU format and * the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed, the [IntlDateFormatter](http://php.net/manual/en/intldateformatter.parse.php) * is used to parse the input value. In all other cases the PHP [DateTime](http://php.net/manual/en/datetime.createfromformat.php) class * is used. The IntlDateFormatter has the advantage that it can parse international dates like `12. Mai 2015` or `12 мая 2014`, while the * PHP parser is limited to English only. The PHP parser however is more strict about the input format as it will not accept +<<<<<<< HEAD * `12.05.05` for the format `php:d.m.Y`, but the IntlDateFormatter will accept it for the format `dd-MM-yyyy`. * If you need to use the IntlDateFormatter you can avoid this problem by specifying a [[min|minimum date]]. >>>>>>> yiichina/master +======= + * `12.05.05` for the format `php:d.m.Y`, but the IntlDateFormatter will accept it for the format `dd.MM.yyyy`. + * If you need to use the IntlDateFormatter you can avoid this problem by specifying a [[min|minimum date]]. +>>>>>>> master */ public $format; /** @@ -90,6 +156,7 @@ class DateValidator extends Validator * @var string the name of the attribute to receive the parsing result. * When this property is not null and the validation is successful, the named attribute will * receive the parsing result. +<<<<<<< HEAD <<<<<<< HEAD */ public $timestampAttribute; @@ -101,6 +168,15 @@ class DateValidator extends Validator * @see timestampAttributeTimeZone */ public $timestampAttribute; +======= + * + * This can be the same attribute as the one being validated. If this is the case, + * the original value will be overwritten with the timestamp value after successful validation. + * @see timestampAttributeFormat + * @see timestampAttributeTimeZone + */ + public $timestampAttribute; +>>>>>>> master /** * @var string the format to use when populating the [[timestampAttribute]]. * The format can be specified in the same way as for [[format]]. @@ -162,7 +238,10 @@ class DateValidator extends Validator * @since 2.0.4 */ public $minString; +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master /** * @var array map of short format names to IntlDateFormatter constant values. @@ -185,7 +264,15 @@ class DateValidator extends Validator $this->message = Yii::t('yii', 'The format of {attribute} is invalid.'); } if ($this->format === null) { - $this->format = Yii::$app->formatter->dateFormat; + if ($this->type === self::TYPE_DATE) { + $this->format = Yii::$app->formatter->dateFormat; + } elseif ($this->type === self::TYPE_DATETIME) { + $this->format = Yii::$app->formatter->datetimeFormat; + } elseif ($this->type === self::TYPE_TIME) { + $this->format = Yii::$app->formatter->timeFormat; + } else { + throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type); + } } if ($this->locale === null) { $this->locale = Yii::$app->language; @@ -194,7 +281,10 @@ class DateValidator extends Validator $this->timeZone = Yii::$app->timeZone; } <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master if ($this->min !== null && $this->tooSmall === null) { $this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.'); } @@ -202,10 +292,17 @@ class DateValidator extends Validator $this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.'); } if ($this->maxString === null) { +<<<<<<< HEAD $this->maxString = (string)$this->max; } if ($this->minString === null) { $this->minString = (string)$this->min; +======= + $this->maxString = (string) $this->max; + } + if ($this->minString === null) { + $this->minString = (string) $this->min; +>>>>>>> master } if ($this->max !== null && is_string($this->max)) { $timestamp = $this->parseDateValue($this->max); @@ -221,7 +318,10 @@ class DateValidator extends Validator } $this->min = $timestamp; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -232,7 +332,19 @@ class DateValidator extends Validator $value = $model->$attribute; $timestamp = $this->parseDateValue($value); if ($timestamp === false) { + if ($this->timestampAttribute === $attribute) { + if ($this->timestampAttributeFormat === null) { + if (is_int($value)) { + return; + } + } else { + if ($this->parseDateValueFormat($value, $this->timestampAttributeFormat) !== false) { + return; + } + } + } $this->addError($model, $attribute, $this->message, []); +<<<<<<< HEAD <<<<<<< HEAD } elseif ($this->timestampAttribute !== null) { $model->{$this->timestampAttribute} = $timestamp; @@ -242,12 +354,22 @@ class DateValidator extends Validator } elseif ($this->max !== null && $timestamp > $this->max) { $this->addError($model, $attribute, $this->tooBig, ['max' => $this->maxString]); } elseif ($this->timestampAttribute !== null) { +======= + } elseif ($this->min !== null && $timestamp < $this->min) { + $this->addError($model, $attribute, $this->tooSmall, ['min' => $this->minString]); + } elseif ($this->max !== null && $timestamp > $this->max) { + $this->addError($model, $attribute, $this->tooBig, ['max' => $this->maxString]); + } elseif ($this->timestampAttribute !== null) { +>>>>>>> master if ($this->timestampAttributeFormat === null) { $model->{$this->timestampAttribute} = $timestamp; } else { $model->{$this->timestampAttribute} = $this->formatTimestamp($timestamp, $this->timestampAttributeFormat); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } } @@ -256,9 +378,12 @@ class DateValidator extends Validator */ protected function validateValue($value) { +<<<<<<< HEAD <<<<<<< HEAD return $this->parseDateValue($value) === false ? [$this->message, []] : null; ======= +======= +>>>>>>> master $timestamp = $this->parseDateValue($value); if ($timestamp === false) { return [$this->message, []]; @@ -269,29 +394,49 @@ class DateValidator extends Validator } else { return null; } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** * Parses date string into UNIX timestamp * * @param string $value string representing date +<<<<<<< HEAD <<<<<<< HEAD * @return boolean|integer UNIX timestamp or false on failure ======= * @return integer|false a UNIX timestamp or `false` on failure. >>>>>>> yiichina/master +======= + * @return integer|false a UNIX timestamp or `false` on failure. +>>>>>>> master */ protected function parseDateValue($value) + { + // TODO consider merging these methods into single one at 2.1 + return $this->parseDateValueFormat($value, $this->format); + } + + /** + * Parses date string into UNIX timestamp + * + * @param string $value string representing date + * @param string $format expected date format + * @return integer|false a UNIX timestamp or `false` on failure. + */ + private function parseDateValueFormat($value, $format) { if (is_array($value)) { return false; } - $format = $this->format; - if (strncmp($this->format, 'php:', 4) === 0) { + if (strncmp($format, 'php:', 4) === 0) { $format = substr($format, 4); } else { if (extension_loaded('intl')) { +<<<<<<< HEAD <<<<<<< HEAD if (isset($this->_dateFormats[$format])) { $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone); @@ -312,23 +457,96 @@ class DateValidator extends Validator ======= return $this->parseDateValueIntl($value, $format); >>>>>>> yiichina/master +======= + return $this->parseDateValueIntl($value, $format); +>>>>>>> master } else { // fallback to PHP if intl is not installed $format = FormatConverter::convertDateIcuToPhp($format, 'date'); } } +<<<<<<< HEAD <<<<<<< HEAD $date = DateTime::createFromFormat($format, $value, new \DateTimeZone($this->timeZone)); +======= + return $this->parseDateValuePHP($value, $format); + } + + /** + * Parses a date value using the IntlDateFormatter::parse() + * @param string $value string representing date + * @param string $format the expected date format + * @return integer|boolean a UNIX timestamp or `false` on failure. + */ + private function parseDateValueIntl($value, $format) + { + if (isset($this->_dateFormats[$format])) { + if ($this->type === self::TYPE_DATE) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, 'UTC'); + } elseif ($this->type === self::TYPE_DATETIME) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone); + } elseif ($this->type === self::TYPE_TIME) { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone); + } else { + throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type); + } + } else { + // if no time was provided in the format string set time to 0 to get a simple date timestamp + $hasTimeInfo = (strpbrk($format, 'ahHkKmsSA') !== false); + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $hasTimeInfo ? $this->timeZone : 'UTC', null, $format); + } + // enable strict parsing to avoid getting invalid date values + $formatter->setLenient(false); + + // There should not be a warning thrown by parse() but this seems to be the case on windows so we suppress it here + // See https://github.com/yiisoft/yii2/issues/5962 and https://bugs.php.net/bug.php?id=68528 + $parsePos = 0; + $parsedDate = @$formatter->parse($value, $parsePos); + if ($parsedDate === false || $parsePos !== mb_strlen($value, Yii::$app ? Yii::$app->charset : 'UTF-8')) { + return false; + } + + return $parsedDate; + } + + /** + * Parses a date value using the DateTime::createFromFormat() + * @param string $value string representing date + * @param string $format the expected date format + * @return integer|boolean a UNIX timestamp or `false` on failure. + */ + private function parseDateValuePHP($value, $format) + { + // if no time was provided in the format string set time to 0 to get a simple date timestamp + $hasTimeInfo = (strpbrk($format, 'HhGgis') !== false); + + $date = DateTime::createFromFormat($format, $value, new \DateTimeZone($hasTimeInfo ? $this->timeZone : 'UTC')); +>>>>>>> master $errors = DateTime::getLastErrors(); if ($date === false || $errors['error_count'] || $errors['warning_count']) { return false; - } else { - // if no time was provided in the format string set time to 0 to get a simple date timestamp - if (strpbrk($format, 'HhGgis') === false) { - $date->setTime(0, 0, 0); - } - return $date->getTimestamp(); } + + if (!$hasTimeInfo) { + $date->setTime(0, 0, 0); + } + return $date->getTimestamp(); + } + + /** + * Formats a timestamp using the specified format + * @param integer $timestamp + * @param string $format + * @return string + */ + private function formatTimestamp($timestamp, $format) + { + if (strncmp($format, 'php:', 4) === 0) { + $format = substr($format, 4); + } else { + $format = FormatConverter::convertDateIcuToPhp($format, 'date'); + } +<<<<<<< HEAD ======= return $this->parseDateValuePHP($value, $format); } @@ -399,11 +617,16 @@ class DateValidator extends Validator } else { $format = FormatConverter::convertDateIcuToPhp($format, 'date'); } +======= +>>>>>>> master $date = new DateTime(); $date->setTimestamp($timestamp); $date->setTimezone(new \DateTimeZone($this->timestampAttributeTimeZone)); return $date->format($format); +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } } diff --git a/framework/validators/DefaultValueValidator.php b/framework/validators/DefaultValueValidator.php index d31c2f4a19..e31ecf4010 100644 --- a/framework/validators/DefaultValueValidator.php +++ b/framework/validators/DefaultValueValidator.php @@ -19,12 +19,12 @@ namespace yii\validators; class DefaultValueValidator extends Validator { /** - * @var mixed the default value or a PHP callable that returns the default value which will - * be assigned to the attributes being validated if they are empty. The signature of the PHP callable + * @var mixed the default value or an anonymous function that returns the default value which will + * be assigned to the attributes being validated if they are empty. The signature of the anonymous function * should be as follows, * * ```php - * function foo($model, $attribute) { + * function($model, $attribute) { * // compute value * return $value; * } diff --git a/framework/validators/EachValidator.php b/framework/validators/EachValidator.php index 90875eb638..3b547d2a4a 100644 --- a/framework/validators/EachValidator.php +++ b/framework/validators/EachValidator.php @@ -14,10 +14,17 @@ use yii\base\Model; /** * EachValidator validates an array by checking each of its elements against an embedded validation rule. * +<<<<<<< HEAD * ~~~php * class MyModel extends Model * { * public $arrayAttribute = []; +======= + * ```php + * class MyModel extends Model + * { + * public $categoryIDs = []; +>>>>>>> master * * public function rules() * { @@ -27,11 +34,21 @@ use yii\base\Model; * ] * } * } +<<<<<<< HEAD * ~~~ +======= + * ``` +>>>>>>> master * * > Note: This validator will not work with inline validation rules in case of usage outside the model scope, * e.g. via [[validate()]] method. * +<<<<<<< HEAD +======= + * > Note: EachValidator is meant to be used only in basic cases, you should consider usage of tabular input, + * using several models for the more complex case. + * +>>>>>>> master * @author Paul Klimov * @since 2.0.4 */ @@ -43,10 +60,17 @@ class EachValidator extends Validator * contain attribute list as the first element. * For example: * +<<<<<<< HEAD * ~~~ * ['integer'] * ['match', 'pattern' => '/[a-z]/is'] * ~~~ +======= + * ```php + * ['integer'] + * ['match', 'pattern' => '/[a-z]/is'] + * ``` +>>>>>>> master * * Please refer to [[yii\base\Model::rules()]] for more details. */ @@ -115,6 +139,7 @@ class EachValidator extends Validator public function validateAttribute($model, $attribute) { $value = $model->$attribute; +<<<<<<< HEAD $validator = $this->getValidator(); if ($validator instanceof FilterValidator && is_array($value)) { $filteredValue = []; @@ -128,6 +153,39 @@ class EachValidator extends Validator $this->getValidator($model); // ensure model context while validator creation parent::validateAttribute($model, $attribute); } +======= + if (!is_array($value)) { + $this->addError($model, $attribute, $this->message, []); + return; + } + + $validator = $this->getValidator($model); // ensure model context while validator creation + + $originalErrors = $model->getErrors($attribute); + $filteredValue = []; + foreach ($value as $k => $v) { + $model->$attribute = $v; + if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) { + $validator->validateAttribute($model, $attribute); + } + $filteredValue[$k] = $model->$attribute; + if ($model->hasErrors($attribute)) { + $validationErrors = $model->getErrors($attribute); + $model->clearErrors($attribute); + if (!empty($originalErrors)) { + $model->addErrors([$attribute => $originalErrors]); + } + if ($this->allowMessageFromRule) { + $model->addErrors([$attribute => $validationErrors]); + } else { + $this->addError($model, $attribute, $this->message, ['value' => $v]); + } + $model->$attribute = $value; + return; + } + } + $model->$attribute = $filteredValue; +>>>>>>> master } /** @@ -141,9 +199,23 @@ class EachValidator extends Validator $validator = $this->getValidator(); foreach ($value as $v) { +<<<<<<< HEAD $result = $validator->validateValue($v); if ($result !== null) { return $this->allowMessageFromRule ? $result : [$this->message, []]; +======= + if ($validator->skipOnEmpty && $validator->isEmpty($v)) { + continue; + } + $result = $validator->validateValue($v); + if ($result !== null) { + if ($this->allowMessageFromRule) { + $result[1]['value'] = $v; + return $result; + } else { + return [$this->message, ['value' => $v]]; + } +>>>>>>> master } } diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php index 9fd5ebd567..a94167c73b 100644 --- a/framework/validators/EmailValidator.php +++ b/framework/validators/EmailValidator.php @@ -70,19 +70,34 @@ class EmailValidator extends Validator */ protected function validateValue($value) { - // make sure string length is limited to avoid DOS attacks - if (!is_string($value) || strlen($value) >= 320) { + if (!is_string($value)) { $valid = false; - } elseif (!preg_match('/^(.*?)$/', $value, $matches)) { + } elseif (!preg_match('/^(?P(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P.+)@(?P[^>]+))(?P>?))$/i', $value, $matches)) { $valid = false; } else { - $domain = $matches[3]; if ($this->enableIDN) { - $value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4]; + $matches['local'] = idn_to_ascii($matches['local']); + $matches['domain'] = idn_to_ascii($matches['domain']); + $value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close']; } - $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); - if ($valid && $this->checkDNS) { - $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A'); + + if (strlen($matches['local']) > 64) { + // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1 + // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 + $valid = false; + } elseif (strlen($matches['local'] . '@' . $matches['domain']) > 254) { + // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands + // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the + // upper limit on address lengths should normally be considered to be 254. + // + // Dominic Sayers, RFC 3696 erratum 1690 + // http://www.rfc-editor.org/errata_search.php?eid=1690 + $valid = false; + } else { + $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); + if ($valid && $this->checkDNS) { + $valid = checkdnsrr($matches['domain'], 'MX') || checkdnsrr($matches['domain'], 'A'); + } } } @@ -101,7 +116,7 @@ class EmailValidator extends Validator 'message' => Yii::$app->getI18n()->format($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), ], Yii::$app->language), - 'enableIDN' => (boolean) $this->enableIDN, + 'enableIDN' => (bool)$this->enableIDN, ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; @@ -112,10 +127,14 @@ class EmailValidator extends Validator PunycodeAsset::register($view); } +<<<<<<< HEAD <<<<<<< HEAD return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');'; ======= return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');'; >>>>>>> yiichina/master +======= + return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');'; +>>>>>>> master } } diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php index 524013f207..6b859f72e8 100644 --- a/framework/validators/ExistValidator.php +++ b/framework/validators/ExistValidator.php @@ -19,11 +19,15 @@ use yii\base\InvalidConfigException; * This validator is often used to verify that a foreign key contains a value * that can be found in the foreign table. * +<<<<<<< HEAD <<<<<<< HEAD * The followings are examples of validation rules using this validator: ======= * The following are examples of validation rules using this validator: >>>>>>> yiichina/master +======= + * The following are examples of validation rules using this validator: +>>>>>>> master * * ```php * // a1 needs to exist @@ -95,7 +99,7 @@ class ExistValidator extends Validator } $params = []; foreach ($targetAttribute as $k => $v) { - $params[$v] = is_integer($k) ? $model->$v : $model->$k; + $params[$v] = is_int($k) ? $model->$v : $model->$k; } } else { $params = [$targetAttribute => $model->$attribute]; diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index 4de9def002..5c6c9d1cd6 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -8,6 +8,9 @@ namespace yii\validators; use Yii; +use yii\helpers\Html; +use yii\helpers\Json; +use yii\web\JsExpression; use yii\web\UploadedFile; use yii\helpers\FileHelper; @@ -29,7 +32,7 @@ class FileValidator extends Validator * separated by space or comma (e.g. "gif, jpg"). * Extension names are case-insensitive. Defaults to null, meaning all file name * extensions are allowed. - * @see wrongType for the customized message for wrong file type. + * @see wrongExtension for the customized message for wrong file type. */ public $extensions; /** @@ -41,8 +44,9 @@ class FileValidator extends Validator * @var array|string a list of file MIME types that are allowed to be uploaded. * This can be either an array or a string consisting of file MIME types * separated by space or comma (e.g. "text/plain, image/png"). - * Mime type names are case-insensitive. Defaults to null, meaning all MIME types - * are allowed. + * The mask with the special character `*` can be used to match groups of mime types. + * For example `image/*` will pass all mime types, that begin with `image/` (e.g. `image/jpeg`, `image/png`). + * Mime type names are case-insensitive. Defaults to null, meaning all MIME types are allowed. * @see wrongMimeType for the customized message for wrong MIME type. */ public $mimeTypes; @@ -55,15 +59,24 @@ class FileValidator extends Validator /** * @var integer the maximum number of bytes required for the uploaded file. * Defaults to null, meaning no limit. - * Note, the size limit is also affected by 'upload_max_filesize' INI setting - * and the 'MAX_FILE_SIZE' hidden field value. + * Note, the size limit is also affected by `upload_max_filesize` and `post_max_size` INI setting + * and the 'MAX_FILE_SIZE' hidden field value. See [[getSizeLimit()]] for details. + * @see http://php.net/manual/en/ini.core.php#ini.upload-max-filesize + * @see http://php.net/post-max-size + * @see getSizeLimit * @see tooBig for the customized message for a file that is too big. */ public $maxSize; /** * @var integer the maximum file count the given attribute can hold. - * It defaults to 1, meaning single file upload. By defining a higher number, - * multiple uploads become possible. + * Defaults to 1, meaning single file upload. By defining a higher number, + * multiple uploads become possible. Setting it to `0` means there is no limit on + * the number of files that can be uploaded simultaneously. + * + * > Note: The maximum number of files allowed to be uploaded simultaneously is + * also limited with PHP directive `max_file_uploads`, which defaults to 20. + * + * @see http://php.net/manual/en/ini.core.php#ini.max-file-uploads * @see tooMany for the customized message when too many files are uploaded. */ public $maxFiles = 1; @@ -84,6 +97,8 @@ class FileValidator extends Validator * - {attribute}: the attribute name * - {file}: the uploaded file name * - {limit}: the maximum size allowed (see [[getSizeLimit()]]) + * - {formattedLimit}: the maximum size formatted + * with [[\yii\i18n\Formatter::asShortSize()|Formatter::asShortSize()]] */ public $tooBig; /** @@ -93,6 +108,8 @@ class FileValidator extends Validator * - {attribute}: the attribute name * - {file}: the uploaded file name * - {limit}: the value of [[minSize]] + * - {formattedLimit}: the value of [[minSize]] formatted + * with [[\yii\i18n\Formatter::asShortSize()|Formatter::asShortSize()] */ public $tooSmall; /** @@ -114,7 +131,7 @@ class FileValidator extends Validator public $wrongExtension; /** * @var string the error message used when the file has an mime type - * that is not listed in [[mimeTypes]]. + * that is not allowed by [[mimeTypes]] property. * You may use the following tokens in the message: * * - {attribute}: the attribute name @@ -122,7 +139,7 @@ class FileValidator extends Validator * - {mimeTypes}: the value of [[mimeTypes]] */ public $wrongMimeType; - + /** * @inheritdoc @@ -143,10 +160,10 @@ class FileValidator extends Validator $this->wrongExtension = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.'); } if ($this->tooBig === null) { - $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.'); + $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.'); } if ($this->tooSmall === null) { - $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.'); + $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.'); } if (!is_array($this->extensions)) { $this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY); @@ -168,7 +185,7 @@ class FileValidator extends Validator */ public function validateAttribute($model, $attribute) { - if ($this->maxFiles > 1) { + if ($this->maxFiles != 1) { $files = $model->$attribute; if (!is_array($files)) { $this->addError($model, $attribute, $this->uploadRequired); @@ -184,7 +201,7 @@ class FileValidator extends Validator if (empty($files)) { $this->addError($model, $attribute, $this->uploadRequired); } - if (count($files) > $this->maxFiles) { + if ($this->maxFiles && count($files) > $this->maxFiles) { $this->addError($model, $attribute, $this->tooMany, ['limit' => $this->maxFiles]); } else { foreach ($files as $file) { @@ -213,20 +230,37 @@ class FileValidator extends Validator switch ($file->error) { case UPLOAD_ERR_OK: - if ($this->maxSize !== null && $file->size > $this->maxSize) { - return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]]; + if ($this->maxSize !== null && $file->size > $this->getSizeLimit()) { + return [ + $this->tooBig, + [ + 'file' => $file->name, + 'limit' => $this->getSizeLimit(), + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), + ], + ]; } elseif ($this->minSize !== null && $file->size < $this->minSize) { - return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]]; + return [ + $this->tooSmall, + [ + 'file' => $file->name, + 'limit' => $this->minSize, + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), + ], + ]; } elseif (!empty($this->extensions) && !$this->validateExtension($file)) { return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]]; - } elseif (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($file->tempName), $this->mimeTypes, false)) { + } elseif (!empty($this->mimeTypes) && !$this->validateMimeType($file)) { return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]]; - } else { - return null; } + return null; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: - return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]]; + return [$this->tooBig, [ + 'file' => $file->name, + 'limit' => $this->getSizeLimit(), + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), + ]]; case UPLOAD_ERR_PARTIAL: Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__); break; @@ -248,9 +282,10 @@ class FileValidator extends Validator /** * Returns the maximum size allowed for uploaded files. - * This is determined based on three factors: + * This is determined based on four factors: * * - 'upload_max_filesize' in php.ini + * - 'post_max_size' in php.ini * - 'MAX_FILE_SIZE' hidden field * - [[maxSize]] * @@ -258,7 +293,13 @@ class FileValidator extends Validator */ public function getSizeLimit() { + // Get the lowest between post_max_size and upload_max_filesize, log a warning if the first is < than the latter $limit = $this->sizeToBytes(ini_get('upload_max_filesize')); + $postLimit = $this->sizeToBytes(ini_get('post_max_size')); + if ($postLimit > 0 && $postLimit < $limit) { + Yii::warning('PHP.ini\'s \'post_max_size\' is less than \'upload_max_filesize\'.', __METHOD__); + $limit = $postLimit; + } if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) { $limit = $this->maxSize; } @@ -282,7 +323,7 @@ class FileValidator extends Validator * Converts php.ini style size to bytes * * @param string $sizeStr $sizeStr - * @return int + * @return integer */ private function sizeToBytes($sizeStr) { @@ -308,7 +349,7 @@ class FileValidator extends Validator */ protected function validateExtension($file) { - $extension = mb_strtolower($file->extension, 'utf-8'); + $extension = mb_strtolower($file->extension, 'UTF-8'); if ($this->checkExtensionByMimeType) { @@ -338,7 +379,7 @@ class FileValidator extends Validator { ValidationAsset::register($view); $options = $this->getClientOptions($model, $attribute); - return 'yii.validation.file(attribute, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + return 'yii.validation.file(attribute, messages, ' . Json::encode($options) . ');'; } /** @@ -360,52 +401,96 @@ class FileValidator extends Validator $options['skipOnEmpty'] = $this->skipOnEmpty; - if ( !$this->skipOnEmpty ) { + if (!$this->skipOnEmpty) { $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [ 'attribute' => $label, ], Yii::$app->language); } - if ( $this->mimeTypes !== null ) { - $options['mimeTypes'] = $this->mimeTypes; + if ($this->mimeTypes !== null) { + $mimeTypes = []; + foreach ($this->mimeTypes as $mimeType) { + $mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($this->buildMimeTypeRegexp($mimeType))); + } + $options['mimeTypes'] = $mimeTypes; $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ 'attribute' => $label, - 'mimeTypes' => join(', ', $this->mimeTypes) + 'mimeTypes' => implode(', ', $this->mimeTypes), ], Yii::$app->language); } - if ( $this->extensions !== null ) { + if ($this->extensions !== null) { $options['extensions'] = $this->extensions; $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ 'attribute' => $label, - 'extensions' => join(', ', $this->extensions) + 'extensions' => implode(', ', $this->extensions), ], Yii::$app->language); } - if ( $this->minSize !== null ) { + if ($this->minSize !== null) { $options['minSize'] = $this->minSize; $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ 'attribute' => $label, - 'limit' => $this->minSize + 'limit' => $this->minSize, + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), ], Yii::$app->language); } - if ( $this->maxSize !== null ) { + if ($this->maxSize !== null) { $options['maxSize'] = $this->maxSize; $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ 'attribute' => $label, - 'limit' => $this->maxSize + 'limit' => $this->getSizeLimit(), + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), ], Yii::$app->language); } - if ( $this->maxFiles !== null ) { + if ($this->maxFiles !== null) { $options['maxFiles'] = $this->maxFiles; $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ 'attribute' => $label, - 'limit' => $this->maxFiles + 'limit' => $this->maxFiles, ], Yii::$app->language); } return $options; } + + /** + * Builds the RegExp from the $mask + * + * @param string $mask + * @return string the regular expression + * @see mimeTypes + */ + private function buildMimeTypeRegexp($mask) + { + return '/^' . str_replace('\*', '.*', preg_quote($mask, '/')) . '$/'; + } + + /** + * Checks the mimeType of the $file against the list in the [[mimeTypes]] property + * + * @param UploadedFile $file + * @return boolean whether the $file mimeType is allowed + * @throws \yii\base\InvalidConfigException + * @see mimeTypes + * @since 2.0.8 + */ + protected function validateMimeType($file) + { + $fileMimeType = FileHelper::getMimeType($file->tempName); + + foreach ($this->mimeTypes as $mimeType) { + if ($mimeType === $fileMimeType) { + return true; + } + + if (strpos($mimeType, '*') !== false && preg_match($this->buildMimeTypeRegexp($mimeType), $fileMimeType)) { + return true; + } + } + + return false; + } } diff --git a/framework/validators/FilterValidator.php b/framework/validators/FilterValidator.php index d2d3cb7d73..c9208280fa 100644 --- a/framework/validators/FilterValidator.php +++ b/framework/validators/FilterValidator.php @@ -17,9 +17,12 @@ use yii\base\InvalidConfigException; * and save the processed value back to the attribute. The filter must be * a valid PHP callback with the following signature: * - * ~~~ - * function foo($value) {...return $newValue; } - * ~~~ + * ```php + * function foo($value) { + * // compute $newValue here + * return $newValue; + * } + * ``` * * Many PHP functions qualify this signature (e.g. `trim()`). * @@ -34,9 +37,12 @@ class FilterValidator extends Validator * @var callable the filter. This can be a global function name, anonymous function, etc. * The function signature must be as follows, * - * ~~~ - * function foo($value) {...return $newValue; } - * ~~~ + * ```php + * function foo($value) { + * // compute $newValue here + * return $newValue; + * } + * ``` */ public $filter; /** @@ -89,6 +95,6 @@ class FilterValidator extends Validator ValidationAsset::register($view); - return 'yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; } } diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php index e2fd930c77..4e72f63a39 100644 --- a/framework/validators/ImageValidator.php +++ b/framework/validators/ImageValidator.php @@ -180,7 +180,7 @@ class ImageValidator extends FileValidator if ($this->notImage !== null) { $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ - 'attribute' => $label + 'attribute' => $label, ], Yii::$app->language); } @@ -188,7 +188,7 @@ class ImageValidator extends FileValidator $options['minWidth'] = $this->minWidth; $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ 'attribute' => $label, - 'limit' => $this->minWidth + 'limit' => $this->minWidth, ], Yii::$app->language); } @@ -196,7 +196,7 @@ class ImageValidator extends FileValidator $options['maxWidth'] = $this->maxWidth; $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ 'attribute' => $label, - 'limit' => $this->maxWidth + 'limit' => $this->maxWidth, ], Yii::$app->language); } @@ -204,7 +204,7 @@ class ImageValidator extends FileValidator $options['minHeight'] = $this->minHeight; $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ 'attribute' => $label, - 'limit' => $this->minHeight + 'limit' => $this->minHeight, ], Yii::$app->language); } @@ -212,7 +212,7 @@ class ImageValidator extends FileValidator $options['maxHeight'] = $this->maxHeight; $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ 'attribute' => $label, - 'limit' => $this->maxHeight + 'limit' => $this->maxHeight, ], Yii::$app->language); } diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php index 3df96dbc70..8423e8bf28 100644 --- a/framework/validators/InlineValidator.php +++ b/framework/validators/InlineValidator.php @@ -12,9 +12,9 @@ namespace yii\validators; * * The validation method must have the following signature: * - * ~~~ + * ```php * function foo($attribute, $params) - * ~~~ + * ``` * * where `$attribute` refers to the name of the attribute being validated, while `$params` * is an array representing the additional parameters supplied in the validation rule. @@ -30,9 +30,9 @@ class InlineValidator extends Validator * where `$attribute` is the name of the attribute to be validated, and `$params` contains the value * of [[params]] that you specify when declaring the inline validation rule: * - * ~~~ + * ```php * function foo($attribute, $params) - * ~~~ + * ``` */ public $method; /** @@ -43,12 +43,12 @@ class InlineValidator extends Validator * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code. * The signature of the method should be like the following: * - * ~~~ + * ```php * function foo($attribute, $params) * { * return "javascript"; * } - * ~~~ + * ``` * * where `$attribute` refers to the attribute name to be validated. * diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php new file mode 100644 index 0000000000..455788a540 --- /dev/null +++ b/framework/validators/IpValidator.php @@ -0,0 +1,622 @@ + false], // IPv4 address (IPv6 is disabled) + * ['ip_address', 'ip', 'subnet' => true], // requires a CIDR prefix (like 10.0.0.1/24) for the IP address + * ['ip_address', 'ip', 'subnet' => null], // CIDR prefix is optional + * ['ip_address', 'ip', 'subnet' => null, 'normalize' => true], // CIDR prefix is optional and will be added when missing + * ['ip_address', 'ip', 'ranges' => ['192.168.0.0/24']], // only IP addresses from the specified subnet are allowed + * ['ip_address', 'ip', 'ranges' => ['!192.168.0.0/24', 'any']], // any IP is allowed except IP in the specified subnet + * ['ip_address', 'ip', 'expandIPv6' => true], // expands IPv6 address to a full notation format + * ``` + * + * @property array $ranges The IPv4 or IPv6 ranges that are allowed or forbidden. See [[setRanges()]] for + * detailed description. + * + * @author Dmitry Naumenko + * @since 2.0.7 + */ +class IpValidator extends Validator +{ + /** + * The length of IPv6 address in bits + */ + const IPV6_ADDRESS_LENGTH = 128; + /** + * The length of IPv4 address in bits + */ + const IPV4_ADDRESS_LENGTH = 32; + /** + * Negation char. Used to negate [[ranges]] or [[networks]] + * or to negate validating value when [[negation]] is set to `true` + * @see negation + * @see networks + * @see ranges + */ + const NEGATION_CHAR = '!'; + + /** + * @var array The network aliases, that can be used in [[ranges]]. + * - key - alias name + * - value - array of strings. String can be an IP range, IP address or another alias. String can be + * negated with [[NEGATION_CHAR]] (independent of `negation` option). + * + * The following aliases are defined by default: + * - `*`: `any` + * - `any`: `0.0.0.0/0, ::/0` + * - `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8` + * - `multicast`: `224.0.0.0/4, ff00::/8` + * - `linklocal`: `169.254.0.0/16, fe80::/10` + * - `localhost`: `127.0.0.0/8', ::1` + * - `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32` + * - `system`: `multicast, linklocal, localhost, documentation` + * + */ + public $networks = [ + '*' => ['any'], + 'any' => ['0.0.0.0/0', '::/0'], + 'private' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fd00::/8'], + 'multicast' => ['224.0.0.0/4', 'ff00::/8'], + 'linklocal' => ['169.254.0.0/16', 'fe80::/10'], + 'localhost' => ['127.0.0.0/8', '::1'], + 'documentation' => ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', '2001:db8::/32'], + 'system' => ['multicast', 'linklocal', 'localhost', 'documentation'], + ]; + /** + * @var boolean whether the validating value can be an IPv6 address. Defaults to `true`. + */ + public $ipv6 = true; + /** + * @var boolean whether the validating value can be an IPv4 address. Defaults to `true`. + */ + public $ipv4 = true; + /** + * @var boolean whether the address can be an IP with CIDR subnet, like `192.168.10.0/24`. + * The following values are possible: + * + * - `false` - the address must not have a subnet (default). + * - `true` - specifying a subnet is required. + * - `null` - specifying a subnet is optional. + */ + public $subnet = false; + /** + * @var boolean whether to add the CIDR prefix with the smallest length (32 for IPv4 and 128 for IPv6) to an + * address without it. Works only when `subnet` is not `false`. For example: + * - `10.0.1.5` will normalized to `10.0.1.5/32` + * - `2008:db0::1` will be normalized to `2008:db0::1/128` + * Defaults to `false`. + * @see subnet + */ + public $normalize = false; + /** + * @var boolean whether address may have a [[NEGATION_CHAR]] character at the beginning. + * Defaults to `false`. + */ + public $negation = false; + /** + * @var boolean whether to expand an IPv6 address to the full notation format. + * Defaults to `false`. + */ + public $expandIPv6 = false; + /** + * @var string Regexp-pattern to validate IPv4 address + */ + public $ipv4Pattern = '/^(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))$/'; + /** + * @var string Regexp-pattern to validate IPv6 address + */ + public $ipv6Pattern = '/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/'; + /** + * @var string user-defined error message is used when validation fails due to the wrong IP address format. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + */ + public $message; + /** + * @var string user-defined error message is used when validation fails due to the disabled IPv6 validation. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * @see ipv6 + */ + public $ipv6NotAllowed; + /** + * @var string user-defined error message is used when validation fails due to the disabled IPv4 validation. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * @see ipv4 + */ + public $ipv4NotAllowed; + /** + * @var string user-defined error message is used when validation fails due to the wrong CIDR. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * @see subnet + */ + public $wrongCidr; + /** + * @var string user-defined error message is used when validation fails due to subnet [[subnet]] set to 'only', + * but the CIDR prefix is not set. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * @see subnet + */ + public $noSubnet; + /** + * @var string user-defined error message is used when validation fails + * due to [[subnet]] is false, but CIDR prefix is present. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * @see subnet + */ + public $hasSubnet; + /** + * @var string user-defined error message is used when validation fails due to IP address + * is not not allowed by [[ranges]] check. + * + * You may use the following placeholders in the message: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * @see ranges + */ + public $notInRange; + + /** + * @var array + */ + private $_ranges = []; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (!$this->ipv4 && !$this->ipv6) { + throw new InvalidConfigException('Both IPv4 and IPv6 checks can not be disabled at the same time'); + } + + if (!defined('AF_INET6') && $this->ipv6) { + throw new InvalidConfigException('IPv6 validation can not be used. PHP is compiled without IPv6'); + } + + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} must be a valid IP address.'); + } + if ($this->ipv6NotAllowed === null) { + $this->ipv6NotAllowed = Yii::t('yii', '{attribute} must not be an IPv6 address.'); + } + if ($this->ipv4NotAllowed === null) { + $this->ipv4NotAllowed = Yii::t('yii', '{attribute} must not be an IPv4 address.'); + } + if ($this->wrongCidr === null) { + $this->wrongCidr = Yii::t('yii', '{attribute} contains wrong subnet mask.'); + } + if ($this->noSubnet === null) { + $this->noSubnet = Yii::t('yii', '{attribute} must be an IP address with specified subnet.'); + } + if ($this->hasSubnet === null) { + $this->hasSubnet = Yii::t('yii', '{attribute} must not be a subnet.'); + } + if ($this->notInRange === null) { + $this->notInRange = Yii::t('yii', '{attribute} is not in the allowed range.'); + } + } + + /** + * Set the IPv4 or IPv6 ranges that are allowed or forbidden. + * + * The following preparation tasks are performed: + * + * - Recursively substitutes aliases (described in [[networks]]) with their values. + * - Removes duplicates + * + * @property array the IPv4 or IPv6 ranges that are allowed or forbidden. + * See [[setRanges()]] for detailed description. + * @param array $ranges the IPv4 or IPv6 ranges that are allowed or forbidden. + * + * When the array is empty, or the option not set, all IP addresses are allowed. + * + * Otherwise, the rules are checked sequentially until the first match is found. + * An IP address is forbidden, when it has not matched any of the rules. + * + * Example: + * + * ```php + * [ + * 'ranges' => [ + * '192.168.10.128' + * '!192.168.10.0/24', + * 'any' // allows any other IP addresses + * ] + * ] + * ``` + * + * In this example, access is allowed for all the IPv4 and IPv6 addresses excluding the `192.168.10.0/24` subnet. + * IPv4 address `192.168.10.128` is also allowed, because it is listed before the restriction. + */ + public function setRanges($ranges) + { + $this->_ranges = $this->prepareRanges((array) $ranges); + } + + /** + * @return array The IPv4 or IPv6 ranges that are allowed or forbidden. + */ + public function getRanges() + { + return $this->_ranges; + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + $result = $this->validateSubnet($value); + if (is_array($result)) { + $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]); + return $result; + } else { + return null; + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($model, $attribute) + { + $value = $model->$attribute; + + $result = $this->validateSubnet($value); + if (is_array($result)) { + $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]); + $this->addError($model, $attribute, $result[0], $result[1]); + } else { + $model->$attribute = $result; + } + } + + /** + * Validates an IPv4/IPv6 address or subnet + * + * @param $ip string + * @return string|array + * string - the validation was successful; + * array - an error occurred during the validation. + * Array[0] contains the text of an error, array[1] contains values for the placeholders in the error message + */ + private function validateSubnet($ip) + { + if (!is_string($ip)) { + return [$this->message, []]; + } + + $negation = null; + $cidr = null; + $isCidrDefault = false; + + if (preg_match($this->getIpParsePattern(), $ip, $matches)) { + $negation = ($matches[1] !== '') ? $matches[1] : null; + $ip = $matches[2]; + $cidr = isset($matches[4]) ? $matches[4] : null; + } + + if ($this->subnet === true && $cidr === null) { + return [$this->noSubnet, []]; + } + if ($this->subnet === false && $cidr !== null) { + return [$this->hasSubnet, []]; + } + if ($this->negation === false && $negation !== null) { + return [$this->message, []]; + } + + if ($this->getIpVersion($ip) == 6) { + if ($cidr !== null) { + if ($cidr > static::IPV6_ADDRESS_LENGTH || $cidr < 0) { + return [$this->wrongCidr, []]; + } + } else { + $isCidrDefault = true; + $cidr = static::IPV6_ADDRESS_LENGTH; + } + + if (!$this->ipv6) { + return [$this->ipv6NotAllowed, []]; + } + if (!$this->validateIPv6($ip)) { + return [$this->message, []]; + } + + if ($this->expandIPv6) { + $ip = $this->expandIPv6($ip); + } + } else { + if ($cidr !== null) { + if ($cidr > static::IPV4_ADDRESS_LENGTH || $cidr < 0) { + return [$this->wrongCidr, []]; + } + } else { + $isCidrDefault = true; + $cidr = static::IPV4_ADDRESS_LENGTH; + } + + if (!$this->ipv4) { + return [$this->ipv4NotAllowed, []]; + } + if (!$this->validateIPv4($ip)) { + return [$this->message, []]; + } + } + + if (!$this->isAllowed($ip, $cidr)) { + return [$this->notInRange, []]; + } + + $result = $negation . $ip; + + if ($this->subnet !== false && (!$isCidrDefault || $isCidrDefault && $this->normalize)) { + $result .= "/$cidr"; + } + + return $result; + } + + /** + * Expands an IPv6 address to it's full notation. For example `2001:db8::1` will be + * expanded to `2001:0db8:0000:0000:0000:0000:0000:0001` + * + * @param string $ip the original IPv6 + * @return string the expanded IPv6 + */ + private function expandIPv6($ip) + { + $hex = unpack('H*hex', inet_pton($ip)); + return substr(preg_replace('/([a-f0-9]{4})/i', '$1:', $hex['hex']), 0, -1); + } + + /** + * The method checks whether the IP address with specified CIDR is allowed according to the [[ranges]] list. + * + * @param string $ip + * @param integer $cidr + * @return boolean + * @see ranges + */ + private function isAllowed($ip, $cidr) + { + if (empty($this->ranges)) { + return true; + } + + foreach ($this->ranges as $string) { + list($isNegated, $range) = $this->parseNegatedRange($string); + if ($this->inRange($ip, $cidr, $range)) { + return !$isNegated; + } + } + + return false; + } + + /** + * Parses IP address/range for the negation with [[NEGATION_CHAR]]. + * + * @param $string + * @return array `[0 => boolean, 1 => string]` + * - boolean: whether the string is negated + * - string: the string without negation (when the negation were present) + */ + private function parseNegatedRange($string) + { + $isNegated = strpos($string, static::NEGATION_CHAR) === 0; + return [$isNegated, $isNegated ? substr($string, strlen(static::NEGATION_CHAR)) : $string]; + } + + /** + * Prepares array to fill in [[ranges]]: + * - Recursively substitutes aliases, described in [[networks]] with their values + * - Removes duplicates + * + * + * @param $ranges + * @return array + * @see networks + */ + private function prepareRanges($ranges) + { + $result = []; + foreach ($ranges as $string) { + list($isRangeNegated, $range) = $this->parseNegatedRange($string); + if (isset($this->networks[$range])) { + $replacements = $this->prepareRanges($this->networks[$range]); + foreach ($replacements as &$replacement) { + list($isReplacementNegated, $replacement) = $this->parseNegatedRange($replacement); + $result[] = ($isRangeNegated && !$isReplacementNegated ? static::NEGATION_CHAR : '') . $replacement; + } + } else { + $result[] = $string; + } + } + return array_unique($result); + } + + /** + * Validates IPv4 address + * + * @param string $value + * @return boolean + */ + protected function validateIPv4($value) + { + return preg_match($this->ipv4Pattern, $value) !== 0; + } + + /** + * Validates IPv6 address + * + * @param string $value + * @return boolean + */ + protected function validateIPv6($value) + { + return preg_match($this->ipv6Pattern, $value) !== 0; + } + + /** + * Gets the IP version + * + * @param string $ip + * @return integer + */ + private function getIpVersion($ip) + { + return strpos($ip, ':') === false ? 4 : 6; + } + + /** + * Used to get the Regexp pattern for initial IP address parsing + * @return string + */ + private function getIpParsePattern() + { + return '/^(' . preg_quote(static::NEGATION_CHAR) . '?)(.+?)(\/(\d+))?$/'; + } + + /** + * Checks whether the IP is in subnet range + * + * @param string $ip an IPv4 or IPv6 address + * @param integer $cidr + * @param string $range subnet in CIDR format e.g. `10.0.0.0/8` or `2001:af::/64` + * @return boolean + */ + private function inRange($ip, $cidr, $range) + { + $ipVersion = $this->getIpVersion($ip); + $binIp = $this->ip2bin($ip); + + $parts = explode('/', $range); + $net = array_shift($parts); + $range_cidr = array_shift($parts); + + + $netVersion = $this->getIpVersion($net); + if ($ipVersion !== $netVersion) { + return false; + } + if ($range_cidr === null) { + $range_cidr = $netVersion === 4 ? static::IPV4_ADDRESS_LENGTH : static::IPV6_ADDRESS_LENGTH; + } + + $binNet = $this->ip2bin($net); + return substr($binIp, 0, $range_cidr) === substr($binNet, 0, $range_cidr) && $cidr >= $range_cidr; + } + + /** + * Converts IP address to bits representation + * + * @param string $ip + * @return string bits as a string + */ + private function ip2bin($ip) + { + if ($this->getIpVersion($ip) === 4) { + return str_pad(base_convert(ip2long($ip), 10, 2), static::IPV4_ADDRESS_LENGTH, '0', STR_PAD_LEFT); + } else { + $unpack = unpack('A16', inet_pton($ip)); + $binStr = array_shift($unpack); + $bytes = static::IPV6_ADDRESS_LENGTH / 8; // 128 bit / 8 = 16 bytes + $result = ''; + while ($bytes-- > 0) { + $result = sprintf('%08b', isset($binStr[$bytes]) ? ord($binStr[$bytes]) : '0') . $result; + } + return $result; + } + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($model, $attribute, $view) + { + $messages = [ + 'ipv6NotAllowed' => $this->ipv6NotAllowed, + 'ipv4NotAllowed' => $this->ipv4NotAllowed, + 'message' => $this->message, + 'noSubnet' => $this->noSubnet, + 'hasSubnet' => $this->hasSubnet, + ]; + foreach ($messages as &$message) { + $message = Yii::$app->getI18n()->format($message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ], Yii::$app->language); + } + + $options = [ + 'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv4Pattern)), + 'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv6Pattern)), + 'messages' => $messages, + 'ipv4' => (boolean)$this->ipv4, + 'ipv6' => (boolean)$this->ipv6, + 'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())), + 'negation' => $this->negation, + 'subnet' => $this->subnet, + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');'; + } +} diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index 2defac9f9d..35ffeb52c2 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -101,7 +101,7 @@ class NumberValidator extends Validator */ protected function validateValue($value) { - if (is_array($value)) { + if (is_array($value) || is_object($value)) { return [Yii::t('yii', '{attribute} is invalid.'), []]; } $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern; @@ -133,7 +133,7 @@ class NumberValidator extends Validator if ($this->min !== null) { // ensure numeric value to make javascript comparison equal to PHP comparison // https://github.com/yiisoft/yii2/issues/3118 - $options['min'] = is_string($this->min) ? (float)$this->min : $this->min; + $options['min'] = is_string($this->min) ? (float) $this->min : $this->min; $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ 'attribute' => $label, 'min' => $this->min, @@ -142,7 +142,7 @@ class NumberValidator extends Validator if ($this->max !== null) { // ensure numeric value to make javascript comparison equal to PHP comparison // https://github.com/yiisoft/yii2/issues/3118 - $options['max'] = is_string($this->max) ? (float)$this->max : $this->max; + $options['max'] = is_string($this->max) ? (float) $this->max : $this->max; $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ 'attribute' => $label, 'max' => $this->max, @@ -154,10 +154,14 @@ class NumberValidator extends Validator ValidationAsset::register($view); +<<<<<<< HEAD <<<<<<< HEAD return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; ======= return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');'; >>>>>>> yiichina/master +======= + return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');'; +>>>>>>> master } } diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php index 04f37b293c..27e128f09a 100644 --- a/framework/validators/RangeValidator.php +++ b/framework/validators/RangeValidator.php @@ -9,6 +9,7 @@ namespace yii\validators; use Yii; use yii\base\InvalidConfigException; +use yii\helpers\ArrayHelper; /** * RangeValidator validates that the attribute value is among a list of values. @@ -23,7 +24,15 @@ use yii\base\InvalidConfigException; class RangeValidator extends Validator { /** - * @var array list of valid values that the attribute value should be among + * @var array|\Traversable|\Closure a list of valid values that the attribute value should be among or an anonymous function that returns + * such a list. The signature of the anonymous function should be as follows, + * + * ```php + * function($model, $attribute) { + * // compute range + * return $range; + * } + * ``` */ public $range; /** @@ -47,7 +56,10 @@ class RangeValidator extends Validator public function init() { parent::init(); - if (!is_array($this->range)) { + if (!is_array($this->range) + && !($this->range instanceof \Closure) + && !($this->range instanceof \Traversable) + ) { throw new InvalidConfigException('The "range" property must be set.'); } if ($this->message === null) { @@ -60,27 +72,42 @@ class RangeValidator extends Validator */ protected function validateValue($value) { - if (!$this->allowArray && is_array($value)) { - return [$this->message, []]; + $in = false; + + if ($this->allowArray + && ($value instanceof \Traversable || is_array($value)) + && ArrayHelper::isSubset($value, $this->range, $this->strict) + ) { + $in = true; } - $in = true; - - foreach ((is_array($value) ? $value : [$value]) as $v) { - if (!in_array($v, $this->range, $this->strict)) { - $in = false; - break; - } + if (!$in && ArrayHelper::isIn($value, $this->range, $this->strict)) { + $in = true; } return $this->not !== $in ? null : [$this->message, []]; } + /** + * @inheritdoc + */ + public function validateAttribute($model, $attribute) + { + if ($this->range instanceof \Closure) { + $this->range = call_user_func($this->range, $model, $attribute); + } + parent::validateAttribute($model, $attribute); + } + /** * @inheritdoc */ public function clientValidateAttribute($model, $attribute, $view) { + if ($this->range instanceof \Closure) { + $this->range = call_user_func($this->range, $model, $attribute); + } + $range = []; foreach ($this->range as $value) { $range[] = (string) $value; diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php index a38da13362..33a0fa9579 100644 --- a/framework/validators/RegularExpressionValidator.php +++ b/framework/validators/RegularExpressionValidator.php @@ -9,6 +9,7 @@ namespace yii\validators; use Yii; use yii\base\InvalidConfigException; +use yii\helpers\Html; use yii\web\JsExpression; use yii\helpers\Json; @@ -64,19 +65,7 @@ class RegularExpressionValidator extends Validator */ public function clientValidateAttribute($model, $attribute, $view) { - $pattern = $this->pattern; - $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern); - $deliminator = substr($pattern, 0, 1); - $pos = strrpos($pattern, $deliminator, 1); - $flag = substr($pattern, $pos + 1); - if ($deliminator !== '/') { - $pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/'; - } else { - $pattern = substr($pattern, 0, $pos + 1); - } - if (!empty($flag)) { - $pattern .= preg_replace('/[^igm]/', '', $flag); - } + $pattern = Html::escapeJsRegularExpression($this->pattern); $options = [ 'pattern' => new JsExpression($pattern), @@ -91,10 +80,14 @@ class RegularExpressionValidator extends Validator ValidationAsset::register($view); +<<<<<<< HEAD <<<<<<< HEAD return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; ======= return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');'; >>>>>>> yiichina/master +======= + return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');'; +>>>>>>> master } } diff --git a/framework/validators/SafeValidator.php b/framework/validators/SafeValidator.php index 846d63e68a..ec0252d340 100644 --- a/framework/validators/SafeValidator.php +++ b/framework/validators/SafeValidator.php @@ -10,6 +10,14 @@ namespace yii\validators; /** * SafeValidator serves as a dummy validator whose main purpose is to mark the attributes to be safe for massive assignment. * + * This class is required because of the way in which Yii determines whether a property is safe for massive assignment, that is, + * when a user submits form data to be loaded into a model directly from the POST data, is it ok for a property to be copied. + * In many cases, this is required but because sometimes properties are internal and you do not want the POST data to be able to + * override these internal values (especially things like database row ids), Yii assumes all values are unsafe for massive assignment + * unless a validation rule exists for the property, which in most cases it will. Sometimes, however, an item is safe for massive assigment but + * does not have a validation rule associated with it - for instance, due to no validation being performed, in which case, you use this class + * as a validation rule for that property. Although it has no functionality, it allows Yii to determine that the property is safe to copy. + * * @author Qiang Xue * @since 2.0 */ diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index c3199b3e56..f798ac1021 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -16,11 +16,15 @@ use yii\db\ActiveRecordInterface; * UniqueValidator checks if the value being validated is unique in the table column specified by * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]]. * +<<<<<<< HEAD <<<<<<< HEAD * The followings are examples of validation rules using this validator: ======= * The following are examples of validation rules using this validator: >>>>>>> yiichina/master +======= + * The following are examples of validation rules using this validator: +>>>>>>> master * * ```php * // a1 needs to be unique @@ -87,7 +91,7 @@ class UniqueValidator extends Validator if (is_array($targetAttribute)) { $params = []; foreach ($targetAttribute as $k => $v) { - $params[$v] = is_integer($k) ? $model->$v : $model->$k; + $params[$v] = is_int($k) ? $model->$v : $model->$k; } } else { $params = [$targetAttribute => $model->$attribute]; @@ -110,8 +114,9 @@ class UniqueValidator extends Validator $query->andWhere($this->filter); } - if (!$model instanceof ActiveRecordInterface || $model->getIsNewRecord()) { + if (!$model instanceof ActiveRecordInterface || $model->getIsNewRecord() || $model->className() !== $targetClass::className()) { // if current $model isn't in the database yet then it's OK just to call exists() + // also there's no need to run check based on primary keys, when $targetClass is not the same as $model's class $exists = $query->exists(); } else { // if current $model is in the database already we can't use exists() diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php index 15623eff77..2bdd534d36 100644 --- a/framework/validators/UrlValidator.php +++ b/framework/validators/UrlValidator.php @@ -16,7 +16,7 @@ use yii\helpers\Json; * UrlValidator validates that the attribute value is a valid http or https URL. * * Note that this validator only checks if the URL scheme and host part are correct. - * It does not check the rest part of a URL. + * It does not check the remaining parts of a URL. * * @author Qiang Xue * @since 2.0 @@ -28,7 +28,7 @@ class UrlValidator extends Validator * The pattern may contain a `{schemes}` token that will be replaced * by a regular expression which represents the [[validSchemes]]. */ - public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i'; + public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(?::\d{1,5})?(?:$|[?\/#])/i'; /** * @var array list of URI schemes which should be considered valid. By default, http and https * are considered to be valid schemes. @@ -124,7 +124,7 @@ class UrlValidator extends Validator 'message' => Yii::$app->getI18n()->format($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), ], Yii::$app->language), - 'enableIDN' => (boolean) $this->enableIDN, + 'enableIDN' => (bool) $this->enableIDN, ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; @@ -138,10 +138,14 @@ class UrlValidator extends Validator PunycodeAsset::register($view); } +<<<<<<< HEAD <<<<<<< HEAD return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; ======= return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');'; >>>>>>> yiichina/master +======= + return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');'; +>>>>>>> master } } diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index 1cee31f318..ce48f7296d 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -18,7 +18,7 @@ use yii\base\NotSupportedException; * logic of performing data validation. Child classes may also override [[clientValidateAttribute()]] * to provide client-side validation support. * - * Validator declares a set of [[builtInValidators|built-in validators] which can + * Validator declares a set of [[builtInValidators|built-in validators]] which can * be referenced using short names. They are listed as follows: * * - `boolean`: [[BooleanValidator]] @@ -28,9 +28,13 @@ use yii\base\NotSupportedException; * - `default`: [[DefaultValueValidator]] * - `double`: [[NumberValidator]] <<<<<<< HEAD +<<<<<<< HEAD ======= * - `each`: [[EachValidator]] >>>>>>> yiichina/master +======= + * - `each`: [[EachValidator]] +>>>>>>> master * - `email`: [[EmailValidator]] * - `exist`: [[ExistValidator]] * - `file`: [[FileValidator]] @@ -45,6 +49,7 @@ use yii\base\NotSupportedException; * - `trim`: [[FilterValidator]] * - `unique`: [[UniqueValidator]] * - `url`: [[UrlValidator]] + * - `ip`: [[IpValidator]] * * @author Qiang Xue * @since 2.0 @@ -62,9 +67,13 @@ class Validator extends Component 'default' => 'yii\validators\DefaultValueValidator', 'double' => 'yii\validators\NumberValidator', <<<<<<< HEAD +<<<<<<< HEAD ======= 'each' => 'yii\validators\EachValidator', >>>>>>> yiichina/master +======= + 'each' => 'yii\validators\EachValidator', +>>>>>>> master 'email' => 'yii\validators\EmailValidator', 'exist' => 'yii\validators\ExistValidator', 'file' => 'yii\validators\FileValidator', @@ -87,6 +96,7 @@ class Validator extends Component ], 'unique' => 'yii\validators\UniqueValidator', 'url' => 'yii\validators\UrlValidator', + 'ip' => 'yii\validators\IpValidator', ]; /** * @var array|string attributes to be validated by this validator. For multiple attributes, @@ -162,7 +172,8 @@ class Validator extends Component /** * @var string a JavaScript function name whose return value determines whether this validator should be applied * on the client side. The signature of the function should be `function (attribute, value)`, where - * `attribute` is the name of the attribute being validated and `value` the current value of the attribute. + * `attribute` is an object describing the attribute being validated (see [[clientValidateAttribute()]]) + * and `value` the current value of the attribute. * * This property is mainly provided to support conditional validation on the client side. * If this property is not set, this validator will be always applied on the client side. @@ -171,7 +182,7 @@ class Validator extends Component * * ```php * function (attribute, value) { - * return $('#country').value == 'USA'; + * return $('#country').val() === 'USA'; * } * ``` * @@ -227,17 +238,26 @@ class Validator extends Component * Validates the specified object. * @param \yii\base\Model $model the data model being validated * @param array|null $attributes the list of attributes to be validated. - * Note that if an attribute is not associated with the validator, - * it will be ignored. - * If this parameter is null, every attribute listed in [[attributes]] will be validated. + * Note that if an attribute is not associated with the validator, or is is prefixed with `!` char - it will be + * ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated. */ public function validateAttributes($model, $attributes = null) { if (is_array($attributes)) { - $attributes = array_intersect($this->attributes, $attributes); + $newAttributes = []; + foreach ($attributes as $attribute) { + if (in_array($attribute, $this->attributes) || in_array('!' . $attribute, $this->attributes)) { + $newAttributes[] = $attribute; + } + } + $attributes = $newAttributes; } else { - $attributes = $this->attributes; + $attributes = []; + foreach ($this->attributes as $attribute) { + $attributes[] = $attribute[0] === '!' ? substr($attribute, 1) : $attribute; + } } + foreach ($attributes as $attribute) { $skip = $this->skipOnError && $model->hasErrors($attribute) || $this->skipOnEmpty && $this->isEmpty($model->$attribute); @@ -279,7 +299,13 @@ class Validator extends Component list($message, $params) = $result; $params['attribute'] = Yii::t('yii', 'the input value'); - $params['value'] = is_array($value) ? 'array()' : $value; + if (is_array($value)) { + $params['value'] = 'array()'; + } elseif (is_object($value)) { + $params['value'] = 'object'; + } else { + $params['value'] = $value; + } $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language); return false; @@ -306,10 +332,19 @@ class Validator extends Component * * The following JavaScript variables are predefined and can be used in the validation code: * - * - `attribute`: the name of the attribute being validated. + * - `attribute`: an object describing the the attribute being validated. * - `value`: the value being validated. * - `messages`: an array used to hold the validation error messages for the attribute. * - `deferred`: an array used to hold deferred objects for asynchronous validation + * - `$form`: a jQuery object containing the form element + * + * The `attribute` object contains the following properties: + * - `id`: a unique ID identifying the attribute (e.g. "loginform-username") in the form + * - `name`: attribute name or expression (e.g. "[0]content" for tabular input) + * - `container`: the jQuery selector of the container of the input field + * - `input`: the jQuery selector of the input field under the context of the form + * - `error`: the jQuery selector of the error tag under the context of the container + * - `status`: status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating * * @param \yii\base\Model $model the data model being validated * @param string $attribute the name of the attribute to be validated. @@ -350,19 +385,25 @@ class Validator extends Component */ public function addError($model, $attribute, $message, $params = []) { - $value = $model->$attribute; $params['attribute'] = $model->getAttributeLabel($attribute); - $params['value'] = is_array($value) ? 'array()' : $value; + if (!isset($params['value'])) { + $value = $model->$attribute; + $params['value'] = is_array($value) ? 'array()' : $value; + } $model->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language)); } /** * Checks if the given value is empty. +<<<<<<< HEAD <<<<<<< HEAD * A value is considered empty if it is null, an empty array, or the trimmed result is an empty string. ======= * A value is considered empty if it is null, an empty array, or an empty string. >>>>>>> yiichina/master +======= + * A value is considered empty if it is null, an empty array, or an empty string. +>>>>>>> master * Note that this method is different from PHP empty(). It will return false when the value is 0. * @param mixed $value the value to be checked * @return boolean whether the value is empty diff --git a/framework/views/_addColumns.php b/framework/views/_addColumns.php new file mode 100644 index 0000000000..d929e52dc8 --- /dev/null +++ b/framework/views/_addColumns.php @@ -0,0 +1,14 @@ + + $this->addColumn('', '', $this->); +render('_addForeignKeys', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]); diff --git a/framework/views/_addForeignKeys.php b/framework/views/_addForeignKeys.php new file mode 100644 index 0000000000..0b5f2c25ed --- /dev/null +++ b/framework/views/_addForeignKeys.php @@ -0,0 +1,19 @@ + $fkData): ?> + + // creates index for column `` + $this->createIndex( + '', + '', + '' + ); + + // add foreign key for table `` + $this->addForeignKey( + '', + '', + '', + '', + 'id', + 'CASCADE' + ); + $this->createTable('', [ + + '', + + \$this->{$field['decorators']}" ?>, + + ]); +render('_addForeignKeys', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]); diff --git a/framework/views/_dropColumns.php b/framework/views/_dropColumns.php new file mode 100644 index 0000000000..109c57db47 --- /dev/null +++ b/framework/views/_dropColumns.php @@ -0,0 +1,10 @@ +render('_dropForeignKeys', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]); + +foreach ($fields as $field): ?> + $this->dropColumn('', ''); + $fkData): ?> + // drops foreign key for table `` + $this->dropForeignKey( + '', + '' + ); + + // drops index for column `` + $this->dropIndex( + '', + '' + ); + +render('_dropForeignKeys', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]) ?> + $this->dropTable(''); diff --git a/framework/views/_foreignTables.php b/framework/views/_foreignTables.php new file mode 100644 index 0000000000..cdeb76be31 --- /dev/null +++ b/framework/views/_foreignTables.php @@ -0,0 +1,14 @@ + + * Has foreign keys to the tables: + * + + * - `` + + +use yii\db\Migration; + +/** + * Handles adding to table ``. +render('_foreignTables', [ + 'foreignKeys' => $foreignKeys, + ]) ?> + */ +class extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { +render('_addColumns', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } + + /** + * @inheritdoc + */ + public function down() + { +render('_dropColumns', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } +} diff --git a/framework/views/createJunctionMigration.php b/framework/views/createJunctionMigration.php new file mode 100644 index 0000000000..0aaf31e628 --- /dev/null +++ b/framework/views/createJunctionMigration.php @@ -0,0 +1,73 @@ + + +use yii\db\Migration; + +/** + * Handles the creation of table `` which is a junction between + * table `` and table ``. + */ +class extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('', [ + '_id' => $this->integer(), + '_id' => $this->integer(), + 'PRIMARY KEY(_id, _id)', + ]); + + $this->createIndex( + 'idx-_id', + '', + '_id' + ); + + $this->createIndex( + 'idx-_id', + '', + '_id' + ); + + $this->addForeignKey( + 'fk-_id', + '', + '_id', + '', + 'id', + 'CASCADE' + ); + + $this->addForeignKey( + 'fk-_id', + '', + '_id', + '', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable(''); + } +} diff --git a/framework/views/createTableMigration.php b/framework/views/createTableMigration.php new file mode 100644 index 0000000000..a4a6f29b4f --- /dev/null +++ b/framework/views/createTableMigration.php @@ -0,0 +1,48 @@ + + +use yii\db\Migration; + +/** + * Handles the creation for table ``. +render('_foreignTables', [ + 'foreignKeys' => $foreignKeys, +]) ?> + */ +class extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { +render('_createTable', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } + + /** + * @inheritdoc + */ + public function down() + { +render('_dropTable', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]) +?> + } +} diff --git a/framework/views/dropColumnMigration.php b/framework/views/dropColumnMigration.php new file mode 100644 index 0000000000..c975e748ae --- /dev/null +++ b/framework/views/dropColumnMigration.php @@ -0,0 +1,50 @@ + + +use yii\db\Migration; + +/** + * Handles dropping from table ``. +render('_foreignTables', [ + 'foreignKeys' => $foreignKeys, +]) ?> + */ +class extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { +render('_dropColumns', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } + + /** + * @inheritdoc + */ + public function down() + { +render('_addColumns', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } +} diff --git a/framework/views/dropTableMigration.php b/framework/views/dropTableMigration.php new file mode 100644 index 0000000000..ebaa7dc219 --- /dev/null +++ b/framework/views/dropTableMigration.php @@ -0,0 +1,47 @@ + + +use yii\db\Migration; + +/** + * Handles the dropping for table ``. +render('_foreignTables', [ + 'foreignKeys' => $foreignKeys, +]) ?> + */ +class extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { +render('_dropTable', [ + 'table' => $table, + 'foreignKeys' => $foreignKeys, +]) +?> + } + + /** + * @inheritdoc + */ + public function down() + { +render('_createTable', [ + 'table' => $table, + 'fields' => $fields, + 'foreignKeys' => $foreignKeys, +]) +?> + } +} diff --git a/framework/views/errorHandler/callStackItem.php b/framework/views/errorHandler/callStackItem.php index 0f30012a1e..7144965b20 100644 --- a/framework/views/errorHandler/callStackItem.php +++ b/framework/views/errorHandler/callStackItem.php @@ -7,6 +7,7 @@ /* @var $lines string[] */ /* @var $begin integer */ /* @var $end integer */ +/* @var $args array */ /* @var $handler \yii\web\ErrorHandler */ ?>
      • . htmlEncode($file); ?> + + + + addTypeLinks("$class::$method") : $handler->htmlEncode($method)) . '(' . $handler->argumentsToString($args) . ')' ?> - - @@ -34,7 +37,7 @@
        htmlEncode($lines[$i]);
        +                        echo (trim($lines[$i]) === '') ? " \n" : $handler->htmlEncode($lines[$i]);
                             }
                         ?>
        diff --git a/framework/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php index 7995db16cf..03a384cf88 100644 --- a/framework/views/errorHandler/exception.php +++ b/framework/views/errorHandler/exception.php @@ -52,8 +52,11 @@ h1,h2,h3,p,img,ul li{ font-family: Arial,sans-serif; color: #505050; } -html,body{ - overflow-x: hidden; +/*corresponds to min-width of 860px for some elements (.header .footer .element ...)*/ +@media screen and (min-width: 960px) { + html,body{ + overflow-x: hidden; + } } /* header */ @@ -152,6 +155,7 @@ html,body{ .call-stack ul li .element-wrap{ cursor: pointer; padding: 15px 0; + background-color: #fdfdfd; } .call-stack ul li.application .element-wrap{ background-color: #fafafa; @@ -164,7 +168,6 @@ html,body{ margin: 0 auto; padding: 0 50px; position: relative; - white-space: nowrap; } .call-stack ul li a{ color: #505050; @@ -183,17 +186,19 @@ html,body{ color: #505050; } .call-stack ul li .at{ - position: absolute; - right: 110px; /* 50px + 60px */ + float: right; + display: inline-block; + width: 7em; + padding-left: 1em; + text-align: left; color: #aaa; } .call-stack ul li.application .at{ color: #505050; } .call-stack ul li .line{ - position: absolute; - right: 50px; - width: 60px; + display: inline-block; + width: 3em; text-align: right; } .call-stack ul li .code-wrap{ @@ -209,7 +214,7 @@ html,body{ position: absolute; width: 100%; z-index: 100; - margin-top: -61px; + margin-top: 0; } .call-stack ul li .hover-line{ background: none; @@ -233,7 +238,7 @@ html,body{ color: #aaa; line-height: 20px; font-size: 12px; - margin-top: -63px; + margin-top: 1px; font-family: Consolas, Courier New, monospace; } .call-stack ul li .code pre{ @@ -328,6 +333,13 @@ html,body{ .variable{ color: #a00; } + +body pre { + pointer-events: none; +} +body.mousedown pre { + pointer-events: auto; +} @@ -370,7 +382,7 @@ html,body{ renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1) ?> getTrace(), $length = count($trace); $i < $length; ++$i): ?> renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, - @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $trace[$i]['args'], $i + 2) ?> + @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, @$trace[$i]['args'] ?: [], $i + 2) ?> @@ -392,71 +404,34 @@ html,body{ var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
        ")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.php=function(a){var e={cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var b=[a.inherit(a.ASM,{i:null}),a.inherit(a.QSM,{i:null}),{cN:"string",b:'b"',e:'"',c:[a.BE]},{cN:"string",b:"b'",e:"'",c:[a.BE]}];var c=[a.BNM,a.CNM];var d={cN:"title",b:a.UIR};return{cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return implements parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception php_user_filter default die require __FUNCTION__ enddeclare final try this switch continue endfor endif declare unset true false namespace trait goto instanceof insteadof __DIR__ __NAMESPACE__ __halt_compiler",c:[a.CLCM,a.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"}]},{cN:"comment",eB:true,b:"__halt_compiler.+?;",eW:true},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[a.BE]},{cN:"preprocessor",b:"<\\?php",r:10},{cN:"preprocessor",b:"\\?>"},e,{cN:"function",bWK:true,e:"{",k:"function",i:"\\$|\\[|%",c:[d,{cN:"params",b:"\\(",e:"\\)",c:["self",e,a.CBLCLM].concat(b).concat(c)}]},{cN:"class",bWK:true,e:"{",k:"class",i:"[:\\(\\$]",c:[{bWK:true,eW:true,k:"extends",c:[d]},d]},{b:"=>"}].concat(b).concat(c)}}(hljs); - - endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> diff --git a/framework/views/messageConfig.php b/framework/views/messageConfig.php index caab3c1bf8..f7666f67fc 100644 --- a/framework/views/messageConfig.php +++ b/framework/views/messageConfig.php @@ -16,15 +16,24 @@ return [ // messages will be separated from the old (translated) ones. 'sort' => false, // boolean, whether to remove messages that no longer appear in the source code. - // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. + // Defaults to false, which means these messages will NOT be removed. 'removeUnused' => false, <<<<<<< HEAD +<<<<<<< HEAD ======= +======= + // boolean, whether to mark messages that no longer appear in the source code. + // Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks. + 'markUnused' => true, +>>>>>>> master // array, list of patterns that specify which files (not directories) should be processed. // If empty or not set, all files will be processed. // Please refer to "except" for details about the patterns. 'only' => ['*.php'], +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master // array, list of patterns that specify which files/directories should NOT be processed. // If empty or not set, all files/directories will be processed. // A path matches a pattern if it contains the pattern string at its end. For example, @@ -33,6 +42,7 @@ return [ // and the '.svn' will match all files and directories named exactly '.svn'. // Note, the '/' characters in a pattern matches both '/' and '\'. // See helpers/FileHelper::findFiles() description for more details on pattern matching rules. +<<<<<<< HEAD <<<<<<< HEAD 'only' => ['*.php'], // array, list of patterns that specify which files (not directories) should be processed. @@ -40,6 +50,8 @@ return [ // Please refer to "except" for details about the patterns. ======= >>>>>>> yiichina/master +======= +>>>>>>> master // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. 'except' => [ '.svn', diff --git a/framework/views/migration.php b/framework/views/migration.php index cebeb758b6..52241db1a8 100644 --- a/framework/views/migration.php +++ b/framework/views/migration.php @@ -8,7 +8,6 @@ echo " -use yii\db\Schema; use yii\db\Migration; class extends Migration @@ -24,13 +23,13 @@ class extends Migration return false; } - + /* // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } - + public function safeDown() { } diff --git a/framework/web/Application.php b/framework/web/Application.php index a3d7d37ac1..045a54dd59 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -34,13 +34,13 @@ class Application extends \yii\base\Application * The rest of the array elements (key-value pairs) specify the parameters to be bound * to the action. For example, * - * ~~~ + * ```php * [ * 'offline/notice', * 'param1' => 'value1', * 'param2' => 'value2', * ] - * ~~~ + * ``` * * Defaults to null, meaning catch-all is not used. */ @@ -75,12 +75,17 @@ class Application extends \yii\base\Application list ($route, $params) = $request->resolve(); } else { $route = $this->catchAll[0]; +<<<<<<< HEAD <<<<<<< HEAD $params = array_splice($this->catchAll, 1); ======= $params = $this->catchAll; unset($params[0]); >>>>>>> yiichina/master +======= + $params = $this->catchAll; + unset($params[0]); +>>>>>>> master } try { Yii::trace("Route requested: '$route'", __METHOD__); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index e3f16c1ae7..ac9c19d2f7 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -8,6 +8,7 @@ namespace yii\web; use yii\base\Object; +use yii\helpers\ArrayHelper; use yii\helpers\Url; use Yii; @@ -38,9 +39,13 @@ class AssetBundle extends Object * * You can use either a directory or an alias of the directory. <<<<<<< HEAD +<<<<<<< HEAD ======= * @see $publishOptions >>>>>>> yiichina/master +======= + * @see $publishOptions +>>>>>>> master */ public $sourcePath; /** @@ -84,15 +89,18 @@ class AssetBundle extends Object * - a relative path representing a local asset (e.g. `js/main.js`). The actual file path of a local * asset can be determined by prefixing [[basePath]] to the relative path, and the actual URL * of the asset can be determined by prefixing [[baseUrl]] to the relative path. + * - an array, with the first entry being the URL or relative path as described before, and a list of key => value pairs + * that will be used to overwrite [[jsOptions]] settings for this entry. + * This functionality is available since version 2.0.7. * - * Note that only forward slash "/" should be used as directory separators. + * Note that only a forward slash "/" should be used as directory separator. */ public $js = []; /** * @var array list of CSS files that this bundle contains. Each CSS file can be specified * in one of the three formats as explained in [[js]]. * - * Note that only forward slash "/" can be used as directory separator. + * Note that only a forward slash "/" should be used as directory separator. */ public $css = []; /** @@ -147,10 +155,26 @@ class AssetBundle extends Object { $manager = $view->getAssetManager(); foreach ($this->js as $js) { - $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions); + if (is_array($js)) { + $file = array_shift($js); + $options = ArrayHelper::merge($this->jsOptions, $js); + $view->registerJsFile($manager->getAssetUrl($this, $file), $options); + } else { + if ($js !== null) { + $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions); + } + } } foreach ($this->css as $css) { - $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions); + if (is_array($css)) { + $file = array_shift($css); + $options = ArrayHelper::merge($this->cssOptions, $css); + $view->registerCssFile($manager->getAssetUrl($this, $file), $options); + } else { + if ($css !== null) { + $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions); + } + } } } @@ -168,12 +192,26 @@ class AssetBundle extends Object if (isset($this->basePath, $this->baseUrl) && ($converter = $am->getConverter()) !== null) { foreach ($this->js as $i => $js) { - if (Url::isRelative($js)) { + if (is_array($js)) { + $file = array_shift($js); + if (Url::isRelative($file)) { + $js = ArrayHelper::merge($this->jsOptions, $js); + array_unshift($js, $converter->convert($file, $this->basePath)); + $this->js[$i] = $js; + } + } elseif (Url::isRelative($js)) { $this->js[$i] = $converter->convert($js, $this->basePath); } } foreach ($this->css as $i => $css) { - if (Url::isRelative($css)) { + if (is_array($css)) { + $file = array_shift($css); + if (Url::isRelative($file)) { + $css = ArrayHelper::merge($this->cssOptions, $css); + array_unshift($css, $converter->convert($file, $this->basePath)); + $this->css[$i] = $css; + } + } elseif (Url::isRelative($css)) { $this->css[$i] = $converter->convert($css, $this->basePath); } } diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php index ac178ee70a..d708f2ffce 100644 --- a/framework/web/AssetConverter.php +++ b/framework/web/AssetConverter.php @@ -65,7 +65,7 @@ class AssetConverter extends Component implements AssetConverterInterface if (isset($this->commands[$ext])) { list ($ext, $command) = $this->commands[$ext]; $result = substr($asset, 0, $pos + 1) . $ext; - if ($this->forceConvert || @filemtime("$basePath/$result") < filemtime("$basePath/$asset")) { + if ($this->forceConvert || @filemtime("$basePath/$result") < @filemtime("$basePath/$asset")) { $this->runCommand($command, $basePath, $asset, $result); } diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 61f417c370..a18710b836 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -54,13 +54,13 @@ class AssetManager extends Component * The following example shows how to disable the bootstrap css file used by Bootstrap widgets * (because you want to use your own styles): * - * ~~~ + * ```php * [ * 'yii\bootstrap\BootstrapAsset' => [ * 'css' => [], * ], * ] - * ~~~ + * ``` */ public $bundles = []; /** @@ -81,7 +81,8 @@ class AssetManager extends Component * the corresponding value will replace the asset and be registered with the view. * For example, an asset file `my/path/to/jquery.js` matches a key `jquery.js`. * - * Note that the target asset files should be either absolute URLs or paths relative to [[baseUrl]] and [[basePath]]. + * Note that the target asset files should be absolute URLs, domain relative URLs (starting from '/') or paths + * relative to [[baseUrl]] and [[basePath]]. * * In the following example, any assets ending with `jquery.min.js` will be replaced with `jquery/dist/jquery.js` * which is relative to [[baseUrl]] and [[basePath]]. @@ -91,6 +92,14 @@ class AssetManager extends Component * 'jquery.min.js' => 'jquery/dist/jquery.js', * ] * ``` + * + * You may also use aliases while specifying map value, for example: + * + * ```php + * [ + * 'jquery.min.js' => '@web/js/jquery/jquery.js', + * ] + * ``` */ public $assetMap = []; /** @@ -106,9 +115,9 @@ class AssetManager extends Component * to Web users. For example, for Apache Web server, the following configuration directive should be added * for the Web folder: * - * ~~~ + * ```apache * Options FollowSymLinks - * ~~~ + * ``` */ public $linkAssets = false; /** @@ -159,6 +168,33 @@ class AssetManager extends Component * @since 2.0.3 */ public $appendTimestamp = false; + /** + * @var callable a callback that will be called to produce hash for asset directory generation. + * The signature of the callback should be as follows: + * + * ``` + * function ($path) + * ``` + * + * where `$path` is the asset path. Note that the `$path` can be either directory where the asset + * files reside or a single file. For a CSS file that uses relative path in `url()`, the hash + * implementation should use the directory path of the file instead of the file path to include + * the relative asset files in the copying. + * + * If this is not set, the asset manager will use the default CRC32 and filemtime in the `hash` + * method. + * + * Example of an implementation using MD4 hash: + * + * ```php + * function ($path) { + * return hash('md4', $path); + * } + * ``` + * + * @since 2.0.6 + */ + public $hashCallback; private $_dummyBundles = []; @@ -261,15 +297,21 @@ class AssetManager extends Component public function getAssetUrl($bundle, $asset) { if (($actualAsset = $this->resolveAsset($bundle, $asset)) !== false) { - $asset = $actualAsset; - $basePath = $this->basePath; - $baseUrl = $this->baseUrl; + if (strncmp($actualAsset, '@web/', 5) === 0) { + $asset = substr($actualAsset, 5); + $basePath = Yii::getAlias('@webroot'); + $baseUrl = Yii::getAlias('@web'); + } else { + $asset = Yii::getAlias($actualAsset); + $basePath = $this->basePath; + $baseUrl = $this->baseUrl; + } } else { $basePath = $bundle->basePath; $baseUrl = $bundle->baseUrl; } - if (!Url::isRelative($asset)) { + if (!Url::isRelative($asset) || strncmp($asset, '/', 1) === 0) { return $asset; } @@ -309,9 +351,9 @@ class AssetManager extends Component $asset = $bundle->sourcePath . '/' . $asset; } - $n = mb_strlen($asset); + $n = mb_strlen($asset, Yii::$app->charset); foreach ($this->assetMap as $from => $to) { - $n2 = mb_strlen($from); + $n2 = mb_strlen($from, Yii::$app->charset); if ($n2 <= $n && substr_compare($asset, $from, $n - $n2, $n2) === 0) { return $to; } @@ -384,6 +426,9 @@ class AssetManager extends Component * @param array $options the options to be applied when publishing a directory. * The following options are supported: * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. * This overrides [[beforeCopy]] if set. * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. @@ -422,7 +467,7 @@ class AssetManager extends Component */ protected function publishFile($src) { - $dir = $this->hash(dirname($src) . filemtime($src)); + $dir = $this->hash($src); $fileName = basename($src); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; @@ -451,6 +496,9 @@ class AssetManager extends Component * @param array $options the options to be applied when publishing a directory. * The following options are supported: * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. * This overrides [[beforeCopy]] if set. * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. @@ -464,29 +512,31 @@ class AssetManager extends Component */ protected function publishDirectory($src, $options) { - $dir = $this->hash($src . filemtime($src)); + $dir = $this->hash($src); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; if ($this->linkAssets) { if (!is_dir($dstDir)) { + FileHelper::createDirectory(dirname($dstDir), $this->dirMode, true); symlink($src, $dstDir); } } elseif (!empty($options['forceCopy']) || ($this->forceCopy && !isset($options['forceCopy'])) || !is_dir($dstDir)) { - $opts = [ - 'dirMode' => $this->dirMode, - 'fileMode' => $this->fileMode, - ]; - if (isset($options['beforeCopy'])) { - $opts['beforeCopy'] = $options['beforeCopy']; - } elseif ($this->beforeCopy !== null) { - $opts['beforeCopy'] = $this->beforeCopy; - } else { - $opts['beforeCopy'] = function ($from, $to) { - return strncmp(basename($from), '.', 1) !== 0; - }; + $opts = array_merge( + $options, + [ + 'dirMode' => $this->dirMode, + 'fileMode' => $this->fileMode, + ] + ); + if (!isset($opts['beforeCopy'])) { + if ($this->beforeCopy !== null) { + $opts['beforeCopy'] = $this->beforeCopy; + } else { + $opts['beforeCopy'] = function ($from, $to) { + return strncmp(basename($from), '.', 1) !== 0; + }; + } } - if (isset($options['afterCopy'])) { - $opts['afterCopy'] = $options['afterCopy']; - } elseif ($this->afterCopy !== null) { + if (!isset($opts['afterCopy']) && $this->afterCopy !== null) { $opts['afterCopy'] = $this->afterCopy; } FileHelper::copyDirectory($src, $dstDir, $opts); @@ -500,7 +550,7 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file or directory is published, where it will go. * @param string $path directory or file path being published - * @return string the published file path. False if the file or directory does not exist + * @return string|false string the published file path. False if the file or directory does not exist */ public function getPublishedPath($path) { @@ -510,12 +560,7 @@ class AssetManager extends Component return $this->_published[$path][0]; } if (is_string($path) && ($path = realpath($path)) !== false) { - $base = $this->basePath . DIRECTORY_SEPARATOR; - if (is_file($path)) { - return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); - } else { - return $base . $this->hash($path . filemtime($path)); - } + return $this->basePath . DIRECTORY_SEPARATOR . $this->hash($path) . (is_file($path) ? DIRECTORY_SEPARATOR . basename($path) : ''); } else { return false; } @@ -526,7 +571,7 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file path is published, what the URL will be to access it. * @param string $path directory or file path being published - * @return string the published URL for the file or directory. False if the file or directory does not exist. + * @return string|false string the published URL for the file or directory. False if the file or directory does not exist. */ public function getPublishedUrl($path) { @@ -536,11 +581,7 @@ class AssetManager extends Component return $this->_published[$path][1]; } if (is_string($path) && ($path = realpath($path)) !== false) { - if (is_file($path)) { - return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path); - } else { - return $this->baseUrl . '/' . $this->hash($path . filemtime($path)); - } + return $this->baseUrl . '/' . $this->hash($path) . (is_file($path) ? '/' . basename($path) : ''); } else { return false; } @@ -554,6 +595,10 @@ class AssetManager extends Component */ protected function hash($path) { + if (is_callable($this->hashCallback)) { + return call_user_func($this->hashCallback, $path); + } + $path = (is_file($path) ? dirname($path) : $path) . filemtime($path); return sprintf('%x', crc32($path . Yii::getVersion())); } } diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php index 12cc227840..acbf03e6c0 100644 --- a/framework/web/CacheSession.php +++ b/framework/web/CacheSession.php @@ -24,12 +24,12 @@ use yii\di\Instance; * The following example shows how you can configure the application to use CacheSession: * Add the following to your application config under `components`: * - * ~~~ + * ```php * 'session' => [ * 'class' => 'yii\web\CacheSession', * // 'cache' => 'mycache', * ] - * ~~~ + * ``` * * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. * diff --git a/framework/web/Controller.php b/framework/web/Controller.php index 21ec396588..3668f37845 100644 --- a/framework/web/Controller.php +++ b/framework/web/Controller.php @@ -21,7 +21,7 @@ class Controller extends \yii\base\Controller { /** * @var boolean whether to enable CSRF validation for the actions in this controller. - * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. + * CSRF validation is enabled only when both this property and [[\yii\web\Request::enableCsrfValidation]] are true. */ public $enableCsrfValidation = true; /** @@ -73,7 +73,7 @@ class Controller extends \yii\base\Controller $name = $param->getName(); if (array_key_exists($name, $params)) { if ($param->isArray()) { - $args[] = $actionParams[$name] = (array)$params[$name]; + $args[] = $actionParams[$name] = (array) $params[$name]; } elseif (!is_array($params[$name])) { $args[] = $actionParams[$name] = $params[$name]; } else { @@ -110,9 +110,9 @@ class Controller extends \yii\base\Controller throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; - } else { - return false; } + + return false; } /** diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php index 924eb021e6..56e5904c72 100644 --- a/framework/web/Cookie.php +++ b/framework/web/Cookie.php @@ -51,11 +51,11 @@ class Cookie extends \yii\base\Object /** * Magic method to turn a cookie object into a string without having to explicitly access [[value]]. * - * ~~~ + * ```php * if (isset($request->cookies['name'])) { * $value = (string) $request->cookies['name']; * } - * ~~~ + * ``` * * @return string The value of the cookie. If the value property is null, an empty string will be returned. */ diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php index 09d51d3327..fe9eeb7785 100644 --- a/framework/web/CookieCollection.php +++ b/framework/web/CookieCollection.php @@ -49,7 +49,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns an iterator for traversing the cookies in the collection. - * This method is required by the SPL interface `IteratorAggregate`. + * This method is required by the SPL interface [[\IteratorAggregate]]. * It will be implicitly called when you use `foreach` to traverse the collection. * @return ArrayIterator an iterator for traversing the cookies in the collection. */ @@ -191,7 +191,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns whether there is a cookie with the specified name. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `isset($collection[$name])`. * @param string $name the cookie name * @return boolean whether the named cookie exists @@ -203,7 +203,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns the cookie with the specified name. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$cookie = $collection[$name];`. * This is equivalent to [[get()]]. * @param string $name the cookie name @@ -216,7 +216,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Adds the cookie to the collection. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$collection[$name] = $cookie;`. * This is equivalent to [[add()]]. * @param string $name the cookie name @@ -229,7 +229,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Removes the named cookie. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `unset($collection[$name])`. * This is equivalent to [[remove()]]. * @param string $name the cookie name diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php index d567b9a700..108e94ba19 100644 --- a/framework/web/DbSession.php +++ b/framework/web/DbSession.php @@ -22,20 +22,21 @@ use yii\di\Instance; * The following example shows how you can configure the application to use DbSession: * Add the following to your application config under `components`: * - * ~~~ + * ```php * 'session' => [ * 'class' => 'yii\web\DbSession', * // 'db' => 'mydb', * // 'sessionTable' => 'my_session', * ] - * ~~~ + * ``` * - * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. + * DbSession extends [[MultiFieldSession]], thus it allows saving extra fields into the [[sessionTable]]. + * Refer to [[MultiFieldSession]] for more details. * * @author Qiang Xue * @since 2.0 */ -class DbSession extends Session +class DbSession extends MultiFieldSession { /** * @var Connection|array|string the DB connection object or the application component ID of the DB connection. @@ -48,14 +49,14 @@ class DbSession extends Session * @var string the name of the DB table that stores the session data. * The table should be pre-created as follows: * - * ~~~ + * ```sql * CREATE TABLE session * ( * id CHAR(40) NOT NULL PRIMARY KEY, * expire INTEGER, * data BLOB * ) - * ~~~ + * ``` * * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type * that can be used for some popular DBMS: @@ -85,16 +86,6 @@ class DbSession extends Session $this->db = Instance::ensure($this->db, Connection::className()); } - /** - * Returns a value indicating whether to use custom session storage. - * This method overrides the parent implementation and always returns true. - * @return boolean whether to use custom storage. - */ - public function getUseCustomStorage() - { - return true; - } - /** * Updates the current session ID with a newly generated one . * Please refer to for more details. @@ -112,7 +103,7 @@ class DbSession extends Session parent::regenerateID(false); $newID = session_id(); - $query = new Query; + $query = new Query(); $row = $query->from($this->sessionTable) ->where(['id' => $oldID]) ->createCommand($this->db) @@ -131,10 +122,8 @@ class DbSession extends Session } else { // shouldn't reach here normally $this->db->createCommand() - ->insert($this->sessionTable, [ - 'id' => $newID, - 'expire' => time() + $this->getTimeout(), - ])->execute(); + ->insert($this->sessionTable, $this->composeFields($newID, '')) + ->execute(); } } @@ -146,13 +135,16 @@ class DbSession extends Session */ public function readSession($id) { - $query = new Query; - $data = $query->select(['data']) - ->from($this->sessionTable) - ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]) - ->createCommand($this->db) - ->queryScalar(); + $query = new Query(); + $query->from($this->sessionTable) + ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]); + if ($this->readCallback !== null) { + $fields = $query->one($this->db); + return $fields === false ? '' : $this->extractData($fields); + } + + $data = $query->select(['data'])->scalar($this->db); return $data === false ? '' : $data; } @@ -168,23 +160,21 @@ class DbSession extends Session // exception must be caught in session write handler // http://us.php.net/manual/en/function.session-set-save-handler.php try { - $expire = time() + $this->getTimeout(); $query = new Query; $exists = $query->select(['id']) ->from($this->sessionTable) ->where(['id' => $id]) ->createCommand($this->db) ->queryScalar(); + $fields = $this->composeFields($id, $data); if ($exists === false) { $this->db->createCommand() - ->insert($this->sessionTable, [ - 'id' => $id, - 'data' => $data, - 'expire' => $expire, - ])->execute(); + ->insert($this->sessionTable, $fields) + ->execute(); } else { + unset($fields['id']); $this->db->createCommand() - ->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['id' => $id]) + ->update($this->sessionTable, $fields, ['id' => $id]) ->execute(); } } catch (\Exception $e) { diff --git a/framework/web/ErrorAction.php b/framework/web/ErrorAction.php index 46d5543f28..92e24c7872 100644 --- a/framework/web/ErrorAction.php +++ b/framework/web/ErrorAction.php @@ -75,7 +75,8 @@ class ErrorAction extends Action public function run() { if (($exception = Yii::$app->getErrorHandler()->exception) === null) { - return ''; + // action has been invoked not from error handler, but by direct route, so we display '404 Not Found' + $exception = new HttpException(404, Yii::t('yii', 'Page not found.')); } if ($exception instanceof HttpException) { diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index bd651247b5..7098fccee2 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -59,6 +59,14 @@ class ErrorHandler extends \yii\base\ErrorHandler * @var string the path of the view file for rendering previous exceptions. */ public $previousExceptionView = '@yii/views/errorHandler/previousException.php'; + /** + * @var array list of the PHP predefined variables that should be displayed on the error page. + * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be displayed. + * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION']`. + * @see renderRequest() + * @since 2.0.7 + */ + public $displayVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION']; /** @@ -69,7 +77,12 @@ class ErrorHandler extends \yii\base\ErrorHandler { if (Yii::$app->has('response')) { $response = Yii::$app->getResponse(); + // reset parameters of response to avoid interference with partially created response data + // in case the error occurred while sending the response. $response->isSent = false; + $response->stream = null; + $response->data = null; + $response->content = null; } else { $response = new Response(); } @@ -86,7 +99,7 @@ class ErrorHandler extends \yii\base\ErrorHandler } elseif ($response->format === Response::FORMAT_HTML) { if (YII_ENV_TEST || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { // AJAX request - $response->data = '
        ' . $this->htmlEncode($this->convertExceptionToString($exception)) . '
        '; + $response->data = '
        ' . $this->htmlEncode(static::convertExceptionToString($exception)) . '
        '; } else { // if there is an error during error rendering it's useful to // display PHP error in debug mode instead of a blank screen @@ -99,7 +112,7 @@ class ErrorHandler extends \yii\base\ErrorHandler ]); } } elseif ($response->format === Response::FORMAT_RAW) { - $response->data = $exception; + $response->data = static::convertExceptionToString($exception); } else { $response->data = $this->convertExceptionToArray($exception); } @@ -121,7 +134,7 @@ class ErrorHandler extends \yii\base\ErrorHandler protected function convertExceptionToArray($exception) { if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) { - $exception = new HttpException(500, 'There was an error at the server.'); + $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.')); } $array = [ @@ -157,7 +170,7 @@ class ErrorHandler extends \yii\base\ErrorHandler */ public function htmlEncode($text) { - return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset); + return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); } /** @@ -250,8 +263,8 @@ class ErrorHandler extends \yii\base\ErrorHandler * @param integer|null $line number on which call has happened. * @param string|null $class called class name. * @param string|null $method called function/method name. - * @param integer $index number of the call stack element. * @param array $args array of method arguments. + * @param integer $index number of the call stack element. * @return string HTML content of the rendered call stack element. */ public function renderCallStackItem($file, $line, $class, $method, $args, $index) @@ -261,11 +274,11 @@ class ErrorHandler extends \yii\base\ErrorHandler if ($file !== null && $line !== null) { $line--; // adjust line number from one-based to zero-based $lines = @file($file); - if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) { + if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) { return ''; } - $half = (int) (($index == 1 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2); + $half = (int) (($index === 1 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2); $begin = $line - $half > 0 ? $line - $half : 0; $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1; } @@ -284,13 +297,15 @@ class ErrorHandler extends \yii\base\ErrorHandler } /** - * Renders the request information. + * Renders the global variables of the request. + * List of global variables is defined in [[displayVars]]. * @return string the rendering result + * @see displayVars */ public function renderRequest() { $request = ''; - foreach (['_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV'] as $name) { + foreach ($this->displayVars as $name) { if (!empty($GLOBALS[$name])) { $request .= '$' . $name . ' = ' . VarDumper::export($GLOBALS[$name]) . ";\n\n"; } @@ -386,8 +401,8 @@ class ErrorHandler extends \yii\base\ErrorHandler $args[$key] = '' . ($value ? 'true' : 'false') . ''; } elseif (is_string($value)) { $fullValue = $this->htmlEncode($value); - if (mb_strlen($value, 'utf8') > 32) { - $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'utf8')) . '...'; + if (mb_strlen($value, 'UTF-8') > 32) { + $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...'; $args[$key] = "'$displayValue'"; } else { $args[$key] = "'$fullValue'"; @@ -408,7 +423,7 @@ class ErrorHandler extends \yii\base\ErrorHandler $args[$key] = "$key => $args[$key]"; } } - $out = implode(", ", $args); + $out = implode(', ', $args); return $out; } diff --git a/framework/web/GroupUrlRule.php b/framework/web/GroupUrlRule.php index 3395d53489..a57e1b01e5 100644 --- a/framework/web/GroupUrlRule.php +++ b/framework/web/GroupUrlRule.php @@ -77,11 +77,8 @@ class GroupUrlRule extends CompositeUrlRule */ public function init() { - if ($this->routePrefix === null) { - $this->routePrefix = $this->prefix; - } $this->prefix = trim($this->prefix, '/'); - $this->routePrefix = trim($this->routePrefix, '/'); + $this->routePrefix = $this->routePrefix === null ? $this->prefix : trim($this->routePrefix, '/'); parent::init(); } diff --git a/framework/web/HeaderCollection.php b/framework/web/HeaderCollection.php index 48adcf1749..0cdf8d6752 100644 --- a/framework/web/HeaderCollection.php +++ b/framework/web/HeaderCollection.php @@ -31,7 +31,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns an iterator for traversing the headers in the collection. - * This method is required by the SPL interface `IteratorAggregate`. + * This method is required by the SPL interface [[\IteratorAggregate]]. * It will be implicitly called when you use `foreach` to traverse the collection. * @return ArrayIterator an iterator for traversing the headers in the collection. */ @@ -84,7 +84,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * If there is already a header with the same name, it will be replaced. * @param string $name the name of the header * @param string $value the value of the header - * @return static the collection object itself + * @return $this the collection object itself */ public function set($name, $value = '') { @@ -100,7 +100,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * be appended to it instead of replacing it. * @param string $name the name of the header * @param string $value the value of the header - * @return static the collection object itself + * @return $this the collection object itself */ public function add($name, $value) { @@ -115,7 +115,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * If there is already a header with the same name, the new one will be ignored. * @param string $name the name of the header * @param string $value the value of the header - * @return static the collection object itself + * @return $this the collection object itself */ public function setDefault($name, $value) { @@ -186,7 +186,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns whether there is a header with the specified name. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `isset($collection[$name])`. * @param string $name the header name * @return boolean whether the named header exists @@ -198,7 +198,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Returns the header with the specified name. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$header = $collection[$name];`. * This is equivalent to [[get()]]. * @param string $name the header name @@ -211,7 +211,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Adds the header to the collection. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `$collection[$name] = $header;`. * This is equivalent to [[add()]]. * @param string $name the header name @@ -224,7 +224,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Removes the named header. - * This method is required by the SPL interface `ArrayAccess`. + * This method is required by the SPL interface [[\ArrayAccess]]. * It is implicitly called when you use something like `unset($collection[$name])`. * This is equivalent to [[remove()]]. * @param string $name the header name diff --git a/framework/web/IdentityInterface.php b/framework/web/IdentityInterface.php index eab79086ec..c83f686d66 100644 --- a/framework/web/IdentityInterface.php +++ b/framework/web/IdentityInterface.php @@ -13,7 +13,7 @@ namespace yii\web; * This interface can typically be implemented by a user model class. For example, the following * code shows how to implement this interface by a User ActiveRecord class: * - * ~~~ + * ```php * class User extends ActiveRecord implements IdentityInterface * { * public static function findIdentity($id) @@ -41,7 +41,7 @@ namespace yii\web; * return $this->authKey === $authKey; * } * } - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -56,6 +56,7 @@ interface IdentityInterface * or the identity is not in an active state (disabled, deleted, etc.) */ public static function findIdentity($id); + /** * Finds an identity by the given token. * @param mixed $token the token to be looked for @@ -66,11 +67,13 @@ interface IdentityInterface * or the identity is not in an active state (disabled, deleted, etc.) */ public static function findIdentityByAccessToken($token, $type = null); + /** * Returns an ID that can uniquely identify a user identity. * @return string|integer an ID that uniquely identifies a user identity. */ public function getId(); + /** * Returns a key that can be used to check the validity of a given identity ID. * @@ -84,6 +87,7 @@ interface IdentityInterface * @see validateAuthKey() */ public function getAuthKey(); + /** * Validates the given auth key. * diff --git a/framework/web/JsExpression.php b/framework/web/JsExpression.php index 600c6c8153..c05dba8f56 100644 --- a/framework/web/JsExpression.php +++ b/framework/web/JsExpression.php @@ -12,11 +12,15 @@ use yii\base\Object; /** * JsExpression marks a string as a JavaScript expression. * +<<<<<<< HEAD <<<<<<< HEAD * When using [[\yii\helpers\Json::encode()]] to encode a value, JsonExpression objects ======= * When using [[\yii\helpers\Json::encode()]] or [[\yii\helpers\Json::htmlEncode()]] to encode a value, JsonExpression objects >>>>>>> yiichina/master +======= + * When using [[\yii\helpers\Json::encode()]] or [[\yii\helpers\Json::htmlEncode()]] to encode a value, JsonExpression objects +>>>>>>> master * will be specially handled and encoded as a JavaScript expression instead of a string. * * @author Qiang Xue diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 8daa4f675b..bb80e0e7de 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -48,13 +48,13 @@ class JsonParser implements RequestParserInterface public function parse($rawBody, $contentType) { try { - return Json::decode($rawBody, $this->asArray); + $parameters = Json::decode($rawBody, $this->asArray); + return $parameters === null ? [] : $parameters; } catch (InvalidParamException $e) { if ($this->throwException) { - throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage(), 0, $e); + throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage()); } - - return null; + return []; } } } diff --git a/framework/web/JsonResponseFormatter.php b/framework/web/JsonResponseFormatter.php index 21abd1a500..05fc98c064 100644 --- a/framework/web/JsonResponseFormatter.php +++ b/framework/web/JsonResponseFormatter.php @@ -16,6 +16,22 @@ use yii\helpers\Json; * * It is used by [[Response]] to format response data. * + * To configure properties like [[encodeOptions]] or [[prettyPrint]], you can configure the `response` + * application component like the following: + * + * ```php + * 'response' => [ + * // ... + * 'formatters' => [ + * \yii\web\Response::FORMAT_JSON => [ + * 'class' => 'yii\web\JsonResponseFormatter', + * 'prettyPrint' => YII_DEBUG, // use "pretty" output in debug mode + * // ... + * ], + * ], + * ], + * ``` + * * @author Qiang Xue * @since 2.0 */ @@ -27,6 +43,22 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf * function name while the former will be passed to this function as a parameter. */ public $useJsonp = false; + /** + * @var integer the encoding options passed to [[Json::encode()]]. For more details please refer to + * . + * Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`. + * This property has no effect, when [[useJsonp]] is `true`. + * @since 2.0.7 + */ + public $encodeOptions = 320; + /** + * @var bool whether to format the output in a readable "pretty" format. This can be useful for debugging purpose. + * If this is true, `JSON_PRETTY_PRINT` will be added to [[encodeOptions]]. + * Defaults to `false`. + * This property has no effect, when [[useJsonp]] is `true`. + * @since 2.0.7 + */ + public $prettyPrint = false; /** @@ -50,7 +82,11 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf { $response->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8'); if ($response->data !== null) { - $response->content = Json::encode($response->data); + $options = $this->encodeOptions; + if ($this->prettyPrint) { + $options |= JSON_PRETTY_PRINT; + } + $response->content = Json::encode($response->data, $options); } } @@ -62,11 +98,15 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf { $response->getHeaders()->set('Content-Type', 'application/javascript; charset=UTF-8'); if (is_array($response->data) && isset($response->data['data'], $response->data['callback'])) { +<<<<<<< HEAD <<<<<<< HEAD $response->content = sprintf('%s(%s);', $response->data['callback'], Json::encode($response->data['data'])); ======= $response->content = sprintf('%s(%s);', $response->data['callback'], Json::htmlEncode($response->data['data'])); >>>>>>> yiichina/master +======= + $response->content = sprintf('%s(%s);', $response->data['callback'], Json::htmlEncode($response->data['data'])); +>>>>>>> master } elseif ($response->data !== null) { $response->content = ''; Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__); diff --git a/framework/web/MultiFieldSession.php b/framework/web/MultiFieldSession.php new file mode 100644 index 0000000000..c43b80a057 --- /dev/null +++ b/framework/web/MultiFieldSession.php @@ -0,0 +1,141 @@ + + * @since 2.0.6 + */ +abstract class MultiFieldSession extends Session +{ + /** + * @var callable a callback that will be called during session data reading. + * The signature of the callback should be as follows: + * + * ``` + * function ($fields) + * ``` + * + * where `$fields` is the storage field set for read session and `$session` is this session instance. + * If callback returns an array, it will be merged into the session data. + * + * For example: + * + * ```php + * function ($fields) { + * return [ + * 'expireDate' => Yii::$app->formatter->asDate($fields['expire']), + * ]; + * } + * ``` + */ + public $readCallback; + /** + * @var callable a callback that will be called during session data writing. + * The signature of the callback should be as follows: + * + * ``` + * function ($session) + * ``` + * + * where `$session` is this session instance, this variable can be used to retrieve session data. + * Callback should return the actual fields set, which should be saved into the session storage. + * + * For example: + * + * ```php + * function ($session) { + * return [ + * 'user_id' => Yii::$app->user->id, + * 'ip' => $_SERVER['REMOTE_ADDR'], + * 'is_trusted' => $session->get('is_trusted', false), + * ]; + * } + * ``` + */ + public $writeCallback; + + + /** + * Returns a value indicating whether to use custom session storage. + * This method overrides the parent implementation and always returns true. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return true; + } + + /** + * Composes storage field set for session writing. + * @param string $id session id + * @param string $data session data + * @return array storage fields + */ + protected function composeFields($id, $data) + { + $fields = [ + 'data' => $data, + ]; + if ($this->writeCallback !== null) { + $fields = array_merge( + $fields, + call_user_func($this->writeCallback, $this) + ); + if (!is_string($fields['data'])) { + $_SESSION = $fields['data']; + $fields['data'] = session_encode(); + } + } + // ensure 'id' and 'expire' are never affected by [[writeCallback]] + $fields = array_merge($fields, [ + 'id' => $id, + 'expire' => time() + $this->getTimeout(), + ]); + return $fields; + } + + /** + * Extracts session data from storage field set. + * @param array $fields storage fields. + * @return string session data. + */ + protected function extractData($fields) + { + if ($this->readCallback !== null) { + if (!isset($fields['data'])) { + $fields['data'] = ''; + } + $extraData = call_user_func($this->readCallback, $fields); + if (!empty($extraData)) { + session_decode($fields['data']); + $_SESSION = array_merge((array)$_SESSION, (array)$extraData); + return session_encode(); + } + return $fields['data']; + } else { + return isset($fields['data']) ? $fields['data'] : ''; + } + } +} diff --git a/framework/web/Request.php b/framework/web/Request.php index 5bfba16b56..52c16b6250 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -27,10 +27,10 @@ use yii\helpers\StringHelper; * corresponding quality score and other parameters as given in the header. * @property array $acceptableLanguages The languages ordered by the preference level. The first element * represents the most preferred language. - * @property string $authPassword The password sent via HTTP authentication, null if the password is not + * @property string|null $authPassword The password sent via HTTP authentication, null if the password is not + * given. This property is read-only. + * @property string|null $authUser The username sent via HTTP authentication, null if the username is not * given. This property is read-only. - * @property string $authUser The username sent via HTTP authentication, null if the username is not given. - * This property is read-only. * @property string $baseUrl The relative URL for the application. * @property array $bodyParams The request parameters given in the request body. * @property string $contentType Request content-type. Null is returned if this information is not available. @@ -42,7 +42,7 @@ use yii\helpers\StringHelper; * @property array $eTags The entity tags. This property is read-only. * @property HeaderCollection $headers The header collection. This property is read-only. * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. - * `http://www.yiiframework.com`). + * `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. * @property boolean $isDelete Whether this is a DELETE request. This property is read-only. * @property boolean $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is @@ -64,18 +64,17 @@ use yii\helpers\StringHelper; * @property array $queryParams The request GET parameter values. * @property string $queryString Part of the request URL that is after the question mark. This property is * read-only. - * @property string $rawBody The request body. This property is read-only. - * @property string $referrer URL referrer, null if not present. This property is read-only. + * @property string $rawBody The request body. + * @property string|null $referrer URL referrer, null if not available. This property is read-only. * @property string $scriptFile The entry script file path. * @property string $scriptUrl The relative URL of the entry script. * @property integer $securePort Port number for secure requests. - * @property string $serverName Server name. This property is read-only. - * @property integer $serverPort Server port number. This property is read-only. + * @property string $serverName Server name, null if not available. This property is read-only. + * @property integer|null $serverPort Server port number, null if not available. This property is read-only. * @property string $url The currently requested relative URL. Note that the URI returned is URL-encoded. - * @property string $userAgent User agent, null if not present. This property is read-only. - * @property string $userHost User host name, null if cannot be determined. This property is read-only. - * @property string $userIP User IP address. Null is returned if the user IP address cannot be detected. This - * property is read-only. + * @property string|null $userAgent User agent, null if not available. This property is read-only. + * @property string|null $userHost User host name, null if not available. This property is read-only. + * @property string|null $userIP User IP address, null if not available. This property is read-only. * * @author Qiang Xue * @since 2.0 @@ -165,7 +164,7 @@ class Request extends \yii\base\Request */ private $_cookies; /** - * @var array the headers in this collection (indexed by the header names) + * @var HeaderCollection Collection of request headers. */ private $_headers; @@ -180,6 +179,7 @@ class Request extends \yii\base\Request $result = Yii::$app->getUrlManager()->parseRequest($this); if ($result !== false) { list ($route, $params) = $result; +<<<<<<< HEAD <<<<<<< HEAD $_GET = array_merge($_GET, $params); ======= @@ -187,6 +187,14 @@ class Request extends \yii\base\Request >>>>>>> yiichina/master return [$route, $_GET]; +======= + if ($this->_queryParams === null) { + $_GET = $params + $_GET; // preserve numeric keys + } else { + $this->_queryParams = $params + $this->_queryParams; + } + return [$route, $this->getQueryParams()]; +>>>>>>> master } else { throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } @@ -232,11 +240,17 @@ class Request extends \yii\base\Request { if (isset($_POST[$this->methodParam])) { return strtoupper($_POST[$this->methodParam]); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); - } else { - return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; } + + if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } + + if (isset($_SERVER['REQUEST_METHOD'])) { + return strtoupper($_SERVER['REQUEST_METHOD']); + } + + return 'GET'; } /** @@ -304,6 +318,10 @@ class Request extends \yii\base\Request /** * Returns whether this is an AJAX (XMLHttpRequest) request. + * + * Note that jQuery doesn't set the header in case of cross domain + * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header + * * @return boolean whether this is an AJAX (XMLHttpRequest) request. */ public function getIsAjax() @@ -347,7 +365,7 @@ class Request extends \yii\base\Request /** * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests. - * @param $rawBody + * @param string $rawBody the request body */ public function setRawBody($rawBody) { @@ -517,7 +535,8 @@ class Request extends \yii\base\Request * The returned URL does not have an ending slash. * By default this is determined based on the user request information. * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property. - * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`) + * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`), + * null if can't be obtained from `$_SERVER` and wasn't set. * @see setHostInfo() */ public function getHostInfo() @@ -527,7 +546,7 @@ class Request extends \yii\base\Request $http = $secure ? 'https' : 'http'; if (isset($_SERVER['HTTP_HOST'])) { $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST']; - } else { + } elseif (isset($_SERVER['SERVER_NAME'])) { $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME']; $port = $secure ? $this->getSecurePort() : $this->getPort(); if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) { @@ -547,7 +566,7 @@ class Request extends \yii\base\Request */ public function setHostInfo($value) { - $this->_hostInfo = rtrim($value, '/'); + $this->_hostInfo = $value === null ? null : rtrim($value, '/'); } private $_baseUrl; @@ -592,13 +611,13 @@ class Request extends \yii\base\Request if ($this->_scriptUrl === null) { $scriptFile = $this->getScriptFile(); $scriptName = basename($scriptFile); - if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) { + if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) { $this->_scriptUrl = $_SERVER['SCRIPT_NAME']; - } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) { + } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) { $this->_scriptUrl = $_SERVER['PHP_SELF']; } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) { $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME']; - } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) { + } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) { $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName; } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) { $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile)); @@ -618,7 +637,7 @@ class Request extends \yii\base\Request */ public function setScriptUrl($value) { - $this->_scriptUrl = '/' . trim($value, '/'); + $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/'); } private $_scriptFile; @@ -627,10 +646,17 @@ class Request extends \yii\base\Request * Returns the entry script file path. * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`. * @return string the entry script file path + * @throws InvalidConfigException */ public function getScriptFile() { - return isset($this->_scriptFile) ? $this->_scriptFile : $_SERVER['SCRIPT_FILENAME']; + if (isset($this->_scriptFile)) { + return $this->_scriptFile; + } elseif (isset($_SERVER['SCRIPT_FILENAME'])) { + return $_SERVER['SCRIPT_FILENAME']; + } else { + throw new InvalidConfigException('Unable to determine the entry script file path.'); + } } /** @@ -671,7 +697,7 @@ class Request extends \yii\base\Request */ public function setPathInfo($value) { - $this->_pathInfo = ltrim($value, '/'); + $this->_pathInfo = $value === null ? null : ltrim($value, '/'); } /** @@ -720,7 +746,7 @@ class Request extends \yii\base\Request throw new InvalidConfigException('Unable to determine the path info of the current request.'); } - if ($pathInfo[0] === '/') { + if (substr($pathInfo, 0, 1) === '/') { $pathInfo = substr($pathInfo, 1); } @@ -816,25 +842,25 @@ class Request extends \yii\base\Request /** * Returns the server name. - * @return string server name + * @return string server name, null if not available */ public function getServerName() { - return $_SERVER['SERVER_NAME']; + return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; } /** * Returns the server port number. - * @return integer server port number + * @return integer|null server port number, null if not available */ public function getServerPort() { - return (int) $_SERVER['SERVER_PORT']; + return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null; } /** - * Returns the URL referrer, null if not present - * @return string URL referrer, null if not present + * Returns the URL referrer. + * @return string|null URL referrer, null if not available */ public function getReferrer() { @@ -842,8 +868,8 @@ class Request extends \yii\base\Request } /** - * Returns the user agent, null if not present. - * @return string user agent, null if not present + * Returns the user agent. + * @return string|null user agent, null if not available */ public function getUserAgent() { @@ -852,7 +878,7 @@ class Request extends \yii\base\Request /** * Returns the user IP address. - * @return string user IP address. Null is returned if the user IP address cannot be detected. + * @return string|null user IP address, null if not available */ public function getUserIP() { @@ -860,8 +886,8 @@ class Request extends \yii\base\Request } /** - * Returns the user host name, null if it cannot be determined. - * @return string user host name, null if cannot be determined + * Returns the user host name. + * @return string|null user host name, null if not available */ public function getUserHost() { @@ -869,7 +895,7 @@ class Request extends \yii\base\Request } /** - * @return string the username sent via HTTP authentication, null if the username is not given + * @return string|null the username sent via HTTP authentication, null if the username is not given */ public function getAuthUser() { @@ -877,7 +903,7 @@ class Request extends \yii\base\Request } /** - * @return string the password sent via HTTP authentication, null if the password is not given + * @return string|null the password sent via HTTP authentication, null if the password is not given */ public function getAuthPassword() { @@ -1008,11 +1034,11 @@ class Request extends \yii\base\Request */ public function getContentType() { - if (isset($_SERVER["CONTENT_TYPE"])) { - return $_SERVER["CONTENT_TYPE"]; - } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) { + if (isset($_SERVER['CONTENT_TYPE'])) { + return $_SERVER['CONTENT_TYPE']; + } elseif (isset($_SERVER['HTTP_CONTENT_TYPE'])) { //fix bug https://bugs.php.net/bug.php?id=66606 - return $_SERVER["HTTP_CONTENT_TYPE"]; + return $_SERVER['HTTP_CONTENT_TYPE']; } return null; @@ -1180,7 +1206,7 @@ class Request extends \yii\base\Request * Returns the cookie collection. * Through the returned cookie collection, you may access a cookie using the following syntax: * - * ~~~ + * ```php * $cookie = $request->cookies['name'] * if ($cookie !== null) { * $value = $cookie->value; @@ -1188,7 +1214,7 @@ class Request extends \yii\base\Request * * // alternatively * $value = $request->cookies->getValue('name'); - * ~~~ + * ``` * * @return CookieCollection the cookie collection. */ @@ -1228,7 +1254,7 @@ class Request extends \yii\base\Request $cookies[$name] = new Cookie([ 'name' => $name, 'value' => $data[1], - 'expire'=> null + 'expire' => null, ]); } } @@ -1237,7 +1263,7 @@ class Request extends \yii\base\Request $cookies[$name] = new Cookie([ 'name' => $name, 'value' => $value, - 'expire'=> null + 'expire' => null, ]); } } @@ -1250,9 +1276,8 @@ class Request extends \yii\base\Request /** * Returns the token used to perform CSRF validation. * - * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/). - * This token may be passed along via a hidden field of an HTML form or an HTTP header value - * to support CSRF validation. + * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed + * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation. * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time * this method is called, a new CSRF token will be generated and persisted (in session or cookie). * @return string the token used to perform CSRF validation. @@ -1265,11 +1290,15 @@ class Request extends \yii\base\Request } // the mask doesn't need to be very random $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; +<<<<<<< HEAD <<<<<<< HEAD $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, self::CSRF_MASK_LENGTH); ======= $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); >>>>>>> yiichina/master +======= + $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); +>>>>>>> master // The + sign may be decoded as blank space later, which will fail the validation $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } @@ -1332,11 +1361,15 @@ class Request extends \yii\base\Request */ public function getCsrfTokenFromHeader() { +<<<<<<< HEAD <<<<<<< HEAD $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); ======= $key = 'HTTP_' . str_replace('-', '_', strtoupper(static::CSRF_HEADER)); >>>>>>> yiichina/master +======= + $key = 'HTTP_' . str_replace('-', '_', strtoupper(static::CSRF_HEADER)); +>>>>>>> master return isset($_SERVER[$key]) ? $_SERVER[$key] : null; } @@ -1357,6 +1390,7 @@ class Request extends \yii\base\Request /** * Performs the CSRF validation. +<<<<<<< HEAD <<<<<<< HEAD * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. @@ -1379,6 +1413,21 @@ class Request extends \yii\base\Request */ public function validateCsrfToken($token = null) >>>>>>> yiichina/master +======= + * + * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session. + * This method is mainly called in [[Controller::beforeAction()]]. + * + * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method + * is among GET, HEAD or OPTIONS. + * + * @param string $token the user-provided CSRF token to be validated. If null, the token will be retrieved from + * the [[csrfParam]] POST field or HTTP header. + * This parameter is available since version 2.0.4. + * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. + */ + public function validateCsrfToken($token = null) +>>>>>>> master { $method = $this->getMethod(); // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 @@ -1388,17 +1437,23 @@ class Request extends \yii\base\Request $trueToken = $this->loadCsrfToken(); +<<<<<<< HEAD <<<<<<< HEAD return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); ======= +======= +>>>>>>> master if ($token !== null) { return $this->validateCsrfTokenInternal($token, $trueToken); } else { return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master } /** @@ -1412,6 +1467,7 @@ class Request extends \yii\base\Request { $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); +<<<<<<< HEAD <<<<<<< HEAD if ($n <= self::CSRF_MASK_LENGTH) { return false; @@ -1425,6 +1481,13 @@ class Request extends \yii\base\Request $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); >>>>>>> yiichina/master +======= + if ($n <= static::CSRF_MASK_LENGTH) { + return false; + } + $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); + $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); +>>>>>>> master $token = $this->xorTokens($mask, $token); return $token === $trueToken; diff --git a/framework/web/Response.php b/framework/web/Response.php index 5d16c7da4e..5a52fe9c99 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -26,13 +26,13 @@ use yii\helpers\StringHelper; * You can modify its configuration by adding an array to your application config under `components` * as it is shown in the following example: * - * ~~~ + * ```php * 'response' => [ * 'format' => yii\web\Response::FORMAT_JSON, * 'charset' => 'UTF-8', * // ... * ] - * ~~~ + * ``` * * @property CookieCollection $cookies The cookie collection. This property is read-only. * @property string $downloadHeaders The attachment file name. This property is write-only. @@ -72,7 +72,6 @@ class Response extends \yii\base\Response * You may respond to this event to filter the response content before it is sent to the client. */ const EVENT_AFTER_PREPARE = 'afterPrepare'; - const FORMAT_RAW = 'raw'; const FORMAT_HTML = 'html'; const FORMAT_JSON = 'json'; @@ -103,21 +102,29 @@ class Response extends \yii\base\Response public $format = self::FORMAT_HTML; /** * @var string the MIME type (e.g. `application/json`) from the request ACCEPT header chosen for this response. +<<<<<<< HEAD <<<<<<< HEAD * This property is mainly set by [\yii\filters\ContentNegotiator]]. ======= * This property is mainly set by [[\yii\filters\ContentNegotiator]]. >>>>>>> yiichina/master +======= + * This property is mainly set by [[\yii\filters\ContentNegotiator]]. +>>>>>>> master */ public $acceptMimeType; /** * @var array the parameters (e.g. `['q' => 1, 'version' => '1.0']`) associated with the [[acceptMimeType|chosen MIME type]]. * This is a list of name-value pairs associated with [[acceptMimeType]] from the ACCEPT HTTP header. +<<<<<<< HEAD <<<<<<< HEAD * This property is mainly set by [\yii\filters\ContentNegotiator]]. ======= * This property is mainly set by [[\yii\filters\ContentNegotiator]]. >>>>>>> yiichina/master +======= + * This property is mainly set by [[\yii\filters\ContentNegotiator]]. +>>>>>>> master */ public $acceptParams = []; /** @@ -125,6 +132,7 @@ class Response extends \yii\base\Response * The array keys are the format names, and the array values are the corresponding configurations * for creating the formatter objects. * @see format + * @see defaultFormatters */ public $formatters = []; /** @@ -212,6 +220,7 @@ class Response extends \yii\base\Response 416 => 'Requested range unsatisfiable', 417 => 'Expectation failed', 418 => 'I\'m a teapot', + 421 => 'Misdirected Request', 422 => 'Unprocessable entity', 423 => 'Locked', 424 => 'Method failure', @@ -260,12 +269,16 @@ class Response extends \yii\base\Response if ($this->charset === null) { $this->charset = Yii::$app->charset; } +<<<<<<< HEAD <<<<<<< HEAD $formatters = $this->defaultFormatters(); $this->formatters = empty($this->formatters) ? $formatters : array_merge($formatters, $this->formatters); ======= $this->formatters = array_merge($this->defaultFormatters(), $this->formatters); >>>>>>> yiichina/master +======= + $this->formatters = array_merge($this->defaultFormatters(), $this->formatters); +>>>>>>> master } /** @@ -352,8 +365,6 @@ class Response extends \yii\base\Response if (headers_sent()) { return; } - $statusCode = $this->getStatusCode(); - header("HTTP/{$this->version} $statusCode {$this->statusText}"); if ($this->_headers) { $headers = $this->getHeaders(); foreach ($headers as $name => $values) { @@ -366,6 +377,8 @@ class Response extends \yii\base\Response } } } + $statusCode = $this->getStatusCode(); + header("HTTP/{$this->version} {$statusCode} {$this->statusText}"); $this->sendCookies(); } @@ -439,9 +452,9 @@ class Response extends \yii\base\Response * * - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath` * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, - * meaning a download dialog will pop up. + * meaning a download dialog will pop up. * - * @return static the response object itself + * @return $this the response object itself */ public function sendFile($filePath, $attachmentName = null, $options = []) { @@ -469,9 +482,9 @@ class Response extends \yii\base\Response * * - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'. * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, - * meaning a download dialog will pop up. + * meaning a download dialog will pop up. * - * @return static the response object itself + * @return $this the response object itself * @throws HttpException if the requested range is not satisfiable */ public function sendContentAsFile($content, $attachmentName, $options = []) @@ -516,6 +529,7 @@ class Response extends \yii\base\Response * * - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'. * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, +<<<<<<< HEAD * meaning a download dialog will pop up. <<<<<<< HEAD ======= @@ -523,24 +537,36 @@ class Response extends \yii\base\Response * and the content is not seekable. Defaults to content size using `ftell()`. * This option is available since version 2.0.4. >>>>>>> yiichina/master +======= + * meaning a download dialog will pop up. + * - `fileSize`: the size of the content to stream this is useful when size of the content is known + * and the content is not seekable. Defaults to content size using `ftell()`. + * This option is available since version 2.0.4. +>>>>>>> master * - * @return static the response object itself + * @return $this the response object itself * @throws HttpException if the requested range cannot be satisfied. */ public function sendStreamAsFile($handle, $attachmentName, $options = []) { $headers = $this->getHeaders(); +<<<<<<< HEAD <<<<<<< HEAD fseek($handle, 0, SEEK_END); $fileSize = ftell($handle); ======= +======= +>>>>>>> master if (isset($options['fileSize'])) { $fileSize = $options['fileSize']; } else { fseek($handle, 0, SEEK_END); $fileSize = ftell($handle); } +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master $range = $this->getHttpRange($fileSize); if ($range === false) { @@ -572,7 +598,7 @@ class Response extends \yii\base\Response * @param boolean $inline whether the browser should open the file within the browser window. Defaults to false, * meaning a download dialog will pop up. * @param integer $contentLength the byte length of the file being downloaded. If null, `Content-Length` header will NOT be set. - * @return static the response object itself + * @return $this the response object itself */ public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null) { @@ -671,9 +697,9 @@ class Response extends \yii\base\Response * * **Example** * - * ~~~ + * ```php * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg'); - * ~~~ + * ``` * * @param string $filePath file name with full path * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`. @@ -681,10 +707,10 @@ class Response extends \yii\base\Response * * - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath` * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, - * meaning a download dialog will pop up. + * meaning a download dialog will pop up. * - xHeader: string, the name of the x-sendfile header. Defaults to "X-Sendfile". * - * @return static the response object itself + * @return $this the response object itself */ public function xSendFile($filePath, $attachmentName = null, $options = []) { @@ -708,6 +734,8 @@ class Response extends \yii\base\Response ->setDefault('Content-Type', $mimeType) ->setDefault('Content-Disposition', "{$disposition}; filename=\"{$attachmentName}\""); + $this->format = self::FORMAT_RAW; + return $this; } @@ -717,17 +745,17 @@ class Response extends \yii\base\Response * This method adds a "Location" header to the current response. Note that it does not send out * the header until [[send()]] is called. In a controller action you may use this method as follows: * - * ~~~ + * ```php * return Yii::$app->getResponse()->redirect($url); - * ~~~ + * ``` * * In other places, if you want to send out the "Location" header immediately, you should use * the following code: * - * ~~~ + * ```php * Yii::$app->getResponse()->redirect($url)->send(); * return; - * ~~~ + * ``` * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, @@ -737,14 +765,14 @@ class Response extends \yii\base\Response * described above. Otherwise, you should write the following JavaScript code to * handle the redirection: * - * ~~~ + * ```javascript * $document.ajaxComplete(function (event, xhr, settings) { * var url = xhr.getResponseHeader('X-Redirect'); * if (url) { * window.location = url; * } * }); - * ~~~ + * ``` * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: * @@ -764,7 +792,8 @@ class Response extends \yii\base\Response * meaning if the current request is an AJAX or PJAX request, then calling this method will cause the browser * to redirect to the given URL. If this is false, a `Location` header will be sent, which when received as * an AJAX/PJAX response, may NOT cause browser redirection. - * @return static the response object itself + * Takes effect only when request header `X-Ie-Redirect-Compatibility` is absent. + * @return $this the response object itself */ public function redirect($url, $statusCode = 302, $checkAjax = true) { @@ -778,10 +807,16 @@ class Response extends \yii\base\Response } if ($checkAjax) { - if (Yii::$app->getRequest()->getIsPjax()) { - $this->getHeaders()->set('X-Pjax-Url', $url); - } elseif (Yii::$app->getRequest()->getIsAjax()) { - $this->getHeaders()->set('X-Redirect', $url); + if (Yii::$app->getRequest()->getIsAjax()) { + if (Yii::$app->getRequest()->getHeaders()->get('X-Ie-Redirect-Compatibility') !== null && $statusCode === 302) { + // Ajax 302 redirect in IE does not work. Change status code to 200. See https://github.com/yiisoft/yii2/issues/9670 + $statusCode = 200; + } + if (Yii::$app->getRequest()->getIsPjax()) { + $this->getHeaders()->set('X-Pjax-Url', $url); + } else { + $this->getHeaders()->set('X-Redirect', $url); + } } else { $this->getHeaders()->set('Location', $url); } @@ -801,9 +836,9 @@ class Response extends \yii\base\Response * * In a controller action you may use this method like this: * - * ~~~ + * ```php * return Yii::$app->getResponse()->refresh(); - * ~~~ + * ``` * * @param string $anchor the anchor that should be appended to the redirection URL. * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. @@ -820,7 +855,7 @@ class Response extends \yii\base\Response * Returns the cookie collection. * Through the returned cookie collection, you add or remove cookies as follows, * - * ~~~ + * ```php * // add a cookie * $response->cookies->add(new Cookie([ * 'name' => $name, @@ -831,7 +866,7 @@ class Response extends \yii\base\Response * $response->cookies->remove('name'); * // alternatively * unset($response->cookies['name']); - * ~~~ + * ``` * * @return CookieCollection the cookie collection. */ @@ -969,12 +1004,12 @@ class Response extends \yii\base\Response } if (is_array($this->content)) { - throw new InvalidParamException("Response content must not be an array."); + throw new InvalidParamException('Response content must not be an array.'); } elseif (is_object($this->content)) { if (method_exists($this->content, '__toString')) { $this->content = $this->content->__toString(); } else { - throw new InvalidParamException("Response content must be a string or an object implementing __toString()."); + throw new InvalidParamException('Response content must be a string or an object implementing __toString().'); } } } diff --git a/framework/web/Session.php b/framework/web/Session.php index 8c4fe7101a..57e5bd1728 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -22,14 +22,14 @@ use yii\base\InvalidParamException; * * Session can be used like an array to set and get session data. For example, * - * ~~~ + * ```php * $session = new Session; * $session->open(); * $value1 = $session['name1']; // get session variable 'name1' * $value2 = $session['name2']; // get session variable 'name2' * foreach ($session as $name => $value) // traverse all session variables * $session['name3'] = $value3; // set session variable 'name3' - * ~~~ + * ``` * * Session can be extended to support customized session storage. * To do so, override [[useCustomStorage]] so that it returns true, and @@ -97,6 +97,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co { parent::init(); register_shutdown_function([$this, 'close']); + if ($this->getIsActive()) { + $this->updateFlashCounters(); + Yii::warning("Session is already started", __METHOD__); + } } /** @@ -179,7 +183,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co { if ($this->getIsActive()) { @session_unset(); + $sessionId = session_id(); @session_destroy(); + @session_id($sessionId); } } @@ -208,11 +214,15 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) { $this->_hasSessionId = true; } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) { +<<<<<<< HEAD <<<<<<< HEAD $this->_hasSessionId = $request->get($name) !== null; ======= $this->_hasSessionId = $request->get($name) != ''; >>>>>>> yiichina/master +======= + $this->_hasSessionId = $request->get($name) != ''; +>>>>>>> master } else { $this->_hasSessionId = false; } @@ -521,7 +531,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co /** * Returns an iterator for traversing the session variables. - * This method is required by the interface IteratorAggregate. + * This method is required by the interface [[\IteratorAggregate]]. * @return SessionIterator an iterator for traversing the session variables. */ public function getIterator() @@ -542,7 +552,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co /** * Returns the number of items in the session. - * This method is required by Countable interface. + * This method is required by [[\Countable]] interface. * @return integer number of items in the session. */ public function count() @@ -675,14 +685,14 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * * ```php * session->getAllFlashes() as $key => $message) { + * foreach (Yii::$app->session->getAllFlashes() as $key => $message) { * echo '
        ' . $message . '
        '; * } ?> * ``` * * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger` * as the flash message key to influence the color of the div. - * + * * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code. * * [bootstrap alert]: http://getbootstrap.com/components/#alerts @@ -824,7 +834,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param mixed $offset the offset to check on * @return boolean */ @@ -836,7 +846,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param integer $offset the offset to retrieve element. * @return mixed the element at the offset, null if no element is found at the offset */ @@ -848,7 +858,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param integer $offset the offset to set element * @param mixed $item the element value */ @@ -859,7 +869,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } /** - * This method is required by the interface ArrayAccess. + * This method is required by the interface [[\ArrayAccess]]. * @param mixed $offset the offset to unset element */ public function offsetUnset($offset) diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php index 69911d33c6..ccdbbf49b8 100644 --- a/framework/web/SessionIterator.php +++ b/framework/web/SessionIterator.php @@ -8,7 +8,7 @@ namespace yii\web; /** - * SessionIterator implements an iterator for traversing session variables managed by [[Session]]. + * SessionIterator implements an [[\Iterator|iterator]] for traversing session variables managed by [[Session]]. * * @author Qiang Xue * @since 2.0 @@ -35,7 +35,7 @@ class SessionIterator implements \Iterator /** * Rewinds internal array pointer. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. */ public function rewind() { @@ -44,7 +44,7 @@ class SessionIterator implements \Iterator /** * Returns the key of the current array element. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return mixed the key of the current array element */ public function key() @@ -54,7 +54,7 @@ class SessionIterator implements \Iterator /** * Returns the current array element. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return mixed the current array element */ public function current() @@ -64,7 +64,7 @@ class SessionIterator implements \Iterator /** * Moves the internal pointer to the next array element. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. */ public function next() { @@ -75,7 +75,7 @@ class SessionIterator implements \Iterator /** * Returns whether there is an element at current position. - * This method is required by the interface Iterator. + * This method is required by the interface [[\Iterator]]. * @return boolean */ public function valid() diff --git a/framework/web/UnprocessableEntityHttpException.php b/framework/web/UnprocessableEntityHttpException.php new file mode 100644 index 0000000000..dca3a74104 --- /dev/null +++ b/framework/web/UnprocessableEntityHttpException.php @@ -0,0 +1,35 @@ + + * @since 2.0.7 + */ +class UnprocessableEntityHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(422, $message, $code, $previous); + } +} diff --git a/framework/web/UploadedFile.php b/framework/web/UploadedFile.php index e7c036d8d1..db484c2668 100644 --- a/framework/web/UploadedFile.php +++ b/framework/web/UploadedFile.php @@ -74,7 +74,7 @@ class UploadedFile extends Object * @param \yii\base\Model $model the data model * @param string $attribute the attribute name. The attribute name may contain array indexes. * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array. - * @return UploadedFile the instance of the uploaded file. + * @return null|UploadedFile the instance of the uploaded file. * Null is returned if no file is uploaded for the specified model attribute. * @see getInstanceByName() */ @@ -102,7 +102,7 @@ class UploadedFile extends Object * Returns an uploaded file according to the given file input name. * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]'). * @param string $name the name of the file input field. - * @return UploadedFile the instance of the uploaded file. + * @return null|UploadedFile the instance of the uploaded file. * Null is returned if no file is uploaded for the specified name. */ public static function getInstanceByName($name) @@ -116,11 +116,15 @@ class UploadedFile extends Object * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]', * 'files[n]'..., and you can retrieve them all by passing 'files' as the name. * @param string $name the name of the array of files +<<<<<<< HEAD <<<<<<< HEAD * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned ======= * @return UploadedFile[] the array of UploadedFile objects. Empty array is returned >>>>>>> yiichina/master +======= + * @return UploadedFile[] the array of UploadedFile objects. Empty array is returned +>>>>>>> master * if no adequate upload was found. Please note that this array will contain * all files from all sub-arrays regardless how deeply nested they are. */ @@ -175,7 +179,8 @@ class UploadedFile extends Object */ public function getBaseName() { - return pathinfo($this->name, PATHINFO_FILENAME); + // https://github.com/yiisoft/yii2/issues/11012 + return mb_substr(pathinfo('_' . $this->name, PATHINFO_FILENAME), 1, null, '8bit'); } /** @@ -227,7 +232,7 @@ class UploadedFile extends Object foreach ($names as $i => $name) { self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]); } - } elseif ($errors !== UPLOAD_ERR_NO_FILE) { + } elseif ((int)$errors !== UPLOAD_ERR_NO_FILE) { self::$_files[$key] = new static([ 'name' => $names, 'tempName' => $tempNames, @@ -238,7 +243,11 @@ class UploadedFile extends Object } } <<<<<<< HEAD +<<<<<<< HEAD } ======= } >>>>>>> yiichina/master +======= +} +>>>>>>> master diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index 92c42ccf1f..9786640694 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -21,7 +21,7 @@ use yii\caching\Cache; * You can modify its configuration by adding an array to your application config under `components` * as it is shown in the following example: * - * ~~~ + * ```php * 'urlManager' => [ * 'enablePrettyUrl' => true, * 'rules' => [ @@ -29,7 +29,7 @@ use yii\caching\Cache; * ], * // ... * ] - * ~~~ + * ``` * * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs. * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by @@ -46,7 +46,7 @@ class UrlManager extends Component * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query * string part of a URL, pretty URLs allow using path info to represent some of the parameters * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of - * "/index.php?r=news/view&id=100". + * "/index.php?r=news%2Fview&id=100". */ public $enablePrettyUrl = false; /** @@ -79,18 +79,18 @@ class UrlManager extends Component * * Here is an example configuration for RESTful CRUD controller: * - * ~~~php + * ```php * [ * 'dashboard' => 'site/index', * - * 'POST s' => '/create', - * 's' => '/index', + * 'POST s' => '/create', + * 's' => '/index', * - * 'PUT /' => '/update', - * 'DELETE /' => '/delete', - * '/' => '/view', + * 'PUT /' => '/update', + * 'DELETE /' => '/delete', + * '/' => '/view', * ]; - * ~~~ + * ``` * * Note that if you modify this property after the UrlManager object is created, make sure * you populate the array with rule objects instead of rule configurations. @@ -126,13 +126,23 @@ class UrlManager extends Component */ public $ruleConfig = ['class' => 'yii\web\UrlRule']; + /** + * @var string the cache key for cached rules + * @since 2.0.8 + */ + protected $cacheKey = __CLASS__; + private $_baseUrl; private $_scriptUrl; private $_hostInfo; <<<<<<< HEAD +<<<<<<< HEAD ======= private $_ruleCache; >>>>>>> yiichina/master +======= + private $_ruleCache; +>>>>>>> master /** @@ -149,7 +159,7 @@ class UrlManager extends Component $this->cache = Yii::$app->get($this->cache, false); } if ($this->cache instanceof Cache) { - $cacheKey = __CLASS__; + $cacheKey = $this->cacheKey; $hash = md5(json_encode($this->rules)); if (($data = $this->cache->get($cacheKey)) !== false && isset($data[1]) && $data[1] === $hash) { $this->rules = $data[0]; @@ -245,6 +255,11 @@ class UrlManager extends Component Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__); + // Ensure, that $pathInfo does not end with more than one slash. + if (strlen($pathInfo) > 1 && substr_compare($pathInfo, '//', -2, 2) === 0) { + return false; + } + $suffix = (string) $this->suffix; if ($suffix !== '' && $pathInfo !== '') { $n = strlen($this->suffix); @@ -280,7 +295,7 @@ class UrlManager extends Component * array format must be: * * ```php - * // generates: /index.php?r=site/index¶m1=value1¶m2=value2 + * // generates: /index.php?r=site%2Findex¶m1=value1¶m2=value2 * ['site/index', 'param1' => 'value1', 'param2' => 'value2'] * ``` * @@ -288,7 +303,7 @@ class UrlManager extends Component * For example, * * ```php - * // generates: /index.php?r=site/index¶m1=value1#name + * // generates: /index.php?r=site%2Findex¶m1=value1#name * ['site/index', 'param1' => 'value1', '#' => 'name'] * ``` * @@ -313,6 +328,7 @@ class UrlManager extends Component $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl(); if ($this->enablePrettyUrl) { +<<<<<<< HEAD <<<<<<< HEAD /* @var $rule UrlRule */ foreach ($this->rules as $rule) { @@ -322,10 +338,42 @@ class UrlManager extends Component return substr($url, 0, $pos) . $baseUrl . substr($url, $pos); } else { return $url . $baseUrl . $anchor; - } - } else { - return "$baseUrl/{$url}{$anchor}"; +======= + $cacheKey = $route . '?'; + foreach ($params as $key => $value) { + if ($value !== null) { + $cacheKey .= $key . '&'; + } + } + + $url = $this->getUrlFromCache($cacheKey, $route, $params); + + if ($url === false) { + $cacheable = true; + foreach ($this->rules as $rule) { + /* @var $rule UrlRule */ + if (!empty($rule->defaults) && $rule->mode !== UrlRule::PARSING_ONLY) { + // if there is a rule with default values involved, the matching result may not be cached + $cacheable = false; } + if (($url = $rule->createUrl($this, $route, $params)) !== false) { + if ($cacheable) { + $this->setRuleToCache($cacheKey, $rule); +>>>>>>> master + } + break; + } + } + } + + if ($url !== false) { + if (strpos($url, '://') !== false) { + if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) { + return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor; + } else { + return $url . $baseUrl . $anchor; + } +<<<<<<< HEAD ======= $cacheKey = $route . '?' . implode('&', array_keys($params)); @@ -360,6 +408,10 @@ class UrlManager extends Component } else { return "$baseUrl/{$url}{$anchor}"; >>>>>>> yiichina/master +======= + } else { + return "$baseUrl/{$url}{$anchor}"; +>>>>>>> master } } @@ -381,6 +433,41 @@ class UrlManager extends Component } } + /** + * Get URL from internal cache if exists + * @param string $cacheKey generated cache key to store data. + * @param string $route the route (e.g. `site/index`). + * @param array $params rule params. + * @return boolean|string the created URL + * @see createUrl() + * @since 2.0.8 + */ + protected function getUrlFromCache($cacheKey, $route, $params) + { + if (!empty($this->_ruleCache[$cacheKey])) { + foreach ($this->_ruleCache[$cacheKey] as $rule) { + /* @var $rule UrlRule */ + if (($url = $rule->createUrl($this, $route, $params)) !== false) { + return $url; + } + } + } else { + $this->_ruleCache[$cacheKey] = []; + } + return false; + } + + /** + * Store rule (e.g. [[UrlRule]]) to internal cache + * @param $cacheKey + * @param UrlRuleInterface $rule + * @since 2.0.8 + */ + protected function setRuleToCache($cacheKey, UrlRuleInterface $rule) + { + $this->_ruleCache[$cacheKey][] = $rule; + } + /** * Creates an absolute URL using the given route and query parameters. * @@ -438,7 +525,7 @@ class UrlManager extends Component */ public function setBaseUrl($value) { - $this->_baseUrl = rtrim($value, '/'); + $this->_baseUrl = $value === null ? null : rtrim($value, '/'); } /** @@ -497,6 +584,6 @@ class UrlManager extends Component */ public function setHostInfo($value) { - $this->_hostInfo = rtrim($value, '/'); + $this->_hostInfo = $value === null ? null : rtrim($value, '/'); } } diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php index 95cb3a1bd1..62c800c781 100644 --- a/framework/web/UrlRule.php +++ b/framework/web/UrlRule.php @@ -17,12 +17,12 @@ use yii\base\InvalidConfigException; * To define your own URL parsing and creation logic you can extend from this class * and add it to [[UrlManager::rules]] like this: * - * ~~~ + * ```php * 'rules' => [ * ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...], * // ... * ] - * ~~~ + * ``` * * @author Qiang Xue * @since 2.0 @@ -43,8 +43,10 @@ class UrlRule extends Object implements UrlRuleInterface */ public $name; /** + * On the rule initialization, the [[pattern]] matching parameters names will be replaced with [[placeholders]]. * @var string the pattern used to parse and create the path info part of a URL. * @see host + * @see placeholders */ public $pattern; /** @@ -88,6 +90,18 @@ class UrlRule extends Object implements UrlRuleInterface */ public $encodeParams = true; + /** + * @var array list of placeholders for matching parameters names. Used in [[parseRequest()]], [[createUrl()]]. + * On the rule initialization, the [[pattern]] parameters names will be replaced with placeholders. + * This array contains relations between the original parameters names and their placeholders. + * The array keys are the placeholders and the values are the original names. + * + * @see parseRequest() + * @see createUrl() + * @since 2.0.7 + */ + protected $placeholders = []; + /** * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL. */ @@ -151,7 +165,7 @@ class UrlRule extends Object implements UrlRuleInterface $this->pattern = '/' . $this->pattern . '/'; } - if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) { + if (strpos($this->route, '<') !== false && preg_match_all('/<([\w._-]+)>/', $this->route, $matches)) { foreach ($matches[1] as $name) { $this->_routeParams[$name] = "<$name>"; } @@ -166,35 +180,43 @@ class UrlRule extends Object implements UrlRuleInterface '(' => '\\(', ')' => '\\)', ]; + $tr2 = []; - if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { foreach ($matches as $match) { $name = $match[1][0]; $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+'; + $placeholder = 'a' . hash('crc32b', $name); // placeholder must begin with a letter + $this->placeholders[$placeholder] = $name; if (array_key_exists($name, $this->defaults)) { $length = strlen($match[0][0]); $offset = $match[0][1]; +<<<<<<< HEAD <<<<<<< HEAD if ($offset > 1 && $this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') { ======= if ($offset > 1 && $this->pattern[$offset - 1] === '/' && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')) { >>>>>>> yiichina/master $tr["/<$name>"] = "(/(?P<$name>$pattern))?"; +======= + if ($offset > 1 && $this->pattern[$offset - 1] === '/' && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')) { + $tr["/<$name>"] = "(/(?P<$placeholder>$pattern))?"; +>>>>>>> master } else { - $tr["<$name>"] = "(?P<$name>$pattern)?"; + $tr["<$name>"] = "(?P<$placeholder>$pattern)?"; } } else { - $tr["<$name>"] = "(?P<$name>$pattern)"; + $tr["<$name>"] = "(?P<$placeholder>$pattern)"; } if (isset($this->_routeParams[$name])) { - $tr2["<$name>"] = "(?P<$name>$pattern)"; + $tr2["<$name>"] = "(?P<$placeholder>$pattern)"; } else { $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#u"; } } } - $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern); + $this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern); $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u'; if (!empty($this->_routeParams)) { @@ -220,7 +242,7 @@ class UrlRule extends Object implements UrlRuleInterface } $pathInfo = $request->getPathInfo(); - $suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix); + $suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix); if ($suffix !== '' && $pathInfo !== '') { $n = strlen($suffix); if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) { @@ -241,6 +263,8 @@ class UrlRule extends Object implements UrlRuleInterface if (!preg_match($this->pattern, $pathInfo, $matches)) { return false; } + $matches = $this->substitutePlaceholderNames($matches); + foreach ($this->defaults as $name => $value) { if (!isset($matches[$name]) || $matches[$name] === '') { $matches[$name] = $value; @@ -285,6 +309,7 @@ class UrlRule extends Object implements UrlRuleInterface // match the route part first if ($route !== $this->route) { if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) { + $matches = $this->substitutePlaceholderNames($matches); foreach ($this->_routeParams as $name => $token) { if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) { $tr[$token] = ''; @@ -345,4 +370,36 @@ class UrlRule extends Object implements UrlRuleInterface return $url; } + + /** + * Returns list of regex for matching parameter. + * @return array parameter keys and regexp rules. + * + * @since 2.0.6 + */ + protected function getParamRules() + { + return $this->_paramRules; + } + + /** + * Iterates over [[placeholders]] and checks whether each placeholder exists as a key in $matches array. + * When found - replaces this placeholder key with a appropriate name of matching parameter. + * Used in [[parseRequest()]], [[createUrl()]]. + * + * @param array $matches result of `preg_match()` call + * @return array input array with replaced placeholder keys + * @see placeholders + * @since 2.0.7 + */ + protected function substitutePlaceholderNames(array $matches) + { + foreach ($this->placeholders as $placeholder => $name) { + if (isset($matches[$placeholder])) { + $matches[$name] = $matches[$placeholder]; + unset($matches[$placeholder]); + } + } + return $matches; + } } diff --git a/framework/web/UrlRuleInterface.php b/framework/web/UrlRuleInterface.php index 3d837d3dc8..8ca6403ccc 100644 --- a/framework/web/UrlRuleInterface.php +++ b/framework/web/UrlRuleInterface.php @@ -8,7 +8,7 @@ namespace yii\web; /** - * UrlRuleInterface is the interface that should be implemented URL rule classes. + * UrlRuleInterface is the interface that should be implemented by URL rule classes. * * @author Qiang Xue * @since 2.0 @@ -23,6 +23,7 @@ interface UrlRuleInterface * If false, it means this rule cannot be used to parse this path info. */ public function parseRequest($manager, $request); + /** * Creates a URL according to the given route and parameters. * @param UrlManager $manager the URL manager diff --git a/framework/web/User.php b/framework/web/User.php index 818efadaf6..c394cc323b 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -11,6 +11,7 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; use yii\base\InvalidValueException; +use yii\rbac\CheckAccessInterface; /** * User is the class for the "user" application component that manages the user authentication status. @@ -36,14 +37,14 @@ use yii\base\InvalidValueException; * You can modify its configuration by adding an array to your application config under `components` * as it is shown in the following example: * - * ~~~ + * ```php * 'user' => [ * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface * 'enableAutoLogin' => true, * // 'loginUrl' => ['user/login'], * // ... * ] - * ~~~ + * ``` * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. @@ -84,9 +85,9 @@ class User extends Component * The first element of the array should be the route to the login action, and the rest of * the name-value pairs are GET parameters used to construct the login URL. For example, * - * ~~~ + * ```php * ['site/login', 'ref' => 1] - * ~~~ + * ``` * * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called. */ @@ -103,6 +104,12 @@ class User extends Component * Note that this will not work if [[enableAutoLogin]] is true. */ public $authTimeout; + /** + * @var CheckAccessInterface The acess checker to use for checking access. + * If not set the application auth manager will be used. + * @since 2.0.9 + */ + public $accessChecker; /** * @var integer the number of seconds in which the user will be logged out automatically * regardless of activity. @@ -136,6 +143,11 @@ class User extends Component * @var string the session variable name used to store the value of [[returnUrl]]. */ public $returnUrlParam = '__returnUrl'; + /** + * @var array MIME types for which this component should redirect to the [[loginUrl]]. + * @since 2.0.8 + */ + public $acceptableRedirectTypes = ['text/html', 'application/xhtml+xml']; private $_access = []; @@ -279,35 +291,17 @@ class User extends Component */ protected function loginByCookie() { - $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); - if ($value === null) { - return; - } - - $data = json_decode($value, true); - if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) { - return; - } - - list ($id, $authKey, $duration) = $data; - /* @var $class IdentityInterface */ - $class = $this->identityClass; - $identity = $class::findIdentity($id); - if ($identity === null) { - return; - } elseif (!$identity instanceof IdentityInterface) { - throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); - } - - if ($identity->validateAuthKey($authKey)) { + $data = $this->getIdentityAndDurationFromCookie(); + if (isset($data['identity'], $data['duration'])) { + $identity = $data['identity']; + $duration = $data['duration']; if ($this->beforeLogin($identity, true, $duration)) { $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); + $id = $identity->getId(); $ip = Yii::$app->getRequest()->getUserIP(); Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); $this->afterLogin($identity, true, $duration); } - } else { - Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); } } @@ -391,9 +385,9 @@ class User extends Component * The first element of the array should be the route, and the rest of * the name-value pairs are GET parameters used to construct the URL. For example, * - * ~~~ + * ```php * ['admin/index', 'ref' => 1] - * ~~~ + * ``` */ public function setReturnUrl($url) { @@ -413,17 +407,27 @@ class User extends Component * * @param boolean $checkAjax whether to check if the request is an AJAX request. When this is true and the request * is an AJAX request, the current URL (for AJAX request) will NOT be set as the return URL. + * @param boolean $checkAcceptHeader whether to check if the request accepts HTML responses. Defaults to `true`. When this is true and + * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of + * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8. * @return Response the redirection response if [[loginUrl]] is set - * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set + * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is + * not applicable. + * @see checkAcceptHeader */ - public function loginRequired($checkAjax = true) + public function loginRequired($checkAjax = true, $checkAcceptHeader = true) { $request = Yii::$app->getRequest(); - if ($this->enableSession && (!$checkAjax || !$request->getIsAjax())) { + $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable(); + if ($this->enableSession + && $request->getIsGet() + && (!$checkAjax || !$request->getIsAjax()) + && $canRedirect + ) { $this->setReturnUrl($request->getUrl()); } - if ($this->loginUrl !== null) { - $loginUrl = (array)$this->loginUrl; + if ($this->loginUrl !== null && $canRedirect) { + $loginUrl = (array) $this->loginUrl; if ($loginUrl[0] !== Yii::$app->requestedRoute) { return Yii::$app->getResponse()->redirect($this->loginUrl); } @@ -546,6 +550,50 @@ class User extends Component Yii::$app->getResponse()->getCookies()->add($cookie); } + /** + * Determines if an identity cookie has a valid format and contains a valid auth key. + * This method is used when [[enableAutoLogin]] is true. + * This method attempts to authenticate a user using the information in the identity cookie. + * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null. + * @see loginByCookie() + * @since 2.0.9 + */ + protected function getIdentityAndDurationFromCookie() + { + $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); + if ($value === null) { + return null; + } + $data = json_decode($value, true); + if (count($data) == 3) { + list ($id, $authKey, $duration) = $data; + /* @var $class IdentityInterface */ + $class = $this->identityClass; + $identity = $class::findIdentity($id); + if ($identity !== null) { + if (!$identity instanceof IdentityInterface) { + throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); + } elseif (!$identity->validateAuthKey($authKey)) { + Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); + } else { + return ['identity' => $identity, 'duration' => $duration]; + } + } + } + $this->removeIdentityCookie(); + return null; + } + + /** + * Removes the identity cookie. + * This method is used when [[enableAutoLogin]] is true. + * @since 2.0.9 + */ + protected function removeIdentityCookie() + { + Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie)); + } + /** * Switches to a new identity for the current user. * @@ -568,6 +616,11 @@ class User extends Component return; } + /* Ensure any existing identity cookies are removed. */ + if ($this->enableAutoLogin) { + $this->removeIdentityCookie(); + } + $session = Yii::$app->getSession(); if (!YII_ENV_TEST) { $session->regenerateID(true); @@ -586,8 +639,6 @@ class User extends Component if ($duration > 0 && $this->enableAutoLogin) { $this->sendIdentityCookie($identity, $duration); } - } elseif ($this->enableAutoLogin) { - Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie)); } } @@ -639,31 +690,80 @@ class User extends Component * Checks if the user can perform the operation as specified by the given permission. * * Note that you must configure "authManager" application component in order to use this method. - * Otherwise an exception will be thrown. + * Otherwise it will always return false. * * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check. * @param array $params name-value pairs that would be passed to the rules associated - * with the roles and permissions assigned to the user. A param with name 'user' is added to - * this array, which holds the value of [[id]]. + * with the roles and permissions assigned to the user. * @param boolean $allowCaching whether to allow caching the result of access check. * When this parameter is true (default), if the access check of an operation was performed * before, its result will be directly returned when calling this method to check the same * operation. If this parameter is false, this method will always call - * [[\yii\rbac\ManagerInterface::checkAccess()]] to obtain the up-to-date access result. Note that this + * [[\yii\rbac\CheckAcessInterface::checkAccess()]] to obtain the up-to-date access result. Note that this * caching is effective only within the same request and only works when `$params = []`. * @return boolean whether the user can perform the operation as specified by the given permission. */ public function can($permissionName, $params = [], $allowCaching = true) { - $auth = Yii::$app->getAuthManager(); if ($allowCaching && empty($params) && isset($this->_access[$permissionName])) { return $this->_access[$permissionName]; } - $access = $auth->checkAccess($this->getId(), $permissionName, $params); + if (($accessChecker = $this->getAccessChecker()) === null) { + return false; + } + $access = $accessChecker->checkAccess($this->getId(), $permissionName, $params); if ($allowCaching && empty($params)) { $this->_access[$permissionName] = $access; } return $access; } + + /** + * Checks if the `Accept` header contains a content type that allows redirection to the login page. + * The login page is assumed to serve `text/html` or `application/xhtml+xml` by default. You can change acceptable + * content types by modifying [[acceptableRedirectTypes]] property. + * @return boolean whether this request may be redirected to the login page. + * @see acceptableRedirectTypes + * @since 2.0.8 + */ + protected function checkRedirectAcceptable() + { + $acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes(); + if (empty($acceptableTypes) || count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*') { + return true; + } + + foreach ($acceptableTypes as $type => $params) { + if (in_array($type, $this->acceptableRedirectTypes, true)) { + return true; + } + } + + return false; + } + + /** + * Returns auth manager associated with the user component. + * + * By default this is the `authManager` application component. + * You may override this method to return a different auth manager instance if needed. + * @return \yii\rbac\ManagerInterface + * @since 2.0.6 + * @deprecated Use `getAccessChecker()` instead. + */ + protected function getAuthManager() + { + return Yii::$app->getAuthManager(); + } + + /** + * Returns the acess checker used for checking access. + * @return CheckAccessInterface + * @since 2.0.9 + */ + protected function getAccessChecker() + { + return $this->accessChecker !== null ? $this->accessChecker : $this->getAuthManager(); + } } diff --git a/framework/web/View.php b/framework/web/View.php index f3907aeb2a..4b54303f9c 100644 --- a/framework/web/View.php +++ b/framework/web/View.php @@ -23,7 +23,7 @@ use yii\base\InvalidConfigException; * You can modify its configuration by adding an array to your application config under `components` * as it is shown in the following example: * - * ~~~ + * ```php * 'view' => [ * 'theme' => 'app\themes\MyTheme', * 'renderers' => [ @@ -31,7 +31,7 @@ use yii\base\InvalidConfigException; * ] * // ... * ] - * ~~~ + * ``` * * @property \yii\web\AssetManager $assetManager The asset manager. Defaults to the "assetManager" application * component. @@ -314,7 +314,10 @@ class View extends \yii\base\View /** * Registers a meta tag. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * * For example, a description meta tag can be added like the following: * @@ -327,7 +330,10 @@ class View extends \yii\base\View * * will result in the meta tag ``. * +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @param array $options the HTML attributes for the meta tag. * @param string $key the key that identifies the meta tag. If two meta tags are registered * with the same key, the latter will overwrite the former. If this is null, the new meta tag @@ -345,7 +351,10 @@ class View extends \yii\base\View /** * Registers a link tag. <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> master * * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon) * can be added like the following: @@ -359,7 +368,10 @@ class View extends \yii\base\View * **Note:** To register link tags for CSS stylesheets, use [[registerCssFile()]] instead, which * has more options for this kind of link tag. * +<<<<<<< HEAD >>>>>>> yiichina/master +======= +>>>>>>> master * @param array $options the HTML attributes for the link tag. * @param string $key the key that identifies the link tag. If two link tags are registered * with the same key, the latter will overwrite the former. If this is null, the new link tag @@ -376,6 +388,7 @@ class View extends \yii\base\View /** * Registers a CSS code block. +<<<<<<< HEAD <<<<<<< HEAD * @param string $css the CSS code block to be registered * @param array $options the HTML attributes for the style tag. @@ -383,6 +396,10 @@ class View extends \yii\base\View * @param string $css the content of the CSS code block to be registered * @param array $options the HTML attributes for the `", Html::style($content)); - $this->assertEquals("", Html::style($content, ['type' => 'text/less'])); + $this->assertEquals("", Html::style($content)); + $this->assertEquals("", Html::style($content, ['type' => 'text/less'])); } public function testScript() { $content = 'a <>'; - $this->assertEquals("", Html::script($content)); - $this->assertEquals("", Html::script($content, ['type' => 'text/js'])); + $this->assertEquals("", Html::script($content)); + $this->assertEquals("", Html::script($content, ['type' => 'text/js'])); } public function testCssFile() @@ -79,6 +89,7 @@ class HtmlTest extends TestCase $this->assertEquals('', Html::cssFile('http://example.com')); $this->assertEquals('', Html::cssFile('')); $this->assertEquals("", Html::cssFile('http://example.com', ['condition' => 'IE 9'])); + $this->assertEquals("\n" . '' . "\n", Html::cssFile('http://example.com', ['condition' => '(gte IE 9)|(!IE)'])); } public function testJsFile() @@ -86,6 +97,7 @@ class HtmlTest extends TestCase $this->assertEquals('', Html::jsFile('http://example.com')); $this->assertEquals('', Html::jsFile('')); $this->assertEquals("", Html::jsFile('http://example.com', ['condition' => 'IE 9'])); + $this->assertEquals("\n" . '' . "\n", Html::jsFile('http://example.com', ['condition' => '(gte IE 9)|(!IE)'])); } public function testBeginForm() @@ -110,6 +122,7 @@ class HtmlTest extends TestCase $this->assertEquals('something', Html::a('something', '/example')); $this->assertEquals('something', Html::a('something', '')); $this->assertEquals('http://www.быстроном.рф', Html::a('http://www.быстроном.рф', 'http://www.быстроном.рф')); + $this->assertEquals('Test page', Html::a('Test page', Url::to(['/site/test'], 'https'))); } public function testMailto() @@ -333,12 +346,22 @@ EOD; EOD; $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, [], ['multiple' => true])); + $this->assertEqualsWithoutLE($expected, Html::listBox('test[]', null, [], ['multiple' => true])); + $expected = << EOD; $this->assertEqualsWithoutLE($expected, Html::listBox('test', '', [], ['unselect' => '0'])); + + $expected = << + + + +EOD; + $this->assertEqualsWithoutLE($expected, Html::listBox('test', new \ArrayObject(['value1', 'value2']), $this->getDataItems())); } public function testCheckboxList() @@ -350,6 +373,7 @@ EOD; EOD; $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test[]', ['value2'], $this->getDataItems())); $expected = << @@ -375,6 +399,26 @@ EOD; return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, ['value' => $value])); } ])); + + $expected = <<text1 +1 +EOD; + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems(), [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, ['value' => $value])); + }, + 'tag' => false + ])); + + + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', new \ArrayObject(['value2']), $this->getDataItems(), [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, ['value' => $value])); + }, + 'tag' => false + ])); + } public function testRadioList() @@ -411,6 +455,24 @@ EOD; return $index . Html::label($label . ' ' . Html::radio($name, $checked, ['value' => $value])); } ])); + + $expected = <<text1 +1 +EOD; + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $this->getDataItems(), [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::radio($name, $checked, ['value' => $value])); + }, + 'tag' => false + ])); + + $this->assertEqualsWithoutLE($expected, Html::radioList('test', new \ArrayObject(['value2']), $this->getDataItems(), [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::radio($name, $checked, ['value' => $value])); + }, + 'tag' => false + ])); } public function testUl() @@ -535,6 +597,10 @@ EOD; $this->assertEquals('', Html::renderTagAttributes([])); $this->assertEquals(' name="test" value="1<>"', Html::renderTagAttributes(['name' => 'test', 'empty' => null, 'value' => '1<>'])); $this->assertEquals(' checked disabled', Html::renderTagAttributes(['checked' => true, 'disabled' => true, 'hidden' => false])); + $this->assertEquals(' class="first second"', Html::renderTagAttributes(['class' => ['first', 'second']])); + $this->assertEquals('', Html::renderTagAttributes(['class' => []])); + $this->assertEquals(' style="width: 100px; height: 200px;"', Html::renderTagAttributes(['style' => ['width' => '100px', 'height' => '200px']])); + $this->assertEquals('', Html::renderTagAttributes(['style' => []])); } public function testAddCssClass() @@ -554,6 +620,38 @@ EOD; $this->assertEquals(['class' => 'test test2 test3'], $options); Html::addCssClass($options, 'test2'); $this->assertEquals(['class' => 'test test2 test3'], $options); + + $options = [ + 'class' => ['test'] + ]; + Html::addCssClass($options, 'test2'); + $this->assertEquals(['class' => ['test', 'test2']], $options); + Html::addCssClass($options, 'test2'); + $this->assertEquals(['class' => ['test', 'test2']], $options); + Html::addCssClass($options, ['test3']); + $this->assertEquals(['class' => ['test', 'test2', 'test3']], $options); + + $options = [ + 'class' => 'test' + ]; + Html::addCssClass($options, ['test1', 'test2']); + $this->assertEquals(['class' => 'test test1 test2'], $options); + } + + /** + * @depends testAddCssClass + */ + public function testMergeCssClass() + { + $options = [ + 'class' => [ + 'persistent' => 'test1' + ] + ]; + Html::addCssClass($options, ['persistent' => 'test2']); + $this->assertEquals(['persistent' => 'test1'], $options['class']); + Html::addCssClass($options, ['additional' => 'test2']); + $this->assertEquals(['persistent' => 'test1', 'additional' => 'test2'], $options['class']); } public function testRemoveCssClass() @@ -567,6 +665,19 @@ EOD; $this->assertEquals(['class' => 'test3'], $options); Html::removeCssClass($options, 'test3'); $this->assertEquals([], $options); + + $options = ['class' => ['test', 'test2', 'test3']]; + Html::removeCssClass($options, 'test2'); + $this->assertEquals(['class' => ['test', 2 => 'test3']], $options); + Html::removeCssClass($options, 'test'); + Html::removeCssClass($options, 'test3'); + $this->assertEquals([], $options); + + $options = [ + 'class' => 'test test1 test2' + ]; + Html::removeCssClass($options, ['test1', 'test2']); + $this->assertEquals(['class' => 'test'], $options); } public function testCssStyleFromArray() @@ -608,6 +719,14 @@ EOD; $options = []; Html::addCssStyle($options, 'width: 110px; color: red;', false); $this->assertEquals('width: 110px; color: red;', $options['style']); + + $options = [ + 'style' => [ + 'width' => '100px' + ], + ]; + Html::addCssStyle($options, ['color' => 'red'], false); + $this->assertEquals('width: 100px; color: red;', $options['style']); } public function testRemoveCssStyle() @@ -623,6 +742,14 @@ EOD; $options = []; Html::removeCssStyle($options, ['color', 'background']); $this->assertTrue(!array_key_exists('style', $options)); + $options = [ + 'style' => [ + 'color' => 'red', + 'width' => '100px', + ], + ]; + Html::removeCssStyle($options, ['color']); + $this->assertEquals('width: 100px;', $options['style']); } public function testBooleanAttributes() @@ -655,4 +782,174 @@ EOD; 'value 2' => 'text 2', ]; } + + /** + * Data provider for [[testActiveTextInput()]] + * @return array test data + */ + public function dataProviderActiveTextInput() + { + return [ + [ + 'some text', + [], + '', + ], + [ + '', + [ + 'maxlength' => true + ], + '', + ], + [ + '', + [ + 'maxlength' => 99 + ], + '', + ], + ]; + } + + /** + * @dataProvider dataProviderActiveTextInput + * + * @param string $value + * @param array $options + * @param string $expectedHtml + */ + public function testActiveTextInput($value, array $options, $expectedHtml) + { + $model = new HtmlTestModel(); + $model->name = $value; + $this->assertEquals($expectedHtml, Html::activeTextInput($model, 'name', $options)); + } + + /** + * Data provider for [[testActivePasswordInput()]] + * @return array test data + */ + public function dataProviderActivePasswordInput() + { + return [ + [ + 'some text', + [], + '', + ], + [ + '', + [ + 'maxlength' => true + ], + '', + ], + [ + '', + [ + 'maxlength' => 99 + ], + '', + ], + ]; + } + + /** + * @dataProvider dataProviderActivePasswordInput + * + * @param string $value + * @param array $options + * @param string $expectedHtml + */ + public function testActivePasswordInput($value, array $options, $expectedHtml) + { + $model = new HtmlTestModel(); + $model->name = $value; + $this->assertEquals($expectedHtml, Html::activePasswordInput($model, 'name', $options)); + } + + /** + * Data provider for [[testActiveTextArea()]] + * @return array test data + */ + public function dataProviderActiveTextArea() + { + return [ + [ + 'some text', + [], + '', + ], + [ + 'some text', + [ + 'maxlength' => true + ], + '', + ], + [ + 'some text', + [ + 'maxlength' => 99 + ], + '', + ], + [ + 'some text', + [ + 'value' => 'override text' + ], + '', + ], + ]; + } + + /** + * @dataProvider dataProviderActiveTextArea + * + * @param string $value + * @param array $options + * @param string $expectedHtml + */ + public function testActiveTextArea($value, array $options, $expectedHtml) + { + $model = new HtmlTestModel(); + $model->description = $value; + $this->assertEquals($expectedHtml, Html::activeTextArea($model, 'description', $options)); + } + + /** + * Fixes #10078 + */ + public function testCsrfDisable() + { + Yii::$app->request->enableCsrfValidation = true; + Yii::$app->request->cookieValidationKey = 'foobar'; + + $csrfForm = Html::beginForm('/index.php', 'post', ['id' => 'mycsrfform']); + $this->assertEquals( + '' + . "\n" . '', + $csrfForm + ); + + $noCsrfForm = Html::beginForm('/index.php', 'post', ['csrf' => false, 'id' => 'myform']); + $this->assertEquals('', $noCsrfForm); + } +} + +class HtmlTestModel extends Model +{ + public $name; + public $description; + + public function rules() + { + return [ + ['name', 'required'], + ['name', 'string', 'max' => 100], + ['description', 'string', 'max' => 500], + ]; + } } diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/framework/helpers/HtmlTest.php~HEAD similarity index 99% rename from tests/unit/framework/helpers/HtmlTest.php rename to tests/framework/helpers/HtmlTest.php~HEAD index f38b0e6a9d..f8ee237b8e 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/framework/helpers/HtmlTest.php~HEAD @@ -109,6 +109,7 @@ class HtmlTest extends TestCase $this->assertEquals('something<>', Html::a('something<>')); $this->assertEquals('something', Html::a('something', '/example')); $this->assertEquals('something', Html::a('something', '')); + $this->assertEquals('http://www.быстроном.рф', Html::a('http://www.быстроном.рф', 'http://www.быстроном.рф')); } public function testMailto() diff --git a/tests/framework/helpers/InflectorTest.php b/tests/framework/helpers/InflectorTest.php index 658eef6367..cb15fc09f5 100644 --- a/tests/framework/helpers/InflectorTest.php +++ b/tests/framework/helpers/InflectorTest.php @@ -30,6 +30,7 @@ class InflectorTest extends TestCase 'bus' => 'buses', 'test' => 'tests', 'car' => 'cars', + 'netherlands' => 'netherlands', ]; foreach ($testData as $testIn => $testOut) { @@ -57,6 +58,7 @@ class InflectorTest extends TestCase 'buses' => 'bus', 'tests' => 'test', 'cars' => 'car', + 'Netherlands' => 'Netherlands', ]; foreach ($testData as $testIn => $testOut) { $this->assertEquals($testOut, Inflector::singularize($testIn)); @@ -162,26 +164,24 @@ class InflectorTest extends TestCase $data = [ // Korean '해동검도' => 'haedong-geomdo', - // Hiragana 'ひらがな' => 'hiragana', - // Georgian 'საქართველო' => 'sakartvelo', - // Arabic 'العربي' => 'alrby', 'عرب' => 'rb', - // Hebrew 'עִבְרִית' => 'iberiyt', - // Turkish - 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'sanrm-hepimiz-ayn-seyi-dusunuyoruz', - + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'sanirim-hepimiz-ayni-seyi-dusunuyoruz', // Russian 'недвижимость' => 'nedvizimost', 'Контакты' => 'kontakty', + // Chinese + '美国' => 'mei-guo', + // Estonian + 'Jääär' => 'jaaar', ]; foreach ($data as $source => $expected) { @@ -189,6 +189,135 @@ class InflectorTest extends TestCase } } + public function testTransliterateStrict() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + + // Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius! + $data = [ + // Korean + '해동검도' => 'haedong-geomdo', + // Hiragana + 'ひらがな' => 'hiragana', + // Georgian + 'საქართველო' => 'sakartvelo', + // Arabic + 'العربي' => 'ạlʿrby', + 'عرب' => 'ʿrb', + // Hebrew + 'עִבְרִית' => 'ʻibĕriyţ', + // Turkish + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanırım hepimiz aynı şeyi düşünüyoruz.', + + // Russian + 'недвижимость' => 'nedvižimostʹ', + 'Контакты' => 'Kontakty', + + // Ukrainian + 'Українська: ґанок, європа' => 'Ukraí̈nsʹka: g̀anok, êvropa', + + // Serbian + 'Српска: ђ, њ, џ!' => 'Srpska: đ, n̂, d̂!', + + // Spanish + '¿Español?' => '¿Español?', + // Chinese + '美国' => 'měi guó', + ]; + + foreach ($data as $source => $expected) { + $this->assertEquals($expected, Inflector::transliterate($source, Inflector::TRANSLITERATE_STRICT)); + } + } + + public function testTransliterateMedium() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + + // Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius! + $data = [ + // Korean + '해동검도' => 'haedong-geomdo', + // Hiragana + 'ひらがな' => 'hiragana', + // Georgian + 'საქართველო' => 'sakartvelo', + // Arabic + 'العربي' => 'alʿrby', + 'عرب' => 'ʿrb', + // Hebrew + 'עִבְרִית' => 'ʻiberiyt', + // Turkish + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', + + // Russian + 'недвижимость' => 'nedvizimostʹ', + 'Контакты' => 'Kontakty', + + // Ukrainian + 'Українська: ґанок, європа' => 'Ukrainsʹka: ganok, evropa', + + // Serbian + 'Српска: ђ, њ, џ!' => 'Srpska: d, n, d!', + + // Spanish + '¿Español?' => '¿Espanol?', + // Chinese + '美国' => 'mei guo', + ]; + + foreach ($data as $source => $expected) { + $this->assertEquals($expected, Inflector::transliterate($source, Inflector::TRANSLITERATE_MEDIUM)); + } + } + + public function testTransliterateLoose() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + + // Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius! + $data = [ + // Korean + '해동검도' => 'haedong-geomdo', + // Hiragana + 'ひらがな' => 'hiragana', + // Georgian + 'საქართველო' => 'sakartvelo', + // Arabic + 'العربي' => 'alrby', + 'عرب' => 'rb', + // Hebrew + 'עִבְרִית' => 'iberiyt', + // Turkish + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', + + // Russian + 'недвижимость' => 'nedvizimost', + 'Контакты' => 'Kontakty', + + // Ukrainian + 'Українська: ґанок, європа' => 'Ukrainska: ganok, evropa', + + // Serbian + 'Српска: ђ, њ, џ!' => 'Srpska: d, n, d!', + + // Spanish + '¿Español?' => 'Espanol?', + // Chinese + '美国' => 'mei guo', + ]; + + foreach ($data as $source => $expected) { + $this->assertEquals($expected, Inflector::transliterate($source, Inflector::TRANSLITERATE_LOOSE)); + } + } + public function testSlugPhp() { $data = [ diff --git a/tests/unit/framework/helpers/InflectorTest.php b/tests/framework/helpers/InflectorTest.php~HEAD similarity index 100% rename from tests/unit/framework/helpers/InflectorTest.php rename to tests/framework/helpers/InflectorTest.php~HEAD diff --git a/tests/framework/helpers/JsonTest.php b/tests/framework/helpers/JsonTest.php index eb8bca9306..2694fbe05a 100644 --- a/tests/framework/helpers/JsonTest.php +++ b/tests/framework/helpers/JsonTest.php @@ -3,9 +3,17 @@ namespace yiiunit\framework\helpers; use yii\base\Model; +<<<<<<< HEAD use yii\helpers\Json; use yiiunit\TestCase; use yii\web\JsExpression; +======= +use yii\helpers\BaseJson; +use yii\helpers\Json; +use yiiunit\TestCase; +use yii\web\JsExpression; +use yiiunit\framework\web\Post; +>>>>>>> master /** * @group helpers @@ -100,6 +108,27 @@ class JsonTest extends TestCase // JsonSerializable $data = new JsonModel(); $this->assertSame('{"json":"serializable"}', Json::htmlEncode($data)); +<<<<<<< HEAD +======= + + // https://github.com/yiisoft/yii2/issues/10278 + $xml = ' + + ieu2iqw4o + + Kiev + +'; + + $document = simplexml_load_string($xml); + $this->assertSame('{"apiKey":"ieu2iqw4o","methodProperties":{"FindByString":"Kiev"}}', Json::encode($document)); + + $postsStack = new \SplStack(); + $postsStack->push(new Post(915, 'record1')); + $postsStack->push(new Post(456, 'record2')); + + $this->assertSame('{"1":{"id":456,"title":"record2"},"0":{"id":915,"title":"record1"}}', Json::encode($postsStack)); +>>>>>>> master } public function testDecode() @@ -117,6 +146,34 @@ class JsonTest extends TestCase $this->setExpectedException('yii\base\InvalidParamException'); Json::decode($json); } +<<<<<<< HEAD +======= + + public function testHandleJsonError() + { + // Basic syntax error + try { + $json = "{'a': '1'}"; + Json::decode($json); + } catch (\yii\base\InvalidParamException $e) { + $this->assertSame(BaseJson::$jsonErrorMessages['JSON_ERROR_SYNTAX'], $e->getMessage()); + } + + // Unsupported type since PHP 5.5 + try { + $fp = fopen('php://stdin', 'r'); + $data = ['a' => $fp]; + Json::encode($data); + fclose($fp); + } catch (\yii\base\InvalidParamException $e) { + if (PHP_VERSION_ID >= 50500) { + $this->assertSame(BaseJson::$jsonErrorMessages['JSON_ERROR_UNSUPPORTED_TYPE'], $e->getMessage()); + } else { + $this->assertSame(BaseJson::$jsonErrorMessages['JSON_ERROR_SYNTAX'], $e->getMessage()); + } + } + } +>>>>>>> master } class JsonModel extends Model implements \JsonSerializable diff --git a/tests/framework/helpers/MarkdownTest.php b/tests/framework/helpers/MarkdownTest.php new file mode 100644 index 0000000000..af83c626e7 --- /dev/null +++ b/tests/framework/helpers/MarkdownTest.php @@ -0,0 +1,31 @@ + + * @group helpers + */ +class MarkdownTest extends TestCase +{ + public function testOriginalFlavor() + { + $text = <<assertEquals(Markdown::process($text), Markdown::process($text, 'original')); + + Markdown::$defaultFlavor = 'gfm-comment'; + $this->assertNotEquals(Markdown::process($text), Markdown::process($text, 'original')); + $this->assertEquals(Markdown::process($text), Markdown::process($text, 'gfm-comment')); + } +} diff --git a/tests/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php index b305aaf97c..318e8db7f9 100644 --- a/tests/framework/helpers/StringHelperTest.php +++ b/tests/framework/helpers/StringHelperTest.php @@ -118,9 +118,11 @@ class StringHelperTest extends TestCase $this->assertEquals('это тестовая multibyte!!!', StringHelper::truncateWords('это тестовая multibyte строка', 3, '!!!')); $this->assertEquals('это строка с неожиданными...', StringHelper::truncateWords('это строка с неожиданными пробелами', 4)); + $this->assertEquals('lorem ipsum', StringHelper::truncateWords('lorem ipsum', 3, '...', true)); // With Html $this->assertEquals('This is a test...', StringHelper::truncateWords('This is a test sentance', 4, '...', true)); $this->assertEquals('This is a test...', StringHelper::truncateWords('This is a test sentance', 4, '...', true)); + $this->assertEquals('

        раз два три четыре пять

        шесть

        ...', StringHelper::truncateWords('

        раз два три четыре пять

        шесть семь восемь девять десять

        ', 6, '...', true)); } /** @@ -231,9 +233,21 @@ class StringHelperTest extends TestCase public function testExplode() { $this->assertEquals(['It', 'is', 'a first', 'test'], StringHelper::explode("It, is, a first, test")); + $this->assertEquals(['It', 'is', 'a test with trimmed digits', '0', '1', '2'], StringHelper::explode("It, is, a test with trimmed digits, 0, 1, 2", ',', true, true)); $this->assertEquals(['It', 'is', 'a second', 'test'], StringHelper::explode("It+ is+ a second+ test", '+')); $this->assertEquals(['Save', '', '', 'empty trimmed string'], StringHelper::explode("Save, ,, empty trimmed string", ',')); $this->assertEquals(['Здесь', 'multibyte', 'строка'], StringHelper::explode("Здесь我 multibyte我 строка", '我')); $this->assertEquals(['Disable', ' trim ', 'here but ignore empty'], StringHelper::explode("Disable, trim ,,,here but ignore empty", ',', false, true)); + $this->assertEquals(['It/', ' is?', ' a', ' test with rtrim'], StringHelper::explode("It/, is?, a , test with rtrim", ',', 'rtrim')); + $this->assertEquals(['It', ' is', ' a ', ' test with closure'], StringHelper::explode("It/, is?, a , test with closure", ',', function ($value) { return trim($value, '/?'); })); + } + + public function testWordCount() + { + $this->assertEquals(3, StringHelper::countWords('china 中国 ㄍㄐㄋㄎㄌ')); + $this->assertEquals(4, StringHelper::countWords('и много тут слов?')); + $this->assertEquals(4, StringHelper::countWords("и\rмного\r\nтут\nслов?")); + $this->assertEquals(1, StringHelper::countWords('крем-брюле')); + $this->assertEquals(1, StringHelper::countWords(' слово ')); } } diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php~HEAD similarity index 100% rename from tests/unit/framework/helpers/StringHelperTest.php rename to tests/framework/helpers/StringHelperTest.php~HEAD diff --git a/tests/framework/helpers/UrlTest.php b/tests/framework/helpers/UrlTest.php index 463036210f..d634ec41d4 100644 --- a/tests/framework/helpers/UrlTest.php +++ b/tests/framework/helpers/UrlTest.php @@ -205,4 +205,13 @@ class UrlTest extends TestCase $this->assertEquals('http://example.com/base/index.php?r=page%2Fview&id=10', Url::canonical()); $this->removeMockedAction(); } + + public function testIsRelative() + { + $this->assertTrue(Url::isRelative('/test/index.php')); + $this->assertTrue(Url::isRelative('index.php')); + $this->assertFalse(Url::isRelative('//example.com/')); + $this->assertFalse(Url::isRelative('http://example.com/')); + $this->assertFalse(Url::isRelative('https://example.com/')); + } } diff --git a/tests/unit/framework/helpers/UrlTest.php b/tests/framework/helpers/UrlTest.php~HEAD similarity index 100% rename from tests/unit/framework/helpers/UrlTest.php rename to tests/framework/helpers/UrlTest.php~HEAD diff --git a/tests/framework/helpers/VarDumperTest.php b/tests/framework/helpers/VarDumperTest.php index de8290cecc..7e00e1182e 100644 --- a/tests/framework/helpers/VarDumperTest.php +++ b/tests/framework/helpers/VarDumperTest.php @@ -2,6 +2,10 @@ namespace yiiunit\framework\helpers; use yii\helpers\VarDumper; +<<<<<<< HEAD +======= +use yiiunit\data\helpers\CustomDebugInfo; +>>>>>>> master use yiiunit\TestCase; /** @@ -9,6 +13,7 @@ use yiiunit\TestCase; */ class VarDumperTest extends TestCase { +<<<<<<< HEAD public function testDumpObject() { $obj = new \StdClass(); @@ -16,6 +21,37 @@ class VarDumperTest extends TestCase VarDumper::dump($obj); $this->assertEquals("stdClass#1\n(\n)", ob_get_contents()); ob_end_clean(); +======= + public function testDumpIncompleteObject() + { + $serializedObj = 'O:16:"nonExistingClass":0:{}'; + $incompleteObj = unserialize($serializedObj); + $dumpResult = VarDumper::dumpAsString($incompleteObj); + $this->assertContains("__PHP_Incomplete_Class#1\n(", $dumpResult); + $this->assertContains("nonExistingClass", $dumpResult); + } + + public function testExportIncompleteObject() + { + $serializedObj = 'O:16:"nonExistingClass":0:{}'; + $incompleteObj = unserialize($serializedObj); + $exportResult = VarDumper::export($incompleteObj); + $this->assertContains("nonExistingClass", $exportResult); + } + + public function testDumpObject() + { + $obj = new \StdClass(); + $this->assertEquals("stdClass#1\n(\n)", VarDumper::dumpAsString($obj)); + + $obj = new \StdClass(); + $obj->name = 'test-name'; + $obj->price = 19; + $dumpResult = VarDumper::dumpAsString($obj); + $this->assertContains("stdClass#1\n(", $dumpResult); + $this->assertContains("[name] => 'test-name'", $dumpResult); + $this->assertContains("[price] => 19", $dumpResult); +>>>>>>> master } /** @@ -83,6 +119,29 @@ RESULT; RESULT; $data[] = [$var, $expectedResult]; +<<<<<<< HEAD +======= + $var = [ + 'key1' => [ + 'subkey1' => 'value2', + ], + 'key2' => [ + 'subkey2' => 'value3', + ], + ]; + $expectedResult = << [ + 'subkey1' => 'value2', + ], + 'key2' => [ + 'subkey2' => 'value3', + ], +] +RESULT; + $data[] = [$var, $expectedResult]; + +>>>>>>> master // Objects : $var = new \StdClass(); @@ -90,9 +149,14 @@ RESULT; $expectedResult = "unserialize('" . serialize($var) . "')"; $data[] = [$var, $expectedResult]; +<<<<<<< HEAD $var = new \StdClass(); $var->testFunction = function () {return 2;}; $expectedResult = var_export($var, true); +======= + $var = function () {return 2;}; + $expectedResult = 'function () {return 2;}'; +>>>>>>> master $data[] = [$var, $expectedResult]; return $data; @@ -110,4 +174,41 @@ RESULT; $this->assertEqualsWithoutLE($expectedResult, $exportResult); //$this->assertEquals($var, eval('return ' . $exportResult . ';')); } +<<<<<<< HEAD +======= + + /** + * @depends testExport + */ + public function testExportObjectFallback() + { + $var = new \StdClass(); + $var->testFunction = function () {return 2;}; + $exportResult = VarDumper::export($var); + $this->assertNotEmpty($exportResult); + + $master = new \StdClass(); + $slave = new \StdClass(); + $master->slave = $slave; + $slave->master = $master; + $master->function = function() {return true;}; + + $exportResult = VarDumper::export($master); + $this->assertNotEmpty($exportResult); + } + + /** + * @depends testDumpObject + */ + public function testDumpClassWithCustomDebugInfo() + { + $object = new CustomDebugInfo(); + $object->volume = 10; + $object->unitPrice = 15; + + $dumpResult = VarDumper::dumpAsString($object); + $this->assertContains('totalPrice', $dumpResult); + $this->assertNotContains('unitPrice', $dumpResult); + } +>>>>>>> master } diff --git a/tests/framework/i18n/DbMessageSourceTest.php b/tests/framework/i18n/DbMessageSourceTest.php new file mode 100644 index 0000000000..8215d736e5 --- /dev/null +++ b/tests/framework/i18n/DbMessageSourceTest.php @@ -0,0 +1,168 @@ + + * @since 2.0.7 + */ +class DbMessageSourceTest extends I18NTest +{ + protected static $database; + protected static $driverName = 'mysql'; + + /** + * @var Connection + */ + protected static $db; + + protected function setI18N() + { + $this->i18n = new I18N([ + 'translations' => [ + 'test' => [ + 'class' => $this->getMessageSourceClass(), + 'db' => static::$db, + ] + ] + ]); + } + + private function getMessageSourceClass() + { + return DbMessageSource::className(); + } + + protected static function runConsoleAction($route, $params = []) + { + if (Yii::$app === null) { + new \yii\console\Application([ + 'id' => 'Migrator', + 'basePath' => '@yiiunit', + 'controllerMap' => [ + 'migrate' => EchoMigrateController::className(), + ], + 'components' => [ + 'db' => static::getConnection(), + ], + ]); + } + + ob_start(); + $result = Yii::$app->runAction($route, $params); + echo "Result is " . $result; + if ($result !== \yii\console\Controller::EXIT_CODE_NORMAL) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + $databases = static::getParam('databases'); + static::$database = $databases[static::$driverName]; + $pdo_database = 'pdo_' . static::$driverName; + + if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { + static::markTestSkipped('pdo and ' . $pdo_database . ' extension are required.'); + } + + static::runConsoleAction('migrate/up', ['migrationPath' => '@yii/i18n/migrations/', 'interactive' => false]); + + static::$db->createCommand()->batchInsert('source_message', ['id', 'category', 'message'], [ + [1, 'test', 'Hello world!'], + [2, 'test', 'The dog runs fast.'], + [3, 'test', 'His speed is about {n} km/h.'], + [4, 'test', 'His name is {name} and his speed is about {n, number} km/h.'], + [5, 'test', 'There {n, plural, =0{no cats} =1{one cat} other{are # cats}} on lying on the sofa!'], + ])->execute(); + + static::$db->createCommand()->batchInsert('message', ['id', 'language', 'translation'], [ + [1, 'de', 'Hallo Welt!'], + [2, 'de-DE', 'Der Hund rennt schnell.'], + [2, 'en-US', 'The dog runs fast (en-US).'], + [2, 'ru', 'Собака бегает быстро.'], + [3, 'de-DE', 'Seine Geschwindigkeit beträgt {n} km/h.'], + [4, 'de-DE', 'Er heißt {name} und ist {n, number} km/h schnell.'], + [5, 'ru', 'На диване {n, plural, =0{нет кошек} =1{лежит одна кошка} one{лежит # кошка} few{лежит # кошки} many{лежит # кошек} other{лежит # кошки}}!'], + ])->execute(); + } + + public static function tearDownAfterClass() + { + static::runConsoleAction('migrate/down', ['migrationPath' => '@yii/i18n/migrations/', 'interactive' => false]); + if (static::$db) { + static::$db->close(); + } + Yii::$app = null; + parent::tearDownAfterClass(); + } + + /** + * @throws \yii\base\InvalidParamException + * @throws \yii\db\Exception + * @throws \yii\base\InvalidConfigException + * @return \yii\db\Connection + */ + public static function getConnection() + { + if (static::$db == null) { + $db = new Connection; + $db->dsn = static::$database['dsn']; + if (isset(static::$database['username'])) { + $db->username = static::$database['username']; + $db->password = static::$database['password']; + } + if (isset(static::$database['attributes'])) { + $db->attributes = static::$database['attributes']; + } + if (!$db->isActive) { + $db->open(); + } + static::$db = $db; + } + return static::$db; + } + + public function testMissingTranslationEvent() + { + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + $this->assertEquals('Missing translation message.', $this->i18n->translate('test', 'Missing translation message.', [], 'de-DE')); + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + + Event::on(DbMessageSource::className(), DbMessageSource::EVENT_MISSING_TRANSLATION, function ($event) {}); + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + $this->assertEquals('Missing translation message.', $this->i18n->translate('test', 'Missing translation message.', [], 'de-DE')); + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + Event::off(DbMessageSource::className(), DbMessageSource::EVENT_MISSING_TRANSLATION); + + Event::on(DbMessageSource::className(), DbMessageSource::EVENT_MISSING_TRANSLATION, function ($event) { + if ($event->message == 'New missing translation message.') { + $event->translatedMessage = 'TRANSLATION MISSING HERE!'; + } + }); + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + $this->assertEquals('Another missing translation message.', $this->i18n->translate('test', 'Another missing translation message.', [], 'de-DE')); + $this->assertEquals('Missing translation message.', $this->i18n->translate('test', 'Missing translation message.', [], 'de-DE')); + $this->assertEquals('TRANSLATION MISSING HERE!', $this->i18n->translate('test', 'New missing translation message.', [], 'de-DE')); + $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); + Event::off(DbMessageSource::className(), DbMessageSource::EVENT_MISSING_TRANSLATION); + } + + + public function testIssue11429($sourceLanguage = null) + { + $this->markTestSkipped('DbMessageSource does not produce any errors when messages file is missing.'); + } +} diff --git a/tests/framework/i18n/FallbackMessageFormatterTest.php b/tests/framework/i18n/FallbackMessageFormatterTest.php index 5e9780cabb..97765d52e8 100644 --- a/tests/framework/i18n/FallbackMessageFormatterTest.php +++ b/tests/framework/i18n/FallbackMessageFormatterTest.php @@ -19,6 +19,13 @@ class FallbackMessageFormatterTest extends TestCase { const N = 'n'; const N_VALUE = 42; + const F = 'f'; + const F_VALUE = 2e+8; + const F_VALUE_FORMATTED = "200,000,000"; + const D = 'd'; + const D_VALUE = 200000000.101; + const D_VALUE_FORMATTED = "200,000,000.101"; + const D_VALUE_FORMATTED_INTEGER = "200,000,000"; const SUBJECT = 'сабж'; const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything'; @@ -52,6 +59,38 @@ class FallbackMessageFormatterTest extends TestCase ] ], + [ + 'Here is a big number: {'.self::F.', number}', // pattern + 'Here is a big number: '.self::F_VALUE_FORMATTED, // expected + [ // params + self::F => self::F_VALUE + ] + ], + + [ + 'Here is a big number: {'.self::F.', number, integer}', // pattern + 'Here is a big number: '.self::F_VALUE_FORMATTED, // expected + [ // params + self::F => self::F_VALUE + ] + ], + + [ + 'Here is a big number: {'.self::D.', number}', // pattern + 'Here is a big number: '.self::D_VALUE_FORMATTED, // expected + [ // params + self::D => self::D_VALUE + ] + ], + + [ + 'Here is a big number: {'.self::D.', number, integer}', // pattern + 'Here is a big number: '.self::D_VALUE_FORMATTED_INTEGER, // expected + [ // params + self::D => self::D_VALUE + ] + ], + // This one was provided by Aura.Intl. Thanks! [<<<_MSG_ {gender_of_host, select, @@ -116,6 +155,13 @@ _MSG_ ], ], + // formatting a message that contains params but they are not provided. + [ + 'Incorrect password (length must be from {min, number} to {max, number} symbols).', + 'Incorrect password (length must be from {min, number} to {max, number} symbols).', + ['attribute' => 'password'], + ], + // some parser specific verifications [ '{gender} and {gender, select, female{she} male{{he}} other{it}} loves {nr} is {gender}!', @@ -168,6 +214,22 @@ _MSG_ $result = $formatter->fallbackFormat($pattern, ['begin' => 1, 'end' => 5, 'totalCount' => 10], 'en-US'); $this->assertEquals('Showing 1-5 of 10 items.', $result); } + + public function testUnsupportedPercentException() + { + $pattern = 'Number {'.self::N.', number, percent}'; + $formatter = new FallbackMessageFormatter(); + $this->setExpectedException('yii\base\NotSupportedException'); + $formatter->fallbackFormat($pattern, [self::N => self::N_VALUE], 'en-US'); + } + + public function testUnsupportedCurrencyException() + { + $pattern = 'Number {'.self::N.', number, currency}'; + $formatter = new FallbackMessageFormatter(); + $this->setExpectedException('yii\base\NotSupportedException'); + $formatter->fallbackFormat($pattern, [self::N => self::N_VALUE], 'en-US'); + } } class FallbackMessageFormatter extends MessageFormatter diff --git a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php b/tests/framework/i18n/FallbackMessageFormatterTest.php~HEAD similarity index 100% rename from tests/unit/framework/i18n/FallbackMessageFormatterTest.php rename to tests/framework/i18n/FallbackMessageFormatterTest.php~HEAD diff --git a/tests/framework/i18n/FormatterDateTest.php b/tests/framework/i18n/FormatterDateTest.php index e0ccfcdedf..30c9e7b85e 100644 --- a/tests/framework/i18n/FormatterDateTest.php +++ b/tests/framework/i18n/FormatterDateTest.php @@ -88,6 +88,43 @@ class FormatterDateTest extends TestCase $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null)); } + public function testIntlAsDateOtherCalendars() + { + // Persian calendar + $this->formatter->locale = 'fa_IR@calendar=persian'; + $this->formatter->calendar = \IntlDateFormatter::TRADITIONAL; + $this->formatter->timeZone = 'UTC'; + + $value = 1451606400; // Fri, 01 Jan 2016 00:00:00 (UTC) + $this->assertSame('۱۳۹۴', $this->formatter->asDate($value, 'php:Y')); + + $value = new DateTime(); + $value->setTimestamp(1451606400); // Fri, 01 Jan 2016 00:00:00 (UTC) + $this->assertSame('۱۳۹۴', $this->formatter->asDate($value, 'php:Y')); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $value = new \DateTimeImmutable('2016-01-01 00:00:00', new \DateTimeZone('UTC')); + $this->assertSame('۱۳۹۴', $this->formatter->asDate($value, 'php:Y')); + } + + // Buddhist calendar + $this->formatter->locale = 'fr_FR@calendar=buddhist'; + $this->formatter->calendar = \IntlDateFormatter::TRADITIONAL; + $this->formatter->timeZone = 'UTC'; + + $value = 1451606400; // Fri, 01 Jan 2016 00:00:00 (UTC) + $this->assertSame('2559', $this->formatter->asDate($value, 'php:Y')); + + $value = new DateTime(); + $value->setTimestamp(1451606400); // Fri, 01 Jan 2016 00:00:00 (UTC) + $this->assertSame('2559', $this->formatter->asDate($value, 'php:Y')); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $value = new \DateTimeImmutable('2016-01-01 00:00:00', new \DateTimeZone('UTC')); + $this->assertSame('2559', $this->formatter->asDate($value, 'php:Y')); + } + } + public function testIntlAsTime() { $this->testAsTime(); @@ -129,31 +166,31 @@ class FormatterDateTest extends TestCase // empty input $this->formatter->locale = 'de-DE'; - $this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime('')); - $this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime(0)); - $this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime(false)); + $this->assertRegExp('~01\.01\.1970,? 00:00:00~', $this->formatter->asDatetime('')); + $this->assertRegExp('~01\.01\.1970,? 00:00:00~', $this->formatter->asDatetime(0)); + $this->assertRegExp('~01\.01\.1970,? 00:00:00~', $this->formatter->asDatetime(false)); } public function testAsDatetime() { $value = time(); - $this->assertSame(date('M j, Y g:i:s A', $value), $this->formatter->asDatetime($value)); + $this->assertRegExp(date('~M j, Y,? g:i:s A~', $value), $this->formatter->asDatetime($value)); $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A')); $value = new DateTime(); - $this->assertSame(date('M j, Y g:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value)); + $this->assertRegExp(date('~M j, Y,? g:i:s A~', $value->getTimestamp()), $this->formatter->asDatetime($value)); $this->assertSame(date('Y/m/d h:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A')); if (version_compare(PHP_VERSION, '5.5.0', '>=')) { $value = new \DateTimeImmutable(); - $this->assertSame(date('M j, Y g:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value)); + $this->assertRegExp(date('~M j, Y,? g:i:s A~', $value->getTimestamp()), $this->formatter->asDatetime($value)); $this->assertSame(date('Y/m/d h:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A')); } // empty input - $this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime('')); - $this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime(0)); - $this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime(false)); + $this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime('')); + $this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime(0)); + $this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime(false)); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null)); } @@ -361,6 +398,102 @@ class FormatterDateTest extends TestCase $this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time())); } + public function testIntlAsDuration() + { + $this->testAsDuration(); + } + + public function testAsDuration() + { + $interval_0_seconds = new DateInterval("PT0S"); + $interval_1_second = new DateInterval("PT1S"); + $interval_244_seconds = new DateInterval("PT244S"); + $interval_1_minute = new DateInterval("PT1M"); + $interval_33_minutes = new DateInterval("PT33M"); + $interval_1_hour = new DateInterval("PT1H"); + $interval_6_hours = new DateInterval("PT6H"); + $interval_1_day = new DateInterval("P1D"); + $interval_89_days = new DateInterval("P89D"); + $interval_1_month = new DateInterval("P1M"); + $interval_5_months = new DateInterval("P5M"); + $interval_1_year = new DateInterval("P1Y"); + $interval_12_years = new DateInterval("P12Y"); + + // Pass a DateInterval + $this->assertSame('0 seconds', $this->formatter->asDuration($interval_0_seconds)); + $this->assertSame('1 second', $this->formatter->asDuration($interval_1_second)); + $this->assertSame('244 seconds', $this->formatter->asDuration($interval_244_seconds)); + $this->assertSame('1 minute', $this->formatter->asDuration($interval_1_minute)); + $this->assertSame('33 minutes', $this->formatter->asDuration($interval_33_minutes)); + $this->assertSame('1 hour', $this->formatter->asDuration($interval_1_hour)); + $this->assertSame('6 hours', $this->formatter->asDuration($interval_6_hours)); + $this->assertSame('1 day', $this->formatter->asDuration($interval_1_day)); + $this->assertSame('89 days', $this->formatter->asDuration($interval_89_days)); + $this->assertSame('1 month', $this->formatter->asDuration($interval_1_month)); + $this->assertSame('5 months', $this->formatter->asDuration($interval_5_months)); + $this->assertSame('1 year', $this->formatter->asDuration($interval_1_year)); + $this->assertSame('12 years', $this->formatter->asDuration($interval_12_years)); + + // Pass a numeric value + $this->assertSame('0 seconds', $this->formatter->asDuration(0)); + $this->assertSame('1 second', $this->formatter->asDuration(1)); + $this->assertSame('4 minutes, 4 seconds', $this->formatter->asDuration(244)); + $this->assertSame('1 minute', $this->formatter->asDuration(60)); + $this->assertSame('33 minutes', $this->formatter->asDuration(1980)); + $this->assertSame('1 hour', $this->formatter->asDuration(3600)); + $this->assertSame('6 hours', $this->formatter->asDuration(21600)); + $this->assertSame('1 day', $this->formatter->asDuration(86400)); + + // Pass a DateInterval string + $this->assertSame('1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('2007-03-01T13:00:00Z/2008-05-11T15:30:00Z')); + $this->assertSame('1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('2007-03-01T13:00:00Z/P1Y2M10DT2H30M')); + $this->assertSame('1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('P1Y2M10DT2H30M/2008-05-11T15:30:00Z')); + $this->assertSame('1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('P1Y2M10DT2H30M')); + $this->assertSame('-1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('P-1Y2M10DT2H30M')); + $this->assertSame('94 months', $this->formatter->asDuration('P94M')); + $this->assertSame('-94 months', $this->formatter->asDuration('P-94M')); + + // Invert all the DateIntervals + $interval_0_seconds->invert = true; + $interval_1_second->invert = true; + $interval_244_seconds->invert = true; + $interval_1_minute->invert = true; + $interval_33_minutes->invert = true; + $interval_1_hour->invert = true; + $interval_6_hours->invert = true; + $interval_1_day->invert = true; + $interval_89_days->invert = true; + $interval_1_month->invert = true; + $interval_5_months->invert = true; + $interval_1_year->invert = true; + $interval_12_years->invert = true; + + // Pass a inverted DateInterval + $this->assertSame('0 seconds', $this->formatter->asDuration($interval_0_seconds)); + $this->assertSame('-1 second', $this->formatter->asDuration($interval_1_second)); + $this->assertSame('-244 seconds', $this->formatter->asDuration($interval_244_seconds)); + $this->assertSame('-1 minute', $this->formatter->asDuration($interval_1_minute)); + $this->assertSame('-33 minutes', $this->formatter->asDuration($interval_33_minutes)); + $this->assertSame('-1 hour', $this->formatter->asDuration($interval_1_hour)); + $this->assertSame('-6 hours', $this->formatter->asDuration($interval_6_hours)); + $this->assertSame('-1 day', $this->formatter->asDuration($interval_1_day)); + $this->assertSame('-89 days', $this->formatter->asDuration($interval_89_days)); + $this->assertSame('-1 month', $this->formatter->asDuration($interval_1_month)); + $this->assertSame('-5 months', $this->formatter->asDuration($interval_5_months)); + $this->assertSame('-1 year', $this->formatter->asDuration($interval_1_year)); + $this->assertSame('-12 years', $this->formatter->asDuration($interval_12_years)); + + // other options + $this->assertSame('minus 244 seconds', $this->formatter->asDuration($interval_244_seconds, ' and ', 'minus ')); + $this->assertSame('minus 4 minutes and 4 seconds', $this->formatter->asDuration(-244, ' and ', 'minus ')); + + // Pass a inverted DateInterval string + $this->assertSame('-1 year, 2 months, 10 days, 2 hours, 30 minutes', $this->formatter->asDuration('2008-05-11T15:30:00Z/2007-03-01T13:00:00Z')); + + // null display + $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDuration(null)); + } + public function dateInputs() { return [ @@ -575,4 +708,59 @@ class FormatterDateTest extends TestCase $this->assertFalse(DateTime::createFromFormat('Y-m-d H:i:s', '2014-05-08')); } + public function testIntlInputFractionSeconds() + { + $this->testInputFractionSeconds(); + } + + public function testInputFractionSeconds() + { + $this->formatter->defaultTimeZone = 'UTC'; + + $timeStamp = '2015-04-28 10:06:15.000000'; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('2015-04-28 10:06:15+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('2015-04-28 12:06:15+0200', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + + $timeStamp = '2015-04-28 10:06:15'; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('2015-04-28 10:06:15+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('2015-04-28 12:06:15+0200', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + } + + + public function testInputUnixTimestamp() + { + $this->formatter->defaultTimeZone = 'UTC'; + $timeStamp = 1431907200; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('2015-05-18 00:00:00+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('2015-05-18 02:00:00+0200', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + + $this->formatter->defaultTimeZone = 'Europe/Berlin'; + $timeStamp = 1431907200; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('2015-05-18 00:00:00+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('2015-05-18 02:00:00+0200', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + + $this->formatter->defaultTimeZone = 'UTC'; + $timeStamp = -1431907200; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('1924-08-17 00:00:00+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('1924-08-17 01:00:00+0100', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + + $this->formatter->defaultTimeZone = 'Europe/Berlin'; + $timeStamp = -1431907200; + $this->formatter->timeZone = 'UTC'; + $this->assertEquals('1924-08-17 00:00:00+0000', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + $this->formatter->timeZone = 'Europe/Berlin'; + $this->assertEquals('1924-08-17 01:00:00+0100', $this->formatter->asDateTime($timeStamp, 'yyyy-MM-dd HH:mm:ssZ')); + + } + } diff --git a/tests/unit/framework/i18n/FormatterDateTest.php b/tests/framework/i18n/FormatterDateTest.php~HEAD similarity index 99% rename from tests/unit/framework/i18n/FormatterDateTest.php rename to tests/framework/i18n/FormatterDateTest.php~HEAD index 410ee3daf2..e0ccfcdedf 100644 --- a/tests/unit/framework/i18n/FormatterDateTest.php +++ b/tests/framework/i18n/FormatterDateTest.php~HEAD @@ -398,6 +398,9 @@ class FormatterDateTest extends TestCase ['UTC'], ['Europe/Berlin'], ['America/Jamaica'], + // these two are near the International Date Line on different sides + ['Pacific/Kiritimati'], + ['Pacific/Honolulu'], ]; } diff --git a/tests/framework/i18n/FormatterNumberTest.php b/tests/framework/i18n/FormatterNumberTest.php index af24243c97..e0399b1c80 100644 --- a/tests/framework/i18n/FormatterNumberTest.php +++ b/tests/framework/i18n/FormatterNumberTest.php @@ -511,6 +511,35 @@ class FormatterNumberTest extends TestCase $this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null)); } + public function testIntlAsSizeNegative() + { + $this->formatter->numberFormatterOptions = [ + \NumberFormatter::MIN_FRACTION_DIGITS => 0, + \NumberFormatter::MAX_FRACTION_DIGITS => 2, + ]; + + // tests for base 1000 + $this->formatter->sizeFormatBase = 1000; + $this->assertSame("-999 bytes", $this->formatter->asSize(-999)); + $this->assertSame("-999 bytes", $this->formatter->asSize('-999')); + $this->assertSame("-1.05 megabytes", $this->formatter->asSize(-1024 * 1024)); + $this->assertSame("-1 kilobyte", $this->formatter->asSize(-1000)); + $this->assertSame("-1.02 kilobytes", $this->formatter->asSize(-1023)); + $this->assertSame("-3 gigabytes", $this->formatter->asSize(-3 * 1000 * 1000 * 1000)); + $this->assertNotEquals("3 PB", $this->formatter->asSize(-3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB + // tests for base 1024 + $this->formatter->sizeFormatBase = 1024; + $this->assertSame("-1 kibibyte", $this->formatter->asSize(-1024)); + $this->assertSame("-1 mebibyte", $this->formatter->asSize(-1024 * 1024)); + $this->assertSame("-1023 bytes", $this->formatter->asSize(-1023)); + $this->assertSame("-5 gibibytes", $this->formatter->asSize(-5 * 1024 * 1024 * 1024)); + $this->assertNotEquals("-5 pibibytes", $this->formatter->asSize(-5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB + $this->assertSame("-2 gibibytes", $this->formatter->asSize(-2147483647)); // round 1.999 up to 2 + $this->formatter->decimalSeparator = ','; + $this->formatter->numberFormatterOptions = []; + $this->assertSame("-1,001 kibibytes", $this->formatter->asSize(-1025, 3)); + } + public function testAsSize() { // tests for base 1000 @@ -539,6 +568,31 @@ class FormatterNumberTest extends TestCase $this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null)); } + public function testAsSizeNegative() + { + // tests for base 1000 + $this->formatter->sizeFormatBase = 1000; + $this->assertSame("-999 bytes", $this->formatter->asSize(-999)); + $this->assertSame("-999 bytes", $this->formatter->asSize('-999')); + $this->assertSame("-1.05 megabytes", $this->formatter->asSize(-1024 * 1024)); + $this->assertSame("-1.0486 megabytes", $this->formatter->asSize(-1024 * 1024, 4)); + $this->assertSame("-1.00 kilobyte", $this->formatter->asSize(-1000)); + $this->assertSame("-1.02 kilobytes", $this->formatter->asSize(-1023)); + $this->assertSame("-3.00 gigabytes", $this->formatter->asSize(-3 * 1000 * 1000 * 1000)); + $this->assertNotEquals("3 PB", $this->formatter->asSize(-3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB + // tests for base 1024 + $this->formatter->sizeFormatBase = 1024; + $this->assertSame("-1.00 kibibyte", $this->formatter->asSize(-1024)); + $this->assertSame("-1.00 mebibyte", $this->formatter->asSize(-1024 * 1024)); + $this->assertSame("-1023 bytes", $this->formatter->asSize(-1023)); + $this->assertSame("-5.00 gibibytes", $this->formatter->asSize(-5 * 1024 * 1024 * 1024)); + $this->assertNotEquals("-5.00 pibibytes", $this->formatter->asSize(-5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB + $this->assertSame("-2.00 gibibytes", $this->formatter->asSize(-2147483647)); // round 1.999 up to 2 + $this->formatter->decimalSeparator = ','; + $this->formatter->numberFormatterOptions = []; + $this->assertSame("-1,001 kibibytes", $this->formatter->asSize(-1025, 3)); + } + public function testIntlAsSizeConfiguration() { $this->assertSame("1023 bytes", $this->formatter->asSize(1023)); diff --git a/tests/unit/framework/i18n/FormatterNumberTest.php b/tests/framework/i18n/FormatterNumberTest.php~HEAD similarity index 83% rename from tests/unit/framework/i18n/FormatterNumberTest.php rename to tests/framework/i18n/FormatterNumberTest.php~HEAD index e07a392cc2..af24243c97 100644 --- a/tests/unit/framework/i18n/FormatterNumberTest.php +++ b/tests/framework/i18n/FormatterNumberTest.php~HEAD @@ -155,8 +155,8 @@ class FormatterNumberTest extends TestCase $this->assertSame("-123,456.123", $this->formatter->asDecimal($value)); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->assertSame("0", $this->formatter->asDecimal(false)); + $this->assertSame("0", $this->formatter->asDecimal("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null)); @@ -196,8 +196,8 @@ class FormatterNumberTest extends TestCase $this->assertSame("-123,456.123", $this->formatter->asDecimal($value, 3)); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->assertSame("0.00", $this->formatter->asDecimal(false)); + $this->assertSame("0.00", $this->formatter->asDecimal("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null)); @@ -218,8 +218,8 @@ class FormatterNumberTest extends TestCase $this->assertSame("-1%", $this->formatter->asPercent('-0.009343')); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->assertSame("0%", $this->formatter->asPercent(false)); + $this->assertSame("0%", $this->formatter->asPercent("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null)); @@ -250,9 +250,48 @@ class FormatterNumberTest extends TestCase $this->formatter->currencyCode = 'EUR'; $this->assertSame('123,00 €', $this->formatter->asCurrency('123')); + $this->formatter->locale = 'de-DE'; + $this->formatter->currencyCode = null; + $this->assertSame('123,00 €', $this->formatter->asCurrency('123', 'EUR')); + $this->assertSame('123,00 $', $this->formatter->asCurrency('123', 'USD')); + $this->formatter->currencyCode = 'USD'; + $this->assertSame('123,00 €', $this->formatter->asCurrency('123', 'EUR')); + $this->assertSame('123,00 $', $this->formatter->asCurrency('123', 'USD')); + $this->formatter->currencyCode = 'EUR'; + $this->assertSame('123,00 €', $this->formatter->asCurrency('123', 'EUR')); + $this->assertSame('123,00 $', $this->formatter->asCurrency('123', 'USD')); + + // default russian currency symbol + $this->formatter->locale = 'ru-RU'; + $this->formatter->currencyCode = null; + $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); + $this->formatter->currencyCode = 'RUB'; + $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); + + // custom currency symbol + $this->formatter->currencyCode = null; + $this->formatter->numberFormatterSymbols = [ + NumberFormatter::CURRENCY_SYMBOL => '₽', + ]; + $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); + $this->formatter->numberFormatterSymbols = [ + NumberFormatter::CURRENCY_SYMBOL => '₽', + ]; + $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); + // setting the currency code overrides the symbol + $this->formatter->currencyCode = 'RUB'; + $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); + $this->formatter->numberFormatterSymbols = [NumberFormatter::CURRENCY_SYMBOL => '₽']; + $this->assertSame('123,00 $', $this->formatter->asCurrency('123', 'USD')); + $this->formatter->numberFormatterSymbols = [NumberFormatter::CURRENCY_SYMBOL => '₽']; + $this->assertSame('123,00 €', $this->formatter->asCurrency('123', 'EUR')); + // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->formatter->locale = 'de-DE'; + $this->formatter->currencyCode = null; + $this->formatter->numberFormatterSymbols = []; + $this->assertSame("0,00 €", $this->formatter->asCurrency(false)); + $this->assertSame("0,00 €", $this->formatter->asCurrency("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null)); @@ -288,8 +327,10 @@ class FormatterNumberTest extends TestCase $this->assertSame('EUR -123.45', $this->formatter->asCurrency(-123.45)); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->formatter->currencyCode = 'USD'; + $this->formatter->numberFormatterSymbols = []; + $this->assertSame("USD 0.00", $this->formatter->asCurrency(false)); + $this->assertSame("USD 0.00", $this->formatter->asCurrency("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null)); @@ -305,8 +346,8 @@ class FormatterNumberTest extends TestCase $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value)); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->assertSame("0E0", $this->formatter->asScientific(false)); + $this->assertSame("0E0", $this->formatter->asScientific("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null)); @@ -322,8 +363,8 @@ class FormatterNumberTest extends TestCase $this->assertSame("-1.234561E+5", $this->formatter->asScientific($value)); // empty input - $this->assertSame("0", $this->formatter->asInteger(false)); - $this->assertSame("0", $this->formatter->asInteger("")); + $this->assertSame("0.000000E+0", $this->formatter->asScientific(false)); + $this->assertSame("0.000000E+0", $this->formatter->asScientific("")); // null display $this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null)); @@ -374,6 +415,12 @@ class FormatterNumberTest extends TestCase $this->assertSame("1 KB", $this->formatter->asShortSize(1000)); $this->assertSame("1.02 KB", $this->formatter->asShortSize(1023)); $this->assertNotEquals("3 PB", $this->formatter->asShortSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB + // string values + $this->assertSame("28.41 GB", $this->formatter->asShortSize(28406984038)); + $this->assertSame("28.41 GB", $this->formatter->asShortSize((string)28406984038)); + $this->assertSame("56.81 GB", $this->formatter->asShortSize(28406984038 + 28406984038)); + $this->assertSame("56.81 GB", $this->formatter->asShortSize((string)(28406984038 + 28406984038))); + // tests for base 1024 $this->formatter->sizeFormatBase = 1024; $this->assertSame("1 KiB", $this->formatter->asShortSize(1024)); @@ -406,6 +453,12 @@ class FormatterNumberTest extends TestCase $this->assertSame("1.00 KB", $this->formatter->asShortSize(1000)); $this->assertSame("1.02 KB", $this->formatter->asShortSize(1023)); $this->assertNotEquals("3 PB", $this->formatter->asShortSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB + // string values + $this->assertSame("28.41 GB", $this->formatter->asShortSize(28406984038)); + $this->assertSame("28.41 GB", $this->formatter->asShortSize((string)28406984038)); + $this->assertSame("56.81 GB", $this->formatter->asShortSize(28406984038 + 28406984038)); + $this->assertSame("56.81 GB", $this->formatter->asShortSize((string)(28406984038 + 28406984038))); + // tests for base 1024 $this->formatter->sizeFormatBase = 1024; $this->assertSame("1.00 KiB", $this->formatter->asShortSize(1024)); diff --git a/tests/framework/i18n/I18NTest.php b/tests/framework/i18n/I18NTest.php index f6c4d2c9d3..5b558e5de9 100644 --- a/tests/framework/i18n/I18NTest.php +++ b/tests/framework/i18n/I18NTest.php @@ -7,6 +7,7 @@ namespace yiiunit\framework\i18n; +use Yii; use yii\base\Event; use yii\i18n\I18N; use yii\i18n\PhpMessageSource; @@ -28,21 +29,32 @@ class I18NTest extends TestCase { parent::setUp(); $this->mockApplication(); + $this->setI18N(); + } + + protected function setI18N() + { $this->i18n = new I18N([ 'translations' => [ - 'test' => new PhpMessageSource([ + 'test' => [ + 'class' => $this->getMessageSourceClass(), 'basePath' => '@yiiunit/data/i18n/messages', - ]) + ] ] ]); } + private function getMessageSourceClass() + { + return PhpMessageSource::className(); + } + public function testTranslate() { $msg = 'The dog runs fast.'; // source = target. Should be returned as is. - $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, [], 'en')); + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, [], 'en-US')); // exact match $this->assertEquals('Der Hund rennt schnell.', $this->i18n->translate('test', $msg, [], 'de-DE')); @@ -58,20 +70,21 @@ class I18NTest extends TestCase { $i18n = new I18N([ 'translations' => [ - '*' => new PhpMessageSource([ - 'basePath' => '@yiiunit/data/i18n/messages', + '*' => [ + 'class' => $this->getMessageSourceClass(), + 'basePath' => '@yiiunit/data/i18n/messages', 'fileMap' => [ 'test' => 'test.php', 'foo' => 'test.php', ], - ]) + ] ] ]); $msg = 'The dog runs fast.'; // source = target. Should be returned as is. - $this->assertEquals($msg, $i18n->translate('test', $msg, [], 'en')); + $this->assertEquals($msg, $i18n->translate('test', $msg, [], 'en-US')); // exact match $this->assertEquals('Der Hund rennt schnell.', $i18n->translate('test', $msg, [], 'de-DE')); @@ -85,6 +98,39 @@ class I18NTest extends TestCase $this->assertEquals('Hallo Welt!', $i18n->translate('test', 'Hello world!', [], 'de-DE')); } + /** + * https://github.com/yiisoft/yii2/issues/7964 + */ + public function testSourceLanguageFallback() + { + $i18n = new I18N([ + 'translations' => [ + '*' => new PhpMessageSource([ + 'basePath' => '@yiiunit/data/i18n/messages', + 'sourceLanguage' => 'de-DE', + 'fileMap' => [ + 'test' => 'test.php', + 'foo' => 'test.php', + ], + ] + ) + ] + ]); + + $msg = 'The dog runs fast.'; + + // source = target. Should be returned as is. + $this->assertEquals($msg, $i18n->translate('test', $msg, [], 'de-DE')); + + // target is less specific, than a source. Messages from sourceLanguage file should be loaded as a fallback + $this->assertEquals('Der Hund rennt schnell.', $i18n->translate('test', $msg, [], 'de')); + $this->assertEquals('Hallo Welt!', $i18n->translate('test', 'Hello world!', [], 'de')); + + // target is a different language than source + $this->assertEquals('Собака бегает быстро.', $i18n->translate('test', $msg, [], 'ru-RU')); + $this->assertEquals('Собака бегает быстро.', $i18n->translate('test', $msg, [], 'ru')); + } + public function testTranslateParams() { $msg = 'His speed is about {n} km/h.'; @@ -136,15 +182,15 @@ class I18NTest extends TestCase public function testUsingSourceLanguageForMissingTranslation() { - \Yii::$app->sourceLanguage = 'ru'; - \Yii::$app->language = 'en'; + Yii::$app->sourceLanguage = 'ru'; + Yii::$app->language = 'en'; $msg = '{n, plural, =0{Нет комментариев} =1{# комментарий} one{# комментарий} few{# комментария} many{# комментариев} other{# комментария}}'; - $this->assertEquals('5 комментариев', \Yii::t('app', $msg, ['n' => 5])); - $this->assertEquals('3 комментария', \Yii::t('app', $msg, ['n' => 3])); - $this->assertEquals('1 комментарий', \Yii::t('app', $msg, ['n' => 1])); - $this->assertEquals('21 комментарий', \Yii::t('app', $msg, ['n' => 21])); - $this->assertEquals('Нет комментариев', \Yii::t('app', $msg, ['n' => 0])); + $this->assertEquals('5 комментариев', Yii::t('app', $msg, ['n' => 5])); + $this->assertEquals('3 комментария', Yii::t('app', $msg, ['n' => 3])); + $this->assertEquals('1 комментарий', Yii::t('app', $msg, ['n' => 1])); + $this->assertEquals('21 комментарий', Yii::t('app', $msg, ['n' => 21])); + $this->assertEquals('Нет комментариев', Yii::t('app', $msg, ['n' => 0])); } /** @@ -174,4 +220,58 @@ class I18NTest extends TestCase $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); Event::off(PhpMessageSource::className(), PhpMessageSource::EVENT_MISSING_TRANSLATION); } + + public function sourceLanguageDataProvider() + { + return [ + ['en-GB'], + ['en'] + ]; + } + + /** + * @dataProvider sourceLanguageDataProvider + * @param $sourceLanguage + */ + public function testIssue11429($sourceLanguage) + { + $this->mockApplication(); + $this->setI18N(); + + Yii::$app->sourceLanguage = $sourceLanguage; + $logger = Yii::getLogger(); + $logger->messages = []; + $filter = function ($array) { + // Ensures that error message is related to PhpMessageSource + $className = $this->getMessageSourceClass(); + return substr_compare($array[2], $className, 0, strlen($className)) === 0; + }; + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en-GB')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en-CA')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'hz-HZ')); + $this->assertCount(1, array_filter($logger->messages, $filter)); + $logger->messages = []; + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'hz')); + $this->assertCount(1, array_filter($logger->messages, $filter)); + $logger->messages = []; + } + + /** + * Formatting a message that contains params but they are not provided. + * https://github.com/yiisoft/yii2/issues/10884 + */ + public function testFormatMessageWithNoParam() + { + $message = 'Incorrect password (length must be from {min, number} to {max, number} symbols).'; + $this->assertEquals($message, $this->i18n->format($message, ['attribute' => 'password'], 'en')); + } } diff --git a/tests/unit/framework/i18n/I18NTest.php b/tests/framework/i18n/I18NTest.php~HEAD similarity index 100% rename from tests/unit/framework/i18n/I18NTest.php rename to tests/framework/i18n/I18NTest.php~HEAD diff --git a/tests/framework/i18n/MessageFormatterTest.php b/tests/framework/i18n/MessageFormatterTest.php index 85f0c52205..e2b684a7ae 100644 --- a/tests/framework/i18n/MessageFormatterTest.php +++ b/tests/framework/i18n/MessageFormatterTest.php @@ -19,6 +19,13 @@ class MessageFormatterTest extends TestCase { const N = 'n'; const N_VALUE = 42; + const F = 'f'; + const F_VALUE = 2e+8; + const F_VALUE_FORMATTED = "200,000,000"; + const D = 'd'; + const D_VALUE = 200000000.101; + const D_VALUE_FORMATTED = "200,000,000.101"; + const D_VALUE_FORMATTED_INTEGER = "200,000,000"; const SUBJECT = 'сабж'; const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything'; @@ -43,6 +50,39 @@ class MessageFormatterTest extends TestCase ] ], + [ + 'Here is a big number: {'.self::F.', number}', // pattern + 'Here is a big number: '.self::F_VALUE_FORMATTED, // expected + [ // params + self::F => self::F_VALUE + ] + ], + + + [ + 'Here is a big number: {'.self::F.', number, integer}', // pattern + 'Here is a big number: '.self::F_VALUE_FORMATTED, // expected + [ // params + self::F => self::F_VALUE + ] + ], + + [ + 'Here is a big number: {'.self::D.', number}', // pattern + 'Here is a big number: '.self::D_VALUE_FORMATTED, // expected + [ // params + self::D => self::D_VALUE + ] + ], + + [ + 'Here is a big number: {'.self::D.', number, integer}', // pattern + 'Here is a big number: '.self::D_VALUE_FORMATTED_INTEGER, // expected + [ // params + self::D => self::D_VALUE + ] + ], + // This one was provided by Aura.Intl. Thanks! [<<<_MSG_ {gender_of_host, select, @@ -129,6 +169,13 @@ _MSG_ 'select format is available in ICU > 4.4' ], + // formatting a message that contains params but they are not provided. + [ + 'Incorrect password (length must be from {min, number} to {max, number} symbols).', + 'Incorrect password (length must be from {min, number} to {max, number} symbols).', + ['attribute' => 'password'], + ], + // test ICU version compatibility [ 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.', diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/framework/i18n/MessageFormatterTest.php~HEAD similarity index 100% rename from tests/unit/framework/i18n/MessageFormatterTest.php rename to tests/framework/i18n/MessageFormatterTest.php~HEAD diff --git a/tests/framework/log/DispatcherTest.php b/tests/framework/log/DispatcherTest.php new file mode 100644 index 0000000000..a16ede9e0f --- /dev/null +++ b/tests/framework/log/DispatcherTest.php @@ -0,0 +1,71 @@ + + */ + +namespace yiiunit\framework\log; + +use yii\log\Dispatcher; +use yii\log\Logger; +use Yii; +use yiiunit\TestCase; + +/** + * @group log + */ +class DispatcherTest extends TestCase +{ + + public function testConfigureLogger() + { + $dispatcher = new Dispatcher(); + $this->assertSame(Yii::getLogger(), $dispatcher->getLogger()); + + + $logger = new Logger(); + $dispatcher = new Dispatcher([ + 'logger' => $logger, + ]); + $this->assertSame($logger, $dispatcher->getLogger()); + + + $dispatcher = new Dispatcher([ + 'logger' => 'yii\log\Logger', + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(0, $dispatcher->getLogger()->traceLevel); + + + $dispatcher = new Dispatcher([ + 'logger' => [ + 'class' => 'yii\log\Logger', + 'traceLevel' => 42, + ], + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(42, $dispatcher->getLogger()->traceLevel); + } + + public function testSetLogger() + { + $dispatcher = new Dispatcher(); + $this->assertSame(Yii::getLogger(), $dispatcher->getLogger()); + + $logger = new Logger(); + $dispatcher->setLogger($logger); + $this->assertSame($logger, $dispatcher->getLogger()); + + $dispatcher->setLogger('yii\log\Logger'); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(0, $dispatcher->getLogger()->traceLevel); + + + $dispatcher->setLogger([ + 'class' => 'yii\log\Logger', + 'traceLevel' => 42, + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(42, $dispatcher->getLogger()->traceLevel); + } + +} diff --git a/tests/framework/mutex/FileMutexTest.php b/tests/framework/mutex/FileMutexTest.php new file mode 100644 index 0000000000..dd4d982431 --- /dev/null +++ b/tests/framework/mutex/FileMutexTest.php @@ -0,0 +1,37 @@ +markTestSkipped('FileMutex does not have MS Windows operating system support.'); + } + } + + /** + * @return FileMutex + * @throws \yii\base\InvalidConfigException + */ + protected function createMutex() + { + return \Yii::createObject([ + 'class' => FileMutex::className(), + ]); + } + +} diff --git a/tests/framework/mutex/MutexTestTrait.php b/tests/framework/mutex/MutexTestTrait.php new file mode 100644 index 0000000000..2c1df6c49d --- /dev/null +++ b/tests/framework/mutex/MutexTestTrait.php @@ -0,0 +1,39 @@ +createMutex(); + + $this->assertTrue($mutex->acquire(self::$mutexName)); + } + + public function testThatMutexLockIsWorking() + { + $mutexOne = $this->createMutex(); + $mutexTwo = $this->createMutex(); + + $this->assertTrue($mutexOne->acquire(self::$mutexName)); + $this->assertFalse($mutexTwo->acquire(self::$mutexName)); + + $mutexOne->release(self::$mutexName); + + $this->assertTrue($mutexTwo->acquire(self::$mutexName)); + } +} diff --git a/tests/framework/mutex/MysqlMutexTest.php b/tests/framework/mutex/MysqlMutexTest.php new file mode 100644 index 0000000000..a339064321 --- /dev/null +++ b/tests/framework/mutex/MysqlMutexTest.php @@ -0,0 +1,34 @@ + MysqlMutex::className(), + 'db' => $this->getConnection(), + ]); + } + +} diff --git a/tests/framework/mutex/PgsqlMutexTest.php b/tests/framework/mutex/PgsqlMutexTest.php new file mode 100644 index 0000000000..3eef4057f2 --- /dev/null +++ b/tests/framework/mutex/PgsqlMutexTest.php @@ -0,0 +1,34 @@ + PgsqlMutex::className(), + 'db' => $this->getConnection(), + ]); + } + +} diff --git a/tests/framework/rbac/ActionRule.php b/tests/framework/rbac/ActionRule.php new file mode 100644 index 0000000000..2db99f4b2e --- /dev/null +++ b/tests/framework/rbac/ActionRule.php @@ -0,0 +1,16 @@ +action === 'all' || $this->action === $params['action']; + } +} diff --git a/tests/framework/rbac/ManagerTestCase.php b/tests/framework/rbac/ManagerTestCase.php index 06ed689daf..1c38baebe7 100644 --- a/tests/framework/rbac/ManagerTestCase.php +++ b/tests/framework/rbac/ManagerTestCase.php @@ -4,7 +4,6 @@ namespace yiiunit\framework\rbac; use yii\rbac\Item; use yii\rbac\Permission; -use yii\rbac\PhpManager; use yii\rbac\Role; use yiiunit\TestCase; @@ -113,6 +112,16 @@ abstract class ManagerTestCase extends TestCase $rule = $this->auth->getRule('newName'); $this->assertEquals(true, $rule->reallyReally); + + $item = $this->auth->getPermission('createPost'); + $item->name = 'new createPost'; + $this->auth->update('createPost', $item); + + $item = $this->auth->getPermission('createPost'); + $this->assertEquals(null, $item); + + $item = $this->auth->getPermission('new createPost'); + $this->assertEquals('new createPost', $item->name); } public function testGetRules() @@ -141,6 +150,10 @@ abstract class ManagerTestCase extends TestCase $rules = $this->auth->getRules(); $this->assertEmpty($rules); + + $this->auth->remove($this->auth->getPermission('createPost')); + $item = $this->auth->getPermission('createPost'); + $this->assertNull($item); } public function testCheckAccess() @@ -158,6 +171,7 @@ abstract class ManagerTestCase extends TestCase 'createPost' => true, 'readPost' => true, 'updatePost' => true, + 'deletePost' => true, 'updateAnyPost' => false, ], 'admin C' => [ @@ -165,6 +179,8 @@ abstract class ManagerTestCase extends TestCase 'readPost' => true, 'updatePost' => false, 'updateAnyPost' => true, + 'blablabla' => false, + null => false, ], ]; @@ -194,6 +210,10 @@ abstract class ManagerTestCase extends TestCase $readPost->description = 'read a post'; $this->auth->add($readPost); + $deletePost = $this->auth->createPermission('deletePost'); + $deletePost->description = 'delete a post'; + $this->auth->add($deletePost); + $updatePost = $this->auth->createPermission('updatePost'); $updatePost->description = 'update a post'; $updatePost->ruleName = $rule->name; @@ -222,37 +242,50 @@ abstract class ManagerTestCase extends TestCase $this->auth->assign($reader, 'reader A'); $this->auth->assign($author, 'author B'); + $this->auth->assign($deletePost, 'author B'); $this->auth->assign($admin, 'admin C'); } public function testGetPermissionsByRole() { $this->prepareData(); - $roles = $this->auth->getPermissionsByRole('admin'); + $permissions = $this->auth->getPermissionsByRole('admin'); $expectedPermissions = ['createPost', 'updatePost', 'readPost', 'updateAnyPost']; - $this->assertEquals(count($roles), count($expectedPermissions)); - foreach ($expectedPermissions as $permission) { - $this->assertTrue($roles[$permission] instanceof Permission); + $this->assertEquals(count($expectedPermissions), count($permissions)); + foreach ($expectedPermissions as $permissionName) { + $this->assertTrue($permissions[$permissionName] instanceof Permission); } } public function testGetPermissionsByUser() { $this->prepareData(); - $roles = $this->auth->getPermissionsByUser('author B'); - $expectedPermissions = ['createPost', 'updatePost', 'readPost']; - $this->assertEquals(count($roles), count($expectedPermissions)); - foreach ($expectedPermissions as $permission) { - $this->assertTrue($roles[$permission] instanceof Permission); + $permissions = $this->auth->getPermissionsByUser('author B'); + $expectedPermissions = ['deletePost', 'createPost', 'updatePost', 'readPost']; + $this->assertEquals(count($expectedPermissions), count($permissions)); + foreach ($expectedPermissions as $permissionName) { + $this->assertTrue($permissions[$permissionName] instanceof Permission); } } public function testGetRolesByUser() { $this->prepareData(); + $reader = $this->auth->getRole('reader'); + $this->auth->assign($reader, 0); + $this->auth->assign($reader, 123); + $roles = $this->auth->getRolesByUser('reader A'); $this->assertTrue(reset($roles) instanceof Role); $this->assertEquals($roles['reader']->name, 'reader'); + + $roles = $this->auth->getRolesByUser(0); + $this->assertTrue(reset($roles) instanceof Role); + $this->assertEquals($roles['reader']->name, 'reader'); + + $roles = $this->auth->getRolesByUser(123); + $this->assertTrue(reset($roles) instanceof Role); + $this->assertEquals($roles['reader']->name, 'reader'); } public function testAssignMultipleRoles() @@ -292,4 +325,130 @@ abstract class ManagerTestCase extends TestCase $this->assertEquals(1, count($this->auth->getAssignments(42))); $this->assertEquals(2, count($this->auth->getAssignments(1337))); } + + public function testGetAssignmentsByRole() + { + $this->prepareData(); + $reader = $this->auth->getRole('reader'); + $this->auth->assign($reader, 123); + + $this->auth = $this->createManager(); + + $this->assertEquals([], $this->auth->getUserIdsByRole('nonexisting')); + $this->assertEquals(['reader A', '123'], $this->auth->getUserIdsByRole('reader'), '', 0.0, 10, true); + $this->assertEquals(['author B'], $this->auth->getUserIdsByRole('author')); + $this->assertEquals(['admin C'], $this->auth->getUserIdsByRole('admin')); + } + + public function testCanAddChild() + { + $this->prepareData(); + + $author = $this->auth->createRole('author'); + $reader = $this->auth->createRole('reader'); + + $this->assertTrue($this->auth->canAddChild($author, $reader)); + $this->assertFalse($this->auth->canAddChild($reader, $author)); + } + + + public function testRemoveAllRules() + { + $this->prepareData(); + + $this->auth->removeAllRules(); + + $this->assertEmpty($this->auth->getRules()); + + $this->assertNotEmpty($this->auth->getRoles()); + $this->assertNotEmpty($this->auth->getPermissions()); + } + + public function testRemoveAllRoles() + { + $this->prepareData(); + + $this->auth->removeAllRoles(); + + $this->assertEmpty($this->auth->getRoles()); + + $this->assertNotEmpty($this->auth->getRules()); + $this->assertNotEmpty($this->auth->getPermissions()); + } + + public function testRemoveAllPermissions() + { + $this->prepareData(); + + $this->auth->removeAllPermissions(); + + $this->assertEmpty($this->auth->getPermissions()); + + $this->assertNotEmpty($this->auth->getRules()); + $this->assertNotEmpty($this->auth->getRoles()); + } + + public function testAssignRule() + { + $auth = $this->auth; + $userId = 3; + + $auth->removeAll(); + $role = $auth->createRole('Admin'); + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Admin')); + + // with normal register rule + $auth->removeAll(); + $rule = new ActionRule(); + $auth->add($rule); + $role = $auth->createRole('Reader'); + $role->ruleName = $rule->name; + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Reader', ['action' => 'read'])); + $this->assertFalse($auth->checkAccess($userId, 'Reader', ['action' => 'write'])); + + // using rule class name + $auth->removeAll(); + $role = $auth->createRole('Reader'); + $role->ruleName = 'yiiunit\framework\rbac\ActionRule'; + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Reader', ['action' => 'read'])); + $this->assertFalse($auth->checkAccess($userId, 'Reader', ['action' => 'write'])); + + // using DI + \Yii::$container->set('write_rule', ['class' => 'yiiunit\framework\rbac\ActionRule', 'action' => 'write']); + \Yii::$container->set('delete_rule', ['class' => 'yiiunit\framework\rbac\ActionRule', 'action' => 'delete']); + \Yii::$container->set('all_rule', ['class' => 'yiiunit\framework\rbac\ActionRule', 'action' => 'all']); + + $role = $auth->createRole('Writer'); + $role->ruleName = 'write_rule'; + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Writer', ['action' => 'write'])); + $this->assertFalse($auth->checkAccess($userId, 'Writer', ['action' => 'update'])); + + $role = $auth->createRole('Deleter'); + $role->ruleName = 'delete_rule'; + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Deleter', ['action' => 'delete'])); + $this->assertFalse($auth->checkAccess($userId, 'Deleter', ['action' => 'update'])); + + $role = $auth->createRole('Author'); + $role->ruleName = 'all_rule'; + $auth->add($role); + $auth->assign($role, $userId); + $this->assertTrue($auth->checkAccess($userId, 'Author', ['action' => 'update'])); + + // update role and rule + $role = $auth->getRole('Reader'); + $role->name = 'AdminPost'; + $role->ruleName = 'all_rule'; + $auth->update('Reader', $role); + $this->assertTrue($auth->checkAccess($userId, 'AdminPost', ['action' => 'print'])); + } } diff --git a/tests/unit/framework/rbac/ManagerTestCase.php b/tests/framework/rbac/ManagerTestCase.php~HEAD similarity index 96% rename from tests/unit/framework/rbac/ManagerTestCase.php rename to tests/framework/rbac/ManagerTestCase.php~HEAD index 0acc79194b..06ed689daf 100644 --- a/tests/unit/framework/rbac/ManagerTestCase.php +++ b/tests/framework/rbac/ManagerTestCase.php~HEAD @@ -182,6 +182,10 @@ abstract class ManagerTestCase extends TestCase $rule = new AuthorRule; $this->auth->add($rule); + $uniqueTrait = $this->auth->createPermission('Fast Metabolism'); + $uniqueTrait->description = 'Your metabolic rate is twice normal. This means that you are much less resistant to radiation and poison, but your body heals faster.'; + $this->auth->add($uniqueTrait); + $createPost = $this->auth->createPermission('createPost'); $createPost->description = 'create a post'; $this->auth->add($createPost); @@ -214,6 +218,8 @@ abstract class ManagerTestCase extends TestCase $this->auth->addChild($admin, $author); $this->auth->addChild($admin, $updateAnyPost); + $this->auth->assign($uniqueTrait, 'reader A'); + $this->auth->assign($reader, 'reader A'); $this->auth->assign($author, 'author B'); $this->auth->assign($admin, 'admin C'); diff --git a/tests/framework/rbac/PhpManagerTest.php b/tests/framework/rbac/PhpManagerTest.php index d2ece8f8c2..fe198e8692 100644 --- a/tests/framework/rbac/PhpManagerTest.php +++ b/tests/framework/rbac/PhpManagerTest.php @@ -139,4 +139,19 @@ class PhpManagerTest extends ManagerTestCase $permission->name = 'createPost'; $this->auth->update($name, $permission); } + + public function testSaveAssignments() + { + $this->auth->removeAll(); + $role = $this->auth->createRole('Admin'); + $this->auth->add($role); + $this->auth->assign($role, 13); + $this->assertContains('Admin', file_get_contents($this->getAssignmentFile())); + $role->name = 'NewAdmin'; + $this->auth->update('Admin', $role); + $this->assertContains('NewAdmin', file_get_contents($this->getAssignmentFile())); + $this->auth->remove($role); + $this->assertNotContains('NewAdmin', file_get_contents($this->getAssignmentFile())); + + } } diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/framework/rbac/PhpManagerTest.php~HEAD similarity index 72% rename from tests/unit/framework/rbac/PhpManagerTest.php rename to tests/framework/rbac/PhpManagerTest.php~HEAD index 7e2214c2e4..d2ece8f8c2 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/framework/rbac/PhpManagerTest.php~HEAD @@ -109,4 +109,34 @@ class PhpManagerTest extends ManagerTestCase $this->assertEquals($assignments, $this->auth->assignments); $this->assertEquals($rules, $this->auth->rules); } + + public function testUpdateItemName() + { + $this->prepareData(); + + $name = 'readPost'; + $permission = $this->auth->getPermission($name); + $permission->name = 'UPDATED-NAME'; + $this->assertTrue($this->auth->update($name, $permission), 'You should be able to update name.'); + } + + public function testUpdateDescription() { + $this->prepareData(); + $name = 'readPost'; + $permission = $this->auth->getPermission($name); + $permission->description = 'UPDATED-DESCRIPTION'; + $this->assertTrue($this->auth->update($name, $permission), 'You should be able to save w/o changing name.'); + } + + /** + * @expectedException \yii\base\InvalidParamException + */ + public function testOverwriteName() + { + $this->prepareData(); + $name = 'readPost'; + $permission = $this->auth->getPermission($name); + $permission->name = 'createPost'; + $this->auth->update($name, $permission); + } } diff --git a/tests/framework/validators/BooleanValidatorTest.php b/tests/framework/validators/BooleanValidatorTest.php index d8441bce57..c3f013017a 100644 --- a/tests/framework/validators/BooleanValidatorTest.php +++ b/tests/framework/validators/BooleanValidatorTest.php @@ -1,6 +1,7 @@ validateAttribute($obj, 'attrD'); $this->assertTrue($obj->hasErrors('attrD')); } + + public function testErrorMessage() + { + $validator = new BooleanValidator([ + 'trueValue' => true, + 'falseValue' => false, + 'strict' => true, + ]); + $validator->validate('someIncorrectValue', $errorMessage); + + $this->assertEquals('the input value must be either "true" or "false".', $errorMessage); + + $obj = new FakedValidationModel; + $obj->attrA = true; + $obj->attrB = '1'; + $obj->attrC = '0'; + $obj->attrD = []; + + $this->assertEquals( + 'yii.validation.boolean(value, messages, {"trueValue":true,"falseValue":false,"message":"attrB must be either \"true\" or \"false\".","skipOnEmpty":1,"strict":1});', + $validator->clientValidateAttribute($obj, 'attrB', new ViewStub) + ); + } +} + +class ViewStub extends \yii\web\View +{ + public function registerAssetBundle($name, $position = null) + { + return; + } } diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/framework/validators/BooleanValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/BooleanValidatorTest.php rename to tests/framework/validators/BooleanValidatorTest.php~HEAD diff --git a/tests/framework/validators/CompareValidatorTest.php b/tests/framework/validators/CompareValidatorTest.php index 239be30bfe..4360f6cc29 100644 --- a/tests/framework/validators/CompareValidatorTest.php +++ b/tests/framework/validators/CompareValidatorTest.php @@ -141,6 +141,50 @@ class CompareValidatorTest extends TestCase $this->assertTrue($model->hasErrors('attr_o')); } + public function testAttributeErrorMessages() + { + $model = FakedValidationModel::createWithAttributes([ + 'attr1' => 1, + 'attr2' => 2, + 'attrN' => 2, + ]); + + foreach ($this->getTestDataForMessages() as $data) { + $model->clearErrors($data[0]); + $validator = new CompareValidator(); + $validator->operator = $data[1]; + $validator->message = null; + $validator->init(); // reload messages + $validator->{$data[4]} = $data[2]; + $validator->validateAttribute($model, $data[0]); + $error = $model->getErrors($data[0])[0]; + $this->assertEquals($data[3], $error); + } + } + + protected function getTestDataForMessages() + { + return [ + ['attr1', '==', 2, 'attr1 must be equal to "2".', 'compareValue'], + ['attr1', '===', 2, 'attr1 must be equal to "2".', 'compareValue'], + ['attrN', '!=', 2, 'attrN must not be equal to "2".', 'compareValue'], + ['attrN', '!==', 2, 'attrN must not be equal to "2".', 'compareValue'], + ['attr1', '>', 2, 'attr1 must be greater than "2".', 'compareValue'], + ['attr1', '>=', 2, 'attr1 must be greater than or equal to "2".', 'compareValue'], + ['attr2', '<', 1, 'attr2 must be less than "1".', 'compareValue'], + ['attr2', '<=', 1, 'attr2 must be less than or equal to "1".', 'compareValue'], + + ['attr1', '==', 'attr2', 'attr1 must be equal to "attr2".', 'compareAttribute'], + ['attr1', '===', 'attr2', 'attr1 must be equal to "attr2".', 'compareAttribute'], + ['attrN', '!=', 'attr2', 'attrN must not be equal to "attr2".', 'compareAttribute'], + ['attrN', '!==', 'attr2', 'attrN must not be equal to "attr2".', 'compareAttribute'], + ['attr1', '>', 'attr2', 'attr1 must be greater than "attr2".', 'compareAttribute'], + ['attr1', '>=', 'attr2', 'attr1 must be greater than or equal to "attr2".', 'compareAttribute'], + ['attr2', '<', 'attr1', 'attr2 must be less than "attr1".', 'compareAttribute'], + ['attr2', '<=', 'attr1', 'attr2 must be less than or equal to "attr1".', 'compareAttribute'], + ]; + } + public function testValidateAttributeOperators() { $value = 55; diff --git a/tests/unit/framework/validators/CompareValidatorTest.php b/tests/framework/validators/CompareValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/CompareValidatorTest.php rename to tests/framework/validators/CompareValidatorTest.php~HEAD diff --git a/tests/framework/validators/DateValidatorTest.php b/tests/framework/validators/DateValidatorTest.php index 213435f31e..093d64b1ee 100644 --- a/tests/framework/validators/DateValidatorTest.php +++ b/tests/framework/validators/DateValidatorTest.php @@ -280,6 +280,40 @@ class DateValidatorTest extends TestCase public function testIntlValidationWithTime($timezone) { $this->testValidationWithTime($timezone); +<<<<<<< HEAD +======= + + $this->mockApplication([ + 'language' => 'en-GB', + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'long', + 'datetimeFormat' => 'short', // this is the format to be used by the validator by default + ] + ] + ]); + $val = new DateValidator(['type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31/5/2017 12:30')); + $this->assertFalse($val->validate('5/31/2017 12:30')); + $val = new DateValidator(['format' => 'short', 'locale' => 'en-GB', 'type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31/5/2017 12:30')); + $this->assertFalse($val->validate('5/31/2017 12:30')); + $this->mockApplication([ + 'language' => 'de-DE', + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'long', + 'datetimeFormat' => 'short', // this is the format to be used by the validator by default + ] + ] + ]); + $val = new DateValidator(['type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31.5.2017 12:30')); + $this->assertFalse($val->validate('5.31.2017 12:30')); + $val = new DateValidator(['format' => 'short', 'locale' => 'de-DE', 'type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31.5.2017 12:30')); + $this->assertFalse($val->validate('5.31.2017 12:30')); +>>>>>>> master } /** @@ -418,11 +452,29 @@ class DateValidatorTest extends TestCase public function testValidateValueRange() { +<<<<<<< HEAD $date = '14-09-13'; $val = new DateValidator(['format' => 'yyyy-MM-dd']); $this->assertTrue($val->validate($date), "$date is valid"); $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01']); +======= + if (PHP_INT_SIZE == 8) { // this passes only on 64bit systems + // intl parser allows 14 for yyyy pattern, see the following for more details: + // https://github.com/yiisoft/yii2/blob/a003a8fb487dfa60c0f88ecfacf18a7407ced18b/framework/validators/DateValidator.php#L51-L57 + $date = '14-09-13'; + $val = new DateValidator(['format' => 'yyyy-MM-dd']); + $this->assertTrue($val->validate($date), "$date is valid"); + + $min = '1900-01-01'; + $beforeMin = '1899-12-31'; + } else { + $min = '1920-01-01'; + $beforeMin = '1919-12-31'; + } + + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => $min]); +>>>>>>> master $date = "1958-01-12"; $this->assertTrue($val->validate($date), "$date is valid"); @@ -432,11 +484,19 @@ class DateValidatorTest extends TestCase $date = "1958-01-12"; $this->assertTrue($val->validate($date), "$date is valid"); +<<<<<<< HEAD $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01', 'max' => '2000-01-01']); $this->assertTrue($val->validate('1999-12-31'), "max -1 day is valid"); $this->assertTrue($val->validate('2000-01-01'), "max is inside range"); $this->assertTrue($val->validate('1900-01-01'), "min is inside range"); $this->assertFalse($val->validate('1899-12-31'), "min -1 day is invalid"); +======= + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => $min, 'max' => '2000-01-01']); + $this->assertTrue($val->validate('1999-12-31'), "max -1 day is valid"); + $this->assertTrue($val->validate('2000-01-01'), "max is inside range"); + $this->assertTrue($val->validate($min), "min is inside range"); + $this->assertFalse($val->validate($beforeMin), "min -1 day is invalid"); +>>>>>>> master $this->assertFalse($val->validate('2000-01-02'), "max +1 day is invalid"); } @@ -458,11 +518,29 @@ class DateValidatorTest extends TestCase public function testValidateAttributeRange() { +<<<<<<< HEAD $val = new DateValidator(['format' => 'yyyy-MM-dd']); $date = '14-09-13'; $this->validateModelAttribute($val, $date, true, "$date is valid"); $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01']); +======= + if (PHP_INT_SIZE == 8) { // this passes only on 64bit systems + // intl parser allows 14 for yyyy pattern, see the following for more details: + // https://github.com/yiisoft/yii2/blob/a003a8fb487dfa60c0f88ecfacf18a7407ced18b/framework/validators/DateValidator.php#L51-L57 + $val = new DateValidator(['format' => 'yyyy-MM-dd']); + $date = '14-09-13'; + $this->validateModelAttribute($val, $date, true, "$date is valid"); + + $min = '1900-01-01'; + $beforeMin = '1899-12-31'; + } else { + $min = '1920-01-01'; + $beforeMin = '1919-12-31'; + } + + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => $min]); +>>>>>>> master $date = '1958-01-12'; $this->validateModelAttribute($val, $date, true, "$date is valid"); @@ -472,11 +550,19 @@ class DateValidatorTest extends TestCase $date = '1958-01-12'; $this->validateModelAttribute($val, $date, true, "$date is valid"); +<<<<<<< HEAD $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01', 'max' => '2000-01-01']); $this->validateModelAttribute($val, '1999-12-31', true, "max -1 day is valid"); $this->validateModelAttribute($val, '2000-01-01', true, "max is inside range"); $this->validateModelAttribute($val, '1900-01-01', true, "min is inside range"); $this->validateModelAttribute($val, '1899-12-31', false, "min -1 day is invalid"); +======= + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => $min, 'max' => '2000-01-01']); + $this->validateModelAttribute($val, '1999-12-31', true, "max -1 day is valid"); + $this->validateModelAttribute($val, '2000-01-01', true, "max is inside range"); + $this->validateModelAttribute($val, $min, true, "min is inside range"); + $this->validateModelAttribute($val, $beforeMin, false, "min -1 day is invalid"); +>>>>>>> master $this->validateModelAttribute($val, '2000-01-02', false, "max +1 day is invalid"); } @@ -486,7 +572,11 @@ class DateValidatorTest extends TestCase $this->markTestSkipped("ICU is too old."); } $date = '14-09-13'; +<<<<<<< HEAD $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01']); +======= + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1920-01-01']); +>>>>>>> master $this->assertFalse($val->validate($date), "$date is too small"); } @@ -496,7 +586,11 @@ class DateValidatorTest extends TestCase $this->markTestSkipped("ICU is too old."); } $date = '14-09-13'; +<<<<<<< HEAD $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1900-01-01']); +======= + $val = new DateValidator(['format' => 'yyyy-MM-dd', 'min' => '1920-01-01']); +>>>>>>> master $this->validateModelAttribute($val, $date, false, "$date is too small"); } @@ -519,4 +613,39 @@ class DateValidatorTest extends TestCase return false; } +<<<<<<< HEAD +======= + + /** + * @depends testValidateAttributePHPFormat + */ + public function testTimestampAttributeSkipValidation() + { + // timestamp as integer + $val = new DateValidator(['format' => 'php:Y/m/d', 'timestampAttribute' => 'attr_date']); + $model = new FakedValidationModel(); + $model->attr_date = 1379030400; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + + $val = new DateValidator(['format' => 'php:Y/m/d', 'timestampAttribute' => 'attr_date']); + $model = new FakedValidationModel(); + $model->attr_date = 'invalid'; + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); + + // timestamp as formatted date + $val = new DateValidator(['format' => 'php:Y/m/d', 'timestampAttribute' => 'attr_date', 'timestampAttributeFormat' => 'php:Y-m-d']); + $model = new FakedValidationModel(); + $model->attr_date = '2013-09-13'; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + + $val = new DateValidator(['format' => 'php:Y/m/d', 'timestampAttribute' => 'attr_date', 'timestampAttributeFormat' => 'php:Y-m-d']); + $model = new FakedValidationModel(); + $model->attr_date = '2013-09-2013'; + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); + } +>>>>>>> master } diff --git a/tests/framework/validators/EachValidatorTest.php b/tests/framework/validators/EachValidatorTest.php index 3a50e59fa5..77b48ec0ab 100644 --- a/tests/framework/validators/EachValidatorTest.php +++ b/tests/framework/validators/EachValidatorTest.php @@ -72,4 +72,91 @@ class EachValidatorTest extends TestCase $validator->validateAttribute($model, 'attr_one'); $this->assertNotContains('integer', $model->getFirstError('attr_one')); } -} \ No newline at end of file +<<<<<<< HEAD +} +======= + + /** + * @depends testValidate + */ + public function testCustomMessageValue() + { + $model = FakedValidationModel::createWithAttributes([ + 'attr_one' => [ + 'TEXT', + ], + ]); + $validator = new EachValidator(['rule' => ['integer', 'message' => '{value} is not an integer']]); + + $validator->validateAttribute($model, 'attr_one'); + $this->assertSame('TEXT is not an integer', $model->getFirstError('attr_one')); + + $model->clearErrors(); + $validator->allowMessageFromRule = false; + $validator->message = '{value} is invalid'; + $validator->validateAttribute($model, 'attr_one'); + $this->assertEquals('TEXT is invalid', $model->getFirstError('attr_one')); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/10825 + * + * @depends testValidate + */ + public function testSkipOnEmpty() + { + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => true]]); + $this->assertTrue($validator->validate([''])); + + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => false]]); + $this->assertFalse($validator->validate([''])); + + $model = FakedValidationModel::createWithAttributes([ + 'attr_one' => [ + '' + ], + ]); + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => true]]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertFalse($model->hasErrors('attr_one')); + + $model->clearErrors(); + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => false]]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertTrue($model->hasErrors('attr_one')); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/9935 + * + * @depends testValidate + */ + public function testCompare() + { + $model = FakedValidationModel::createWithAttributes([ + 'attr_one' => [ + 'value1', + 'value2', + 'value3', + ], + 'attr_two' => 'value2', + ]); + $validator = new EachValidator(['rule' => ['compare', 'compareAttribute' => 'attr_two']]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertNotEmpty($model->getErrors('attr_one')); + $this->assertEquals(3, count($model->attr_one)); + + $model = FakedValidationModel::createWithAttributes([ + 'attr_one' => [ + 'value1', + 'value2', + 'value3', + ], + 'attr_two' => 'value4', + ]); + $validator = new EachValidator(['rule' => ['compare', 'compareAttribute' => 'attr_two', 'operator' => '!=']]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertEmpty($model->getErrors('attr_one')); + } +} +>>>>>>> master diff --git a/tests/framework/validators/EmailValidatorTest.php b/tests/framework/validators/EmailValidatorTest.php index 4365a81350..42b6b6686a 100644 --- a/tests/framework/validators/EmailValidatorTest.php +++ b/tests/framework/validators/EmailValidatorTest.php @@ -22,6 +22,9 @@ class EmailValidatorTest extends TestCase $this->assertTrue($validator->validate('sam@rmcreative.ru')); $this->assertTrue($validator->validate('5011@gmail.com')); + $this->assertTrue($validator->validate('Abc.123@example.com')); + $this->assertTrue($validator->validate('user+mailbox/department=shipping@example.com')); + $this->assertTrue($validator->validate('!#$%&\'*+-/=?^_`.{|}~@example.com')); $this->assertFalse($validator->validate('rmcreative.ru')); $this->assertFalse($validator->validate('Carsten Brandt ')); $this->assertFalse($validator->validate('"Carsten Brandt" ')); @@ -42,7 +45,10 @@ class EmailValidatorTest extends TestCase $this->assertFalse($validator->validate('Informtation info@oertliches.de')); $this->assertTrue($validator->validate('test@example.com')); $this->assertTrue($validator->validate('John Smith ')); + $this->assertTrue($validator->validate('"This name is longer than 64 characters. Blah blah blah blah blah" ')); $this->assertFalse($validator->validate('John Smith ')); + $this->assertFalse($validator->validate('Short Name ')); + $this->assertFalse($validator->validate('Short Name ')); } public function testValidateValueIdn() @@ -81,7 +87,10 @@ class EmailValidatorTest extends TestCase $this->assertTrue($validator->validate('')); $this->assertTrue($validator->validate('test@example.com')); $this->assertTrue($validator->validate('John Smith ')); + $this->assertTrue($validator->validate('"Такое имя достаточно длинное, но оно все равно может пройти валидацию" ')); $this->assertFalse($validator->validate('John Smith ')); + $this->assertFalse($validator->validate('Короткое имя <после-преобразования-в-idn-тут-будет-больше-чем-64-символа@пример.com>')); + $this->assertFalse($validator->validate('Короткое имя <тест@это-доменное-имя.после-преобразования-в-idn.будет-содержать-больше-254-символов.бла-бла-бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.com>')); } public function testValidateValueMx() diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/framework/validators/EmailValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/EmailValidatorTest.php rename to tests/framework/validators/EmailValidatorTest.php~HEAD diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php index d2d035846b..aff946edaf 100644 --- a/tests/framework/validators/FileValidatorTest.php +++ b/tests/framework/validators/FileValidatorTest.php @@ -2,6 +2,7 @@ namespace yiiunit\framework\validators; +use yii\helpers\FileHelper; use yii\validators\FileValidator; use yii\web\UploadedFile; use Yii; @@ -70,7 +71,7 @@ class FileValidatorTest extends TestCase public function testGetSizeLimit() { - $size = $this->sizeToBytes(ini_get('upload_max_filesize')); + $size = min($this->sizeToBytes(ini_get('upload_max_filesize')),$this->sizeToBytes(ini_get('post_max_size'))); $val = new FileValidator(); $this->assertEquals($size, $val->getSizeLimit()); $val->maxSize = $size + 1; // set and test if value is overridden @@ -137,6 +138,12 @@ class FileValidatorTest extends TestCase $val->validateAttribute($m, 'attr_files'); $this->assertTrue($m->hasErrors()); $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'you can upload at most') !== false); + + $val->maxFiles = 0; + $m->clearErrors(); + $val->validateAttribute($m, 'attr_files'); + $this->assertFalse($m->hasErrors()); + $m = FakedValidationModel::createWithAttributes( [ 'attr_images' => $this->createTestFiles( @@ -152,8 +159,8 @@ class FileValidatorTest extends TestCase 'type' => 'image/png' ], [ - 'name' => 'text.txt', - 'size' => 1024 + 'name' => 'text.txt', + 'size' => 1024 ], ] ) @@ -257,6 +264,23 @@ class FileValidatorTest extends TestCase return $files; } + /** + * @param $fileName + * @return UploadedFile + */ + protected function getRealTestFile($fileName) + { + $filePath = \Yii::getAlias('@yiiunit/framework/validators/data/mimeType/') . $fileName; + + return new UploadedFile([ + 'name' => $fileName, + 'tempName' => $filePath, + 'type' => FileHelper::getMimeType($filePath), + 'size' => filesize($filePath), + 'error' => UPLOAD_ERR_OK + ]); + } + public function testValidateAttribute() { // single File @@ -322,6 +346,63 @@ class FileValidatorTest extends TestCase $this->assertTrue(stripos(current($m->getErrors('attr_exe')), 'Only files with these extensions ') !== false); } + public function testIssue11012() + { + $baseName = '飛兒樂團光茫'; + /** @var UploadedFile $file */ + $file = $this->createTestFiles([ + ['name' => $baseName . '.txt'], + ]); + $this->assertEquals($baseName, $file->getBaseName()); + } + + /** + * @param string $fileName + * @param string $mask + * @dataProvider validMimeTypes + */ + public function testValidateMimeTypeMaskValid($fileName, $mask) + { + $validator = new FileValidator(['mimeTypes' => $mask]); + $file = $this->getRealTestFile($fileName); + $this->assertTrue($validator->validate($file)); + } + + /** + * @param string $fileName + * @param string $mask + * @dataProvider invalidMimeTypes + */ + public function testValidateMimeTypeMaskInvalid($fileName, $mask) + { + $validator = new FileValidator(['mimeTypes' => $mask]); + $file = $this->getRealTestFile($fileName); + $this->assertFalse($validator->validate($file)); + } + + public function validMimeTypes() + { + return [ + ['test.svg', 'image/*'], + ['test.jpg', 'image/*'], + ['test.png', 'image/*'], + ['test.png', 'IMAGE/*'], + ['test.txt', 'text/*'], + ['test.xml', '*/xml'], + ['test.odt', 'application/vnd*'] + ]; + } + + public function invalidMimeTypes() + { + return [ + ['test.txt', 'image/*'], + ['test.odt', 'text/*'], + ['test.xml', '*/svg+xml'], + ['test.png', 'image/x-iso9660-image'], + ]; + } + protected function createModelForAttributeTest() { return FakedValidationModel::createWithAttributes( diff --git a/tests/unit/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/FileValidatorTest.php rename to tests/framework/validators/FileValidatorTest.php~HEAD diff --git a/tests/framework/validators/IpValidatorTest.php b/tests/framework/validators/IpValidatorTest.php new file mode 100644 index 0000000000..9471a336e1 --- /dev/null +++ b/tests/framework/validators/IpValidatorTest.php @@ -0,0 +1,362 @@ +markTestSkipped('The environment does not support IPv6.'); + } + + parent::setUp(); + $this->mockApplication(); + } + + public function testInitException() + { + $this->setExpectedException('yii\base\InvalidConfigException', + 'Both IPv4 and IPv6 checks can not be disabled at the same time'); + new IpValidator(['ipv4' => false, 'ipv6' => false]); + } + + public function provideRangesForSubstitution() { + return [ + ['10.0.0.1', ['10.0.0.1']], + [['192.168.0.32', 'fa::/32', 'any'], ['192.168.0.32', 'fa::/32', '0.0.0.0/0', '::/0']], + [['10.0.0.1', '!private'], ['10.0.0.1', '!10.0.0.0/8', '!172.16.0.0/12', '!192.168.0.0/16', '!fd00::/8']], + [['private', '!system'], ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fd00::/8', '!224.0.0.0/4', '!ff00::/8', '!169.254.0.0/16', '!fe80::/10', '!127.0.0.0/8', '!::1', '!192.0.2.0/24', '!198.51.100.0/24', '!203.0.113.0/24', '!2001:db8::/32']] + ]; + } + + /** + * @dataProvider provideRangesForSubstitution + */ + public function testRangesSubstitution($range, $expectedRange) + { + $validator = new IpValidator(['ranges' => $range]); + $this->assertEquals($expectedRange, $validator->ranges); + } + + + public function testValidateOrder() + { + $validator = new IpValidator([ + 'ranges' => ['10.0.0.1', '!10.0.0.0/8', '!babe::/8', 'any'] + ]); + + $this->assertTrue($validator->validate('10.0.0.1')); + $this->assertFalse($validator->validate('10.0.0.2')); + $this->assertTrue($validator->validate('192.168.5.101')); + $this->assertTrue($validator->validate('cafe::babe')); + $this->assertFalse($validator->validate('babe::cafe')); + } + + public function provideBadIps() { + return [['not.an.ip'], [['what an array', '??']], [123456], [true], [false]]; + } + + /** + * @dataProvider provideBadIps + */ + public function testValidateValueNotAnIP($badIp) + { + $validator = new IpValidator(); + + $this->assertFalse($validator->validate($badIp)); + } + + public function testValidateValueIPv4() + { + $validator = new IpValidator(); + + $this->assertTrue($validator->validate('192.168.10.11')); + $this->assertTrue($validator->validate('192.168.005.001')); + $this->assertFalse($validator->validate('192.168.5.321')); + $this->assertFalse($validator->validate('!192.168.5.32')); + $this->assertFalse($validator->validate('192.168.5.32/11')); + + $validator->ipv4 = false; + $this->assertFalse($validator->validate('192.168.10.11')); + + $validator->ipv4 = true; + $validator->subnet = null; + + $this->assertTrue($validator->validate('192.168.5.32/11')); + $this->assertTrue($validator->validate('192.168.5.32/32')); + $this->assertTrue($validator->validate('0.0.0.0/0')); + $this->assertFalse($validator->validate('192.168.5.32/33')); + $this->assertFalse($validator->validate('192.168.5.32/33')); + $this->assertFalse($validator->validate('192.168.5.32/af')); + $this->assertFalse($validator->validate('192.168.5.32/11/12')); + + $validator->subnet = true; + $this->assertTrue($validator->validate('10.0.0.1/24')); + $this->assertTrue($validator->validate('10.0.0.1/0')); + $this->assertFalse($validator->validate('10.0.0.1')); + + $validator->negation = true; + $this->assertTrue($validator->validate('!192.168.5.32/32')); + $this->assertFalse($validator->validate('!!192.168.5.32/32')); + } + + + public function testValidateValueIPv6() + { + $validator = new IpValidator(); + + $this->assertTrue($validator->validate('2008:fa::1')); + $this->assertTrue($validator->validate('2008:00fa::0001')); + $this->assertFalse($validator->validate('2008:fz::0')); + $this->assertFalse($validator->validate('2008:fa::0::1')); + $this->assertFalse($validator->validate('!2008:fa::0::1')); + $this->assertFalse($validator->validate('2008:fa::0:1/64')); + + $validator->ipv4 = false; + $this->assertTrue($validator->validate('2008:fa::1')); + + $validator->ipv6 = false; + $this->assertFalse($validator->validate('2008:fa::1')); + + $validator->ipv6 = true; + $validator->subnet = null; + + $this->assertTrue($validator->validate('2008:fa::0:1/64')); + $this->assertTrue($validator->validate('2008:fa::0:1/128')); + $this->assertTrue($validator->validate('2008:fa::0:1/0')); + $this->assertFalse($validator->validate('!2008:fa::0:1/0')); + $this->assertFalse($validator->validate('2008:fz::0/129')); + + $validator->subnet = true; + $this->assertTrue($validator->validate('2008:db0::1/64')); + $this->assertFalse($validator->validate('2008:db0::1')); + + $validator->negation = true; + $this->assertTrue($validator->validate('!2008:fa::0:1/64')); + $this->assertFalse($validator->validate('!!2008:fa::0:1/64')); + } + + public function testValidateValueIPvBoth() + { + $validator = new IpValidator(); + + $this->assertTrue($validator->validate('192.168.10.11')); + $this->assertTrue($validator->validate('2008:fa::1')); + $this->assertTrue($validator->validate('2008:00fa::0001')); + $this->assertTrue($validator->validate('192.168.005.001')); + $this->assertFalse($validator->validate('192.168.5.321')); + $this->assertFalse($validator->validate('!192.168.5.32')); + $this->assertFalse($validator->validate('192.168.5.32/11')); + $this->assertFalse($validator->validate('2008:fz::0')); + $this->assertFalse($validator->validate('2008:fa::0::1')); + $this->assertFalse($validator->validate('!2008:fa::0::1')); + $this->assertFalse($validator->validate('2008:fa::0:1/64')); + + $validator->ipv4 = false; + $this->assertFalse($validator->validate('192.168.10.11')); + $this->assertTrue($validator->validate('2008:fa::1')); + + $validator->ipv6 = false; + $validator->ipv4 = true; + $this->assertTrue($validator->validate('192.168.10.11')); + $this->assertFalse($validator->validate('2008:fa::1')); + + $validator->ipv6 = true; + $validator->subnet = null; + + $this->assertTrue($validator->validate('192.168.5.32/11')); + $this->assertTrue($validator->validate('192.168.5.32/32')); + $this->assertTrue($validator->validate('0.0.0.0/0')); + $this->assertTrue($validator->validate('2008:fa::0:1/64')); + $this->assertTrue($validator->validate('2008:fa::0:1/128')); + $this->assertTrue($validator->validate('2008:fa::0:1/0')); + $this->assertFalse($validator->validate('!2008:fa::0:1/0')); + $this->assertFalse($validator->validate('192.168.5.32/33')); + $this->assertFalse($validator->validate('2008:fz::0/129')); + $this->assertFalse($validator->validate('192.168.5.32/33')); + $this->assertFalse($validator->validate('192.168.5.32/af')); + $this->assertFalse($validator->validate('192.168.5.32/11/12')); + + $validator->subnet = true; + $this->assertTrue($validator->validate('10.0.0.1/24')); + $this->assertTrue($validator->validate('10.0.0.1/0')); + $this->assertTrue($validator->validate('2008:db0::1/64')); + $this->assertFalse($validator->validate('2008:db0::1')); + $this->assertFalse($validator->validate('10.0.0.1')); + + $validator->negation = true; + + $this->assertTrue($validator->validate('!192.168.5.32/32')); + $this->assertTrue($validator->validate('!2008:fa::0:1/64')); + $this->assertFalse($validator->validate('!!192.168.5.32/32')); + $this->assertFalse($validator->validate('!!2008:fa::0:1/64')); + } + + public function testValidateRangeIPv4() + { + $validator = new IpValidator([ + 'ranges' => ['10.0.1.0/24'] + ]); + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertFalse($validator->validate('192.5.1.1')); + + $validator->ranges = ['10.0.1.0/24']; + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertFalse($validator->validate('10.0.3.2')); + + $validator->ranges = ['!10.0.1.0/24', '10.0.0.0/8', 'localhost']; + $this->assertFalse($validator->validate('10.0.1.2')); + $this->assertTrue($validator->validate('127.0.0.1')); + + $validator->subnet = null; + $validator->ranges = ['10.0.1.0/24', '!10.0.0.0/8', 'localhost']; + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertTrue($validator->validate('127.0.0.1')); + $this->assertTrue($validator->validate('10.0.1.28/28')); + $this->assertFalse($validator->validate('10.2.2.2')); + $this->assertFalse($validator->validate('10.0.1.1/22')); + } + + public function testValidateRangeIPv6() + { + $validator = new IpValidator([ + 'ranges' => '2001:db0:1:1::/64' + ]); + $this->assertTrue($validator->validate('2001:db0:1:1::6')); + $this->assertFalse($validator->validate('2001:db0:1:2::7')); + + $validator->ranges = ['2001:db0:1:2::/64']; + $this->assertTrue($validator->validate('2001:db0:1:2::7')); + + $validator->ranges = ['!2001:db0::/32', '2001:db0:1:2::/64', ]; + $this->assertFalse($validator->validate('2001:db0:1:2::7')); + + $validator->subnet = null; + $validator->ranges = array_reverse($validator->ranges); + $this->assertTrue($validator->validate('2001:db0:1:2::7')); + } + + public function testValidateRangeIPvBoth() + { + $validator = new IpValidator([ + 'ranges' => '10.0.1.0/24', + ]); + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertFalse($validator->validate('192.5.1.1')); + $this->assertFalse($validator->validate('2001:db0:1:2::7')); + + $validator->ranges = ['10.0.1.0/24', '2001:db0:1:2::/64', '127.0.0.1']; + $this->assertTrue($validator->validate('2001:db0:1:2::7')); + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertFalse($validator->validate('10.0.3.2')); + + $validator->ranges = ['!system', 'any']; + $this->assertFalse($validator->validate('127.0.0.1')); + $this->assertFalse($validator->validate('fe80::face')); + $this->assertTrue($validator->validate('8.8.8.8')); + + $validator->subnet = null; + $validator->ranges = ['10.0.1.0/24', '2001:db0:1:2::/64', 'localhost', '!all']; + $this->assertTrue($validator->validate('10.0.1.2')); + $this->assertTrue($validator->validate('2001:db0:1:2::7')); + $this->assertTrue($validator->validate('127.0.0.1')); + $this->assertTrue($validator->validate('10.0.1.28/28')); + $this->assertFalse($validator->validate('10.2.2.2')); + $this->assertFalse($validator->validate('10.0.1.1/22')); + } + + public function testValidateAttributeIPv4() + { + $validator = new IpValidator(); + $model = new FakedValidationModel(); + + $validator->subnet = null; + + $model->attr_ip = '8.8.8.8'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('8.8.8.8', $model->attr_ip); + + $validator->subnet = false; + + $model->attr_ip = '8.8.8.8'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('8.8.8.8', $model->attr_ip); + + $model->attr_ip = '8.8.8.8/24'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertTrue($model->hasErrors('attr_ip')); + $this->assertEquals('attr_ip must not be a subnet.', $model->getFirstError('attr_ip')); + $model->clearErrors(); + + $validator->subnet = null; + $validator->normalize = true; + + $model->attr_ip = '8.8.8.8'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('8.8.8.8/32', $model->attr_ip); + } + + + public function testValidateAttributeIPv6() + { + $validator = new IpValidator(); + $model = new FakedValidationModel(); + + $validator->subnet = null; + + $model->attr_ip = '2001:db0:1:2::1'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('2001:db0:1:2::1', $model->attr_ip); + + $validator->subnet = false; + + $model->attr_ip = '2001:db0:1:2::7'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('2001:db0:1:2::7', $model->attr_ip); + + $model->attr_ip = '2001:db0:1:2::7/64'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertTrue($model->hasErrors('attr_ip')); + $this->assertEquals('attr_ip must not be a subnet.', $model->getFirstError('attr_ip')); + $model->clearErrors(); + + $validator->subnet = null; + $validator->normalize = true; + + $model->attr_ip = 'fa01::1'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('fa01::1/128', $model->attr_ip); + + $model->attr_ip = 'fa01::1/64'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('fa01::1/64', $model->attr_ip); + + $validator->expandIPv6 = true; + + $model->attr_ip = 'fa01::1/64'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertFalse($model->hasErrors('attr_ip')); + $this->assertEquals('fa01:0000:0000:0000:0000:0000:0000:0001/64', $model->attr_ip); + + $model->attr_ip = 'fa01::2/614'; + $validator->validateAttribute($model, 'attr_ip'); + $this->assertTrue($model->hasErrors('attr_ip')); + $this->assertEquals('fa01::2/614', $model->attr_ip); + $this->assertEquals('attr_ip contains wrong subnet mask.', $model->getFirstError('attr_ip')); + } +} \ No newline at end of file diff --git a/tests/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php index 47bb1bb654..828c783dae 100644 --- a/tests/framework/validators/NumberValidatorTest.php +++ b/tests/framework/validators/NumberValidatorTest.php @@ -208,4 +208,11 @@ class NumberValidatorTest extends TestCase $this->assertContains('"min":5.65', $js); $this->assertContains('"max":13.37', $js); } + + public function testValidateObject() + { + $val = new NumberValidator(); + $value = new \stdClass(); + $this->assertFalse($val->validate($value)); + } } diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/NumberValidatorTest.php rename to tests/framework/validators/NumberValidatorTest.php~HEAD diff --git a/tests/framework/validators/RangeValidatorTest.php b/tests/framework/validators/RangeValidatorTest.php index b4cbf32214..e105513826 100644 --- a/tests/framework/validators/RangeValidatorTest.php +++ b/tests/framework/validators/RangeValidatorTest.php @@ -105,4 +105,39 @@ class RangeValidatorTest extends TestCase $err = $m->getErrors('attr_r2'); $this->assertTrue(stripos($err[0], 'attr_r2') !== false); } + + public function testValidateSubsetArrayable() + { + + // Test in array, values are arrays. IE: ['a'] in [['a'], ['b']] + $val = new RangeValidator([ + 'range' => [['a'], ['b']], + 'allowArray' => false + ]); + $this->assertTrue($val->validate(['a'])); + + // Test in array, values are arrays. IE: ['a', 'b'] subset [['a', 'b', 'c'] + $val = new RangeValidator([ + 'range' => ['a', 'b', 'c'], + 'allowArray' => true + ]); + $this->assertTrue($val->validate(['a', 'b'])); + + // Test in array, values are arrays. IE: ['a', 'b'] subset [['a', 'b', 'c'] + $val = new RangeValidator([ + 'range' => ['a', 'b', 'c'], + 'allowArray' => true + ]); + $this->assertTrue($val->validate(new \ArrayObject(['a', 'b']))); + + + // Test range as ArrayObject. + $val = new RangeValidator([ + 'range' => new \ArrayObject(['a', 'b']), + 'allowArray' => false + ]); + $this->assertTrue($val->validate('a')); + + + } } diff --git a/tests/unit/framework/validators/RangeValidatorTest.php b/tests/framework/validators/RangeValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/RangeValidatorTest.php rename to tests/framework/validators/RangeValidatorTest.php~HEAD diff --git a/tests/framework/validators/RequiredValidatorTest.php b/tests/framework/validators/RequiredValidatorTest.php index ce62773154..8e5cccc970 100644 --- a/tests/framework/validators/RequiredValidatorTest.php +++ b/tests/framework/validators/RequiredValidatorTest.php @@ -30,7 +30,6 @@ class RequiredValidatorTest extends TestCase $val = new RequiredValidator(['requiredValue' => 55]); $this->assertTrue($val->validate(55)); $this->assertTrue($val->validate("55")); - $this->assertTrue($val->validate("0x37")); $this->assertFalse($val->validate("should fail")); $this->assertTrue($val->validate(true)); $val->strict = true; diff --git a/tests/unit/framework/validators/RequiredValidatorTest.php b/tests/framework/validators/RequiredValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/RequiredValidatorTest.php rename to tests/framework/validators/RequiredValidatorTest.php~HEAD diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index 7a1b805725..5f599f6a74 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -5,8 +5,10 @@ namespace yiiunit\framework\validators; use yii\validators\UniqueValidator; use Yii; use yiiunit\data\ar\ActiveRecord; +use yiiunit\data\ar\Customer; use yiiunit\data\ar\Order; use yiiunit\data\ar\OrderItem; +use yiiunit\data\ar\Profile; use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\data\validators\models\ValidatorTestMainModel; use yiiunit\data\validators\models\ValidatorTestRefModel; @@ -32,12 +34,33 @@ class UniqueValidatorTest extends DatabaseTestCase $this->assertTrue(is_string($val->message)); } + public function testValidateInvalidAttribute() + { + $validator = new UniqueValidator(); + $messageError = Yii::t('yii', '{attribute} is invalid.', ['attribute' => 'Name']); + + /** @var Customer $customerModel */ + $customerModel = Customer::findOne(1); + $customerModel->name = ['test array data']; + $validator->validateAttribute($customerModel, 'name'); + $this->assertEquals($messageError, $customerModel->getFirstError('name')); + + $customerModel->clearErrors(); + + $customerModel->name = 'test data'; + $customerModel->email = ['email@mail.com', 'email2@mail.com',]; + $validator->targetAttribute = ['email', 'name']; + $validator->validateAttribute($customerModel, 'name'); + $this->assertEquals($messageError, $customerModel->getFirstError('name')); + } + public function testValidateAttributeDefault() { $val = new UniqueValidator(); $m = ValidatorTestMainModel::find()->one(); $val->validateAttribute($m, 'id'); $this->assertFalse($m->hasErrors('id')); + /** @var ValidatorTestRefModel $m */ $m = ValidatorTestRefModel::findOne(1); $val->validateAttribute($m, 'ref'); $this->assertTrue($m->hasErrors('ref')); @@ -73,6 +96,7 @@ class UniqueValidatorTest extends DatabaseTestCase public function testValidateNonDatabaseAttribute() { $val = new UniqueValidator(['targetClass' => ValidatorTestRefModel::className(), 'targetAttribute' => 'ref']); + /** @var ValidatorTestMainModel $m */ $m = ValidatorTestMainModel::findOne(1); $val->validateAttribute($m, 'testMainVal'); $this->assertFalse($m->hasErrors('testMainVal')); @@ -97,6 +121,7 @@ class UniqueValidatorTest extends DatabaseTestCase 'targetAttribute' => ['order_id', 'item_id'], ]); // validate old record + /** @var OrderItem $m */ $m = OrderItem::findOne(['order_id' => 1, 'item_id' => 2]); $val->validateAttribute($m, 'order_id'); $this->assertFalse($m->hasErrors('order_id')); @@ -117,6 +142,7 @@ class UniqueValidatorTest extends DatabaseTestCase 'targetAttribute' => ['id' => 'order_id'], ]); // validate old record + /** @var Order $m */ $m = Order::findOne(1); $val->validateAttribute($m, 'id'); $this->assertTrue($m->hasErrors('id')); @@ -136,4 +162,73 @@ class UniqueValidatorTest extends DatabaseTestCase $val->validateAttribute($m, 'id'); $this->assertFalse($m->hasErrors('id')); } -} + + public function testValidateTargetClass() + { + // Check whether "Description" and "address" aren't equal + $val = new UniqueValidator([ + 'targetClass' => Customer::className(), + 'targetAttribute' => ['description'=>'address'], + ]); + + /** @var Profile $m */ + $m = Profile::findOne(1); + $this->assertEquals('profile customer 1', $m->description); + $val->validateAttribute($m, 'description'); + $this->assertFalse($m->hasErrors('description')); + + // ID of Profile is not equal to ID of Customer + // (1, description = address2) <=> (2, address = address2) + $m->description = 'address2'; + $val->validateAttribute($m, 'description'); + $this->assertTrue($m->hasErrors('description')); + $m->clearErrors('description'); + + // ID of Profile IS equal to ID of Customer + // (1, description = address1) <=> (1, address = address1) + // https://github.com/yiisoft/yii2/issues/10263 + $m->description = 'address1'; + $val->validateAttribute($m, 'description'); + $this->assertTrue($m->hasErrors('description')); + } + + public function testValidateScopeNamespaceTargetClassForNewClass() + { + $validator = new UniqueValidator(); + + /** @var Profile $profileModel */ + $profileModel = new Profile(['description'=>'profile customer 1']); + $validator->validateAttribute($profileModel, 'description'); + $this->assertTrue($profileModel->hasErrors('description')); + + $profileModel->clearErrors(); + $validator->targetClass = 'yiiunit\data\ar\Profile'; + $validator->validateAttribute($profileModel, 'description'); + $this->assertTrue($profileModel->hasErrors('description')); + + $profileModel->clearErrors(); + $validator->targetClass = '\yiiunit\data\ar\Profile'; + $validator->validateAttribute($profileModel, 'description'); + $this->assertTrue($profileModel->hasErrors('description')); + } + + public function testValidateScopeNamespaceTargetClass() + { + $validator = new UniqueValidator(); + + /** @var Profile $profileModel */ + $profileModel = Profile::findOne(1); + $validator->validateAttribute($profileModel, 'description'); + $this->assertFalse($profileModel->hasErrors('description')); + + $profileModel->clearErrors(); + $validator->targetClass = 'yiiunit\data\ar\Profile'; + $validator->validateAttribute($profileModel, 'description'); + $this->assertFalse($profileModel->hasErrors('description')); + + $profileModel->clearErrors(); + $validator->targetClass = '\yiiunit\data\ar\Profile'; + $validator->validateAttribute($profileModel, 'description'); + $this->assertFalse($profileModel->hasErrors('description')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/UniqueValidatorTest.php rename to tests/framework/validators/UniqueValidatorTest.php~HEAD diff --git a/tests/framework/validators/UrlValidatorTest.php b/tests/framework/validators/UrlValidatorTest.php index 5ae66c9502..b9591670b9 100644 --- a/tests/framework/validators/UrlValidatorTest.php +++ b/tests/framework/validators/UrlValidatorTest.php @@ -28,6 +28,23 @@ class UrlValidatorTest extends TestCase .'&rls=org.mozilla:de:official&client=firefox-a&gws_rd=cr')); $this->assertFalse($val->validate('ftp://ftp.ruhr-uni-bochum.de/')); $this->assertFalse($val->validate('http://invalid,domain')); + $this->assertFalse($val->validate('http://example.com,')); + $this->assertFalse($val->validate('http://example.com*12')); + $this->assertTrue($val->validate('http://example.com/*12')); + $this->assertTrue($val->validate('http://example.com/?test')); + $this->assertTrue($val->validate('http://example.com/#test')); + $this->assertTrue($val->validate('http://example.com:80/#test')); + $this->assertTrue($val->validate('http://example.com:65535/#test')); + $this->assertTrue($val->validate('http://example.com:81/?good')); + $this->assertTrue($val->validate('http://example.com?test')); + $this->assertTrue($val->validate('http://example.com#test')); + $this->assertTrue($val->validate('http://example.com:81#test')); + $this->assertTrue($val->validate('http://example.com:81?good')); + $this->assertFalse($val->validate('http://example.com,?test')); + $this->assertFalse($val->validate('http://example.com:?test')); + $this->assertFalse($val->validate('http://example.com:test')); + $this->assertFalse($val->validate('http://example.com:123456/test')); + $this->assertFalse($val->validate('http://äüö?=!"§$%&/()=}][{³²€.edu')); } diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/framework/validators/UrlValidatorTest.php~HEAD similarity index 100% rename from tests/unit/framework/validators/UrlValidatorTest.php rename to tests/framework/validators/UrlValidatorTest.php~HEAD diff --git a/tests/framework/validators/data/mimeType/test.jpg b/tests/framework/validators/data/mimeType/test.jpg new file mode 100644 index 0000000000..1cda9a53dc Binary files /dev/null and b/tests/framework/validators/data/mimeType/test.jpg differ diff --git a/tests/framework/validators/data/mimeType/test.odt b/tests/framework/validators/data/mimeType/test.odt new file mode 100644 index 0000000000..642bd44122 Binary files /dev/null and b/tests/framework/validators/data/mimeType/test.odt differ diff --git a/tests/framework/validators/data/mimeType/test.png b/tests/framework/validators/data/mimeType/test.png new file mode 100644 index 0000000000..1914264c08 Binary files /dev/null and b/tests/framework/validators/data/mimeType/test.png differ diff --git a/tests/framework/validators/data/mimeType/test.svg b/tests/framework/validators/data/mimeType/test.svg new file mode 100644 index 0000000000..52d8a8c1c4 --- /dev/null +++ b/tests/framework/validators/data/mimeType/test.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/tests/framework/validators/data/mimeType/test.txt b/tests/framework/validators/data/mimeType/test.txt new file mode 100644 index 0000000000..30d74d2584 --- /dev/null +++ b/tests/framework/validators/data/mimeType/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/tests/framework/validators/data/mimeType/test.xml b/tests/framework/validators/data/mimeType/test.xml new file mode 100644 index 0000000000..5011f2828e --- /dev/null +++ b/tests/framework/validators/data/mimeType/test.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/framework/web/AssetBundleTest.php b/tests/framework/web/AssetBundleTest.php index 2f67ebe6e2..1f95880fb9 100644 --- a/tests/framework/web/AssetBundleTest.php +++ b/tests/framework/web/AssetBundleTest.php @@ -24,19 +24,135 @@ class AssetBundleTest extends \yiiunit\TestCase Yii::setAlias('@testWeb', '/'); Yii::setAlias('@testWebRoot', '@yiiunit/data/web'); + Yii::setAlias('@testAssetsPath', '@testWebRoot/assets'); + Yii::setAlias('@testAssetsUrl', '@testWeb/assets'); + Yii::setAlias('@testSourcePath', '@testWebRoot/assetSources'); } - protected function getView() + /** + * Returns View with configured AssetManager + * + * @param array $config may be used to override default AssetManager config + * @return View + */ + protected function getView(array $config = []) { + $this->mockApplication(); $view = new View(); - $view->setAssetManager(new AssetManager([ - 'basePath' => '@testWebRoot/assets', - 'baseUrl' => '@testWeb/assets', - ])); + $config = array_merge([ + 'basePath' => '@testAssetsPath', + 'baseUrl' => '@testAssetsUrl', + ], $config); + $view->setAssetManager(new AssetManager($config)); return $view; } + public function testSourcesPublish() + { + $view = $this->getView(); + $am = $view->assetManager; + + $bundle = TestSourceAsset::register($view); + $bundle->publish($am); + + $this->assertTrue(is_dir($bundle->basePath)); + foreach ($bundle->js as $filename) { + $publishedFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; + $sourceFile = $bundle->sourcePath . DIRECTORY_SEPARATOR . $filename; + $this->assertFileExists($publishedFile); + $this->assertFileEquals($publishedFile, $sourceFile); + $this->assertTrue(unlink($publishedFile)); + } + $this->assertTrue(rmdir($bundle->basePath . DIRECTORY_SEPARATOR . 'js')); + + $this->assertTrue(rmdir($bundle->basePath)); + } + + public function testSourcesPublishedBySymlink() + { + $view = $this->getView(['linkAssets' => true]); + $this->verifySourcesPublishedBySymlink($view); + } + + public function testSourcesPublishedBySymlink_Issue9333() + { + $view = $this->getView([ + 'linkAssets' => true, + 'hashCallback' => function ($path) { + return sprintf('%x/%x', crc32($path), crc32(Yii::getVersion())); + } + ]); + $bundle = $this->verifySourcesPublishedBySymlink($view); + $this->assertTrue(rmdir(dirname($bundle->basePath))); + } + + public function testSourcesPublish_AssetManagerBeforeCopy() + { + $view = $this->getView([ + 'beforeCopy' => function ($from, $to) { + return false; + } + ]); + $am = $view->assetManager; + + $bundle = TestSourceAsset::register($view); + $bundle->publish($am); + + $this->assertTrue(is_dir($bundle->basePath)); + foreach ($bundle->js as $filename) { + $publishedFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; + $this->assertFileNotExists($publishedFile); + } + $this->assertTrue(rmdir($bundle->basePath)); + } + + public function testSourcesPublish_AssetBeforeCopy() + { + $view = $this->getView(); + $am = $view->assetManager; + + $bundle = new TestSourceAsset(); + $bundle->publishOptions = [ + 'beforeCopy' => function ($from, $to) { + return false; + } + ]; + $bundle->publish($am); + + $this->assertTrue(is_dir($bundle->basePath)); + foreach ($bundle->js as $filename) { + $publishedFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; + $this->assertFileNotExists($publishedFile); + } + $this->assertTrue(rmdir($bundle->basePath)); + } + + /** + * @param View $view + * @return AssetBundle + */ + protected function verifySourcesPublishedBySymlink($view) + { + $am = $view->assetManager; + + $bundle = TestSourceAsset::register($view); + $bundle->publish($am); + + $this->assertTrue(is_dir($bundle->basePath)); + foreach ($bundle->js as $filename) { + $publishedFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; + $sourceFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; + + $this->assertTrue(is_link($bundle->basePath)); + $this->assertFileExists($publishedFile); + $this->assertFileEquals($publishedFile, $sourceFile); + } + + $this->assertTrue(unlink($bundle->basePath)); + return $bundle; + } + public function testRegister() { $view = $this->getView(); @@ -201,6 +317,22 @@ EOF; $expected = <<4 +EOF; + $this->assertEquals($expected, $view->renderFile('@yiiunit/data/views/rawlayout.php')); + } + + public function testPerFileOptions() + { + $view = $this->getView(); + + $this->assertEmpty($view->assetBundles); + TestAssetPerFileOptions::register($view); + + $expected = << + +23 +4 EOF; $this->assertEquals($expected, $view->renderFile('@yiiunit/data/views/rawlayout.php')); } @@ -215,6 +347,14 @@ class TestSimpleAsset extends AssetBundle ]; } +class TestSourceAsset extends AssetBundle +{ + public $sourcePath = '@testSourcePath'; + public $js = [ + 'js/jquery.js', + ]; +} + class TestAssetBundle extends AssetBundle { public $basePath = '@testWebRoot/files'; @@ -271,3 +411,20 @@ class TestAssetCircleB extends AssetBundle 'yiiunit\\framework\\web\\TestAssetCircleA' ]; } + +class TestAssetPerFileOptions extends AssetBundle +{ + public $basePath = '@testWebRoot'; + public $baseUrl = '@testWeb'; + public $css = [ + 'default_options.css', + ['tv.css', 'media' => 'tv'], + ['screen_and_print.css', 'media' => 'screen, print'] + ]; + public $js = [ + 'normal.js', + ['defered.js', 'defer' => true], + ]; + public $cssOptions = ['media' => 'screen', 'hreflang' => 'en']; + public $jsOptions = ['charset' => 'utf-8']; +} diff --git a/tests/unit/framework/web/AssetBundleTest.php b/tests/framework/web/AssetBundleTest.php~HEAD similarity index 100% rename from tests/unit/framework/web/AssetBundleTest.php rename to tests/framework/web/AssetBundleTest.php~HEAD diff --git a/tests/framework/web/ControllerTest.php b/tests/framework/web/ControllerTest.php new file mode 100644 index 0000000000..07e1bb330b --- /dev/null +++ b/tests/framework/web/ControllerTest.php @@ -0,0 +1,41 @@ +mockApplication(); + + $controller = new FakeController('fake', Yii::$app); + $aksi1 = new InlineAction('aksi1', $controller, 'actionAksi1'); + + $params = ['fromGet'=>'from query params','q'=>'d426','validator'=>'avaliable']; + list($fromGet, $other) = $controller->bindActionParams($aksi1, $params); + $this->assertEquals('from query params', $fromGet); + $this->assertEquals('default', $other); + + $params = ['fromGet'=>'from query params','q'=>'d426','other'=>'avaliable']; + list($fromGet, $other) = $controller->bindActionParams($aksi1, $params); + $this->assertEquals('from query params', $fromGet); + $this->assertEquals('avaliable', $other); + + } +} diff --git a/tests/framework/web/DbSessionTest.php b/tests/framework/web/DbSessionTest.php new file mode 100644 index 0000000000..57eb066de5 --- /dev/null +++ b/tests/framework/web/DbSessionTest.php @@ -0,0 +1,141 @@ +markTestIncomplete('DbSessionTest requires SQLite!'); + } + $this->mockApplication(); + Yii::$app->set('db', [ + 'class' => Connection::className(), + 'dsn' => 'sqlite::memory:', + ]); + } + + protected function createTableSession() + { + Yii::$app->db->createCommand()->createTable('session', [ + 'id' => 'string', + 'expire' => 'integer', + 'data' => 'text', + 'user_id' => 'integer', + ])->execute(); + } + + // Tests : + + public function testReadWrite() + { + $this->createTableSession(); + + $session = new DbSession(); + + $session->writeSession('test', 'session data'); + $this->assertEquals('session data', $session->readSession('test')); + $session->destroySession('test'); + $this->assertEquals('', $session->readSession('test')); + } + + /** + * @depends testReadWrite + */ + public function testGarbageCollection() + { + $this->createTableSession(); + + $session = new DbSession(); + + $session->writeSession('new', 'new data'); + $session->writeSession('expire', 'expire data'); + + $session->db->createCommand() + ->update('session', ['expire' => time() - 100], 'id = :id', ['id' => 'expire']) + ->execute(); + $session->gcSession(1); + + $this->assertEquals('', $session->readSession('expire')); + $this->assertEquals('new data', $session->readSession('new')); + } + + /** + * @depends testReadWrite + */ + public function testWriteCustomField() + { + $this->createTableSession(); + + $session = new DbSession(); + $session->writeCallback = function ($session) { + return [ + 'user_id' => 15 + ]; + }; + + $session->writeSession('test', 'session data'); + + $query = new Query(); + $sessionRow = $query->from('session') + ->where(['id' => 'test']) + ->one(); + + $this->assertEquals('session data', $sessionRow['data']); + $this->assertEquals(15, $sessionRow['user_id']); + } + + protected function runMigrate($action, $params = []) + { + $migrate = new EchoMigrateController('migrate', Yii::$app, [ + 'migrationPath' => '@yii/web/migrations', + 'interactive' => false, + ]); + + ob_start(); + ob_implicit_flush(false); + $migrate->run($action, $params); + ob_get_clean(); + + return array_map(function($version){ + return substr($version, 15); + }, (new Query())->select(['version'])->from('migration')->column()); + } + + public function testMigration() + { + $this->mockWebApplication([ + 'components' => [ + 'db' => [ + 'class' => Connection::className(), + 'dsn' => 'sqlite::memory:', + ] + ], + ]); + + $history = $this->runMigrate('history'); + $this->assertEquals(['base'], $history); + + $history = $this->runMigrate('up'); + $this->assertEquals(['base','session_init'], $history); + + $history = $this->runMigrate('down'); + $this->assertEquals(['base'], $history); + } +} diff --git a/tests/framework/web/FakeController.php b/tests/framework/web/FakeController.php new file mode 100644 index 0000000000..c75d1846c2 --- /dev/null +++ b/tests/framework/web/FakeController.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class FakeController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionAksi1($fromGet, $other = 'default') + { + } + +} diff --git a/tests/framework/web/FormatterTest.php b/tests/framework/web/FormatterTest.php index 1c998793f9..b67052b596 100644 --- a/tests/framework/web/FormatterTest.php +++ b/tests/framework/web/FormatterTest.php @@ -63,6 +63,18 @@ abstract class FormatterTest extends \yiiunit\TestCase $this->assertEquals($json, $this->response->content); } + /** + * @param mixed $data the data to be formatted + * @param string $json the expected JSON body + * @dataProvider formatTraversableObjectDataProvider + */ + public function testFormatTraversableObjects($data, $json) + { + $this->response->data = $data; + $this->formatter->format($this->response); + $this->assertEquals($json, $this->response->content); + } + /** * @param mixed $data the data to be formatted * @param string $json the expected JSON body @@ -74,4 +86,16 @@ abstract class FormatterTest extends \yiiunit\TestCase $this->formatter->format($this->response); $this->assertEquals($json, $this->response->content); } -} \ No newline at end of file + + /** + * @param mixed $data the data to be formatted + * @param string $expectedResult the expected body + * @dataProvider formatModelDataProvider + */ + public function testFormatModels($data, $expectedResult) + { + $this->response->data = $data; + $this->formatter->format($this->response); + $this->assertEquals($expectedResult, $this->response->content); + } +} diff --git a/tests/unit/framework/web/FormatterTest.php b/tests/framework/web/FormatterTest.php~HEAD similarity index 100% rename from tests/unit/framework/web/FormatterTest.php rename to tests/framework/web/FormatterTest.php~HEAD diff --git a/tests/framework/web/JsonResponseFormatterTest.php b/tests/framework/web/JsonResponseFormatterTest.php index ca49f48499..37b7b3dfc3 100644 --- a/tests/framework/web/JsonResponseFormatterTest.php +++ b/tests/framework/web/JsonResponseFormatterTest.php @@ -8,6 +8,10 @@ namespace yiiunit\framework\web; use yii\web\JsonResponseFormatter; +<<<<<<< HEAD +======= +use yiiunit\framework\web\stubs\ModelStub; +>>>>>>> master /** * @author Alexander Makarov @@ -38,24 +42,43 @@ class JsonResponseFormatterTest extends FormatterTest public function formatArrayDataProvider() { return [ +<<<<<<< HEAD [[], "[]"], [[1, 'abc'], '[1,"abc"]'], [[ 'a' => 1, 'b' => 'abc', ], '{"a":1,"b":"abc"}'], +======= + // input, json, pretty json + [[], "[]", "[]"], + [[1, 'abc'], '[1,"abc"]', "[\n 1,\n \"abc\"\n]"], + [[ + 'a' => 1, + 'b' => 'abc', + ], '{"a":1,"b":"abc"}', "{\n \"a\": 1,\n \"b\": \"abc\"\n}"], +>>>>>>> master [[ 1, 'abc', [2, 'def'], true, +<<<<<<< HEAD ], '[1,"abc",[2,"def"],true]'], +======= + ], '[1,"abc",[2,"def"],true]', "[\n 1,\n \"abc\",\n [\n 2,\n \"def\"\n ],\n true\n]"], +>>>>>>> master [[ 'a' => 1, 'b' => 'abc', 'c' => [2, '<>'], true, +<<<<<<< HEAD ], '{"a":1,"b":"abc","c":[2,"<>"],"0":true}'], +======= + ], '{"a":1,"b":"abc","c":[2,"<>"],"0":true}', + "{\n \"a\": 1,\n \"b\": \"abc\",\n \"c\": [\n 2,\n \"<>\"\n ],\n \"0\": true\n}"], +>>>>>>> master ]; } @@ -73,4 +96,40 @@ class JsonResponseFormatterTest extends FormatterTest ], '{"0":{"id":123,"title":"<>"},"a":{"id":456,"title":"def"}}'], ]; } +<<<<<<< HEAD +======= + + public function formatTraversableObjectDataProvider() + { + $postsStack = new \SplStack(); + $postsStack->push(new Post(915, 'record1')); + $postsStack->push(new Post(456, 'record2')); + + return [ + [$postsStack, '{"1":{"id":456,"title":"record2"},"0":{"id":915,"title":"record1"}}'] + ]; + } + + public function formatModelDataProvider() + { + return [ + [new ModelStub(['id' => 123, 'title' => 'abc', 'hidden' => 'hidden']), '{"id":123,"title":"abc"}'] + ]; + } + + /** + * @param mixed $data the data to be formatted + * @param string $json the expected JSON body + * @param string $prettyJson the expected pretty JSON body + * @dataProvider formatArrayDataProvider + */ + public function testFormatArraysPretty($data, $json, $prettyJson) + { + $this->response->data = $data; + $this->formatter->prettyPrint = true; + $this->formatter->format($this->response); + $this->assertEquals($prettyJson, $this->response->content); + } + +>>>>>>> master } diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index 2d276f95da..2f83cde634 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -78,4 +78,130 @@ class RequestTest extends TestCase $request->acceptableLanguages = ['en-us', 'de']; $this->assertEquals('pl', $request->getPreferredLanguage(['pl', 'ru-ru'])); } +<<<<<<< HEAD +======= + + public function testCsrfTokenValidation() + { + $this->mockWebApplication(); + + $request = new Request(); + $request->enableCsrfCookie = false; + + $token = $request->getCsrfToken(); + + $this->assertTrue($request->validateCsrfToken($token)); + } + + public function testResolve() + { + $this->mockWebApplication([ + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'cache' => null, + 'rules' => [ + 'posts' => 'post/list', + 'post/' => 'post/view', + ], + ] + ] + ]); + + $request = new Request(); + $request->pathInfo = 'posts'; + + $_GET['page'] = 1; + $result = $request->resolve(); + $this->assertEquals(['post/list', ['page' => 1]], $result); + $this->assertEquals($_GET, ['page' => 1]); + + $request->setQueryParams(['page' => 5]); + $result = $request->resolve(); + $this->assertEquals(['post/list', ['page' => 5]], $result); + $this->assertEquals($_GET, ['page' => 1]); + + $request->setQueryParams(['custom-page' => 5]); + $result = $request->resolve(); + $this->assertEquals(['post/list', ['custom-page' => 5]], $result); + $this->assertEquals($_GET, ['page' => 1]); + + unset($_GET['page']); + + $request = new Request(); + $request->pathInfo = 'post/21'; + + $this->assertEquals($_GET, []); + $result = $request->resolve(); + $this->assertEquals(['post/view', ['id' => 21]], $result); + $this->assertEquals($_GET, ['id' => 21]); + + $_GET['id'] = 42; + $result = $request->resolve(); + $this->assertEquals(['post/view', ['id' => 21]], $result); + $this->assertEquals($_GET, ['id' => 21]); + + $_GET['id'] = 63; + $request->setQueryParams(['token' => 'secret']); + $result = $request->resolve(); + $this->assertEquals(['post/view', ['id' => 21, 'token' => 'secret']], $result); + $this->assertEquals($_GET, ['id' => 63]); + } + + public function testGetHostInfo() + { + $request = new Request(); + + unset($_SERVER['SERVER_NAME'], $_SERVER['HTTP_HOST']); + $this->assertEquals(null, $request->getHostInfo()); + + $request->setHostInfo('http://servername.com:80'); + $this->assertEquals('http://servername.com:80', $request->getHostInfo()); + } + + /** + * @expectedException \yii\base\InvalidConfigException + */ + public function testGetScriptFileWithEmptyServer() + { + $request = new Request(); + $_SERVER = []; + + $request->getScriptFile(); + } + + /** + * @expectedException \yii\base\InvalidConfigException + */ + public function testGetScriptUrlWithEmptyServer() + { + $request = new Request(); + $_SERVER = []; + + $request->getScriptUrl(); + } + + public function testGetServerName() + { + $request = new Request(); + + $_SERVER['SERVER_NAME'] = 'servername'; + $this->assertEquals('servername', $request->getServerName()); + + unset($_SERVER['SERVER_NAME']); + $this->assertEquals(null, $request->getServerName()); + } + + public function testGetServerPort() + { + $request = new Request(); + + $_SERVER['SERVER_PORT'] = 33; + $this->assertEquals(33, $request->getServerPort()); + + unset($_SERVER['SERVER_PORT']); + $this->assertEquals(null, $request->getServerPort()); + } +>>>>>>> master } diff --git a/tests/framework/web/SessionTest.php b/tests/framework/web/SessionTest.php new file mode 100644 index 0000000000..370d1aa2d9 --- /dev/null +++ b/tests/framework/web/SessionTest.php @@ -0,0 +1,30 @@ +open(); + $oldSessionId = @session_id(); + + $this->assertNotEmpty($oldSessionId); + + $session->destroy(); + + $newSessionId = @session_id(); + $this->assertNotEmpty($newSessionId); + $this->assertEquals($oldSessionId, $newSessionId); + } +} diff --git a/tests/framework/web/UrlManagerTest.php b/tests/framework/web/UrlManagerTest.php index 4a2097db9e..922a2a23cb 100644 --- a/tests/framework/web/UrlManagerTest.php +++ b/tests/framework/web/UrlManagerTest.php @@ -64,7 +64,29 @@ class UrlManagerTest extends TestCase $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); - // todo: test showScriptName + // test showScriptName + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'baseUrl' => '/test', + 'scriptUrl' => '/test/index.php', + 'showScriptName' => true, + 'cache' => null, + ]); + $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); + $url = $manager->createUrl(['/post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'baseUrl' => '/test', + 'scriptUrl' => '/test/index.php', + 'showScriptName' => false, + 'cache' => null, + ]); + $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); + $url = $manager->createUrl(['/post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); // pretty URL with rules $manager = new UrlManager([ @@ -125,6 +147,47 @@ class UrlManagerTest extends TestCase $this->assertEquals('http://en.example.com/test/post/1/sample+post', $url); $url = $manager->createUrl(['post/index', 'page' => 1]); $this->assertEquals('/test/post/index?page=1', $url); + + // create url with the same route but different params/defaults + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => [ + [ + 'pattern' => '', + 'route' => 'frontend/page/view', + 'defaults' => ['slug' => 'index'], + ], + 'page/' => 'frontend/page/view', + ], + 'baseUrl' => '/test', + 'scriptUrl' => '/test', + ]); + $url = $manager->createUrl(['frontend/page/view', 'slug' => 'services']); + $this->assertEquals('/test/page/services', $url); + $url = $manager->createUrl(['frontend/page/view', 'slug' => 'index']); + $this->assertEquals('/test/', $url); + } + + /** + * @depends testCreateUrl + * @see https://github.com/yiisoft/yii2/issues/10935 + */ + public function testCreateUrlWithNullParams() + { + $manager = new UrlManager([ + 'rules' => [ + '/' => 'site/index', + '' => 'site/index', + ], + 'enablePrettyUrl' => true, + 'scriptUrl' => '/test', + + ]); + $this->assertEquals('/test/111', $manager->createUrl(['site/index', 'param1' => 111, 'param2' => null])); + $this->assertEquals('/test/123', $manager->createUrl(['site/index', 'param1' => 123, 'param2' => null])); + $this->assertEquals('/test/111/222', $manager->createUrl(['site/index', 'param1' => 111, 'param2' => 222])); + $this->assertEquals('/test/112/222', $manager->createUrl(['site/index', 'param1' => 112, 'param2' => 222])); } /** @@ -369,4 +432,67 @@ class UrlManagerTest extends TestCase unset($_SERVER['REQUEST_METHOD']); } + + /** + * Tests if hash-anchor present + * + * https://github.com/yiisoft/yii2/pull/9596 + */ + public function testHash() + { + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => [ + 'http://example.com/testPage' => 'site/test', + ], + 'hostInfo' => 'http://example.com', + 'scriptUrl' => '/index.php', + ]); + $url = $manager->createAbsoluteUrl(['site/test', '#' => 'testhash']); + $this->assertEquals('http://example.com/index.php/testPage#testhash', $url); + } + + /** + * Tests if multislashes not accepted at the end of URL if PrettyUrl is enabled + * + * @see https://github.com/yiisoft/yii2/issues/10739 + */ + public function testMultiSlashesAtTheEnd() + { + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + ]); + + $request = new Request; + + $request->pathInfo = 'post/multi/slash/'; + $result = $manager->parseRequest($request); + $this->assertEquals(['post/multi/slash/', []], $result); + + $request->pathInfo = 'post/multi/slash//'; + $result = $manager->parseRequest($request); + $this->assertEquals(false, $result); + + $request->pathInfo = 'post/multi/slash////'; + $result = $manager->parseRequest($request); + $this->assertEquals(false, $result); + + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'suffix' => '/' + ]); + + $request->pathInfo = 'post/multi/slash/'; + $result = $manager->parseRequest($request); + $this->assertEquals(['post/multi/slash', []], $result); + + $request->pathInfo = 'post/multi/slash//'; + $result = $manager->parseRequest($request); + $this->assertEquals(false, $result); + + $request->pathInfo = 'post/multi/slash///////'; + $result = $manager->parseRequest($request); + $this->assertEquals(false, $result); + } } diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/framework/web/UrlManagerTest.php~HEAD similarity index 100% rename from tests/unit/framework/web/UrlManagerTest.php rename to tests/framework/web/UrlManagerTest.php~HEAD diff --git a/tests/framework/web/UrlRuleTest.php b/tests/framework/web/UrlRuleTest.php index a376437da3..cf89c9c7ec 100644 --- a/tests/framework/web/UrlRuleTest.php +++ b/tests/framework/web/UrlRuleTest.php @@ -137,6 +137,30 @@ class UrlRuleTest extends TestCase ['post/index', ['page' => 1, 'tag' => 'a'], 'post/1-a'], ], ], + [ + 'multiple params with special chars', + [ + 'pattern' => 'post///', + 'route' => 'post/index', + ], + [ + ['post/index', [], false], + ['post/index', ['page-number' => '1', 'per_page' => '25'], false], + ['post/index', ['page-number' => '1', 'per_page' => '25', 'author.login' => 'yiiuser'], 'post/1/25/yiiuser'], + ], + ], + [ + 'multiple params with leading non-letter chars', + [ + 'pattern' => 'post/<1page-number:\d+>/<-per_page:\d+>/<_author.login>', + 'route' => 'post/index', + ], + [ + ['post/index', [], false], + ['post/index', ['1page-number' => '1', '-per_page' => '25'], false], + ['post/index', ['1page-number' => '1', '-per_page' => '25', '_author.login' => 'yiiuser'], 'post/1/25/yiiuser'], + ], + ], [ 'with optional param', [ @@ -467,6 +491,30 @@ class UrlRuleTest extends TestCase ['post/1/a', false], ], ], + [ + 'multiple params with special chars', + [ + 'pattern' => 'post///', + 'route' => 'post/index', + ], + [ + ['post/1/25/yiiuser', 'post/index', ['page-number' => '1', 'per_page' => '25', 'author.login' => 'yiiuser']], + ['post/1/25', false], + ['post', false], + ], + ], + [ + 'multiple params with special chars', + [ + 'pattern' => 'post/<1page-number:\d+>/<-per_page:\d+>/<_author.login>', + 'route' => 'post/index', + ], + [ + ['post/1/25/yiiuser', 'post/index', ['1page-number' => '1', '-per_page' => '25', '_author.login' => 'yiiuser']], + ['post/1/25', false], + ['post', false], + ], + ], [ 'with optional param', [ diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/framework/web/UrlRuleTest.php~HEAD similarity index 98% rename from tests/unit/framework/web/UrlRuleTest.php rename to tests/framework/web/UrlRuleTest.php~HEAD index 59e0b0b070..a376437da3 100644 --- a/tests/unit/framework/web/UrlRuleTest.php +++ b/tests/framework/web/UrlRuleTest.php~HEAD @@ -693,6 +693,18 @@ class UrlRuleTest extends TestCase ['post/1/a', false], ], ], + [ + 'host info + defaults', // https://github.com/yiisoft/yii2/issues/6871 + [ + 'pattern' => 'http://en.example.com/', + 'route' => 'post/index', + 'defaults' => ['page' => 1], + ], + [ + ['', 'post/index', ['page' => 1]], + ['2', 'post/index', ['page' => 2]], + ], + ], ]; } } diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 9acb83b0e8..539557a2f8 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -17,10 +17,20 @@ namespace yiiunit\framework\web; use yii\base\NotSupportedException; use yii\base\Component; use yii\rbac\PhpManager; +<<<<<<< HEAD +======= +use yii\web\ForbiddenHttpException; +use yii\web\Cookie; +use yii\web\CookieCollection; +>>>>>>> master use yii\web\IdentityInterface; use yii\web\UrlManager; use yii\web\UrlRule; use yii\web\Request; +<<<<<<< HEAD +======= +use yii\web\Response; +>>>>>>> master use Yii; use yiiunit\TestCase; @@ -93,6 +103,206 @@ class UserTest extends TestCase $this->assertFalse(Yii::$app->user->can('doSomething')); } +<<<<<<< HEAD +======= + + public function testCookieCleanup() + { + global $cookiesMock; + + $cookiesMock = new CookieCollection(); + + $appConfig = [ + 'components' => [ + 'user' => [ + 'identityClass' => UserIdentity::className(), + 'enableAutoLogin' => true, + ], + 'response' => [ + 'class' => MockResponse::className(), + ], + 'request' => [ + 'class' => MockRequest::className(), + ], + ], + ]; + + $this->mockWebApplication($appConfig); + Yii::$app->session->removeAll(); + + $cookie = new Cookie(Yii::$app->user->identityCookie); + $cookie->value = 'junk'; + $cookiesMock->add($cookie); + Yii::$app->user->getIdentity(); + $this->assertTrue(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + + Yii::$app->user->login(UserIdentity::findIdentity('user1'),3600); + $this->assertFalse(Yii::$app->user->isGuest); + $this->assertSame(Yii::$app->user->id, 'user1'); + $this->assertFalse(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + + Yii::$app->user->login(UserIdentity::findIdentity('user2'),0); + $this->assertFalse(Yii::$app->user->isGuest); + $this->assertSame(Yii::$app->user->id, 'user2'); + $this->assertTrue(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + } + + /** + * Resets request, response and $_SERVER. + */ + protected function reset() + { + static $server; + + if (!isset($server)) { + $server = $_SERVER; + } + + $_SERVER = $server; + Yii::$app->set('response',['class' => 'yii\web\Response']); + Yii::$app->set('request',[ + 'class' => 'yii\web\Request', + 'scriptFile' => __DIR__ .'/index.php', + 'scriptUrl' => '/index.php', + 'url' => '' + ]); + Yii::$app->user->setReturnUrl(null); + } + public function testLoginRequired() + { + $appConfig = [ + 'components' => [ + 'user' => [ + 'identityClass' => UserIdentity::className(), + ], + 'authManager' => [ + 'class' => PhpManager::className(), + 'itemFile' => '@runtime/user_test_rbac_items.php', + 'assignmentFile' => '@runtime/user_test_rbac_assignments.php', + 'ruleFile' => '@runtime/user_test_rbac_rules.php', + ] + ], + ]; + $this->mockWebApplication($appConfig); + + + $user = Yii::$app->user; + + $this->reset(); + Yii::$app->request->setUrl('normal'); + $user->loginRequired(); + $this->assertEquals('normal', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + + $this->reset(); + Yii::$app->request->setUrl('ajax'); + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + + $user->loginRequired(); + $this->assertEquals(Yii::$app->getHomeUrl(), $user->getReturnUrl()); + // AJAX requests don't update returnUrl but they do cause redirection. + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $user->loginRequired(false); + $this->assertEquals('ajax', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('json-only'); + $_SERVER['HTTP_ACCEPT'] = 'Accept: text/json, q=0.1'; + $user->loginRequired(true, false); + $this->assertEquals('json-only', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('json-only'); + $_SERVER['HTTP_ACCEPT'] = 'text/json,q=0.1'; + $user->loginRequired(true, false); + $this->assertEquals('json-only', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('accept-all'); + $_SERVER['HTTP_ACCEPT'] = '*/*;q=0.1'; + $user->loginRequired(); + $this->assertEquals('accept-all', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('json-and-accept-all'); + $_SERVER['HTTP_ACCEPT'] = 'text/json, */*; q=0.1'; + try { + $user->loginRequired(); + } catch (ForbiddenHttpException $e) {} + $this->assertFalse(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('accept-html-json'); + $_SERVER['HTTP_ACCEPT'] = 'text/json; q=1, text/html; q=0.1'; + $user->loginRequired(); + $this->assertEquals('accept-html-json', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + Yii::$app->request->setUrl('accept-html-json'); + $_SERVER['HTTP_ACCEPT'] = 'text/json;q=1,application/xhtml+xml;q=0.1'; + $user->loginRequired(); + $this->assertEquals('accept-html-json', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); + $_SERVER['REQUEST_METHOD'] = 'POST'; + Yii::$app->request->setUrl('dont-set-return-url-on-post-request'); + Yii::$app->getSession()->set($user->returnUrlParam, null); + $user->loginRequired(); + $this->assertNull(Yii::$app->getSession()->get($user->returnUrlParam)); + + $this->reset(); + $_SERVER['REQUEST_METHOD'] = 'GET'; + Yii::$app->request->setUrl('set-return-url-on-get-request'); + Yii::$app->getSession()->set($user->returnUrlParam, null); + $user->loginRequired(); + $this->assertEquals('set-return-url-on-get-request', Yii::$app->getSession()->get($user->returnUrlParam)); + + // Confirm that returnUrl is not set. + $this->reset(); + Yii::$app->request->setUrl('json-only'); + $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; + try { + $user->loginRequired(); + } catch (ForbiddenHttpException $e) {} + $this->assertNotEquals('json-only', $user->getReturnUrl()); + + $this->reset(); + $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; + $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + $user->loginRequired(); + } + + public function testLoginRequiredException1() + { + $appConfig = [ + 'components' => [ + 'user' => [ + 'identityClass' => UserIdentity::className(), + ], + 'authManager' => [ + 'class' => PhpManager::className(), + 'itemFile' => '@runtime/user_test_rbac_items.php', + 'assignmentFile' => '@runtime/user_test_rbac_assignments.php', + 'ruleFile' => '@runtime/user_test_rbac_rules.php', + ] + ], + ]; + + $this->mockWebApplication($appConfig); + $this->reset(); + $_SERVER['HTTP_ACCEPT'] = 'text/json,q=0.1'; + $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + Yii::$app->user->loginRequired(); + } +>>>>>>> master } @@ -127,11 +337,43 @@ class UserIdentity extends Component implements IdentityInterface public function getAuthKey() { +<<<<<<< HEAD throw new NotSupportedException(); +======= + return 'ABCD1234'; +>>>>>>> master } public function validateAuthKey($authKey) { +<<<<<<< HEAD throw new NotSupportedException(); } -} \ No newline at end of file +} +======= + return $authKey === 'ABCD1234'; + } +} + +static $cookiesMock; + +class MockRequest extends \yii\web\Request +{ + public function getCookies() + { + global $cookiesMock; + + return $cookiesMock; + } +} + +class MockResponse extends \yii\web\Response +{ + public function getCookies() + { + global $cookiesMock; + + return $cookiesMock; + } +} +>>>>>>> master diff --git a/tests/framework/web/XmlResponseFormatterTest.php b/tests/framework/web/XmlResponseFormatterTest.php index d98da3dd7c..359d1ef67d 100644 --- a/tests/framework/web/XmlResponseFormatterTest.php +++ b/tests/framework/web/XmlResponseFormatterTest.php @@ -8,6 +8,7 @@ namespace yiiunit\framework\web; use yii\web\XmlResponseFormatter; +use yiiunit\framework\web\stubs\ModelStub; /** * @author Qiang Xue @@ -69,6 +70,27 @@ class XmlResponseFormatterTest extends FormatterTest ]); } + public function formatTraversableObjectDataProvider() + { + $expectedXmlForStack = ''; + + $postsStack = new \SplStack(); + + $postsStack->push(new Post(915, 'record1')); + $expectedXmlForStack = '915record1' . + $expectedXmlForStack; + + $postsStack->push(new Post(456, 'record2')); + $expectedXmlForStack = '456record2' . + $expectedXmlForStack; + + $data = [ + [$postsStack, "$expectedXmlForStack\n"] + ]; + + return $this->addXmlHead($data); + } + public function formatObjectDataProvider() { return $this->addXmlHead([ @@ -83,4 +105,14 @@ class XmlResponseFormatterTest extends FormatterTest ], "123<>456def\n"], ]); } + + public function formatModelDataProvider() + { + return $this->addXmlHead([ + [ + new ModelStub(['id' => 123, 'title' => 'abc', 'hidden' => 'hidden']), + "123abc\n" + ] + ]); + } } diff --git a/tests/unit/framework/web/XmlResponseFormatterTest.php b/tests/framework/web/XmlResponseFormatterTest.php~HEAD similarity index 100% rename from tests/unit/framework/web/XmlResponseFormatterTest.php rename to tests/framework/web/XmlResponseFormatterTest.php~HEAD diff --git a/tests/framework/web/stubs/ModelStub.php b/tests/framework/web/stubs/ModelStub.php new file mode 100644 index 0000000000..aa3f38883e --- /dev/null +++ b/tests/framework/web/stubs/ModelStub.php @@ -0,0 +1,17 @@ + $this->id, 'title' => $this->title]; + } +} diff --git a/tests/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php index a9714c6fd7..b4de017095 100644 --- a/tests/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php @@ -16,8 +16,17 @@ use yii\web\AssetManager; */ class ActiveFieldTest extends \yiiunit\TestCase { + /** + * @var ActiveFieldExtend + */ private $activeField; + /** + * @var DynamicModel + */ private $helperModel; + /** + * @var ActiveForm + */ private $helperForm; private $attributeName = 'attributeName'; @@ -35,7 +44,8 @@ class ActiveFieldTest extends \yiiunit\TestCase $this->helperModel = new DynamicModel(['attributeName']); ob_start(); - $this->helperForm = new ActiveForm(['action' => '/something']); + $this->helperForm = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + ActiveForm::end(); ob_end_clean(); $this->activeField = new ActiveFieldExtend(true); @@ -86,6 +96,26 @@ EOD; $this->assertEqualsWithoutLE($expectedValue, $actualValue); } + /** + * @link https://github.com/yiisoft/yii2/issues/7627 + */ + public function testRenderWithCustomInputId() + { + $expectedValue = << + + + +
        + +EOD; + + $this->activeField->inputOptions['id'] = 'custom-input-id'; + + $actualValue = $this->activeField->render(); + $this->assertEqualsWithoutLE($expectedValue, $actualValue); + } + public function testBeginHasErrors() { $this->helperModel->addError($this->attributeName, "Error Message"); @@ -233,10 +263,34 @@ EOD; EOD; $this->activeField->listBox(["1" => "Item One", "2" => "Item 2"]); $this->assertEqualsWithoutLE($expectedValue, $this->activeField->parts['{input}']); + + // https://github.com/yiisoft/yii2/issues/8848 + $expectedValue = << +EOD; + $this->activeField->listBox(["value1" => "Item One", "value2" => "Item 2"], ['options' => [ + 'value1' => ['disabled' => true], + 'value2' => ['label' => 'value 2'], + ]]); + $this->assertEqualsWithoutLE($expectedValue, $this->activeField->parts['{input}']); + + $expectedValue = << +EOD; + $this->activeField->model->{$this->attributeName} = 'value2'; + $this->activeField->listBox(["value1" => "Item One", "value2" => "Item 2"], ['options' => [ + 'value1' => ['disabled' => true], + 'value2' => ['label' => 'value 2'], + ]]); + $this->assertEqualsWithoutLE($expectedValue, $this->activeField->parts['{input}']); } - - public function testGetClientOptionsReturnEmpty() { // setup: we want the real deal here! @@ -308,6 +362,45 @@ EOD; $this->assertEquals($expectedJsExpression, $actualValue['validate']->expression); } + /** + * @see https://github.com/yiisoft/yii2/issues/8779 + */ + public function testEnctype() + { + $this->activeField->fileInput(); + $this->assertEquals('multipart/form-data', $this->activeField->form->options['enctype']); + } + + /** + * @link https://github.com/yiisoft/yii2/issues/7627 + */ + public function testGetClientOptionsWithCustomInputId() + { + $this->activeField->setClientOptionsEmpty(false); + + $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); + $this->activeField->inputOptions['id'] = 'custom-input-id'; + $this->activeField->textInput(); + $actualValue = $this->activeField->getClientOptions(); + + $this->assertArraySubset([ + 'id' => 'dynamicmodel-attributename', + 'name' => $this->attributeName, + 'container' => '.field-custom-input-id', + 'input' => '#custom-input-id', + ], $actualValue); + + $this->activeField->textInput(['id' => 'custom-textinput-id']); + $actualValue = $this->activeField->getClientOptions(); + + $this->assertArraySubset([ + 'id' => 'dynamicmodel-attributename', + 'name' => $this->attributeName, + 'container' => '.field-custom-textinput-id', + 'input' => '#custom-textinput-id', + ], $actualValue); + } + /** * Helper methods */ diff --git a/tests/unit/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php~HEAD similarity index 98% rename from tests/unit/framework/widgets/ActiveFieldTest.php rename to tests/framework/widgets/ActiveFieldTest.php~HEAD index 8baf71f2e8..a9714c6fd7 100644 --- a/tests/unit/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php~HEAD @@ -21,8 +21,9 @@ class ActiveFieldTest extends \yiiunit\TestCase private $helperForm; private $attributeName = 'attributeName'; - public function setUp() + protected function setUp() { + parent::setUp(); // dirty way to have Request object not throwing exception when running testHomeLinkNull() $_SERVER['SCRIPT_FILENAME'] = "index.php"; $_SERVER['SCRIPT_NAME'] = "index.php"; @@ -85,7 +86,7 @@ EOD; $this->assertEqualsWithoutLE($expectedValue, $actualValue); } - public function testBeginHasErros() + public function testBeginHasErrors() { $this->helperModel->addError($this->attributeName, "Error Message"); @@ -95,7 +96,7 @@ EOD; $this->assertEquals($expectedValue, $actualValue); } - public function testBeginAttributeIsRequered() + public function testBeginAttributeIsRequired() { $this->helperModel->addRule($this->attributeName, 'required'); diff --git a/tests/framework/widgets/ActiveFormTest.php b/tests/framework/widgets/ActiveFormTest.php index ac3a4f232d..26e0ad51b9 100644 --- a/tests/framework/widgets/ActiveFormTest.php +++ b/tests/framework/widgets/ActiveFormTest.php @@ -25,7 +25,8 @@ class ActiveFormTest extends \yiiunit\TestCase $model = new DynamicModel(['name']); ob_start(); - $form = new ActiveForm(['action' => '/something']); + $form = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + ActiveForm::end(); ob_end_clean(); $this->assertEqualsWithoutLE(<<categories = 1; ob_start(); - $form = new ActiveForm(['action' => '/something']); + $form = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + ActiveForm::end(); ob_end_clean(); // https://github.com/yiisoft/yii2/issues/5356 @@ -74,4 +76,34 @@ EOF EOF , (string) $form->field($model, 'categories', $o)->listBox(['apple', 'banana', 'avocado'], ['multiple' => true])); } + + public function testOutputBuffering() + { + $obLevel = ob_get_level(); + ob_start(); + + $model = new DynamicModel(['name']); + + $form = ActiveForm::begin(['id' => 'someform', 'action' => '/someform', 'enableClientScript' => false]); + echo "\n" . $form->field($model, 'name') . "\n"; + ActiveForm::end(); + + $content = ob_get_clean(); + //ob_end_clean(); + + $this->assertEquals($obLevel, ob_get_level(), 'Output buffers not closed correctly.'); + + $this->assertEqualsWithoutLE(<< +
        + + + +
        +
        + +HTML +, $content); + + } } diff --git a/tests/unit/framework/widgets/ActiveFormTest.php b/tests/framework/widgets/ActiveFormTest.php~HEAD similarity index 98% rename from tests/unit/framework/widgets/ActiveFormTest.php rename to tests/framework/widgets/ActiveFormTest.php~HEAD index ae33fc7e43..ac3a4f232d 100644 --- a/tests/unit/framework/widgets/ActiveFormTest.php +++ b/tests/framework/widgets/ActiveFormTest.php~HEAD @@ -15,6 +15,7 @@ class ActiveFormTest extends \yiiunit\TestCase { protected function setUp() { + parent::setUp(); $this->mockApplication(); } diff --git a/tests/framework/widgets/BreadcrumbsTest.php b/tests/framework/widgets/BreadcrumbsTest.php index 6997e5c289..75124c1457 100644 --- a/tests/framework/widgets/BreadcrumbsTest.php +++ b/tests/framework/widgets/BreadcrumbsTest.php @@ -162,6 +162,26 @@ class BreadcrumbsTest extends \yiiunit\TestCase $this->assertEquals('
      • demo
      • ' . "\n", $result); } + public function testTag() + { + $this->breadcrumbs->homeLink = ['label' => 'home-link']; + $this->breadcrumbs->links = ['label' => 'My Home Page', 'url' => 'http://my.example.com/yii2/link/page']; + $this->breadcrumbs->itemTemplate = "{link}\n"; + $this->breadcrumbs->activeItemTemplate = "{link}\n"; + $this->breadcrumbs->tag = false; + + $expectedHtml = "home-link\n" + . "My Home Page\n" + . "http://my.example.com/yii2/link/page\n"; + + ob_start(); + $this->breadcrumbs->run(); + $actualHtml = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals($expectedHtml, $actualHtml); + } + /** * Helper methods */ diff --git a/tests/unit/framework/widgets/BreadcrumbsTest.php b/tests/framework/widgets/BreadcrumbsTest.php~HEAD similarity index 99% rename from tests/unit/framework/widgets/BreadcrumbsTest.php rename to tests/framework/widgets/BreadcrumbsTest.php~HEAD index f9d96cfaf3..6997e5c289 100644 --- a/tests/unit/framework/widgets/BreadcrumbsTest.php +++ b/tests/framework/widgets/BreadcrumbsTest.php~HEAD @@ -14,8 +14,9 @@ class BreadcrumbsTest extends \yiiunit\TestCase { private $breadcrumbs; - public function setUp() + protected function setUp() { + parent::setUp(); // dirty way to have Request object not throwing exception when running testHomeLinkNull() $_SERVER['SCRIPT_FILENAME'] = "/index.php"; $_SERVER['SCRIPT_NAME'] = "/index.php"; diff --git a/tests/framework/widgets/DetailViewTest.php b/tests/framework/widgets/DetailViewTest.php new file mode 100644 index 0000000000..6d8ede5efe --- /dev/null +++ b/tests/framework/widgets/DetailViewTest.php @@ -0,0 +1,188 @@ + + */ +namespace yiiunit\framework\widgets; +use yii\base\Arrayable; +use yii\base\ArrayableTrait; +use yii\base\DynamicModel; +use yii\base\Object; +use yii\widgets\ActiveForm; +use yii\widgets\DetailView; + +/** + * @group widgets + */ +class DetailViewTest extends \yiiunit\TestCase +{ + /** @var DetailView */ + public $detailView; + + protected function setUp() + { + parent::setUp(); + + $this->mockWebApplication(); + } + + public function testRelationAttribute() + { + $model = new ObjectMock(); + $model->id = 'model'; + $model->related = new ObjectMock(); + $model->related->id = 'related'; + + $this->detailView = new PublicDetailView([ + 'model' => $model, + 'template' => '{label}:{value}', + 'attributes' => [ + 'id', + 'related.id', + ], + ]); + + $this->assertEquals('Id:model', $this->detailView->renderAttribute($this->detailView->attributes[0], 0)); + $this->assertEquals('Related Id:related', $this->detailView->renderAttribute($this->detailView->attributes[1], 1)); + + // test null relation + $model->related = null; + + $this->detailView = new PublicDetailView([ + 'model' => $model, + 'template' => '{label}:{value}', + 'attributes' => [ + 'id', + 'related.id', + ], + ]); + + $this->assertEquals('Id:model', $this->detailView->renderAttribute($this->detailView->attributes[0], 0)); + $this->assertEquals('Related Id:(not set)', $this->detailView->renderAttribute($this->detailView->attributes[1], 1)); + } + + public function testArrayableModel() + { + $expectedValue = [ + [ + 'attribute' => 'id', + 'format' => 'text', + 'label' => 'Id', + 'value' => 1 + ], + [ + 'attribute' => 'text', + 'format' => 'text', + 'label' => 'Text', + 'value' => 'I`m arrayable' + ], + ]; + + $model = new ArrayableMock(); + $model->id = 1; + $model->text = 'I`m arrayable'; + + $this->detailView = new DetailView([ + 'model' => $model, + ]); + + $this->assertEquals($expectedValue, $this->detailView->attributes); + } + + public function testObjectModel() + { + $expectedValue = [ + [ + 'attribute' => 'id', + 'format' => 'text', + 'label' => 'Id', + 'value' => 1 + ], + [ + 'attribute' => 'text', + 'format' => 'text', + 'label' => 'Text', + 'value' => 'I`m an object' + ], + ]; + + $model = new ObjectMock(); + $model->id = 1; + $model->text = 'I`m an object'; + + $this->detailView = new DetailView([ + 'model' => $model, + ]); + + $this->assertEquals($expectedValue, $this->detailView->attributes); + } + + public function testArrayModel() + { + $expectedValue = [ + [ + 'attribute' => 'id', + 'format' => 'text', + 'label' => 'Id', + 'value' => 1 + ], + [ + 'attribute' => 'text', + 'format' => 'text', + 'label' => 'Text', + 'value' => 'I`m an array' + ], + ]; + + $model = [ + 'id' => 1, + 'text' => 'I`m an array' + ]; + + $this->detailView = new DetailView([ + 'model' => $model, + ]); + + $this->assertEquals($expectedValue, $this->detailView->attributes); + } +} + +/** + * Helper Class + */ +class ArrayableMock implements Arrayable +{ + use ArrayableTrait; + + public $id; + + public $text; +} + +/** + * Helper Class + */ +class ObjectMock extends Object +{ + public $id; + public $text; + + private $_related; + + public function getRelated() + { + return $this->_related; + } + + public function setRelated($related) + { + $this->_related = $related; + } +} + +class PublicDetailView extends DetailView +{ + public function renderAttribute($attribute, $index) + { + return parent::renderAttribute($attribute, $index); + } +} \ No newline at end of file diff --git a/tests/framework/widgets/MenuTest.php b/tests/framework/widgets/MenuTest.php index 2b578989a4..7bd5839158 100644 --- a/tests/framework/widgets/MenuTest.php +++ b/tests/framework/widgets/MenuTest.php @@ -98,6 +98,32 @@ HTML item2 HTML , $output); + + $output = Menu::widget([ + 'route' => 'test/test', + 'params' => [], + 'encodeLabels' => true, + 'options' => [ + 'tag' => false, + ], + 'items' => [ + [ + 'label' => 'item1', + 'url' => '#', + ], + [ + 'label' => 'item2', + 'url' => '#', + ], + ], + 'itemOptions' => ['tag' => false] + ]); + + $this->assertEqualsWithoutLE(<<item1 +item2 +HTML + , $output); } diff --git a/tests/unit/framework/widgets/MenuTest.php b/tests/framework/widgets/MenuTest.php~HEAD similarity index 68% rename from tests/unit/framework/widgets/MenuTest.php rename to tests/framework/widgets/MenuTest.php~HEAD index c953bbd3ba..2b578989a4 100644 --- a/tests/unit/framework/widgets/MenuTest.php +++ b/tests/framework/widgets/MenuTest.php~HEAD @@ -66,4 +66,39 @@ HTML , $output); } + + /** + * @see https://github.com/yiisoft/yii2/issues/8064 + */ + public function testTagOption() + { + $output = Menu::widget([ + 'route' => 'test/test', + 'params' => [], + 'encodeLabels' => true, + 'options' => [ + 'tag' => false, + ], + 'items' => [ + [ + 'label' => 'item1', + 'url' => '#', + 'options' => ['tag' => 'div'], + ], + [ + 'label' => 'item2', + 'url' => '#', + 'options' => ['tag' => false], + ], + ] + ]); + + $this->assertEqualsWithoutLE(<<item1 +item2 +HTML + , $output); + } + + } diff --git a/tests/runtime/assets/.gitignore b/tests/runtime/assets/.gitignore new file mode 100644 index 0000000000..f59ec20aab --- /dev/null +++ b/tests/runtime/assets/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore deleted file mode 100644 index f5f5f83867..0000000000 --- a/tests/unit/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/runtime/cache/* -/data/config.local.php \ No newline at end of file diff --git a/tests/unit/VendorTestCase.php b/tests/unit/VendorTestCase.php deleted file mode 100644 index c8f76716a0..0000000000 --- a/tests/unit/VendorTestCase.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @since 2.0 - */ -class ActiveRecord extends \yii\db\ActiveRecord -{ - public static $db; - - public static function getDb() - { - return self::$db; - } -} diff --git a/tests/unit/data/ar/Animal.php b/tests/unit/data/ar/Animal.php deleted file mode 100644 index 129588f25c..0000000000 --- a/tests/unit/data/ar/Animal.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @property integer $id - * @property string $type - */ -class Animal extends ActiveRecord -{ - - public $does; - - public static function tableName() - { - return 'animal'; - } - - public function init() - { - parent::init(); - $this->type = get_called_class(); - } - - public function getDoes() - { - return $this->does; - } - - /** - * - * @param type $row - * @return \yiiunit\data\ar\Animal - */ - public static function instantiate($row) - { - $class = $row['type']; - return new $class; - } - -} diff --git a/tests/unit/data/ar/Cat.php b/tests/unit/data/ar/Cat.php deleted file mode 100644 index 831cb8d6fb..0000000000 --- a/tests/unit/data/ar/Cat.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Cat extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'meow'; - } - -} diff --git a/tests/unit/data/ar/Category.php b/tests/unit/data/ar/Category.php deleted file mode 100644 index aa193261bb..0000000000 --- a/tests/unit/data/ar/Category.php +++ /dev/null @@ -1,43 +0,0 @@ -hasMany(Item::className(), ['category_id' => 'id']); - } - - public function getLimitedItems() - { - return $this->hasMany(Item::className(), ['category_id' => 'id']) - ->onCondition(['item.id' => [1, 2, 3]]); - } - - public function getOrderItems() - { - return $this->hasMany(OrderItem::className(), ['item_id' => 'id'])->via('items'); - } - - public function getOrders() - { - return $this->hasMany(Order::className(), ['id' => 'order_id'])->via('orderItems'); - } -} diff --git a/tests/unit/data/ar/CustomerQuery.php b/tests/unit/data/ar/CustomerQuery.php deleted file mode 100644 index 4a7b7736b2..0000000000 --- a/tests/unit/data/ar/CustomerQuery.php +++ /dev/null @@ -1,18 +0,0 @@ -andWhere('status=1'); - - return $this; - } -} diff --git a/tests/unit/data/ar/Dog.php b/tests/unit/data/ar/Dog.php deleted file mode 100644 index 9422cc5bed..0000000000 --- a/tests/unit/data/ar/Dog.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Dog extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'bark'; - } - -} diff --git a/tests/unit/data/ar/Item.php b/tests/unit/data/ar/Item.php deleted file mode 100644 index 6567567a84..0000000000 --- a/tests/unit/data/ar/Item.php +++ /dev/null @@ -1,23 +0,0 @@ -hasOne(Category::className(), ['id' => 'category_id']); - } -} diff --git a/tests/unit/data/ar/NullValues.php b/tests/unit/data/ar/NullValues.php deleted file mode 100644 index ad228999e3..0000000000 --- a/tests/unit/data/ar/NullValues.php +++ /dev/null @@ -1,20 +0,0 @@ -hasOne(Order::className(), ['id' => 'order_id']); - } - - public function getItem() - { - return $this->hasOne(Item::className(), ['id' => 'item_id']); - } -} diff --git a/tests/unit/data/ar/OrderItemWithNullFK.php b/tests/unit/data/ar/OrderItemWithNullFK.php deleted file mode 100644 index 63e6551c45..0000000000 --- a/tests/unit/data/ar/OrderItemWithNullFK.php +++ /dev/null @@ -1,20 +0,0 @@ - - */ - -namespace yiiunit\data\ar; - -/** - * Class Profile - * - * @property integer $id - * @property string $description - * - */ -class Profile extends ActiveRecord -{ - public static function tableName() - { - return 'profile'; - } -} diff --git a/tests/unit/data/ar/Type.php b/tests/unit/data/ar/Type.php deleted file mode 100644 index 7922ba5629..0000000000 --- a/tests/unit/data/ar/Type.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class ActiveRecord extends \yii\elasticsearch\ActiveRecord -{ - public static $db; - - /** - * @return \yii\elasticsearch\Connection - */ - public static function getDb() - { - return self::$db; - } - - public static function index() - { - return 'yiitest'; - } -} diff --git a/tests/unit/data/ar/elasticsearch/Animal.php b/tests/unit/data/ar/elasticsearch/Animal.php deleted file mode 100644 index 0967b0d0b5..0000000000 --- a/tests/unit/data/ar/elasticsearch/Animal.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @since 2.0 - */ -class Animal extends ActiveRecord -{ - - public $does; - - public static function primaryKey() - { - return ['id']; - } - - public static function type() - { - return 'test_animals'; - } - - public function attributes() - { - return ['id', 'type']; - } - - /** - * sets up the index for this record - * @param Command $command - */ - public static function setUpMapping($command) - { - $command->deleteMapping(static::index(), static::type()); - $command->setMapping(static::index(), static::type(), [ - static::type() => [ - "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], - "properties" => [ - "type" => ["type" => "string", "index" => "not_analyzed"] - ] - ] - ]); - } - - public function init() - { - parent::init(); - $this->type = get_called_class(); - } - - public function getDoes() - { - return $this->does; - } - - /** - * - * @param type $row - * @return \yiiunit\data\ar\elasticsearch\Animal - */ - public static function instantiate($row) - { - $class = $row['_source']['type']; - return new $class; - } - -} diff --git a/tests/unit/data/ar/elasticsearch/Cat.php b/tests/unit/data/ar/elasticsearch/Cat.php deleted file mode 100644 index d4fc39b8ff..0000000000 --- a/tests/unit/data/ar/elasticsearch/Cat.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Cat extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'meow'; - } - -} diff --git a/tests/unit/data/ar/elasticsearch/Customer.php b/tests/unit/data/ar/elasticsearch/Customer.php deleted file mode 100644 index 1ae7caa8bc..0000000000 --- a/tests/unit/data/ar/elasticsearch/Customer.php +++ /dev/null @@ -1,90 +0,0 @@ -hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('created_at'); - } - - public function getExpensiveOrders() - { - return $this->hasMany(Order::className(), ['customer_id' => 'id'])->filter(['range' => ['total' => ['gte' => 50]]])->orderBy('id'); - } - - public function getExpensiveOrdersWithNullFK() - { - return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id'])->filter(['range' => ['total' => ['gte' => 50]]])->orderBy('id'); - } - - public function getOrdersWithNullFK() - { - return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id'])->orderBy('created_at'); - } - - public function afterSave($insert, $changedAttributes) - { - ActiveRecordTest::$afterSaveInsert = $insert; - ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; - parent::afterSave($insert, $changedAttributes); - } - - /** - * sets up the index for this record - * @param Command $command - * @param boolean $statusIsBoolean - */ - public static function setUpMapping($command, $statusIsBoolean = false) - { - $command->deleteMapping(static::index(), static::type()); - $command->setMapping(static::index(), static::type(), [ - static::type() => [ - "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], - "properties" => [ - "name" => ["type" => "string", "index" => "not_analyzed"], - "email" => ["type" => "string", "index" => "not_analyzed"], - "address" => ["type" => "string", "index" => "analyzed"], - "status" => $statusIsBoolean ? ["type" => "boolean"] : ["type" => "integer"], - ] - ] - ]); - - } - - /** - * @inheritdoc - * @return CustomerQuery - */ - public static function find() - { - return new CustomerQuery(get_called_class()); - } -} diff --git a/tests/unit/data/ar/elasticsearch/CustomerQuery.php b/tests/unit/data/ar/elasticsearch/CustomerQuery.php deleted file mode 100644 index 746a2ec1a4..0000000000 --- a/tests/unit/data/ar/elasticsearch/CustomerQuery.php +++ /dev/null @@ -1,18 +0,0 @@ -andWhere(['status' => 1]); - - return $this; - } -} diff --git a/tests/unit/data/ar/elasticsearch/Dog.php b/tests/unit/data/ar/elasticsearch/Dog.php deleted file mode 100644 index 431e55c21a..0000000000 --- a/tests/unit/data/ar/elasticsearch/Dog.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Dog extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'bark'; - } - -} diff --git a/tests/unit/data/ar/elasticsearch/Item.php b/tests/unit/data/ar/elasticsearch/Item.php deleted file mode 100644 index 283ac7b361..0000000000 --- a/tests/unit/data/ar/elasticsearch/Item.php +++ /dev/null @@ -1,44 +0,0 @@ -deleteMapping(static::index(), static::type()); - $command->setMapping(static::index(), static::type(), [ - static::type() => [ - "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], - "properties" => [ - "name" => ["type" => "string", "index" => "not_analyzed"], - "category_id" => ["type" => "integer"], - ] - ] - ]); - - } -} diff --git a/tests/unit/data/ar/elasticsearch/Order.php b/tests/unit/data/ar/elasticsearch/Order.php deleted file mode 100644 index 84a4555af8..0000000000 --- a/tests/unit/data/ar/elasticsearch/Order.php +++ /dev/null @@ -1,126 +0,0 @@ -hasOne(Customer::className(), ['id' => 'customer_id']); - } - - public function getOrderItems() - { - return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); - } - - /** - * A relation to Item defined via array valued attribute - */ - public function getItemsByArrayValue() - { - return $this->hasMany(Item::className(), ['id' => 'itemsArray'])->indexBy('id'); - } - - public function getItems() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems')->orderBy('id'); - } - - public function getItemsIndexed() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems')->indexBy('id'); - } - - public function getItemsWithNullFK() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItemsWithNullFK'); - } - - public function getOrderItemsWithNullFK() - { - return $this->hasMany(OrderItemWithNullFK::className(), ['order_id' => 'id']); - } - - public function getItemsInOrder1() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems', function ($q) { - $q->orderBy(['subtotal' => SORT_ASC]); - })->orderBy('name'); - } - - public function getItemsInOrder2() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems', function ($q) { - $q->orderBy(['subtotal' => SORT_DESC]); - })->orderBy('name'); - } - - public function getBooks() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems') - ->where(['category_id' => 1]); - } - - public function getBooksWithNullFK() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItemsWithNullFK') - ->where(['category_id' => 1]); - } - - public function beforeSave($insert) - { - if (parent::beforeSave($insert)) { -// $this->created_at = time(); - return true; - } else { - return false; - } - } - - /** - * sets up the index for this record - * @param Command $command - */ - public static function setUpMapping($command) - { - $command->deleteMapping(static::index(), static::type()); - $command->setMapping(static::index(), static::type(), [ - static::type() => [ - "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], - "properties" => [ - "customer_id" => ["type" => "integer"], -// "created_at" => ["type" => "string", "index" => "not_analyzed"], - "total" => ["type" => "integer"], - ] - ] - ]); - } -} diff --git a/tests/unit/data/ar/elasticsearch/OrderItem.php b/tests/unit/data/ar/elasticsearch/OrderItem.php deleted file mode 100644 index 4541559f5d..0000000000 --- a/tests/unit/data/ar/elasticsearch/OrderItem.php +++ /dev/null @@ -1,53 +0,0 @@ -hasOne(Order::className(), ['id' => 'order_id']); - } - - public function getItem() - { - return $this->hasOne(Item::className(), ['id' => 'item_id']); - } - - /** - * sets up the index for this record - * @param Command $command - */ - public static function setUpMapping($command) - { - $command->deleteMapping(static::index(), static::type()); - $command->setMapping(static::index(), static::type(), [ - static::type() => [ - "properties" => [ - "order_id" => ["type" => "integer"], - "item_id" => ["type" => "integer"], - "quantity" => ["type" => "integer"], - "subtotal" => ["type" => "integer"], - ] - ] - ]); - - } -} diff --git a/tests/unit/data/ar/elasticsearch/OrderItemWithNullFK.php b/tests/unit/data/ar/elasticsearch/OrderItemWithNullFK.php deleted file mode 100644 index 74415c748f..0000000000 --- a/tests/unit/data/ar/elasticsearch/OrderItemWithNullFK.php +++ /dev/null @@ -1,10 +0,0 @@ - - * @since 2.0 - */ -class Animal extends ActiveRecord -{ - - public $does; - - public static function collectionName() - { - return 'test_animals'; - } - - public function attributes() - { - return ['_id', 'type']; - } - - public function init() - { - parent::init(); - $this->type = get_called_class(); - } - - public function getDoes() - { - return $this->does; - } - - /** - * - * @param type $row - * @return \yiiunit\data\ar\elasticsearch\Animal - */ - public static function instantiate($row) - { - $class = $row['type']; - return new $class; - } - -} diff --git a/tests/unit/data/ar/mongodb/Cat.php b/tests/unit/data/ar/mongodb/Cat.php deleted file mode 100644 index b202cd3c65..0000000000 --- a/tests/unit/data/ar/mongodb/Cat.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Cat extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'meow'; - } - -} diff --git a/tests/unit/data/ar/mongodb/Customer.php b/tests/unit/data/ar/mongodb/Customer.php deleted file mode 100644 index a4859220a6..0000000000 --- a/tests/unit/data/ar/mongodb/Customer.php +++ /dev/null @@ -1,44 +0,0 @@ -hasMany(CustomerOrder::className(), ['customer_id' => '_id']); - } - - public function getFile() - { - return $this->hasOne(CustomerFile::className(), ['_id' => 'file_id']); - } - - /** - * @inheritdoc - * @return CustomerQuery - */ - public static function find() - { - return new CustomerQuery(get_called_class()); - } -} diff --git a/tests/unit/data/ar/mongodb/CustomerOrder.php b/tests/unit/data/ar/mongodb/CustomerOrder.php deleted file mode 100644 index c3d406d659..0000000000 --- a/tests/unit/data/ar/mongodb/CustomerOrder.php +++ /dev/null @@ -1,31 +0,0 @@ -hasOne(Customer::className(), ['_id' => 'customer_id']); - } - - public function getItems() - { - return $this->hasMany(Item::className(), ['_id' => 'item_ids']); - } -} diff --git a/tests/unit/data/ar/mongodb/CustomerQuery.php b/tests/unit/data/ar/mongodb/CustomerQuery.php deleted file mode 100644 index e7a9887614..0000000000 --- a/tests/unit/data/ar/mongodb/CustomerQuery.php +++ /dev/null @@ -1,18 +0,0 @@ -andWhere(['status' => 2]); - - return $this; - } -} diff --git a/tests/unit/data/ar/mongodb/Dog.php b/tests/unit/data/ar/mongodb/Dog.php deleted file mode 100644 index 852d970db9..0000000000 --- a/tests/unit/data/ar/mongodb/Dog.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @since 2.0 - */ -class Dog extends Animal -{ - - /** - * - * @param self $record - * @param array $row - */ - public static function populateRecord($record, $row) - { - parent::populateRecord($record, $row); - - $record->does = 'bark'; - } - -} diff --git a/tests/unit/data/ar/mongodb/Item.php b/tests/unit/data/ar/mongodb/Item.php deleted file mode 100644 index 63a21acd12..0000000000 --- a/tests/unit/data/ar/mongodb/Item.php +++ /dev/null @@ -1,20 +0,0 @@ -andWhere(['status' => 2]); - - return $this; - } -} diff --git a/tests/unit/data/ar/redis/ActiveRecord.php b/tests/unit/data/ar/redis/ActiveRecord.php deleted file mode 100644 index 0e1cc935f3..0000000000 --- a/tests/unit/data/ar/redis/ActiveRecord.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class ActiveRecord extends \yii\redis\ActiveRecord -{ - public static $db; - - public static function getDb() - { - return self::$db; - } -} diff --git a/tests/unit/data/ar/redis/Customer.php b/tests/unit/data/ar/redis/Customer.php deleted file mode 100644 index 6c850ad72b..0000000000 --- a/tests/unit/data/ar/redis/Customer.php +++ /dev/null @@ -1,72 +0,0 @@ -hasMany(Order::className(), ['customer_id' => 'id']); - } - - /** - * @return \yii\redis\ActiveQuery - */ - public function getExpensiveOrders() - { - return $this->hasMany(Order::className(), ['customer_id' => 'id'])->andWhere("tonumber(redis.call('HGET','order' .. ':a:' .. pk, 'total')) > 50"); - } - - /** - * @return \yii\redis\ActiveQuery - */ - public function getExpensiveOrdersWithNullFK() - { - return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id'])->andWhere("tonumber(redis.call('HGET','order' .. ':a:' .. pk, 'total')) > 50"); - } - - /** - * @return \yii\redis\ActiveQuery - */ - public function getOrdersWithNullFK() - { - return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id']); - } - - /** - * @inheritdoc - */ - public function afterSave($insert, $changedAttributes) - { - ActiveRecordTest::$afterSaveInsert = $insert; - ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; - parent::afterSave($insert, $changedAttributes); - } - - /** - * @inheritdoc - * @return CustomerQuery - */ - public static function find() - { - return new CustomerQuery(get_called_class()); - } -} diff --git a/tests/unit/data/ar/redis/CustomerQuery.php b/tests/unit/data/ar/redis/CustomerQuery.php deleted file mode 100644 index fc564a0536..0000000000 --- a/tests/unit/data/ar/redis/CustomerQuery.php +++ /dev/null @@ -1,18 +0,0 @@ -andWhere(['status' => 1]); - - return $this; - } -} diff --git a/tests/unit/data/ar/redis/Item.php b/tests/unit/data/ar/redis/Item.php deleted file mode 100644 index b06ba800b1..0000000000 --- a/tests/unit/data/ar/redis/Item.php +++ /dev/null @@ -1,11 +0,0 @@ -hasOne(Customer::className(), ['id' => 'customer_id']); - } - - public function getOrderItems() - { - return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); - } - - public function getItems() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems', function ($q) { - // additional query configuration - }); - } - - public function getItemsIndexed() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems')->indexBy('id'); - } - - public function getItemsWithNullFK() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItemsWithNullFK'); - } - - public function getOrderItemsWithNullFK() - { - return $this->hasMany(OrderItemWithNullFK::className(), ['order_id' => 'id']); - } - - public function getItemsInOrder1() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems', function ($q) { - $q->orderBy(['subtotal' => SORT_ASC]); - })->orderBy('name'); - } - - public function getItemsInOrder2() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems', function ($q) { - $q->orderBy(['subtotal' => SORT_DESC]); - })->orderBy('name'); - } - - public function getBooks() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItems') - ->where(['category_id' => 1]); - } - - public function getBooksWithNullFK() - { - return $this->hasMany(Item::className(), ['id' => 'item_id']) - ->via('orderItemsWithNullFK') - ->where(['category_id' => 1]); - } - - public function beforeSave($insert) - { - if (parent::beforeSave($insert)) { - $this->created_at = time(); - - return true; - } else { - return false; - } - } -} diff --git a/tests/unit/data/ar/redis/OrderItem.php b/tests/unit/data/ar/redis/OrderItem.php deleted file mode 100644 index d65643d167..0000000000 --- a/tests/unit/data/ar/redis/OrderItem.php +++ /dev/null @@ -1,26 +0,0 @@ -hasOne(Order::className(), ['id' => 'order_id']); - } - - public function getItem() - { - return $this->hasOne(Item::className(), ['id' => 'item_id']); - } -} diff --git a/tests/unit/data/ar/redis/OrderItemWithNullFK.php b/tests/unit/data/ar/redis/OrderItemWithNullFK.php deleted file mode 100644 index 1a95684a4c..0000000000 --- a/tests/unit/data/ar/redis/OrderItemWithNullFK.php +++ /dev/null @@ -1,24 +0,0 @@ - $this, - 'link' => ['id' => 'id'], - 'multiple' => false, - ]); - } -} diff --git a/tests/unit/data/ar/sphinx/ArticleIndex.php b/tests/unit/data/ar/sphinx/ArticleIndex.php deleted file mode 100644 index f8b9e7e1f1..0000000000 --- a/tests/unit/data/ar/sphinx/ArticleIndex.php +++ /dev/null @@ -1,46 +0,0 @@ -hasOne(ArticleDb::className(), ['id' => 'id']); - } - - public function getSourceCompositeLink() - { - return $this->hasOne(ArticleDb::className(), ['id' => 'id', 'author_id' => 'author_id']); - } - - public function getTags() - { - return $this->hasMany(TagDb::className(), ['id' => 'tag']); - } - - /** - * @inheritdoc - */ - public function getSnippetSource() - { - return $this->source->content; - } - - /** - * @return ArticleIndexQuery - */ - public static function find() - { - return new ArticleIndexQuery(get_called_class()); - } -} diff --git a/tests/unit/data/ar/sphinx/ArticleIndexQuery.php b/tests/unit/data/ar/sphinx/ArticleIndexQuery.php deleted file mode 100644 index 4f5b7a629e..0000000000 --- a/tests/unit/data/ar/sphinx/ArticleIndexQuery.php +++ /dev/null @@ -1,18 +0,0 @@ -andWhere('author_id=1'); - - return $this; - } -} diff --git a/tests/unit/data/ar/sphinx/ItemDb.php b/tests/unit/data/ar/sphinx/ItemDb.php deleted file mode 100644 index 386813a1fd..0000000000 --- a/tests/unit/data/ar/sphinx/ItemDb.php +++ /dev/null @@ -1,13 +0,0 @@ - 'Lennon'], - [['lastName'], 'required'], - [['underscore_style'], 'yii\captcha\CaptchaValidator'], - [['test'], 'required', 'when' => function($model) { return $model->firstName === 'cebe'; }], - ]; - } -} diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php deleted file mode 100644 index 5e801110e9..0000000000 --- a/tests/unit/data/base/Speaker.php +++ /dev/null @@ -1,45 +0,0 @@ - 'This is the custom label', - ]; - } - - public function rules() - { - return []; - } - - public function scenarios() - { - return [ - 'test' => ['firstName', 'lastName', '!underscore_style'], - ]; - } -} diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php deleted file mode 100644 index 870f9000d7..0000000000 --- a/tests/unit/data/config.php +++ /dev/null @@ -1,80 +0,0 @@ - [ - 'cubrid' => [ - 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', - 'username' => 'dba', - 'password' => '', - 'fixture' => __DIR__ . '/cubrid.sql', - ], - 'mysql' => [ - 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', - 'username' => 'travis', - 'password' => '', - 'fixture' => __DIR__ . '/mysql.sql', - ], - 'sqlite' => [ - 'dsn' => 'sqlite::memory:', - 'fixture' => __DIR__ . '/sqlite.sql', - ], - 'sqlsrv' => [ - 'dsn' => 'sqlsrv:Server=localhost;Database=test', - 'username' => '', - 'password' => '', - 'fixture' => __DIR__ . '/mssql.sql', - ], - 'pgsql' => [ - 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', - 'username' => 'postgres', - 'password' => 'postgres', - 'fixture' => __DIR__ . '/postgres.sql', - ], - 'elasticsearch' => [ - 'dsn' => 'elasticsearch://localhost:9200' - ], - 'redis' => [ - 'hostname' => 'localhost', - 'port' => 6379, - 'database' => 0, - 'password' => null, - ], - ], - 'sphinx' => [ - 'sphinx' => [ - 'dsn' => 'mysql:host=127.0.0.1;port=9306;', - 'username' => 'travis', - 'password' => '', - ], - 'db' => [ - 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', - 'username' => 'travis', - 'password' => '', - 'fixture' => __DIR__ . '/sphinx/source.sql', - ], - ], - 'mongodb' => [ - 'dsn' => 'mongodb://travis:test@localhost:27017', - 'defaultDatabaseName' => 'yii2test', - 'options' => [], - ] -]; - -if (is_file(__DIR__ . '/config.local.php')) { - include(__DIR__ . '/config.local.php'); -} - -return $config; \ No newline at end of file diff --git a/tests/unit/data/console/controllers/fixtures/FirstFixture.php b/tests/unit/data/console/controllers/fixtures/FirstFixture.php deleted file mode 100644 index d97b542dde..0000000000 --- a/tests/unit/data/console/controllers/fixtures/FirstFixture.php +++ /dev/null @@ -1,19 +0,0 @@ -generator->sentence($nbWords); - return mb_substr($sentence, 0, mb_strlen($sentence) - 1); - } - -} diff --git a/tests/unit/data/extensions/faker/templates/book.php b/tests/unit/data/extensions/faker/templates/book.php deleted file mode 100644 index 301225a407..0000000000 --- a/tests/unit/data/extensions/faker/templates/book.php +++ /dev/null @@ -1,9 +0,0 @@ - $faker->title, -]; diff --git a/tests/unit/data/extensions/faker/templates/profile.php b/tests/unit/data/extensions/faker/templates/profile.php deleted file mode 100644 index 1a7bfbf56f..0000000000 --- a/tests/unit/data/extensions/faker/templates/profile.php +++ /dev/null @@ -1,13 +0,0 @@ -getSecurity(); - -return [ - 'address' => $faker->address, - 'phone' => $faker->phoneNumber, - 'first_name' => $faker->firstName, -]; diff --git a/tests/unit/data/extensions/faker/templates/user.php b/tests/unit/data/extensions/faker/templates/user.php deleted file mode 100644 index 9795a4e093..0000000000 --- a/tests/unit/data/extensions/faker/templates/user.php +++ /dev/null @@ -1,15 +0,0 @@ -getSecurity(); - -return [ - 'username' => $faker->userName, - 'email' => $faker->email, - 'auth_key' => $security->generateRandomString(), - 'created_at' => time(), - 'updated_at' => time(), -]; diff --git a/tests/unit/data/i18n/messages/de/test.php b/tests/unit/data/i18n/messages/de/test.php deleted file mode 100644 index 6d7b903f55..0000000000 --- a/tests/unit/data/i18n/messages/de/test.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Hallo Welt!', -]; diff --git a/tests/unit/data/i18n/messages/en-US/test.php b/tests/unit/data/i18n/messages/en-US/test.php deleted file mode 100644 index d14d9ca565..0000000000 --- a/tests/unit/data/i18n/messages/en-US/test.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Der Hund rennt schell.', -]; diff --git a/tests/unit/data/i18n/test.mo b/tests/unit/data/i18n/test.mo deleted file mode 100644 index d5f94f14a3..0000000000 Binary files a/tests/unit/data/i18n/test.mo and /dev/null differ diff --git a/tests/unit/data/i18n/test.po b/tests/unit/data/i18n/test.po deleted file mode 100644 index fed95c9d6d..0000000000 --- a/tests/unit/data/i18n/test.po +++ /dev/null @@ -1,64 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: resurtm \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.5.5\n" - -msgctxt "context1" -msgid "" -"Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\n" -"aliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel " -"malesuada.\n" -"Nunc vel sapien nunc, a pretium nulla." -msgstr "" -"Олицетворение однократно. Представленный лексико-семантический анализ " -"является\n" -"психолингвистическим в своей основе, но механизм сочленений полидисперсен. " -"Впечатление\n" -"однократно. Различное расположение выбирает сюжетный механизм сочленений." - -msgctxt "context1" -msgid "String number two." -msgstr "Строка номер два." - -msgctxt "context2" -msgid "" -"The other\n" -"\n" -"context.\n" -msgstr "" -"Другой\n" -"\n" -"контекст.\n" - -msgctxt "context1" -msgid "" -"Missing\n" -"\r\t\"translation." -msgstr "" - -msgctxt "context1" -msgid "" -"Nunc vel sapien nunc, a pretium nulla.\n" -"Pellentesque habitant morbi tristique senectus et netus et malesuada fames " -"ac turpis egestas." -msgstr "Короткий перевод." - -msgid "contextless" -msgstr "" - -msgctxt "context2" -msgid "" -"test1\\ntest2\n" -"\\\n" -"test3" -msgstr "" -"тест1\\nтест2\n" -"\\\n" -"тест3" diff --git a/tests/unit/data/imagine/GothamRnd-Light.otf b/tests/unit/data/imagine/GothamRnd-Light.otf deleted file mode 100644 index 4181a78d41..0000000000 Binary files a/tests/unit/data/imagine/GothamRnd-Light.otf and /dev/null differ diff --git a/tests/unit/data/imagine/large.jpg b/tests/unit/data/imagine/large.jpg deleted file mode 100644 index 81c47e565b..0000000000 Binary files a/tests/unit/data/imagine/large.jpg and /dev/null differ diff --git a/tests/unit/data/imagine/xparent.gif b/tests/unit/data/imagine/xparent.gif deleted file mode 100644 index 2e6fd662af..0000000000 Binary files a/tests/unit/data/imagine/xparent.gif and /dev/null differ diff --git a/tests/unit/data/sphinx/source.sql b/tests/unit/data/sphinx/source.sql deleted file mode 100644 index cfa8642f02..0000000000 --- a/tests/unit/data/sphinx/source.sql +++ /dev/null @@ -1,59 +0,0 @@ -/** - * This is the MySQL database schema for creation of the test Sphinx index sources. - */ - -DROP TABLE IF EXISTS yii2_test_article; -DROP TABLE IF EXISTS yii2_test_item; -DROP TABLE IF EXISTS yii2_test_tag; -DROP TABLE IF EXISTS yii2_test_article_tag; - -CREATE TABLE IF NOT EXISTS `yii2_test_article` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `title` varchar(255) NOT NULL, - `content` text NOT NULL, - `author_id` int(11) NOT NULL, - `create_date` datetime NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ; - -CREATE TABLE IF NOT EXISTS `yii2_test_item` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `description` text NOT NULL, - `category_id` int(11) NOT NULL, - `price` float NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3; - -CREATE TABLE IF NOT EXISTS `yii2_test_tag` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=5; - -CREATE TABLE IF NOT EXISTS `yii2_test_article_tag` ( - `article_id` int(11) NOT NULL, - `tag_id` int(11) NOT NULL, - PRIMARY KEY (`article_id`,`tag_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -INSERT INTO `yii2_test_article` (`id`, `title`, `content`, `author_id`, `create_date`) VALUES -(1, 'About cats', 'This article is about cats', 1, '2013-10-23 00:00:00'), -(2, 'About dogs', 'This article is about dogs', 2, '2013-11-15 00:00:00'); - -INSERT INTO `yii2_test_item` (`id`, `name`, `description`, `category_id`, `price`) VALUES -(1, 'pencil', 'Simple pencil', 1, 2.5), -(2, 'table', 'Wooden table', 2, 100); - -INSERT INTO `yii2_test_tag` (`id`, `name`) VALUES -(1, 'tag1'), -(2, 'tag2'), -(3, 'tag3'), -(4, 'tag4'); - -INSERT INTO `yii2_test_article_tag` (`article_id`, `tag_id`) VALUES -(1, 1), -(1, 2), -(1, 3), -(2, 3), -(2, 4); \ No newline at end of file diff --git a/tests/unit/data/sphinx/sphinx.conf b/tests/unit/data/sphinx/sphinx.conf deleted file mode 100644 index 583bcc5d8e..0000000000 --- a/tests/unit/data/sphinx/sphinx.conf +++ /dev/null @@ -1,132 +0,0 @@ -# Sphinx configuration for the unit tests -# -# Setup test environment: -# - initialize test database source: -# mysql -D yiitest -u test < /path/to/yii/tests/unit/data/sphinx/source.sql -# - setup test Sphinx indexes: -# indexer --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf --all [--rotate] -# - run the "searchd" daemon: -# searchd --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf - - -source yii2_test_article_src -{ - type = mysql - - sql_host = localhost - sql_user = travis - sql_pass = - sql_db = yiitest - sql_port = 3306 # optional, default is 3306 - - sql_query = \ - SELECT *, UNIX_TIMESTAMP(create_date) AS add_date \ - FROM yii2_test_article - - sql_attr_uint = id - sql_attr_uint = author_id - sql_attr_timestamp = add_date - sql_attr_multi = uint tag from query; SELECT article_id AS id, tag_id AS tag FROM yii2_test_article_tag - - sql_query_info = SELECT * FROM yii2_test_article WHERE id=$id -} - - -source yii2_test_item_src -{ - type = mysql - - sql_host = localhost - sql_user = travis - sql_pass = - sql_db = yiitest - sql_port = 3306 # optional, default is 3306 - - sql_query = \ - SELECT *, CURRENT_TIMESTAMP() AS add_date \ - FROM yii2_test_item \ - WHERE id <= 100 - - sql_attr_uint = id - sql_attr_uint = category_id - sql_attr_float = price - sql_attr_timestamp = add_date - - sql_query_info = SELECT * FROM yii2_test_item WHERE id=$id -} - - -source yii2_test_item_delta_src : yii2_test_item_src -{ - sql_query = \ - SELECT *, CURRENT_TIMESTAMP() AS add_date \ - FROM yii2_test_item \ - WHERE id > 100 -} - - -index yii2_test_article_index -{ - source = yii2_test_article_src - path = SPHINX_BASE_DIR/yii2_test_article - docinfo = extern - charset_type = sbcs -} - - -index yii2_test_item_index -{ - source = yii2_test_item_src - path = SPHINX_BASE_DIR/yii2_test_item - docinfo = extern - charset_type = sbcs -} - - -index yii2_test_item_delta_index : yii2_test_item_index -{ - source = yii2_test_item_delta_src - path = SPHINX_BASE_DIR/yii2_test_item_delta -} - - -index yii2_test_rt_index -{ - type = rt - path = SPHINX_BASE_DIR/yii2_test_rt - rt_field = title - rt_field = content - rt_attr_uint = type_id - rt_attr_multi = category -} - - -index yii2_test_distributed -{ - type = distributed - local = yii2_test_article_index -} - - -indexer -{ - mem_limit = 32M -} - - -searchd -{ - listen = 127.0.0.1:9312 - listen = 9306:mysql41 - log = SPHINX_BASE_DIR/searchd.log - query_log = SPHINX_BASE_DIR/query.log - read_timeout = 5 - max_children = 30 - pid_file = SPHINX_BASE_DIR/searchd.pid - max_matches = 1000 - seamless_rotate = 1 - preopen_indexes = 1 - unlink_old = 1 - workers = threads # for RT to work - binlog_path = SPHINX_BASE_DIR -} diff --git a/tests/unit/data/travis/README.md b/tests/unit/data/travis/README.md deleted file mode 100644 index f30aa12a75..0000000000 --- a/tests/unit/data/travis/README.md +++ /dev/null @@ -1,20 +0,0 @@ -This directory contains scripts for automated test runs via the [Travis CI](http://travis-ci.org) build service. They are used for the preparation of worker instances by setting up needed extensions and configuring database access. - -These scripts might be used to configure your own system for test runs. But since their primary purpose remains to support Travis in running the test cases, you would be best advised to stick to the setup notes in the tests themselves. - -The scripts are: - - - [`apc-setup.sh`](apc-setup.sh) - Installs and configures the [apc pecl extension](http://pecl.php.net/package/apc) - - [`cubrid-setup.sh`](cubrid-setup.sh) - Prepares the [CUBRID](http://www.cubrid.org/) server instance by installing the server and PHP PDO driver - - [`init-apps.sh`](init-apps.sh) - Prepare test environment for basic and advanced application - - [`memcache-setup.sh`](memcache-setup.sh) - Compiles and installs the [memcache pecl extension](http://pecl.php.net/package/memcache) - - [`mongodb-setup.sh`](mongodb-setup.sh) - Enables Mongo DB PHP extension - - [`setup-apps.sh`](setup-apps.sh) - Prepare test environment for basic and advanced application - - [`sphinx-setup.sh`](sphinx-setup.sh) - Prepares the [Sphinx](http://sphinxsearch.com/) server instances by installing the server and attaching it to MySQL \ No newline at end of file diff --git a/tests/unit/data/travis/apc-setup.sh b/tests/unit/data/travis/apc-setup.sh deleted file mode 100755 index d64e8a5398..0000000000 --- a/tests/unit/data/travis/apc-setup.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e - -if [ "$(expr "$TRAVIS_PHP_VERSION" "<" "5.5")" -eq 1 ]; then - echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini -else - echo "Not installing APC as it is not available in PHP 5.5 anymore." -fi \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-setup.sh b/tests/unit/data/travis/cubrid-setup.sh deleted file mode 100755 index 7ae97a9cf5..0000000000 --- a/tests/unit/data/travis/cubrid-setup.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/sh -e -# -# install CUBRID DBMS - -if (php --version | grep -i HipHop > /dev/null); then - echo "Skipping CUBRID on HHVM" - exit 0 -fi - -CWD=$(pwd) - -# cubrid dbms -mkdir -p cubrid/$CUBRID_VERSION -cd cubrid -if (test -f $CUBRID_VERSION-linux.x86_64.tar.gz); then - echo "CUBRID is already downloaded" -else - wget http://ftp.cubrid.org/CUBRID_Engine/$CUBRID_VERSION-linux.x86_64.tar.gz -O $CUBRID_VERSION-linux.x86_64.tar.gz -fi - - cd $CUBRID_VERSION - tar xzf ../../$CUBRID_VERSION-linux.x86_64.tar.gz - cd ../.. - - -# setting cubrid env -CUBRID=$CWD/cubrid/$CUBRID_VERSION/CUBRID -CUBRID_DATABASES=$CUBRID/databases -CUBRID_LANG=en_US - -ld_lib_path=`printenv LD_LIBRARY_PATH` -if [ "$ld_lib_path" = "" ] -then - LD_LIBRARY_PATH=$CUBRID/lib -else - LD_LIBRARY_PATH=$CUBRID/lib:$LD_LIBRARY_PATH -fi - -SHLIB_PATH=$LD_LIBRARY_PATH -LIBPATH=$LD_LIBRARY_PATH -PATH=$CUBRID/bin:$CUBRID/cubridmanager:$PATH - -export CUBRID -export CUBRID_DATABASES -export CUBRID_LANG -export LD_LIBRARY_PATH -export SHLIB_PATH -export LIBPATH -export PATH - -# start cubrid -cubrid service start -# create and start the demo db -$CUBRID/demo/make_cubrid_demo.sh -cubrid server start demodb - -echo "" -echo "Installed CUBRID $CUBRID_VERSION" -echo "" - -# cubrid pdo -install_pdo_cubrid() { - if (test "! (-f PDO_CUBRID-$CUBRID_PDO_VERSION.tgz)"); then - wget "http://pecl.php.net/get/PDO_CUBRID-$CUBRID_PDO_VERSION.tgz" -O PDO_CUBRID-$CUBRID_PDO_VERSION.tgz - fi - tar -zxf "PDO_CUBRID-$CUBRID_PDO_VERSION.tgz" - sh -c "cd PDO_CUBRID-$CUBRID_PDO_VERSION && phpize && ./configure --prefix=$CWD/cubrid/PDO_CUBRID-$CUBRID_PDO_VERSION && make" - - echo "extension=$CWD/cubrid/PDO_CUBRID-$CUBRID_PDO_VERSION/modules/pdo_cubrid.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - return $? -} - -install_pdo_cubrid > ~/pdo_cubrid.log || ( echo "=== PDO CUBRID BUILD FAILED ==="; cat ~/pdo_cubrid.log; exit 1 ) - -echo "" -echo "Installed CUBRID PDO $CUBRID_PDO_VERSION" -echo "" - -cd .. diff --git a/tests/unit/data/travis/cubrid-solo.rb b/tests/unit/data/travis/cubrid-solo.rb deleted file mode 100644 index f5f0004430..0000000000 --- a/tests/unit/data/travis/cubrid-solo.rb +++ /dev/null @@ -1,5 +0,0 @@ -file_cache_path "/tmp/chef-solo" -data_bag_path "/tmp/chef-solo/data_bags" -encrypted_data_bag_secret "/tmp/chef-solo/data_bag_key" -cookbook_path [ "/tmp/chef-solo/cookbooks" ] -role_path "/tmp/chef-solo/roles" \ No newline at end of file diff --git a/tests/unit/data/travis/init-apps.sh b/tests/unit/data/travis/init-apps.sh deleted file mode 100755 index 0ed39ee7b8..0000000000 --- a/tests/unit/data/travis/init-apps.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e - -if (php --version | grep -i HipHop > /dev/null); then - echo "skipping application init on HHVM" -else - - mysql -e 'CREATE DATABASE yii2_advanced_tests;'; - cd apps/advanced/tests/codeception/bin - php yii migrate --interactive=0 - cd ../../../../.. -fi diff --git a/tests/unit/data/travis/mongodb-setup.sh b/tests/unit/data/travis/mongodb-setup.sh deleted file mode 100755 index 13e01c2931..0000000000 --- a/tests/unit/data/travis/mongodb-setup.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -e -# -# install mongodb - -if (php --version | grep -i HipHop > /dev/null); then - echo "mongodb does not work on HHVM currently, skipping" - exit 0 -else - echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini -fi - -echo "MongoDB Server version:" -mongod --version - -echo "MongoDB PHP Extension version:" -php -i |grep mongo -4 |grep -2 Version - -# enable text search -mongo --eval 'db.adminCommand( { setParameter: true, textSearchEnabled : true})' - -cat /etc/mongodb.conf diff --git a/tests/unit/data/travis/setup-apps.sh b/tests/unit/data/travis/setup-apps.sh deleted file mode 100755 index 6c3abec77f..0000000000 --- a/tests/unit/data/travis/setup-apps.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -e - -if (php --version | grep -i HipHop > /dev/null); then - echo "skipping application setup on HHVM" -else - - # basic application: - - composer install --dev --prefer-dist -d apps/basic - cd apps/basic && sed -i "s/'cookieValidationKey' => ''/'cookieValidationKey' => 'testkey'/" config/web.php - cd tests && codecept build && cd ../../.. - - - # advanced application: - - composer install --dev --prefer-dist -d apps/advanced - cd apps/advanced && ./init --env=Development - sed -i s/root/travis/ common/config/main-local.php - sed -i "s/'cookieValidationKey' => ''/'cookieValidationKey' => 'testkey'/" frontend/config/main.php - sed -i "s/'cookieValidationKey' => ''/'cookieValidationKey' => 'testkey'/" backend/config/main.php - cd tests/codeception/backend && codecept build - cd ../common && codecept build - cd ../console && codecept build - cd ../frontend && codecept build - cd ../../../ -fi diff --git a/tests/unit/data/travis/sphinx-setup.sh b/tests/unit/data/travis/sphinx-setup.sh deleted file mode 100755 index 5f6388f626..0000000000 --- a/tests/unit/data/travis/sphinx-setup.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -e -SCRIPT=$(readlink -f "$0") -CWD=$(dirname "$SCRIPT") - -# make dir that is used in sphinx config -mkdir -p sphinx -sed -i s\~SPHINX_BASE_DIR~$PWD/sphinx~g $CWD/../sphinx/sphinx.conf - -# Setup source database -mysql -D yiitest -u travis < $CWD/../sphinx/source.sql - -# setup test Sphinx indexes: -indexer --config $CWD/../sphinx/sphinx.conf --all - -# run searchd: -searchd --config $CWD/../sphinx/sphinx.conf diff --git a/tests/unit/data/validators/TestValidator.php b/tests/unit/data/validators/TestValidator.php deleted file mode 100644 index b26a589fe0..0000000000 --- a/tests/unit/data/validators/TestValidator.php +++ /dev/null @@ -1,43 +0,0 @@ -markAttributeValidated($attribute); - if ($this->_setErrorOnValidateAttribute == true) { - $this->addError($object, $attribute, sprintf('%s##%s', $attribute, get_class($object))); - } - } - - protected function markAttributeValidated($attr, $increaseBy = 1) - { - if (!isset($this->_validatedAttributes[$attr])) { - $this->_validatedAttributes[$attr] = 1; - } else { - $this->_validatedAttributes[$attr] = $this->_validatedAttributes[$attr] + $increaseBy; - } - } - - public function countAttributeValidations($attr) - { - return isset($this->_validatedAttributes[$attr]) ? $this->_validatedAttributes[$attr] : 0; - } - - public function isAttributeValidated($attr) - { - return isset($this->_validatedAttributes[$attr]); - } - - public function enableErrorOnValidateAttribute() - { - $this->_setErrorOnValidateAttribute = true; - } -} diff --git a/tests/unit/data/validators/models/FakedValidationModel.php b/tests/unit/data/validators/models/FakedValidationModel.php deleted file mode 100644 index 6da3aa290e..0000000000 --- a/tests/unit/data/validators/models/FakedValidationModel.php +++ /dev/null @@ -1,66 +0,0 @@ - $value) { - $m->$attribute = $value; - } - - return $m; - } - - public function rules() - { - return [ - [['val_attr_a', 'val_attr_b'], 'required', 'on' => 'reqTest'], - ['val_attr_c', 'integer'], - ['attr_images', 'file', 'maxFiles' => 3, 'extensions' => ['png'], 'on' => 'validateMultipleFiles', 'checkExtensionByMimeType' => false], - ['attr_image', 'file', 'extensions' => ['png'], 'on' => 'validateFile', 'checkExtensionByMimeType' => false] - ]; - } - - public function inlineVal($attribute, $params = []) - { - return true; - } - - public function __get($name) - { - if (stripos($name, 'attr') === 0) { - return isset($this->attr[$name]) ? $this->attr[$name] : null; - } - - return parent::__get($name); - } - - public function __set($name, $value) - { - if (stripos($name, 'attr') === 0) { - $this->attr[$name] = $value; - } else { - parent::__set($name, $value); - } - } - - public function getAttributeLabel($attr) - { - return $attr; - } -} diff --git a/tests/unit/data/validators/models/ValidatorTestMainModel.php b/tests/unit/data/validators/models/ValidatorTestMainModel.php deleted file mode 100644 index 7031e3fb9b..0000000000 --- a/tests/unit/data/validators/models/ValidatorTestMainModel.php +++ /dev/null @@ -1,20 +0,0 @@ -hasMany(ValidatorTestRefModel::className(), ['ref' => 'id']); - } -} diff --git a/tests/unit/data/views/layout.php b/tests/unit/data/views/layout.php deleted file mode 100644 index 321819b760..0000000000 --- a/tests/unit/data/views/layout.php +++ /dev/null @@ -1,20 +0,0 @@ - -beginPage(); ?> - - - - Test - head(); ?> - - -beginBody(); ?> - - - -endBody(); ?> - - -endPage(); ?> diff --git a/tests/unit/data/views/rawlayout.php b/tests/unit/data/views/rawlayout.php deleted file mode 100644 index 0d4f3e0c0d..0000000000 --- a/tests/unit/data/views/rawlayout.php +++ /dev/null @@ -1,3 +0,0 @@ -beginPage(); ?>1head(); ?>2beginBody(); ?>3endBody(); ?>4endPage(); ?> diff --git a/tests/unit/data/views/simple.php b/tests/unit/data/views/simple.php deleted file mode 100644 index 25334861b3..0000000000 --- a/tests/unit/data/views/simple.php +++ /dev/null @@ -1 +0,0 @@ -This is a damn simple view file. diff --git a/tests/unit/data/web/assets/.gitignore b/tests/unit/data/web/assets/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/tests/unit/data/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/unit/data/web/data.txt b/tests/unit/data/web/data.txt deleted file mode 100644 index 8e58281ed8..0000000000 --- a/tests/unit/data/web/data.txt +++ /dev/null @@ -1 +0,0 @@ -12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=? \ No newline at end of file diff --git a/tests/unit/extensions/authclient/AuthActionTest.php b/tests/unit/extensions/authclient/AuthActionTest.php deleted file mode 100644 index 0c0f6acffb..0000000000 --- a/tests/unit/extensions/authclient/AuthActionTest.php +++ /dev/null @@ -1,67 +0,0 @@ - [ - 'user' => [ - 'identityClass' => '\yii\web\IdentityInterface' - ], - 'request' => [ - 'hostInfo' => 'http://testdomain.com', - 'scriptUrl' => '/index.php', - ], - ] - ]; - $this->mockApplication($config, '\yii\web\Application'); - } - - public function testSetGet() - { - $action = new AuthAction(null, null); - - $successUrl = 'http://test.success.url'; - $action->setSuccessUrl($successUrl); - $this->assertEquals($successUrl, $action->getSuccessUrl(), 'Unable to setup success URL!'); - - $cancelUrl = 'http://test.cancel.url'; - $action->setCancelUrl($cancelUrl); - $this->assertEquals($cancelUrl, $action->getCancelUrl(), 'Unable to setup cancel URL!'); - } - - /** - * @depends testSetGet - */ - public function testGetDefaultSuccessUrl() - { - $action = new AuthAction(null, null); - - $this->assertNotEmpty($action->getSuccessUrl(), 'Unable to get default success URL!'); - } - - /** - * @depends testSetGet - */ - public function testGetDefaultCancelUrl() - { - $action = new AuthAction(null, null); - - $this->assertNotEmpty($action->getSuccessUrl(), 'Unable to get default cancel URL!'); - } - - public function testRedirect() - { - $action = new AuthAction(null, null); - - $url = 'http://test.url'; - $response = $action->redirect($url, true); - - $this->assertContains($url, $response->content); - } -} diff --git a/tests/unit/extensions/authclient/BaseClientTest.php b/tests/unit/extensions/authclient/BaseClientTest.php deleted file mode 100644 index af1beaa633..0000000000 --- a/tests/unit/extensions/authclient/BaseClientTest.php +++ /dev/null @@ -1,156 +0,0 @@ -setId($id); - $this->assertEquals($id, $client->getId(), 'Unable to setup id!'); - - $name = 'test_name'; - $client->setName($name); - $this->assertEquals($name, $client->getName(), 'Unable to setup name!'); - - $title = 'test_title'; - $client->setTitle($title); - $this->assertEquals($title, $client->getTitle(), 'Unable to setup title!'); - - $userAttributes = [ - 'attribute1' => 'value1', - 'attribute2' => 'value2', - ]; - $client->setUserAttributes($userAttributes); - $this->assertEquals($userAttributes, $client->getUserAttributes(), 'Unable to setup user attributes!'); - - $normalizeUserAttributeMap = [ - 'name' => 'some/name', - 'email' => 'some/email', - ]; - $client->setNormalizeUserAttributeMap($normalizeUserAttributeMap); - $this->assertEquals($normalizeUserAttributeMap, $client->getNormalizeUserAttributeMap(), 'Unable to setup normalize user attribute map!'); - - $viewOptions = [ - 'option1' => 'value1', - 'option2' => 'value2', - ]; - $client->setViewOptions($viewOptions); - $this->assertEquals($viewOptions, $client->getViewOptions(), 'Unable to setup view options!'); - } - - public function testGetDefaults() - { - $client = new Client(); - - $this->assertNotEmpty($client->getName(), 'Unable to get default name!'); - $this->assertNotEmpty($client->getTitle(), 'Unable to get default title!'); - $this->assertNotNull($client->getViewOptions(), 'Unable to get default view options!'); - $this->assertNotNull($client->getNormalizeUserAttributeMap(), 'Unable to get default normalize user attribute map!'); - } - - /** - * Data provider for [[testNormalizeUserAttributes()]] - * @return array test data - */ - public function dataProviderNormalizeUserAttributes() - { - return [ - [ - [ - 'name' => 'raw/name', - 'email' => 'raw/email', - ], - [ - 'raw/name' => 'name value', - 'raw/email' => 'email value', - ], - [ - 'name' => 'name value', - 'email' => 'email value', - ], - ], - [ - [ - 'name' => function ($attributes) { - return $attributes['firstName'] . ' ' . $attributes['lastName']; - }, - ], - [ - 'firstName' => 'John', - 'lastName' => 'Smith', - ], - [ - 'name' => 'John Smith', - ], - ], - [ - [ - 'email' => ['emails', 'prime'], - ], - [ - 'emails' => [ - 'prime' => 'some@email.com' - ], - ], - [ - 'email' => 'some@email.com', - ], - ], - [ - [ - 'email' => ['emails', 0], - 'secondaryEmail' => ['emails', 1], - ], - [ - 'emails' => [ - 'some@email.com', - ], - ], - [ - 'email' => 'some@email.com', - ], - ], - [ - [ - 'name' => 'file_get_contents', - ], - [ - 'file_get_contents' => 'value', - ], - [ - 'name' => 'value', - ], - ], - ]; - } - - /** - * @dataProvider dataProviderNormalizeUserAttributes - * - * @depends testSetGet - * - * @param array $normalizeUserAttributeMap - * @param array $rawUserAttributes - * @param array $expectedNormalizedUserAttributes - */ - public function testNormalizeUserAttributes($normalizeUserAttributeMap, $rawUserAttributes, $expectedNormalizedUserAttributes) - { - $client = new Client(); - $client->setNormalizeUserAttributeMap($normalizeUserAttributeMap); - - $client->setUserAttributes($rawUserAttributes); - $normalizedUserAttributes = $client->getUserAttributes(); - - $this->assertEquals(array_merge($rawUserAttributes, $expectedNormalizedUserAttributes), $normalizedUserAttributes); - } -} - -class Client extends BaseClient -{ -} diff --git a/tests/unit/extensions/authclient/BaseOAuthTest.php b/tests/unit/extensions/authclient/BaseOAuthTest.php deleted file mode 100644 index ce293218f6..0000000000 --- a/tests/unit/extensions/authclient/BaseOAuthTest.php +++ /dev/null @@ -1,252 +0,0 @@ -getMock(BaseOAuth::className(), ['setState', 'getState', 'composeRequestCurlOptions', 'refreshAccessToken', 'apiInternal']); - $oauthClient->expects($this->any())->method('setState')->will($this->returnValue($oauthClient)); - $oauthClient->expects($this->any())->method('getState')->will($this->returnValue(null)); - - return $oauthClient; - } - - /** - * Invokes the OAuth client method even if it is protected. - * @param BaseOAuth $oauthClient OAuth client instance. - * @param string $methodName name of the method to be invoked. - * @param array $arguments method arguments. - * @return mixed method invoke result. - */ - protected function invokeOAuthClientMethod($oauthClient, $methodName, array $arguments = []) - { - $classReflection = new \ReflectionClass(get_class($oauthClient)); - $methodReflection = $classReflection->getMethod($methodName); - $methodReflection->setAccessible(true); - $result = $methodReflection->invokeArgs($oauthClient, $arguments); - $methodReflection->setAccessible(false); - - return $result; - } - - // Tests : - - public function testSetGet() - { - $oauthClient = $this->createOAuthClient(); - - $returnUrl = 'http://test.return.url'; - $oauthClient->setReturnUrl($returnUrl); - $this->assertEquals($returnUrl, $oauthClient->getReturnUrl(), 'Unable to setup return URL!'); - - $curlOptions = [ - 'option1' => 'value1', - 'option2' => 'value2', - ]; - $oauthClient->setCurlOptions($curlOptions); - $this->assertEquals($curlOptions, $oauthClient->getCurlOptions(), 'Unable to setup cURL options!'); - } - - public function testSetupComponents() - { - $oauthClient = $this->createOAuthClient(); - - $oauthToken = new OAuthToken(); - $oauthClient->setAccessToken($oauthToken); - $this->assertEquals($oauthToken, $oauthClient->getAccessToken(), 'Unable to setup token!'); - - $oauthSignatureMethod = new PlainText(); - $oauthClient->setSignatureMethod($oauthSignatureMethod); - $this->assertEquals($oauthSignatureMethod, $oauthClient->getSignatureMethod(), 'Unable to setup signature method!'); - } - - /** - * @depends testSetupComponents - */ - public function testSetupComponentsByConfig() - { - $oauthClient = $this->createOAuthClient(); - - $oauthToken = [ - 'token' => 'test_token', - 'tokenSecret' => 'test_token_secret', - ]; - $oauthClient->setAccessToken($oauthToken); - $this->assertEquals($oauthToken['token'], $oauthClient->getAccessToken()->getToken(), 'Unable to setup token as config!'); - - $oauthSignatureMethod = [ - 'class' => 'yii\authclient\signature\PlainText' - ]; - $oauthClient->setSignatureMethod($oauthSignatureMethod); - $returnedSignatureMethod = $oauthClient->getSignatureMethod(); - $this->assertEquals($oauthSignatureMethod['class'], get_class($returnedSignatureMethod), 'Unable to setup signature method as config!'); - } - - /** - * Data provider for [[testComposeUrl()]]. - * @return array test data. - */ - public function composeUrlDataProvider() - { - return [ - [ - 'http://test.url', - [ - 'param1' => 'value1', - 'param2' => 'value2', - ], - 'http://test.url?param1=value1¶m2=value2', - ], - [ - 'http://test.url?with=some', - [ - 'param1' => 'value1', - 'param2' => 'value2', - ], - 'http://test.url?with=some¶m1=value1¶m2=value2', - ], - ]; - } - - /** - * @dataProvider composeUrlDataProvider - * - * @param string $url request URL. - * @param array $params request params - * @param string $expectedUrl expected composed URL. - */ - public function testComposeUrl($url, array $params, $expectedUrl) - { - $oauthClient = $this->createOAuthClient(); - $composedUrl = $this->invokeOAuthClientMethod($oauthClient, 'composeUrl', [$url, $params]); - $this->assertEquals($expectedUrl, $composedUrl); - } - - /** - * Data provider for {@link testDetermineContentTypeByHeaders}. - * @return array test data. - */ - public function determineContentTypeByHeadersDataProvider() - { - return [ - [ - ['content_type' => 'application/json'], - 'json' - ], - [ - ['content_type' => 'application/x-www-form-urlencoded'], - 'urlencoded' - ], - [ - ['content_type' => 'application/xml'], - 'xml' - ], - [ - ['some_header' => 'some_header_value'], - 'auto' - ], - [ - ['content_type' => 'unknown'], - 'auto' - ], - ]; - } - - /** - * @dataProvider determineContentTypeByHeadersDataProvider - * - * @param array $headers request headers. - * @param string $expectedResponseType expected response type. - */ - public function testDetermineContentTypeByHeaders(array $headers, $expectedResponseType) - { - $oauthClient = $this->createOAuthClient(); - $responseType = $this->invokeOAuthClientMethod($oauthClient, 'determineContentTypeByHeaders', [$headers]); - $this->assertEquals($expectedResponseType, $responseType); - } - - /** - * Data provider for [[testDetermineContentTypeByRaw]]. - * @return array test data. - */ - public function determineContentTypeByRawDataProvider() - { - return [ - ['{name: value}', 'json'], - ['name=value', 'urlencoded'], - ['name1=value1&name2=value2', 'urlencoded'], - ['Value', 'xml'], - ['Value', 'xml'], - ]; - } - - /** - * @dataProvider determineContentTypeByRawDataProvider - * - * @param string $rawResponse raw response content. - * @param string $expectedResponseType expected response type. - */ - public function testDetermineContentTypeByRaw($rawResponse, $expectedResponseType) - { - $oauthClient = $this->createOAuthClient(); - $responseType = $this->invokeOAuthClientMethod($oauthClient, 'determineContentTypeByRaw', [$rawResponse]); - $this->assertEquals($expectedResponseType, $responseType); - } - - /** - * Data provider for [[testApiUrl]]. - * @return array test data. - */ - public function apiUrlDataProvider() - { - return [ - [ - 'http://api.base.url', - 'sub/url', - 'http://api.base.url/sub/url', - ], - [ - 'http://api.base.url', - 'http://api.base.url/sub/url', - 'http://api.base.url/sub/url', - ], - [ - 'http://api.base.url', - 'https://api.base.url/sub/url', - 'https://api.base.url/sub/url', - ], - ]; - } - - /** - * @dataProvider apiUrlDataProvider - * - * @param $apiBaseUrl - * @param $apiSubUrl - * @param $expectedApiFullUrl - */ - public function testApiUrl($apiBaseUrl, $apiSubUrl, $expectedApiFullUrl) - { - $oauthClient = $this->createOAuthClient(); - $oauthClient->expects($this->any())->method('apiInternal')->will($this->returnArgument(1)); - - $accessToken = new OAuthToken(); - $accessToken->setToken('test_access_token'); - $accessToken->setExpireDuration(1000); - $oauthClient->setAccessToken($accessToken); - - $oauthClient->apiBaseUrl = $apiBaseUrl; - - $this->assertEquals($expectedApiFullUrl, $oauthClient->api($apiSubUrl)); - } -} diff --git a/tests/unit/extensions/authclient/CollectionTest.php b/tests/unit/extensions/authclient/CollectionTest.php deleted file mode 100644 index 0aa70c16c6..0000000000 --- a/tests/unit/extensions/authclient/CollectionTest.php +++ /dev/null @@ -1,84 +0,0 @@ - new TestClient(), - 'testClient2' => new TestClient(), - ]; - $collection->setClients($clients); - $this->assertEquals($clients, $collection->getClients(), 'Unable to setup clients!'); - } - - /** - * @depends testSetGet - */ - public function testGetProviderById() - { - $collection = new Collection(); - - $clientId = 'testClientId'; - $client = new TestClient(); - $clients = [ - $clientId => $client - ]; - $collection->setClients($clients); - - $this->assertEquals($client, $collection->getClient($clientId), 'Unable to get client by id!'); - } - - /** - * @depends testGetProviderById - */ - public function testCreateProvider() - { - $collection = new Collection(); - - $clientId = 'testClientId'; - $clientClassName = TestClient::className(); - $clients = [ - $clientId => [ - 'class' => $clientClassName - ] - ]; - $collection->setClients($clients); - - $provider = $collection->getClient($clientId); - $this->assertTrue(is_object($provider), 'Unable to create client by config!'); - $this->assertTrue(is_a($provider, $clientClassName), 'Client has wrong class name!'); - } - - /** - * @depends testSetGet - */ - public function testHasProvider() - { - $collection = new Collection(); - - $clientName = 'testClientName'; - $clients = [ - $clientName => [ - 'class' => 'TestClient1' - ], - ]; - $collection->setClients($clients); - - $this->assertTrue($collection->hasClient($clientName), 'Existing client check fails!'); - $this->assertFalse($collection->hasClient('unExistingClientName'), 'Not existing client check fails!'); - } -} - -class TestClient extends BaseClient -{ -} diff --git a/tests/unit/extensions/authclient/OAuth1Test.php b/tests/unit/extensions/authclient/OAuth1Test.php deleted file mode 100644 index 0a862adbb8..0000000000 --- a/tests/unit/extensions/authclient/OAuth1Test.php +++ /dev/null @@ -1,119 +0,0 @@ - [ - 'request' => [ - 'hostInfo' => 'http://testdomain.com', - 'scriptUrl' => '/index.php', - ], - ] - ]; - $this->mockApplication($config, '\yii\web\Application'); - } - - /** - * Invokes the OAuth client method even if it is protected. - * @param OAuth1 $oauthClient OAuth client instance. - * @param string $methodName name of the method to be invoked. - * @param array $arguments method arguments. - * @return mixed method invoke result. - */ - protected function invokeOAuthClientMethod($oauthClient, $methodName, array $arguments = []) - { - $classReflection = new \ReflectionClass(get_class($oauthClient)); - $methodReflection = $classReflection->getMethod($methodName); - $methodReflection->setAccessible(true); - $result = $methodReflection->invokeArgs($oauthClient, $arguments); - $methodReflection->setAccessible(false); - - return $result; - } - - // Tests : - - public function testSignRequest() - { - $oauthClient = new OAuth1(); - - $oauthSignatureMethod = new PlainText(); - $oauthClient->setSignatureMethod($oauthSignatureMethod); - - $signedParams = $this->invokeOAuthClientMethod($oauthClient, 'signRequest', ['GET', 'http://test.url', []]); - $this->assertNotEmpty($signedParams['oauth_signature'], 'Unable to sign request!'); - } - - /** - * Data provider for [[testComposeAuthorizationHeader()]]. - * @return array test data. - */ - public function composeAuthorizationHeaderDataProvider() - { - return [ - [ - '', - [ - 'oauth_test_name_1' => 'oauth_test_value_1', - 'oauth_test_name_2' => 'oauth_test_value_2', - ], - 'Authorization: OAuth oauth_test_name_1="oauth_test_value_1", oauth_test_name_2="oauth_test_value_2"' - ], - [ - 'test_realm', - [ - 'oauth_test_name_1' => 'oauth_test_value_1', - 'oauth_test_name_2' => 'oauth_test_value_2', - ], - 'Authorization: OAuth realm="test_realm", oauth_test_name_1="oauth_test_value_1", oauth_test_name_2="oauth_test_value_2"' - ], - [ - '', - [ - 'oauth_test_name_1' => 'oauth_test_value_1', - 'test_name_2' => 'test_value_2', - ], - 'Authorization: OAuth oauth_test_name_1="oauth_test_value_1"' - ], - ]; - } - - /** - * @dataProvider composeAuthorizationHeaderDataProvider - * - * @param string $realm authorization realm. - * @param array $params request params. - * @param string $expectedAuthorizationHeader expected authorization header. - */ - public function testComposeAuthorizationHeader($realm, array $params, $expectedAuthorizationHeader) - { - $oauthClient = new OAuth1(); - $authorizationHeader = $this->invokeOAuthClientMethod($oauthClient, 'composeAuthorizationHeader', [$params, $realm]); - $this->assertEquals($expectedAuthorizationHeader, $authorizationHeader); - } - - public function testBuildAuthUrl() - { - $oauthClient = new OAuth1(); - $authUrl = 'http://test.auth.url'; - $oauthClient->authUrl = $authUrl; - - $requestTokenToken = 'test_request_token'; - $requestToken = new OAuthToken(); - $requestToken->setToken($requestTokenToken); - - $builtAuthUrl = $oauthClient->buildAuthUrl($requestToken); - - $this->assertContains($authUrl, $builtAuthUrl, 'No auth URL present!'); - $this->assertContains($requestTokenToken, $builtAuthUrl, 'No token present!'); - } -} diff --git a/tests/unit/extensions/authclient/OAuth2Test.php b/tests/unit/extensions/authclient/OAuth2Test.php deleted file mode 100644 index 47e7b8b3b0..0000000000 --- a/tests/unit/extensions/authclient/OAuth2Test.php +++ /dev/null @@ -1,41 +0,0 @@ - [ - 'request' => [ - 'hostInfo' => 'http://testdomain.com', - 'scriptUrl' => '/index.php', - ], - ] - ]; - $this->mockApplication($config, '\yii\web\Application'); - } - - // Tests : - - public function testBuildAuthUrl() - { - $oauthClient = new OAuth2(); - $authUrl = 'http://test.auth.url'; - $oauthClient->authUrl = $authUrl; - $clientId = 'test_client_id'; - $oauthClient->clientId = $clientId; - $returnUrl = 'http://test.return.url'; - $oauthClient->setReturnUrl($returnUrl); - - $builtAuthUrl = $oauthClient->buildAuthUrl(); - - $this->assertContains($authUrl, $builtAuthUrl, 'No auth URL present!'); - $this->assertContains($clientId, $builtAuthUrl, 'No client id present!'); - $this->assertContains(rawurlencode($returnUrl), $builtAuthUrl, 'No return URL present!'); - } -} diff --git a/tests/unit/extensions/authclient/OpenIdTest.php b/tests/unit/extensions/authclient/OpenIdTest.php deleted file mode 100644 index 6e2bde0492..0000000000 --- a/tests/unit/extensions/authclient/OpenIdTest.php +++ /dev/null @@ -1,129 +0,0 @@ - [ - 'request' => [ - 'hostInfo' => 'http://testdomain.com', - 'scriptUrl' => '/index.php', - ], - ] - ]; - $this->mockApplication($config, '\yii\web\Application'); - } - - /** - * Invokes the object method even if it is protected. - * @param object $object object instance - * @param string $methodName name of the method to be invoked. - * @param array $args method arguments. - * @return mixed method invoke result. - */ - protected function invokeMethod($object, $methodName, array $args = []) - { - $classReflection = new \ReflectionClass(get_class($object)); - $methodReflection = $classReflection->getMethod($methodName); - $methodReflection->setAccessible(true); - $result = $methodReflection->invokeArgs($object, $args); - $methodReflection->setAccessible(false); - return $result; - } - - // Tests : - - public function testSetGet() - { - $client = new OpenId(); - - $trustRoot = 'http://trust.root'; - $client->setTrustRoot($trustRoot); - $this->assertEquals($trustRoot, $client->getTrustRoot(), 'Unable to setup trust root!'); - - $returnUrl = 'http://return.url'; - $client->setReturnUrl($returnUrl); - $this->assertEquals($returnUrl, $client->getReturnUrl(), 'Unable to setup return URL!'); - } - - /** - * @depends testSetGet - */ - public function testGetDefaults() - { - $client = new OpenId(); - - $this->assertNotEmpty($client->getTrustRoot(), 'Unable to get default trust root!'); - $this->assertNotEmpty($client->getReturnUrl(), 'Unable to get default return URL!'); - } - - public function testDiscover() - { - $url = 'https://www.google.com/accounts/o8/id'; - $client = new OpenId(); - $info = $client->discover($url); - $this->assertNotEmpty($info); - $this->assertNotEmpty($info['url']); - $this->assertNotEmpty($info['identity']); - $this->assertEquals(2, $info['version']); - $this->assertArrayHasKey('identifier_select', $info); - $this->assertArrayHasKey('ax', $info); - $this->assertArrayHasKey('sreg', $info); - } - - /** - * Data provider for [[testCompareUrl()]] - * @return array test data - */ - public function dataProviderCompareUrl() - { - return [ - [ - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient', - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient', - true - ], - [ - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient', - 'http://domain.com/index.php?r=site/auth&authclient=myclient', - true - ], - [ - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient', - 'http://domain.com/index.php?r=site/auth&authclient=myclient2', - false - ], - [ - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient&custom=value', - 'http://domain.com/index.php?r=site%2Fauth&custom=value&authclient=myclient', - true - ], - [ - 'https://domain.com/index.php?r=site%2Fauth&authclient=myclient', - 'http://domain.com/index.php?r=site%2Fauth&authclient=myclient', - false - ], - ]; - } - - /** - * @see https://github.com/yiisoft/yii2/issues/3633 - * - * @dataProvider dataProviderCompareUrl - * - * @param string $url1 - * @param string $url2 - * @param boolean $expectedResult - */ - public function testCompareUrl($url1, $url2, $expectedResult) - { - $client = new OpenId(); - $comparisonResult = $this->invokeMethod($client, 'compareUrl', [$url1, $url2]); - $this->assertEquals($expectedResult, $comparisonResult); - } -} diff --git a/tests/unit/extensions/authclient/TestCase.php b/tests/unit/extensions/authclient/TestCase.php deleted file mode 100644 index f08dfcc98e..0000000000 --- a/tests/unit/extensions/authclient/TestCase.php +++ /dev/null @@ -1,30 +0,0 @@ - 'test_token_param_key', - 'tokenSecretParamKey' => 'test_token_secret_param_key', - ]; - $oauthToken = new OAuthToken($config); - $this->assertTrue(is_object($oauthToken), 'Unable to create access token!'); - foreach ($config as $name => $value) { - $this->assertEquals($value, $oauthToken->$name, 'Unable to setup attributes by constructor!'); - } - $this->assertTrue($oauthToken->createTimestamp > 0, 'Unable to fill create timestamp!'); - } - - public function testSetupParams() - { - $oauthToken = new OAuthToken(); - - $params = [ - 'name_1' => 'value_1', - 'name_2' => 'value_2', - ]; - $oauthToken->setParams($params); - $this->assertEquals($params, $oauthToken->getParams(), 'Unable to setup params!'); - - $newParamName = 'new_param_name'; - $newParamValue = 'new_param_value'; - $oauthToken->setParam($newParamName, $newParamValue); - $this->assertEquals($newParamValue, $oauthToken->getParam($newParamName), 'Unable to setup param by name!'); - } - - /** - * @depends testSetupParams - */ - public function testSetupParamsShortcuts() - { - $oauthToken = new OAuthToken(); - - $token = 'test_token_value'; - $oauthToken->setToken($token); - $this->assertEquals($token, $oauthToken->getToken(), 'Unable to setup token!'); - - $tokenSecret = 'test_token_secret'; - $oauthToken->setTokenSecret($tokenSecret); - $this->assertEquals($tokenSecret, $oauthToken->getTokenSecret(), 'Unable to setup token secret!'); - - $tokenExpireDuration = rand(1000, 2000); - $oauthToken->setExpireDuration($tokenExpireDuration); - $this->assertEquals($tokenExpireDuration, $oauthToken->getExpireDuration(), 'Unable to setup expire duration!'); - } - - /** - * Data provider for {@link testAutoFetchExpireDuration}. - * @return array test data. - */ - public function autoFetchExpireDurationDataProvider() - { - return [ - [ - ['expire_in' => 123345], - 123345 - ], - [ - ['expire' => 233456], - 233456 - ], - [ - ['expiry_in' => 34567], - 34567 - ], - [ - ['expiry' => 45678], - 45678 - ], - ]; - } - - /** - * @depends testSetupParamsShortcuts - * @dataProvider autoFetchExpireDurationDataProvider - * - * @param array $params - * @param $expectedExpireDuration - */ - public function testAutoFetchExpireDuration(array $params, $expectedExpireDuration) - { - $oauthToken = new OAuthToken(); - $oauthToken->setParams($params); - $this->assertEquals($expectedExpireDuration, $oauthToken->getExpireDuration()); - } - - /** - * @depends testSetupParamsShortcuts - */ - public function testGetIsExpired() - { - $oauthToken = new OAuthToken(); - $expireDuration = 3600; - $oauthToken->setExpireDuration($expireDuration); - - $this->assertFalse($oauthToken->getIsExpired(), 'Not expired token check fails!'); - - $oauthToken->createTimestamp = $oauthToken->createTimestamp - ($expireDuration +1); - $this->assertTrue($oauthToken->getIsExpired(), 'Expired token check fails!'); - } - - /** - * @depends testGetIsExpired - */ - public function testGetIsValid() - { - $oauthToken = new OAuthToken(); - $expireDuration = 3600; - $oauthToken->setExpireDuration($expireDuration); - - $this->assertFalse($oauthToken->getIsValid(), 'Empty token is valid!'); - - $oauthToken->setToken('test_token'); - $this->assertTrue($oauthToken->getIsValid(), 'Filled up token is invalid!'); - - $oauthToken->createTimestamp = $oauthToken->createTimestamp - ($expireDuration +1); - $this->assertFalse($oauthToken->getIsValid(), 'Expired token is valid!'); - } -} diff --git a/tests/unit/extensions/authclient/signature/BaseMethodTest.php b/tests/unit/extensions/authclient/signature/BaseMethodTest.php deleted file mode 100644 index aa838ff5d7..0000000000 --- a/tests/unit/extensions/authclient/signature/BaseMethodTest.php +++ /dev/null @@ -1,51 +0,0 @@ -getMock('\yii\authclient\signature\BaseMethod', ['getName', 'generateSignature']); - $signatureMethod->expects($this->any())->method('getName')->will($this->returnValue('testMethodName')); - $signatureMethod->expects($this->any())->method('generateSignature')->will($this->returnValue('testSignature')); - - return $signatureMethod; - } - - // Tests : - - public function testGenerateSignature() - { - $signatureMethod = $this->createTestSignatureMethod(); - - $baseString = 'test_base_string'; - $key = 'test_key'; - - $signature = $signatureMethod->generateSignature($baseString, $key); - - $this->assertNotEmpty($signature, 'Unable to generate signature!'); - } - - /** - * @depends testGenerateSignature - */ - public function testVerify() - { - $signatureMethod = $this->createTestSignatureMethod(); - - $baseString = 'test_base_string'; - $key = 'test_key'; - $signature = 'unsigned'; - $this->assertFalse($signatureMethod->verify($signature, $baseString, $key), 'Unsigned signature is valid!'); - - $generatedSignature = $signatureMethod->generateSignature($baseString, $key); - $this->assertTrue($signatureMethod->verify($generatedSignature, $baseString, $key), 'Generated signature is invalid!'); - } -} diff --git a/tests/unit/extensions/authclient/signature/HmacSha1Test.php b/tests/unit/extensions/authclient/signature/HmacSha1Test.php deleted file mode 100644 index e766793d97..0000000000 --- a/tests/unit/extensions/authclient/signature/HmacSha1Test.php +++ /dev/null @@ -1,20 +0,0 @@ -generateSignature($baseString, $key); - $this->assertNotEmpty($signature, 'Unable to generate signature!'); - } -} diff --git a/tests/unit/extensions/authclient/signature/PlainTextTest.php b/tests/unit/extensions/authclient/signature/PlainTextTest.php deleted file mode 100644 index 584cff65ac..0000000000 --- a/tests/unit/extensions/authclient/signature/PlainTextTest.php +++ /dev/null @@ -1,20 +0,0 @@ -generateSignature($baseString, $key); - $this->assertNotEmpty($signature, 'Unable to generate signature!'); - } -} diff --git a/tests/unit/extensions/authclient/signature/RsaSha1Test.php b/tests/unit/extensions/authclient/signature/RsaSha1Test.php deleted file mode 100644 index 447c3cd160..0000000000 --- a/tests/unit/extensions/authclient/signature/RsaSha1Test.php +++ /dev/null @@ -1,110 +0,0 @@ -setPrivateCertificate($this->getTestPrivateCertificate()); - $signatureMethod->setPublicCertificate($this->getTestPublicCertificate()); - - $baseString = 'test_base_string'; - $key = 'test_key'; - - $signature = $signatureMethod->generateSignature($baseString, $key); - $this->assertNotEmpty($signature, 'Unable to generate signature!'); - } - - /** - * @depends testGenerateSignature - */ - public function testVerify() - { - $signatureMethod = new RsaSha1(); - $signatureMethod->setPrivateCertificate($this->getTestPrivateCertificate()); - $signatureMethod->setPublicCertificate($this->getTestPublicCertificate()); - - $baseString = 'test_base_string'; - $key = 'test_key'; - $signature = 'unsigned'; - $this->assertFalse($signatureMethod->verify($signature, $baseString, $key), 'Unsigned signature is valid!'); - - $generatedSignature = $signatureMethod->generateSignature($baseString, $key); - $this->assertTrue($signatureMethod->verify($generatedSignature, $baseString, $key), 'Generated signature is invalid!'); - } - - public function testInitPrivateCertificate() - { - $signatureMethod = new RsaSha1(); - - $certificateFileName = __FILE__; - $signatureMethod->privateCertificateFile = $certificateFileName; - $this->assertEquals(file_get_contents($certificateFileName), $signatureMethod->getPrivateCertificate(), 'Unable to fetch private certificate from file!'); - } - - public function testInitPublicCertificate() - { - $signatureMethod = new RsaSha1(); - - $certificateFileName = __FILE__; - $signatureMethod->publicCertificateFile = $certificateFileName; - $this->assertEquals(file_get_contents($certificateFileName), $signatureMethod->getPublicCertificate(), 'Unable to fetch public certificate from file!'); - } -} diff --git a/tests/unit/extensions/bootstrap/BootstrapTestCase.php b/tests/unit/extensions/bootstrap/BootstrapTestCase.php deleted file mode 100644 index efb547fa9f..0000000000 --- a/tests/unit/extensions/bootstrap/BootstrapTestCase.php +++ /dev/null @@ -1,15 +0,0 @@ -mockWebApplication(); - } -} \ No newline at end of file diff --git a/tests/unit/extensions/bootstrap/ButtonDropdownTest.php b/tests/unit/extensions/bootstrap/ButtonDropdownTest.php deleted file mode 100644 index 28231d7c01..0000000000 --- a/tests/unit/extensions/bootstrap/ButtonDropdownTest.php +++ /dev/null @@ -1,31 +0,0 @@ - [ - 'class' => $containerClass, - ], - 'label' => 'Action', - 'dropdown' => [ - 'items' => [ - ['label' => 'DropdownA', 'url' => '/'], - ['label' => 'DropdownB', 'url' => '#'], - ], - ], - ]); - - $this->assertContains("$containerClass btn-group", $out); - } -} diff --git a/tests/unit/extensions/bootstrap/CollapseTest.php b/tests/unit/extensions/bootstrap/CollapseTest.php deleted file mode 100644 index 8f3e896bfc..0000000000 --- a/tests/unit/extensions/bootstrap/CollapseTest.php +++ /dev/null @@ -1,74 +0,0 @@ - [ - [ - 'label' => 'Collapsible Group Item #1', - 'content' => 'test content1', - ], - [ - 'label' => '

        Collapsible Group Item #2

        ', - 'content' => '

        test content2

        ', - 'contentOptions' => [ - 'class' => 'testContentOptions2' - ], - 'options' => [ - 'class' => 'testClass2', - 'id' => 'testId2' - ], - 'encode' => true - ], - [ - 'label' => '

        Collapsible Group Item #3

        ', - 'content' => '

        test content3

        ', - 'contentOptions' => [ - 'class' => 'testContentOptions3' - ], - 'options' => [ - 'class' => 'testClass3', - 'id' => 'testId3' - ], - 'encode' => false - ], - [ - 'label' => '

        Collapsible Group Item #4

        ', - 'content' => '

        test content4

        ', - ], - ] - ]); - - $this->assertEqualsWithoutLE(<< -
        -
        test content1
        -
        - -
        -

        test content3

        -
        - - - -HTML - , $output); - } -} diff --git a/tests/unit/extensions/bootstrap/DropdownTest.php b/tests/unit/extensions/bootstrap/DropdownTest.php deleted file mode 100644 index f6f30d2e38..0000000000 --- a/tests/unit/extensions/bootstrap/DropdownTest.php +++ /dev/null @@ -1,50 +0,0 @@ - [ - [ - 'label' => 'Page1', - 'content' => 'Page1', - ], - [ - 'label' => 'Dropdown1', - 'items' => [ - ['label' => 'Page2', 'content' => 'Page2'], - ['label' => 'Page3', 'content' => 'Page3'], - ] - ], - [ - 'label' => 'Dropdown2', - 'visible' => false, - 'items' => [ - ['label' => 'Page4', 'content' => 'Page4'], - ['label' => 'Page5', 'content' => 'Page5'], - ] - ] - ] - ] - ); - - $expected = << - -EXPECTED; - - $this->assertEqualsWithoutLE($expected, $out); - } -} diff --git a/tests/unit/extensions/bootstrap/NavTest.php b/tests/unit/extensions/bootstrap/NavTest.php deleted file mode 100644 index 0be7117f23..0000000000 --- a/tests/unit/extensions/bootstrap/NavTest.php +++ /dev/null @@ -1,50 +0,0 @@ - [ - [ - 'label' => 'Page1', - 'content' => 'Page1', - ], - [ - 'label' => 'Dropdown1', - 'items' => [ - ['label' => 'Page2', 'content' => 'Page2'], - ['label' => 'Page3', 'content' => 'Page3'], - ] - ], - [ - 'label' => 'Dropdown2', - 'visible' => false, - 'items' => [ - ['label' => 'Page4', 'content' => 'Page4'], - ['label' => 'Page5', 'content' => 'Page5'], - ] - ] - ] - ] - ); - - $expected = <<
      • Page1
      • - -EXPECTED; - - $this->assertEqualsWithoutLE($expected, $out); - } -} diff --git a/tests/unit/extensions/bootstrap/TabsTest.php b/tests/unit/extensions/bootstrap/TabsTest.php deleted file mode 100644 index 7a86fe23a6..0000000000 --- a/tests/unit/extensions/bootstrap/TabsTest.php +++ /dev/null @@ -1,74 +0,0 @@ - [ - [ - 'label' => 'Page1', 'content' => 'Page1', - ], - [ - 'label' => 'Dropdown1', - 'items' => [ - ['label' => 'Page2', 'content' => 'Page2'], - ['label' => 'Page3', 'content' => 'Page3'], - ] - ], - [ - 'label' => 'Dropdown2', - 'items' => [ - ['label' => 'Page4', 'content' => 'Page4'], - ['label' => 'Page5', 'content' => 'Page5'], - ] - ] - ] - ]); - - $page1 = 'w0-tab0'; - $page2 = 'w0-dd1-tab0'; - $page3 = 'w0-dd1-tab1'; - $page4 = 'w0-dd2-tab0'; - $page5 = 'w0-dd2-tab1'; - - $shouldContain = [ - 'w0', // nav widget container - "#$page1", // Page1 - - 'w1', // Dropdown1 - "$page2", // Page2 - "$page3", // Page3 - - - 'w2', // Dropdown2 - "#$page4", // Page4 - "#$page5", // Page5 - - // containers - "id=\"$page1\"", - "id=\"$page2\"", - "id=\"$page3\"", - "id=\"$page4\"", - "id=\"$page5\"", - ]; - - foreach ($shouldContain as $string) { - $this->assertContains($string, $out); - } - } -} diff --git a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php deleted file mode 100644 index 4d6ec68f92..0000000000 --- a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php +++ /dev/null @@ -1,928 +0,0 @@ -getConnection()->createCommand()->flushIndex('yiitest'); - } - - public function setUp() - { - parent::setUp(); - - /* @var $db Connection */ - $db = ActiveRecord::$db = $this->getConnection(); - - // delete index - if ($db->createCommand()->indexExists('yiitest')) { - $db->createCommand()->deleteIndex('yiitest'); - } - $db->createCommand()->createIndex('yiitest'); - - $command = $db->createCommand(); - Customer::setUpMapping($command); - Item::setUpMapping($command); - Order::setUpMapping($command); - OrderItem::setUpMapping($command); - OrderWithNullFK::setUpMapping($command); - OrderItemWithNullFK::setUpMapping($command); - Animal::setUpMapping($command); - - $db->createCommand()->flushIndex('yiitest'); - - $customer = new Customer(); - $customer->id = 1; - $customer->setAttributes(['email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1], false); - $customer->save(false); - $customer = new Customer(); - $customer->id = 2; - $customer->setAttributes(['email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1], false); - $customer->save(false); - $customer = new Customer(); - $customer->id = 3; - $customer->setAttributes(['email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2], false); - $customer->save(false); - -// INSERT INTO category (name) VALUES ('Books'); -// INSERT INTO category (name) VALUES ('Movies'); - - $item = new Item(); - $item->id = 1; - $item->setAttributes(['name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1], false); - $item->save(false); - $item = new Item(); - $item->id = 2; - $item->setAttributes(['name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1], false); - $item->save(false); - $item = new Item(); - $item->id = 3; - $item->setAttributes(['name' => 'Ice Age', 'category_id' => 2], false); - $item->save(false); - $item = new Item(); - $item->id = 4; - $item->setAttributes(['name' => 'Toy Story', 'category_id' => 2], false); - $item->save(false); - $item = new Item(); - $item->id = 5; - $item->setAttributes(['name' => 'Cars', 'category_id' => 2], false); - $item->save(false); - - $order = new Order(); - $order->id = 1; - $order->setAttributes(['customer_id' => 1, 'created_at' => 1325282384, 'total' => 110.0, 'itemsArray' => [1, 2]], false); - $order->save(false); - $order = new Order(); - $order->id = 2; - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325334482, 'total' => 33.0, 'itemsArray' => [4, 5, 3]], false); - $order->save(false); - $order = new Order(); - $order->id = 3; - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325502201, 'total' => 40.0, 'itemsArray' => [2]], false); - $order->save(false); - - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); - $orderItem->save(false); - - $order = new OrderWithNullFK(); - $order->id = 1; - $order->setAttributes(['customer_id' => 1, 'created_at' => 1325282384, 'total' => 110.0], false); - $order->save(false); - $order = new OrderWithNullFK(); - $order->id = 2; - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325334482, 'total' => 33.0], false); - $order->save(false); - $order = new OrderWithNullFK(); - $order->id = 3; - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325502201, 'total' => 40.0], false); - $order->save(false); - - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); - $orderItem->save(false); - - (new Cat())->save(false); - (new Dog())->save(false); - - $db->createCommand()->flushIndex('yiitest'); - } - - public function testSaveNoChanges() - { - // this should not fail with exception - $customer = new Customer(); - // insert - $customer->save(false); - // update - $customer->save(false); - } - - public function testFindAsArray() - { - // asArray - $customer = Customer::find()->where(['id' => 2])->asArray()->one(); - $this->assertEquals([ - 'id' => 2, - 'email' => 'user2@example.com', - 'name' => 'user2', - 'address' => 'address2', - 'status' => 1, -// '_score' => 1.0 - ], $customer['_source']); - } - - public function testSearch() - { - $customers = Customer::find()->search()['hits']; - $this->assertEquals(3, $customers['total']); - $this->assertEquals(3, count($customers['hits'])); - $this->assertTrue($customers['hits'][0] instanceof Customer); - $this->assertTrue($customers['hits'][1] instanceof Customer); - $this->assertTrue($customers['hits'][2] instanceof Customer); - - // limit vs. totalcount - $customers = Customer::find()->limit(2)->search()['hits']; - $this->assertEquals(3, $customers['total']); - $this->assertEquals(2, count($customers['hits'])); - - // asArray - $result = Customer::find()->asArray()->search()['hits']; - $this->assertEquals(3, $result['total']); - $customers = $result['hits']; - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers[0]['_source']); - $this->assertArrayHasKey('name', $customers[0]['_source']); - $this->assertArrayHasKey('email', $customers[0]['_source']); - $this->assertArrayHasKey('address', $customers[0]['_source']); - $this->assertArrayHasKey('status', $customers[0]['_source']); - $this->assertArrayHasKey('id', $customers[1]['_source']); - $this->assertArrayHasKey('name', $customers[1]['_source']); - $this->assertArrayHasKey('email', $customers[1]['_source']); - $this->assertArrayHasKey('address', $customers[1]['_source']); - $this->assertArrayHasKey('status', $customers[1]['_source']); - $this->assertArrayHasKey('id', $customers[2]['_source']); - $this->assertArrayHasKey('name', $customers[2]['_source']); - $this->assertArrayHasKey('email', $customers[2]['_source']); - $this->assertArrayHasKey('address', $customers[2]['_source']); - $this->assertArrayHasKey('status', $customers[2]['_source']); - - // TODO test asArray() + fields() + indexBy() - // find by attributes - $result = Customer::find()->where(['name' => 'user2'])->search()['hits']; - $customer = reset($result['hits']); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals(2, $customer->id); - - // TODO test query() and filter() - } - - // TODO test aggregations -// public function testSearchFacets() -// { -// $result = Customer::find()->addAggregation('status_stats', ['field' => 'status'])->search(); -// $this->assertArrayHasKey('facets', $result); -// $this->assertEquals(3, $result['facets']['status_stats']['count']); -// $this->assertEquals(4, $result['facets']['status_stats']['total']); // sum of values -// $this->assertEquals(1, $result['facets']['status_stats']['min']); -// $this->assertEquals(2, $result['facets']['status_stats']['max']); -// } - - public function testGetDb() - { - $this->mockApplication(['components' => ['elasticsearch' => Connection::className()]]); - $this->assertInstanceOf(Connection::className(), ActiveRecord::getDb()); - } - - public function testGet() - { - $this->assertInstanceOf(Customer::className(), Customer::get(1)); - $this->assertNull(Customer::get(5)); - } - - public function testMget() - { - $this->assertEquals([], Customer::mget([])); - - $records = Customer::mget([1]); - $this->assertEquals(1, count($records)); - $this->assertInstanceOf(Customer::className(), reset($records)); - - $records = Customer::mget([5]); - $this->assertEquals(0, count($records)); - - $records = Customer::mget([1, 3, 5]); - $this->assertEquals(2, count($records)); - $this->assertInstanceOf(Customer::className(), $records[0]); - $this->assertInstanceOf(Customer::className(), $records[1]); - } - - public function testFindLazy() - { - /* @var $customer Customer */ - $customer = Customer::findOne(2); - $orders = $customer->orders; - $this->assertEquals(2, count($orders)); - - $orders = $customer->getOrders()->where(['between', 'created_at', 1325334000, 1325400000])->all(); - $this->assertEquals(1, count($orders)); - $this->assertEquals(2, $orders[0]->id); - } - - public function testFindEagerViaRelation() - { - $orders = Order::find()->with('items')->orderBy('created_at')->all(); - $this->assertEquals(3, count($orders)); - $order = $orders[0]; - $this->assertEquals(1, $order->id); - $this->assertTrue($order->isRelationPopulated('items')); - $this->assertEquals(2, count($order->items)); - $this->assertEquals(1, $order->items[0]->id); - $this->assertEquals(2, $order->items[1]->id); - } - - public function testInsertNoPk() - { - $this->assertEquals(['id'], Customer::primaryKey()); - $pkName = 'id'; - - $customer = new Customer; - $customer->email = 'user4@example.com'; - $customer->name = 'user4'; - $customer->address = 'address4'; - - $this->assertNull($customer->primaryKey); - $this->assertNull($customer->oldPrimaryKey); - $this->assertNull($customer->$pkName); - $this->assertTrue($customer->isNewRecord); - - $customer->save(); - $this->afterSave(); - - $this->assertNotNull($customer->primaryKey); - $this->assertNotNull($customer->oldPrimaryKey); - $this->assertNotNull($customer->$pkName); - $this->assertEquals($customer->primaryKey, $customer->oldPrimaryKey); - $this->assertEquals($customer->primaryKey, $customer->$pkName); - $this->assertFalse($customer->isNewRecord); - } - - public function testInsertPk() - { - $pkName = 'id'; - - $customer = new Customer; - $customer->$pkName = 5; - $customer->email = 'user5@example.com'; - $customer->name = 'user5'; - $customer->address = 'address5'; - - $this->assertTrue($customer->isNewRecord); - - $customer->save(); - - $this->assertEquals(5, $customer->primaryKey); - $this->assertEquals(5, $customer->oldPrimaryKey); - $this->assertEquals(5, $customer->$pkName); - $this->assertFalse($customer->isNewRecord); - } - - public function testUpdatePk() - { - $pkName = 'id'; - - $orderItem = Order::findOne([$pkName => 2]); - $this->assertEquals(2, $orderItem->primaryKey); - $this->assertEquals(2, $orderItem->oldPrimaryKey); - $this->assertEquals(2, $orderItem->$pkName); - -// $this->setExpectedException('yii\base\InvalidCallException'); - $orderItem->$pkName = 13; - $this->assertEquals(13, $orderItem->primaryKey); - $this->assertEquals(2, $orderItem->oldPrimaryKey); - $this->assertEquals(13, $orderItem->$pkName); - $orderItem->save(); - $this->afterSave(); - $this->assertEquals(13, $orderItem->primaryKey); - $this->assertEquals(13, $orderItem->oldPrimaryKey); - $this->assertEquals(13, $orderItem->$pkName); - - $this->assertNull(Order::findOne([$pkName => 2])); - $this->assertNotNull(Order::findOne([$pkName => 13])); - } - - public function testFindLazyVia2() - { - /* @var $this TestCase|ActiveRecordTestTrait */ - /* @var $order Order */ - $orderClass = $this->getOrderClass(); - $pkName = 'id'; - - $order = new $orderClass(); - $order->$pkName = 100; - $this->assertEquals([], $order->items); - } - - /** - * Some PDO implementations(e.g. cubrid) do not support boolean values. - * Make sure this does not affect AR layer. - */ - public function testBooleanAttribute() - { - $db = $this->getConnection(); - Customer::deleteAll(); - Customer::setUpMapping($db->createCommand(), true); - - $customerClass = $this->getCustomerClass(); - $customer = new $customerClass(); - $customer->name = 'boolean customer'; - $customer->email = 'mail@example.com'; - $customer->status = true; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(true, $customer->status); - - $customer->status = false; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(false, $customer->status); - - $customer = new Customer(); - $customer->setAttributes(['email' => 'user2b@example.com', 'name' => 'user2b', 'status' => true], false); - $customer->save(false); - $customer = new Customer(); - $customer->setAttributes(['email' => 'user3b@example.com', 'name' => 'user3b', 'status' => false], false); - $customer->save(false); - $this->afterSave(); - - $customers = Customer::find()->where(['status' => true])->all(); - $this->assertEquals(1, count($customers)); - - $customers = Customer::find()->where(['status' => false])->all(); - $this->assertEquals(2, count($customers)); - } - - public function testScriptFields() - { - $orderItems = OrderItem::find()->fields([ - 'quantity', - 'subtotal', - 'total' => [ - 'script' => "doc['quantity'].value * doc['subtotal'].value", - 'lang' => 'groovy', - ] - ])->all(); - $this->assertNotEmpty($orderItems); - foreach ($orderItems as $item) { - $this->assertEquals($item->subtotal * $item->quantity, $item->total); - } - } - - public function testFindAsArrayFields() - { - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy + asArray - $customers = Customer::find()->asArray()->fields(['id', 'name'])->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers[0]['fields']); - $this->assertArrayHasKey('name', $customers[0]['fields']); - $this->assertArrayNotHasKey('email', $customers[0]['fields']); - $this->assertArrayNotHasKey('address', $customers[0]['fields']); - $this->assertArrayNotHasKey('status', $customers[0]['fields']); - $this->assertArrayHasKey('id', $customers[1]['fields']); - $this->assertArrayHasKey('name', $customers[1]['fields']); - $this->assertArrayNotHasKey('email', $customers[1]['fields']); - $this->assertArrayNotHasKey('address', $customers[1]['fields']); - $this->assertArrayNotHasKey('status', $customers[1]['fields']); - $this->assertArrayHasKey('id', $customers[2]['fields']); - $this->assertArrayHasKey('name', $customers[2]['fields']); - $this->assertArrayNotHasKey('email', $customers[2]['fields']); - $this->assertArrayNotHasKey('address', $customers[2]['fields']); - $this->assertArrayNotHasKey('status', $customers[2]['fields']); - } - - public function testFindAsArraySourceFilter() - { - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy + asArray - $customers = Customer::find()->asArray()->source(['id', 'name'])->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers[0]['_source']); - $this->assertArrayHasKey('name', $customers[0]['_source']); - $this->assertArrayNotHasKey('email', $customers[0]['_source']); - $this->assertArrayNotHasKey('address', $customers[0]['_source']); - $this->assertArrayNotHasKey('status', $customers[0]['_source']); - $this->assertArrayHasKey('id', $customers[1]['_source']); - $this->assertArrayHasKey('name', $customers[1]['_source']); - $this->assertArrayNotHasKey('email', $customers[1]['_source']); - $this->assertArrayNotHasKey('address', $customers[1]['_source']); - $this->assertArrayNotHasKey('status', $customers[1]['_source']); - $this->assertArrayHasKey('id', $customers[2]['_source']); - $this->assertArrayHasKey('name', $customers[2]['_source']); - $this->assertArrayNotHasKey('email', $customers[2]['_source']); - $this->assertArrayNotHasKey('address', $customers[2]['_source']); - $this->assertArrayNotHasKey('status', $customers[2]['_source']); - } - - public function testFindIndexBySource() - { - $customerClass = $this->getCustomerClass(); - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy + asArray - $customers = Customer::find()->indexBy('name')->source('id', 'name')->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['user1'] instanceof $customerClass); - $this->assertTrue($customers['user2'] instanceof $customerClass); - $this->assertTrue($customers['user3'] instanceof $customerClass); - $this->assertNotNull($customers['user1']->id); - $this->assertNotNull($customers['user1']->name); - $this->assertNull($customers['user1']->email); - $this->assertNull($customers['user1']->address); - $this->assertNull($customers['user1']->status); - $this->assertNotNull($customers['user2']->id); - $this->assertNotNull($customers['user2']->name); - $this->assertNull($customers['user2']->email); - $this->assertNull($customers['user2']->address); - $this->assertNull($customers['user2']->status); - $this->assertNotNull($customers['user3']->id); - $this->assertNotNull($customers['user3']->name); - $this->assertNull($customers['user3']->email); - $this->assertNull($customers['user3']->address); - $this->assertNull($customers['user3']->status); - - // indexBy callable + asArray - $customers = Customer::find()->indexBy(function ($customer) { - return $customer->id . '-' . $customer->name; - })->fields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['1-user1'] instanceof $customerClass); - $this->assertTrue($customers['2-user2'] instanceof $customerClass); - $this->assertTrue($customers['3-user3'] instanceof $customerClass); - $this->assertNotNull($customers['1-user1']->id); - $this->assertNotNull($customers['1-user1']->name); - $this->assertNull($customers['1-user1']->email); - $this->assertNull($customers['1-user1']->address); - $this->assertNull($customers['1-user1']->status); - $this->assertNotNull($customers['2-user2']->id); - $this->assertNotNull($customers['2-user2']->name); - $this->assertNull($customers['2-user2']->email); - $this->assertNull($customers['2-user2']->address); - $this->assertNull($customers['2-user2']->status); - $this->assertNotNull($customers['3-user3']->id); - $this->assertNotNull($customers['3-user3']->name); - $this->assertNull($customers['3-user3']->email); - $this->assertNull($customers['3-user3']->address); - $this->assertNull($customers['3-user3']->status); - } - - public function testFindIndexByAsArrayFields() - { - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy + asArray - $customers = Customer::find()->indexBy('name')->asArray()->fields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers['user1']['fields']); - $this->assertArrayHasKey('name', $customers['user1']['fields']); - $this->assertArrayNotHasKey('email', $customers['user1']['fields']); - $this->assertArrayNotHasKey('address', $customers['user1']['fields']); - $this->assertArrayNotHasKey('status', $customers['user1']['fields']); - $this->assertArrayHasKey('id', $customers['user2']['fields']); - $this->assertArrayHasKey('name', $customers['user2']['fields']); - $this->assertArrayNotHasKey('email', $customers['user2']['fields']); - $this->assertArrayNotHasKey('address', $customers['user2']['fields']); - $this->assertArrayNotHasKey('status', $customers['user2']['fields']); - $this->assertArrayHasKey('id', $customers['user3']['fields']); - $this->assertArrayHasKey('name', $customers['user3']['fields']); - $this->assertArrayNotHasKey('email', $customers['user3']['fields']); - $this->assertArrayNotHasKey('address', $customers['user3']['fields']); - $this->assertArrayNotHasKey('status', $customers['user3']['fields']); - - // indexBy callable + asArray - $customers = Customer::find()->indexBy(function ($customer) { - return reset($customer['fields']['id']) . '-' . reset($customer['fields']['name']); - })->asArray()->fields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers['1-user1']['fields']); - $this->assertArrayHasKey('name', $customers['1-user1']['fields']); - $this->assertArrayNotHasKey('email', $customers['1-user1']['fields']); - $this->assertArrayNotHasKey('address', $customers['1-user1']['fields']); - $this->assertArrayNotHasKey('status', $customers['1-user1']['fields']); - $this->assertArrayHasKey('id', $customers['2-user2']['fields']); - $this->assertArrayHasKey('name', $customers['2-user2']['fields']); - $this->assertArrayNotHasKey('email', $customers['2-user2']['fields']); - $this->assertArrayNotHasKey('address', $customers['2-user2']['fields']); - $this->assertArrayNotHasKey('status', $customers['2-user2']['fields']); - $this->assertArrayHasKey('id', $customers['3-user3']['fields']); - $this->assertArrayHasKey('name', $customers['3-user3']['fields']); - $this->assertArrayNotHasKey('email', $customers['3-user3']['fields']); - $this->assertArrayNotHasKey('address', $customers['3-user3']['fields']); - $this->assertArrayNotHasKey('status', $customers['3-user3']['fields']); - } - - public function testFindIndexByAsArray() - { - /* @var $customerClass \yii\db\ActiveRecordInterface */ - $customerClass = $this->getCustomerClass(); - - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy + asArray - $customers = $customerClass::find()->asArray()->indexBy('name')->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers['user1']['_source']); - $this->assertArrayHasKey('name', $customers['user1']['_source']); - $this->assertArrayHasKey('email', $customers['user1']['_source']); - $this->assertArrayHasKey('address', $customers['user1']['_source']); - $this->assertArrayHasKey('status', $customers['user1']['_source']); - $this->assertArrayHasKey('id', $customers['user2']['_source']); - $this->assertArrayHasKey('name', $customers['user2']['_source']); - $this->assertArrayHasKey('email', $customers['user2']['_source']); - $this->assertArrayHasKey('address', $customers['user2']['_source']); - $this->assertArrayHasKey('status', $customers['user2']['_source']); - $this->assertArrayHasKey('id', $customers['user3']['_source']); - $this->assertArrayHasKey('name', $customers['user3']['_source']); - $this->assertArrayHasKey('email', $customers['user3']['_source']); - $this->assertArrayHasKey('address', $customers['user3']['_source']); - $this->assertArrayHasKey('status', $customers['user3']['_source']); - - // indexBy callable + asArray - $customers = $customerClass::find()->indexBy(function ($customer) { - return $customer['_source']['id'] . '-' . $customer['_source']['name']; - })->asArray()->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers['1-user1']['_source']); - $this->assertArrayHasKey('name', $customers['1-user1']['_source']); - $this->assertArrayHasKey('email', $customers['1-user1']['_source']); - $this->assertArrayHasKey('address', $customers['1-user1']['_source']); - $this->assertArrayHasKey('status', $customers['1-user1']['_source']); - $this->assertArrayHasKey('id', $customers['2-user2']['_source']); - $this->assertArrayHasKey('name', $customers['2-user2']['_source']); - $this->assertArrayHasKey('email', $customers['2-user2']['_source']); - $this->assertArrayHasKey('address', $customers['2-user2']['_source']); - $this->assertArrayHasKey('status', $customers['2-user2']['_source']); - $this->assertArrayHasKey('id', $customers['3-user3']['_source']); - $this->assertArrayHasKey('name', $customers['3-user3']['_source']); - $this->assertArrayHasKey('email', $customers['3-user3']['_source']); - $this->assertArrayHasKey('address', $customers['3-user3']['_source']); - $this->assertArrayHasKey('status', $customers['3-user3']['_source']); - } - - public function testAfterFindGet() - { - /* @var $customerClass BaseActiveRecord */ - $customerClass = $this->getCustomerClass(); - - $afterFindCalls = []; - Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND, function ($event) use (&$afterFindCalls) { - /* @var $ar BaseActiveRecord */ - $ar = $event->sender; - $afterFindCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')]; - }); - - $customer = Customer::get(1); - $this->assertNotNull($customer); - $this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls); - $afterFindCalls = []; - - $customer = Customer::mget([1, 2]); - $this->assertNotNull($customer); - $this->assertEquals([ - [$customerClass, false, 1, false], - [$customerClass, false, 2, false], - ], $afterFindCalls); - $afterFindCalls = []; - - Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND); - } - - public function testFindEmptyPkCondition() - { - /* @var $this TestCase|ActiveRecordTestTrait */ - /* @var $orderItemClass \yii\db\ActiveRecordInterface */ - $orderItemClass = $this->getOrderItemClass(); - $orderItem = new $orderItemClass(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); - $orderItem->save(false); - $this->afterSave(); - - $orderItems = $orderItemClass::find()->where(['_id' => [$orderItem->getPrimaryKey()]])->all(); - $this->assertEquals(1, count($orderItems)); - - $orderItems = $orderItemClass::find()->where(['_id' => []])->all(); - $this->assertEquals(0, count($orderItems)); - - $orderItems = $orderItemClass::find()->where(['_id' => null])->all(); - $this->assertEquals(0, count($orderItems)); - - $orderItems = $orderItemClass::find()->where(['IN', '_id', [$orderItem->getPrimaryKey()]])->all(); - $this->assertEquals(1, count($orderItems)); - - $orderItems = $orderItemClass::find()->where(['IN', '_id', []])->all(); - $this->assertEquals(0, count($orderItems)); - - $orderItems = $orderItemClass::find()->where(['IN', '_id', [null]])->all(); - $this->assertEquals(0, count($orderItems)); - } - - public function testArrayAttributes() - { - $this->assertTrue(is_array(Order::findOne(1)->itemsArray)); - $this->assertTrue(is_array(Order::findOne(2)->itemsArray)); - $this->assertTrue(is_array(Order::findOne(3)->itemsArray)); - } - - public function testArrayAttributeRelationLazy() - { - $order = Order::findOne(1); - $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - $this->assertTrue($items[1] instanceof Item); - $this->assertTrue($items[2] instanceof Item); - - $order = Order::findOne(2); - $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); - $this->assertTrue(isset($items[3])); - $this->assertTrue(isset($items[4])); - $this->assertTrue(isset($items[5])); - $this->assertTrue($items[3] instanceof Item); - $this->assertTrue($items[4] instanceof Item); - $this->assertTrue($items[5] instanceof Item); - } - - public function testArrayAttributeRelationEager() - { - /* @var $order Order */ - $order = Order::find()->with('itemsByArrayValue')->where(['id' => 1])->one(); - $this->assertTrue($order->isRelationPopulated('itemsByArrayValue')); - $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - $this->assertTrue($items[1] instanceof Item); - $this->assertTrue($items[2] instanceof Item); - - /* @var $order Order */ - $order = Order::find()->with('itemsByArrayValue')->where(['id' => 2])->one(); - $this->assertTrue($order->isRelationPopulated('itemsByArrayValue')); - $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); - $this->assertTrue(isset($items[3])); - $this->assertTrue(isset($items[4])); - $this->assertTrue(isset($items[5])); - $this->assertTrue($items[3] instanceof Item); - $this->assertTrue($items[4] instanceof Item); - $this->assertTrue($items[5] instanceof Item); - } - - public function testArrayAttributeRelationLink() - { - /* @var $order Order */ - $order = Order::find()->where(['id' => 1])->one(); - $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - - $item = Item::get(5); - $order->link('itemsByArrayValue', $item); - $this->afterSave(); - - $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - $this->assertTrue(isset($items[5])); - - // check also after refresh - $this->assertTrue($order->refresh()); - $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - $this->assertTrue(isset($items[5])); - } - - public function testArrayAttributeRelationUnLink() - { - /* @var $order Order */ - $order = Order::find()->where(['id' => 1])->one(); - $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - - $item = Item::get(2); - $order->unlink('itemsByArrayValue', $item); - $this->afterSave(); - - $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertFalse(isset($items[2])); - - // check also after refresh - $this->assertTrue($order->refresh()); - $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertFalse(isset($items[2])); - } - - /** - * https://github.com/yiisoft/yii2/issues/6065 - */ - public function testArrayAttributeRelationUnLinkBrokenArray() - { - /* @var $order Order */ - $order = Order::find()->where(['id' => 1])->one(); - - $itemIds = $order->itemsArray; - $removeId = reset($itemIds); - $item = Item::get($removeId); - $order->unlink('itemsByArrayValue', $item); - $this->afterSave(); - - $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); - $this->assertFalse(isset($items[$removeId])); - - // check also after refresh - $this->assertTrue($order->refresh()); - $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); - $this->assertFalse(isset($items[$removeId])); - } - - /** - * @expectedException \yii\base\NotSupportedException - */ - public function testArrayAttributeRelationUnLinkAll() - { - /* @var $order Order */ - $order = Order::find()->where(['id' => 1])->one(); - $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); - $this->assertTrue(isset($items[1])); - $this->assertTrue(isset($items[2])); - - $order->unlinkAll('itemsByArrayValue'); - $this->afterSave(); - - $items = $order->itemsByArrayValue; - $this->assertEquals(0, count($items)); - - // check also after refresh - $this->assertTrue($order->refresh()); - $items = $order->itemsByArrayValue; - $this->assertEquals(0, count($items)); - } - - public function testUnlinkAll() - { - // not supported by elasticsearch - } - - /** - * @expectedException \yii\base\NotSupportedException - */ - public function testUnlinkAllAndConditionSetNull() - { - /* @var $customerClass \yii\db\BaseActiveRecord */ - $customerClass = $this->getCustomerClass(); - /* @var $orderClass \yii\db\BaseActiveRecord */ - $orderClass = $this->getOrderWithNullFKClass(); - - // in this test all orders are owned by customer 1 - $orderClass::updateAll(['customer_id' => 1]); - $this->afterSave(); - - $customer = $customerClass::findOne(1); - $this->assertEquals(3, count($customer->ordersWithNullFK)); - $this->assertEquals(1, count($customer->expensiveOrdersWithNullFK)); - $this->assertEquals(3, $orderClass::find()->count()); - $customer->unlinkAll('expensiveOrdersWithNullFK'); - } - - /** - * @expectedException \yii\base\NotSupportedException - */ - public function testUnlinkAllAndConditionDelete() - { - /* @var $customerClass \yii\db\BaseActiveRecord */ - $customerClass = $this->getCustomerClass(); - /* @var $orderClass \yii\db\BaseActiveRecord */ - $orderClass = $this->getOrderWithNullFKClass(); - - // in this test all orders are owned by customer 1 - $orderClass::updateAll(['customer_id' => 1]); - $this->afterSave(); - - $customer = $customerClass::findOne(1); - $this->assertEquals(3, count($customer->ordersWithNullFK)); - $this->assertEquals(1, count($customer->expensiveOrdersWithNullFK)); - $this->assertEquals(3, $orderClass::find()->count()); - $customer->unlinkAll('expensiveOrdersWithNullFK', true); - } - - public function testPopulateRecordCallWhenQueryingOnParentClass() - { - $animal = Animal::find()->where(['type' => Dog::className()])->one(); - $this->assertEquals('bark', $animal->getDoes()); - - $animal = Animal::find()->where(['type' => Cat::className()])->one(); - $this->assertEquals('meow', $animal->getDoes()); - } - - // TODO test AR with not mapped PK -} diff --git a/tests/unit/extensions/elasticsearch/ElasticSearchConnectionTest.php b/tests/unit/extensions/elasticsearch/ElasticSearchConnectionTest.php deleted file mode 100644 index ee44ac9853..0000000000 --- a/tests/unit/extensions/elasticsearch/ElasticSearchConnectionTest.php +++ /dev/null @@ -1,27 +0,0 @@ -autodetectCluster; - $connection->nodes = [ - ['http_address' => 'inet[/127.0.0.1:9200]'], - ]; - $this->assertNull($connection->activeNode); - $connection->open(); - $this->assertNotNull($connection->activeNode); - $this->assertArrayHasKey('name', reset($connection->nodes)); -// $this->assertArrayHasKey('hostname', reset($connection->nodes)); - $this->assertArrayHasKey('version', reset($connection->nodes)); - $this->assertArrayHasKey('http_address', reset($connection->nodes)); - } -} diff --git a/tests/unit/extensions/elasticsearch/ElasticSearchTestCase.php b/tests/unit/extensions/elasticsearch/ElasticSearchTestCase.php deleted file mode 100644 index d1a59b8994..0000000000 --- a/tests/unit/extensions/elasticsearch/ElasticSearchTestCase.php +++ /dev/null @@ -1,52 +0,0 @@ -mockApplication(); - - $databases = self::getParam('databases'); - $params = isset($databases['elasticsearch']) ? $databases['elasticsearch'] : null; - if ($params === null || !isset($params['dsn'])) { - $this->markTestSkipped('No elasticsearch server connection configured.'); - } - $dsn = explode('/', $params['dsn']); - $host = $dsn[2]; - if (strpos($host, ':')===false) { - $host .= ':9200'; - } - if (!@stream_socket_client($host, $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No elasticsearch server running at ' . $params['dsn'] . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - - parent::setUp(); - } - - /** - * @param boolean $reset whether to clean up the test database - * @return Connection - */ - public function getConnection($reset = true) - { - $databases = self::getParam('databases'); - $params = isset($databases['elasticsearch']) ? $databases['elasticsearch'] : []; - $db = new Connection(); - if ($reset) { - $db->open(); - } - - return $db; - } -} diff --git a/tests/unit/extensions/elasticsearch/QueryBuilderTest.php b/tests/unit/extensions/elasticsearch/QueryBuilderTest.php deleted file mode 100644 index bad2387076..0000000000 --- a/tests/unit/extensions/elasticsearch/QueryBuilderTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getConnection()->createCommand(); - - // delete index - if ($command->indexExists('yiitest')) { - $command->deleteIndex('yiitest'); - } - } - - private function prepareDbData() - { - $command = $this->getConnection()->createCommand(); - $command->insert('yiitest', 'article', ['title' => 'I love yii!'], 1); - $command->insert('yiitest', 'article', ['title' => 'Symfony2 is another framework'], 2); - $command->insert('yiitest', 'article', ['title' => 'Yii2 out now!'], 3); - $command->insert('yiitest', 'article', ['title' => 'yii test'], 4); - - $command->flushIndex('yiitest'); - } - - public function testQueryBuilderRespectsQuery() - { - $queryParts = ['field' => ['title' => 'yii']]; - $queryBuilder = new QueryBuilder($this->getConnection()); - $query = new Query(); - $query->query = $queryParts; - $build = $queryBuilder->build($query); - $this->assertTrue(array_key_exists('queryParts', $build)); - $this->assertTrue(array_key_exists('query', $build['queryParts'])); - $this->assertFalse(array_key_exists('match_all', $build['queryParts']), 'Match all should not be set'); - $this->assertSame($queryParts, $build['queryParts']['query']); - } - - public function testYiiCanBeFoundByQuery() - { - $this->prepareDbData(); - $queryParts = ['term' => ['title' => 'yii']]; - $query = new Query(); - $query->from('yiitest', 'article'); - $query->query = $queryParts; - $result = $query->search($this->getConnection()); - $this->assertEquals(2, $result['hits']['total']); - } - - public function testFuzzySearch() - { - $this->prepareDbData(); - $queryParts = [ - "fuzzy_like_this" => [ - "fields" => ["title"], - "like_text" => "Similar to YII", - "max_query_terms" => 4 - ] - ]; - $query = new Query(); - $query->from('yiitest', 'article'); - $query->query = $queryParts; - $result = $query->search($this->getConnection()); - $this->assertEquals(3, $result['hits']['total']); - } -} diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php deleted file mode 100644 index 1e853da85c..0000000000 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ /dev/null @@ -1,246 +0,0 @@ -getConnection()->createCommand(); - - // delete index - if ($command->indexExists('yiitest')) { - $command->deleteIndex('yiitest'); - } - - $command->insert('yiitest', 'user', ['name' => 'user1', 'email' => 'user1@example.com', 'status' => 1], 1); - $command->insert('yiitest', 'user', ['name' => 'user2', 'email' => 'user2@example.com', 'status' => 1], 2); - $command->insert('yiitest', 'user', ['name' => 'user3', 'email' => 'user3@example.com', 'status' => 2], 3); - $command->insert('yiitest', 'user', ['name' => 'user4', 'email' => 'user4@example.com', 'status' => 1], 4); - - $command->flushIndex(); - } - - public function testFields() - { - $query = new Query; - $query->from('yiitest', 'user'); - - $query->fields(['name', 'status']); - $this->assertEquals(['name', 'status'], $query->fields); - - $query->fields('name', 'status'); - $this->assertEquals(['name', 'status'], $query->fields); - - $result = $query->one($this->getConnection()); - $this->assertEquals(2, count($result['fields'])); - $this->assertArrayHasKey('status', $result['fields']); - $this->assertArrayHasKey('name', $result['fields']); - $this->assertArrayHasKey('_id', $result); - - $query->fields([]); - $this->assertEquals([], $query->fields); - - $result = $query->one($this->getConnection()); - $this->assertArrayNotHasKey('fields', $result); - $this->assertArrayHasKey('_id', $result); - - $query->fields(null); - $this->assertNull($query->fields); - - $result = $query->one($this->getConnection()); - $this->assertEquals(3, count($result['_source'])); - $this->assertArrayHasKey('status', $result['_source']); - $this->assertArrayHasKey('email', $result['_source']); - $this->assertArrayHasKey('name', $result['_source']); - $this->assertArrayHasKey('_id', $result); - } - - public function testOne() - { - $query = new Query; - $query->from('yiitest', 'user'); - - $result = $query->one($this->getConnection()); - $this->assertEquals(3, count($result['_source'])); - $this->assertArrayHasKey('status', $result['_source']); - $this->assertArrayHasKey('email', $result['_source']); - $this->assertArrayHasKey('name', $result['_source']); - $this->assertArrayHasKey('_id', $result); - - $result = $query->where(['name' => 'user1'])->one($this->getConnection()); - $this->assertEquals(3, count($result['_source'])); - $this->assertArrayHasKey('status', $result['_source']); - $this->assertArrayHasKey('email', $result['_source']); - $this->assertArrayHasKey('name', $result['_source']); - $this->assertArrayHasKey('_id', $result); - $this->assertEquals(1, $result['_id']); - - $result = $query->where(['name' => 'user5'])->one($this->getConnection()); - $this->assertFalse($result); - } - - public function testAll() - { - $query = new Query; - $query->from('yiitest', 'user'); - - $results = $query->all($this->getConnection()); - $this->assertEquals(4, count($results)); - $result = reset($results); - $this->assertEquals(3, count($result['_source'])); - $this->assertArrayHasKey('status', $result['_source']); - $this->assertArrayHasKey('email', $result['_source']); - $this->assertArrayHasKey('name', $result['_source']); - $this->assertArrayHasKey('_id', $result); - - $query = new Query; - $query->from('yiitest', 'user'); - - $results = $query->where(['name' => 'user1'])->all($this->getConnection()); - $this->assertEquals(1, count($results)); - $result = reset($results); - $this->assertEquals(3, count($result['_source'])); - $this->assertArrayHasKey('status', $result['_source']); - $this->assertArrayHasKey('email', $result['_source']); - $this->assertArrayHasKey('name', $result['_source']); - $this->assertArrayHasKey('_id', $result); - $this->assertEquals(1, $result['_id']); - - // indexBy - $query = new Query; - $query->from('yiitest', 'user'); - - $results = $query->indexBy('name')->all($this->getConnection()); - $this->assertEquals(4, count($results)); - ksort($results); - $this->assertEquals(['user1', 'user2', 'user3', 'user4'], array_keys($results)); - } - - public function testScalar() - { - $query = new Query; - $query->from('yiitest', 'user'); - - $result = $query->where(['name' => 'user1'])->scalar('name', $this->getConnection()); - $this->assertEquals('user1', $result); - $result = $query->where(['name' => 'user1'])->scalar('noname', $this->getConnection()); - $this->assertNull($result); - $result = $query->where(['name' => 'user5'])->scalar('name', $this->getConnection()); - $this->assertNull($result); - } - - public function testColumn() - { - $query = new Query; - $query->from('yiitest', 'user'); - - $result = $query->orderBy(['name' => SORT_ASC])->column('name', $this->getConnection()); - $this->assertEquals(['user1', 'user2', 'user3', 'user4'], $result); - $result = $query->column('noname', $this->getConnection()); - $this->assertEquals([null, null, null, null], $result); - $result = $query->where(['name' => 'user5'])->scalar('name', $this->getConnection()); - $this->assertNull($result); - - } - - public function testFilterWhere() - { - // should work with hash format - $query = new Query; - $query->filterWhere([ - 'id' => 0, - 'title' => ' ', - 'author_ids' => [], - ]); - $this->assertEquals(['id' => 0], $query->where); - - $query->andFilterWhere(['status' => null]); - $this->assertEquals(['id' => 0], $query->where); - - $query->orFilterWhere(['name' => '']); - $this->assertEquals(['id' => 0], $query->where); - - // should work with operator format - $query = new Query; - $condition = ['like', 'name', 'Alex']; - $query->filterWhere($condition); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->orFilterWhere(['not between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not like', 'id', ' ']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or not like', 'id', null]); - $this->assertEquals($condition, $query->where); - } - - public function testFilterWhereRecursively() - { - $query = new Query(); - $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); - $this->assertEquals(['and', ['id' => 1]], $query->where); - } - - // TODO test facets - - // TODO test complex where() every edge of QueryBuilder - - public function testOrder() - { - $query = new Query; - $query->orderBy('team'); - $this->assertEquals(['team' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('company'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('age'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); - - $query->addOrderBy(['age' => SORT_DESC]); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); - - $query->addOrderBy('age ASC, company DESC'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); - } - - public function testLimitOffset() - { - $query = new Query; - $query->limit(10)->offset(5); - $this->assertEquals(10, $query->limit); - $this->assertEquals(5, $query->offset); - } - - public function testUnion() - { - } -} diff --git a/tests/unit/extensions/faker/FixtureControllerTest.php b/tests/unit/extensions/faker/FixtureControllerTest.php deleted file mode 100644 index d9d2143af8..0000000000 --- a/tests/unit/extensions/faker/FixtureControllerTest.php +++ /dev/null @@ -1,171 +0,0 @@ -markTestSkipped('Can not test on HHVM because require is cached.'); - } - - $this->mockApplication(); - - $this->_fixtureController = Yii::createObject([ - 'class' => 'yiiunit\extensions\faker\FixtureConsoledController', - 'interactive' => false, - 'fixtureDataPath' => '@runtime/faker', - 'templatePath' => '@yiiunit/data/extensions/faker/templates' - ],['fixture-faker', Yii::$app]); - } - - public function tearDown() - { - @unlink(Yii::getAlias('@runtime/faker/user.php')); - @unlink(Yii::getAlias('@runtime/faker/profile.php')); - @unlink(Yii::getAlias('@runtime/faker/book.php')); - parent::tearDown(); - } - - public function testGenerateOne() - { - $filename = Yii::getAlias('@runtime/faker/user.php'); - $this->assertFileNotExists($filename, 'file to be generated should not exist before'); - - $this->_fixtureController->actionGenerate('user'); - $this->assertFileExists($filename, 'fixture template file should be generated'); - - $generatedData = require Yii::getAlias('@runtime/faker/user.php'); - $this->assertCount(2, $generatedData, 'by default only 2 fixtures should be generated'); - - foreach ($generatedData as $fixtureData) { - $this->assertNotNull($fixtureData['username'], 'generated "username" should not be empty'); - $this->assertNotNull($fixtureData['email'], 'generated "email" should not be empty'); - $this->assertNotNull($fixtureData['auth_key'], 'generated "auth_key" should not be empty'); - $this->assertNotNull($fixtureData['created_at'],'generated "created_at" should not be empty'); - $this->assertNotNull($fixtureData['updated_at'],'generated "updated_at" should not be empty'); - } - } - - public function testGenerateBoth() - { - $userFilename = Yii::getAlias('@runtime/faker/user.php'); - $this->assertFileNotExists($userFilename, 'file to be generated should not exist before'); - - $profileFilename = Yii::getAlias('@runtime/faker/profile.php'); - $this->assertFileNotExists($profileFilename, 'file to be generated should not exist before'); - - $this->_fixtureController->actionGenerate('user', 'profile'); - $this->assertFileExists($userFilename, 'fixture template file should be generated'); - $this->assertFileExists($profileFilename, 'fixture template file should be generated'); - } - - public function testGenerateNotFound() - { - $fileName = Yii::getAlias('@runtime/faker/not_existing_template.php'); - $this->_fixtureController->actionGenerate('not_existing_template'); - $this->assertFileNotExists($fileName, 'not existing template should not be generated'); - } - - public function testGenerateProvider() - { - $bookFilename = Yii::getAlias('@runtime/faker/book.php'); - $this->assertFileNotExists($bookFilename, 'file to be generated should not exist before'); - - $this->_fixtureController->providers[] = 'yiiunit\data\extensions\faker\providers\Book'; - $this->_fixtureController->run('generate',['book']); - $this->assertFileExists($bookFilename, 'fixture template file should be generated'); - } - - /** - * @expectedException yii\console\Exception - */ - public function testNothingToGenerateException() - { - $this->_fixtureController->actionGenerate(); - } - - /** - * @expectedException yii\console\Exception - */ - public function testWrongTemplatePathException() - { - $this->_fixtureController->templatePath = '@not/existing/fixtures/templates/path'; - $this->_fixtureController->run('generate',['user']); - } - - public function testGenerateParticularTimes() - { - $filename = Yii::getAlias('@runtime/faker/user.php'); - $this->assertFileNotExists($filename, 'file to be generated should not exist before'); - - $this->_fixtureController->count = 5; - $this->_fixtureController->actionGenerate('user'); - $this->assertFileExists($filename, 'fixture template file should be generated'); - - $generatedData = require Yii::getAlias('@runtime/faker/user.php'); - $this->assertCount(5, $generatedData, 'exactly 5 fixtures should be generated for the given template'); - } - - public function testGenerateParticlularLanguage() - { - $filename = Yii::getAlias('@runtime/faker/profile.php'); - $this->assertFileNotExists($filename, 'file to be generated should not exist before'); - - $this->_fixtureController->language = 'ru_RU'; - $this->_fixtureController->actionGenerate('profile'); - $this->assertFileExists($filename, 'fixture template file should be generated'); - - $generatedData = require Yii::getAlias('@runtime/faker/profile.php'); - $this->assertEquals(1, preg_match('/^[а-яё]*$/iu', $generatedData[0]['first_name']), 'generated value should be in ru-RU language'); - } - - public function testGenerateAll() - { - $userFilename = Yii::getAlias('@runtime/faker/user.php'); - $this->assertFileNotExists($userFilename, 'file to be generated should not exist before'); - - $profileFilename = Yii::getAlias('@runtime/faker/profile.php'); - $this->assertFileNotExists($profileFilename, 'file to be generated should not exist before'); - - $bookFilename = Yii::getAlias('@runtime/faker/book.php'); - $this->assertFileNotExists($bookFilename, 'file to be generated should not exist before'); - - $this->_fixtureController->providers[] = 'yiiunit\data\extensions\faker\providers\Book'; - $this->_fixtureController->run('generate-all'); - - $this->assertFileExists($userFilename, 'fixture template file should be generated'); - $this->assertFileExists($profileFilename, 'fixture template file should be generated'); - $this->assertFileExists($bookFilename, 'fixture template file should be generated'); - } - -} - -class FixtureConsoledController extends FixtureController -{ - - public function stdout($string) - { - } - -} diff --git a/tests/unit/extensions/gii/GeneratorsTest.php b/tests/unit/extensions/gii/GeneratorsTest.php deleted file mode 100644 index 7c6413c27a..0000000000 --- a/tests/unit/extensions/gii/GeneratorsTest.php +++ /dev/null @@ -1,105 +0,0 @@ -template = 'default'; - $generator->controllerClass = 'app\runtime\TestController'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $this->assertNotEmpty($generator->generate()); - } - - public function testExtensionGenerator() - { - $generator = new ExtensionGenerator(); - $generator->template = 'default'; - $generator->vendorName = 'samdark'; - $generator->namespace = 'samdark\\'; - $generator->license = 'BSD'; - $generator->title = 'Sample extension'; - $generator->description = 'This is sample description.'; - $generator->authorName = 'Alexander Makarov'; - $generator->authorEmail = 'sam@rmcreative.ru'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $this->assertNotEmpty($generator->generate()); - } - - public function testModelGenerator() - { - $generator = new ModelGenerator(); - $generator->template = 'default'; - $generator->tableName = 'profile'; - $generator->modelClass = 'Profile'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $files = $generator->generate(); - $modelCode = $files[0]->content; - - $this->assertTrue(strpos($modelCode, "'id' => 'ID'") !== false, "ID label should be there:\n" . $modelCode); - $this->assertTrue(strpos($modelCode, "'description' => 'Description',") !== false, "Description label should be there:\n" . $modelCode); - } - - public function testModuleGenerator() - { - $generator = new ModuleGenerator(); - $generator->template = 'default'; - $generator->moduleID = 'test'; - $generator->moduleClass = 'app\modules\test\Module'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $this->assertNotEmpty($generator->generate()); - } - - - public function testFormGenerator() - { - $generator = new FormGenerator(); - $generator->template = 'default'; - $generator->modelClass = 'yiiunit\extensions\gii\Profile'; - $generator->viewName = 'profile'; - $generator->viewPath = '@yiiunit/runtime'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $this->assertNotEmpty($generator->generate()); - } - - public function testCRUDGenerator() - { - $generator = new CRUDGenerator(); - $generator->template = 'default'; - $generator->modelClass = 'yiiunit\extensions\gii\Profile'; - $generator->controllerClass = 'app\TestController'; - - $valid = $generator->validate(); - $this->assertTrue($valid, 'Validation failed: ' . print_r($generator->getErrors(), true)); - - $this->assertNotEmpty($generator->generate()); - } -} diff --git a/tests/unit/extensions/gii/GiiTestCase.php b/tests/unit/extensions/gii/GiiTestCase.php deleted file mode 100644 index ed01e0a77d..0000000000 --- a/tests/unit/extensions/gii/GiiTestCase.php +++ /dev/null @@ -1,50 +0,0 @@ -driverName]; - $pdo_database = 'pdo_'.$this->driverName; - - if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { - $this->markTestSkipped('pdo and '.$pdo_database.' extension are required.'); - } - - $this->mockApplication([ - 'components' => [ - 'db' => [ - 'class' => isset($config['class']) ? $config['class'] : 'yii\db\Connection', - 'dsn' => $config['dsn'], - 'username' => isset($config['username']) ? $config['username'] : null, - 'password' => isset($config['password']) ? $config['password'] : null, - ], - ], - ]); - - if(isset($config['fixture'])) { - Yii::$app->db->open(); - $lines = explode(';', file_get_contents($config['fixture'])); - foreach ($lines as $line) { - if (trim($line) !== '') { - Yii::$app->db->pdo->exec($line); - } - } - } - } -} \ No newline at end of file diff --git a/tests/unit/extensions/gii/Profile.php b/tests/unit/extensions/gii/Profile.php deleted file mode 100644 index 1635b16041..0000000000 --- a/tests/unit/extensions/gii/Profile.php +++ /dev/null @@ -1,40 +0,0 @@ - 128] - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'id' => 'ID', - 'description' => 'Description', - ]; - } -} \ No newline at end of file diff --git a/tests/unit/extensions/imagine/AbstractImageTest.php b/tests/unit/extensions/imagine/AbstractImageTest.php deleted file mode 100644 index aac94d58fc..0000000000 --- a/tests/unit/extensions/imagine/AbstractImageTest.php +++ /dev/null @@ -1,98 +0,0 @@ -imageFile = Yii::getAlias('@yiiunit/data/imagine/large') . '.jpg'; - $this->watermarkFile = Yii::getAlias('@yiiunit/data/imagine/xparent') . '.gif'; - $this->runtimeTextFile = Yii::getAlias('@yiiunit/runtime/image-text-test') . '.png'; - $this->runtimeWatermarkFile = Yii::getAlias('@yiiunit/runtime/image-watermark-test') . '.png'; - parent::setUp(); - } - - protected function tearDown() - { - @unlink($this->runtimeTextFile); - @unlink($this->runtimeWatermarkFile); - } - - public function testText() - { - if (!$this->isFontTestSupported()) { - $this->markTestSkipped('Skipping ImageGdTest Gd not installed'); - } - - $fontFile = Yii::getAlias('@yiiunit/data/imagine/GothamRnd-Light') . '.otf'; - - $img = Image::text($this->imageFile, 'Yii-2 Image', $fontFile, [0, 0], [ - 'size' => 12, - 'color' => '000' - ]); - - $img->save($this->runtimeTextFile); - $this->assertTrue(file_exists($this->runtimeTextFile)); - - } - - public function testCrop() - { - $point = [20, 20]; - $img = Image::crop($this->imageFile, 100, 100, $point); - - $this->assertEquals(100, $img->getSize()->getWidth()); - $this->assertEquals(100, $img->getSize()->getHeight()); - - } - - public function testWatermark() - { - $img = Image::watermark($this->imageFile, $this->watermarkFile); - $img->save($this->runtimeWatermarkFile); - $this->assertTrue(file_exists($this->runtimeWatermarkFile)); - } - - public function testFrame() - { - $frameSize = 5; - $original = Image::getImagine()->open($this->imageFile); - $originalSize = $original->getSize(); - $img = Image::frame($this->imageFile, $frameSize, '666', 0); - $size = $img->getSize(); - - $this->assertEquals($size->getWidth(), $originalSize->getWidth() + ($frameSize * 2)); - } - - public function testThumbnail() - { - $img = Image::thumbnail($this->imageFile, 120, 120); - - $this->assertEquals(120, $img->getSize()->getWidth()); - $this->assertEquals(120, $img->getSize()->getHeight()); - } - - /** - * @expectedException \yii\base\InvalidConfigException - */ - public function testShouldThrowExceptionOnDriverInvalidArgument() - { - Image::setImagine(null); - Image::$driver = 'fake-driver'; - Image::getImagine(); - } - - abstract protected function isFontTestSupported(); -} diff --git a/tests/unit/extensions/imagine/ImageGdTest.php b/tests/unit/extensions/imagine/ImageGdTest.php deleted file mode 100644 index 322ceb17a1..0000000000 --- a/tests/unit/extensions/imagine/ImageGdTest.php +++ /dev/null @@ -1,30 +0,0 @@ -markTestSkipped('Skipping ImageGdTest, Gd not installed'); - } else { - Image::setImagine(null); - Image::$driver = Image::DRIVER_GD2; - parent::setUp(); - } - } - - protected function isFontTestSupported() - { - $infos = gd_info(); - - return isset($infos['FreeType Support']) ? $infos['FreeType Support'] : false; - } -} diff --git a/tests/unit/extensions/imagine/ImageGmagickTest.php b/tests/unit/extensions/imagine/ImageGmagickTest.php deleted file mode 100644 index bfa4e94323..0000000000 --- a/tests/unit/extensions/imagine/ImageGmagickTest.php +++ /dev/null @@ -1,29 +0,0 @@ -markTestSkipped('Skipping ImageGmagickTest, Gmagick is not installed'); - } else { - Image::setImagine(null); - Image::$driver = Image::DRIVER_GMAGICK; - parent::setUp(); - } - } - - protected function isFontTestSupported() - { - return true; - } -} diff --git a/tests/unit/extensions/imagine/ImageImagickTest.php b/tests/unit/extensions/imagine/ImageImagickTest.php deleted file mode 100644 index c768612a0a..0000000000 --- a/tests/unit/extensions/imagine/ImageImagickTest.php +++ /dev/null @@ -1,31 +0,0 @@ -markTestSkipped('Skipping ImageImagickTest, Imagick is not installed'); - } elseif (defined('HHVM_VERSION')) { - $this->markTestSkipped('Imagine does not seem to support HHVM right now.'); - } else { - Image::setImagine(null); - Image::$driver = Image::DRIVER_IMAGICK; - parent::setUp(); - } - } - - protected function isFontTestSupported() - { - return true; - } -} diff --git a/tests/unit/extensions/mongodb/ActiveDataProviderTest.php b/tests/unit/extensions/mongodb/ActiveDataProviderTest.php deleted file mode 100644 index f90c5d118b..0000000000 --- a/tests/unit/extensions/mongodb/ActiveDataProviderTest.php +++ /dev/null @@ -1,91 +0,0 @@ -getConnection(); - $this->setUpTestRows(); - } - - protected function tearDown() - { - $this->dropCollection(Customer::collectionName()); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = []; - for ($i = 1; $i <= 10; $i++) { - $rows[] = [ - 'name' => 'name' . $i, - 'email' => 'email' . $i, - 'address' => 'address' . $i, - 'status' => $i, - ]; - } - $collection->batchInsert($rows); - } - - // Tests : - - public function testQuery() - { - $query = new Query; - $query->from('customer'); - - $provider = new ActiveDataProvider([ - 'query' => $query, - 'db' => $this->getConnection(), - ]); - $models = $provider->getModels(); - $this->assertEquals(10, count($models)); - - $provider = new ActiveDataProvider([ - 'query' => $query, - 'db' => $this->getConnection(), - 'pagination' => [ - 'pageSize' => 5, - ] - ]); - $models = $provider->getModels(); - $this->assertEquals(5, count($models)); - } - - public function testActiveQuery() - { - $provider = new ActiveDataProvider([ - 'query' => Customer::find()->orderBy('id ASC'), - ]); - $models = $provider->getModels(); - $this->assertEquals(10, count($models)); - $this->assertTrue($models[0] instanceof Customer); - $keys = $provider->getKeys(); - $this->assertTrue($keys[0] instanceof \MongoId); - - $provider = new ActiveDataProvider([ - 'query' => Customer::find(), - 'pagination' => [ - 'pageSize' => 5, - ] - ]); - $models = $provider->getModels(); - $this->assertEquals(5, count($models)); - } -} diff --git a/tests/unit/extensions/mongodb/ActiveRecordTest.php b/tests/unit/extensions/mongodb/ActiveRecordTest.php deleted file mode 100644 index 8760750546..0000000000 --- a/tests/unit/extensions/mongodb/ActiveRecordTest.php +++ /dev/null @@ -1,313 +0,0 @@ -getConnection(); - $this->setUpTestRows(); - } - - protected function tearDown() - { - $this->dropCollection(Customer::collectionName()); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = []; - for ($i = 1; $i <= 10; $i++) { - $rows[] = [ - 'name' => 'name' . $i, - 'email' => 'email' . $i, - 'address' => 'address' . $i, - 'status' => $i, - ]; - } - $collection->batchInsert($rows); - $this->testRows = $rows; - } - - // Tests : - - public function testFind() - { - // find one - $result = Customer::find(); - $this->assertTrue($result instanceof ActiveQuery); - $customer = $result->one(); - $this->assertTrue($customer instanceof Customer); - - // find all - $customers = Customer::find()->all(); - $this->assertEquals(10, count($customers)); - $this->assertTrue($customers[0] instanceof Customer); - $this->assertTrue($customers[1] instanceof Customer); - - // find by _id - $testId = $this->testRows[0]['_id']; - $customer = Customer::findOne($testId); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals($testId, $customer->_id); - - // find by column values - $customer = Customer::findOne(['name' => 'name5']); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals($this->testRows[4]['_id'], $customer->_id); - $this->assertEquals('name5', $customer->name); - $customer = Customer::findOne(['name' => 'unexisting name']); - $this->assertNull($customer); - - // find by attributes - $customer = Customer::find()->where(['status' => 4])->one(); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals(4, $customer->status); - - // find count, sum, average, min, max, distinct - $this->assertEquals(10, Customer::find()->count()); - $this->assertEquals(1, Customer::find()->where(['status' => 2])->count()); - $this->assertEquals((1+10)/2*10, Customer::find()->sum('status')); - $this->assertEquals((1+10)/2, Customer::find()->average('status')); - $this->assertEquals(1, Customer::find()->min('status')); - $this->assertEquals(10, Customer::find()->max('status')); - $this->assertEquals(range(1, 10), Customer::find()->distinct('status')); - - // scope - $this->assertEquals(1, Customer::find()->activeOnly()->count()); - - // asArray - $testRow = $this->testRows[2]; - $customer = Customer::find()->where(['_id' => $testRow['_id']])->asArray()->one(); - $this->assertEquals($testRow, $customer); - - // indexBy - $customers = Customer::find()->indexBy('name')->all(); - $this->assertTrue($customers['name1'] instanceof Customer); - $this->assertTrue($customers['name2'] instanceof Customer); - - // indexBy callable - $customers = Customer::find()->indexBy(function ($customer) { - return $customer->status . '-' . $customer->status; - })->all(); - $this->assertTrue($customers['1-1'] instanceof Customer); - $this->assertTrue($customers['2-2'] instanceof Customer); - } - - public function testInsert() - { - $record = new Customer; - $record->name = 'new name'; - $record->email = 'new email'; - $record->address = 'new address'; - $record->status = 7; - - $this->assertTrue($record->isNewRecord); - - $record->save(); - - $this->assertTrue($record->_id instanceof \MongoId); - $this->assertFalse($record->isNewRecord); - } - - /** - * @depends testInsert - */ - public function testUpdate() - { - $record = new Customer; - $record->name = 'new name'; - $record->email = 'new email'; - $record->address = 'new address'; - $record->status = 7; - $record->save(); - - // save - $record = Customer::findOne($record->_id); - $this->assertTrue($record instanceof Customer); - $this->assertEquals(7, $record->status); - $this->assertFalse($record->isNewRecord); - - $record->status = 9; - $record->save(); - $this->assertEquals(9, $record->status); - $this->assertFalse($record->isNewRecord); - $record2 = Customer::findOne($record->_id); - $this->assertEquals(9, $record2->status); - - // updateAll - $pk = ['_id' => $record->_id]; - $ret = Customer::updateAll(['status' => 55], $pk); - $this->assertEquals(1, $ret); - $record = Customer::findOne($pk); - $this->assertEquals(55, $record->status); - } - - /** - * @depends testInsert - */ - public function testDelete() - { - // delete - $record = new Customer; - $record->name = 'new name'; - $record->email = 'new email'; - $record->address = 'new address'; - $record->status = 7; - $record->save(); - - $record = Customer::findOne($record->_id); - $record->delete(); - $record = Customer::findOne($record->_id); - $this->assertNull($record); - - // deleteAll - $record = new Customer; - $record->name = 'new name'; - $record->email = 'new email'; - $record->address = 'new address'; - $record->status = 7; - $record->save(); - - $ret = Customer::deleteAll(['name' => 'new name']); - $this->assertEquals(1, $ret); - $records = Customer::find()->where(['name' => 'new name'])->all(); - $this->assertEquals(0, count($records)); - } - - public function testUpdateAllCounters() - { - $this->assertEquals(1, Customer::updateAllCounters(['status' => 10], ['status' => 10])); - - $record = Customer::findOne(['status' => 10]); - $this->assertNull($record); - } - - /** - * @depends testUpdateAllCounters - */ - public function testUpdateCounters() - { - $record = Customer::findOne($this->testRows[9]); - - $originalCounter = $record->status; - $counterIncrement = 20; - $record->updateCounters(['status' => $counterIncrement]); - $this->assertEquals($originalCounter + $counterIncrement, $record->status); - - $refreshedRecord = Customer::findOne($record->_id); - $this->assertEquals($originalCounter + $counterIncrement, $refreshedRecord->status); - } - - /** - * @depends testUpdate - */ - public function testUpdateNestedAttribute() - { - $record = new Customer; - $record->name = 'new name'; - $record->email = 'new email'; - $record->address = [ - 'city' => 'SomeCity', - 'street' => 'SomeStreet', - ]; - $record->status = 7; - $record->save(); - - // save - $record = Customer::findOne($record->_id); - $newAddress = [ - 'city' => 'AnotherCity' - ]; - $record->address = $newAddress; - $record->save(); - $record2 = Customer::findOne($record->_id); - $this->assertEquals($newAddress, $record2->address); - } - - /** - * @depends testFind - * @depends testInsert - */ - public function testQueryByIntegerField() - { - $record = new Customer; - $record->name = 'new name'; - $record->status = 7; - $record->save(); - - $row = Customer::find()->where(['status' => 7])->one(); - $this->assertNotEmpty($row); - $this->assertEquals(7, $row->status); - - $rowRefreshed = Customer::find()->where(['status' => $row->status])->one(); - $this->assertNotEmpty($rowRefreshed); - $this->assertEquals(7, $rowRefreshed->status); - } - - public function testModify() - { - $searchName = 'name7'; - $newName = 'new name'; - - $customer = Customer::find() - ->where(['name' => $searchName]) - ->modify(['$set' => ['name' => $newName]], ['new' => true]); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals($newName, $customer->name); - - $customer = Customer::find() - ->where(['name' => 'not existing name']) - ->modify(['$set' => ['name' => $newName]], ['new' => false]); - $this->assertNull($customer); - } - - /** - * @depends testInsert - * - * @see https://github.com/yiisoft/yii2/issues/6026 - */ - public function testInsertEmptyAttributes() - { - $record = new Customer(); - $record->save(false); - - $this->assertTrue($record->_id instanceof \MongoId); - $this->assertFalse($record->isNewRecord); - } - - public function testPopulateRecordCallWhenQueryingOnParentClass() - { - (new Cat())->save(false); - (new Dog())->save(false); - - $animal = Animal::find()->where(['type' => Dog::className()])->one(); - $this->assertEquals('bark', $animal->getDoes()); - - $animal = Animal::find()->where(['type' => Cat::className()])->one(); - $this->assertEquals('meow', $animal->getDoes()); - } -} diff --git a/tests/unit/extensions/mongodb/ActiveRelationTest.php b/tests/unit/extensions/mongodb/ActiveRelationTest.php deleted file mode 100644 index 12125379b4..0000000000 --- a/tests/unit/extensions/mongodb/ActiveRelationTest.php +++ /dev/null @@ -1,114 +0,0 @@ -getConnection(); - $this->setUpTestRows(); - } - - protected function tearDown() - { - $this->dropCollection(Customer::collectionName()); - $this->dropCollection(CustomerOrder::collectionName()); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $customers = []; - for ($i = 1; $i <= 5; $i++) { - $customers[] = [ - 'name' => 'name' . $i, - 'email' => 'email' . $i, - 'address' => 'address' . $i, - 'status' => $i, - ]; - } - $customerCollection = $this->getConnection()->getCollection('customer'); - $customers = $customerCollection->batchInsert($customers); - - $items = []; - for ($i = 1; $i <= 10; $i++) { - $items[] = [ - 'name' => 'name' . $i, - 'price' => $i, - ]; - } - $itemCollection = $this->getConnection()->getCollection('item'); - $items = $itemCollection->batchInsert($items); - - $customerOrders = []; - foreach ($customers as $i => $customer) { - $customerOrders[] = [ - 'customer_id' => $customer['_id'], - 'number' => $customer['status'], - 'item_ids' => [ - $items[$i]['_id'], - $items[$i+5]['_id'], - ], - ]; - $customerOrders[] = [ - 'customer_id' => $customer['_id'], - 'number' => $customer['status'] + 100, - 'item_ids' => [ - $items[$i]['_id'], - $items[$i+5]['_id'], - ], - ]; - } - $customerOrderCollection = $this->getConnection()->getCollection('customer_order'); - $customerOrderCollection->batchInsert($customerOrders); - } - - // Tests : - - public function testFindLazy() - { - /* @var $order CustomerOrder */ - $order = CustomerOrder::findOne(['number' => 2]); - $this->assertFalse($order->isRelationPopulated('customer')); - $customer = $order->customer; - $this->assertTrue($order->isRelationPopulated('customer')); - $this->assertTrue($customer instanceof Customer); - $this->assertEquals((string) $customer->_id, (string) $order->customer_id); - $this->assertEquals(1, count($order->relatedRecords)); - } - - public function testFindEager() - { - $orders = CustomerOrder::find()->with('customer')->all(); - $this->assertEquals(10, count($orders)); - $this->assertTrue($orders[0]->isRelationPopulated('customer')); - $this->assertTrue($orders[1]->isRelationPopulated('customer')); - $this->assertTrue($orders[0]->customer instanceof Customer); - $this->assertEquals((string) $orders[0]->customer->_id, (string) $orders[0]->customer_id); - $this->assertTrue($orders[1]->customer instanceof Customer); - $this->assertEquals((string) $orders[1]->customer->_id, (string) $orders[1]->customer_id); - } - - /** - * @see https://github.com/yiisoft/yii2/issues/5411 - * - * @depends testFindEager - */ - public function testFindEagerHasManyByArrayKey() - { - $order = CustomerOrder::find()->where(['number' => 1])->with('items')->one(); - $this->assertNotEmpty($order->items); - } -} diff --git a/tests/unit/extensions/mongodb/CacheTest.php b/tests/unit/extensions/mongodb/CacheTest.php deleted file mode 100644 index 3b300778e2..0000000000 --- a/tests/unit/extensions/mongodb/CacheTest.php +++ /dev/null @@ -1,135 +0,0 @@ -dropCollection(static::$cacheCollection); - parent::tearDown(); - } - - /** - * Creates test cache instance. - * @return Cache cache instance. - */ - protected function createCache() - { - return Yii::createObject([ - 'class' => Cache::className(), - 'db' => $this->getConnection(), - 'cacheCollection' => static::$cacheCollection, - 'gcProbability' => 0, - ]); - } - - // Tests: - - public function testSet() - { - $cache = $this->createCache(); - - $key = 'test_key'; - $value = 'test_value'; - $this->assertTrue($cache->set($key, $value), 'Unable to set value!'); - $this->assertEquals($value, $cache->get($key), 'Unable to set value correctly!'); - - $newValue = 'test_new_value'; - $this->assertTrue($cache->set($key, $newValue), 'Unable to update value!'); - $this->assertEquals($newValue, $cache->get($key), 'Unable to update value correctly!'); - } - - public function testAdd() - { - $cache = $this->createCache(); - - $key = 'test_key'; - $value = 'test_value'; - $this->assertTrue($cache->add($key, $value), 'Unable to add value!'); - $this->assertEquals($value, $cache->get($key), 'Unable to add value correctly!'); - - $newValue = 'test_new_value'; - $this->assertTrue($cache->add($key, $newValue), 'Unable to re-add value!'); - $this->assertEquals($value, $cache->get($key), 'Original value is lost!'); - } - - /** - * @depends testSet - */ - public function testDelete() - { - $cache = $this->createCache(); - - $key = 'test_key'; - $value = 'test_value'; - $cache->set($key, $value); - - $this->assertTrue($cache->delete($key), 'Unable to delete key!'); - $this->assertEquals(false, $cache->get($key), 'Value is not deleted!'); - } - - /** - * @depends testSet - */ - public function testFlush() - { - $cache = $this->createCache(); - - $cache->set('key1', 'value1'); - $cache->set('key2', 'value2'); - - $this->assertTrue($cache->flush(), 'Unable to flush cache!'); - - $collection = $cache->db->getCollection($cache->cacheCollection); - $rows = $this->findAll($collection); - $this->assertCount(0, $rows, 'Unable to flush records!'); - } - - /** - * @depends testSet - */ - public function testGc() - { - $cache = $this->createCache(); - - $cache->set('key1', 'value1'); - $cache->set('key2', 'value2'); - - $collection = $cache->db->getCollection($cache->cacheCollection); - - list($row) = $this->findAll($collection); - $collection->update(['_id' => $row['_id']], ['expire' => time() - 10]); - - $cache->gc(true); - - $rows = $this->findAll($collection); - $this->assertCount(1, $rows, 'Unable to collect garbage!'); - } - - /** - * @depends testSet - */ - public function testGetExpired() - { - $cache = $this->createCache(); - - $key = 'test_key'; - $value = 'test_value'; - $cache->set($key, $value); - - $collection = $cache->db->getCollection($cache->cacheCollection); - list($row) = $this->findAll($collection); - $collection->update(['_id' => $row['_id']], ['expire' => time() - 10]); - - $this->assertEquals(false, $cache->get($key), 'Expired key value returned!'); - } -} diff --git a/tests/unit/extensions/mongodb/CollectionTest.php b/tests/unit/extensions/mongodb/CollectionTest.php deleted file mode 100644 index f5d1034f9f..0000000000 --- a/tests/unit/extensions/mongodb/CollectionTest.php +++ /dev/null @@ -1,466 +0,0 @@ -dropCollection('customer'); - $this->dropCollection('mapReduceOut'); - parent::tearDown(); - } - - // Tests : - - public function testGetName() - { - $collectionName = 'customer'; - $collection = $this->getConnection()->getCollection($collectionName); - $this->assertEquals($collectionName, $collection->getName()); - $this->assertEquals($this->mongoDbConfig['defaultDatabaseName'] . '.' . $collectionName, $collection->getFullName()); - } - - public function testFind() - { - $collection = $this->getConnection()->getCollection('customer'); - $cursor = $collection->find(); - $this->assertTrue($cursor instanceof \MongoCursor); - } - - public function testInsert() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->insert($data); - $this->assertTrue($id instanceof \MongoId); - $this->assertNotEmpty($id->__toString()); - } - - /** - * @depends testInsert - * @depends testFind - */ - public function testFindAll() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->insert($data); - - $cursor = $collection->find(); - $rows = []; - foreach ($cursor as $row) { - $rows[] = $row; - } - $this->assertEquals(1, count($rows)); - $this->assertEquals($id, $rows[0]['_id']); - } - - /** - * @depends testFind - */ - public function testBatchInsert() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = [ - [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ], - [ - 'name' => 'customer 2', - 'address' => 'customer 2 address', - ], - ]; - $insertedRows = $collection->batchInsert($rows); - $this->assertTrue($insertedRows[0]['_id'] instanceof \MongoId); - $this->assertTrue($insertedRows[1]['_id'] instanceof \MongoId); - $this->assertEquals(count($rows), $collection->find()->count()); - } - - public function testSave() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->save($data); - $this->assertTrue($id instanceof \MongoId); - $this->assertNotEmpty($id->__toString()); - } - - /** - * @depends testSave - */ - public function testUpdateBySave() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $newId = $collection->save($data); - - $updatedId = $collection->save($data); - $this->assertEquals($newId, $updatedId, 'Unable to update data!'); - - $data['_id'] = $newId->__toString(); - $updatedId = $collection->save($data); - $this->assertEquals($newId, $updatedId, 'Unable to updated data by string id!'); - } - - /** - * @depends testFindAll - */ - public function testRemove() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->insert($data); - - $count = $collection->remove(['_id' => $id]); - $this->assertEquals(1, $count); - - $rows = $this->findAll($collection); - $this->assertEquals(0, count($rows)); - } - - /** - * @depends testFindAll - */ - public function testUpdate() - { - $collection = $this->getConnection()->getCollection('customer'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->insert($data); - - $newData = [ - 'name' => 'new name' - ]; - $count = $collection->update(['_id' => $id], $newData); - $this->assertEquals(1, $count); - - list($row) = $this->findAll($collection); - $this->assertEquals($newData['name'], $row['name']); - } - - /** - * @depends testBatchInsert - */ - public function testGroup() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = [ - [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ], - [ - 'name' => 'customer 2', - 'address' => 'customer 2 address', - ], - ]; - $collection->batchInsert($rows); - - $keys = ['address' => 1]; - $initial = ['items' => []]; - $reduce = "function (obj, prev) { prev.items.push(obj.name); }"; - $result = $collection->group($keys, $initial, $reduce); - $this->assertEquals(2, count($result)); - $this->assertNotEmpty($result[0]['address']); - $this->assertNotEmpty($result[0]['items']); - } - - public function testFindAndModify() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = [ - [ - 'name' => 'customer 1', - 'status' => 1, - 'amount' => 100, - ], - [ - 'name' => 'customer 2', - 'status' => 1, - 'amount' => 200, - ], - ]; - $collection->batchInsert($rows); - - // increment field - $result = $collection->findAndModify(['name' => 'customer 1'], ['$inc' => ['status' => 1]]); - $this->assertEquals('customer 1', $result['name']); - $this->assertEquals(1, $result['status']); - $newResult = $collection->findOne(['name' => 'customer 1']); - $this->assertEquals(2, $newResult['status']); - - // $set and return modified document - $result = $collection->findAndModify( - ['name' => 'customer 2'], - ['$set' => ['status' => 2]], - [], - ['new' => true] - ); - $this->assertEquals('customer 2', $result['name']); - $this->assertEquals(2, $result['status']); - - // Full update document - $data = [ - 'name' => 'customer 3', - 'city' => 'Minsk' - ]; - $result = $collection->findAndModify( - ['name' => 'customer 2'], - $data, - [], - ['new' => true] - ); - $this->assertEquals('customer 3', $result['name']); - $this->assertEquals('Minsk', $result['city']); - $this->assertTrue(!isset($result['status'])); - - // Test exceptions - $this->setExpectedException('\yii\mongodb\Exception'); - $collection->findAndModify(['name' => 'customer 1'], ['$wrongOperator' => ['status' => 1]]); - } - - /** - * @depends testBatchInsert - */ - public function testMapReduce() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = [ - [ - 'name' => 'customer 1', - 'status' => 1, - 'amount' => 100, - ], - [ - 'name' => 'customer 2', - 'status' => 1, - 'amount' => 200, - ], - [ - 'name' => 'customer 2', - 'status' => 2, - 'amount' => 400, - ], - [ - 'name' => 'customer 2', - 'status' => 3, - 'amount' => 500, - ], - ]; - $collection->batchInsert($rows); - - $result = $collection->mapReduce( - 'function () {emit(this.status, this.amount)}', - 'function (key, values) {return Array.sum(values)}', - 'mapReduceOut', - ['status' => ['$lt' => 3]] - ); - $this->assertEquals('mapReduceOut', $result); - - $outputCollection = $this->getConnection()->getCollection($result); - $rows = $this->findAll($outputCollection); - $expectedRows = [ - [ - '_id' => 1, - 'value' => 300, - ], - [ - '_id' => 2, - 'value' => 400, - ], - ]; - $this->assertEquals($expectedRows, $rows); - } - - /** - * @depends testMapReduce - */ - public function testMapReduceInline() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = [ - [ - 'name' => 'customer 1', - 'status' => 1, - 'amount' => 100, - ], - [ - 'name' => 'customer 2', - 'status' => 1, - 'amount' => 200, - ], - [ - 'name' => 'customer 2', - 'status' => 2, - 'amount' => 400, - ], - [ - 'name' => 'customer 2', - 'status' => 3, - 'amount' => 500, - ], - ]; - $collection->batchInsert($rows); - - $result = $collection->mapReduce( - 'function () {emit(this.status, this.amount)}', - 'function (key, values) {return Array.sum(values)}', - ['inline' => true], - ['status' => ['$lt' => 3]] - ); - $expectedRows = [ - [ - '_id' => 1, - 'value' => 300, - ], - [ - '_id' => 2, - 'value' => 400, - ], - ]; - $this->assertEquals($expectedRows, $result); - } - - public function testCreateIndex() - { - $collection = $this->getConnection()->getCollection('customer'); - $columns = [ - 'name', - 'status' => \MongoCollection::DESCENDING, - ]; - $this->assertTrue($collection->createIndex($columns)); - $indexInfo = $collection->mongoCollection->getIndexInfo(); - $this->assertEquals(2, count($indexInfo)); - } - - /** - * @depends testCreateIndex - */ - public function testDropIndex() - { - $collection = $this->getConnection()->getCollection('customer'); - - $collection->createIndex('name'); - $this->assertTrue($collection->dropIndex('name')); - $indexInfo = $collection->mongoCollection->getIndexInfo(); - $this->assertEquals(1, count($indexInfo)); - - $this->setExpectedException('\yii\mongodb\Exception'); - $collection->dropIndex('name'); - } - - /** - * @depends testCreateIndex - */ - public function testDropAllIndexes() - { - $collection = $this->getConnection()->getCollection('customer'); - $collection->createIndex('name'); - $this->assertEquals(2, $collection->dropAllIndexes()); - $indexInfo = $collection->mongoCollection->getIndexInfo(); - $this->assertEquals(1, count($indexInfo)); - } - - /** - * @depends testBatchInsert - * @depends testCreateIndex - */ - public function testFullTextSearch() - { - if (version_compare('2.4', $this->getServerVersion(), '>')) { - $this->markTestSkipped("Mongo Server 2.4 required."); - } - - $collection = $this->getConnection()->getCollection('customer'); - - $rows = [ - [ - 'name' => 'customer 1', - 'status' => 1, - 'amount' => 100, - ], - [ - 'name' => 'some customer', - 'status' => 1, - 'amount' => 200, - ], - [ - 'name' => 'no search keyword', - 'status' => 1, - 'amount' => 200, - ], - ]; - $collection->batchInsert($rows); - $collection->createIndex(['name' => 'text']); - - $result = $collection->fullTextSearch('customer'); - $this->assertNotEmpty($result); - $this->assertCount(2, $result); - } - - /** - * @depends testInsert - * @depends testFind - */ - public function testFindByNotObjectId() - { - $collection = $this->getConnection()->getCollection('customer'); - - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - ]; - $id = $collection->insert($data); - - $cursor = $collection->find(['_id' => (string) $id]); - $this->assertTrue($cursor instanceof \MongoCursor); - $row = $cursor->getNext(); - $this->assertEquals($id, $row['_id']); - - $cursor = $collection->find(['_id' => 'fake']); - $this->assertTrue($cursor instanceof \MongoCursor); - $this->assertEquals(0, $cursor->count()); - } - - /** - * @depends testInsert - * - * @see https://github.com/yiisoft/yii2/issues/2548 - */ - public function testInsertMongoBin() - { - $collection = $this->getConnection()->getCollection('customer'); - - $fileName = realpath(__DIR__ . '/../../../../extensions/gii/assets/logo.png'); - $data = [ - 'name' => 'customer 1', - 'address' => 'customer 1 address', - 'binData' => new \MongoBinData(file_get_contents($fileName), 2), - ]; - $id = $collection->insert($data); - $this->assertTrue($id instanceof \MongoId); - $this->assertNotEmpty($id->__toString()); - } -} diff --git a/tests/unit/extensions/mongodb/ConnectionTest.php b/tests/unit/extensions/mongodb/ConnectionTest.php deleted file mode 100644 index b97c661ecf..0000000000 --- a/tests/unit/extensions/mongodb/ConnectionTest.php +++ /dev/null @@ -1,157 +0,0 @@ -getConnection(false); - $params = $this->mongoDbConfig; - - $connection->open(); - - $this->assertEquals($params['dsn'], $connection->dsn); - $this->assertEquals($params['defaultDatabaseName'], $connection->defaultDatabaseName); - $this->assertEquals($params['options'], $connection->options); - } - - public function testOpenClose() - { - $connection = $this->getConnection(false, false); - - $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->mongoClient); - - $connection->open(); - $this->assertTrue($connection->isActive); - $this->assertTrue(is_object($connection->mongoClient)); - - $connection->close(); - $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->mongoClient); - - $connection = new Connection; - $connection->dsn = 'unknown::memory:'; - $this->setExpectedException('yii\mongodb\Exception'); - $connection->open(); - } - - public function testGetDatabase() - { - $connection = $this->getConnection(); - - $database = $connection->getDatabase($connection->defaultDatabaseName); - $this->assertTrue($database instanceof Database); - $this->assertTrue($database->mongoDb instanceof \MongoDB); - - $database2 = $connection->getDatabase($connection->defaultDatabaseName); - $this->assertTrue($database === $database2); - - $databaseRefreshed = $connection->getDatabase($connection->defaultDatabaseName, true); - $this->assertFalse($database === $databaseRefreshed); - } - - /** - * Data provider for [[testFetchDefaultDatabaseName()]] - * @return array test data - */ - public function dataProviderFetchDefaultDatabaseName() - { - return [ - [ - 'mongodb://travis:test@localhost:27017/dbname', - 'dbname', - ], - [ - 'mongodb://travis:test@localhost:27017/dbname?replicaSet=test&connectTimeoutMS=300000', - 'dbname', - ], - ]; - } - - /** - * @dataProvider dataProviderFetchDefaultDatabaseName - * - * @param string $dsn - * @param string $databaseName - */ - public function testFetchDefaultDatabaseName($dsn, $databaseName) - { - $connection = new Connection(); - $connection->dsn = $dsn; - - $reflection = new \ReflectionObject($connection); - $method = $reflection->getMethod('fetchDefaultDatabaseName'); - $method->setAccessible(true); - $method->invoke($connection); - - $this->assertEquals($databaseName, $connection->defaultDatabaseName); - } - - /** - * @depends testGetDatabase - * @depends testFetchDefaultDatabaseName - */ - public function testGetDefaultDatabase() - { - $connection = new Connection(); - $connection->dsn = $this->mongoDbConfig['dsn']; - $connection->defaultDatabaseName = $this->mongoDbConfig['defaultDatabaseName']; - $database = $connection->getDatabase(); - $this->assertTrue($database instanceof Database, 'Unable to get default database!'); - - $connection = new Connection(); - $connection->dsn = $this->mongoDbConfig['dsn']; - $connection->options = ['db' => $this->mongoDbConfig['defaultDatabaseName']]; - $database = $connection->getDatabase(); - $this->assertTrue($database instanceof Database, 'Unable to determine default database from options!'); - - $connection = new Connection(); - $connection->dsn = $this->mongoDbConfig['dsn'] . '/' . $this->mongoDbConfig['defaultDatabaseName']; - $database = $connection->getDatabase(); - $this->assertTrue($database instanceof Database, 'Unable to determine default database from dsn!'); - } - - /** - * @depends testGetDefaultDatabase - */ - public function testGetCollection() - { - $connection = $this->getConnection(); - - $collection = $connection->getCollection('customer'); - $this->assertTrue($collection instanceof Collection); - - $collection2 = $connection->getCollection('customer'); - $this->assertTrue($collection === $collection2); - - $collection2 = $connection->getCollection('customer', true); - $this->assertFalse($collection === $collection2); - } - - /** - * @depends testGetDefaultDatabase - */ - public function testGetFileCollection() - { - $connection = $this->getConnection(); - - $collection = $connection->getFileCollection('testfs'); - $this->assertTrue($collection instanceof FileCollection); - - $collection2 = $connection->getFileCollection('testfs'); - $this->assertTrue($collection === $collection2); - - $collection2 = $connection->getFileCollection('testfs', true); - $this->assertFalse($collection === $collection2); - } -} diff --git a/tests/unit/extensions/mongodb/DatabaseTest.php b/tests/unit/extensions/mongodb/DatabaseTest.php deleted file mode 100644 index f4bc542812..0000000000 --- a/tests/unit/extensions/mongodb/DatabaseTest.php +++ /dev/null @@ -1,70 +0,0 @@ -dropCollection('customer'); - $this->dropFileCollection('testfs'); - parent::tearDown(); - } - - // Tests : - - public function testGetCollection() - { - $database = $connection = $this->getConnection()->getDatabase(); - - $collection = $database->getCollection('customer'); - $this->assertTrue($collection instanceof Collection); - $this->assertTrue($collection->mongoCollection instanceof \MongoCollection); - - $collection2 = $database->getCollection('customer'); - $this->assertTrue($collection === $collection2); - - $collectionRefreshed = $database->getCollection('customer', true); - $this->assertFalse($collection === $collectionRefreshed); - } - - public function testGetFileCollection() - { - $database = $connection = $this->getConnection()->getDatabase(); - - $collection = $database->getFileCollection('testfs'); - $this->assertTrue($collection instanceof FileCollection); - $this->assertTrue($collection->mongoCollection instanceof \MongoGridFS); - - $collection2 = $database->getFileCollection('testfs'); - $this->assertTrue($collection === $collection2); - - $collectionRefreshed = $database->getFileCollection('testfs', true); - $this->assertFalse($collection === $collectionRefreshed); - } - - public function testExecuteCommand() - { - $database = $connection = $this->getConnection()->getDatabase(); - - $result = $database->executeCommand([ - 'distinct' => 'customer', - 'key' => 'name' - ]); - $this->assertTrue(array_key_exists('ok', $result)); - $this->assertTrue(array_key_exists('values', $result)); - } - - public function testCreateCollection() - { - $database = $connection = $this->getConnection()->getDatabase(); - $collection = $database->createCollection('customer'); - $this->assertTrue($collection instanceof \MongoCollection); - } -} diff --git a/tests/unit/extensions/mongodb/MongoDbTestCase.php b/tests/unit/extensions/mongodb/MongoDbTestCase.php deleted file mode 100644 index 43bd22e082..0000000000 --- a/tests/unit/extensions/mongodb/MongoDbTestCase.php +++ /dev/null @@ -1,151 +0,0 @@ - 'mongodb://localhost:27017', - 'defaultDatabaseName' => 'yii2test', - 'options' => [], - ]; - /** - * @var Connection Mongo connection instance. - */ - protected $mongodb; - - public static function setUpBeforeClass() - { - static::loadClassMap(); - } - - protected function setUp() - { - parent::setUp(); - if (!extension_loaded('mongo')) { - $this->markTestSkipped('mongo extension required.'); - } - $config = self::getParam('mongodb'); - if (!empty($config)) { - $this->mongoDbConfig = $config; - } - $this->mockApplication(); - static::loadClassMap(); - } - - protected function tearDown() - { - if ($this->mongodb) { - $this->mongodb->close(); - } - $this->destroyApplication(); - } - - /** - * Adds sphinx extension files to [[Yii::$classPath]], - * avoiding the necessity of usage Composer autoloader. - */ - protected static function loadClassMap() - { - $baseNameSpace = 'yii/mongodb'; - $basePath = realpath(__DIR__. '/../../../../extensions/mongodb'); - - $alias = '@' . $baseNameSpace; - if (!in_array($alias, Yii::$aliases)) { - Yii::setAlias($alias, $basePath); - } - } - - /** - * @param boolean $reset whether to clean up the test database - * @param boolean $open whether to open test database - * @return \yii\mongodb\Connection - */ - public function getConnection($reset = false, $open = true) - { - if (!$reset && $this->mongodb) { - return $this->mongodb; - } - $db = new Connection; - $db->dsn = $this->mongoDbConfig['dsn']; - $db->defaultDatabaseName = $this->mongoDbConfig['defaultDatabaseName']; - if (isset($this->mongoDbConfig['options'])) { - $db->options = $this->mongoDbConfig['options']; - } - if ($open) { - $db->open(); - } - $this->mongodb = $db; - - return $db; - } - - /** - * Drops the specified collection. - * @param string $name collection name. - */ - protected function dropCollection($name) - { - if ($this->mongodb) { - try { - $this->mongodb->getCollection($name)->drop(); - } catch (Exception $e) { - // shut down exception - } - } - } - - /** - * Drops the specified file collection. - * @param string $name file collection name. - */ - protected function dropFileCollection($name = 'fs') - { - if ($this->mongodb) { - try { - $this->mongodb->getFileCollection($name)->drop(); - } catch (Exception $e) { - // shut down exception - } - } - } - - /** - * Finds all records in collection. - * @param \yii\mongodb\Collection $collection - * @param array $condition - * @param array $fields - * @return array rows - */ - protected function findAll($collection, $condition = [], $fields = []) - { - $cursor = $collection->find($condition, $fields); - $result = []; - foreach ($cursor as $data) { - $result[] = $data; - } - - return $result; - } - - /** - * Returns the Mongo server version. - * @return string Mongo server version. - */ - protected function getServerVersion() - { - $connection = $this->getConnection(); - $buildInfo = $connection->getDatabase()->executeCommand(['buildinfo' => true]); - - return $buildInfo['version']; - } -} diff --git a/tests/unit/extensions/mongodb/QueryRunTest.php b/tests/unit/extensions/mongodb/QueryRunTest.php deleted file mode 100644 index a81e5833d0..0000000000 --- a/tests/unit/extensions/mongodb/QueryRunTest.php +++ /dev/null @@ -1,282 +0,0 @@ -setUpTestRows(); - } - - protected function tearDown() - { - $this->dropCollection('customer'); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $collection = $this->getConnection()->getCollection('customer'); - $rows = []; - for ($i = 1; $i <= 10; $i++) { - $rows[] = [ - 'name' => 'name' . $i, - 'address' => 'address' . $i, - 'avatar' => [ - 'width' => 50 + $i, - 'height' => 100 + $i, - 'url' => 'http://some.url/' . $i, - ], - ]; - } - $collection->batchInsert($rows); - } - - // Tests : - - public function testAll() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer')->all($connection); - $this->assertEquals(10, count($rows)); - } - - public function testDirectMatch() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where(['name' => 'name1']) - ->all($connection); - $this->assertEquals(1, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - } - - public function testIndexBy() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->indexBy('name') - ->all($connection); - $this->assertEquals(10, count($rows)); - $this->assertNotEmpty($rows['name1']); - } - - public function testInCondition() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where([ - 'name' => ['name1', 'name5'] - ]) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('name5', $rows[1]['name']); - } - - public function testOrCondition() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where(['name' => 'name1']) - ->orWhere(['address' => 'address5']) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('address5', $rows[1]['address']); - } - - public function testCombinedInAndCondition() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where([ - 'name' => ['name1', 'name5'] - ]) - ->andWhere(['name' => 'name1']) - ->all($connection); - $this->assertEquals(1, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - } - - public function testCombinedInLikeAndCondition() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where([ - 'name' => ['name1', 'name5', 'name10'] - ]) - ->andWhere(['LIKE', 'name', 'me1']) - ->andWhere(['name' => 'name10']) - ->all($connection); - $this->assertEquals(1, count($rows)); - $this->assertEquals('name10', $rows[0]['name']); - } - - public function testNestedCombinedInAndCondition() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where([ - 'and', - ['name' => ['name1', 'name2', 'name3']], - ['name' => 'name1'] - ]) - ->orWhere([ - 'and', - ['name' => ['name4', 'name5', 'name6']], - ['name' => 'name6'] - ]) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('name6', $rows[1]['name']); - } - - public function testOrder() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('customer') - ->orderBy(['name' => SORT_DESC]) - ->all($connection); - $this->assertEquals('name9', $rows[0]['name']); - - $query = new Query; - $rows = $query->from('customer') - ->orderBy(['avatar.height' => SORT_DESC]) - ->all($connection); - $this->assertEquals('name10', $rows[0]['name']); - } - - public function testMatchPlainId() - { - $connection = $this->getConnection(); - $query = new Query; - $row = $query->from('customer')->one($connection); - $query = new Query; - $rows = $query->from('customer') - ->where(['_id' => $row['_id']->__toString()]) - ->all($connection); - $this->assertEquals(1, count($rows)); - } - - public function testRegex() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->where(['REGEX', 'name', '/me1/']) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('name10', $rows[1]['name']); - } - - public function testLike() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('customer') - ->where(['LIKE', 'name', 'me1']) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('name10', $rows[1]['name']); - - $query = new Query; - $rowsUppercase = $query->from('customer') - ->where(['LIKE', 'name', 'ME1']) - ->all($connection); - $this->assertEquals($rows, $rowsUppercase); - } - - public function testModify() - { - $connection = $this->getConnection(); - - $query = new Query(); - - $searchName = 'name5'; - $newName = 'new name'; - $row = $query->from('customer') - ->where(['name' => $searchName]) - ->modify(['$set' => ['name' => $newName]], ['new' => false], $connection); - $this->assertEquals($searchName, $row['name']); - - $searchName = 'name7'; - $newName = 'new name'; - $row = $query->from('customer') - ->where(['name' => $searchName]) - ->modify(['$set' => ['name' => $newName]], ['new' => true], $connection); - $this->assertEquals($newName, $row['name']); - - $row = $query->from('customer') - ->where(['name' => 'not existing name']) - ->modify(['$set' => ['name' => 'new name']], ['new' => false], $connection); - $this->assertNull($row); - } - - /** - * @see https://github.com/yiisoft/yii2/issues/4879 - * - * @depends testInCondition - */ - public function testInConditionIgnoreKeys() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - /*->where([ - 'name' => [ - 0 => 'name1', - 15 => 'name5' - ] - ])*/ - ->where(['in', 'name', [ - 10 => 'name1', - 15 => 'name5' - ]]) - ->all($connection); - $this->assertEquals(2, count($rows)); - $this->assertEquals('name1', $rows[0]['name']); - $this->assertEquals('name5', $rows[1]['name']); - } - - /** - * @see https://github.com/yiisoft/yii2/issues/7010 - */ - public function testSelect() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('customer') - ->select(['name' => true, '_id' => false]) - ->limit(1) - ->all($connection); - $row = array_pop($rows); - $this->assertArrayHasKey('name', $row); - $this->assertArrayNotHasKey('address', $row); - $this->assertArrayNotHasKey('_id', $row); - } -} diff --git a/tests/unit/extensions/mongodb/QueryTest.php b/tests/unit/extensions/mongodb/QueryTest.php deleted file mode 100644 index b628cfc32d..0000000000 --- a/tests/unit/extensions/mongodb/QueryTest.php +++ /dev/null @@ -1,115 +0,0 @@ -select($select); - $this->assertEquals($select, $query->select); - - $query = new Query; - $select = ['name', 'something']; - $query->select($select); - $this->assertEquals($select, $query->select); - } - - public function testFrom() - { - $query = new Query; - $from = 'customer'; - $query->from($from); - $this->assertEquals($from, $query->from); - - $query = new Query; - $from = ['', 'customer']; - $query->from($from); - $this->assertEquals($from, $query->from); - } - - public function testWhere() - { - $query = new Query; - $query->where(['name' => 'name1']); - $this->assertEquals(['name' => 'name1'], $query->where); - - $query->andWhere(['address' => 'address1']); - $this->assertEquals( - [ - 'and', - ['name' => 'name1'], - ['address' => 'address1'] - ], - $query->where - ); - - $query->orWhere(['name' => 'name2']); - $this->assertEquals( - [ - 'or', - [ - 'and', - ['name' => 'name1'], - ['address' => 'address1'] - ], - ['name' => 'name2'] - - ], - $query->where - ); - } - - public function testFilterWhere() - { - // should work with hash format - $query = new Query; - $query->filterWhere([ - 'id' => 0, - 'title' => ' ', - 'author_ids' => [], - ]); - $this->assertEquals(['id' => 0], $query->where); - - $query->andFilterWhere(['status' => null]); - $this->assertEquals(['id' => 0], $query->where); - - $query->orFilterWhere(['name' => '']); - $this->assertEquals(['id' => 0], $query->where); - } - - public function testOrder() - { - $query = new Query; - $query->orderBy('team'); - $this->assertEquals(['team' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('company'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('age'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); - - $query->addOrderBy(['age' => SORT_DESC]); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); - - $query->addOrderBy('age ASC, company DESC'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); - } - - public function testLimitOffset() - { - $query = new Query; - $query->limit(10)->offset(5); - $this->assertEquals(10, $query->limit); - $this->assertEquals(5, $query->offset); - } -} diff --git a/tests/unit/extensions/mongodb/SessionTest.php b/tests/unit/extensions/mongodb/SessionTest.php deleted file mode 100644 index ba47b0bda7..0000000000 --- a/tests/unit/extensions/mongodb/SessionTest.php +++ /dev/null @@ -1,140 +0,0 @@ -dropCollection(static::$sessionCollection); - parent::tearDown(); - } - - /** - * Creates test session instance. - * @return Session session instance. - */ - protected function createSession() - { - return Yii::createObject([ - 'class' => Session::className(), - 'db' => $this->getConnection(), - 'sessionCollection' => static::$sessionCollection, - ]); - } - - // Tests: - - public function testWriteSession() - { - $session = $this->createSession(); - - $id = uniqid(); - $data = [ - 'name' => 'value' - ]; - $dataSerialized = serialize($data); - $this->assertTrue($session->writeSession($id, $dataSerialized), 'Unable to write session!'); - - $collection = $session->db->getCollection($session->sessionCollection); - $rows = $this->findAll($collection); - $this->assertCount(1, $rows, 'No session record!'); - - $row = array_shift($rows); - $this->assertEquals($id, $row['id'], 'Wrong session id!'); - $this->assertEquals($dataSerialized, $row['data'], 'Wrong session data!'); - $this->assertTrue($row['expire'] > time(), 'Wrong session expire!'); - - $newData = [ - 'name' => 'new value' - ]; - $newDataSerialized = serialize($newData); - $this->assertTrue($session->writeSession($id, $newDataSerialized), 'Unable to update session!'); - - $rows = $this->findAll($collection); - $this->assertCount(1, $rows, 'Wrong session records after update!'); - $newRow = array_shift($rows); - $this->assertEquals($id, $newRow['id'], 'Wrong session id after update!'); - $this->assertEquals($newDataSerialized, $newRow['data'], 'Wrong session data after update!'); - $this->assertTrue($newRow['expire'] >= $row['expire'], 'Wrong session expire after update!'); - } - - /** - * @depends testWriteSession - */ - public function testDestroySession() - { - $session = $this->createSession(); - - $id = uniqid(); - $data = [ - 'name' => 'value' - ]; - $dataSerialized = serialize($data); - $session->writeSession($id, $dataSerialized); - - $this->assertTrue($session->destroySession($id), 'Unable to destroy session!'); - - $collection = $session->db->getCollection($session->sessionCollection); - $rows = $this->findAll($collection); - $this->assertEmpty($rows, 'Session record not deleted!'); - } - - /** - * @depends testWriteSession - */ - public function testReadSession() - { - $session = $this->createSession(); - - $id = uniqid(); - $data = [ - 'name' => 'value' - ]; - $dataSerialized = serialize($data); - $session->writeSession($id, $dataSerialized); - - $sessionData = $session->readSession($id); - $this->assertEquals($dataSerialized, $sessionData, 'Unable to read session!'); - - $collection = $session->db->getCollection($session->sessionCollection); - list($row) = $this->findAll($collection); - $newRow = $row; - $newRow['expire'] = time() - 1; - unset($newRow['_id']); - $collection->update(['_id' => $row['_id']], $newRow); - - $sessionData = $session->readSession($id); - $this->assertEquals('', $sessionData, 'Expired session read!'); - } - - public function testGcSession() - { - $session = $this->createSession(); - $collection = $session->db->getCollection($session->sessionCollection); - $collection->batchInsert([ - [ - 'id' => uniqid(), - 'expire' => time() + 10, - 'data' => 'actual', - ], - [ - 'id' => uniqid(), - 'expire' => time() - 10, - 'data' => 'expired', - ], - ]); - $this->assertTrue($session->gcSession(10), 'Unable to collection garbage session!'); - - $rows = $this->findAll($collection); - $this->assertCount(1, $rows, 'Wrong records count!'); - } -} diff --git a/tests/unit/extensions/mongodb/console/controllers/EchoMigrateController.php b/tests/unit/extensions/mongodb/console/controllers/EchoMigrateController.php deleted file mode 100644 index 3d5cc0c818..0000000000 --- a/tests/unit/extensions/mongodb/console/controllers/EchoMigrateController.php +++ /dev/null @@ -1,18 +0,0 @@ -migrateControllerClass = EchoMigrateController::className(); - $this->migrationBaseClass = Migration::className(); - - parent::setUp(); - - $this->setUpMigrationPath(); - Yii::$app->setComponents(['mongodb' => $this->getConnection()]); - } - - public function tearDown() - { - parent::tearDown(); - if (extension_loaded('mongo')) { - try { - $this->getConnection()->getCollection('migration')->drop(); - } catch (Exception $e) { - // shutdown exception - } - } - $this->tearDownMigrationPath(); - } - - /** - * @return array applied migration entries - */ - protected function getMigrationHistory() - { - $query = new Query(); - return $query->from('migration')->all(); - } -} \ No newline at end of file diff --git a/tests/unit/extensions/mongodb/file/ActiveRecordTest.php b/tests/unit/extensions/mongodb/file/ActiveRecordTest.php deleted file mode 100644 index 9deb993476..0000000000 --- a/tests/unit/extensions/mongodb/file/ActiveRecordTest.php +++ /dev/null @@ -1,324 +0,0 @@ -getConnection(); - $this->setUpTestRows(); - $filePath = $this->getTestFilePath(); - if (!file_exists($filePath)) { - FileHelper::createDirectory($filePath); - } - } - - protected function tearDown() - { - $filePath = $this->getTestFilePath(); - if (file_exists($filePath)) { - FileHelper::removeDirectory($filePath); - } - $this->dropFileCollection(CustomerFile::collectionName()); - parent::tearDown(); - } - - /** - * @return string test file path. - */ - protected function getTestFilePath() - { - return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $collection = $this->getConnection()->getFileCollection(CustomerFile::collectionName()); - $rows = []; - for ($i = 1; $i <= 10; $i++) { - $record = [ - 'tag' => 'tag' . $i, - 'status' => $i, - ]; - $content = 'content' . $i; - $record['_id'] = $collection->insertFileContent($content, $record); - $record['content'] = $content; - $rows[] = $record; - } - $this->testRows = $rows; - } - - // Tests : - - public function testFind() - { - // find one - $result = CustomerFile::find(); - $this->assertTrue($result instanceof ActiveQuery); - $customer = $result->one(); - $this->assertTrue($customer instanceof CustomerFile); - - // find all - $customers = CustomerFile::find()->all(); - $this->assertEquals(10, count($customers)); - $this->assertTrue($customers[0] instanceof CustomerFile); - $this->assertTrue($customers[1] instanceof CustomerFile); - - // find by _id - $testId = $this->testRows[0]['_id']; - $customer = CustomerFile::findOne($testId); - $this->assertTrue($customer instanceof CustomerFile); - $this->assertEquals($testId, $customer->_id); - - // find by column values - $customer = CustomerFile::findOne(['tag' => 'tag5']); - $this->assertTrue($customer instanceof CustomerFile); - $this->assertEquals($this->testRows[4]['_id'], $customer->_id); - $this->assertEquals('tag5', $customer->tag); - $customer = CustomerFile::findOne(['tag' => 'unexisting tag']); - $this->assertNull($customer); - - // find by attributes - $customer = CustomerFile::find()->where(['status' => 4])->one(); - $this->assertTrue($customer instanceof CustomerFile); - $this->assertEquals(4, $customer->status); - - // find count, sum, average, min, max, distinct - $this->assertEquals(10, CustomerFile::find()->count()); - $this->assertEquals(1, CustomerFile::find()->where(['status' => 2])->count()); - $this->assertEquals((1+10)/2*10, CustomerFile::find()->sum('status')); - $this->assertEquals((1+10)/2, CustomerFile::find()->average('status')); - $this->assertEquals(1, CustomerFile::find()->min('status')); - $this->assertEquals(10, CustomerFile::find()->max('status')); - $this->assertEquals(range(1, 10), CustomerFile::find()->distinct('status')); - - // scope - $this->assertEquals(1, CustomerFile::find()->activeOnly()->count()); - - // asArray - $testRow = $this->testRows[2]; - $customer = CustomerFile::find()->where(['_id' => $testRow['_id']])->asArray()->one(); - $this->assertEquals($testRow['_id'], $customer['_id']); - $this->assertEquals($testRow['tag'], $customer['tag']); - $this->assertEquals($testRow['status'], $customer['status']); - - // indexBy - $customers = CustomerFile::find()->indexBy('tag')->all(); - $this->assertTrue($customers['tag1'] instanceof CustomerFile); - $this->assertTrue($customers['tag2'] instanceof CustomerFile); - - // indexBy callable - $customers = CustomerFile::find()->indexBy(function ($customer) { - return $customer->status . '-' . $customer->status; - })->all(); - $this->assertTrue($customers['1-1'] instanceof CustomerFile); - $this->assertTrue($customers['2-2'] instanceof CustomerFile); - } - - public function testInsert() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - - $this->assertTrue($record->isNewRecord); - - $record->save(); - - $this->assertTrue($record->_id instanceof \MongoId); - $this->assertFalse($record->isNewRecord); - - $fileContent = $record->getFileContent(); - $this->assertEmpty($fileContent); - } - - /** - * @depends testInsert - */ - public function testInsertFile() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - - $fileName = __FILE__; - $record->setAttribute('file', $fileName); - - $record->save(); - - $this->assertTrue($record->_id instanceof \MongoId); - $this->assertFalse($record->isNewRecord); - - $fileContent = $record->getFileContent(); - $this->assertEquals(file_get_contents($fileName), $fileContent); - } - - /** - * @depends testInsert - */ - public function testInsertFileContent() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - - $newFileContent = 'Test new file content'; - $record->setAttribute('newFileContent', $newFileContent); - - $record->save(); - - $this->assertTrue($record->_id instanceof \MongoId); - $this->assertFalse($record->isNewRecord); - - $fileContent = $record->getFileContent(); - $this->assertEquals($newFileContent, $fileContent); - } - - /** - * @depends testInsert - */ - public function testUpdate() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - $record->save(); - - // save - $record = CustomerFile::findOne($record->_id); - $this->assertTrue($record instanceof CustomerFile); - $this->assertEquals(7, $record->status); - $this->assertFalse($record->isNewRecord); - - $record->status = 9; - $record->save(); - $this->assertEquals(9, $record->status); - $this->assertFalse($record->isNewRecord); - $record2 = CustomerFile::findOne($record->_id); - $this->assertEquals(9, $record2->status); - - // updateAll - $pk = ['_id' => $record->_id]; - $ret = CustomerFile::updateAll(['status' => 55], $pk); - $this->assertEquals(1, $ret); - $record = CustomerFile::findOne($pk); - $this->assertEquals(55, $record->status); - } - - /** - * @depends testUpdate - * @depends testInsertFileContent - */ - public function testUpdateFile() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - $newFileContent = 'Test new file content'; - $record->setAttribute('newFileContent', $newFileContent); - $record->save(); - - $updateFileName = __FILE__; - $record = CustomerFile::findOne($record->_id); - $record->setAttribute('file', $updateFileName); - $record->status = 55; - $record->save(); - $this->assertEquals(file_get_contents($updateFileName), $record->getFileContent()); - - $record2 = CustomerFile::findOne($record->_id); - $this->assertEquals($record->status, $record2->status); - $this->assertEquals(file_get_contents($updateFileName), $record2->getFileContent()); - $this->assertEquals($record->tag, $record2->tag); - } - - /** - * @depends testUpdate - * @depends testInsertFileContent - */ - public function testUpdateFileContent() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - $newFileContent = 'Test new file content'; - $record->setAttribute('newFileContent', $newFileContent); - $record->save(); - - $updateFileContent = 'New updated file content'; - $record = CustomerFile::findOne($record->_id); - $record->setAttribute('newFileContent', $updateFileContent); - $record->status = 55; - $record->save(); - $this->assertEquals($updateFileContent, $record->getFileContent()); - - $record2 = CustomerFile::findOne($record->_id); - $this->assertEquals($record->status, $record2->status); - $this->assertEquals($updateFileContent, $record2->getFileContent()); - } - - /** - * @depends testInsertFileContent - */ - public function testWriteFile() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - $newFileContent = 'Test new file content'; - $record->setAttribute('newFileContent', $newFileContent); - $record->save(); - - $outputFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . 'out.txt'; - $this->assertTrue($record->writeFile($outputFileName)); - $this->assertEquals($newFileContent, file_get_contents($outputFileName)); - - $record2 = CustomerFile::findOne($record->_id); - $outputFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . 'out_refreshed.txt'; - $this->assertTrue($record2->writeFile($outputFileName)); - $this->assertEquals($newFileContent, file_get_contents($outputFileName)); - } - - /** - * @depends testInsertFileContent - */ - public function testGetFileResource() - { - $record = new CustomerFile; - $record->tag = 'new new'; - $record->status = 7; - $newFileContent = 'Test new file content'; - $record->setAttribute('newFileContent', $newFileContent); - $record->save(); - - $fileResource = $record->getFileResource(); - $contents = stream_get_contents($fileResource); - fclose($fileResource); - $this->assertEquals($newFileContent, $contents); - - $record2 = CustomerFile::findOne($record->_id); - $fileResource = $record2->getFileResource(); - $contents = stream_get_contents($fileResource); - fclose($fileResource); - $this->assertEquals($newFileContent, $contents); - } -} diff --git a/tests/unit/extensions/mongodb/file/ActiveRelationTest.php b/tests/unit/extensions/mongodb/file/ActiveRelationTest.php deleted file mode 100644 index 69699a0d3c..0000000000 --- a/tests/unit/extensions/mongodb/file/ActiveRelationTest.php +++ /dev/null @@ -1,85 +0,0 @@ -getConnection(); - \yiiunit\data\ar\mongodb\file\ActiveRecord::$db = $this->getConnection(); - $this->setUpTestRows(); - } - - protected function tearDown() - { - $this->dropCollection(Customer::collectionName()); - $this->dropCollection(CustomerFile::collectionName()); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $fileCollection = $this->getConnection()->getFileCollection(CustomerFile::collectionName()); - $customers = []; - $files = []; - for ($i = 1; $i <= 5; $i++) { - $file = [ - 'tag' => 'tag' . $i, - 'status' => $i, - ]; - $content = 'content' . $i; - $file['_id'] = $fileCollection->insertFileContent($content, $file); - $file['content'] = $content; - $files[] = $file; - - $customers[] = [ - 'name' => 'name' . $i, - 'email' => 'email' . $i, - 'address' => 'address' . $i, - 'status' => $i, - 'file_id' => $file['_id'], - ]; - } - $customerCollection = $this->getConnection()->getCollection(Customer::collectionName()); - $customers = $customerCollection->batchInsert($customers); - } - - // Tests : - - public function testFindLazy() - { - /* @var $customer Customer */ - $customer = Customer::findOne(['status' => 2]); - $this->assertFalse($customer->isRelationPopulated('file')); - $file = $customer->file; - $this->assertTrue($customer->isRelationPopulated('file')); - $this->assertTrue($file instanceof CustomerFile); - $this->assertEquals((string) $file->_id, (string) $customer->file_id); - $this->assertEquals(1, count($customer->relatedRecords)); - } - - public function testFindEager() - { - /* @var $customers Customer[] */ - $customers = Customer::find()->with('file')->all(); - $this->assertEquals(5, count($customers)); - $this->assertTrue($customers[0]->isRelationPopulated('file')); - $this->assertTrue($customers[1]->isRelationPopulated('file')); - $this->assertTrue($customers[0]->file instanceof CustomerFile); - $this->assertEquals((string) $customers[0]->file->_id, (string) $customers[0]->file_id); - $this->assertTrue($customers[1]->file instanceof CustomerFile); - $this->assertEquals((string) $customers[1]->file->_id, (string) $customers[1]->file_id); - } -} \ No newline at end of file diff --git a/tests/unit/extensions/mongodb/file/CollectionTest.php b/tests/unit/extensions/mongodb/file/CollectionTest.php deleted file mode 100644 index c73833c59c..0000000000 --- a/tests/unit/extensions/mongodb/file/CollectionTest.php +++ /dev/null @@ -1,98 +0,0 @@ -dropFileCollection('fs'); - parent::tearDown(); - } - - // Tests : - - public function testGetChunkCollection() - { - $collection = $this->getConnection()->getFileCollection(); - $chunkCollection = $collection->getChunkCollection(); - $this->assertTrue($chunkCollection instanceof \yii\mongodb\Collection); - $this->assertTrue($chunkCollection->mongoCollection instanceof \MongoCollection); - } - - public function testFind() - { - $collection = $this->getConnection()->getFileCollection(); - $cursor = $collection->find(); - $this->assertTrue($cursor instanceof \MongoGridFSCursor); - } - - public function testInsertFile() - { - $collection = $this->getConnection()->getFileCollection(); - - $filename = __FILE__; - $id = $collection->insertFile($filename); - $this->assertTrue($id instanceof \MongoId); - - $files = $this->findAll($collection); - $this->assertEquals(1, count($files)); - - /* @var $file \MongoGridFSFile */ - $file = $files[0]; - $this->assertEquals($filename, $file->getFilename()); - $this->assertEquals(file_get_contents($filename), $file->getBytes()); - } - - public function testInsertFileContent() - { - $collection = $this->getConnection()->getFileCollection(); - - $bytes = 'Test file content'; - $id = $collection->insertFileContent($bytes); - $this->assertTrue($id instanceof \MongoId); - - $files = $this->findAll($collection); - $this->assertEquals(1, count($files)); - - /* @var $file \MongoGridFSFile */ - $file = $files[0]; - $this->assertEquals($bytes, $file->getBytes()); - } - - /** - * @depends testInsertFileContent - */ - public function testGet() - { - $collection = $this->getConnection()->getFileCollection(); - - $bytes = 'Test file content'; - $id = $collection->insertFileContent($bytes); - - $file = $collection->get($id); - $this->assertTrue($file instanceof \MongoGridFSFile); - $this->assertEquals($bytes, $file->getBytes()); - } - - /** - * @depends testGet - */ - public function testDelete() - { - $collection = $this->getConnection()->getFileCollection(); - - $bytes = 'Test file content'; - $id = $collection->insertFileContent($bytes); - - $this->assertTrue($collection->delete($id)); - - $file = $collection->get($id); - $this->assertNull($file); - } -} diff --git a/tests/unit/extensions/mongodb/file/QueryTest.php b/tests/unit/extensions/mongodb/file/QueryTest.php deleted file mode 100644 index 839dfc2d2a..0000000000 --- a/tests/unit/extensions/mongodb/file/QueryTest.php +++ /dev/null @@ -1,70 +0,0 @@ -setUpTestRows(); - } - - protected function tearDown() - { - $this->dropFileCollection(); - parent::tearDown(); - } - - /** - * Sets up test rows. - */ - protected function setUpTestRows() - { - $collection = $this->getConnection()->getFileCollection(); - for ($i = 1; $i <= 10; $i++) { - $collection->insertFileContent('content' . $i, [ - 'filename' => 'name' . $i, - 'file_index' => $i, - ]); - } - } - - // Tests : - - public function testAll() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('fs')->all($connection); - $this->assertEquals(10, count($rows)); - } - - public function testOne() - { - $connection = $this->getConnection(); - $query = new Query; - $row = $query->from('fs')->one($connection); - $this->assertTrue(is_array($row)); - $this->assertTrue($row['file'] instanceof \MongoGridFSFile); - } - - public function testDirectMatch() - { - $connection = $this->getConnection(); - $query = new Query; - $rows = $query->from('fs') - ->where(['file_index' => 5]) - ->all($connection); - $this->assertEquals(1, count($rows)); - /* @var $file \MongoGridFSFile */ - $file = $rows[0]; - $this->assertEquals('name5', $file['filename']); - } -} diff --git a/tests/unit/extensions/redis/ActiveRecordTest.php b/tests/unit/extensions/redis/ActiveRecordTest.php deleted file mode 100644 index 0bc4dd837b..0000000000 --- a/tests/unit/extensions/redis/ActiveRecordTest.php +++ /dev/null @@ -1,370 +0,0 @@ -getConnection(); - - $customer = new Customer(); - $customer->setAttributes(['email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1, 'profile_id' => 1], false); - $customer->save(false); - $customer = new Customer(); - $customer->setAttributes(['email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1, 'profile_id' => null], false); - $customer->save(false); - $customer = new Customer(); - $customer->setAttributes(['email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2, 'profile_id' => 2], false); - $customer->save(false); - -// INSERT INTO category (name) VALUES ('Books'); -// INSERT INTO category (name) VALUES ('Movies'); - - $item = new Item(); - $item->setAttributes(['name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1], false); - $item->save(false); - $item = new Item(); - $item->setAttributes(['name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1], false); - $item->save(false); - $item = new Item(); - $item->setAttributes(['name' => 'Ice Age', 'category_id' => 2], false); - $item->save(false); - $item = new Item(); - $item->setAttributes(['name' => 'Toy Story', 'category_id' => 2], false); - $item->save(false); - $item = new Item(); - $item->setAttributes(['name' => 'Cars', 'category_id' => 2], false); - $item->save(false); - - $order = new Order(); - $order->setAttributes(['customer_id' => 1, 'created_at' => 1325282384, 'total' => 110.0], false); - $order->save(false); - $order = new Order(); - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325334482, 'total' => 33.0], false); - $order->save(false); - $order = new Order(); - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325502201, 'total' => 40.0], false); - $order->save(false); - - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); - $orderItem->save(false); - $orderItem = new OrderItem(); - $orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); - $orderItem->save(false); - - $order = new OrderWithNullFK(); - $order->setAttributes(['customer_id' => 1, 'created_at' => 1325282384, 'total' => 110.0], false); - $order->save(false); - $order = new OrderWithNullFK(); - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325334482, 'total' => 33.0], false); - $order->save(false); - $order = new OrderWithNullFK(); - $order->setAttributes(['customer_id' => 2, 'created_at' => 1325502201, 'total' => 40.0], false); - $order->save(false); - - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); - $orderItem->save(false); - $orderItem = new OrderItemWithNullFK(); - $orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); - $orderItem->save(false); - - } - - public function testFindEagerViaRelationPreserveOrder() - { - $this->markTestSkipped('Redis does not support orderBy.'); - } - - public function testFindEagerViaRelationPreserveOrderB() - { - $this->markTestSkipped('Redis does not support orderBy.'); - } - - /** - * overridden because null values are not part of the asArray result in redis - */ - public function testFindAsArray() - { - /* @var $customerClass \yii\db\ActiveRecordInterface */ - $customerClass = $this->getCustomerClass(); - - // asArray - $customer = $customerClass::find()->where(['id' => 2])->asArray()->one(); - $this->assertEquals([ - 'id' => 2, - 'email' => 'user2@example.com', - 'name' => 'user2', - 'address' => 'address2', - 'status' => 1, - ], $customer); - - // find all asArray - $customers = $customerClass::find()->asArray()->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers[0]); - $this->assertArrayHasKey('name', $customers[0]); - $this->assertArrayHasKey('email', $customers[0]); - $this->assertArrayHasKey('address', $customers[0]); - $this->assertArrayHasKey('status', $customers[0]); - $this->assertArrayHasKey('id', $customers[1]); - $this->assertArrayHasKey('name', $customers[1]); - $this->assertArrayHasKey('email', $customers[1]); - $this->assertArrayHasKey('address', $customers[1]); - $this->assertArrayHasKey('status', $customers[1]); - $this->assertArrayHasKey('id', $customers[2]); - $this->assertArrayHasKey('name', $customers[2]); - $this->assertArrayHasKey('email', $customers[2]); - $this->assertArrayHasKey('address', $customers[2]); - $this->assertArrayHasKey('status', $customers[2]); - } - - public function testStatisticalFind() - { - // find count, sum, average, min, max, scalar - $this->assertEquals(3, Customer::find()->count()); - $this->assertEquals(6, Customer::find()->sum('id')); - $this->assertEquals(2, Customer::find()->average('id')); - $this->assertEquals(1, Customer::find()->min('id')); - $this->assertEquals(3, Customer::find()->max('id')); - - $this->assertEquals(6, OrderItem::find()->count()); - $this->assertEquals(7, OrderItem::find()->sum('quantity')); - } - - public function testFindIndexBy() - { - $customerClass = $this->getCustomerClass(); - /* @var $this TestCase|ActiveRecordTestTrait */ - // indexBy - $customers = Customer::find()->indexBy('name')/*->orderBy('id')*/->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['user1'] instanceof $customerClass); - $this->assertTrue($customers['user2'] instanceof $customerClass); - $this->assertTrue($customers['user3'] instanceof $customerClass); - - // indexBy callable - $customers = Customer::find()->indexBy(function ($customer) { - return $customer->id . '-' . $customer->name; - })/*->orderBy('id')*/->all(); // TODO this test is duplicated because of missing orderBy support in redis - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['1-user1'] instanceof $customerClass); - $this->assertTrue($customers['2-user2'] instanceof $customerClass); - $this->assertTrue($customers['3-user3'] instanceof $customerClass); - } - - public function testFindLimit() - { - // TODO this test is duplicated because of missing orderBy support in redis - /* @var $this TestCase|ActiveRecordTestTrait */ - // all() - $customers = Customer::find()->all(); - $this->assertEquals(3, count($customers)); - - $customers = Customer::find()/*->orderBy('id')*/->limit(1)->all(); - $this->assertEquals(1, count($customers)); - $this->assertEquals('user1', $customers[0]->name); - - $customers = Customer::find()/*->orderBy('id')*/->limit(1)->offset(1)->all(); - $this->assertEquals(1, count($customers)); - $this->assertEquals('user2', $customers[0]->name); - - $customers = Customer::find()/*->orderBy('id')*/->limit(1)->offset(2)->all(); - $this->assertEquals(1, count($customers)); - $this->assertEquals('user3', $customers[0]->name); - - $customers = Customer::find()/*->orderBy('id')*/->limit(2)->offset(1)->all(); - $this->assertEquals(2, count($customers)); - $this->assertEquals('user2', $customers[0]->name); - $this->assertEquals('user3', $customers[1]->name); - - $customers = Customer::find()->limit(2)->offset(3)->all(); - $this->assertEquals(0, count($customers)); - - // one() - $customer = Customer::find()/*->orderBy('id')*/->one(); - $this->assertEquals('user1', $customer->name); - - $customer = Customer::find()/*->orderBy('id')*/->offset(0)->one(); - $this->assertEquals('user1', $customer->name); - - $customer = Customer::find()/*->orderBy('id')*/->offset(1)->one(); - $this->assertEquals('user2', $customer->name); - - $customer = Customer::find()/*->orderBy('id')*/->offset(2)->one(); - $this->assertEquals('user3', $customer->name); - - $customer = Customer::find()->offset(3)->one(); - $this->assertNull($customer); - } - - public function testFindEagerViaRelation() - { - /* @var $orderClass \yii\db\ActiveRecordInterface */ - $orderClass = $this->getOrderClass(); - - /* @var $this TestCase|ActiveRecordTestTrait */ - $orders = $orderClass::find()->with('items')/*->orderBy('id')*/->all(); // TODO this test is duplicated because of missing orderBy support in redis - $this->assertEquals(3, count($orders)); - $order = $orders[0]; - $this->assertEquals(1, $order->id); - $this->assertEquals(2, count($order->items)); - $this->assertEquals(1, $order->items[0]->id); - $this->assertEquals(2, $order->items[1]->id); - } - - public function testFindColumn() - { - // TODO this test is duplicated because of missing orderBy support in redis - $this->assertEquals(['user1', 'user2', 'user3'], Customer::find()->column('name')); - // TODO $this->assertEquals(['user3', 'user2', 'user1'], Customer::find()->orderBy(['name' => SORT_DESC])->column('name')); - } - - // TODO test serial column incr - - public function testUpdatePk() - { - // updateCounters - $pk = ['order_id' => 2, 'item_id' => 4]; - $orderItem = OrderItem::findOne($pk); - $this->assertEquals(2, $orderItem->order_id); - $this->assertEquals(4, $orderItem->item_id); - - $orderItem->order_id = 2; - $orderItem->item_id = 10; - $orderItem->save(); - - $this->assertNull(OrderItem::findOne($pk)); - $this->assertNotNull(OrderItem::findOne(['order_id' => 2, 'item_id' => 10])); - } - - public function testFilterWhere() - { - // should work with hash format - $query = new ActiveQuery('dummy'); - $query->filterWhere([ - 'id' => 0, - 'title' => ' ', - 'author_ids' => [], - ]); - $this->assertEquals(['id' => 0], $query->where); - - $query->andFilterWhere(['status' => null]); - $this->assertEquals(['id' => 0], $query->where); - - $query->orFilterWhere(['name' => '']); - $this->assertEquals(['id' => 0], $query->where); - - // should work with operator format - $query = new ActiveQuery('dummy'); - $condition = ['like', 'name', 'Alex']; - $query->filterWhere($condition); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->orFilterWhere(['not between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not like', 'id', ' ']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or not like', 'id', null]); - $this->assertEquals($condition, $query->where); - } - - public function testFilterWhereRecursively() - { - $query = new ActiveQuery('dummy'); - $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); - $this->assertEquals(['and', ['id' => 1]], $query->where); - } -} diff --git a/tests/unit/extensions/redis/RedisCacheTest.php b/tests/unit/extensions/redis/RedisCacheTest.php deleted file mode 100644 index b03143258f..0000000000 --- a/tests/unit/extensions/redis/RedisCacheTest.php +++ /dev/null @@ -1,125 +0,0 @@ -markTestSkipped('No redis server connection configured.'); - } - $connection = new Connection($params); - if (!@stream_socket_client($connection->hostname . ':' . $connection->port, $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No redis server running at ' . $connection->hostname . ':' . $connection->port . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - - $this->mockApplication(['components' => ['redis' => $connection]]); - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new Cache(); - } - - return $this->_cacheInstance; - } - - public function testExpireMilliseconds() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->set('expire_test_ms', 'expire_test_ms', 0.2)); - usleep(100000); - $this->assertEquals('expire_test_ms', $cache->get('expire_test_ms')); - usleep(300000); - $this->assertFalse($cache->get('expire_test_ms')); - } - - public function testExpireAddMilliseconds() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->add('expire_testa_ms', 'expire_testa_ms', 0.2)); - usleep(100000); - $this->assertEquals('expire_testa_ms', $cache->get('expire_testa_ms')); - usleep(300000); - $this->assertFalse($cache->get('expire_testa_ms')); - } - - /** - * Store a value that is 2 times buffer size big - * https://github.com/yiisoft/yii2/issues/743 - */ - public function testLargeData() - { - $cache = $this->getCacheInstance(); - - $data = str_repeat('XX', 8192); // http://www.php.net/manual/en/function.fread.php - $key = 'bigdata1'; - - $this->assertFalse($cache->get($key)); - $cache->set($key, $data); - $this->assertTrue($cache->get($key) === $data); - - // try with multibyte string - $data = str_repeat('ЖЫ', 8192); // http://www.php.net/manual/en/function.fread.php - $key = 'bigdata2'; - - $this->assertFalse($cache->get($key)); - $cache->set($key, $data); - $this->assertTrue($cache->get($key) === $data); - } - - /** - * Store a megabyte and see how it goes - * https://github.com/yiisoft/yii2/issues/6547 - */ - public function testReallyLargeData() - { - $cache = $this->getCacheInstance(); - - $keys = []; - for($i = 1; $i < 16; $i++) { - $key = 'realbigdata' . $i; - $data = str_repeat('X', 100 * 1024); // 100 KB - $keys[$key] = $data; - -// $this->assertTrue($cache->get($key) === false); // do not display 100KB in terminal if this fails :) - $cache->set($key, $data); - } - $values = $cache->mget(array_keys($keys)); - foreach($keys as $key => $value) { - $this->assertArrayHasKey($key, $values); - $this->assertTrue($values[$key] === $value); - } - } - - public function testMultiByteGetAndSet() - { - $cache = $this->getCacheInstance(); - - $data = ['abc' => 'ежик', 2 => 'def']; - $key = 'data1'; - - $this->assertFalse($cache->get($key)); - $cache->set($key, $data); - $this->assertTrue($cache->get($key) === $data); - } -} diff --git a/tests/unit/extensions/redis/RedisConnectionTest.php b/tests/unit/extensions/redis/RedisConnectionTest.php deleted file mode 100644 index b25e28dcda..0000000000 --- a/tests/unit/extensions/redis/RedisConnectionTest.php +++ /dev/null @@ -1,82 +0,0 @@ -getConnection(false); - $db->open(); - $this->assertTrue($db->ping()); - $db->set('YIITESTKEY', 'YIITESTVALUE'); - $db->close(); - - $db = $this->getConnection(false); - $db->database = 0; - $db->open(); - $this->assertEquals('YIITESTVALUE', $db->get('YIITESTKEY')); - $db->close(); - - $db = $this->getConnection(false); - $db->database = 1; - $db->open(); - $this->assertNull($db->get('YIITESTKEY')); - $db->close(); - } - - public function keyValueData() - { - return [ - [123], - [-123], - [0], - ['test'], - ["test\r\ntest"], - [''], - ]; - } - - /** - * @dataProvider keyValueData - */ - public function testStoreGet($data) - { - $db = $this->getConnection(true); - - $db->set('hi', $data); - $this->assertEquals($data, $db->get('hi')); - } - - /** - * https://github.com/yiisoft/yii2/issues/4745 - */ - public function testReturnType() - { - $redis = $this->getConnection(); - $redis->executeCommand('SET',['key1','val1']); - $redis->executeCommand('HMSET',['hash1','hk3','hv3','hk4','hv4']); - $redis->executeCommand('RPUSH',['newlist2','tgtgt','tgtt','44',11]); - $redis->executeCommand('SADD',['newset2','segtggttval','sv1','sv2','sv3']); - $redis->executeCommand('ZADD',['newz2',2,'ss',3,'pfpf']); - $allKeys = $redis->executeCommand('KEYS',['*']); - sort($allKeys); - $this->assertEquals(['hash1', 'key1', 'newlist2', 'newset2', 'newz2'], $allKeys); - $expected = [ - 'hash1' => 'hash', - 'key1' => 'string', - 'newlist2' => 'list', - 'newset2' => 'set', - 'newz2' => 'zset', - ]; - foreach($allKeys as $key) { - $this->assertEquals($expected[$key], $redis->executeCommand('TYPE',[$key])); - } - } -} diff --git a/tests/unit/extensions/redis/RedisTestCase.php b/tests/unit/extensions/redis/RedisTestCase.php deleted file mode 100644 index bd351171a6..0000000000 --- a/tests/unit/extensions/redis/RedisTestCase.php +++ /dev/null @@ -1,49 +0,0 @@ -markTestSkipped('No redis server connection configured.'); - } - $connection = new Connection($params); - if (!@stream_socket_client($connection->hostname . ':' . $connection->port, $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No redis server running at ' . $connection->hostname . ':' . $connection->port . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - - $this->mockApplication(['components' => ['redis' => $connection]]); - - parent::setUp(); - } - - /** - * @param boolean $reset whether to clean up the test database - * @return Connection - */ - public function getConnection($reset = true) - { - $databases = self::getParam('databases'); - $params = isset($databases['redis']) ? $databases['redis'] : []; - $db = new Connection($params); - if ($reset) { - $db->open(); - $db->flushdb(); - } - - return $db; - } -} diff --git a/tests/unit/extensions/smarty/ViewRendererTest.php b/tests/unit/extensions/smarty/ViewRendererTest.php deleted file mode 100644 index 6a24cf65da..0000000000 --- a/tests/unit/extensions/smarty/ViewRendererTest.php +++ /dev/null @@ -1,131 +0,0 @@ - - */ - -namespace yiiunit\extensions\smarty; - -use yii\helpers\FileHelper; -use yii\web\AssetManager; -use yii\web\View; -use Yii; -use yiiunit\data\base\Singer; -use yiiunit\TestCase; - -/** - * @group smarty - */ -class ViewRendererTest extends TestCase -{ - protected function setUp() - { - parent::setUp(); - $this->mockWebApplication(); - } - - protected function tearDown() - { - parent::tearDown(); - FileHelper::removeDirectory(Yii::getAlias('@runtime/assets')); - FileHelper::removeDirectory(Yii::getAlias('@runtime/Smarty')); - } - - /** - * https://github.com/yiisoft/yii2/issues/2265 - */ - public function testNoParams() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/smarty/views/simple.tpl'); - - $this->assertEquals('simple view without parameters.', $content); - } - - public function testRender() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/smarty/views/view.tpl', ['param' => 'Hello World!']); - - $this->assertEquals('test view Hello World!.', $content); - } - - public function testLayoutAssets() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/smarty/views/layout.tpl'); - - $this->assertEquals(1, preg_match('#\s*#', $content), 'Content does not contain the jquery js:' . $content); - } - - - public function testChangeTitle() - { - $view = $this->mockView(); - $view->title = 'Original title'; - - $content = $view->renderFile('@yiiunit/extensions/smarty/views/changeTitle.tpl'); - $this->assertTrue(strpos($content, 'New title') !== false, 'New title should be there:' . $content); - $this->assertFalse(strpos($content, 'Original title') !== false, 'Original title should not be there:' . $content); - } - - public function testForm() - { - $view = $this->mockView(); - $model = new Singer(); - $content = $view->renderFile('@yiiunit/extensions/smarty/views/form.tpl', ['model' => $model]); - $this->assertEquals(1, preg_match('#
        .*?
        #s', $content), 'Content does not contain form:' . $content); - } - - public function testInheritance() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/smarty/views/extends2.tpl'); - $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); - $this->assertTrue(strpos($content, 'extends2 block') !== false, 'extends2 block should be there:' . $content); - $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); - - $content = $view->renderFile('@yiiunit/extensions/smarty/views/extends3.tpl'); - $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); - $this->assertTrue(strpos($content, 'extends3 block') !== false, 'extends3 block should be there:' . $content); - $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); - } - - public function testUse() - { - $view = $this->mockView(); - $view->renderFile('@yiiunit/extensions/smarty/views/use.tpl'); - } - - /** - * @return View - */ - protected function mockView() - { - return new View([ - 'renderers' => [ - 'tpl' => [ - 'class' => 'yii\smarty\ViewRenderer', - 'options' => [ - 'force_compile' => true, // always recompile templates, don't do it in production - ], - ], - ], - 'assetManager' => $this->mockAssetManager(), - ]); - } - - protected function mockAssetManager() - { - $assetDir = Yii::getAlias('@runtime/assets'); - if (!is_dir($assetDir)) { - mkdir($assetDir, 0777, true); - } - - return new AssetManager([ - 'basePath' => $assetDir, - 'baseUrl' => '/assets', - ]); - } -} diff --git a/tests/unit/extensions/smarty/views/changeTitle.tpl b/tests/unit/extensions/smarty/views/changeTitle.tpl deleted file mode 100644 index 0b3460275d..0000000000 --- a/tests/unit/extensions/smarty/views/changeTitle.tpl +++ /dev/null @@ -1,3 +0,0 @@ -{set title='New title'} - -{$this->title} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/extends1.tpl b/tests/unit/extensions/smarty/views/extends1.tpl deleted file mode 100644 index a5a386a1bf..0000000000 --- a/tests/unit/extensions/smarty/views/extends1.tpl +++ /dev/null @@ -1,5 +0,0 @@ -Hello, I'm inheritance test! - -{block name=test} -extends1 block -{/block} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/extends2.tpl b/tests/unit/extensions/smarty/views/extends2.tpl deleted file mode 100644 index 53d95a3424..0000000000 --- a/tests/unit/extensions/smarty/views/extends2.tpl +++ /dev/null @@ -1,5 +0,0 @@ -{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"} - -{block name=test} -extends2 block -{/block} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/extends3.tpl b/tests/unit/extensions/smarty/views/extends3.tpl deleted file mode 100644 index 5296d8c828..0000000000 --- a/tests/unit/extensions/smarty/views/extends3.tpl +++ /dev/null @@ -1,5 +0,0 @@ -{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"} - -{block name=test} -extends3 block -{/block} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/form.tpl b/tests/unit/extensions/smarty/views/form.tpl deleted file mode 100644 index 91e7342e68..0000000000 --- a/tests/unit/extensions/smarty/views/form.tpl +++ /dev/null @@ -1,9 +0,0 @@ -{use class='yii\widgets\ActiveForm' type='block'} -{ActiveForm assign='form' id='login-form' action='/form-handler' options=['class' => 'form-horizontal']} - {$form->field($model, 'firstName')} -
        -
        - -
        -
        -{/ActiveForm} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/layout.tpl b/tests/unit/extensions/smarty/views/layout.tpl deleted file mode 100644 index 4d07e8215f..0000000000 --- a/tests/unit/extensions/smarty/views/layout.tpl +++ /dev/null @@ -1,16 +0,0 @@ -{use class="yii\web\JqueryAsset"} -{JqueryAsset::register($this)|void} -{$this->beginPage()} - - - - - {$this->title|escape} - {$this->head()} - - -{$this->beginBody()} - body -{$this->endBody()} - -{$this->endPage()} \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/simple.tpl b/tests/unit/extensions/smarty/views/simple.tpl deleted file mode 100644 index a9f660c8de..0000000000 --- a/tests/unit/extensions/smarty/views/simple.tpl +++ /dev/null @@ -1 +0,0 @@ -simple view without parameters. \ No newline at end of file diff --git a/tests/unit/extensions/smarty/views/use.tpl b/tests/unit/extensions/smarty/views/use.tpl deleted file mode 100644 index 9f3ab5311b..0000000000 --- a/tests/unit/extensions/smarty/views/use.tpl +++ /dev/null @@ -1,12 +0,0 @@ -{use class='yii\helpers\Html'} -{use class='yii\bootstrap\Nav' type='function'} -{use class='yii\bootstrap\NavBar' type='block'} - -{NavBar brandLabel=$app->name brandUrl=$app->homeUrl - options=['class' => 'test']} - - {Nav options=['class' => 'test2'] items=[ - ['label' => 'Home', 'url' => 'http://example.com/'] - ]} - -{/NavBar} diff --git a/tests/unit/extensions/smarty/views/view.tpl b/tests/unit/extensions/smarty/views/view.tpl deleted file mode 100644 index 22f58fb90d..0000000000 --- a/tests/unit/extensions/smarty/views/view.tpl +++ /dev/null @@ -1 +0,0 @@ -test view {$param}. \ No newline at end of file diff --git a/tests/unit/extensions/sphinx/ActiveDataProviderTest.php b/tests/unit/extensions/sphinx/ActiveDataProviderTest.php deleted file mode 100644 index 50841e5ffa..0000000000 --- a/tests/unit/extensions/sphinx/ActiveDataProviderTest.php +++ /dev/null @@ -1,66 +0,0 @@ -getConnection(); - } - - // Tests : - - public function testQuery() - { - $query = new Query; - $query->from('yii2_test_article_index'); - - $provider = new ActiveDataProvider([ - 'query' => $query, - 'db' => $this->getConnection(), - ]); - $models = $provider->getModels(); - $this->assertEquals(2, count($models)); - - $provider = new ActiveDataProvider([ - 'query' => $query, - 'db' => $this->getConnection(), - 'pagination' => [ - 'pageSize' => 1, - ] - ]); - $models = $provider->getModels(); - $this->assertEquals(1, count($models)); - } - - public function testActiveQuery() - { - $provider = new ActiveDataProvider([ - 'query' => ArticleIndex::find()->orderBy('id ASC'), - ]); - $models = $provider->getModels(); - $this->assertEquals(2, count($models)); - $this->assertTrue($models[0] instanceof ArticleIndex); - $this->assertTrue($models[1] instanceof ArticleIndex); - $this->assertEquals([1, 2], $provider->getKeys()); - - $provider = new ActiveDataProvider([ - 'query' => ArticleIndex::find(), - 'pagination' => [ - 'pageSize' => 1, - ] - ]); - $models = $provider->getModels(); - $this->assertEquals(1, count($models)); - } -} diff --git a/tests/unit/extensions/sphinx/ActiveRecordTest.php b/tests/unit/extensions/sphinx/ActiveRecordTest.php deleted file mode 100644 index e5e2dd8357..0000000000 --- a/tests/unit/extensions/sphinx/ActiveRecordTest.php +++ /dev/null @@ -1,258 +0,0 @@ -getConnection(); - } - - protected function tearDown() - { - $this->truncateRuntimeIndex('yii2_test_rt_index'); - parent::tearDown(); - } - - // Tests : - - public function testFind() - { - // find one - $result = ArticleIndex::find(); - $this->assertTrue($result instanceof ActiveQuery); - $article = $result->one(); - $this->assertTrue($article instanceof ArticleIndex); - - // find all - $articles = ArticleIndex::find()->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles[0] instanceof ArticleIndex); - $this->assertTrue($articles[1] instanceof ArticleIndex); - - // find fulltext - $article = ArticleIndex::findOne(2); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->id); - - // find by column values - $article = ArticleIndex::findOne(['id' => 2, 'author_id' => 2]); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->id); - $this->assertEquals(2, $article->author_id); - $article = ArticleIndex::findOne(['id' => 2, 'author_id' => 1]); - $this->assertNull($article); - - // find by attributes - $article = ArticleIndex::find()->where(['author_id' => 2])->one(); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->id); - - // find by comparison - $article = ArticleIndex::find()->where(['>', 'author_id', 1])->one(); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->id); - - // find custom column - $article = ArticleIndex::find()->select(['*', '(5*2) AS custom_column']) - ->where(['author_id' => 1])->one(); - $this->assertEquals(1, $article->id); - $this->assertEquals(10, $article->custom_column); - - // find count, sum, average, min, max, scalar - $this->assertEquals(2, ArticleIndex::find()->count()); - $this->assertEquals(1, ArticleIndex::find()->where('id=1')->count()); - $this->assertEquals(3, ArticleIndex::find()->sum('id')); - $this->assertEquals(1.5, ArticleIndex::find()->average('id')); - $this->assertEquals(1, ArticleIndex::find()->min('id')); - $this->assertEquals(2, ArticleIndex::find()->max('id')); - $this->assertEquals(2, ArticleIndex::find()->select('COUNT(*)')->scalar()); - - // scope - $this->assertEquals(1, ArticleIndex::find()->favoriteAuthor()->count()); - - // asArray - $article = ArticleIndex::find()->where('id=2')->asArray()->one(); - unset($article['add_date']); - $this->assertEquals([ - 'id' => '2', - 'author_id' => '2', - 'tag' => '3,4', - ], $article); - - // indexBy - $articles = ArticleIndex::find()->indexBy('author_id')->orderBy('id DESC')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles['1'] instanceof ArticleIndex); - $this->assertTrue($articles['2'] instanceof ArticleIndex); - - // indexBy callable - $articles = ArticleIndex::find()->indexBy(function ($article) { - return $article->id . '-' . $article->author_id; - })->orderBy('id DESC')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles['1-1'] instanceof ArticleIndex); - $this->assertTrue($articles['2-2'] instanceof ArticleIndex); - } - - public function testFindBySql() - { - // find one - $article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index ORDER BY id DESC')->one(); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->author_id); - - // find all - $articles = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index')->all(); - $this->assertEquals(2, count($articles)); - - // find with parameter binding - $article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index WHERE id=:id', [':id' => 2])->one(); - $this->assertTrue($article instanceof ArticleIndex); - $this->assertEquals(2, $article->author_id); - } - - public function testInsert() - { - $record = new RuntimeIndex; - $record->id = 15; - $record->title = 'test title'; - $record->content = 'test content'; - $record->type_id = 7; - $record->category = [1, 2]; - - $this->assertTrue($record->isNewRecord); - - $record->save(); - - $this->assertEquals(15, $record->id); - $this->assertFalse($record->isNewRecord); - } - - /** - * @depends testInsert - */ - public function testUpdate() - { - $record = new RuntimeIndex; - $record->id = 2; - $record->title = 'test title'; - $record->content = 'test content'; - $record->type_id = 7; - $record->category = [1, 2]; - $record->save(); - - // save - $record = RuntimeIndex::findOne(2); - $this->assertTrue($record instanceof RuntimeIndex); - $this->assertEquals(7, $record->type_id); - $this->assertFalse($record->isNewRecord); - - $record->type_id = 9; - $record->save(); - $this->assertEquals(9, $record->type_id); - $this->assertFalse($record->isNewRecord); - $record2 = RuntimeIndex::findOne(['id' => 2]); - $this->assertEquals(9, $record2->type_id); - - // replace - $query = 'replace'; - $rows = RuntimeIndex::find()->match($query)->all(); - $this->assertEmpty($rows); - $record = RuntimeIndex::findOne(2); - $record->content = 'Test content with ' . $query; - $record->save(); - $rows = RuntimeIndex::find()->match($query); - $this->assertNotEmpty($rows); - - // updateAll - $pk = ['id' => 2]; - $ret = RuntimeIndex::updateAll(['type_id' => 55], $pk); - $this->assertEquals(1, $ret); - $record = RuntimeIndex::findOne($pk); - $this->assertEquals(55, $record->type_id); - } - - /** - * @depends testInsert - */ - public function testDelete() - { - // delete - $record = new RuntimeIndex; - $record->id = 2; - $record->title = 'test title'; - $record->content = 'test content'; - $record->type_id = 7; - $record->category = [1, 2]; - $record->save(); - - $record = RuntimeIndex::findOne(2); - $record->delete(); - $record = RuntimeIndex::findOne(2); - $this->assertNull($record); - - // deleteAll - $record = new RuntimeIndex; - $record->id = 2; - $record->title = 'test title'; - $record->content = 'test content'; - $record->type_id = 7; - $record->category = [1, 2]; - $record->save(); - - $ret = RuntimeIndex::deleteAll('id = 2'); - $this->assertEquals(1, $ret); - $records = RuntimeIndex::find()->all(); - $this->assertEquals(0, count($records)); - } - - public function testCallSnippets() - { - $query = 'pencil'; - $source = 'Some data sentence about ' . $query; - - $snippet = ArticleIndex::callSnippets($source, $query); - $this->assertNotEmpty($snippet, 'Unable to call snippets!'); - $this->assertContains('' . $query . '', $snippet, 'Query not present in the snippet!'); - - $rows = ArticleIndex::callSnippets([$source], $query); - $this->assertNotEmpty($rows, 'Unable to call snippets!'); - $this->assertContains('' . $query . '', $rows[0], 'Query not present in the snippet!'); - } - - public function testCallKeywords() - { - $text = 'table pencil'; - $rows = ArticleIndex::callKeywords($text); - $this->assertNotEmpty($rows, 'Unable to call keywords!'); - $this->assertArrayHasKey('tokenized', $rows[0], 'No tokenized keyword!'); - $this->assertArrayHasKey('normalized', $rows[0], 'No normalized keyword!'); - } - - /** - * @see https://github.com/yiisoft/yii2/issues/4830 - * - * @depends testFind - */ - public function testFindQueryReuse() - { - $result = ArticleIndex::find()->andWhere(['author_id' => 1]); - $this->assertTrue($result->one() instanceof ArticleIndex); - $this->assertTrue($result->one() instanceof ArticleIndex); - - $result = ArticleIndex::find()->match('dogs'); - $this->assertTrue($result->one() instanceof ArticleIndex); - $this->assertTrue($result->one() instanceof ArticleIndex); - } -} diff --git a/tests/unit/extensions/sphinx/ActiveRelationTest.php b/tests/unit/extensions/sphinx/ActiveRelationTest.php deleted file mode 100644 index 516aa4605c..0000000000 --- a/tests/unit/extensions/sphinx/ActiveRelationTest.php +++ /dev/null @@ -1,58 +0,0 @@ -getConnection(); - ActiveRecordDb::$db = $this->getDbConnection(); - } - - // Tests : - - public function testFindLazy() - { - /* @var $article ArticleDb */ - $article = ArticleDb::findOne(['id' => 2]); - $this->assertFalse($article->isRelationPopulated('index')); - $index = $article->index; - $this->assertTrue($article->isRelationPopulated('index')); - $this->assertTrue($index instanceof ArticleIndex); - $this->assertEquals(1, count($article->relatedRecords)); - $this->assertEquals($article->id, $index->id); - } - - public function testFindEager() - { - $articles = ArticleDb::find()->with('index')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles[0]->isRelationPopulated('index')); - $this->assertTrue($articles[1]->isRelationPopulated('index')); - $this->assertTrue($articles[0]->index instanceof ArticleIndex); - $this->assertTrue($articles[1]->index instanceof ArticleIndex); - } - - /** - * @see https://github.com/yiisoft/yii2/issues/4018 - */ - public function testFindCompositeLink() - { - $articles = ArticleIndex::find()->with('sourceCompositeLink')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles[0]->isRelationPopulated('sourceCompositeLink')); - $this->assertNotEmpty($articles[0]->sourceCompositeLink); - $this->assertTrue($articles[1]->isRelationPopulated('sourceCompositeLink')); - $this->assertNotEmpty($articles[1]->sourceCompositeLink); - } -} diff --git a/tests/unit/extensions/sphinx/ColumnSchemaTest.php b/tests/unit/extensions/sphinx/ColumnSchemaTest.php deleted file mode 100644 index fb0e67a3f2..0000000000 --- a/tests/unit/extensions/sphinx/ColumnSchemaTest.php +++ /dev/null @@ -1,55 +0,0 @@ -type = $type; - $columnSchema->phpType = $phpType; - $this->assertEquals($expectedResult, $columnSchema->phpTypecast($value)); - } -} diff --git a/tests/unit/extensions/sphinx/CommandTest.php b/tests/unit/extensions/sphinx/CommandTest.php deleted file mode 100644 index 9d09231642..0000000000 --- a/tests/unit/extensions/sphinx/CommandTest.php +++ /dev/null @@ -1,409 +0,0 @@ -truncateRuntimeIndex('yii2_test_rt_index'); - parent::tearDown(); - } - - // Tests : - - public function testConstruct() - { - $db = $this->getConnection(false); - - // null - $command = $db->createCommand(); - $this->assertEquals(null, $command->sql); - - // string - $sql = 'SELECT * FROM yii2_test_item_index'; - $params = [ - 'name' => 'value' - ]; - $command = $db->createCommand($sql, $params); - $this->assertEquals($sql, $command->sql); - $this->assertEquals($params, $command->params); - } - - public function testGetSetSql() - { - $db = $this->getConnection(false); - - $sql = 'SELECT * FROM yii2_test_item_index'; - $command = $db->createCommand($sql); - $this->assertEquals($sql, $command->sql); - - $sql2 = 'SELECT * FROM yii2_test_item_index'; - $command->sql = $sql2; - $this->assertEquals($sql2, $command->sql); - } - - public function testAutoQuoting() - { - $db = $this->getConnection(false); - - $sql = 'SELECT [[id]], [[t.name]] FROM {{yii2_test_item_index}} t'; - $command = $db->createCommand($sql); - $this->assertEquals("SELECT `id`, `t`.`name` FROM `yii2_test_item_index` t", $command->sql); - } - - public function testPrepareCancel() - { - $db = $this->getConnection(false); - - $command = $db->createCommand('SELECT * FROM yii2_test_item_index'); - $this->assertEquals(null, $command->pdoStatement); - $command->prepare(); - $this->assertNotEquals(null, $command->pdoStatement); - $command->cancel(); - $this->assertEquals(null, $command->pdoStatement); - } - - public function testExecute() - { - $db = $this->getConnection(); - - $sql = 'SELECT COUNT(*) FROM yii2_test_item_index WHERE MATCH(\'wooden\')'; - $command = $db->createCommand($sql); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); - $command->execute(); - } - - public function testQuery() - { - $db = $this->getConnection(); - - // query - $sql = 'SELECT * FROM yii2_test_item_index'; - $reader = $db->createCommand($sql)->query(); - $this->assertTrue($reader instanceof DataReader); - - // queryAll - $rows = $db->createCommand('SELECT * FROM yii2_test_item_index')->queryAll(); - $this->assertEquals(2, count($rows)); - $row = $rows[1]; - $this->assertEquals(2, $row['id']); - $this->assertEquals(2, $row['category_id']); - - $rows = $db->createCommand('SELECT * FROM yii2_test_item_index WHERE id=10')->queryAll(); - $this->assertEquals([], $rows); - - // queryOne - $sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; - $row = $db->createCommand($sql)->queryOne(); - $this->assertEquals(1, $row['id']); - $this->assertEquals(1, $row['category_id']); - - $sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; - $command = $db->createCommand($sql); - $command->prepare(); - $row = $command->queryOne(); - $this->assertEquals(1, $row['id']); - $this->assertEquals(1, $row['category_id']); - - $sql = 'SELECT * FROM yii2_test_item_index WHERE id=10'; - $command = $db->createCommand($sql); - $this->assertFalse($command->queryOne()); - - // queryColumn - $sql = 'SELECT * FROM yii2_test_item_index'; - $column = $db->createCommand($sql)->queryColumn(); - $this->assertEquals(range(1, 2), $column); - - $command = $db->createCommand('SELECT id FROM yii2_test_item_index WHERE id=10'); - $this->assertEquals([], $command->queryColumn()); - - // queryScalar - $sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; - $this->assertEquals($db->createCommand($sql)->queryScalar(), 1); - - $sql = 'SELECT id FROM yii2_test_item_index ORDER BY id ASC'; - $command = $db->createCommand($sql); - $command->prepare(); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('SELECT id FROM yii2_test_item_index WHERE id=10'); - $this->assertFalse($command->queryScalar()); - - $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); - $command->query(); - } - - /** - * @depends testQuery - */ - public function testInsert() - { - $db = $this->getConnection(); - - $command = $db->createCommand()->insert('yii2_test_rt_index', [ - 'title' => 'Test title', - 'content' => 'Test content', - 'type_id' => 2, - 'category' => [1, 2], - 'id' => 1, - ]); - $this->assertEquals(1, $command->execute(), 'Unable to execute insert!'); - - $rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals(1, count($rows), 'No row inserted!'); - } - - /** - * @depends testInsert - */ - public function testBatchInsert() - { - $db = $this->getConnection(); - - $command = $db->createCommand()->batchInsert( - 'yii2_test_rt_index', - [ - 'title', - 'content', - 'type_id', - 'category', - 'id', - ], - [ - [ - 'Test title 1', - 'Test content 1', - 1, - [1, 2], - 1, - ], - [ - 'Test title 2', - 'Test content 2', - 2, - [3, 4], - 2, - ], - ] - ); - $this->assertEquals(2, $command->execute(), 'Unable to execute batch insert!'); - - $rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals(2, count($rows), 'No rows inserted!'); - } - - /** - * @depends testInsert - */ - public function testReplace() - { - $db = $this->getConnection(); - - $command = $db->createCommand()->replace('yii2_test_rt_index', [ - 'title' => 'Test title', - 'content' => 'Test content', - 'type_id' => 2, - 'category' => [1, 2], - 'id' => 1, - ]); - $this->assertEquals(1, $command->execute(), 'Unable to execute replace!'); - - $rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals(1, count($rows), 'No row inserted!'); - - $newTypeId = 5; - $command = $db->createCommand()->replace('yii2_test_rt_index', [ - 'type_id' => $newTypeId, - 'category' => [3, 4], - 'id' => 1, - ]); - $this->assertEquals(1, $command->execute(), 'Unable to update via replace!'); - - list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); - } - - /** - * @depends testReplace - */ - public function testBatchReplace() - { - $db = $this->getConnection(); - - $command = $db->createCommand()->batchReplace( - 'yii2_test_rt_index', - [ - 'title', - 'content', - 'type_id', - 'category', - 'id', - ], - [ - [ - 'Test title 1', - 'Test content 1', - 1, - [1, 2], - 1, - ], - [ - 'Test title 2', - 'Test content 2', - 2, - [3, 4], - 2, - ], - ] - ); - $this->assertEquals(2, $command->execute(), 'Unable to execute batch replace!'); - - $rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals(2, count($rows), 'No rows inserted!'); - - $newTypeId = 5; - $command = $db->createCommand()->replace('yii2_test_rt_index', [ - 'type_id' => $newTypeId, - 'id' => 1, - ]); - $this->assertEquals(1, $command->execute(), 'Unable to update via replace!'); - list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); - } - - /** - * @depends testInsert - */ - public function testUpdate() - { - $db = $this->getConnection(); - - $db->createCommand()->insert('yii2_test_rt_index', [ - 'title' => 'Test title', - 'content' => 'Test content', - 'type_id' => 2, - 'id' => 1, - ])->execute(); - - $newTypeId = 5; - $command = $db->createCommand()->update( - 'yii2_test_rt_index', - [ - 'type_id' => $newTypeId, - 'category' => [3, 4], - ], - 'id = 1' - ); - $this->assertEquals(1, $command->execute(), 'Unable to execute update!'); - - list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); - } - - /** - * @depends testUpdate - */ - public function testUpdateWithOptions() - { - $db = $this->getConnection(); - - $db->createCommand()->insert('yii2_test_rt_index', [ - 'title' => 'Test title', - 'content' => 'Test content', - 'type_id' => 2, - 'id' => 1, - ])->execute(); - - $newTypeId = 5; - $command = $db->createCommand()->update( - 'yii2_test_rt_index', - [ - 'type_id' => $newTypeId, - 'non_existing_attribute' => 10, - ], - 'id = 1', - [], - [ - 'ignore_nonexistent_columns' => 1 - ] - ); - $this->assertEquals(1, $command->execute(), 'Unable to execute update!'); - } - - /** - * @depends testInsert - */ - public function testDelete() - { - $db = $this->getConnection(); - - $db->createCommand()->insert('yii2_test_rt_index', [ - 'title' => 'Test title', - 'content' => 'Test content', - 'type_id' => 2, - 'id' => 1, - ])->execute(); - - $command = $db->createCommand()->delete('yii2_test_rt_index', 'id = 1'); - $this->assertEquals(1, $command->execute(), 'Unable to execute delete!'); - - $rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); - $this->assertEquals(0, count($rows), 'Unable to delete record!'); - } - - /** - * @depends testQuery - */ - public function testCallSnippets() - { - $db = $this->getConnection(); - - $query = 'pencil'; - $source = 'Some data sentence about ' . $query; - - $rows = $db->createCommand()->callSnippets('yii2_test_item_index', $source, $query)->queryColumn(); - $this->assertNotEmpty($rows, 'Unable to call snippets!'); - $this->assertContains('' . $query . '', $rows[0], 'Query not present in the snippet!'); - - $rows = $db->createCommand()->callSnippets('yii2_test_item_index', [$source], $query)->queryColumn(); - $this->assertNotEmpty($rows, 'Unable to call snippets for array source!'); - - $options = [ - 'before_match' => '[', - 'after_match' => ']', - 'limit' => 20, - ]; - $snippet = $db->createCommand()->callSnippets('yii2_test_item_index', $source, $query, $options)->queryScalar(); - $this->assertContains($options['before_match'] . $query . $options['after_match'], $snippet, 'Unable to apply options!'); - } - - /** - * @depends testQuery - */ - public function testCallKeywords() - { - $db = $this->getConnection(); - - $text = 'table pencil'; - $rows = $db->createCommand()->callKeywords('yii2_test_item_index', $text)->queryAll(); - $this->assertNotEmpty($rows, 'Unable to call keywords!'); - $this->assertArrayHasKey('tokenized', $rows[0], 'No tokenized keyword!'); - $this->assertArrayHasKey('normalized', $rows[0], 'No normalized keyword!'); - - $text = 'table pencil'; - $rows = $db->createCommand()->callKeywords('yii2_test_item_index', $text, true)->queryAll(); - $this->assertNotEmpty($rows, 'Unable to call keywords with statistic!'); - $this->assertArrayHasKey('docs', $rows[0], 'No docs!'); - $this->assertArrayHasKey('hits', $rows[0], 'No hits!'); - } -} diff --git a/tests/unit/extensions/sphinx/ConnectionTest.php b/tests/unit/extensions/sphinx/ConnectionTest.php deleted file mode 100644 index 10bf232ca2..0000000000 --- a/tests/unit/extensions/sphinx/ConnectionTest.php +++ /dev/null @@ -1,42 +0,0 @@ -getConnection(false); - $params = $this->sphinxConfig; - - $this->assertEquals($params['dsn'], $connection->dsn); - $this->assertEquals($params['username'], $connection->username); - $this->assertEquals($params['password'], $connection->password); - } - - public function testOpenClose() - { - $connection = $this->getConnection(false, false); - - $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->pdo); - - $connection->open(); - $this->assertTrue($connection->isActive); - $this->assertTrue($connection->pdo instanceof \PDO); - - $connection->close(); - $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->pdo); - - $connection = new Connection; - $connection->dsn = 'unknown::memory:'; - $this->setExpectedException('yii\db\Exception'); - $connection->open(); - } -} diff --git a/tests/unit/extensions/sphinx/ExternalActiveRelationTest.php b/tests/unit/extensions/sphinx/ExternalActiveRelationTest.php deleted file mode 100644 index 7d7dd1ffd0..0000000000 --- a/tests/unit/extensions/sphinx/ExternalActiveRelationTest.php +++ /dev/null @@ -1,87 +0,0 @@ -getConnection(); - ActiveRecordDb::$db = $this->getDbConnection(); - } - - // Tests : - - public function testFindLazy() - { - /* @var $article ArticleIndex */ - $article = ArticleIndex::findOne(['id' => 2]); - - // has one : - $this->assertFalse($article->isRelationPopulated('source')); - $source = $article->source; - $this->assertTrue($article->isRelationPopulated('source')); - $this->assertTrue($source instanceof ArticleDb); - $this->assertEquals(1, count($article->relatedRecords)); - - // has many : - $this->assertFalse($article->isRelationPopulated('tags')); - $tags = $article->tags; - $this->assertTrue($article->isRelationPopulated('tags')); - $this->assertEquals(count($article->tag), count($tags)); - $this->assertTrue($tags[0] instanceof TagDb); - foreach ($tags as $tag) { - $this->assertTrue(in_array($tag->id, $article->tag)); - } - } - - public function testFindEager() - { - // has one : - $articles = ArticleIndex::find()->with('source')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles[0]->isRelationPopulated('source')); - $this->assertTrue($articles[1]->isRelationPopulated('source')); - $this->assertTrue($articles[0]->source instanceof ArticleDb); - $this->assertTrue($articles[1]->source instanceof ArticleDb); - - // has many : - $articles = ArticleIndex::find()->with('tags')->all(); - $this->assertEquals(2, count($articles)); - $this->assertTrue($articles[0]->isRelationPopulated('tags')); - $this->assertTrue($articles[1]->isRelationPopulated('tags')); - foreach ($articles as $article) { - $this->assertTrue($article->isRelationPopulated('tags')); - $tags = $article->tags; - $this->assertEquals(count($article->tag), count($tags)); - //var_dump($tags); - $this->assertTrue($tags[0] instanceof TagDb); - foreach ($tags as $tag) { - $this->assertTrue(in_array($tag->id, $article->tag)); - } - } - } - - /** - * @depends testFindEager - */ - public function testFindWithSnippets() - { - $articles = ArticleIndex::find() - ->match('about') - ->with('source') - ->snippetByModel() - ->all(); - $this->assertEquals(2, count($articles)); - } -} diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php deleted file mode 100644 index 2fdf7fbdb0..0000000000 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ /dev/null @@ -1,366 +0,0 @@ -select('*'); - $this->assertEquals(['*'], $query->select); - $this->assertNull($query->distinct); - $this->assertEquals(null, $query->selectOption); - - $query = new Query; - $query->select('id, name', 'something')->distinct(true); - $this->assertEquals(['id', 'name'], $query->select); - $this->assertTrue($query->distinct); - $this->assertEquals('something', $query->selectOption); - } - - public function testFrom() - { - $query = new Query; - $query->from('user'); - $this->assertEquals(['user'], $query->from); - } - - public function testMatch() - { - $query = new Query; - $match = 'test match'; - $query->match($match); - $this->assertEquals($match, $query->match); - - $command = $query->createCommand($this->getConnection(false)); - $this->assertContains('MATCH(', $command->getSql(), 'No MATCH operator present!'); - $this->assertContains($match, $command->params, 'No match query among params!'); - } - - public function testWhere() - { - $query = new Query; - $query->where('id = :id', [':id' => 1]); - $this->assertEquals('id = :id', $query->where); - $this->assertEquals([':id' => 1], $query->params); - - $query->andWhere('name = :name', [':name' => 'something']); - $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); - - $query->orWhere('age = :age', [':age' => '30']); - $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); - } - - public function testFilterWhere() - { - // should work with hash format - $query = new Query; - $query->filterWhere([ - 'id' => 0, - 'title' => ' ', - 'author_ids' => [], - ]); - $this->assertEquals(['id' => 0], $query->where); - - $query->andFilterWhere(['status' => null]); - $this->assertEquals(['id' => 0], $query->where); - - $query->orFilterWhere(['name' => '']); - $this->assertEquals(['id' => 0], $query->where); - - // should work with operator format - $query = new Query; - $condition = ['like', 'name', 'Alex']; - $query->filterWhere($condition); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->orFilterWhere(['not between', 'id', null, null]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not in', 'id', []]); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or like', 'id', '']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['not like', 'id', ' ']); - $this->assertEquals($condition, $query->where); - - $query->andFilterWhere(['or not like', 'id', null]); - $this->assertEquals($condition, $query->where); - } - - public function testFilterWhereRecursively() - { - $query = new Query(); - $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); - $this->assertEquals(['and', ['id' => 1]], $query->where); - } - - public function testGroup() - { - $query = new Query; - $query->groupBy('team'); - $this->assertEquals(['team'], $query->groupBy); - - $query->addGroupBy('company'); - $this->assertEquals(['team', 'company'], $query->groupBy); - - $query->addGroupBy('age'); - $this->assertEquals(['team', 'company', 'age'], $query->groupBy); - } - - public function testHaving() - { - $query = new Query; - $query->having('id = :id', [':id' => 1]); - $this->assertEquals('id = :id', $query->having); - $this->assertEquals([':id' => 1], $query->params); - - $query->andHaving('name = :name', [':name' => 'something']); - $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->having); - $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); - - $query->orHaving('age = :age', [':age' => '30']); - $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->having); - $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); - } - - public function testOrder() - { - $query = new Query; - $query->orderBy('team'); - $this->assertEquals(['team' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('company'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); - - $query->addOrderBy('age'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); - - $query->addOrderBy(['age' => SORT_DESC]); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); - - $query->addOrderBy('age ASC, company DESC'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); - } - - public function testLimitOffset() - { - $query = new Query; - $query->limit(10)->offset(5); - $this->assertEquals(10, $query->limit); - $this->assertEquals(5, $query->offset); - } - - public function testWithin() - { - $query = new Query; - $query->within('team'); - $this->assertEquals(['team' => SORT_ASC], $query->within); - - $query->addWithin('company'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->within); - - $query->addWithin('age'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->within); - - $query->addWithin(['age' => SORT_DESC]); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->within); - - $query->addWithin('age ASC, company DESC'); - $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->within); - } - - public function testOptions() - { - $query = new Query; - $options = [ - 'cutoff' => 50, - 'max_matches' => 50, - ]; - $query->options($options); - $this->assertEquals($options, $query->options); - - $newMaxMatches = $options['max_matches'] + 10; - $query->addOptions(['max_matches' => $newMaxMatches]); - $this->assertEquals($newMaxMatches, $query->options['max_matches']); - } - - public function testRun() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('yii2_test_article_index') - ->match('about') - ->options([ - 'cutoff' => 50, - 'field_weights' => [ - 'title' => 10, - 'content' => 3, - ], - ]) - ->all($connection); - $this->assertNotEmpty($rows); - } - - /** - * @depends testRun - */ - public function testSnippet() - { - $connection = $this->getConnection(); - - $match = 'about'; - $snippetPrefix = 'snippet#'; - $snippetCallback = function () use ($match, $snippetPrefix) { - return [ - $snippetPrefix . '1: ' . $match, - $snippetPrefix . '2: ' . $match, - ]; - }; - $snippetOptions = [ - 'before_match' => '[', - 'after_match' => ']', - ]; - - $query = new Query; - $rows = $query->from('yii2_test_article_index') - ->match($match) - ->snippetCallback($snippetCallback) - ->snippetOptions($snippetOptions) - ->all($connection); - $this->assertNotEmpty($rows); - foreach ($rows as $row) { - $this->assertContains($snippetPrefix, $row['snippet'], 'Snippet source not present!'); - $this->assertContains($snippetOptions['before_match'] . $match, $row['snippet'] . $snippetOptions['after_match'], 'Options not applied!'); - } - } - - public function testCount() - { - $connection = $this->getConnection(); - - $query = new Query; - $count = $query->from('yii2_test_article_index') - ->match('about') - ->count('*', $connection); - $this->assertEquals(2, $count); - } - - /** - * @depends testRun - */ - public function testWhereSpecialCharValue() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('yii2_test_article_index') - ->andWhere(['author_id' => 'some"']) - ->all($connection); - $this->assertEmpty($rows); - } - - /** - * Data provider for [[testMatchSpecialCharValue()]] - * @return array test data - */ - public function dataProviderMatchSpecialCharValue() - { - return [ - ["'"], - ['"'], - ['@'], - ['\\'], - ['()'], - ['<<<'], - ['>>>'], - ["\x00"], - ["\n"], - ["\r"], - ["\x1a"], - ['\\' . "'"], - ['\\' . '"'], - ]; - } - - /** - * @dataProvider dataProviderMatchSpecialCharValue - * @depends testRun - * - * @param string $char char to be tested - * - * @see https://github.com/yiisoft/yii2/issues/3668 - */ - public function testMatchSpecialCharValue($char) - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('yii2_test_article_index') - ->match('about' . $char) - ->all($connection); - $this->assertTrue(is_array($rows)); // no query error - } - - /** - * @depends testMatchSpecialCharValue - */ - public function testMatchComplex() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('yii2_test_article_index') - ->match(new Expression(':match', ['match' => '@(content) ' . $connection->escapeMatchValue('about\\"')])) - ->all($connection); - $this->assertNotEmpty($rows); - } - - /** - * @depends testRun - * - * @see https://github.com/yiisoft/yii2/issues/4375 - */ - public function testRunOnDistributedIndex() - { - $connection = $this->getConnection(); - - $query = new Query; - $rows = $query->from('yii2_test_distributed') - ->match('about') - ->options([ - 'cutoff' => 50, - 'field_weights' => [ - 'title' => 10, - 'content' => 3, - ], - ]) - ->all($connection); - $this->assertNotEmpty($rows); - } -} diff --git a/tests/unit/extensions/sphinx/SchemaTest.php b/tests/unit/extensions/sphinx/SchemaTest.php deleted file mode 100644 index 38d8c44c2b..0000000000 --- a/tests/unit/extensions/sphinx/SchemaTest.php +++ /dev/null @@ -1,83 +0,0 @@ -getConnection()->schema; - - $indexes = $schema->getIndexNames(); - $this->assertContains('yii2_test_article_index', $indexes); - $this->assertContains('yii2_test_item_index', $indexes); - $this->assertContains('yii2_test_rt_index', $indexes); - } - - public function testGetIndexSchemas() - { - $schema = $this->getConnection()->schema; - - $indexes = $schema->getIndexSchemas(); - $this->assertEquals(count($schema->getIndexNames()), count($indexes)); - foreach ($indexes as $index) { - $this->assertInstanceOf('yii\sphinx\IndexSchema', $index); - } - } - - public function testGetNonExistingIndexSchema() - { - $this->assertNull($this->getConnection()->schema->getIndexSchema('non_existing_index')); - } - - public function testSchemaRefresh() - { - $schema = $this->getConnection()->schema; - - $schema->db->enableSchemaCache = true; - $schema->db->schemaCache = new FileCache(); - $noCacheIndex = $schema->getIndexSchema('yii2_test_rt_index', true); - $cachedIndex = $schema->getIndexSchema('yii2_test_rt_index', true); - $this->assertEquals($noCacheIndex, $cachedIndex); - } - - public function testGetPDOType() - { - $values = [ - [null, \PDO::PARAM_NULL], - ['', \PDO::PARAM_STR], - ['hello', \PDO::PARAM_STR], - [0, \PDO::PARAM_INT], - [1, \PDO::PARAM_INT], - [1337, \PDO::PARAM_INT], - [true, \PDO::PARAM_BOOL], - [false, \PDO::PARAM_BOOL], - [$fp = fopen(__FILE__, 'rb'), \PDO::PARAM_LOB], - ]; - - $schema = $this->getConnection()->schema; - - foreach ($values as $value) { - $this->assertEquals($value[1], $schema->getPdoType($value[0])); - } - fclose($fp); - } - - public function testIndexType() - { - $schema = $this->getConnection()->schema; - - $index = $schema->getIndexSchema('yii2_test_article_index'); - $this->assertEquals('local', $index->type); - $this->assertFalse($index->isRuntime); - - $index = $schema->getIndexSchema('yii2_test_rt_index'); - $this->assertEquals('rt', $index->type); - $this->assertTrue($index->isRuntime); - } -} diff --git a/tests/unit/extensions/sphinx/SphinxTestCase.php b/tests/unit/extensions/sphinx/SphinxTestCase.php deleted file mode 100644 index 98c621c3d0..0000000000 --- a/tests/unit/extensions/sphinx/SphinxTestCase.php +++ /dev/null @@ -1,162 +0,0 @@ - 'mysql:host=127.0.0.1;port=9306;', - 'username' => '', - 'password' => '', - ]; - /** - * @var Connection Sphinx connection instance. - */ - protected $sphinx; - /** - * @var array Database connection configuration. - */ - protected $dbConfig = [ - 'dsn' => 'mysql:host=127.0.0.1;', - 'username' => '', - 'password' => '', - ]; - /** - * @var \yii\db\Connection database connection instance. - */ - protected $db; - - public static function setUpBeforeClass() - { - static::loadClassMap(); - } - - protected function setUp() - { - parent::setUp(); - if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) { - $this->markTestSkipped('pdo and pdo_mysql extension are required.'); - } - $config = self::getParam('sphinx'); - if (!empty($config)) { - $this->sphinxConfig = $config['sphinx']; - $this->dbConfig = $config['db']; - } - // check whether sphinx is running and skip tests if not. - if (preg_match('/host=([\w\d.]+)/i', $this->sphinxConfig['dsn'], $hm) && preg_match('/port=(\d+)/i', $this->sphinxConfig['dsn'], $pm)) { - if (!@stream_socket_client($hm[1] . ':' . $pm[1], $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No Sphinx searchd running at ' . $hm[1] . ':' . $pm[1] . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - } - $this->mockApplication(); - static::loadClassMap(); - } - - protected function tearDown() - { - if ($this->sphinx) { - $this->sphinx->close(); - } - $this->destroyApplication(); - } - - /** - * Adds sphinx extension files to [[Yii::$classPath]], - * avoiding the necessity of usage Composer autoloader. - */ - protected static function loadClassMap() - { - $baseNameSpace = 'yii/sphinx'; - $basePath = realpath(__DIR__. '/../../../../extensions/sphinx'); - $files = FileHelper::findFiles($basePath); - foreach ($files as $file) { - $classRelativePath = str_replace($basePath, '', $file); - $classFullName = str_replace(['/', '.php'], ['\\', ''], $baseNameSpace . $classRelativePath); - Yii::$classMap[$classFullName] = $file; - } - } - - /** - * @param boolean $reset whether to clean up the test database - * @param boolean $open whether to open test database - * @return \yii\sphinx\Connection - */ - public function getConnection($reset = false, $open = true) - { - if (!$reset && $this->sphinx) { - return $this->sphinx; - } - $db = new Connection; - $db->dsn = $this->sphinxConfig['dsn']; - if (isset($this->sphinxConfig['username'])) { - $db->username = $this->sphinxConfig['username']; - $db->password = $this->sphinxConfig['password']; - } - if (isset($this->sphinxConfig['attributes'])) { - $db->attributes = $this->sphinxConfig['attributes']; - } - if ($open) { - $db->open(); - } - $this->sphinx = $db; - - return $db; - } - - /** - * Truncates the runtime index. - * @param string $indexName index name. - */ - protected function truncateRuntimeIndex($indexName) - { - if ($this->sphinx) { - $this->sphinx->createCommand('TRUNCATE RTINDEX ' . $indexName)->execute(); - } - } - - /** - * @param boolean $reset whether to clean up the test database - * @param boolean $open whether to open and populate test database - * @return \yii\db\Connection - */ - public function getDbConnection($reset = true, $open = true) - { - if (!$reset && $this->db) { - return $this->db; - } - $db = new \yii\db\Connection; - $db->dsn = $this->dbConfig['dsn']; - if (isset($this->dbConfig['username'])) { - $db->username = $this->dbConfig['username']; - $db->password = $this->dbConfig['password']; - } - if (isset($this->dbConfig['attributes'])) { - $db->attributes = $this->dbConfig['attributes']; - } - if ($open) { - $db->open(); - if (!empty($this->dbConfig['fixture'])) { - $lines = explode(';', file_get_contents($this->dbConfig['fixture'])); - foreach ($lines as $line) { - if (trim($line) !== '') { - $db->pdo->exec($line); - } - } - } - } - $this->db = $db; - - return $db; - } -} diff --git a/tests/unit/extensions/swiftmailer/MailerTest.php b/tests/unit/extensions/swiftmailer/MailerTest.php deleted file mode 100644 index e5236bf8bd..0000000000 --- a/tests/unit/extensions/swiftmailer/MailerTest.php +++ /dev/null @@ -1,125 +0,0 @@ -mockApplication([ - 'components' => [ - 'email' => $this->createTestEmailComponent() - ] - ]); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new Mailer(); - - return $component; - } - - // Tests : - - public function testSetupTransport() - { - $mailer = new Mailer(); - - $transport = \Swift_MailTransport::newInstance(); - $mailer->setTransport($transport); - $this->assertEquals($transport, $mailer->getTransport(), 'Unable to setup transport!'); - } - - /** - * @depends testSetupTransport - */ - public function testConfigureTransport() - { - $mailer = new Mailer(); - - $transportConfig = [ - 'class' => 'Swift_SmtpTransport', - 'host' => 'localhost', - 'username' => 'username', - 'password' => 'password', - ]; - $mailer->setTransport($transportConfig); - $transport = $mailer->getTransport(); - $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); - $this->assertEquals($transportConfig['class'], get_class($transport), 'Invalid transport class!'); - $this->assertEquals($transportConfig['host'], $transport->getHost(), 'Invalid transport host!'); - } - - /** - * @depends testConfigureTransport - */ - public function testConfigureTransportConstruct() - { - $mailer = new Mailer(); - - $class = 'Swift_SmtpTransport'; - $host = 'some.test.host'; - $port = 999; - $transportConfig = [ - 'class' => $class, - 'constructArgs' => [ - $host, - $port, - ], - ]; - $mailer->setTransport($transportConfig); - $transport = $mailer->getTransport(); - $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); - $this->assertEquals($class, get_class($transport), 'Invalid transport class!'); - $this->assertEquals($host, $transport->getHost(), 'Invalid transport host!'); - $this->assertEquals($port, $transport->getPort(), 'Invalid transport host!'); - } - - /** - * @depends testConfigureTransportConstruct - */ - public function testConfigureTransportWithPlugins() - { - $mailer = new Mailer(); - - $pluginClass = 'Swift_Plugins_ThrottlerPlugin'; - $rate = 10; - - $transportConfig = [ - 'class' => 'Swift_SmtpTransport', - 'plugins' => [ - [ - 'class' => $pluginClass, - 'constructArgs' => [ - $rate, - ], - ], - ], - ]; - $mailer->setTransport($transportConfig); - $transport = $mailer->getTransport(); - $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); - $this->assertContains(':' . $pluginClass . ':', print_r($transport, true), 'Plugin not added'); - } - - public function testGetSwiftMailer() - { - $mailer = new Mailer(); - $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); - } -} diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php deleted file mode 100644 index a0c88de13b..0000000000 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ /dev/null @@ -1,366 +0,0 @@ -mockApplication([ - 'components' => [ - 'mailer' => $this->createTestEmailComponent() - ] - ]); - $filePath = $this->getTestFilePath(); - if (!file_exists($filePath)) { - FileHelper::createDirectory($filePath); - } - } - - public function tearDown() - { - $filePath = $this->getTestFilePath(); - if (file_exists($filePath)) { - FileHelper::removeDirectory($filePath); - } - } - - /** - * @return string test file path. - */ - protected function getTestFilePath() - { - return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new Mailer([ - 'useFileTransport' => true, - ]); - - return $component; - } - - /** - * @return Message test message instance. - */ - protected function createTestMessage() - { - return Yii::$app->get('mailer')->compose(); - } - - /** - * Creates image file with given text. - * @param string $fileName file name. - * @param string $text text to be applied on image. - * @return string image file full name. - */ - protected function createImageFile($fileName = 'test.jpg', $text = 'Test Image') - { - if (!function_exists('imagecreatetruecolor')) { - $this->markTestSkipped('GD lib required.'); - } - $fileFullName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $fileName; - $image = imagecreatetruecolor(120, 20); - $textColor = imagecolorallocate($image, 233, 14, 91); - imagestring($image, 1, 5, 5, $text, $textColor); - imagejpeg($image, $fileFullName); - imagedestroy($image); - - return $fileFullName; - } - - /** - * Finds the attachment object in the message. - * @param Message $message message instance - * @return null|\Swift_Mime_Attachment attachment instance. - */ - protected function getAttachment(Message $message) - { - $messageParts = $message->getSwiftMessage()->getChildren(); - $attachment = null; - foreach ($messageParts as $part) { - if ($part instanceof \Swift_Mime_Attachment) { - $attachment = $part; - break; - } - } - - return $attachment; - } - - // Tests : - - public function testGetSwiftMessage() - { - $message = new Message(); - $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); - } - - /** - * @depends testGetSwiftMessage - */ - public function testSetGet() - { - $message = new Message(); - - $charset = 'utf-16'; - $message->setCharset($charset); - $this->assertEquals($charset, $message->getCharset(), 'Unable to set charset!'); - - $subject = 'Test Subject'; - $message->setSubject($subject); - $this->assertEquals($subject, $message->getSubject(), 'Unable to set subject!'); - - $from = 'from@somedomain.com'; - $message->setFrom($from); - $this->assertContains($from, array_keys($message->getFrom()), 'Unable to set from!'); - - $replyTo = 'reply-to@somedomain.com'; - $message->setReplyTo($replyTo); - $this->assertContains($replyTo, array_keys($message->getReplyTo()), 'Unable to set replyTo!'); - - $to = 'someuser@somedomain.com'; - $message->setTo($to); - $this->assertContains($to, array_keys($message->getTo()), 'Unable to set to!'); - - $cc = 'ccuser@somedomain.com'; - $message->setCc($cc); - $this->assertContains($cc, array_keys($message->getCc()), 'Unable to set cc!'); - - $bcc = 'bccuser@somedomain.com'; - $message->setBcc($bcc); - $this->assertContains($bcc, array_keys($message->getBcc()), 'Unable to set bcc!'); - } - - /** - * @depends testGetSwiftMessage - */ - public function testSetupHeaders() - { - $charset = 'utf-16'; - $subject = 'Test Subject'; - $from = 'from@somedomain.com'; - $replyTo = 'reply-to@somedomain.com'; - $to = 'someuser@somedomain.com'; - $cc = 'ccuser@somedomain.com'; - $bcc = 'bccuser@somedomain.com'; - - $messageString = $this->createTestMessage() - ->setCharset($charset) - ->setSubject($subject) - ->setFrom($from) - ->setReplyTo($replyTo) - ->setTo($to) - ->setCc($cc) - ->setBcc($bcc) - ->toString(); - - $this->assertContains('charset=' . $charset, $messageString, 'Incorrect charset!'); - $this->assertContains('Subject: ' . $subject, $messageString, 'Incorrect "Subject" header!'); - $this->assertContains('From: ' . $from, $messageString, 'Incorrect "From" header!'); - $this->assertContains('Reply-To: ' . $replyTo, $messageString, 'Incorrect "Reply-To" header!'); - $this->assertContains('To: ' . $to, $messageString, 'Incorrect "To" header!'); - $this->assertContains('Cc: ' . $cc, $messageString, 'Incorrect "Cc" header!'); - $this->assertContains('Bcc: ' . $bcc, $messageString, 'Incorrect "Bcc" header!'); - } - - /** - * @depends testGetSwiftMessage - */ - public function testSend() - { - $message = $this->createTestMessage(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Test'); - $message->setTextBody('Yii Swift Test body'); - $this->assertTrue($message->send()); - } - - /** - * @depends testSend - */ - public function testAttachFile() - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Attach File Test'); - $message->setTextBody('Yii Swift Attach File Test body'); - $fileName = __FILE__; - $message->attach($fileName); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertContains($attachment->getFilename(), $fileName, 'Invalid file name!'); - } - - /** - * @depends testSend - */ - public function testAttachContent() - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Create Attachment Test'); - $message->setTextBody('Yii Swift Create Attachment Test body'); - $fileName = 'test.txt'; - $fileContent = 'Test attachment content'; - $message->attachContent($fileContent, ['fileName' => $fileName]); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertEquals($fileName, $attachment->getFilename(), 'Invalid file name!'); - } - - /** - * @depends testSend - */ - public function testEmbedFile() - { - $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); - - $message = $this->createTestMessage(); - - $cid = $message->embed($fileName); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Embed File Test'); - $message->setHtmlBody('Embed image: pic'); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertContains($attachment->getFilename(), $fileName, 'Invalid file name!'); - } - - /** - * @depends testSend - */ - public function testEmbedContent() - { - $fileFullName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); - $message = $this->createTestMessage(); - - $fileName = basename($fileFullName); - $contentType = 'image/jpeg'; - $fileContent = file_get_contents($fileFullName); - - $cid = $message->embedContent($fileContent, ['fileName' => $fileName, 'contentType' => $contentType]); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Embed File Test'); - $message->setHtmlBody('Embed image: pic'); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertEquals($fileName, $attachment->getFilename(), 'Invalid file name!'); - $this->assertEquals($contentType, $attachment->getContentType(), 'Invalid content type!'); - } - - /** - * @depends testSend - */ - public function testSendAlternativeBody() - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Alternative Body Test'); - $message->setHtmlBody('Yii Swift test HTML body'); - $message->setTextBody('Yii Swift test plain text body'); - - $this->assertTrue($message->send()); - - $messageParts = $message->getSwiftMessage()->getChildren(); - $textPresent = false; - $htmlPresent = false; - foreach ($messageParts as $part) { - if (!($part instanceof \Swift_Mime_Attachment)) { - /* @var $part \Swift_Mime_MimePart */ - if ($part->getContentType() == 'text/plain') { - $textPresent = true; - } - if ($part->getContentType() == 'text/html') { - $htmlPresent = true; - } - } - } - $this->assertTrue($textPresent, 'No text!'); - $this->assertTrue($htmlPresent, 'No HTML!'); - } - - /** - * @depends testGetSwiftMessage - */ - public function testSerialize() - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Alternative Body Test'); - $message->setTextBody('Yii Swift test plain text body'); - - $serializedMessage = serialize($message); - $this->assertNotEmpty($serializedMessage, 'Unable to serialize message!'); - - $unserializedMessaage = unserialize($serializedMessage); - $this->assertEquals($message, $unserializedMessaage, 'Unable to unserialize message!'); - } - - /** - * @depends testSendAlternativeBody - */ - public function testAlternativeBodyCharset() - { - $message = $this->createTestMessage(); - $charset = 'windows-1251'; - $message->setCharset($charset); - - $message->setTextBody('some text'); - $message->setHtmlBody('some html'); - $content = $message->toString(); - $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body.'); - - $message->setTextBody('some text override'); - $content = $message->toString(); - $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body override.'); - } -} diff --git a/tests/unit/extensions/twig/ViewRendererTest.php b/tests/unit/extensions/twig/ViewRendererTest.php deleted file mode 100644 index cd13c81971..0000000000 --- a/tests/unit/extensions/twig/ViewRendererTest.php +++ /dev/null @@ -1,161 +0,0 @@ - - * @author Carsten Brandt - */ -class ViewRendererTest extends DatabaseTestCase -{ - protected $driverName = 'sqlite'; - - protected function setUp() - { - parent::setUp(); - $this->mockWebApplication(); - } - - protected function tearDown() - { - parent::tearDown(); - FileHelper::removeDirectory(Yii::getAlias('@runtime/assets')); - } - - /** - * https://github.com/yiisoft/yii2/issues/1755 - */ - public function testLayoutAssets() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/layout.twig'); - - $this->assertEquals(1, preg_match('#\s*#', $content), 'Content does not contain the jquery js:' . $content); - } - - public function testAppGlobal() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/layout.twig'); - - $this->assertEquals(1, preg_match('##', $content), 'Content does not contain charset:' . $content); - } - - /** - * https://github.com/yiisoft/yii2/issues/3877 - */ - public function testLexerOptions() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/comments.twig'); - - $this->assertFalse(strpos($content, 'CUSTOM_LEXER_TWIG_COMMENT'), 'Custom comment lexerOptions were not applied: ' . $content); - $this->assertTrue(strpos($content, 'DEFAULT_TWIG_COMMENT') !== false, 'Default comment style was not modified via lexerOptions:' . $content); - } - - public function testForm() - { - $view = $this->mockView(); - $model = new Singer(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/form.twig', ['model' => $model]); - $this->assertEquals(1, preg_match('#
        .*?
        #s', $content), 'Content does not contain form:' . $content); - } - - public function testCalls() - { - $view = $this->mockView(); - $model = new Singer(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/calls.twig', ['model' => $model]); - $this->assertFalse(strpos($content, 'silence'), 'silence should not be echoed when void() used: ' . $content); - $this->assertTrue(strpos($content, 'echo') !== false, 'echo should be there:' . $content); - $this->assertTrue(strpos($content, 'variable') !== false, 'variable should be there:' . $content); - } - - public function testInheritance() - { - $view = $this->mockView(); - $content = $view->renderFile('@yiiunit/extensions/twig/views/extends2.twig'); - $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); - $this->assertTrue(strpos($content, 'extends2 block') !== false, 'extends2 block should be there:' . $content); - $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); - - $content = $view->renderFile('@yiiunit/extensions/twig/views/extends3.twig'); - $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); - $this->assertTrue(strpos($content, 'extends3 block') !== false, 'extends3 block should be there:' . $content); - $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); - } - - public function testChangeTitle() - { - $view = $this->mockView(); - $view->title = 'Original title'; - - $content = $view->renderFile('@yiiunit/extensions/twig/views/changeTitle.twig'); - $this->assertTrue(strpos($content, 'New title') !== false, 'New title should be there:' . $content); - $this->assertFalse(strpos($content, 'Original title') !== false, 'Original title should not be there:' . $content); - } - - public function testNullsInAr() - { - $view = $this->mockView(); - $order = new Order(); - $order::$db = $this->getConnection(); - $view->renderFile('@yiiunit/extensions/twig/views/nulls.twig', ['order' => $order]); - } - - /** - * Mocks view instance - * @return View - */ - protected function mockView() - { - return new View([ - 'renderers' => [ - 'twig' => [ - 'class' => 'yii\twig\ViewRenderer', - 'options' => [ - 'cache' => false, - ], - 'globals' => [ - 'html' => '\yii\helpers\Html', - 'pos_begin' => View::POS_BEGIN, - ], - 'functions' => [ - 't' => '\Yii::t', - 'json_encode' => '\yii\helpers\Json::encode', - ], - 'lexerOptions' => [ - 'tag_comment' => [ '{*', '*}' ], - ], - ], - ], - 'assetManager' => $this->mockAssetManager(), - ]); - } - - /** - * Mocks asset manager - * @return AssetManager - */ - protected function mockAssetManager() - { - $assetDir = Yii::getAlias('@runtime/assets'); - if (!is_dir($assetDir)) { - mkdir($assetDir, 0777, true); - } - - return new AssetManager([ - 'basePath' => $assetDir, - 'baseUrl' => '/assets', - ]); - } -} diff --git a/tests/unit/extensions/twig/views/calls.twig b/tests/unit/extensions/twig/views/calls.twig deleted file mode 100644 index 474512a9d5..0000000000 --- a/tests/unit/extensions/twig/views/calls.twig +++ /dev/null @@ -1,5 +0,0 @@ -{{ json_encode('echo') | raw }} -{{ void(json_encode('silence')) }} - -{% set var = json_encode('variable') %} -{{ json_encode(var) | raw }} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/changeTitle.twig b/tests/unit/extensions/twig/views/changeTitle.twig deleted file mode 100644 index bfa62a4e17..0000000000 --- a/tests/unit/extensions/twig/views/changeTitle.twig +++ /dev/null @@ -1,3 +0,0 @@ -{{ set(this, 'title', 'New title') }} - -{{ this.title }} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/comments.twig b/tests/unit/extensions/twig/views/comments.twig deleted file mode 100644 index a0ee4e1417..0000000000 --- a/tests/unit/extensions/twig/views/comments.twig +++ /dev/null @@ -1,2 +0,0 @@ -{# DEFAULT_TWIG_COMMENT #} -{* CUSTOM_LEXER_TWIG_COMMENT *} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends1.twig b/tests/unit/extensions/twig/views/extends1.twig deleted file mode 100644 index 78b7e79b7d..0000000000 --- a/tests/unit/extensions/twig/views/extends1.twig +++ /dev/null @@ -1,5 +0,0 @@ -Hello, I'm inheritance test! - -{% block test %} -extends1 block -{% endblock %} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends2.twig b/tests/unit/extensions/twig/views/extends2.twig deleted file mode 100644 index b230a1d01b..0000000000 --- a/tests/unit/extensions/twig/views/extends2.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "extends1.twig" %} - -{% block test %} -extends2 block -{% endblock %} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends3.twig b/tests/unit/extensions/twig/views/extends3.twig deleted file mode 100644 index 1870ddf272..0000000000 --- a/tests/unit/extensions/twig/views/extends3.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "@yiiunit/extensions/twig/views/extends1.twig" %} - -{% block test %} -extends3 block -{% endblock %} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/form.twig b/tests/unit/extensions/twig/views/form.twig deleted file mode 100644 index 1d0e00f251..0000000000 --- a/tests/unit/extensions/twig/views/form.twig +++ /dev/null @@ -1,18 +0,0 @@ -{{ use('yii/widgets/ActiveForm') }} - -{% set form = active_form_begin({ - 'id': 'login-form', - 'action' : '/form-handler', - 'options': { - 'class': 'form-horizontal', - } -}) %} - {{ form.field(model, 'firstName') | raw }} -
        -
        - {{ html.submitButton('Login', { - 'class': 'btn btn-primary', - }) | raw }} -
        -
        -{{ active_form_end() }} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/layout.twig b/tests/unit/extensions/twig/views/layout.twig deleted file mode 100644 index 9c50a0736a..0000000000 --- a/tests/unit/extensions/twig/views/layout.twig +++ /dev/null @@ -1,16 +0,0 @@ -{{ use('yii/web/JqueryAsset') }} -{{ register_jquery_asset() }} -{{ begin_page() }} - - - - - {{ html.encode(this.title) }} - {{ head() }} - - -{{ begin_body() }} - body -{{ end_body() }} - -{{ end_page() }} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/nulls.twig b/tests/unit/extensions/twig/views/nulls.twig deleted file mode 100644 index bd81f1d018..0000000000 --- a/tests/unit/extensions/twig/views/nulls.twig +++ /dev/null @@ -1 +0,0 @@ -{{ order.customer }} \ No newline at end of file diff --git a/tests/unit/framework/base/BehaviorTest.php b/tests/unit/framework/base/BehaviorTest.php deleted file mode 100644 index 9a950f6990..0000000000 --- a/tests/unit/framework/base/BehaviorTest.php +++ /dev/null @@ -1,107 +0,0 @@ - __NAMESPACE__ . '\BarBehavior', - ]; - } -} - -class BarBehavior extends Behavior -{ - public $behaviorProperty = 'behavior property'; - - public function behaviorMethod() - { - return 'behavior method'; - } - - public function __call($name, $params) - { - if ($name == 'magicBehaviorMethod') { - return 'Magic Behavior Method Result!'; - } - - return parent::__call($name, $params); - } - - public function hasMethod($name) - { - if ($name == 'magicBehaviorMethod') { - return true; - } - - return parent::hasMethod($name); - } -} - -/** - * @group base - */ -class BehaviorTest extends TestCase -{ - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - - public function testAttachAndAccessing() - { - $bar = new BarClass(); - $behavior = new BarBehavior(); - $bar->attachBehavior('bar', $behavior); - $this->assertEquals('behavior property', $bar->behaviorProperty); - $this->assertEquals('behavior method', $bar->behaviorMethod()); - $this->assertEquals('behavior property', $bar->getBehavior('bar')->behaviorProperty); - $this->assertEquals('behavior method', $bar->getBehavior('bar')->behaviorMethod()); - - $behavior = new BarBehavior(['behaviorProperty' => 'reattached']); - $bar->attachBehavior('bar', $behavior); - $this->assertEquals('reattached', $bar->behaviorProperty); - } - - public function testAutomaticAttach() - { - $foo = new FooClass(); - $this->assertEquals('behavior property', $foo->behaviorProperty); - $this->assertEquals('behavior method', $foo->behaviorMethod()); - } - - public function testMagicMethods() - { - $bar = new BarClass(); - $behavior = new BarBehavior(); - - $this->assertFalse($bar->hasMethod('magicBehaviorMethod')); - $bar->attachBehavior('bar', $behavior); - $this->assertFalse($bar->hasMethod('magicBehaviorMethod', false)); - $this->assertTrue($bar->hasMethod('magicBehaviorMethod')); - - $this->assertEquals('Magic Behavior Method Result!', $bar->magicBehaviorMethod()); - } - - public function testCallUnknownMethod() - { - $bar = new BarClass(); - $behavior = new BarBehavior(); - $this->setExpectedException('yii\base\UnknownMethodException'); - - $this->assertFalse($bar->hasMethod('nomagicBehaviorMethod')); - $bar->attachBehavior('bar', $behavior); - $bar->nomagicBehaviorMethod(); - } -} diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php deleted file mode 100644 index e95d387a3f..0000000000 --- a/tests/unit/framework/base/ComponentTest.php +++ /dev/null @@ -1,451 +0,0 @@ -sender->eventHandled = true; -} - -function globalEventHandler2($event) -{ - $event->sender->eventHandled = true; - $event->handled = true; -} - -/** - * @group base - */ -class ComponentTest extends TestCase -{ - /** - * @var NewComponent - */ - protected $component; - - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - $this->component = new NewComponent(); - } - - protected function tearDown() - { - parent::tearDown(); - $this->component = null; - } - - public function testClone() - { - $component = new NewComponent(); - $behavior = new NewBehavior(); - $component->attachBehavior('a', $behavior); - $this->assertSame($behavior, $component->getBehavior('a')); - $component->on('test', 'fake'); - $this->assertTrue($component->hasEventHandlers('test')); - - $clone = clone $component; - $this->assertNotSame($component, $clone); - $this->assertNull($clone->getBehavior('a')); - $this->assertFalse($clone->hasEventHandlers('test')); - } - - public function testHasProperty() - { - $this->assertTrue($this->component->hasProperty('Text')); - $this->assertTrue($this->component->hasProperty('text')); - $this->assertFalse($this->component->hasProperty('Caption')); - $this->assertTrue($this->component->hasProperty('content')); - $this->assertFalse($this->component->hasProperty('content', false)); - $this->assertFalse($this->component->hasProperty('Content')); - } - - public function testCanGetProperty() - { - $this->assertTrue($this->component->canGetProperty('Text')); - $this->assertTrue($this->component->canGetProperty('text')); - $this->assertFalse($this->component->canGetProperty('Caption')); - $this->assertTrue($this->component->canGetProperty('content')); - $this->assertFalse($this->component->canGetProperty('content', false)); - $this->assertFalse($this->component->canGetProperty('Content')); - } - - public function testCanSetProperty() - { - $this->assertTrue($this->component->canSetProperty('Text')); - $this->assertTrue($this->component->canSetProperty('text')); - $this->assertFalse($this->component->canSetProperty('Object')); - $this->assertFalse($this->component->canSetProperty('Caption')); - $this->assertTrue($this->component->canSetProperty('content')); - $this->assertFalse($this->component->canSetProperty('content', false)); - $this->assertFalse($this->component->canSetProperty('Content')); - - // behavior - $this->assertFalse($this->component->canSetProperty('p2')); - $behavior = new NewBehavior(); - $this->component->attachBehavior('a', $behavior); - $this->assertTrue($this->component->canSetProperty('p2')); - $this->component->detachBehavior('a'); - } - - public function testGetProperty() - { - $this->assertTrue('default' === $this->component->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); - $value2 = $this->component->Caption; - } - - public function testSetProperty() - { - $value = 'new value'; - $this->component->Text = $value; - $this->assertEquals($value, $this->component->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); - $this->component->NewMember = $value; - } - - public function testIsset() - { - $this->assertTrue(isset($this->component->Text)); - $this->assertFalse(empty($this->component->Text)); - - $this->component->Text = ''; - $this->assertTrue(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); - - $this->component->Text = null; - $this->assertFalse(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); - - $this->assertFalse(isset($this->component->p2)); - $this->component->attachBehavior('a', new NewBehavior()); - $this->component->setP2('test'); - $this->assertTrue(isset($this->component->p2)); - } - - public function testCallUnknownMethod() - { - $this->setExpectedException('yii\base\UnknownMethodException'); - $this->component->unknownMethod(); - } - - public function testUnset() - { - unset($this->component->Text); - $this->assertFalse(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); - - $this->component->attachBehavior('a', new NewBehavior()); - $this->component->setP2('test'); - $this->assertEquals('test', $this->component->getP2()); - - unset($this->component->p2); - $this->assertNull($this->component->getP2()); - } - - public function testUnsetReadonly() - { - $this->setExpectedException('yii\base\InvalidCallException'); - unset($this->component->object); - } - - public function testOn() - { - $this->assertFalse($this->component->hasEventHandlers('click')); - $this->component->on('click', 'foo'); - $this->assertTrue($this->component->hasEventHandlers('click')); - - $this->assertFalse($this->component->hasEventHandlers('click2')); - $p = 'on click2'; - $this->component->$p = 'foo2'; - $this->assertTrue($this->component->hasEventHandlers('click2')); - } - - public function testOff() - { - $this->assertFalse($this->component->hasEventHandlers('click')); - $this->component->on('click', 'foo'); - $this->assertTrue($this->component->hasEventHandlers('click')); - $this->component->off('click', 'foo'); - $this->assertFalse($this->component->hasEventHandlers('click')); - - $this->component->on('click2', 'foo'); - $this->component->on('click2', 'foo2'); - $this->component->on('click2', 'foo3'); - $this->assertTrue($this->component->hasEventHandlers('click2')); - $this->component->off('click2', 'foo3'); - $this->assertTrue($this->component->hasEventHandlers('click2')); - $this->component->off('click2'); - $this->assertFalse($this->component->hasEventHandlers('click2')); - } - - public function testTrigger() - { - $this->component->on('click', [$this->component, 'myEventHandler']); - $this->assertFalse($this->component->eventHandled); - $this->assertNull($this->component->event); - $this->component->raiseEvent(); - $this->assertTrue($this->component->eventHandled); - $this->assertEquals('click', $this->component->event->name); - $this->assertEquals($this->component, $this->component->event->sender); - $this->assertFalse($this->component->event->handled); - - $eventRaised = false; - $this->component->on('click', function ($event) use (&$eventRaised) { - $eventRaised = true; - }); - $this->component->raiseEvent(); - $this->assertTrue($eventRaised); - - // raise event w/o parameters - $eventRaised = false; - $this->component->on('test', function ($event) use (&$eventRaised) { - $eventRaised = true; - }); - $this->component->trigger('test'); - $this->assertTrue($eventRaised); - } - - public function testHasEventHandlers() - { - $this->assertFalse($this->component->hasEventHandlers('click')); - $this->component->on('click', 'foo'); - $this->assertTrue($this->component->hasEventHandlers('click')); - } - - public function testStopEvent() - { - $component = new NewComponent; - $component->on('click', 'yiiunit\framework\base\globalEventHandler2'); - $component->on('click', [$this->component, 'myEventHandler']); - $component->raiseEvent(); - $this->assertTrue($component->eventHandled); - $this->assertFalse($this->component->eventHandled); - } - - public function testAttachBehavior() - { - $component = new NewComponent; - $this->assertFalse($component->hasProperty('p')); - $this->assertFalse($component->behaviorCalled); - $this->assertNull($component->getBehavior('a')); - - $behavior = new NewBehavior; - $component->attachBehavior('a', $behavior); - $this->assertSame($behavior, $component->getBehavior('a')); - $this->assertTrue($component->hasProperty('p')); - $component->test(); - $this->assertTrue($component->behaviorCalled); - - $this->assertSame($behavior, $component->detachBehavior('a')); - $this->assertFalse($component->hasProperty('p')); - $this->setExpectedException('yii\base\UnknownMethodException'); - $component->test(); - - $p = 'as b'; - $component = new NewComponent; - $component->$p = ['class' => 'NewBehavior']; - $this->assertSame($behavior, $component->getBehavior('a')); - $this->assertTrue($component->hasProperty('p')); - $component->test(); - $this->assertTrue($component->behaviorCalled); - } - - public function testAttachBehaviors() - { - $component = new NewComponent; - $this->assertNull($component->getBehavior('a')); - $this->assertNull($component->getBehavior('b')); - - $behavior = new NewBehavior; - - $component->attachBehaviors([ - 'a' => $behavior, - 'b' => $behavior, - ]); - - $this->assertSame(['a' => $behavior, 'b' => $behavior], $component->getBehaviors()); - } - - public function testDetachBehavior() - { - $component = new NewComponent; - $behavior = new NewBehavior; - - $component->attachBehavior('a', $behavior); - $this->assertSame($behavior, $component->getBehavior('a')); - - $detachedBehavior = $component->detachBehavior('a'); - $this->assertSame($detachedBehavior, $behavior); - $this->assertNull($component->getBehavior('a')); - - $detachedBehavior = $component->detachBehavior('z'); - $this->assertNull($detachedBehavior); - } - - public function testDetachBehaviors() - { - $component = new NewComponent; - $behavior = new NewBehavior; - - $component->attachBehavior('a', $behavior); - $this->assertSame($behavior, $component->getBehavior('a')); - $component->attachBehavior('b', $behavior); - $this->assertSame($behavior, $component->getBehavior('b')); - - $component->detachBehaviors(); - $this->assertNull($component->getBehavior('a')); - $this->assertNull($component->getBehavior('b')); - } - - public function testSetReadOnlyProperty() - { - $this->setExpectedException( - '\yii\base\InvalidCallException', - 'Setting read-only property: yiiunit\framework\base\NewComponent::object' - ); - $this->component->object = 'z'; - } - - public function testSetPropertyOfBehavior() - { - $this->assertNull($this->component->getBehavior('a')); - - $behavior = new NewBehavior; - $this->component->attachBehaviors([ - 'a' => $behavior, - ]); - $this->component->p = 'Yii is cool.'; - - $this->assertSame('Yii is cool.', $this->component->getBehavior('a')->p); - } - - public function testSettingBehaviorWithSetter() - { - $behaviorName = 'foo'; - $this->assertNull($this->component->getBehavior($behaviorName)); - $p = 'as ' . $behaviorName; - $this->component->$p = __NAMESPACE__ . '\NewBehavior'; - $this->assertSame(__NAMESPACE__ . '\NewBehavior', get_class($this->component->getBehavior($behaviorName))); - } - - public function testWriteOnlyProperty() - { - $this->setExpectedException( - '\yii\base\InvalidCallException', - 'Getting write-only property: yiiunit\framework\base\NewComponent::writeOnly' - ); - $this->component->writeOnly; - } - - public function testSuccessfulMethodCheck() - { - $this->assertTrue($this->component->hasMethod('hasProperty')); - } - - public function testTurningOffNonExistingBehavior() - { - $this->assertFalse($this->component->hasEventHandlers('foo')); - $this->assertFalse($this->component->off('foo')); - } -} - -class NewComponent extends Component -{ - private $_object = null; - private $_text = 'default'; - private $_items = []; - public $content; - - public function getText() - { - return $this->_text; - } - - public function setText($value) - { - $this->_text = $value; - } - - public function getObject() - { - if (!$this->_object) { - $this->_object = new self; - $this->_object->_text = 'object text'; - } - - return $this->_object; - } - - public function getExecute() - { - return function ($param) { - return $param * 2; - }; - } - - public function getItems() - { - return $this->_items; - } - - public $eventHandled = false; - public $event; - public $behaviorCalled = false; - - public function myEventHandler($event) - { - $this->eventHandled = true; - $this->event = $event; - } - - public function raiseEvent() - { - $this->trigger('click', new Event); - } - - public function setWriteOnly() - { - } -} - -class NewBehavior extends Behavior -{ - public $p; - private $p2; - - public function getP2() - { - return $this->p2; - } - - public function setP2($value) - { - $this->p2 = $value; - } - - public function test() - { - $this->owner->behaviorCalled = true; - - return 2; - } -} - -class NewComponent2 extends Component -{ - public $a; - public $b; - public $c; - - public function __construct($b, $c) - { - $this->b = $b; - $this->c = $c; - } -} diff --git a/tests/unit/framework/base/DynamicModelTest.php b/tests/unit/framework/base/DynamicModelTest.php deleted file mode 100644 index 534f37215e..0000000000 --- a/tests/unit/framework/base/DynamicModelTest.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @since 2.0 - */ -class DynamicModelTest extends TestCase -{ - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - - public function testValidateData() - { - $email = 'invalid'; - $name = 'long name'; - $age = ''; - $model = DynamicModel::validateData(compact('name', 'email', 'age'), [ - [['email', 'name', 'age'], 'required'], - ['email', 'email'], - ['name', 'string', 'max' => 3], - ]); - $this->assertTrue($model->hasErrors()); - $this->assertTrue($model->hasErrors('email')); - $this->assertTrue($model->hasErrors('name')); - $this->assertTrue($model->hasErrors('age')); - } - - public function testAddRule() - { - $model = new DynamicModel(); - $this->assertEquals(0, $model->getValidators()->count()); - $model->addRule('name', 'string', ['min' => 12]); - $this->assertEquals(1, $model->getValidators()->count()); - $model->addRule('email', 'email'); - $this->assertEquals(2, $model->getValidators()->count()); - $model->addRule(['name', 'email'], 'required'); - $this->assertEquals(3, $model->getValidators()->count()); - } - - public function testValidateWithAddRule() - { - $email = 'invalid'; - $name = 'long name'; - $age = ''; - $model = new DynamicModel(compact('name', 'email', 'age')); - $model->addRule(['email', 'name', 'age'], 'required') - ->addRule('email', 'email') - ->addRule('name', 'string', ['max' => 3]) - ->validate(); - $this->assertTrue($model->hasErrors()); - $this->assertTrue($model->hasErrors('email')); - $this->assertTrue($model->hasErrors('name')); - $this->assertTrue($model->hasErrors('age')); - } - - public function testDynamicProperty() - { - $email = 'invalid'; - $name = 'long name'; - $model = new DynamicModel(compact('name', 'email')); - $this->assertEquals($email, $model->email); - $this->assertEquals($name, $model->name); - $this->setExpectedException('yii\base\UnknownPropertyException'); - $age = $model->age; - } -} diff --git a/tests/unit/framework/base/ExposedSecurity.php b/tests/unit/framework/base/ExposedSecurity.php deleted file mode 100644 index 431e123924..0000000000 --- a/tests/unit/framework/base/ExposedSecurity.php +++ /dev/null @@ -1,27 +0,0 @@ -mockApplication(); - $this->object = new NewObject; - } - - protected function tearDown() - { - parent::tearDown(); - $this->object = null; - } - - public function testHasProperty() - { - $this->assertTrue($this->object->hasProperty('Text')); - $this->assertTrue($this->object->hasProperty('text')); - $this->assertFalse($this->object->hasProperty('Caption')); - $this->assertTrue($this->object->hasProperty('content')); - $this->assertFalse($this->object->hasProperty('content', false)); - $this->assertFalse($this->object->hasProperty('Content')); - } - - public function testCanGetProperty() - { - $this->assertTrue($this->object->canGetProperty('Text')); - $this->assertTrue($this->object->canGetProperty('text')); - $this->assertFalse($this->object->canGetProperty('Caption')); - $this->assertTrue($this->object->canGetProperty('content')); - $this->assertFalse($this->object->canGetProperty('content', false)); - $this->assertFalse($this->object->canGetProperty('Content')); - } - - public function testCanSetProperty() - { - $this->assertTrue($this->object->canSetProperty('Text')); - $this->assertTrue($this->object->canSetProperty('text')); - $this->assertFalse($this->object->canSetProperty('Object')); - $this->assertFalse($this->object->canSetProperty('Caption')); - $this->assertTrue($this->object->canSetProperty('content')); - $this->assertFalse($this->object->canSetProperty('content', false)); - $this->assertFalse($this->object->canSetProperty('Content')); - } - - public function testGetProperty() - { - $this->assertTrue('default' === $this->object->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); - $value2 = $this->object->Caption; - } - - public function testSetProperty() - { - $value = 'new value'; - $this->object->Text = $value; - $this->assertEquals($value, $this->object->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); - $this->object->NewMember = $value; - } - - public function testSetReadOnlyProperty() - { - $this->setExpectedException('yii\base\InvalidCallException'); - $this->object->object = 'test'; - } - - public function testIsset() - { - $this->assertTrue(isset($this->object->Text)); - $this->assertFalse(empty($this->object->Text)); - - $this->object->Text = ''; - $this->assertTrue(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); - - $this->object->Text = null; - $this->assertFalse(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); - - $this->assertFalse(isset($this->object->unknownProperty)); - $this->assertTrue(empty($this->object->unknownProperty)); - } - - public function testUnset() - { - unset($this->object->Text); - $this->assertFalse(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); - } - - public function testUnsetReadOnlyProperty() - { - $this->setExpectedException('yii\base\InvalidCallException'); - unset($this->object->object); - } - - public function testCallUnknownMethod() - { - $this->setExpectedException('yii\base\UnknownMethodException'); - $this->object->unknownMethod(); - } - - public function testArrayProperty() - { - $this->assertEquals([], $this->object->items); - // the following won't work - /* - $this->object->items[] = 1; - $this->assertEquals([1], $this->object->items); - */ - } - - public function testObjectProperty() - { - $this->assertTrue($this->object->object instanceof NewObject); - $this->assertEquals('object text', $this->object->object->text); - $this->object->object->text = 'new text'; - $this->assertEquals('new text', $this->object->object->text); - } - - public function testConstruct() - { - $object = new NewObject(['text' => 'test text']); - $this->assertEquals('test text', $object->getText()); - } - - public function testGetClassName() - { - $object = $this->object; - $this->assertSame(get_class($object), $object::className()); - } - - public function testReadingWriteOnlyProperty() - { - $this->setExpectedException( - 'yii\base\InvalidCallException', - 'Getting write-only property: yiiunit\framework\base\NewObject::writeOnly' - ); - $this->object->writeOnly; - } -} - - -class NewObject extends Object -{ - private $_object = null; - private $_text = 'default'; - private $_items = []; - public $content; - - public function getText() - { - return $this->_text; - } - - public function setText($value) - { - $this->_text = $value; - } - - public function getObject() - { - if (!$this->_object) { - $this->_object = new self; - $this->_object->_text = 'object text'; - } - - return $this->_object; - } - - public function getExecute() - { - return function ($param) { - return $param * 2; - }; - } - - public function getItems() - { - return $this->_items; - } - - public function setWriteOnly() {} -} diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php deleted file mode 100644 index 3bd6b7269f..0000000000 --- a/tests/unit/framework/behaviors/SluggableBehaviorTest.php +++ /dev/null @@ -1,193 +0,0 @@ -mockApplication([ - 'components' => [ - 'db' => [ - 'class' => '\yii\db\Connection', - 'dsn' => 'sqlite::memory:', - ] - ] - ]); - - $columns = [ - 'id' => 'pk', - 'name' => 'string', - 'slug' => 'string', - 'category_id' => 'integer', - ]; - Yii::$app->getDb()->createCommand()->createTable('test_slug', $columns)->execute(); - } - - public function tearDown() - { - Yii::$app->getDb()->close(); - parent::tearDown(); - } - - // Tests : - - public function testSlug() - { - $model = new ActiveRecordSluggable(); - $model->name = 'test name'; - $model->validate(); - - $this->assertEquals('test-name', $model->slug); - } - - /** - * @depends testSlug - */ - public function testSlugSeveralAttributes() - { - $model = new ActiveRecordSluggable(); - $model->getBehavior('sluggable')->attribute = array('name', 'category_id'); - - $model->name = 'test'; - $model->category_id = 10; - - $model->validate(); - $this->assertEquals('test-10', $model->slug); - } - - /** - * @depends testSlug - */ - public function testUniqueByIncrement() - { - $name = 'test name'; - - $model = new ActiveRecordSluggableUnique(); - $model->name = $name; - $model->save(); - - $model = new ActiveRecordSluggableUnique(); - $model->sluggable->uniqueSlugGenerator = 'increment'; - $model->name = $name; - $model->save(); - - $this->assertEquals('test-name-2', $model->slug); - } - - /** - * @depends testUniqueByIncrement - */ - public function testUniqueByCallback() - { - $name = 'test name'; - - $model = new ActiveRecordSluggableUnique(); - $model->name = $name; - $model->save(); - - $model = new ActiveRecordSluggableUnique(); - $model->sluggable->uniqueSlugGenerator = function($baseSlug, $iteration) {return $baseSlug . '-callback';}; - $model->name = $name; - $model->save(); - - $this->assertEquals('test-name-callback', $model->slug); - } - - /** - * @depends testSlug - */ - public function testUpdateUnique() - { - $name = 'test name'; - - $model = new ActiveRecordSluggableUnique(); - $model->name = $name; - $model->save(); - - $model->save(); - $this->assertEquals('test-name', $model->slug); - - $model = ActiveRecordSluggableUnique::find()->one(); - $model->save(); - $this->assertEquals('test-name', $model->slug); - - $model->name = 'test-name'; - $model->save(); - $this->assertEquals('test-name', $model->slug); - } -} - -/** - * Test Active Record class with [[SluggableBehavior]] behavior attached. - * - * @property integer $id - * @property string $name - * @property string $slug - * @property integer $category_id - * - * @property SluggableBehavior $sluggable - */ -class ActiveRecordSluggable extends ActiveRecord -{ - public function behaviors() - { - return [ - 'sluggable' => [ - 'class' => SluggableBehavior::className(), - 'attribute' => 'name', - ], - ]; - } - - public static function tableName() - { - return 'test_slug'; - } - - /** - * @return SluggableBehavior - */ - public function getSluggable() - { - return $this->getBehavior('sluggable'); - } -} - -class ActiveRecordSluggableUnique extends ActiveRecordSluggable -{ - public function behaviors() - { - return [ - 'sluggable' => [ - 'class' => SluggableBehavior::className(), - 'attribute' => 'name', - 'ensureUnique' => true, - ], - ]; - } -} \ No newline at end of file diff --git a/tests/unit/framework/behaviors/TimestampBehaviorTest.php b/tests/unit/framework/behaviors/TimestampBehaviorTest.php deleted file mode 100644 index 40d6886406..0000000000 --- a/tests/unit/framework/behaviors/TimestampBehaviorTest.php +++ /dev/null @@ -1,110 +0,0 @@ -mockApplication([ - 'components' => [ - 'db' => [ - 'class' => '\yii\db\Connection', - 'dsn' => 'sqlite::memory:', - ] - ] - ]); - - $columns = [ - 'id' => 'pk', - 'created_at' => 'integer', - 'updated_at' => 'integer', - ]; - Yii::$app->getDb()->createCommand()->createTable('test_auto_timestamp', $columns)->execute(); - } - - public function tearDown() - { - Yii::$app->getDb()->close(); - parent::tearDown(); - } - - // Tests : - - public function testNewRecord() - { - $currentTime = time(); - - $model = new ActiveRecordTimestamp(); - $model->save(false); - - $this->assertTrue($model->created_at >= $currentTime); - $this->assertTrue($model->updated_at >= $currentTime); - } - - /** - * @depends testNewRecord - */ - public function testUpdateRecord() - { - $currentTime = time(); - - $model = new ActiveRecordTimestamp(); - $model->save(false); - - $enforcedTime = $currentTime - 100; - - $model->created_at = $enforcedTime; - $model->updated_at = $enforcedTime; - $model->save(false); - - $this->assertEquals($enforcedTime, $model->created_at, 'Create time has been set on update!'); - $this->assertTrue($model->updated_at >= $currentTime, 'Update time has NOT been set on update!'); - } -} - -/** - * Test Active Record class with [[TimestampBehavior]] behavior attached. - * - * @property integer $id - * @property integer $created_at - * @property integer $updated_at - */ -class ActiveRecordTimestamp extends ActiveRecord -{ - public function behaviors() - { - return [ - TimestampBehavior::className(), - ]; - } - - public static function tableName() - { - return 'test_auto_timestamp'; - } -} diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php deleted file mode 100644 index 126471c5e6..0000000000 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ /dev/null @@ -1,46 +0,0 @@ -markTestSkipped("APC not installed. Skipping."); - } elseif ('cli' === PHP_SAPI && !ini_get('apc.enable_cli')) { - $this->markTestSkipped("APC cli is not enabled. Skipping."); - } - - if (!ini_get("apc.enabled") || !ini_get("apc.enable_cli")) { - $this->markTestSkipped("APC is installed but not enabled. Skipping."); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new ApcCache(); - } - - return $this->_cacheInstance; - } - - public function testExpire() - { - $this->markTestSkipped("APC keys are expiring only on the next request."); - } - - public function testExpireAdd() - { - $this->markTestSkipped("APC keys are expiring only on the next request."); - } -} diff --git a/tests/unit/framework/caching/ArrayCacheTest.php b/tests/unit/framework/caching/ArrayCacheTest.php deleted file mode 100644 index 5ec7d731b9..0000000000 --- a/tests/unit/framework/caching/ArrayCacheTest.php +++ /dev/null @@ -1,49 +0,0 @@ -_cacheInstance === null) { - $this->_cacheInstance = new ArrayCache(); - } - return $this->_cacheInstance; - } - - public function testExpire() - { - $cache = $this->getCacheInstance(); - - static::$microtime = \microtime(true); - $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - static::$microtime++; - $this->assertEquals('expire_test', $cache->get('expire_test')); - static::$microtime++; - $this->assertFalse($cache->get('expire_test')); - } - - public function testExpireAdd() - { - $cache = $this->getCacheInstance(); - - static::$microtime = \microtime(true); - $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); - static::$microtime++; - $this->assertEquals('expire_testa', $cache->get('expire_testa')); - static::$microtime++; - $this->assertFalse($cache->get('expire_testa')); - } -} diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php deleted file mode 100644 index 4d976c4891..0000000000 --- a/tests/unit/framework/caching/CacheTestCase.php +++ /dev/null @@ -1,246 +0,0 @@ -mockApplication(); - } - - protected function tearDown() - { - static::$time = null; - static::$microtime = null; - } - - /** - * @return Cache - */ - public function prepare() - { - $cache = $this->getCacheInstance(); - - $cache->flush(); - $cache->set('string_test', 'string_test'); - $cache->set('number_test', 42); - $cache->set('array_test', ['array_test' => 'array_test']); - $cache['arrayaccess_test'] = new \stdClass(); - - return $cache; - } - - public function testSet() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->set('string_test', 'string_test')); - $this->assertTrue($cache->set('number_test', 42)); - $this->assertTrue($cache->set('array_test', ['array_test' => 'array_test'])); - } - - public function testGet() - { - $cache = $this->prepare(); - - $this->assertEquals('string_test', $cache->get('string_test')); - - $this->assertEquals(42, $cache->get('number_test')); - - $array = $cache->get('array_test'); - $this->assertArrayHasKey('array_test', $array); - $this->assertEquals('array_test', $array['array_test']); - } - - /** - * @return array testing mset with and without expiry - */ - public function msetExpiry() - { - return [[0], [2]]; - } - - /** - * @dataProvider msetExpiry - */ - public function testMset($expiry) - { - $cache = $this->getCacheInstance(); - $cache->flush(); - - $cache->mset([ - 'string_test' => 'string_test', - 'number_test' => 42, - 'array_test' => ['array_test' => 'array_test'], - ], $expiry); - - $this->assertEquals('string_test', $cache->get('string_test')); - - $this->assertEquals(42, $cache->get('number_test')); - - $array = $cache->get('array_test'); - $this->assertArrayHasKey('array_test', $array); - $this->assertEquals('array_test', $array['array_test']); - } - - public function testExists() - { - $cache = $this->prepare(); - - $this->assertTrue($cache->exists('string_test')); - // check whether exists affects the value - $this->assertEquals('string_test', $cache->get('string_test')); - - $this->assertTrue($cache->exists('number_test')); - $this->assertFalse($cache->exists('not_exists')); - } - - public function testArrayAccess() - { - $cache = $this->getCacheInstance(); - - $cache['arrayaccess_test'] = new \stdClass(); - $this->assertInstanceOf('stdClass', $cache['arrayaccess_test']); - } - - public function testGetNonExistent() - { - $cache = $this->getCacheInstance(); - - $this->assertFalse($cache->get('non_existent_key')); - } - - public function testStoreSpecialValues() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->set('null_value', null)); - $this->assertNull($cache->get('null_value')); - - $this->assertTrue($cache->set('bool_value', true)); - $this->assertTrue($cache->get('bool_value')); - } - - public function testMget() - { - $cache = $this->prepare(); - - $this->assertEquals(['string_test' => 'string_test', 'number_test' => 42], $cache->mget(['string_test', 'number_test'])); - // ensure that order does not matter - $this->assertEquals(['number_test' => 42, 'string_test' => 'string_test'], $cache->mget(['number_test', 'string_test'])); - $this->assertEquals(['number_test' => 42, 'non_existent_key' => null], $cache->mget(['number_test', 'non_existent_key'])); - } - - public function testExpire() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - usleep(500000); - $this->assertEquals('expire_test', $cache->get('expire_test')); - usleep(2500000); - $this->assertFalse($cache->get('expire_test')); - } - - public function testExpireAdd() - { - $cache = $this->getCacheInstance(); - - $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); - usleep(500000); - $this->assertEquals('expire_testa', $cache->get('expire_testa')); - usleep(2500000); - $this->assertFalse($cache->get('expire_testa')); - } - - public function testAdd() - { - $cache = $this->prepare(); - - // should not change existing keys - $this->assertFalse($cache->add('number_test', 13)); - $this->assertEquals(42, $cache->get('number_test')); - - // should store data if it's not there yet - $this->assertFalse($cache->get('add_test')); - $this->assertTrue($cache->add('add_test', 13)); - $this->assertEquals(13, $cache->get('add_test')); - } - - public function testMadd() - { - $cache = $this->prepare(); - - $this->assertFalse($cache->get('add_test')); - - $cache->madd([ - 'number_test' => 13, - 'add_test' => 13, - ]); - - $this->assertEquals(42, $cache->get('number_test')); - $this->assertEquals(13, $cache->get('add_test')); - } - - public function testDelete() - { - $cache = $this->prepare(); - - $this->assertNotNull($cache->get('number_test')); - $this->assertTrue($cache->delete('number_test')); - $this->assertFalse($cache->get('number_test')); - } - - public function testFlush() - { - $cache = $this->prepare(); - $this->assertTrue($cache->flush()); - $this->assertFalse($cache->get('number_test')); - } -} diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php deleted file mode 100644 index 0036ffe84c..0000000000 --- a/tests/unit/framework/caching/DbCacheTest.php +++ /dev/null @@ -1,99 +0,0 @@ -markTestSkipped('pdo and pdo_mysql extensions are required.'); - } - - parent::setUp(); - - $this->getConnection()->createCommand(" - CREATE TABLE IF NOT EXISTS cache ( - id char(128) NOT NULL, - expire int(11) DEFAULT NULL, - data LONGBLOB, - PRIMARY KEY (id), - KEY expire (expire) - ); - ")->execute(); - } - - /** - * @param boolean $reset whether to clean up the test database - * @return \yii\db\Connection - */ - public function getConnection($reset = true) - { - if ($this->_connection === null) { - $databases = self::getParam('databases'); - $params = $databases['mysql']; - $db = new \yii\db\Connection; - $db->dsn = $params['dsn']; - $db->username = $params['username']; - $db->password = $params['password']; - if ($reset) { - $db->open(); - $lines = explode(';', file_get_contents($params['fixture'])); - foreach ($lines as $line) { - if (trim($line) !== '') { - $db->pdo->exec($line); - } - } - } - $this->_connection = $db; - } - - return $this->_connection; - } - - /** - * @return DbCache - */ - protected function getCacheInstance() - { - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new DbCache(['db' => $this->getConnection()]); - } - - return $this->_cacheInstance; - } - - public function testExpire() - { - $cache = $this->getCacheInstance(); - - static::$time = \time(); - $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - static::$time++; - $this->assertEquals('expire_test', $cache->get('expire_test')); - static::$time++; - $this->assertFalse($cache->get('expire_test')); - } - - public function testExpireAdd() - { - $cache = $this->getCacheInstance(); - - static::$time = \time(); - $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); - static::$time++; - $this->assertEquals('expire_testa', $cache->get('expire_testa')); - static::$time++; - $this->assertFalse($cache->get('expire_testa')); - } -} diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php deleted file mode 100644 index 1c7e183f80..0000000000 --- a/tests/unit/framework/caching/FileCacheTest.php +++ /dev/null @@ -1,49 +0,0 @@ -_cacheInstance === null) { - $this->_cacheInstance = new FileCache(['cachePath' => '@yiiunit/runtime/cache']); - } - - return $this->_cacheInstance; - } - - public function testExpire() - { - $cache = $this->getCacheInstance(); - - static::$time = \time(); - $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - static::$time++; - $this->assertEquals('expire_test', $cache->get('expire_test')); - static::$time++; - $this->assertFalse($cache->get('expire_test')); - } - - public function testExpireAdd() - { - $cache = $this->getCacheInstance(); - - static::$time = \time(); - $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); - static::$time++; - $this->assertEquals('expire_testa', $cache->get('expire_testa')); - static::$time++; - $this->assertFalse($cache->get('expire_testa')); - } -} diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php deleted file mode 100644 index d3c35d3b1f..0000000000 --- a/tests/unit/framework/caching/MemCacheTest.php +++ /dev/null @@ -1,51 +0,0 @@ -markTestSkipped("memcache not installed. Skipping."); - } - - // check whether memcached is running and skip tests if not. - if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new MemCache(); - } - - return $this->_cacheInstance; - } - - public function testExpire() - { - if (getenv('TRAVIS') == 'true') { - $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); - } - parent::testExpire(); - } - - public function testExpireAdd() - { - if (getenv('TRAVIS') == 'true') { - $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); - } - parent::testExpireAdd(); - } -} diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php deleted file mode 100644 index 56a5570355..0000000000 --- a/tests/unit/framework/caching/MemCachedTest.php +++ /dev/null @@ -1,51 +0,0 @@ -markTestSkipped("memcached not installed. Skipping."); - } - - // check whether memcached is running and skip tests if not. - if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) { - $this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new MemCache(['useMemcached' => true]); - } - - return $this->_cacheInstance; - } - - public function testExpire() - { - if (getenv('TRAVIS') == 'true') { - $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); - } - parent::testExpire(); - } - - public function testExpireAdd() - { - if (getenv('TRAVIS') == 'true') { - $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); - } - parent::testExpireAdd(); - } -} diff --git a/tests/unit/framework/caching/TagDependencyTest.php b/tests/unit/framework/caching/TagDependencyTest.php deleted file mode 100644 index 8b61b275ed..0000000000 --- a/tests/unit/framework/caching/TagDependencyTest.php +++ /dev/null @@ -1,79 +0,0 @@ - '@yiiunit/runtime/cache']); - - // single tag test - $cache->set('a1', 11, 0, new TagDependency(['tags' => 't1'])); - $cache->set('a2', 12, 0, new TagDependency(['tags' => 't1'])); - $cache->set('b1', 21, 0, new TagDependency(['tags' => 't2'])); - $cache->set('b2', 22, 0, new TagDependency(['tags' => 't2'])); - - $this->assertEquals(11, $cache->get('a1')); - $this->assertEquals(12, $cache->get('a2')); - $this->assertEquals(21, $cache->get('b1')); - $this->assertEquals(22, $cache->get('b2')); - - TagDependency::invalidate($cache, 't1'); - $this->assertFalse($cache->get('a1')); - $this->assertFalse($cache->get('a2')); - $this->assertEquals(21, $cache->get('b1')); - $this->assertEquals(22, $cache->get('b2')); - - TagDependency::invalidate($cache, 't2'); - $this->assertFalse($cache->get('a1')); - $this->assertFalse($cache->get('a2')); - $this->assertFalse($cache->get('b1')); - $this->assertFalse($cache->get('b2')); - - // multiple tag test - $cache->set('a1', 11, 0, new TagDependency(['tags' => ['t1', 't2']])); - $cache->set('a2', 12, 0, new TagDependency(['tags' => 't1'])); - $cache->set('b1', 21, 0, new TagDependency(['tags' => ['t1', 't2']])); - $cache->set('b2', 22, 0, new TagDependency(['tags' => 't2'])); - - $this->assertEquals(11, $cache->get('a1')); - $this->assertEquals(12, $cache->get('a2')); - $this->assertEquals(21, $cache->get('b1')); - $this->assertEquals(22, $cache->get('b2')); - - TagDependency::invalidate($cache, 't1'); - $this->assertFalse($cache->get('a1')); - $this->assertFalse($cache->get('a2')); - $this->assertFalse($cache->get('b1')); - $this->assertEquals(22, $cache->get('b2')); - - TagDependency::invalidate($cache, 't2'); - $this->assertFalse($cache->get('a1')); - $this->assertFalse($cache->get('a2')); - $this->assertFalse($cache->get('b1')); - $this->assertFalse($cache->get('b2')); - - $cache->set('a1', 11, 0, new TagDependency(['tags' => ['t1', 't2']])); - $cache->set('a2', 12, 0, new TagDependency(['tags' => 't1'])); - $cache->set('b1', 21, 0, new TagDependency(['tags' => ['t1', 't2']])); - $cache->set('b2', 22, 0, new TagDependency(['tags' => 't2'])); - - $this->assertEquals(11, $cache->get('a1')); - $this->assertEquals(12, $cache->get('a2')); - $this->assertEquals(21, $cache->get('b1')); - $this->assertEquals(22, $cache->get('b2')); - - TagDependency::invalidate($cache, ['t1', 't2']); - $this->assertFalse($cache->get('a1')); - $this->assertFalse($cache->get('a2')); - $this->assertFalse($cache->get('b1')); - $this->assertFalse($cache->get('b2')); - } -} diff --git a/tests/unit/framework/caching/WinCacheTest.php b/tests/unit/framework/caching/WinCacheTest.php deleted file mode 100644 index ccf0fdcb02..0000000000 --- a/tests/unit/framework/caching/WinCacheTest.php +++ /dev/null @@ -1,34 +0,0 @@ -markTestSkipped("Wincache not installed. Skipping."); - } - - if (!ini_get('wincache.ucenabled')) { - $this->markTestSkipped("Wincache user cache disabled. Skipping."); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new WinCache(); - } - - return $this->_cacheInstance; - } -} diff --git a/tests/unit/framework/caching/XCacheTest.php b/tests/unit/framework/caching/XCacheTest.php deleted file mode 100644 index ca2b79c8b4..0000000000 --- a/tests/unit/framework/caching/XCacheTest.php +++ /dev/null @@ -1,30 +0,0 @@ -markTestSkipped("XCache not installed. Skipping."); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new XCache(); - } - - return $this->_cacheInstance; - } -} diff --git a/tests/unit/framework/caching/ZendDataCacheTest.php b/tests/unit/framework/caching/ZendDataCacheTest.php deleted file mode 100644 index 63c0d10f0f..0000000000 --- a/tests/unit/framework/caching/ZendDataCacheTest.php +++ /dev/null @@ -1,30 +0,0 @@ -markTestSkipped("Zend Data cache not installed. Skipping."); - } - - if ($this->_cacheInstance === null) { - $this->_cacheInstance = new ZendDataCache(); - } - - return $this->_cacheInstance; - } -} diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php deleted file mode 100644 index ae031b045e..0000000000 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ /dev/null @@ -1,547 +0,0 @@ -mockApplication(); - $this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . str_replace('\\', '_', get_class($this)) . uniqid(); - $this->createDir($this->testFilePath); - $this->testAssetsBasePath = $this->testFilePath . DIRECTORY_SEPARATOR . 'assets'; - $this->createDir($this->testAssetsBasePath); - } - - public function tearDown() - { - $this->removeDir($this->testFilePath); - } - - /** - * Creates directory. - * @param string $dirName directory full name. - */ - protected function createDir($dirName) - { - if (!file_exists($dirName)) { - mkdir($dirName, 0777, true); - } - } - - /** - * Removes directory. - * @param string $dirName directory full name - */ - protected function removeDir($dirName) - { - if (!empty($dirName) && file_exists($dirName)) { - exec("rm -rf {$dirName}"); - } - } - - /** - * Creates test asset controller instance. - * @return AssetControllerMock - */ - protected function createAssetController() - { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); - $assetController = new AssetControllerMock('asset', $module); - $assetController->interactive = false; - $assetController->jsCompressor = 'cp {from} {to}'; - $assetController->cssCompressor = 'cp {from} {to}'; - - return $assetController; - } - - /** - * Emulates running of the asset controller action. - * @param string $actionID id of action to be run. - * @param array $args action arguments. - * @return string command output. - */ - protected function runAssetControllerAction($actionID, array $args = []) - { - $controller = $this->createAssetController(); - $controller->run($actionID, $args); - return $controller->flushStdOutBuffer(); - } - - /** - * Creates test compress config. - * @param array[] $bundles asset bundles config. - * @return array config array. - */ - protected function createCompressConfig(array $bundles) - { - static $classNumber = 0; - $classNumber++; - $className = $this->declareAssetBundleClass(['class' => 'AssetBundleAll' . $classNumber]); - $baseUrl = '/test'; - $config = [ - 'bundles' => $bundles, - 'targets' => [ - $className => [ - 'basePath' => $this->testAssetsBasePath, - 'baseUrl' => $baseUrl, - 'js' => 'all.js', - 'css' => 'all.css', - ], - ], - 'assetManager' => [ - 'basePath' => $this->testAssetsBasePath, - 'baseUrl' => '', - ], - ]; - - return $config; - } - - /** - * Creates test compress config file. - * @param string $fileName output file name. - * @param array[] $bundles asset bundles config. - * @throws \Exception on failure. - */ - protected function createCompressConfigFile($fileName, array $bundles) - { - $content = 'createCompressConfig($bundles), true) . ';'; - if (file_put_contents($fileName, $content) <= 0) { - throw new \Exception("Unable to create file '{$fileName}'!"); - } - } - - /** - * Creates test asset file. - * @param string $fileRelativeName file name relative to [[testFilePath]] - * @param string $content file content - * @throws \Exception on failure. - */ - protected function createAssetSourceFile($fileRelativeName, $content) - { - $fileFullName = $this->testFilePath . DIRECTORY_SEPARATOR . $fileRelativeName; - $this->createDir(dirname($fileFullName)); - if (file_put_contents($fileFullName, $content) <= 0) { - throw new \Exception("Unable to create file '{$fileFullName}'!"); - } - } - - /** - * Creates a list of asset source files. - * @param array $files assert source files in format: file/relative/name => fileContent - */ - protected function createAssetSourceFiles(array $files) - { - foreach ($files as $name => $content) { - $this->createAssetSourceFile($name, $content); - } - } - - /** - * Invokes the asset controller method even if it is protected. - * @param string $methodName name of the method to be invoked. - * @param array $args method arguments. - * @return mixed method invoke result. - */ - protected function invokeAssetControllerMethod($methodName, array $args = []) - { - $controller = $this->createAssetController(); - $controllerClassReflection = new \ReflectionClass(get_class($controller)); - $methodReflection = $controllerClassReflection->getMethod($methodName); - $methodReflection->setAccessible(true); - $result = $methodReflection->invokeArgs($controller, $args); - $methodReflection->setAccessible(false); - - return $result; - } - - /** - * Composes asset bundle class source code. - * @param array $config asset bundle config. - * @return string class source code. - */ - protected function composeAssetBundleClassSource(array &$config) - { - $config = array_merge( - [ - 'namespace' => StringHelper::dirname(get_class($this)), - 'class' => 'AppAsset', - 'basePath' => $this->testFilePath, - 'baseUrl' => '', - 'css' => [], - 'js' => [], - 'depends' => [], - ], - $config - ); - foreach ($config as $name => $value) { - if (is_array($value)) { - $config[$name] = var_export($value, true); - } - } - - $source = <<composeAssetBundleClassSource($config); - eval($sourceCode); - - return $config['namespace'] . '\\' . $config['class']; - } - - // Tests : - - public function testActionTemplate() - { - $configFileName = $this->testFilePath . DIRECTORY_SEPARATOR . 'config.php'; - $this->runAssetControllerAction('template', [$configFileName]); - $this->assertTrue(file_exists($configFileName), 'Unable to create config file template!'); - $config = require($configFileName); - $this->assertTrue(is_array($config), 'Invalid config created!'); - } - - public function testActionCompress() - { - // Given : - $cssFiles = [ - 'css/test_body.css' => 'body { - padding-top: 20px; - padding-bottom: 60px; - }', - 'css/test_footer.css' => '.footer { - margin: 20px; - display: block; - }', - ]; - $this->createAssetSourceFiles($cssFiles); - - $jsFiles = [ - 'js/test_alert.js' => "function test() { - alert('Test message'); - }", - 'js/test_sum_ab.js' => "function sumAB(a, b) { - return a + b; - }", - ]; - $this->createAssetSourceFiles($jsFiles); - $assetBundleClassName = $this->declareAssetBundleClass([ - 'css' => array_keys($cssFiles), - 'js' => array_keys($jsFiles), - ]); - - $bundles = [ - $assetBundleClassName - ]; - $bundleFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'bundle.php'; - - $configFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'config2.php'; - $this->createCompressConfigFile($configFile, $bundles); - - // When : - $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); - - // Then : - $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); - $compressedBundleConfig = require($bundleFile); - $this->assertTrue(is_array($compressedBundleConfig), 'Output bundle file has incorrect format!'); - $this->assertCount(2, $compressedBundleConfig, 'Output bundle config contains wrong bundle count!'); - - $this->assertArrayHasKey($assetBundleClassName, $compressedBundleConfig, 'Source bundle is lost!'); - $compressedAssetBundleConfig = $compressedBundleConfig[$assetBundleClassName]; - $this->assertEmpty($compressedAssetBundleConfig['css'], 'Compressed bundle css is not empty!'); - $this->assertEmpty($compressedAssetBundleConfig['js'], 'Compressed bundle js is not empty!'); - $this->assertNotEmpty($compressedAssetBundleConfig['depends'], 'Compressed bundle dependency is invalid!'); - - $compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css'; - $this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!'); - $compressedJsFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.js'; - $this->assertTrue(file_exists($compressedJsFileName), 'Unable to compress JS files!'); - - $compressedCssFileContent = file_get_contents($compressedCssFileName); - foreach ($cssFiles as $name => $content) { - $this->assertContains($content, $compressedCssFileContent, "Source of '{$name}' is missing in combined file!"); - } - $compressedJsFileContent = file_get_contents($compressedJsFileName); - foreach ($jsFiles as $name => $content) { - $this->assertContains($content, $compressedJsFileContent, "Source of '{$name}' is missing in combined file!"); - } - } - - /** - * @depends testActionCompress - * - * @see https://github.com/yiisoft/yii2/issues/5194 - */ - public function testCompressExternalAsset() - { - // Given : - $externalAssetConfig = [ - 'class' => 'ExternalAsset', - 'sourcePath' => null, - 'basePath' => null, - 'js' => [ - '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js', - ], - 'css' => [ - '//ajax.googleapis.com/css/libs/jquery/2.1.1/jquery.ui.min.css' - ], - ]; - $externalAssetBundleClassName = $this->declareAssetBundleClass($externalAssetConfig); - - $cssFiles = [ - 'css/test.css' => 'body { - padding-top: 20px; - padding-bottom: 60px; - }', - ]; - $this->createAssetSourceFiles($cssFiles); - $jsFiles = [ - 'js/test.js' => "function test() { - alert('Test message'); - }", - ]; - $this->createAssetSourceFiles($jsFiles); - $regularAssetBundleClassName = $this->declareAssetBundleClass([ - 'class' => 'RegularAsset', - 'css' => array_keys($cssFiles), - 'js' => array_keys($jsFiles), - 'depends' => [ - $externalAssetBundleClassName - ], - ]); - $bundles = [ - $regularAssetBundleClassName - ]; - $bundleFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'bundle.php'; - - $configFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'config.php'; - $this->createCompressConfigFile($configFile, $bundles); - - // When : - $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); - - // Then : - $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); - $compressedBundleConfig = require($bundleFile); - $this->assertTrue(is_array($compressedBundleConfig), 'Output bundle file has incorrect format!'); - $this->assertArrayHasKey($externalAssetBundleClassName, $compressedBundleConfig, 'External bundle is lost!'); - - $compressedExternalAssetConfig = $compressedBundleConfig[$externalAssetBundleClassName]; - $this->assertEquals($externalAssetConfig['js'], $compressedExternalAssetConfig['js'], 'External bundle js is lost!'); - $this->assertEquals($externalAssetConfig['css'], $compressedExternalAssetConfig['css'], 'External bundle css is lost!'); - - $compressedRegularAssetConfig = $compressedBundleConfig[$regularAssetBundleClassName]; - $this->assertContains($externalAssetBundleClassName, $compressedRegularAssetConfig['depends'], 'Dependency on external bundle is lost!'); - } - - /** - * Data provider for [[testAdjustCssUrl()]]. - * @return array test data. - */ - public function adjustCssUrlDataProvider() - { - return [ - [ - '.published-same-dir-class {background-image: url(published_same_dir.png);}', - '/test/base/path/assets/input', - '/test/base/path/assets/output', - '.published-same-dir-class {background-image: url(../input/published_same_dir.png);}', - ], - [ - '.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}', - '/test/base/path/assets/input', - '/test/base/path/assets/output', - '.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}', - ], - [ - '.static-same-dir-class {background-image: url(\'static_same_dir.png\');}', - '/test/base/path/css', - '/test/base/path/assets/output', - '.static-same-dir-class {background-image: url(\'../../css/static_same_dir.png\');}', - ], - [ - '.static-relative-dir-class {background-image: url("../img/static_relative_dir.png");}', - '/test/base/path/css', - '/test/base/path/assets/output', - '.static-relative-dir-class {background-image: url("../../img/static_relative_dir.png");}', - ], - [ - '.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}', - '/test/base/path/assets/input', - '/test/base/path/assets/output', - '.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}', - ], - [ - '.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}', - '/test/base/path/assets/input', - '/test/base/path/assets/output', - '.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}', - ], - [ - "@font-face { - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'); - }", - '/test/base/path/assets/input/css', - '/test/base/path/assets/output', - "@font-face { - src: url('../input/fonts/glyphicons-halflings-regular.eot'); - src: url('../input/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'); - }", - ], - [ - "@font-face { - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'); - }", - '/test/base/path/assets/input/css', - '/test/base/path/assets', - "@font-face { - src: url('input/fonts/glyphicons-halflings-regular.eot'); - src: url('input/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'); - }", - ], - [ - "@font-face { - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT==) format('truetype'); - }", - '/test/base/path/assets/input/css', - '/test/base/path/assets/output', - "@font-face { - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT==) format('truetype'); - }", - ], - [ - '.published-same-dir-class {background-image: url(published_same_dir.png);}', - 'C:\test\base\path\assets\input', - 'C:\test\base\path\assets\output', - '.published-same-dir-class {background-image: url(../input/published_same_dir.png);}', - ], - [ - '.static-root-relative-class {background-image: url(\'/images/static_root_relative.png\');}', - '/test/base/path/css', - '/test/base/path/assets/output', - '.static-root-relative-class {background-image: url(\'/images/static_root_relative.png\');}', - ], - [ - '.published-relative-dir-class {background-image: url(../img/same_relative_dir.png);}', - '/test/base/path/assets/css', - '/test/base/path/assets/css', - '.published-relative-dir-class {background-image: url(../img/same_relative_dir.png);}', - ], - ]; - } - - /** - * @dataProvider adjustCssUrlDataProvider - * - * @param $cssContent - * @param $inputFilePath - * @param $outputFilePath - * @param $expectedCssContent - */ - public function testAdjustCssUrl($cssContent, $inputFilePath, $outputFilePath, $expectedCssContent) - { - $adjustedCssContent = $this->invokeAssetControllerMethod('adjustCssUrl', [$cssContent, $inputFilePath, $outputFilePath]); - - $this->assertEquals($expectedCssContent, $adjustedCssContent, 'Unable to adjust CSS correctly!'); - } - - /** - * Data provider for [[testFindRealPath()]] - * @return array test data - */ - public function findRealPathDataProvider() - { - return [ - [ - '/linux/absolute/path', - '/linux/absolute/path', - ], - [ - '/linux/up/../path', - '/linux/path', - ], - [ - '/linux/twice/up/../../path', - '/linux/path', - ], - [ - '/linux/../mix/up/../path', - '/mix/path', - ], - [ - 'C:\\windows\\absolute\\path', - 'C:\\windows\\absolute\\path', - ], - [ - 'C:\\windows\\up\\..\\path', - 'C:\\windows\\path', - ], - ]; - } - - /** - * @dataProvider findRealPathDataProvider - * - * @param string $sourcePath - * @param string $expectedRealPath - */ - public function testFindRealPath($sourcePath, $expectedRealPath) - { - $expectedRealPath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $expectedRealPath); - $realPath = $this->invokeAssetControllerMethod('findRealPath', [$sourcePath]); - $this->assertEquals($expectedRealPath, $realPath); - } -} - -/** - * Mock class for [[\yii\console\controllers\AssetController]] - */ -class AssetControllerMock extends AssetController -{ - use StdOutBufferControllerTrait; -} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/CacheControllerTest.php b/tests/unit/framework/console/controllers/CacheControllerTest.php deleted file mode 100644 index 73e00d10d3..0000000000 --- a/tests/unit/framework/console/controllers/CacheControllerTest.php +++ /dev/null @@ -1,143 +0,0 @@ -_cacheController = Yii::createObject([ - 'class' => 'yiiunit\framework\console\controllers\SilencedCacheController', - 'interactive' => false, - ],[null, null]); //id and module are null - - $databases = self::getParam('databases'); - $config = $databases[$this->driverName]; - $pdoDriver = 'pdo_' . $this->driverName; - - if (!extension_loaded('pdo') || !extension_loaded($pdoDriver)) { - $this->markTestSkipped('pdo and ' . $pdoDriver . ' extensions are required.'); - } - - - $this->mockApplication([ - 'components' => [ - 'firstCache' => 'yii\caching\ArrayCache', - 'secondCache' => 'yii\caching\ArrayCache', - 'session' => 'yii\web\CacheSession', // should be ignored at `actionFlushAll()` - 'db' => [ - 'class' => isset($config['class']) ? $config['class'] : 'yii\db\Connection', - 'dsn' => $config['dsn'], - 'username' => isset($config['username']) ? $config['username'] : null, - 'password' => isset($config['password']) ? $config['password'] : null, - 'enableSchemaCache' => true, - 'schemaCache' => 'firstCache', - ], - ], - ]); - - if(isset($config['fixture'])) { - Yii::$app->db->open(); - $lines = explode(';', file_get_contents($config['fixture'])); - foreach ($lines as $line) { - if (trim($line) !== '') { - Yii::$app->db->pdo->exec($line); - } - } - } - } - - public function testFlushOne() - { - Yii::$app->firstCache->set('firstKey', 'firstValue'); - Yii::$app->firstCache->set('secondKey', 'secondValue'); - Yii::$app->secondCache->set('thirdKey', 'thirdValue'); - - $this->_cacheController->actionFlush('firstCache'); - - $this->assertFalse(Yii::$app->firstCache->get('firstKey'),'first cache data should be flushed'); - $this->assertFalse(Yii::$app->firstCache->get('secondKey'),'first cache data should be flushed'); - $this->assertEquals('thirdValue', Yii::$app->secondCache->get('thirdKey'), 'second cache data should not be flushed'); - } - - public function testClearSchema() - { - $schema = Yii::$app->db->schema; - Yii::$app->db->createCommand()->createTable('test_schema_cache', ['id' => 'pk'])->execute(); - $noCacheSchemas = $schema->getTableSchemas('', true); - $cacheSchema = $schema->getTableSchemas('', false); - - $this->assertEquals($noCacheSchemas, $cacheSchema, 'Schema should not be modified.'); - - Yii::$app->db->createCommand()->dropTable('test_schema_cache')->execute(); - $noCacheSchemas = $schema->getTableSchemas('', true); - $this->assertNotEquals($noCacheSchemas, $cacheSchema, 'Schemas should be different.'); - - $this->_cacheController->actionFlushSchema('db'); - $cacheSchema = $schema->getTableSchemas('', false); - $this->assertEquals($noCacheSchemas, $cacheSchema, 'Schema cache should be flushed.'); - - } - - public function testFlushBoth() - { - Yii::$app->firstCache->set('firstKey', 'firstValue'); - Yii::$app->firstCache->set('secondKey', 'secondValue'); - Yii::$app->secondCache->set('thirdKey', 'secondValue'); - - $this->_cacheController->actionFlush('firstCache', 'secondCache'); - - $this->assertFalse(Yii::$app->firstCache->get('firstKey'),'first cache data should be flushed'); - $this->assertFalse(Yii::$app->firstCache->get('secondKey'),'first cache data should be flushed'); - $this->assertFalse(Yii::$app->secondCache->get('thirdKey'), 'second cache data should be flushed'); - } - - public function testNotFoundFlush() - { - Yii::$app->firstCache->set('firstKey', 'firstValue'); - - $this->_cacheController->actionFlush('notExistingCache'); - - $this->assertEquals('firstValue', Yii::$app->firstCache->get('firstKey'), 'first cache data should not be flushed'); - } - - /** - * @expectedException \yii\console\Exception - */ - public function testNothingToFlushException() - { - $this->_cacheController->actionFlush(); - } - - public function testFlushAll() - { - Yii::$app->firstCache->set('firstKey', 'firstValue'); - Yii::$app->secondCache->set('thirdKey', 'secondValue'); - - $this->_cacheController->actionFlushAll(); - - $this->assertFalse(Yii::$app->firstCache->get('firstKey'),'first cache data should be flushed'); - $this->assertFalse(Yii::$app->secondCache->get('thirdKey'), 'second cache data should be flushed'); - } - -} diff --git a/tests/unit/framework/console/controllers/EchoMigrateController.php b/tests/unit/framework/console/controllers/EchoMigrateController.php deleted file mode 100644 index 9e362bd80b..0000000000 --- a/tests/unit/framework/console/controllers/EchoMigrateController.php +++ /dev/null @@ -1,18 +0,0 @@ -_fixtureController = Yii::createObject([ - 'class' => 'yiiunit\framework\console\controllers\FixtureConsoledController', - 'interactive' => false, - 'globalFixtures' => [], - 'namespace' => 'yiiunit\data\console\controllers\fixtures', - ],[null, null]); //id and module are null - } - - protected function tearDown() - { - $this->_fixtureController = null; - FixtureStorage::clear(); - - parent::tearDown(); - } - - public function testLoadGlobalFixture() - { - $this->_fixtureController->globalFixtures = [ - '\yiiunit\data\console\controllers\fixtures\Global' - ]; - - $this->_fixtureController->actionLoad('First'); - - $this->assertCount(1, FixtureStorage::$globalFixturesData, 'global fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - } - - public function testUnloadGlobalFixture() - { - $this->_fixtureController->globalFixtures = [ - '\yiiunit\data\console\controllers\fixtures\Global' - ]; - - FixtureStorage::$globalFixturesData[] = 'some seeded global fixture data'; - FixtureStorage::$firstFixtureData[] = 'some seeded first fixture data'; - - $this->assertCount(1, FixtureStorage::$globalFixturesData, 'global fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - - $this->_fixtureController->actionUnload('First'); - - $this->assertEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should be unloaded'); - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should be unloaded'); - } - - public function testLoadAll() - { - $this->assertEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should be empty'); - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should be empty'); - $this->assertEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should be empty'); - - $this->_fixtureController->actionLoad('*'); - - $this->assertCount(1, FixtureStorage::$globalFixturesData, 'global fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$secondFixtureData, 'second fixture data should be loaded'); - } - - public function testUnloadAll() - { - FixtureStorage::$globalFixturesData[] = 'some seeded global fixture data'; - FixtureStorage::$firstFixtureData[] = 'some seeded first fixture data'; - FixtureStorage::$secondFixtureData[] = 'some seeded second fixture data'; - - $this->assertCount(1, FixtureStorage::$globalFixturesData, 'global fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - $this->assertCount(1, FixtureStorage::$secondFixtureData, 'second fixture data should be loaded'); - - $this->_fixtureController->actionUnload('*'); - - $this->assertEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should be unloaded'); - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should be unloaded'); - $this->assertEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should be unloaded'); - } - - public function testLoadParticularExceptOnes() - { - $this->_fixtureController->actionLoad('First', '-Second', '-Global'); - - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - $this->assertEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should not be loaded'); - $this->assertEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should not be loaded'); - } - - public function testUnloadParticularExceptOnes() - { - FixtureStorage::$globalFixturesData[] = 'some seeded global fixture data'; - FixtureStorage::$firstFixtureData[] = 'some seeded first fixture data'; - FixtureStorage::$secondFixtureData[] = 'some seeded second fixture data'; - - $this->_fixtureController->actionUnload('First', '-Second', '-Global'); - - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should be unloaded'); - $this->assertNotEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should not be unloaded'); - $this->assertNotEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should not be unloaded'); - } - - public function testLoadAllExceptOnes() - { - $this->_fixtureController->actionLoad('*', '-Second', '-Global'); - - $this->assertCount(1, FixtureStorage::$firstFixtureData, 'first fixture data should be loaded'); - $this->assertEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should not be loaded'); - $this->assertEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should not be loaded'); - } - - public function testUnloadAllExceptOnes() - { - FixtureStorage::$globalFixturesData[] = 'some seeded global fixture data'; - FixtureStorage::$firstFixtureData[] = 'some seeded first fixture data'; - FixtureStorage::$secondFixtureData[] = 'some seeded second fixture data'; - - $this->_fixtureController->actionUnload('*', '-Second', '-Global'); - - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should be unloaded'); - $this->assertNotEmpty(FixtureStorage::$globalFixturesData, 'global fixture data should not be unloaded'); - $this->assertNotEmpty(FixtureStorage::$secondFixtureData, 'second fixture data should not be unloaded'); - } - - public function testNothingToLoadParticularExceptOnes() - { - $this->_fixtureController->actionLoad('First', '-First'); - - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should not be loaded'); - } - - public function testNothingToUnloadParticularExceptOnes() - { - $this->_fixtureController->actionUnload('First', '-First'); - - $this->assertEmpty(FixtureStorage::$firstFixtureData, 'first fixture data should not be loaded'); - } - - /** - * @expectedException \yii\console\Exception - */ - public function testNoFixturesWereFoundInLoad() - { - $this->_fixtureController->actionLoad('NotExistingFixture'); - } - - /** - * @expectedException \yii\console\Exception - */ - public function testNoFixturesWereFoundInUnload() - { - $this->_fixtureController->actionUnload('NotExistingFixture'); - } - -} - -class FixtureConsoledController extends FixtureController -{ - - public function stdout($string) - { - } - -} diff --git a/tests/unit/framework/console/controllers/MigrateControllerTest.php b/tests/unit/framework/console/controllers/MigrateControllerTest.php deleted file mode 100644 index 02346feaaa..0000000000 --- a/tests/unit/framework/console/controllers/MigrateControllerTest.php +++ /dev/null @@ -1,53 +0,0 @@ -migrateControllerClass = EchoMigrateController::className(); - $this->migrationBaseClass = Migration::className(); - - $this->mockApplication([ - 'components' => [ - 'db' => [ - 'class' => 'yii\db\Connection', - 'dsn' => 'sqlite::memory:', - ], - ], - ]); - - $this->setUpMigrationPath(); - parent::setUp(); - } - - public function tearDown() - { - $this->tearDownMigrationPath(); - parent::tearDown(); - } - - /** - * @return array applied migration entries - */ - protected function getMigrationHistory() - { - $query = new Query(); - return $query->from('migration')->all(); - } -} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/PHPMessageControllerTest.php b/tests/unit/framework/console/controllers/PHPMessageControllerTest.php deleted file mode 100644 index 918c49dae5..0000000000 --- a/tests/unit/framework/console/controllers/PHPMessageControllerTest.php +++ /dev/null @@ -1,87 +0,0 @@ -messagePath = Yii::getAlias('@yiiunit/runtime/test_messages'); - FileHelper::createDirectory($this->messagePath, 0777); - } - - public function tearDown() - { - parent::tearDown(); - FileHelper::removeDirectory($this->messagePath); - } - - /** - * @inheritdoc - */ - protected function getDefaultConfig() - { - return [ - 'format' => 'php', - 'languages' => [$this->language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'overwrite' => true, - ]; - } - - /** - * @param string $category - * @return string message file path - */ - protected function getMessageFilePath($category) - { - return $this->messagePath . '/' . $this->language . '/' . $category . '.php'; - } - - /** - * @inheritdoc - */ - protected function saveMessages($messages, $category) - { - $fileName = $this->getMessageFilePath($category); - if (file_exists($fileName)) { - unlink($fileName); - } else { - $dirName = dirname($fileName); - if (!file_exists($dirName)) { - mkdir($dirName, 0777, true); - } - } - $fileContent = 'getMessageFilePath($category); - - if (!file_exists($messageFilePath)) { - return []; - } - return require $messageFilePath; - } -} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/POMessageControllerTest.php b/tests/unit/framework/console/controllers/POMessageControllerTest.php deleted file mode 100644 index 18f2c74f32..0000000000 --- a/tests/unit/framework/console/controllers/POMessageControllerTest.php +++ /dev/null @@ -1,87 +0,0 @@ -markTestSkipped('POMessageControllerTest can not run on HHVM because it relies on saving and re-including PHP files which is not supported by HHVM.'); - } - - $this->messagePath = Yii::getAlias('@yiiunit/runtime/test_messages'); - FileHelper::createDirectory($this->messagePath, 0777); - } - - public function tearDown() - { - parent::tearDown(); - FileHelper::removeDirectory($this->messagePath); - } - - /** - * @inheritdoc - */ - protected function getDefaultConfig() - { - return [ - 'format' => 'po', - 'languages' => [$this->language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'overwrite' => true, - ]; - } - - /** - * @return string message file path - */ - protected function getMessageFilePath() - { - return $this->messagePath . '/' . $this->language . '/' . $this->catalog . '.po'; - } - - /** - * @inheritdoc - */ - protected function saveMessages($messages, $category) - { - $messageFilePath = $this->getMessageFilePath(); - FileHelper::createDirectory(dirname($messageFilePath), 0777); - $gettext = new GettextPoFile(); - - $data = []; - foreach ($messages as $message => $translation) { - $data[$category . chr(4) . $message] = $translation; - } - - $gettext->save($messageFilePath, $data); - } - - /** - * @inheritdoc - */ - protected function loadMessages($category) - { - $messageFilePath = $this->getMessageFilePath(); - if (!file_exists($messageFilePath)) { - return []; - } - - $gettext = new GettextPoFile(); - return $gettext->load($messageFilePath, $category); - } -} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/SilencedCacheController.php b/tests/unit/framework/console/controllers/SilencedCacheController.php deleted file mode 100644 index 40eed6ee29..0000000000 --- a/tests/unit/framework/console/controllers/SilencedCacheController.php +++ /dev/null @@ -1,20 +0,0 @@ -stdOutBuffer .= $string; - } - - public function flushStdOutBuffer() - { - $result = $this->stdOutBuffer; - $this->stdOutBuffer = ''; - return $result; - } -} \ No newline at end of file diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php deleted file mode 100644 index f62b0122c4..0000000000 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ /dev/null @@ -1,180 +0,0 @@ - - * @since 2.0 - * - * @group data - * @group db - */ -class ActiveDataProviderTest extends DatabaseTestCase -{ - protected function setUp() - { - parent::setUp(); - ActiveRecord::$db = $this->getConnection(); - } - - public function testActiveQuery() - { - $provider = new ActiveDataProvider([ - 'query' => Order::find()->orderBy('id'), - ]); - $orders = $provider->getModels(); - $this->assertEquals(3, count($orders)); - $this->assertTrue($orders[0] instanceof Order); - $this->assertTrue($orders[1] instanceof Order); - $this->assertTrue($orders[2] instanceof Order); - $this->assertEquals([1, 2, 3], $provider->getKeys()); - - $provider = new ActiveDataProvider([ - 'query' => Order::find(), - 'pagination' => [ - 'pageSize' => 2, - ] - ]); - $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); - } - - public function testActiveRelation() - { - /* @var $customer Customer */ - $customer = Customer::findOne(2); - $provider = new ActiveDataProvider([ - 'query' => $customer->getOrders(), - ]); - $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); - $this->assertTrue($orders[0] instanceof Order); - $this->assertTrue($orders[1] instanceof Order); - $this->assertEquals([2, 3], $provider->getKeys()); - - $provider = new ActiveDataProvider([ - 'query' => $customer->getOrders(), - 'pagination' => [ - 'pageSize' => 1, - ] - ]); - $orders = $provider->getModels(); - $this->assertEquals(1, count($orders)); - } - - public function testActiveRelationVia() - { - /* @var $order Order */ - $order = Order::findOne(2); - $provider = new ActiveDataProvider([ - 'query' => $order->getItems(), - ]); - $items = $provider->getModels(); - $this->assertEquals(3, count($items)); - $this->assertTrue($items[0] instanceof Item); - $this->assertTrue($items[1] instanceof Item); - $this->assertTrue($items[2] instanceof Item); - $this->assertEquals([3, 4, 5], $provider->getKeys()); - - $provider = new ActiveDataProvider([ - 'query' => $order->getItems(), - 'pagination' => [ - 'pageSize' => 2, - ] - ]); - $items = $provider->getModels(); - $this->assertEquals(2, count($items)); - } - - public function testActiveRelationViaTable() - { - /* @var $order Order */ - $order = Order::findOne(1); - $provider = new ActiveDataProvider([ - 'query' => $order->getBooks(), - ]); - $items = $provider->getModels(); - $this->assertEquals(2, count($items)); - $this->assertTrue($items[0] instanceof Item); - $this->assertTrue($items[1] instanceof Item); - - $provider = new ActiveDataProvider([ - 'query' => $order->getBooks(), - 'pagination' => [ - 'pageSize' => 1, - ] - ]); - $items = $provider->getModels(); - $this->assertEquals(1, count($items)); - } - - public function testQuery() - { - $query = new Query; - $provider = new ActiveDataProvider([ - 'db' => $this->getConnection(), - 'query' => $query->from('order')->orderBy('id'), - ]); - $orders = $provider->getModels(); - $this->assertEquals(3, count($orders)); - $this->assertTrue(is_array($orders[0])); - $this->assertEquals([0, 1, 2], $provider->getKeys()); - - $query = new Query; - $provider = new ActiveDataProvider([ - 'db' => $this->getConnection(), - 'query' => $query->from('order'), - 'pagination' => [ - 'pageSize' => 2, - ] - ]); - $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); - } - - public function testRefresh() - { - $query = new Query; - $provider = new ActiveDataProvider([ - 'db' => $this->getConnection(), - 'query' => $query->from('order')->orderBy('id'), - ]); - $this->assertEquals(3, count($provider->getModels())); - - $provider->getPagination()->pageSize = 2; - $this->assertEquals(3, count($provider->getModels())); - $provider->refresh(); - $this->assertEquals(2, count($provider->getModels())); - } - - public function testPaginationBeforeModels() - { - $query = new Query; - $provider = new ActiveDataProvider([ - 'db' => $this->getConnection(), - 'query' => $query->from('order')->orderBy('id'), - ]); - $pagination = $provider->getPagination(); - $this->assertEquals(0, $pagination->getPageCount()); - $this->assertCount(3, $provider->getModels()); - $this->assertEquals(1, $pagination->getPageCount()); - - $provider->getPagination()->pageSize = 2; - $this->assertEquals(3, count($provider->getModels())); - $provider->refresh(); - $this->assertEquals(2, count($provider->getModels())); - } -} diff --git a/tests/unit/framework/data/SortTest.php b/tests/unit/framework/data/SortTest.php deleted file mode 100644 index 11b18f641a..0000000000 --- a/tests/unit/framework/data/SortTest.php +++ /dev/null @@ -1,180 +0,0 @@ - - * @since 2.0 - * - * @group data - */ -class SortTest extends TestCase -{ - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - - public function testGetOrders() - { - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - ]); - - $orders = $sort->getOrders(); - $this->assertEquals(3, count($orders)); - $this->assertEquals(SORT_ASC, $orders['age']); - $this->assertEquals(SORT_DESC, $orders['first_name']); - $this->assertEquals(SORT_DESC, $orders['last_name']); - - $sort->enableMultiSort = false; - $orders = $sort->getOrders(true); - $this->assertEquals(1, count($orders)); - $this->assertEquals(SORT_ASC, $orders['age']); - } - - public function testGetAttributeOrders() - { - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - ]); - - $orders = $sort->getAttributeOrders(); - $this->assertEquals(2, count($orders)); - $this->assertEquals(SORT_ASC, $orders['age']); - $this->assertEquals(SORT_DESC, $orders['name']); - - $sort->enableMultiSort = false; - $orders = $sort->getAttributeOrders(true); - $this->assertEquals(1, count($orders)); - $this->assertEquals(SORT_ASC, $orders['age']); - } - - public function testGetAttributeOrder() - { - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - ]); - - $this->assertEquals(SORT_ASC, $sort->getAttributeOrder('age')); - $this->assertEquals(SORT_DESC, $sort->getAttributeOrder('name')); - $this->assertNull($sort->getAttributeOrder('xyz')); - } - - public function testCreateSortParam() - { - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - 'route' => 'site/index', - ]); - - $this->assertEquals('-age,-name', $sort->createSortParam('age')); - $this->assertEquals('name,age', $sort->createSortParam('name')); - } - - public function testCreateUrl() - { - $manager = new UrlManager([ - 'baseUrl' => '/', - 'ScriptUrl' => '/index.php', - 'cache' => null, - ]); - - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - 'urlManager' => $manager, - 'route' => 'site/index', - ]); - - $this->assertEquals('/index.php?r=site%2Findex&sort=-age%2C-name', $sort->createUrl('age')); - $this->assertEquals('/index.php?r=site%2Findex&sort=name%2Cage', $sort->createUrl('name')); - } - - public function testLink() - { - $this->mockApplication(); - $manager = new UrlManager([ - 'baseUrl' => '/', - 'scriptUrl' => '/index.php', - 'cache' => null, - ]); - - $sort = new Sort([ - 'attributes' => [ - 'age', - 'name' => [ - 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], - 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], - ], - ], - 'params' => [ - 'sort' => 'age,-name' - ], - 'enableMultiSort' => true, - 'urlManager' => $manager, - 'route' => 'site/index', - ]); - - $this->assertEquals('Age', $sort->link('age')); - } -} diff --git a/tests/unit/framework/db/BatchQueryResultTest.php b/tests/unit/framework/db/BatchQueryResultTest.php deleted file mode 100644 index b5b10b3f76..0000000000 --- a/tests/unit/framework/db/BatchQueryResultTest.php +++ /dev/null @@ -1,136 +0,0 @@ -getConnection(); - } - - public function testQuery() - { - $db = $this->getConnection(); - - // initialize property test - $query = new Query(); - $query->from('customer')->orderBy('id'); - $result = $query->batch(2, $db); - $this->assertTrue($result instanceof BatchQueryResult); - $this->assertEquals(2, $result->batchSize); - $this->assertTrue($result->query === $query); - - // normal query - $query = new Query(); - $query->from('customer')->orderBy('id'); - $allRows = []; - $batch = $query->batch(2, $db); - foreach ($batch as $rows) { - $allRows = array_merge($allRows, $rows); - } - $this->assertEquals(3, count($allRows)); - $this->assertEquals('user1', $allRows[0]['name']); - $this->assertEquals('user2', $allRows[1]['name']); - $this->assertEquals('user3', $allRows[2]['name']); - // rewind - $allRows = []; - foreach ($batch as $rows) { - $allRows = array_merge($allRows, $rows); - } - $this->assertEquals(3, count($allRows)); - // reset - $batch->reset(); - - // empty query - $query = new Query(); - $query->from('customer')->where(['id' => 100]); - $allRows = []; - $batch = $query->batch(2, $db); - foreach ($batch as $rows) { - $allRows = array_merge($allRows, $rows); - } - $this->assertEquals(0, count($allRows)); - - // query with index - $query = new Query(); - $query->from('customer')->indexBy('name'); - $allRows = []; - foreach ($query->batch(2, $db) as $rows) { - $allRows = array_merge($allRows, $rows); - } - $this->assertEquals(3, count($allRows)); - $this->assertEquals('address1', $allRows['user1']['address']); - $this->assertEquals('address2', $allRows['user2']['address']); - $this->assertEquals('address3', $allRows['user3']['address']); - - // each - $query = new Query(); - $query->from('customer')->orderBy('id'); - $allRows = []; - foreach ($query->each(100, $db) as $rows) { - $allRows[] = $rows; - } - $this->assertEquals(3, count($allRows)); - $this->assertEquals('user1', $allRows[0]['name']); - $this->assertEquals('user2', $allRows[1]['name']); - $this->assertEquals('user3', $allRows[2]['name']); - - // each with key - $query = new Query(); - $query->from('customer')->orderBy('id')->indexBy('name'); - $allRows = []; - foreach ($query->each(100, $db) as $key => $row) { - $allRows[$key] = $row; - } - $this->assertEquals(3, count($allRows)); - $this->assertEquals('address1', $allRows['user1']['address']); - $this->assertEquals('address2', $allRows['user2']['address']); - $this->assertEquals('address3', $allRows['user3']['address']); - } - - public function testActiveQuery() - { - $db = $this->getConnection(); - - $query = Customer::find()->orderBy('id'); - $customers = []; - foreach ($query->batch(2, $db) as $models) { - $customers = array_merge($customers, $models); - } - $this->assertEquals(3, count($customers)); - $this->assertEquals('user1', $customers[0]->name); - $this->assertEquals('user2', $customers[1]->name); - $this->assertEquals('user3', $customers[2]->name); - - // batch with eager loading - $query = Customer::find()->with('orders')->orderBy('id'); - $customers = []; - foreach ($query->batch(2, $db) as $models) { - $customers = array_merge($customers, $models); - foreach ($models as $model) { - $this->assertTrue($model->isRelationPopulated('orders')); - } - } - $this->assertEquals(3, count($customers)); - $this->assertEquals(1, count($customers[0]->orders)); - $this->assertEquals(2, count($customers[1]->orders)); - $this->assertEquals(0, count($customers[2]->orders)); - } -} diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php deleted file mode 100644 index 3343aaf3f5..0000000000 --- a/tests/unit/framework/db/CommandTest.php +++ /dev/null @@ -1,363 +0,0 @@ -getConnection(false); - - // null - $command = $db->createCommand(); - $this->assertEquals(null, $command->sql); - - // string - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $this->assertEquals($sql, $command->sql); - } - - public function testGetSetSql() - { - $db = $this->getConnection(false); - - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $this->assertEquals($sql, $command->sql); - - $sql2 = 'SELECT * FROM order'; - $command->sql = $sql2; - $this->assertEquals($sql2, $command->sql); - } - - public function testAutoQuoting() - { - $db = $this->getConnection(false); - - $sql = 'SELECT [[id]], [[t.name]] FROM {{customer}} t'; - $command = $db->createCommand($sql); - $this->assertEquals("SELECT `id`, `t`.`name` FROM `customer` t", $command->sql); - } - - public function testPrepareCancel() - { - $db = $this->getConnection(false); - - $command = $db->createCommand('SELECT * FROM customer'); - $this->assertEquals(null, $command->pdoStatement); - $command->prepare(); - $this->assertNotEquals(null, $command->pdoStatement); - $command->cancel(); - $this->assertEquals(null, $command->pdoStatement); - } - - public function testExecute() - { - $db = $this->getConnection(); - - $sql = 'INSERT INTO customer(email, name , address) VALUES (\'user4@example.com\', \'user4\', \'address4\')'; - $command = $db->createCommand($sql); - $this->assertEquals(1, $command->execute()); - - $sql = 'SELECT COUNT(*) FROM customer WHERE name =\'user4\''; - $command = $db->createCommand($sql); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); - $command->execute(); - } - - public function testQuery() - { - $db = $this->getConnection(); - - // query - $sql = 'SELECT * FROM customer'; - $reader = $db->createCommand($sql)->query(); - $this->assertTrue($reader instanceof DataReader); - - // queryAll - $rows = $db->createCommand('SELECT * FROM customer')->queryAll(); - $this->assertEquals(3, count($rows)); - $row = $rows[2]; - $this->assertEquals(3, $row['id']); - $this->assertEquals('user3', $row['name']); - - $rows = $db->createCommand('SELECT * FROM customer WHERE id=10')->queryAll(); - $this->assertEquals([], $rows); - - // queryOne - $sql = 'SELECT * FROM customer ORDER BY id'; - $row = $db->createCommand($sql)->queryOne(); - $this->assertEquals(1, $row['id']); - $this->assertEquals('user1', $row['name']); - - $sql = 'SELECT * FROM customer ORDER BY id'; - $command = $db->createCommand($sql); - $command->prepare(); - $row = $command->queryOne(); - $this->assertEquals(1, $row['id']); - $this->assertEquals('user1', $row['name']); - - $sql = 'SELECT * FROM customer WHERE id=10'; - $command = $db->createCommand($sql); - $this->assertFalse($command->queryOne()); - - // queryColumn - $sql = 'SELECT * FROM customer'; - $column = $db->createCommand($sql)->queryColumn(); - $this->assertEquals(range(1, 3), $column); - - $command = $db->createCommand('SELECT id FROM customer WHERE id=10'); - $this->assertEquals([], $command->queryColumn()); - - // queryScalar - $sql = 'SELECT * FROM customer ORDER BY id'; - $this->assertEquals($db->createCommand($sql)->queryScalar(), 1); - - $sql = 'SELECT id FROM customer ORDER BY id'; - $command = $db->createCommand($sql); - $command->prepare(); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('SELECT id FROM customer WHERE id=10'); - $this->assertFalse($command->queryScalar()); - - $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); - $command->query(); - } - - public function testBindParamValue() - { - $db = $this->getConnection(); - - // bindParam - $sql = 'INSERT INTO customer(email, name, address) VALUES (:email, :name, :address)'; - $command = $db->createCommand($sql); - $email = 'user4@example.com'; - $name = 'user4'; - $address = 'address4'; - $command->bindParam(':email', $email); - $command->bindParam(':name', $name); - $command->bindParam(':address', $address); - $command->execute(); - - $sql = 'SELECT name FROM customer WHERE email=:email'; - $command = $db->createCommand($sql); - $command->bindParam(':email', $email); - $this->assertEquals($name, $command->queryScalar()); - - $sql = 'INSERT INTO type (int_col, char_col, float_col, blob_col, numeric_col, bool_col) VALUES (:int_col, :char_col, :float_col, :blob_col, :numeric_col, :bool_col)'; - $command = $db->createCommand($sql); - $intCol = 123; - $charCol = str_repeat('abc', 33) . 'x'; // a 100 char string - $floatCol = 1.23; - $blobCol = "\x10\x11\x12"; - $numericCol = '1.23'; - $boolCol = false; - $command->bindParam(':int_col', $intCol); - $command->bindParam(':char_col', $charCol); - $command->bindParam(':float_col', $floatCol); - $command->bindParam(':blob_col', $blobCol); - $command->bindParam(':numeric_col', $numericCol); - $command->bindParam(':bool_col', $boolCol); - $this->assertEquals(1, $command->execute()); - - $command = $db->createCommand('SELECT int_col, char_col, float_col, blob_col, numeric_col, bool_col FROM type'); -// $command->prepare(); -// $command->pdoStatement->bindColumn('blob_col', $bc, \PDO::PARAM_LOB); - $row = $command->queryOne(); - $this->assertEquals($intCol, $row['int_col']); - $this->assertEquals($charCol, $row['char_col']); - $this->assertEquals($floatCol, $row['float_col']); - if ($this->driverName === 'mysql' || $this->driverName === 'sqlite') { - $this->assertEquals($blobCol, $row['blob_col']); - } else { - $this->assertTrue(is_resource($row['blob_col'])); - $this->assertEquals($blobCol, stream_get_contents($row['blob_col'])); - } - $this->assertEquals($numericCol, $row['numeric_col']); - if ($this->driverName === 'mysql' || defined('HHVM_VERSION') && $this->driverName === 'sqlite') { - $this->assertEquals($boolCol, (int) $row['bool_col']); - } else { - $this->assertEquals($boolCol, $row['bool_col']); - } - - // bindValue - $sql = 'INSERT INTO customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; - $command = $db->createCommand($sql); - $command->bindValue(':email', 'user5@example.com'); - $command->execute(); - - $sql = 'SELECT email FROM customer WHERE name=:name'; - $command = $db->createCommand($sql); - $command->bindValue(':name', 'user5'); - $this->assertEquals('user5@example.com', $command->queryScalar()); - } - - public function testFetchMode() - { - $db = $this->getConnection(); - - // default: FETCH_ASSOC - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $result = $command->queryOne(); - $this->assertTrue(is_array($result) && isset($result['id'])); - - // FETCH_OBJ, customized via fetchMode property - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $command->fetchMode = \PDO::FETCH_OBJ; - $result = $command->queryOne(); - $this->assertTrue(is_object($result)); - - // FETCH_NUM, customized in query method - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $result = $command->queryOne([], \PDO::FETCH_NUM); - $this->assertTrue(is_array($result) && isset($result[0])); - } - - public function testBatchInsert() - { - $command = $this->getConnection()->createCommand(); - $command->batchInsert('customer', - ['email', 'name', 'address'], [ - ['t1@example.com', 't1', 't1 address'], - ['t2@example.com', null, false], - ] - ); - $this->assertEquals(2, $command->execute()); - } - - /* - public function testInsert() - { - } - - public function testUpdate() - { - } - - public function testDelete() - { - } - - public function testCreateTable() - { - } - - public function testRenameTable() - { - } - - public function testDropTable() - { - } - - public function testTruncateTable() - { - } - - public function testAddColumn() - { - } - - public function testDropColumn() - { - } - - public function testRenameColumn() - { - } - - public function testAlterColumn() - { - } - - public function testAddForeignKey() - { - } - - public function testDropForeignKey() - { - } - - public function testCreateIndex() - { - } - - public function testDropIndex() - { - } - */ - - public function testIntegrityViolation() - { - $this->setExpectedException('\yii\db\IntegrityException'); - - $db = $this->getConnection(); - - $sql = 'INSERT INTO profile(id, description) VALUES (123, \'duplicate\')'; - $command = $db->createCommand($sql); - $command->execute(); - $command->execute(); - } - - public function testQueryCache() - { - $db = $this->getConnection(); - $db->enableQueryCache = true; - $db->queryCache = new FileCache(['cachePath' => '@yiiunit/runtime/cache']); - $command = $db->createCommand('SELECT name FROM customer WHERE id=:id'); - - $this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar()); - $update = $db->createCommand('UPDATE customer SET name=:name WHERE id=:id'); - $update->bindValues([':id' => 1, ':name' => 'user11'])->execute(); - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - - $db->cache(function (Connection $db) use ($command, $update) { - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - $update->bindValues([':id' => 2, ':name' => 'user22'])->execute(); - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - - $db->noCache(function () use ($command) { - $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar()); - }); - - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - }, 10); - - $db->enableQueryCache = false; - $db->cache(function ($db) use ($command, $update) { - $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar()); - $update->bindValues([':id' => 2, ':name' => 'user2'])->execute(); - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - }, 10); - - $db->enableQueryCache = true; - $command = $db->createCommand('SELECT name FROM customer WHERE id=:id')->cache(); - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - $update->bindValues([':id' => 1, ':name' => 'user1'])->execute(); - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar()); - - $command = $db->createCommand('SELECT name FROM customer WHERE id=:id'); - $db->cache(function (Connection $db) use ($command, $update) { - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar()); - }, 10); - } -} diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php deleted file mode 100644 index bd24eea9fa..0000000000 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ /dev/null @@ -1,479 +0,0 @@ -driverName) { - case 'mysql': - return new MysqlQueryBuilder($this->getConnection(true, false)); - case 'sqlite': - return new SqliteQueryBuilder($this->getConnection(true, false)); - case 'mssql': - return new MssqlQueryBuilder($this->getConnection(true, false)); - case 'pgsql': - return new PgsqlQueryBuilder($this->getConnection(true, false)); - case 'cubrid': - return new CubridQueryBuilder($this->getConnection(true, false)); - } - throw new \Exception('Test is not implemented for ' . $this->driverName); - } - - /** - * adjust dbms specific escaping - * @param $sql - * @return mixed - */ - protected function replaceQuotes($sql) - { - if (!in_array($this->driverName, ['mssql', 'mysql', 'sqlite'])) { - return str_replace('`', '"', $sql); - } - return $sql; - } - - /** - * this is not used as a dataprovider for testGetColumnType to speed up the test - * when used as dataprovider every single line will cause a reconnect with the database which is not needed here - */ - public function columnTypes() - { - return [ - [Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'], - [Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'], - [Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_STRING, 'varchar(255)'], - [Schema::TYPE_STRING . '(32)', 'varchar(32)'], - [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], - [Schema::TYPE_TEXT, 'text'], - [Schema::TYPE_TEXT . '(255)', 'text'], - [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], - [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], - [Schema::TYPE_SMALLINT, 'smallint(6)'], - [Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'], - [Schema::TYPE_INTEGER, 'int(11)'], - [Schema::TYPE_INTEGER . '(8)', 'int(8)'], - [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'], - [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'], - [Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'], - [Schema::TYPE_BIGINT, 'bigint(20)'], - [Schema::TYPE_BIGINT . '(8)', 'bigint(8)'], - [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'], - [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'], - [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'], - [Schema::TYPE_FLOAT, 'float'], - [Schema::TYPE_FLOAT . '(16,5)', 'float'], - [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'], - [Schema::TYPE_DOUBLE, 'double'], - [Schema::TYPE_DOUBLE . '(16,5)', 'double'], - [Schema::TYPE_DOUBLE . ' CHECK (value > 5.6)', 'double CHECK (value > 5.6)'], - [Schema::TYPE_DOUBLE . '(16,5) CHECK (value > 5.6)', 'double CHECK (value > 5.6)'], - [Schema::TYPE_DOUBLE . ' NOT NULL', 'double NOT NULL'], - [Schema::TYPE_DECIMAL, 'decimal(10,0)'], - [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], - [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], - [Schema::TYPE_DATETIME, 'datetime'], - [Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], - [Schema::TYPE_TIMESTAMP, 'timestamp'], - [Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], - [Schema::TYPE_TIME, 'time'], - [Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"], - [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], - [Schema::TYPE_DATE, 'date'], - [Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], - [Schema::TYPE_BINARY, 'blob'], - [Schema::TYPE_BOOLEAN, 'tinyint(1)'], - [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'], - [Schema::TYPE_MONEY, 'decimal(19,4)'], - [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], - [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], - ]; - } - - public function testGetColumnType() - { - $qb = $this->getQueryBuilder(); - foreach ($this->columnTypes() as $item) { - list ($column, $expected) = $item; - $this->assertEquals($expected, $qb->getColumnType($column)); - } - } - - public function testCreateTableColumnTypes() - { - $qb = $this->getQueryBuilder(); - if ($qb->db->getTableSchema('column_type_table', true) !== null) { - $this->getConnection(false)->createCommand($qb->dropTable('column_type_table'))->execute(); - } - $columns = []; - $i = 0; - foreach ($this->columnTypes() as $item) { - list ($column, $expected) = $item; - if (strncmp($column, 'pk', 2) !== 0) { - $columns['col' . ++$i] = str_replace('CHECK (value', 'CHECK (col' . $i, $column); - } - } - $this->getConnection(false)->createCommand($qb->createTable('column_type_table', $columns))->execute(); - } - - public function conditionProvider() - { - $conditions = [ - // empty values - [ ['like', 'name', []], '0=1', [] ], - [ ['not like', 'name', []], '', [] ], - [ ['or like', 'name', []], '0=1', [] ], - [ ['or not like', 'name', []], '', [] ], - - // simple like - [ ['like', 'name', 'heyho'], '`name` LIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['not like', 'name', 'heyho'], '`name` NOT LIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['or like', 'name', 'heyho'], '`name` LIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['or not like', 'name', 'heyho'], '`name` NOT LIKE :qp0', [':qp0' => '%heyho%'] ], - - // like for many values - [ ['like', 'name', ['heyho', 'abc']], '`name` LIKE :qp0 AND `name` LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['not like', 'name', ['heyho', 'abc']], '`name` NOT LIKE :qp0 AND `name` NOT LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['or like', 'name', ['heyho', 'abc']], '`name` LIKE :qp0 OR `name` LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['or not like', 'name', ['heyho', 'abc']], '`name` NOT LIKE :qp0 OR `name` NOT LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - - // like with Expression - [ ['like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` LIKE CONCAT("test", colname, "%")', [] ], - [ ['not like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` NOT LIKE CONCAT("test", colname, "%")', [] ], - [ ['or like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` LIKE CONCAT("test", colname, "%")', [] ], - [ ['or not like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` NOT LIKE CONCAT("test", colname, "%")', [] ], - [ ['like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` LIKE CONCAT("test", colname, "%") AND `name` LIKE :qp0', [':qp0' => '%abc%'] ], - [ ['not like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` NOT LIKE CONCAT("test", colname, "%") AND `name` NOT LIKE :qp0', [':qp0' => '%abc%'] ], - [ ['or like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` LIKE CONCAT("test", colname, "%") OR `name` LIKE :qp0', [':qp0' => '%abc%'] ], - [ ['or not like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` NOT LIKE CONCAT("test", colname, "%") OR `name` NOT LIKE :qp0', [':qp0' => '%abc%'] ], - - // not - [ ['not', 'name'], 'NOT (name)', [] ], - - // and - [ ['and', 'id=1', 'id=2'], '(id=1) AND (id=2)', [] ], - [ ['and', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) AND ((id=1) OR (id=2))', [] ], - - // or - [ ['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', [] ], - [ ['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', [] ], - - // between - [ ['between', 'id', 1, 10], '`id` BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ], - [ ['not between', 'id', 1, 10], '`id` NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ], - [ ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')], '`date` BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()', [] ], - [ ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123], '`date` BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0', [':qp0' => 123] ], - [ ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')], '`date` NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()', [] ], - [ ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123], '`date` NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0', [':qp0' => 123] ], - - // in - [ ['in', 'id', [1, 2, 3]], '`id` IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ], - [ ['not in', 'id', [1, 2, 3]], '`id` NOT IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ], - [ ['in', 'id', (new Query())->select('id')->from('users')->where(['active' => 1])], '`id` IN (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - [ ['not in', 'id', (new Query())->select('id')->from('users')->where(['active' => 1])], '`id` NOT IN (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - - // exists - [ ['exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - [ ['not exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - - // simple conditions - [ ['=', 'a', 'b'], '`a` = :qp0', [':qp0' => 'b'] ], - [ ['>', 'a', 1], '`a` > :qp0', [':qp0' => 1] ], - [ ['>=', 'a', 'b'], '`a` >= :qp0', [':qp0' => 'b'] ], - [ ['<', 'a', 2], '`a` < :qp0', [':qp0' => 2] ], - [ ['<=', 'a', 'b'], '`a` <= :qp0', [':qp0' => 'b'] ], - [ ['<>', 'a', 3], '`a` <> :qp0', [':qp0' => 3] ], - [ ['!=', 'a', 'b'], '`a` != :qp0', [':qp0' => 'b'] ], - [ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL 1 MONTH)')], '`date` >= DATE_SUB(NOW(), INTERVAL 1 MONTH)', [] ], - [ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2])], '`date` >= DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2] ], - - // hash condition - [ ['a' => 1, 'b' => 2], '(`a`=:qp0) AND (`b`=:qp1)', [':qp0' => 1, ':qp1' => 2] ], - [ ['a' => new Expression('CONCAT(col1, col2)'), 'b' => 2], '(`a`=CONCAT(col1, col2)) AND (`b`=:qp0)', [':qp0' => 2] ], - - ]; - - switch ($this->driverName) { - case 'mssql': - case 'sqlite': - $conditions = array_merge($conditions, [ - [ ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((`id` = :qp0 AND `name` = :qp1) OR (`id` = :qp2 AND `name` = :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar'] ], - [ ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((`id` != :qp0 OR `name` != :qp1) AND (`id` != :qp2 OR `name` != :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar'] ], - //[ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'EXISTS (SELECT 1 FROM (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0) AS a WHERE a.`id` = `id AND a.`name` = `name`)', [':qp0' => 1] ], - //[ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT 1 FROM (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0) AS a WHERE a.`id` = `id` AND a.`name = `name`)', [':qp0' => 1] ], - ]); - break; - default: - $conditions = array_merge($conditions, [ - [ ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '(`id`, `name`) IN ((:qp0, :qp1), (:qp2, :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar'] ], - [ ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '(`id`, `name`) NOT IN ((:qp0, :qp1), (:qp2, :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar'] ], - [ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - [ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) NOT IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], - ]); - break; - } - - // adjust dbms specific escaping - foreach($conditions as $i => $condition) { - $conditions[$i][1] = $this->replaceQuotes($condition[1]); - } - return $conditions; - } - - public function filterConditionProvider() - { - $conditions = [ - // like - [ ['like', 'name', []], '', [] ], - [ ['not like', 'name', []], '', [] ], - [ ['or like', 'name', []], '', [] ], - [ ['or not like', 'name', []], '', [] ], - - // not - [ ['not', ''], '', [] ], - - // and - [ ['and', '', ''], '', [] ], - [ ['and', '', 'id=2'], '(id=2)', [] ], - [ ['and', 'id=1', ''], '(id=1)', [] ], - [ ['and', 'type=1', ['or', '', 'id=2']], '(type=1) AND ((id=2))', [] ], - - // or - [ ['or', 'id=1', ''], '(id=1)', [] ], - [ ['or', 'type=1', ['or', '', 'id=2']], '(type=1) OR ((id=2))', [] ], - - - // between - [ ['between', 'id', 1, null], '', [] ], - [ ['not between', 'id', null, 10], '', [] ], - - // in - [ ['in', 'id', []], '', [] ], - [ ['not in', 'id', []], '', [] ], - - // simple conditions - [ ['=', 'a', ''], '', [] ], - [ ['>', 'a', ''], '', [] ], - [ ['>=', 'a', ''], '', [] ], - [ ['<', 'a', ''], '', [] ], - [ ['<=', 'a', ''], '', [] ], - [ ['<>', 'a', ''], '', [] ], - [ ['!=', 'a', ''], '', [] ], - ]; - - // adjust dbms specific escaping - foreach($conditions as $i => $condition) { - $conditions[$i][1] = $this->replaceQuotes($condition[1]); - } - return $conditions; - } - - /** - * @dataProvider conditionProvider - */ - public function testBuildCondition($condition, $expected, $expectedParams) - { - $query = (new Query())->where($condition); - list($sql, $params) = $this->getQueryBuilder()->build($query); - $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql); - $this->assertEquals($expectedParams, $params); - } - - /** - * @dataProvider filterConditionProvider - */ - public function testBuildFilterCondition($condition, $expected, $expectedParams) - { - $query = (new Query())->filterWhere($condition); - list($sql, $params) = $this->getQueryBuilder()->build($query); - $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql); - $this->assertEquals($expectedParams, $params); - } - - public function testAddDropPrimaryKey() - { - $tableName = 'constraints'; - $pkeyName = $tableName . "_pkey"; - - // ADD - $qb = $this->getQueryBuilder(); - $qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, ['id'])->execute(); - $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(1, count($tableSchema->primaryKey)); - - //DROP - $qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute(); - $qb = $this->getQueryBuilder(); // resets the schema - $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(0, count($tableSchema->primaryKey)); - } - - public function existsParamsProvider() - { - return [ - ['exists', $this->replaceQuotes("SELECT `id` FROM `TotalExample` `t` WHERE EXISTS (SELECT `1` FROM `Website` `w`)")], - ['not exists', $this->replaceQuotes("SELECT `id` FROM `TotalExample` `t` WHERE NOT EXISTS (SELECT `1` FROM `Website` `w`)")] - ]; - } - - /** - * @dataProvider existsParamsProvider - */ - public function testBuildWhereExists($cond, $expectedQuerySql) - { - $expectedQueryParams = []; - - $subQuery = new Query(); - $subQuery->select('1') - ->from('Website w'); - - $query = new Query(); - $query->select('id') - ->from('TotalExample t') - ->where([$cond, $subQuery]); - - list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query); - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $actualQueryParams); - } - - - public function testBuildWhereExistsWithParameters() - { - $expectedQuerySql = $this->replaceQuotes( - "SELECT `id` FROM `TotalExample` `t` WHERE (EXISTS (SELECT `1` FROM `Website` `w` WHERE (w.id = t.website_id) AND (w.merchant_id = :merchant_id))) AND (t.some_column = :some_value)" - ); - $expectedQueryParams = [':some_value' => "asd", ':merchant_id' => 6]; - - $subQuery = new Query(); - $subQuery->select('1') - ->from('Website w') - ->where('w.id = t.website_id') - ->andWhere('w.merchant_id = :merchant_id', [':merchant_id' => 6]); - - $query = new Query(); - $query->select('id') - ->from('TotalExample t') - ->where(['exists', $subQuery]) - ->andWhere('t.some_column = :some_value', [':some_value' => "asd"]); - - list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $queryParams); - } - - public function testBuildWhereExistsWithArrayParameters() - { - $expectedQuerySql = $this->replaceQuotes( - "SELECT `id` FROM `TotalExample` `t` WHERE (EXISTS (SELECT `1` FROM `Website` `w` WHERE (w.id = t.website_id) AND ((`w`.`merchant_id`=:qp0) AND (`w`.`user_id`=:qp1)))) AND (`t`.`some_column`=:qp2)" - ); - $expectedQueryParams = [':qp0' => 6, ':qp1' => 210, ':qp2' => 'asd']; - - $subQuery = new Query(); - $subQuery->select('1') - ->from('Website w') - ->where('w.id = t.website_id') - ->andWhere(['w.merchant_id' => 6, 'w.user_id' => '210']); - - $query = new Query(); - $query->select('id') - ->from('TotalExample t') - ->where(['exists', $subQuery]) - ->andWhere(['t.some_column' => "asd"]); - - list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $queryParams); - } - - /** - * This test contains three select queries connected with UNION and UNION ALL constructions. - * It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it. - */ - public function testBuildUnion() - { - $expectedQuerySql = $this->replaceQuotes( - "(SELECT `id` FROM `TotalExample` `t1` WHERE (w > 0) AND (x < 2)) UNION ( SELECT `id` FROM `TotalTotalExample` `t2` WHERE w > 5 ) UNION ALL ( SELECT `id` FROM `TotalTotalExample` `t3` WHERE w = 3 )" - ); - $query = new Query(); - $secondQuery = new Query(); - $secondQuery->select('id') - ->from('TotalTotalExample t2') - ->where('w > 5'); - $thirdQuery = new Query(); - $thirdQuery->select('id') - ->from('TotalTotalExample t3') - ->where('w = 3'); - $query->select('id') - ->from('TotalExample t1') - ->where(['and', 'w > 0', 'x < 2']) - ->union($secondQuery) - ->union($thirdQuery, TRUE); - list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals([], $queryParams); - } - - public function testSelectSubquery() - { - $subquery = (new Query()) - ->select('COUNT(*)') - ->from('operations') - ->where('account_id = accounts.id'); - $query = (new Query()) - ->select('*') - ->from('accounts') - ->addSelect(['operations_count' => $subquery]); - list ($sql, $params) = $this->getQueryBuilder()->build($query); - $expected = $this->replaceQuotes('SELECT *, (SELECT COUNT(*) FROM `operations` WHERE account_id = accounts.id) AS `operations_count` FROM `accounts`'); - $this->assertEquals($expected, $sql); - $this->assertEmpty($params); - } - - public function testCompositeInCondition() - { - $condition = [ - 'in', - ['id', 'name'], - [ - ['id' => 1, 'name' => 'foo'], - ['id' => 2, 'name' => 'bar'], - ], - ]; - (new Query())->from('customer')->where($condition)->all($this->getConnection()); - } -} diff --git a/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php b/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php deleted file mode 100644 index f01e69f087..0000000000 --- a/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php +++ /dev/null @@ -1,14 +0,0 @@ -getConnection(); - - // bindParam - $sql = 'INSERT INTO customer(email, name, address) VALUES (:email, :name, :address)'; - $command = $db->createCommand($sql); - $email = 'user4@example.com'; - $name = 'user4'; - $address = 'address4'; - $command->bindParam(':email', $email); - $command->bindParam(':name', $name); - $command->bindParam(':address', $address); - $command->execute(); - - $sql = 'SELECT name FROM customer WHERE email=:email'; - $command = $db->createCommand($sql); - $command->bindParam(':email', $email); - $this->assertEquals($name, $command->queryScalar()); - - $sql = "INSERT INTO type (int_col, char_col, char_col2, enum_col, float_col, blob_col, numeric_col, bool_col) VALUES (:int_col, '', :char_col, :enum_col, :float_col, CHAR_TO_BLOB(:blob_col), :numeric_col, :bool_col)"; - $command = $db->createCommand($sql); - $intCol = 123; - $charCol = 'abc'; - $enumCol = 'a'; - $floatCol = 1.23; - $blobCol = "\x10\x11\x12"; - $numericCol = '1.23'; - $boolCol = true; - $command->bindParam(':int_col', $intCol); - $command->bindParam(':char_col', $charCol); - $command->bindParam(':enum_col', $enumCol); - $command->bindParam(':float_col', $floatCol); - $command->bindParam(':blob_col', $blobCol); - $command->bindParam(':numeric_col', $numericCol); - $command->bindParam(':bool_col', $boolCol); - $this->assertEquals(1, $command->execute()); - - $sql = 'SELECT * FROM type'; - $row = $db->createCommand($sql)->queryOne(); - $this->assertEquals($intCol, $row['int_col']); - $this->assertEquals($enumCol, $row['enum_col']); - $this->assertEquals($charCol, $row['char_col2']); - $this->assertEquals($floatCol, $row['float_col']); - $this->assertEquals($blobCol, fread($row['blob_col'], 3)); - $this->assertEquals($numericCol, $row['numeric_col']); - $this->assertEquals($boolCol, $row['bool_col']); - - // bindValue - $sql = 'INSERT INTO customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; - $command = $db->createCommand($sql); - $command->bindValue(':email', 'user5@example.com'); - $command->execute(); - - $sql = 'SELECT email FROM customer WHERE name=:name'; - $command = $db->createCommand($sql); - $command->bindValue(':name', 'user5'); - $this->assertEquals('user5@example.com', $command->queryScalar()); - } - - public function testAutoQuoting() - { - $db = $this->getConnection(false); - - $sql = 'SELECT [[id]], [[t.name]] FROM {{customer}} t'; - $command = $db->createCommand($sql); - $this->assertEquals('SELECT "id", "t"."name" FROM "customer" t', $command->sql); - } -} diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php deleted file mode 100644 index 12d4c0bc3b..0000000000 --- a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php +++ /dev/null @@ -1,82 +0,0 @@ - 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_STRING, 'varchar(255)'], - [Schema::TYPE_STRING . '(32)', 'varchar(32)'], - [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], - [Schema::TYPE_TEXT, 'varchar'], - [Schema::TYPE_TEXT . '(255)', 'varchar'], - [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . ' NOT NULL', 'varchar NOT NULL'], - [Schema::TYPE_TEXT . '(255) NOT NULL', 'varchar NOT NULL'], - [Schema::TYPE_SMALLINT, 'smallint'], - [Schema::TYPE_SMALLINT . '(8)', 'smallint'], - [Schema::TYPE_INTEGER, 'int'], - [Schema::TYPE_INTEGER . '(8)', 'int'], - [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int CHECK (value > 5)'], - [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int CHECK (value > 5)'], - [Schema::TYPE_INTEGER . ' NOT NULL', 'int NOT NULL'], - [Schema::TYPE_BIGINT, 'bigint'], - [Schema::TYPE_BIGINT . '(8)', 'bigint'], - [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], - [Schema::TYPE_FLOAT, 'float(7)'], - [Schema::TYPE_FLOAT . '(16)', 'float(16)'], - [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float(7) CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)', 'float(16) CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . ' NOT NULL', 'float(7) NOT NULL'], - [Schema::TYPE_DECIMAL, 'decimal(10,0)'], - [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], - [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], - [Schema::TYPE_DATETIME, 'datetime'], - [Schema::TYPE_DATETIME . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], - [Schema::TYPE_TIMESTAMP, 'timestamp'], - [Schema::TYPE_TIMESTAMP . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], - [Schema::TYPE_TIME, 'time'], - [Schema::TYPE_TIME . " CHECK (value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK (value BETWEEN '12:00:00' AND '13:01:01')"], - [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], - [Schema::TYPE_DATE, 'date'], - [Schema::TYPE_DATE . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], - [Schema::TYPE_BINARY, 'blob'], - [Schema::TYPE_BOOLEAN, 'smallint'], - [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'smallint NOT NULL DEFAULT 1'], - [Schema::TYPE_MONEY, 'decimal(19,4)'], - [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], - [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], - ]; - } -} diff --git a/tests/unit/framework/db/cubrid/CubridQueryTest.php b/tests/unit/framework/db/cubrid/CubridQueryTest.php deleted file mode 100644 index 50fe0d8887..0000000000 --- a/tests/unit/framework/db/cubrid/CubridQueryTest.php +++ /dev/null @@ -1,13 +0,0 @@ -getConnection()->schema; - - foreach ($values as $value) { - $this->assertEquals($value[1], $schema->getPdoType($value[0])); - } - fclose($fp); - } - - - public function getExpectedColumns() - { - $columns = parent::getExpectedColumns(); - $columns['int_col']['dbType'] = 'integer'; - $columns['int_col']['size'] = null; - $columns['int_col']['precision'] = null; - $columns['int_col2']['dbType'] = 'integer'; - $columns['int_col2']['size'] = null; - $columns['int_col2']['precision'] = null; - $columns['smallint_col']['dbType'] = 'short'; - $columns['smallint_col']['size'] = null; - $columns['smallint_col']['precision'] = null; - $columns['char_col3']['type'] = 'string'; - $columns['char_col3']['dbType'] = 'varchar(1073741823)'; - $columns['char_col3']['size'] = 1073741823; - $columns['char_col3']['precision'] = 1073741823; - $columns['enum_col']['dbType'] = "enum('a', 'B')"; - $columns['float_col']['dbType'] = 'double'; - $columns['float_col']['size'] = null; - $columns['float_col']['precision'] = null; - $columns['float_col']['scale'] = null; - $columns['numeric_col']['dbType'] = 'numeric(5,2)'; - $columns['blob_col']['phpType'] = 'resource'; - $columns['blob_col']['type'] = 'binary'; - $columns['bool_col']['dbType'] = 'short'; - $columns['bool_col']['size'] = null; - $columns['bool_col']['precision'] = null; - $columns['bool_col2']['dbType'] = 'short'; - $columns['bool_col2']['size'] = null; - $columns['bool_col2']['precision'] = null; - $columns['time']['defaultValue'] = '12:00:00 AM 01/01/2002'; - $columns['ts_default']['defaultValue'] = new Expression('SYS_TIMESTAMP'); - return $columns; - } -} diff --git a/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php b/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php deleted file mode 100644 index e9f49943c6..0000000000 --- a/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php +++ /dev/null @@ -1,14 +0,0 @@ -select('id')->from('example')->limit(10)->offset(5); - - list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query); - - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $actualQueryParams); - } - - public function testLimit() - { - $expectedQuerySql = 'SELECT `id` FROM `exapmle` OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY'; - $expectedQueryParams = null; - - $query = new Query(); - $query->select('id')->from('example')->limit(10); - - list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query); - - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $actualQueryParams); - } - - public function testOffset() - { - $expectedQuerySql = 'SELECT `id` FROM `exapmle` OFFSET 10 ROWS'; - $expectedQueryParams = null; - - $query = new Query(); - $query->select('id')->from('example')->offset(10); - - list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query); - - $this->assertEquals($expectedQuerySql, $actualQuerySql); - $this->assertEquals($expectedQueryParams, $actualQueryParams); - } -} diff --git a/tests/unit/framework/db/mssql/MssqlQueryTest.php b/tests/unit/framework/db/mssql/MssqlQueryTest.php deleted file mode 100644 index 89830d036d..0000000000 --- a/tests/unit/framework/db/mssql/MssqlQueryTest.php +++ /dev/null @@ -1,14 +0,0 @@ -getCustomerClass(); - /* @var $this TestCase|ActiveRecordTestTrait */ - $customer = new $customerClass(); - $customer->name = 'boolean customer'; - $customer->email = 'mail@example.com'; - $customer->bool_status = false; - $customer->save(false); - - $customer->refresh(); - $this->assertSame(false, $customer->bool_status); - - $customer->bool_status = true; - $customer->save(false); - - $customer->refresh(); - $this->assertSame(true, $customer->bool_status); - - $customers = $customerClass::find()->where(['bool_status' => true])->all(); - $this->assertEquals(3, count($customers)); - - $customers = $customerClass::find()->where(['bool_status' => false])->all(); - $this->assertEquals(1, count($customers)); - } - - public function testFindAsArray() - { - /* @var $customerClass \yii\db\ActiveRecordInterface */ - $customerClass = $this->getCustomerClass(); - - // asArray - $customer = $customerClass::find()->where(['id' => 2])->asArray()->one(); - $this->assertEquals([ - 'id' => 2, - 'email' => 'user2@example.com', - 'name' => 'user2', - 'address' => 'address2', - 'status' => 1, - 'profile_id' => null, - 'bool_status' => true, - ], $customer); - - // find all asArray - $customers = $customerClass::find()->asArray()->all(); - $this->assertEquals(3, count($customers)); - $this->assertArrayHasKey('id', $customers[0]); - $this->assertArrayHasKey('name', $customers[0]); - $this->assertArrayHasKey('email', $customers[0]); - $this->assertArrayHasKey('address', $customers[0]); - $this->assertArrayHasKey('status', $customers[0]); - $this->assertArrayHasKey('bool_status', $customers[0]); - $this->assertArrayHasKey('id', $customers[1]); - $this->assertArrayHasKey('name', $customers[1]); - $this->assertArrayHasKey('email', $customers[1]); - $this->assertArrayHasKey('address', $customers[1]); - $this->assertArrayHasKey('status', $customers[1]); - $this->assertArrayHasKey('bool_status', $customers[1]); - $this->assertArrayHasKey('id', $customers[2]); - $this->assertArrayHasKey('name', $customers[2]); - $this->assertArrayHasKey('email', $customers[2]); - $this->assertArrayHasKey('address', $customers[2]); - $this->assertArrayHasKey('status', $customers[2]); - $this->assertArrayHasKey('bool_status', $customers[2]); - } - - public function testBooleanValues() - { - $db = $this->getConnection(); - $command = $db->createCommand(); - $command->batchInsert('bool_values', - ['bool_col'], [ - [true], - [false], - ] - )->execute(); - - $this->assertEquals(1, BoolAR::find()->where('bool_col = TRUE')->count('*', $db)); - $this->assertEquals(1, BoolAR::find()->where('bool_col = FALSE')->count('*', $db)); - $this->assertEquals(2, BoolAR::find()->where('bool_col IN (TRUE, FALSE)')->count('*', $db)); - - $this->assertEquals(1, BoolAR::find()->where(['bool_col' => true])->count('*', $db)); - $this->assertEquals(1, BoolAR::find()->where(['bool_col' => false])->count('*', $db)); - $this->assertEquals(2, BoolAR::find()->where(['bool_col' => [true, false]])->count('*', $db)); - - $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db)); - $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db)); - - $this->assertSame(true, BoolAR::find()->where(['bool_col' => true])->one($db)->bool_col); - $this->assertSame(false, BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col); - } - - /** - * https://github.com/yiisoft/yii2/issues/4672 - */ - public function testBooleanValues2() - { - $db = $this->getConnection(); - $db->charset = 'utf8'; - - $db->createCommand("DROP TABLE IF EXISTS bool_user;")->execute(); - $db->createCommand()->createTable('bool_user', [ - 'id' => Schema::TYPE_PK, - 'username' => Schema::TYPE_STRING . ' NOT NULL', - 'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL', - 'password_hash' => Schema::TYPE_STRING . ' NOT NULL', - 'password_reset_token' => Schema::TYPE_STRING, - 'email' => Schema::TYPE_STRING . ' NOT NULL', - 'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', - - 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', - 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', - 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', - ])->execute(); - $db->createCommand()->addColumn('bool_user', 'is_deleted', Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT FALSE')->execute(); - - $user = new UserAR(); - $user->username = 'test'; - $user->auth_key = 'test'; - $user->password_hash = 'test'; - $user->email = 'test@example.com'; - $user->save(false); - - $this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => false])->all($db))); - $this->assertEquals(0, count(UserAR::find()->where(['is_deleted' => true])->all($db))); - $this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => [true, false]])->all($db))); - } - - public function testBooleanDefaultValues() - { - $model = new BoolAR(); - $this->assertNull($model->bool_col); - $this->assertNull($model->default_true); - $this->assertNull($model->default_false); - $model->loadDefaultValues(); - $this->assertNull($model->bool_col); - $this->assertSame(true, $model->default_true); - $this->assertSame(false, $model->default_false); - - $this->assertTrue($model->save(false)); - } -} - -class BoolAR extends ActiveRecord -{ - public static function tableName() - { - return 'bool_values'; - } -} - -class UserAR extends ActiveRecord -{ - const STATUS_DELETED = 0; - const STATUS_ACTIVE = 10; - const ROLE_USER = 10; - - public static function tableName() - { - return '{{%bool_user}}'; - } - - public function behaviors() - { - return [ - TimestampBehavior::className(), - ]; - } -} - diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php deleted file mode 100644 index 9dbc47ba3e..0000000000 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ /dev/null @@ -1,125 +0,0 @@ - 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'], - [Schema::TYPE_STRING, 'varchar(255)'], - [Schema::TYPE_STRING . '(32)', 'varchar(32)'], - [Schema::TYPE_STRING . ' CHECK (value LIKE \'test%\')', 'varchar(255) CHECK (value LIKE \'test%\')'], - [Schema::TYPE_STRING . '(32) CHECK (value LIKE \'test%\')', 'varchar(32) CHECK (value LIKE \'test%\')'], - [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], - [Schema::TYPE_TEXT, 'text'], - [Schema::TYPE_TEXT . '(255)', 'text'], - [Schema::TYPE_TEXT . ' CHECK (value LIKE \'test%\')', 'text CHECK (value LIKE \'test%\')'], - [Schema::TYPE_TEXT . '(255) CHECK (value LIKE \'test%\')', 'text CHECK (value LIKE \'test%\')'], - [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], - [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], - [Schema::TYPE_SMALLINT, 'smallint'], - [Schema::TYPE_SMALLINT . '(8)', 'smallint'], - [Schema::TYPE_INTEGER, 'integer'], - [Schema::TYPE_INTEGER . '(8)', 'integer'], - [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'], - [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'], - [Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'], - [Schema::TYPE_BIGINT, 'bigint'], - [Schema::TYPE_BIGINT . '(8)', 'bigint'], - [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], - [Schema::TYPE_FLOAT, 'double precision'], - [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . ' NOT NULL', 'double precision NOT NULL'], - [Schema::TYPE_DECIMAL, 'numeric(10,0)'], - [Schema::TYPE_DECIMAL . '(12,4)', 'numeric(12,4)'], - [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'numeric(10,0) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'numeric(12,4) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . ' NOT NULL', 'numeric(10,0) NOT NULL'], - [Schema::TYPE_DATETIME, 'timestamp(0)'], - [Schema::TYPE_DATETIME . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp(0) CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATETIME . ' NOT NULL', 'timestamp(0) NOT NULL'], - [Schema::TYPE_TIMESTAMP, 'timestamp(0)'], - [Schema::TYPE_TIMESTAMP . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp(0) CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp(0) NOT NULL'], - [Schema::TYPE_TIMESTAMP.'(4)', 'timestamp(4)'], - [Schema::TYPE_TIME, 'time(0)'], - [Schema::TYPE_TIME . " CHECK (value BETWEEN '12:00:00' AND '13:01:01')", "time(0) CHECK (value BETWEEN '12:00:00' AND '13:01:01')"], - [Schema::TYPE_TIME . ' NOT NULL', 'time(0) NOT NULL'], - [Schema::TYPE_DATE, 'date'], - [Schema::TYPE_DATE . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], - [Schema::TYPE_BINARY, 'bytea'], - [Schema::TYPE_BOOLEAN, 'boolean'], - [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT TRUE', 'boolean NOT NULL DEFAULT TRUE'], - [Schema::TYPE_MONEY, 'numeric(19,4)'], - [Schema::TYPE_MONEY . '(16,2)', 'numeric(16,2)'], - [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'numeric(19,4) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'numeric(16,2) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . ' NOT NULL', 'numeric(19,4) NOT NULL'], - ]; - } - - public function conditionProvider() - { - return array_merge(parent::conditionProvider(), [ - // adding conditions for ILIKE i.e. case insensitive LIKE - // http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE - - // empty values - [ ['ilike', 'name', []], '0=1', [] ], - [ ['not ilike', 'name', []], '', [] ], - [ ['or ilike', 'name', []], '0=1', [] ], - [ ['or not ilike', 'name', []], '', [] ], - - // simple ilike - [ ['ilike', 'name', 'heyho'], '"name" ILIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['not ilike', 'name', 'heyho'], '"name" NOT ILIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['or ilike', 'name', 'heyho'], '"name" ILIKE :qp0', [':qp0' => '%heyho%'] ], - [ ['or not ilike', 'name', 'heyho'], '"name" NOT ILIKE :qp0', [':qp0' => '%heyho%'] ], - - // ilike for many values - [ ['ilike', 'name', ['heyho', 'abc']], '"name" ILIKE :qp0 AND "name" ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['not ilike', 'name', ['heyho', 'abc']], '"name" NOT ILIKE :qp0 AND "name" NOT ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['or ilike', 'name', ['heyho', 'abc']], '"name" ILIKE :qp0 OR "name" ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - [ ['or not ilike', 'name', ['heyho', 'abc']], '"name" NOT ILIKE :qp0 OR "name" NOT ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], - ]); - } - - public function testAlterColumn() - { - $qb = $this->getQueryBuilder(); - - $expected = 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255)'; - $sql = $qb->alterColumn('foo1', 'bar', 'varchar(255)'); - $this->assertEquals($expected, $sql); - - $expected = 'ALTER TABLE "foo1" ALTER COLUMN "bar" SET NOT null'; - $sql = $qb->alterColumn('foo1', 'bar', 'SET NOT null'); - $this->assertEquals($expected, $sql); - - $expected = 'ALTER TABLE "foo1" ALTER COLUMN "bar" drop default'; - $sql = $qb->alterColumn('foo1', 'bar', 'drop default'); - $this->assertEquals($expected, $sql); - - $expected = 'ALTER TABLE "foo1" ALTER COLUMN "bar" reset xyz'; - $sql = $qb->alterColumn('foo1', 'bar', 'reset xyz'); - $this->assertEquals($expected, $sql); - } -} diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php deleted file mode 100644 index af2c383d96..0000000000 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php +++ /dev/null @@ -1,40 +0,0 @@ -getConnection(); - $command = $db->createCommand(); - $command->batchInsert('bool_values', - ['bool_col'], [ - [true], - [false], - ] - )->execute(); - - $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = TRUE')->count('*', $db)); - $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = FALSE')->count('*', $db)); - $this->assertEquals(2, (new Query())->from('bool_values')->where('bool_col IN (TRUE, FALSE)')->count('*', $db)); - - $this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => true])->count('*', $db)); - $this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => false])->count('*', $db)); - $this->assertEquals(2, (new Query())->from('bool_values')->where(['bool_col' => [true, false]])->count('*', $db)); - - $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db)); - $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db)); - } -} diff --git a/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php b/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php deleted file mode 100644 index 245bc88048..0000000000 --- a/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php +++ /dev/null @@ -1,14 +0,0 @@ -getConnection(false); - - $sql = 'SELECT [[id]], [[t.name]] FROM {{customer}} t'; - $command = $db->createCommand($sql); - $this->assertEquals("SELECT `id`, `t`.`name` FROM `customer` t", $command->sql); - } -} diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php deleted file mode 100644 index 34ab18aacd..0000000000 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ /dev/null @@ -1,94 +0,0 @@ - 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'], - [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'], - [Schema::TYPE_STRING, 'varchar(255)'], - [Schema::TYPE_STRING . '(32)', 'varchar(32)'], - [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], - [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], - [Schema::TYPE_TEXT, 'text'], - [Schema::TYPE_TEXT . '(255)', 'text'], - [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], - [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], - [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], - [Schema::TYPE_SMALLINT, 'smallint'], - [Schema::TYPE_SMALLINT . '(8)', 'smallint'], - [Schema::TYPE_INTEGER, 'integer'], - [Schema::TYPE_INTEGER . '(8)', 'integer'], - [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'], - [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'], - [Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'], - [Schema::TYPE_BIGINT, 'bigint'], - [Schema::TYPE_BIGINT . '(8)', 'bigint'], - [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], - [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], - [Schema::TYPE_FLOAT, 'float'], - [Schema::TYPE_FLOAT . '(16,5)', 'float'], - [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], - [Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'], - [Schema::TYPE_DECIMAL, 'decimal(10,0)'], - [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], - [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], - [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], - [Schema::TYPE_DATETIME, 'datetime'], - [Schema::TYPE_DATETIME . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], - [Schema::TYPE_TIMESTAMP, 'timestamp'], - [Schema::TYPE_TIMESTAMP . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], - [Schema::TYPE_TIME, 'time'], - [Schema::TYPE_TIME . " CHECK (value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK (value BETWEEN '12:00:00' AND '13:01:01')"], - [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], - [Schema::TYPE_DATE, 'date'], - [Schema::TYPE_DATE . " CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK (value BETWEEN '2011-01-01' AND '2013-01-01')"], - [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], - [Schema::TYPE_BINARY, 'blob'], - [Schema::TYPE_BOOLEAN, 'boolean'], - [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'], - [Schema::TYPE_MONEY, 'decimal(19,4)'], - [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], - [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], - [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], - ]; - } - - public function testAddDropPrimaryKey() - { - $this->setExpectedException('yii\base\NotSupportedException'); - parent::testAddDropPrimaryKey(); - } - - public function testBatchInsert() - { - $db = $this->getConnection(); - if (version_compare($db->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '3.7.11', '>=')) { - $this->markTestSkipped('This test is only relevant for SQLite < 3.7.11'); - } - $sql = $this->getQueryBuilder()->batchInsert('{{customer}} t', ['t.id', 't.name'], [[1, 'a'], [2, 'b']]); - $this->assertEquals("INSERT INTO {{customer}} t (`t`.`id`, `t`.`name`) SELECT 1, 'a' UNION SELECT 2, 'b'", $sql); - } -} diff --git a/tests/unit/framework/db/sqlite/SqliteQueryTest.php b/tests/unit/framework/db/sqlite/SqliteQueryTest.php deleted file mode 100644 index 5c85077b3c..0000000000 --- a/tests/unit/framework/db/sqlite/SqliteQueryTest.php +++ /dev/null @@ -1,13 +0,0 @@ - - * @since 2.0 - */ -class ContainerTest extends TestCase -{ - public function testDefault() - { - $namespace = __NAMESPACE__ . '\stubs'; - $QuxInterface = "$namespace\\QuxInterface"; - $Foo = Foo::className(); - $Bar = Bar::className(); - $Qux = Qux::className(); - - // automatic wiring - $container = new Container; - $container->set($QuxInterface, $Qux); - $foo = $container->get($Foo); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); - $foo2 = $container->get($Foo); - $this->assertFalse($foo === $foo2); - - // full wiring - $container = new Container; - $container->set($QuxInterface, $Qux); - $container->set($Bar); - $container->set($Qux); - $container->set($Foo); - $foo = $container->get($Foo); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); - - // wiring by closure - $container = new Container; - $container->set('foo', function () { - $qux = new Qux; - $bar = new Bar($qux); - return new Foo($bar); - }); - $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); - - // wiring by closure which uses container - $container = new Container; - $container->set($QuxInterface, $Qux); - $container->set('foo', function (Container $c, $params, $config) { - return $c->get(Foo::className()); - }); - $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); - - // predefined constructor parameters - $container = new Container; - $container->set('foo', $Foo, [Instance::of('bar')]); - $container->set('bar', $Bar, [Instance::of('qux')]); - $container->set('qux', $Qux); - $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); - - // wiring by closure - $container = new Container; - $container->set('qux', new Qux); - $qux1 = $container->get('qux'); - $qux2 = $container->get('qux'); - $this->assertTrue($qux1 === $qux2); - - // config - $container = new Container; - $container->set('qux', $Qux); - $qux = $container->get('qux', [], ['a' => 2]); - $this->assertEquals(2, $qux->a); - $qux = $container->get('qux', [3]); - $this->assertEquals(3, $qux->a); - $qux = $container->get('qux', [3, ['a' => 4]]); - $this->assertEquals(4, $qux->a); - } -} diff --git a/tests/unit/framework/di/InstanceTest.php b/tests/unit/framework/di/InstanceTest.php deleted file mode 100644 index 70f7970a6f..0000000000 --- a/tests/unit/framework/di/InstanceTest.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @since 2.0 - */ -class InstanceTest extends TestCase -{ - public function testOf() - { - $container = new Container; - $className = Component::className(); - $instance = Instance::of($className); - - $this->assertTrue($instance instanceof Instance); - $this->assertTrue($instance->get($container) instanceof Component); - $this->assertTrue(Instance::ensure($instance, $className, $container) instanceof Component); - $this->assertTrue($instance->get($container) !== Instance::ensure($instance, $className, $container)); - } - - public function testEnsure() - { - $container = new Container; - $container->set('db', [ - 'class' => 'yii\db\Connection', - 'dsn' => 'test', - ]); - - $this->assertTrue(Instance::ensure('db', 'yii\db\Connection', $container) instanceof Connection); - $this->assertTrue(Instance::ensure(new Connection, 'yii\db\Connection', $container) instanceof Connection); - $this->assertTrue(Instance::ensure([ - 'class' => 'yii\db\Connection', - 'dsn' => 'test', - ], 'yii\db\Connection', $container) instanceof Connection); - } -} diff --git a/tests/unit/framework/di/stubs/Bar.php b/tests/unit/framework/di/stubs/Bar.php deleted file mode 100644 index d0bc788696..0000000000 --- a/tests/unit/framework/di/stubs/Bar.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class Bar extends Object -{ - public $qux; - - public function __construct(QuxInterface $qux, $config = []) - { - $this->qux = $qux; - parent::__construct($config); - } -} diff --git a/tests/unit/framework/di/stubs/Foo.php b/tests/unit/framework/di/stubs/Foo.php deleted file mode 100644 index 75a5612f19..0000000000 --- a/tests/unit/framework/di/stubs/Foo.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class Foo extends Object -{ - public $bar; - - public function __construct(Bar $bar, $config = []) - { - $this->bar = $bar; - parent::__construct($config); - } -} diff --git a/tests/unit/framework/di/stubs/Qux.php b/tests/unit/framework/di/stubs/Qux.php deleted file mode 100644 index 4b4486690c..0000000000 --- a/tests/unit/framework/di/stubs/Qux.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class Qux extends Object implements QuxInterface -{ - public $a; - - public function __construct($a = 1, $config = []) - { - $this->a = $a; - parent::__construct($config); - } - - public function quxMethod() - { - } -} diff --git a/tests/unit/framework/di/stubs/QuxInterface.php b/tests/unit/framework/di/stubs/QuxInterface.php deleted file mode 100644 index 8a9847130b..0000000000 --- a/tests/unit/framework/di/stubs/QuxInterface.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 2.0 - */ -interface QuxInterface -{ - function quxMethod(); -} diff --git a/tests/unit/framework/helpers/ConsoleTest.php b/tests/unit/framework/helpers/ConsoleTest.php deleted file mode 100644 index 70344dff2f..0000000000 --- a/tests/unit/framework/helpers/ConsoleTest.php +++ /dev/null @@ -1,132 +0,0 @@ -assertEquals(str_repeat('a', 25), $output); - } - -/* public function testScreenSize() - { - for ($i = 1; $i < 20; $i++) { - echo implode(', ', Console::getScreenSize(true)) . "\n"; - ob_flush(); - sleep(1); - } - }*/ - - public function ansiFormats() - { - return [ - ['test', 'test'], - [Console::ansiFormat('test', [Console::FG_RED]), 'test'], - ['abc' . Console::ansiFormat('def', [Console::FG_RED]) . 'ghj', 'abcdefghj'], - ['abc' . Console::ansiFormat('def', [Console::FG_RED, Console::BG_GREEN]) . 'ghj', 'abcdefghj'], - ['abc' . Console::ansiFormat('def', [Console::FG_GREEN, Console::FG_RED, Console::BG_GREEN]) . 'ghj', 'abcdefghj'], - ['abc' . Console::ansiFormat('def', [Console::BOLD, Console::BG_GREEN]) . 'ghj', 'abcdefghj'], - - [ - Console::ansiFormat('test', [Console::UNDERLINE, Console::OVERLINED, Console::CROSSED_OUT, Console::FG_GREEN]), - 'test' - ], - - [Console::ansiFormatCode([Console::RESET]) . Console::ansiFormatCode([Console::RESET]), ''], - [Console::ansiFormatCode([Console::RESET]) . Console::ansiFormatCode([Console::RESET]) . 'test', 'test'], - [Console::ansiFormatCode([Console::RESET]) . 'test' . Console::ansiFormatCode([Console::RESET]), 'test'], - - [ - Console::ansiFormatCode([Console::BOLD]) . 'abc' . Console::ansiFormatCode([Console::RESET, Console::FG_GREEN]) . 'ghj' . Console::ansiFormatCode([Console::RESET]), - 'abcghj' - ], - [ - Console::ansiFormatCode([Console::FG_GREEN]) . ' a ' . Console::ansiFormatCode([Console::BOLD]) . 'abc' . Console::ansiFormatCode([Console::RESET]) . 'ghj', - ' a abcghj' - ], - [ - Console::ansiFormat('test', [Console::FG_GREEN, Console::BG_BLUE, Console::NEGATIVE]), - 'test' - ], - [ - Console::ansiFormat('test', [Console::NEGATIVE]), - 'test' - ], - [ - Console::ansiFormat('test', [Console::CONCEALED]), - 'test' - ], - ]; - } - - /** - * @dataProvider ansiFormats - */ - public function testAnsi2Html($ansi, $html) - { - $this->assertEquals($html, Console::ansiToHtml($ansi)); - } -} diff --git a/tests/unit/framework/helpers/FallbackInflector.php b/tests/unit/framework/helpers/FallbackInflector.php deleted file mode 100644 index 0eff0e9da3..0000000000 --- a/tests/unit/framework/helpers/FallbackInflector.php +++ /dev/null @@ -1,21 +0,0 @@ -assertSame('"1"', Json::encode($data)); - - // simple array encoding - $data = [1, 2]; - $this->assertSame('[1,2]', Json::encode($data)); - $data = ['a' => 1, 'b' => 2]; - $this->assertSame('{"a":1,"b":2}', Json::encode($data)); - - // simple object encoding - $data = new \stdClass(); - $data->a = 1; - $data->b = 2; - $this->assertSame('{"a":1,"b":2}', Json::encode($data)); - - // expression encoding - $expression = 'function () {}'; - $data = new JsExpression($expression); - $this->assertSame($expression, Json::encode($data)); - - // complex data - $expression1 = 'function (a) {}'; - $expression2 = 'function (b) {}'; - $data = [ - 'a' => [ - 1, new JsExpression($expression1) - ], - 'b' => new JsExpression($expression2), - ]; - $this->assertSame("{\"a\":[1,$expression1],\"b\":$expression2}", Json::encode($data)); - - // https://github.com/yiisoft/yii2/issues/957 - $data = (object) null; - $this->assertSame('{}', Json::encode($data)); - - // JsonSerializable - $data = new JsonModel(); - $this->assertSame('{"json":"serializable"}', Json::encode($data)); - } - - public function testDecode() - { - // basic data decoding - $json = '"1"'; - $this->assertSame('1', Json::decode($json)); - - // array decoding - $json = '{"a":1,"b":2}'; - $this->assertSame(['a' => 1, 'b' => 2], Json::decode($json)); - - // exception - $json = '{"a":1,"b":2'; - $this->setExpectedException('yii\base\InvalidParamException'); - Json::decode($json); - } -} - -class JsonModel extends Model implements \JsonSerializable -{ - function jsonSerialize() - { - return ['json' => 'serializable']; - } -} \ No newline at end of file diff --git a/tests/unit/framework/helpers/VarDumperTest.php b/tests/unit/framework/helpers/VarDumperTest.php deleted file mode 100644 index 6faff10481..0000000000 --- a/tests/unit/framework/helpers/VarDumperTest.php +++ /dev/null @@ -1,108 +0,0 @@ -assertEquals("stdClass#1\n(\n)", ob_get_contents()); - ob_end_clean(); - } - - /** - * Data provider for [[testExport()]] - * @return array test data - */ - public function dataProviderExport() - { - // Regular : - - $data = [ - [ - 'test string', - var_export('test string', true) - ], - [ - 75, - var_export(75, true) - ], - [ - 7.5, - var_export(7.5, true) - ], - [ - null, - 'null' - ], - [ - true, - 'true' - ], - [ - false, - 'false' - ], - [ - [], - '[]' - ], - ]; - - // Arrays : - - $var = [ - 'key1' => 'value1', - 'key2' => 'value2', - ]; - $expectedResult = << 'value1', - 'key2' => 'value2', -] -RESULT; - $data[] = [$var, $expectedResult]; - - $var = [ - 'value1', - 'value2', - ]; - $expectedResult = <<testField = 'Test Value'; - $expectedResult = "unserialize('" . serialize($var) . "')"; - $data[] = [$var, $expectedResult]; - - return $data; - } - - /** - * @dataProvider dataProviderExport - * - * @param mixed $var - * @param string $expectedResult - */ - public function testExport($var, $expectedResult) - { - $exportResult = VarDumper::export($var); - $this->assertEqualsWithoutLE($expectedResult, $exportResult); - $this->assertEquals($var, eval('return ' . $exportResult . ';')); - } -} diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php deleted file mode 100644 index 4751d3b526..0000000000 --- a/tests/unit/framework/i18n/FormatterTest.php +++ /dev/null @@ -1,194 +0,0 @@ -mockApplication([ - 'timeZone' => 'UTC', - 'language' => 'ru-RU', - ]); - $this->formatter = new Formatter(['locale' => 'en-US']); - } - - protected function tearDown() - { - parent::tearDown(); - IntlTestHelper::resetIntlStatus(); - $this->formatter = null; - } - - - public function testFormat() - { - $value = time(); - $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'date')); - $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'DATE')); - $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'php:Y/m/d'])); - $this->setExpectedException('\yii\base\InvalidParamException'); - $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data')); - } - - public function testLocale() - { - // locale is configured explicitly - $f = new Formatter(['locale' => 'en-US']); - $this->assertEquals('en-US', $f->locale); - - // if not, take from application - $f = new Formatter(); - $this->assertEquals('ru-RU', $f->locale); - } - - - public function testAsRaw() - { - $value = '123'; - $this->assertSame($value, $this->formatter->asRaw($value)); - $value = 123; - $this->assertSame($value, $this->formatter->asRaw($value)); - $value = '<>'; - $this->assertSame($value, $this->formatter->asRaw($value)); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asRaw(null)); - } - - public function testAsText() - { - $value = '123'; - $this->assertSame($value, $this->formatter->asText($value)); - $value = 123; - $this->assertSame("$value", $this->formatter->asText($value)); - $value = '<>'; - $this->assertSame('<>', $this->formatter->asText($value)); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asText(null)); - } - - public function testAsNtext() - { - $value = '123'; - $this->assertSame($value, $this->formatter->asNtext($value)); - $value = 123; - $this->assertSame("$value", $this->formatter->asNtext($value)); - $value = '<>'; - $this->assertSame('<>', $this->formatter->asNtext($value)); - $value = "123\n456"; - $this->assertSame("123
        \n456", $this->formatter->asNtext($value)); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asNtext(null)); - } - - public function testAsParagraphs() - { - $value = '123'; - $this->assertSame("

        $value

        ", $this->formatter->asParagraphs($value)); - $value = 123; - $this->assertSame("

        $value

        ", $this->formatter->asParagraphs($value)); - $value = '<>'; - $this->assertSame('

        <>

        ', $this->formatter->asParagraphs($value)); - $value = "123\n456"; - $this->assertSame("

        123\n456

        ", $this->formatter->asParagraphs($value)); - $value = "123\n\n456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - $value = "123\n\n\n456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r\n456"; - $this->assertSame("

        123\r\n456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r\n\r\n456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r\n\r\n\r\n456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r456"; - $this->assertSame("

        123\r456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r\r456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - $value = "123\r\r\r456"; - $this->assertSame("

        123

        \n

        456

        ", $this->formatter->asParagraphs($value)); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asParagraphs(null)); - } - - public function testAsHtml() - { - // todo: dependency on HtmlPurifier - } - - public function testAsEmail() - { - $value = 'test@sample.com'; - $this->assertSame("$value", $this->formatter->asEmail($value)); - $value = 'test@sample.com'; - $this->assertSame("$value", $this->formatter->asEmail($value, ['target' => '_blank'])); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asEmail(null)); - } - - public function testAsUrl() - { - $value = 'http://www.yiiframework.com/'; - $this->assertSame("$value", $this->formatter->asUrl($value)); - $value = 'https://www.yiiframework.com/'; - $this->assertSame("$value", $this->formatter->asUrl($value)); - $value = 'www.yiiframework.com/'; - $this->assertSame("$value", $this->formatter->asUrl($value)); - $value = 'https://www.yiiframework.com/?name=test&value=5"'; - $this->assertSame("https://www.yiiframework.com/?name=test&value=5"", $this->formatter->asUrl($value)); - $value = 'http://www.yiiframework.com/'; - $this->assertSame("$value", $this->formatter->asUrl($value, ['target' => '_blank'])); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asUrl(null)); - } - - public function testAsImage() - { - $value = 'http://sample.com/img.jpg'; - $this->assertSame("\"\"", $this->formatter->asImage($value)); - $value = 'http://sample.com/img.jpg'; - $alt = "Hello!"; - $this->assertSame("\"$alt\"", $this->formatter->asImage($value, ['alt' => $alt])); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asImage(null)); - } - - public function testAsBoolean() - { - $this->assertSame('Yes', $this->formatter->asBoolean(true)); - $this->assertSame('No', $this->formatter->asBoolean(false)); - $this->assertSame('Yes', $this->formatter->asBoolean("111")); - $this->assertSame('No', $this->formatter->asBoolean("")); - $this->assertSame('No', $this->formatter->asBoolean(0)); - - // null display - $this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null)); - } -} diff --git a/tests/unit/framework/i18n/GettextMessageSourceTest.php b/tests/unit/framework/i18n/GettextMessageSourceTest.php deleted file mode 100644 index cb586de8e3..0000000000 --- a/tests/unit/framework/i18n/GettextMessageSourceTest.php +++ /dev/null @@ -1,16 +0,0 @@ -markTestIncomplete(); - } -} diff --git a/tests/unit/framework/i18n/GettextMoFileTest.php b/tests/unit/framework/i18n/GettextMoFileTest.php deleted file mode 100644 index b9ef8971ba..0000000000 --- a/tests/unit/framework/i18n/GettextMoFileTest.php +++ /dev/null @@ -1,98 +0,0 @@ -load($moFilePath, 'context1'); - $context2 = $moFile->load($moFilePath, 'context2'); - - // item count - $this->assertCount(3, $context1); - $this->assertCount(2, $context2); - - // original messages - $this->assertArrayNotHasKey("Missing\n\r\t\"translation.", $context1); - $this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1); - $this->assertArrayHasKey("String number two.", $context1); - $this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1); - - $this->assertArrayHasKey("The other\n\ncontext.\n", $context2); - $this->assertArrayHasKey("test1\\ntest2\n\\\ntest3", $context2); - - // translated messages - $this->assertFalse(in_array("", $context1)); - $this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1)); - $this->assertTrue(in_array('Строка номер два.', $context1)); - $this->assertTrue(in_array('Короткий перевод.', $context1)); - - $this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2)); - $this->assertTrue(in_array("тест1\\nтест2\n\\\nтест3", $context2)); - } - - public function testSave() - { - // initial data - $s = chr(4); - $messages = [ - 'Hello!' => 'Привет!', - "context1{$s}Hello?" => 'Привет?', - 'Hello!?' => '', - "context1{$s}Hello!?!" => '', - "context2{$s}\"Quotes\"" => '"Кавычки"', - "context2{$s}\nNew lines\n" => "\nПереносы строк\n", - "context2{$s}\tTabs\t" => "\tТабы\t", - "context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r", - ]; - - // create temporary directory and dump messages - $poFileDirectory = __DIR__ . '/../../runtime/i18n'; - if (!is_dir($poFileDirectory)) { - mkdir($poFileDirectory); - } - if (is_file($poFileDirectory . '/test.mo')) { - unlink($poFileDirectory . '/test.mo'); - } - - $moFile = new GettextMoFile(); - $moFile->save($poFileDirectory . '/test.mo', $messages); - - // load messages - $context1 = $moFile->load($poFileDirectory . '/test.mo', 'context1'); - $context2 = $moFile->load($poFileDirectory . '/test.mo', 'context2'); - - // context1 - $this->assertCount(2, $context1); - - $this->assertArrayHasKey('Hello?', $context1); - $this->assertTrue(in_array('Привет?', $context1)); - - $this->assertArrayHasKey('Hello!?!', $context1); - $this->assertTrue(in_array('', $context1)); - - // context2 - $this->assertCount(4, $context2); - - $this->assertArrayHasKey("\"Quotes\"", $context2); - $this->assertTrue(in_array('"Кавычки"', $context2)); - - $this->assertArrayHasKey("\nNew lines\n", $context2); - $this->assertTrue(in_array("\nПереносы строк\n", $context2)); - - $this->assertArrayHasKey("\tTabs\t", $context2); - $this->assertTrue(in_array("\tТабы\t", $context2)); - - $this->assertArrayHasKey("\rCarriage returns\r", $context2); - $this->assertTrue(in_array("\rВозвраты кареток\r", $context2)); - } -} diff --git a/tests/unit/framework/i18n/GettextPoFileTest.php b/tests/unit/framework/i18n/GettextPoFileTest.php deleted file mode 100644 index 42aa24ad6b..0000000000 --- a/tests/unit/framework/i18n/GettextPoFileTest.php +++ /dev/null @@ -1,98 +0,0 @@ -load($poFilePath, 'context1'); - $context2 = $poFile->load($poFilePath, 'context2'); - - // item count - $this->assertCount(4, $context1); - $this->assertCount(2, $context2); - - // original messages - $this->assertArrayHasKey("Missing\n\r\t\"translation.", $context1); - $this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1); - $this->assertArrayHasKey("String number two.", $context1); - $this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1); - - $this->assertArrayHasKey("The other\n\ncontext.\n", $context2); - $this->assertArrayHasKey("test1\\\ntest2\n\\\\\ntest3", $context2); - - // translated messages - $this->assertTrue(in_array("", $context1)); - $this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1)); - $this->assertTrue(in_array('Строка номер два.', $context1)); - $this->assertTrue(in_array('Короткий перевод.', $context1)); - - $this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2)); - $this->assertTrue(in_array("тест1\\\nтест2\n\\\\\nтест3", $context2)); - } - - public function testSave() - { - // initial data - $s = chr(4); - $messages = [ - 'Hello!' => 'Привет!', - "context1{$s}Hello?" => 'Привет?', - 'Hello!?' => '', - "context1{$s}Hello!?!" => '', - "context2{$s}\"Quotes\"" => '"Кавычки"', - "context2{$s}\nNew lines\n" => "\nПереносы строк\n", - "context2{$s}\tTabs\t" => "\tТабы\t", - "context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r", - ]; - - // create temporary directory and dump messages - $poFileDirectory = __DIR__ . '/../../runtime/i18n'; - if (!is_dir($poFileDirectory)) { - mkdir($poFileDirectory); - } - if (is_file($poFileDirectory . '/test.po')) { - unlink($poFileDirectory . '/test.po'); - } - - $poFile = new GettextPoFile(); - $poFile->save($poFileDirectory . '/test.po', $messages); - - // load messages - $context1 = $poFile->load($poFileDirectory . '/test.po', 'context1'); - $context2 = $poFile->load($poFileDirectory . '/test.po', 'context2'); - - // context1 - $this->assertCount(2, $context1); - - $this->assertArrayHasKey('Hello?', $context1); - $this->assertTrue(in_array('Привет?', $context1)); - - $this->assertArrayHasKey('Hello!?!', $context1); - $this->assertTrue(in_array('', $context1)); - - // context2 - $this->assertCount(4, $context2); - - $this->assertArrayHasKey("\"Quotes\"", $context2); - $this->assertTrue(in_array('"Кавычки"', $context2)); - - $this->assertArrayHasKey("\nNew lines\n", $context2); - $this->assertTrue(in_array("\nПереносы строк\n", $context2)); - - $this->assertArrayHasKey("\tTabs\t", $context2); - $this->assertTrue(in_array("\tТабы\t", $context2)); - - $this->assertArrayHasKey("\rCarriage returns\r", $context2); - $this->assertTrue(in_array("\rВозвраты кареток\r", $context2)); - } -} diff --git a/tests/unit/framework/i18n/IntlTestHelper.php b/tests/unit/framework/i18n/IntlTestHelper.php deleted file mode 100644 index d9a4eb7f75..0000000000 --- a/tests/unit/framework/i18n/IntlTestHelper.php +++ /dev/null @@ -1,77 +0,0 @@ -getName(false), 'testIntl', 8) === 0) { - if (!extension_loaded('intl')) { - $test->markTestSkipped('intl extension is not installed.'); - } - static::$enableIntl = true; - } else { - static::$enableIntl = false; - } - } - - public static function resetIntlStatus() - { - static::$enableIntl = null; - } - } -} - -namespace yii\i18n { - use yiiunit\framework\i18n\IntlTestHelper; - - if (!function_exists('yii\i18n\extension_loaded')) { - function extension_loaded($name) - { - if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) { - return IntlTestHelper::$enableIntl; - } - return \extension_loaded($name); - } - } -} - -namespace yii\helpers { - use yiiunit\framework\i18n\IntlTestHelper; - - if (!function_exists('yii\helpers\extension_loaded')) { - function extension_loaded($name) - { - if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) { - return IntlTestHelper::$enableIntl; - } - return \extension_loaded($name); - } - } -} - -namespace yii\validators { - use yiiunit\framework\i18n\IntlTestHelper; - - if (!function_exists('yii\validators\extension_loaded')) { - function extension_loaded($name) - { - if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) { - return IntlTestHelper::$enableIntl; - } - return \extension_loaded($name); - } - } -} diff --git a/tests/unit/framework/log/DbTargetTest.php b/tests/unit/framework/log/DbTargetTest.php deleted file mode 100644 index 5ff7fe836b..0000000000 --- a/tests/unit/framework/log/DbTargetTest.php +++ /dev/null @@ -1,142 +0,0 @@ - 'Migrator', - 'basePath' => '@yiiunit', - 'controllerMap' => [ - 'migrate' => EchoMigrateController::className(), - ], - 'components' => [ - 'db' => static::getConnection(), - 'log' => [ - 'targets' => [ - [ - 'class' => 'yii\log\DbTarget', - 'levels' => ['warning'], - 'logTable' => self::$logTable, - ], - ], - ], - ], - ]); - } - - ob_start(); - $result = Yii::$app->runAction($route, $params); - echo "Result is " . $result; - if ($result !== \yii\console\Controller::EXIT_CODE_NORMAL) { - ob_end_flush(); - } else { - ob_end_clean(); - } - } - - public static function setUpBeforeClass() - { - parent::setUpBeforeClass(); - $databases = static::getParam('databases'); - static::$database = $databases[static::$driverName]; - $pdo_database = 'pdo_' . static::$driverName; - - if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { - static::markTestSkipped('pdo and ' . $pdo_database . ' extension are required.'); - } - - static::runConsoleAction('migrate/up', ['migrationPath' => '@yii/log/migrations/', 'interactive' => false]); - } - - public static function tearDownAfterClass() - { - static::runConsoleAction('migrate/down', ['migrationPath' => '@yii/log/migrations/', 'interactive' => false]); - if (static::$db) { - static::$db->close(); - } - Yii::$app = null; - parent::tearDownAfterClass(); - } - - protected function tearDown() - { - parent::tearDown(); - self::getConnection()->createCommand()->truncateTable(self::$logTable)->execute(); - } - - /** - * @throws \yii\base\InvalidParamException - * @throws \yii\db\Exception - * @throws \yii\base\InvalidConfigException - * @return \yii\db\Connection - */ - public static function getConnection() - { - if (static::$db == null) { - $db = new Connection; - $db->dsn = static::$database['dsn']; - if (isset(static::$database['username'])) { - $db->username = static::$database['username']; - $db->password = static::$database['password']; - } - if (isset(static::$database['attributes'])) { - $db->attributes = static::$database['attributes']; - } - if (!$db->isActive) { - $db->open(); - } - static::$db = $db; - } - return static::$db; - } - - /** - * Tests that precision isn't lost for log timestamps - * @see https://github.com/yiisoft/yii2/issues/7384 - */ - public function testTimestamp() - { - $logger = Yii::getLogger(); - - $time = 1424865393.0105; - - // forming message data manually in order to set time - $messsageData = [ - 'test', - Logger::LEVEL_WARNING, - 'test', - $time, - [] - ]; - - $logger->messages[] = $messsageData; - $logger->flush(true); - - $query = (new Query())->select('log_time')->from(self::$logTable)->where(['category' => 'test']); - $loggedTime = $query->createCommand(self::getConnection())->queryScalar(); - static::assertEquals($time, $loggedTime); - } -} \ No newline at end of file diff --git a/tests/unit/framework/log/FileTargetTest.php b/tests/unit/framework/log/FileTargetTest.php deleted file mode 100644 index 122fdee192..0000000000 --- a/tests/unit/framework/log/FileTargetTest.php +++ /dev/null @@ -1,105 +0,0 @@ - - */ - -namespace yiiunit\framework\log; - -use yii\helpers\FileHelper; -use yii\log\Dispatcher; -use yii\log\Logger; -use Yii; -use yiiunit\TestCase; - -/** - * @group log - */ -class FileTargetTest extends TestCase -{ - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - - public function booleanDataProvider() - { - return [ - [true], - [false] - ]; - } - - /** - * @dataProvider booleanDataProvider - */ - public function testRotate($rotateByCopy) - { - $logFile = Yii::getAlias('@yiiunit/runtime/log/filetargettest.log'); - FileHelper::removeDirectory(dirname($logFile)); - mkdir(dirname($logFile), 0777, true); - - $logger = new Logger(); - $dispatcher = new Dispatcher([ - 'logger' => $logger, - 'targets' => [ - 'file' => [ - 'class' => 'yii\log\FileTarget', - 'logFile' => $logFile, - 'levels' => ['warning'], - 'maxFileSize' => 1024, // 1 MB - 'maxLogFiles' => 1, // one file for rotation and one normal log file - 'logVars' => [], - 'rotateByCopy' => $rotateByCopy - ] - ] - ]); - - // one file - - $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); - $logger->flush(true); - - clearstatcache(); - - $this->assertTrue(file_exists($logFile)); - $this->assertFalse(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); - - // exceed max size - for($i = 0; $i < 1024; $i++) { - $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); - } - $logger->flush(true); - - // first rotate - - $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); - $logger->flush(true); - - clearstatcache(); - - $this->assertTrue(file_exists($logFile)); - $this->assertTrue(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); - - // second rotate - - for($i = 0; $i < 1024; $i++) { - $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); - } - $logger->flush(true); - - clearstatcache(); - - $this->assertTrue(file_exists($logFile)); - $this->assertTrue(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); - } -} \ No newline at end of file diff --git a/tests/unit/framework/log/LoggerTest.php b/tests/unit/framework/log/LoggerTest.php deleted file mode 100644 index 44fd5b4af7..0000000000 --- a/tests/unit/framework/log/LoggerTest.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - -namespace yiiunit\framework\log; - -use yii\log\Logger; -use yiiunit\TestCase; - -/** - * @group log - */ -class LoggerTest extends TestCase -{ - - public function testLog() - { - $logger = new Logger(); - - $logger->log('test1', Logger::LEVEL_INFO); - $this->assertEquals(1, count($logger->messages)); - $this->assertEquals('test1', $logger->messages[0][0]); - $this->assertEquals(Logger::LEVEL_INFO, $logger->messages[0][1]); - $this->assertEquals('application', $logger->messages[0][2]); - - $logger->log('test2', Logger::LEVEL_ERROR, 'category'); - $this->assertEquals(2, count($logger->messages)); - $this->assertEquals('test2', $logger->messages[1][0]); - $this->assertEquals(Logger::LEVEL_ERROR, $logger->messages[1][1]); - $this->assertEquals('category', $logger->messages[1][2]); - } -} diff --git a/tests/unit/framework/log/MySQLTargetTest.php b/tests/unit/framework/log/MySQLTargetTest.php deleted file mode 100644 index fd5e2422b7..0000000000 --- a/tests/unit/framework/log/MySQLTargetTest.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ - -namespace yiiunit\framework\log; - -use yii\log\Dispatcher; -use yii\log\Logger; -use yii\log\Target; -use yiiunit\TestCase; - -/** - * @group log - */ -class TargetTest extends TestCase -{ - public static $messages; - - public function filters() - { - return [ - [[], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']], - - [['levels' => 0], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']], - [ - ['levels' => Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_ERROR | Logger::LEVEL_TRACE], - ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] - ], - [['levels' => ['error']], ['B', 'G', 'H']], - [['levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']], - [['levels' => ['error', 'warning']], ['B', 'C', 'G', 'H']], - [['levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C', 'G', 'H']], - - [['categories' => ['application']], ['A', 'B', 'C', 'D', 'E']], - [['categories' => ['application*']], ['A', 'B', 'C', 'D', 'E', 'F']], - [['categories' => ['application.*']], ['F']], - [['categories' => ['application.components']], []], - [['categories' => ['application.components.Test']], ['F']], - [['categories' => ['application.components.*']], ['F']], - [['categories' => ['application.*', 'yii.db.*']], ['F', 'G', 'H']], - [['categories' => ['application.*', 'yii.db.*'], 'except' => ['yii.db.Command.*']], ['F', 'G']], - - [['categories' => ['application', 'yii.db.*'], 'levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']], - [['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR], ['B']], - [['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C']], - ]; - } - - /** - * @dataProvider filters - */ - public function testFilter($filter, $expected) - { - static::$messages = []; - - $logger = new Logger; - $dispatcher = new Dispatcher([ - 'logger' => $logger, - 'targets' => [new TestTarget(array_merge($filter, ['logVars' => []]))], - 'flushInterval' => 1, - ]); - $logger->log('testA', Logger::LEVEL_INFO); - $logger->log('testB', Logger::LEVEL_ERROR); - $logger->log('testC', Logger::LEVEL_WARNING); - $logger->log('testD', Logger::LEVEL_TRACE); - $logger->log('testE', Logger::LEVEL_INFO, 'application'); - $logger->log('testF', Logger::LEVEL_INFO, 'application.components.Test'); - $logger->log('testG', Logger::LEVEL_ERROR, 'yii.db.Command'); - $logger->log('testH', Logger::LEVEL_ERROR, 'yii.db.Command.whatever'); - - $this->assertEquals(count($expected), count(static::$messages)); - $i = 0; - foreach ($expected as $e) { - $this->assertEquals('test' . $e, static::$messages[$i++][0]); - } - } -} - -class TestTarget extends Target -{ - public $exportInterval = 1; - - /** - * Exports log [[messages]] to a specific destination. - * Child classes must implement this method. - */ - public function export() - { - TargetTest::$messages = array_merge(TargetTest::$messages, $this->messages); - $this->messages = []; - } -} diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php deleted file mode 100644 index b6d166f713..0000000000 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ /dev/null @@ -1,449 +0,0 @@ -mockApplication([ - 'components' => [ - 'mailer' => $this->createTestMailComponent(), - ] - ]); - $filePath = $this->getTestFilePath(); - if (!file_exists($filePath)) { - FileHelper::createDirectory($filePath); - } - } - - public function tearDown() - { - $filePath = $this->getTestFilePath(); - if (file_exists($filePath)) { - FileHelper::removeDirectory($filePath); - } - } - - /** - * @return string test file path. - */ - protected function getTestFilePath() - { - return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestMailComponent() - { - $component = new Mailer(); - $component->viewPath = $this->getTestFilePath(); - - return $component; - } - - /** - * @return Mailer mailer instance - */ - protected function getTestMailComponent() - { - return Yii::$app->get('mailer'); - } - - // Tests : - - public function testSetupView() - { - $mailer = new Mailer(); - - $view = new View(); - $mailer->setView($view); - $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); - - $viewConfig = [ - 'params' => [ - 'param1' => 'value1', - 'param2' => 'value2', - ] - ]; - $mailer->setView($viewConfig); - $view = $mailer->getView(); - $this->assertTrue(is_object($view), 'Unable to setup view via config!'); - $this->assertEquals($viewConfig['params'], $view->params, 'Unable to configure view via config array!'); - } - - /** - * @depends testSetupView - */ - public function testGetDefaultView() - { - $mailer = new Mailer(); - $view = $mailer->getView(); - $this->assertTrue(is_object($view), 'Unable to get default view!'); - } - - public function testCreateMessage() - { - $mailer = new Mailer(); - $message = $mailer->compose(); - $this->assertTrue(is_object($message), 'Unable to create message instance!'); - $this->assertEquals($mailer->messageClass, get_class($message), 'Invalid message class!'); - } - - /** - * @depends testCreateMessage - */ - public function testDefaultMessageConfig() - { - $mailer = new Mailer(); - - $notPropertyConfig = [ - 'charset' => 'utf-16', - 'from' => 'from@domain.com', - 'to' => 'to@domain.com', - 'cc' => 'cc@domain.com', - 'bcc' => 'bcc@domain.com', - 'subject' => 'Test subject', - 'textBody' => 'Test text body', - 'htmlBody' => 'Test HTML body', - ]; - $propertyConfig = [ - 'id' => 'test-id', - 'encoding' => 'test-encoding', - ]; - $messageConfig = array_merge($notPropertyConfig, $propertyConfig); - $mailer->messageConfig = $messageConfig; - - $message = $mailer->compose(); - - foreach ($notPropertyConfig as $name => $value) { - $this->assertEquals($value, $message->{'_' . $name}); - } - foreach ($propertyConfig as $name => $value) { - $this->assertEquals($value, $message->$name); - } - } - - /** - * @depends testGetDefaultView - */ - public function testRender() - { - $mailer = $this->getTestMailComponent(); - - $viewName = 'test_view'; - $viewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $viewName . '.php'; - $viewFileContent = ''; - file_put_contents($viewFileName, $viewFileContent); - - $params = [ - 'testParam' => 'test output' - ]; - $renderResult = $mailer->render($viewName, $params); - $this->assertEquals($params['testParam'], $renderResult); - } - - /** - * @depends testRender - */ - public function testRenderLayout() - { - $mailer = $this->getTestMailComponent(); - - $filePath = $this->getTestFilePath(); - - $viewName = 'test_view2'; - $viewFileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; - $viewFileContent = 'view file content'; - file_put_contents($viewFileName, $viewFileContent); - - $layoutName = 'test_layout'; - $layoutFileName = $filePath . DIRECTORY_SEPARATOR . $layoutName . '.php'; - $layoutFileContent = 'Begin Layout End Layout'; - file_put_contents($layoutFileName, $layoutFileContent); - - $renderResult = $mailer->render($viewName, [], $layoutName); - $this->assertEquals('Begin Layout ' . $viewFileContent . ' End Layout', $renderResult); - } - - /** - * @depends testCreateMessage - * @depends testRender - */ - public function testCompose() - { - $mailer = $this->getTestMailComponent(); - $mailer->htmlLayout = false; - $mailer->textLayout = false; - - $htmlViewName = 'test_html_view'; - $htmlViewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $htmlViewName . '.php'; - $htmlViewFileContent = 'HTML view file content'; - file_put_contents($htmlViewFileName, $htmlViewFileContent); - - $textViewName = 'test_text_view'; - $textViewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $textViewName . '.php'; - $textViewFileContent = 'Plain text view file content'; - file_put_contents($textViewFileName, $textViewFileContent); - - $message = $mailer->compose([ - 'html' => $htmlViewName, - 'text' => $textViewName, - ]); - $this->assertEquals($htmlViewFileContent, $message->_htmlBody, 'Unable to render html!'); - $this->assertEquals($textViewFileContent, $message->_textBody, 'Unable to render text!'); - - $message = $mailer->compose($htmlViewName); - $this->assertEquals($htmlViewFileContent, $message->_htmlBody, 'Unable to render html by direct view!'); - $this->assertEquals(strip_tags($htmlViewFileContent), $message->_textBody, 'Unable to render text by direct view!'); - } - - public function htmlAndPlainProvider() - { - return [ - [ - 1, - 'HTML view file content http://yiifresh.com/index.php?r=site%2Freset-password&token=abcdef', - 'HTML view file content http://yiifresh.com/index.php?r=site%2Freset-password&token=abcdef', - ], - [ - 2, <<TEST - - -

        First paragraph - second line - - http://yiifresh.com/index.php?r=site%2Freset-password&token=abcdef - -

        - -

        Test Lorem ipsum...

        - - -HTML -, <<getTestMailComponent(); - $mailer->htmlLayout = false; - $mailer->textLayout = false; - - $htmlViewName = 'test_html_view' . $i; // $i is needed to generate different view files to ensure it works on HHVM - $htmlViewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $htmlViewName . '.php'; - file_put_contents($htmlViewFileName, $htmlViewFileContent); - - $message = $mailer->compose([ - 'html' => $htmlViewName, - ]); - $this->assertEqualsWithoutLE($htmlViewFileContent, $message->_htmlBody, 'Unable to render html!'); - $this->assertEqualsWithoutLE($expectedTextRendering, $message->_textBody, 'Unable to render text!'); - } - - public function testUseFileTransport() - { - $mailer = new Mailer(); - $this->assertFalse($mailer->useFileTransport); - $this->assertEquals('@runtime/mail', $mailer->fileTransportPath); - - $mailer->fileTransportPath = '@yiiunit/runtime/mail'; - $mailer->useFileTransport = true; - $mailer->fileTransportCallback = function () { - return 'message.txt'; - }; - $message = $mailer->compose() - ->setTo('to@example.com') - ->setFrom('from@example.com') - ->setSubject('test subject') - ->setTextBody('text body' . microtime(true)); - $this->assertTrue($mailer->send($message)); - $file = Yii::getAlias($mailer->fileTransportPath) . '/message.txt'; - $this->assertTrue(is_file($file)); - $this->assertEquals($message->toString(), file_get_contents($file)); - } - - public function testBeforeSendEvent() - { - $message = new Message(); - - $mailerMock = $this->getMockBuilder('yiiunit\framework\mail\Mailer')->setMethods(['beforeSend', 'afterSend'])->getMock(); - $mailerMock->expects($this->once())->method('beforeSend')->with($message)->will($this->returnValue(true)); - $mailerMock->expects($this->once())->method('afterSend')->with($message, true); - $mailerMock->send($message); - } -} - -/** - * Test Mailer class - */ -class Mailer extends BaseMailer -{ - public $messageClass = 'yiiunit\framework\mail\Message'; - public $sentMessages = []; - - protected function sendMessage($message) - { - $this->sentMessages[] = $message; - - return true; - } -} - -/** - * Test Message class - */ -class Message extends BaseMessage -{ - public $id; - public $encoding; - public $_charset; - public $_from; - public $_replyTo; - public $_to; - public $_cc; - public $_bcc; - public $_subject; - public $_textBody; - public $_htmlBody; - - public function getCharset() - { - return $this->_charset; - } - - public function setCharset($charset) - { - $this->_charset = $charset; - - return $this; - } - - public function getFrom() - { - return $this->_from; - } - - public function setFrom($from) - { - $this->_from = $from; - - return $this; - } - - public function getTo() - { - return $this->_to; - } - - public function setTo($to) - { - $this->_to = $to; - - return $this; - } - - public function getCc() - { - return $this->_cc; - } - - public function setCc($cc) - { - $this->_cc = $cc; - - return $this; - } - - public function getBcc() - { - return $this->_bcc; - } - - public function setBcc($bcc) - { - $this->_bcc = $bcc; - - return $this; - } - - public function getSubject() - { - return $this->_subject; - } - - public function setSubject($subject) - { - $this->_subject = $subject; - - return $this; - } - - public function getReplyTo() - { - return $this->_replyTo; - } - - public function setReplyTo($replyTo) - { - $this->_replyTo = $replyTo; - - return $this; - } - - public function setTextBody($text) - { - $this->_textBody = $text; - - return $this; - } - - public function setHtmlBody($html) - { - $this->_htmlBody = $html; - - return $this; - } - - public function attachContent($content, array $options = []) {} - - public function attach($fileName, array $options = []) {} - - public function embed($fileName, array $options = []) {} - - public function embedContent($content, array $options = []) {} - - public function toString() - { - $mailer = $this->mailer; - $this->mailer = null; - $s = var_export($this, true); - $this->mailer = $mailer; - return $s; - } -} diff --git a/tests/unit/framework/mail/BaseMessageTest.php b/tests/unit/framework/mail/BaseMessageTest.php deleted file mode 100644 index 539b730828..0000000000 --- a/tests/unit/framework/mail/BaseMessageTest.php +++ /dev/null @@ -1,153 +0,0 @@ -mockApplication([ - 'components' => [ - 'mailer' => $this->createTestEmailComponent() - ] - ]); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new TestMailer(); - - return $component; - } - - /** - * @return TestMailer mailer instance. - */ - protected function getMailer() - { - return Yii::$app->get('mailer'); - } - - // Tests : - - public function testSend() - { - $mailer = $this->getMailer(); - $message = $mailer->compose(); - $message->send($mailer); - $this->assertEquals($message, $mailer->sentMessages[0], 'Unable to send message!'); - } - - public function testToString() - { - $mailer = $this->getMailer(); - $message = $mailer->compose(); - $this->assertEquals($message->toString(), '' . $message); - } -} - -/** - * Test Mailer class - */ -class TestMailer extends BaseMailer -{ - public $messageClass = 'yiiunit\framework\mail\TestMessage'; - public $sentMessages = []; - - protected function sendMessage($message) - { - $this->sentMessages[] = $message; - } -} - -/** - * Test Message class - */ -class TestMessage extends BaseMessage -{ - public $text; - public $html; - - public function getCharset() - { - return ''; - } - - public function setCharset($charset) {} - - public function getFrom() - { - return ''; - } - - public function setFrom($from) {} - - public function getReplyTo() - { - return ''; - } - - public function setReplyTo($replyTo) {} - - public function getTo() - { - return ''; - } - - public function setTo($to) {} - - public function getCc() - { - return ''; - } - - public function setCc($cc) {} - - public function getBcc() - { - return ''; - } - - public function setBcc($bcc) {} - - public function getSubject() - { - return ''; - } - - public function setSubject($subject) {} - - public function setTextBody($text) - { - $this->text = $text; - } - - public function setHtmlBody($html) - { - $this->html = $html; - } - - public function attachContent($content, array $options = []) {} - - public function attach($fileName, array $options = []) {} - - public function embed($fileName, array $options = []) {} - - public function embedContent($content, array $options = []) {} - - public function toString() - { - return get_class($this); - } -} diff --git a/tests/unit/framework/rbac/AuthorRule.php b/tests/unit/framework/rbac/AuthorRule.php deleted file mode 100644 index fa4e794e1b..0000000000 --- a/tests/unit/framework/rbac/AuthorRule.php +++ /dev/null @@ -1,21 +0,0 @@ - 'Migrator', - 'basePath' => '@yiiunit', - 'components' => [ - 'db' => static::getConnection(), - 'authManager' => '\yii\rbac\DbManager', - ], - ]); - } - - ob_start(); - $result = Yii::$app->runAction($route, $params); - echo "Result is ".$result; - if ($result !== Controller::EXIT_CODE_NORMAL) { - ob_end_flush(); - } else { - ob_end_clean(); - } - } - - public static function setUpBeforeClass() - { - parent::setUpBeforeClass(); - $databases = static::getParam('databases'); - static::$database = $databases[static::$driverName]; - $pdo_database = 'pdo_' . static::$driverName; - - if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { - static::markTestSkipped('pdo and ' . $pdo_database . ' extension are required.'); - } - - static::runConsoleAction('migrate/up', ['migrationPath' => '@yii/rbac/migrations/', 'interactive' => false]); - } - - public static function tearDownAfterClass() - { - static::runConsoleAction('migrate/down', ['migrationPath' => '@yii/rbac/migrations/', 'interactive' => false]); - if (static::$db) { - static::$db->close(); - } - Yii::$app = null; - parent::tearDownAfterClass(); - } - - protected function setUp() - { - parent::setUp(); - $this->auth = $this->createManager(); - } - - protected function tearDown() - { - parent::tearDown(); - $this->auth->removeAll(); - } - - /** - * @throws \yii\base\InvalidParamException - * @throws \yii\db\Exception - * @throws \yii\base\InvalidConfigException - * @return \yii\db\Connection - */ - public static function getConnection() - { - if (static::$db == null) { - $db = new Connection; - $db->dsn = static::$database['dsn']; - if (isset(static::$database['username'])) { - $db->username = static::$database['username']; - $db->password = static::$database['password']; - } - if (isset(static::$database['attributes'])) { - $db->attributes = static::$database['attributes']; - } - if (!$db->isActive) { - $db->open(); - } - static::$db = $db; - } - return static::$db; - } - - /** - * @return \yii\rbac\ManagerInterface - */ - protected function createManager() - { - return new DbManager(['db' => $this->getConnection()]); - } -} diff --git a/tests/unit/framework/rbac/ExposedPhpManager.php b/tests/unit/framework/rbac/ExposedPhpManager.php deleted file mode 100644 index bf7c111ff2..0000000000 --- a/tests/unit/framework/rbac/ExposedPhpManager.php +++ /dev/null @@ -1,37 +0,0 @@ - item - /** - * @var array - */ - public $children = []; // itemName, childName => child - /** - * @var \yii\rbac\Assignment[] - */ - public $assignments = []; // userId, itemName => assignment - /** - * @var \yii\rbac\Rule[] - */ - public $rules = []; // ruleName => rule - - public function load() - { - parent::load(); - } - - public function save() - { - parent::save(); - } -} \ No newline at end of file diff --git a/tests/unit/framework/rbac/MySQLManagerCacheTest.php b/tests/unit/framework/rbac/MySQLManagerCacheTest.php deleted file mode 100644 index 615b1378d2..0000000000 --- a/tests/unit/framework/rbac/MySQLManagerCacheTest.php +++ /dev/null @@ -1,24 +0,0 @@ - $this->getConnection(), - 'cache' => new FileCache(['cachePath' => '@yiiunit/runtime/cache']), - ]); - } -} diff --git a/tests/unit/framework/rbac/MySQLManagerTest.php b/tests/unit/framework/rbac/MySQLManagerTest.php deleted file mode 100644 index d64c0cd6ec..0000000000 --- a/tests/unit/framework/rbac/MySQLManagerTest.php +++ /dev/null @@ -1,12 +0,0 @@ - [ - 'name' => 'Requirement 1', - 'mandatory' => true, - 'condition' => true, - 'by' => 'Requirement 1', - 'memo' => 'Requirement 1', - ], - 'requirementError' => [ - 'name' => 'Requirement 2', - 'mandatory' => true, - 'condition' => false, - 'by' => 'Requirement 2', - 'memo' => 'Requirement 2', - ], - 'requirementWarning' => [ - 'name' => 'Requirement 3', - 'mandatory' => false, - 'condition' => false, - 'by' => 'Requirement 3', - 'memo' => 'Requirement 3', - ], - ]; - - $checkResult = $requirementsChecker->check($requirements)->getResult(); - $summary = $checkResult['summary']; - - $this->assertEquals(count($requirements), $summary['total'], 'Wrong summary total!'); - $this->assertEquals(1, $summary['errors'], 'Wrong summary errors!'); - $this->assertEquals(1, $summary['warnings'], 'Wrong summary warnings!'); - - $checkedRequirements = $checkResult['requirements']; - $requirementsKeys = array_flip(array_keys($requirements)); - - $this->assertEquals(false, $checkedRequirements[$requirementsKeys['requirementPass']]['error'], 'Passed requirement has an error!'); - $this->assertEquals(false, $checkedRequirements[$requirementsKeys['requirementPass']]['warning'], 'Passed requirement has a warning!'); - - $this->assertEquals(true, $checkedRequirements[$requirementsKeys['requirementError']]['error'], 'Error requirement has no error!'); - - $this->assertEquals(false, $checkedRequirements[$requirementsKeys['requirementWarning']]['error'], 'Error requirement has an error!'); - $this->assertEquals(true, $checkedRequirements[$requirementsKeys['requirementWarning']]['warning'], 'Error requirement has no warning!'); - } - - /** - * @depends testCheck - */ - public function testCheckEval() - { - $requirementsChecker = new YiiRequirementChecker(); - - $requirements = [ - 'requirementPass' => [ - 'name' => 'Requirement 1', - 'mandatory' => true, - 'condition' => 'eval:2>1', - 'by' => 'Requirement 1', - 'memo' => 'Requirement 1', - ], - 'requirementError' => [ - 'name' => 'Requirement 2', - 'mandatory' => true, - 'condition' => 'eval:2<1', - 'by' => 'Requirement 2', - 'memo' => 'Requirement 2', - ], - ]; - - $checkResult = $requirementsChecker->check($requirements)->getResult(); - $checkedRequirements = $checkResult['requirements']; - $requirementsKeys = array_flip(array_keys($requirements)); - - $this->assertEquals(false, $checkedRequirements[$requirementsKeys['requirementPass']]['error'], 'Passed requirement has an error!'); - $this->assertEquals(false, $checkedRequirements[$requirementsKeys['requirementPass']]['warning'], 'Passed requirement has a warning!'); - - $this->assertEquals(true, $checkedRequirements[$requirementsKeys['requirementError']]['error'], 'Error requirement has no error!'); - } - - /** - * @depends testCheck - */ - public function testCheckChained() - { - $requirementsChecker = new YiiRequirementChecker(); - - $requirements1 = [ - [ - 'name' => 'Requirement 1', - 'mandatory' => true, - 'condition' => true, - 'by' => 'Requirement 1', - 'memo' => 'Requirement 1', - ], - ]; - $requirements2 = [ - [ - 'name' => 'Requirement 2', - 'mandatory' => true, - 'condition' => true, - 'by' => 'Requirement 2', - 'memo' => 'Requirement 2', - ], - ]; - $checkResult = $requirementsChecker->check($requirements1)->check($requirements2)->getResult(); - - $mergedRequirements = array_merge($requirements1, $requirements2); - - $this->assertEquals(count($mergedRequirements), $checkResult['summary']['total'], 'Wrong total checks count!'); - foreach ($mergedRequirements as $key => $mergedRequirement) { - $this->assertEquals($mergedRequirement['name'], $checkResult['requirements'][$key]['name'], 'Wrong requirements list!'); - } - } - - public function testCheckPhpExtensionVersion() - { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Can not test this on HHVM.'); - } - - $requirementsChecker = new YiiRequirementChecker(); - - $this->assertFalse($requirementsChecker->checkPhpExtensionVersion('some_unexisting_php_extension', '0.1'), 'No fail while checking unexisting extension!'); - $this->assertTrue($requirementsChecker->checkPhpExtensionVersion('pdo', '1.0'), 'Unable to check PDO version!'); - } - - /** - * Data provider for [[testGetByteSize()]]. - * @return array - */ - public function dataProviderGetByteSize() - { - return [ - ['456', 456], - ['5K', 5*1024], - ['16KB', 16*1024], - ['4M', 4*1024*1024], - ['14MB', 14*1024*1024], - ['7G', 7*1024*1024*1024], - ['12GB', 12*1024*1024*1024], - ]; - } - - /** - * @dataProvider dataProviderGetByteSize - * - * @param string $verboseValue verbose value. - * @param integer $expectedByteSize expected byte size. - */ - public function testGetByteSize($verboseValue, $expectedByteSize) - { - $requirementsChecker = new YiiRequirementChecker(); - - $this->assertEquals($expectedByteSize, $requirementsChecker->getByteSize($verboseValue), "Wrong byte size for '{$verboseValue}'!"); - } - - /** - * Data provider for [[testCompareByteSize()]] - * @return array - */ - public function dataProviderCompareByteSize() - { - return [ - ['2M', '2K', '>', true], - ['2M', '2K', '>=', true], - ['1K', '1024', '==', true], - ['10M', '11M', '<', true], - ['10M', '11M', '<=', true], - ]; - } - - /** - * @depends testGetByteSize - * @dataProvider dataProviderCompareByteSize - * - * @param string $a first value. - * @param string $b second value. - * @param string $compare comparison. - * @param boolean $expectedComparisonResult expected comparison result. - */ - public function testCompareByteSize($a, $b, $compare, $expectedComparisonResult) - { - $requirementsChecker = new YiiRequirementChecker(); - $this->assertEquals($expectedComparisonResult, $requirementsChecker->compareByteSize($a, $b, $compare), "Wrong compare '{$a}{$compare}{$b}'"); - } -} diff --git a/tests/unit/framework/test/ActiveFixtureTest.php b/tests/unit/framework/test/ActiveFixtureTest.php deleted file mode 100644 index b73f4ff32b..0000000000 --- a/tests/unit/framework/test/ActiveFixtureTest.php +++ /dev/null @@ -1,96 +0,0 @@ -unloadFixtures(); - $this->loadFixtures(); - } - - public function tearDown() - { - } - - public function fixtures() - { - return [ - 'customers' => CustomerFixture::className(), - ]; - } - - public function globalFixtures() - { - return [ - InitDbFixture::className(), - ]; - } -} - -/** - * - * @author Qiang Xue - * @since 2.0 - */ -class ActiveFixtureTest extends DatabaseTestCase -{ - public function setUp() - { - parent::setUp(); - \Yii::$app->set('db', $this->getConnection()); - ActiveRecord::$db = $this->getConnection(); - } - - public function tearDown() - { - parent::tearDown(); - } - - public function testGetData() - { - $test = new MyDbTestCase(); - $test->setUp(); - $fixture = $test->getFixture('customers'); - $this->assertEquals(CustomerFixture::className(), get_class($fixture)); - $this->assertEquals(2, count($fixture)); - $this->assertEquals(1, $fixture['customer1']['id']); - $this->assertEquals('customer1@example.com', $fixture['customer1']['email']); - $this->assertEquals(2, $fixture['customer2']['id']); - $this->assertEquals('customer2@example.com', $fixture['customer2']['email']); - $test->tearDown(); - } - - public function testGetModel() - { - $test = new MyDbTestCase(); - $test->setUp(); - $fixture = $test->getFixture('customers'); - $this->assertEquals(Customer::className(), get_class($fixture->getModel('customer1'))); - $this->assertEquals(1, $fixture->getModel('customer1')->id); - $this->assertEquals('customer1@example.com', $fixture->getModel('customer1')->email); - $this->assertEquals(2, $fixture->getModel('customer2')->id); - $this->assertEquals('customer2@example.com', $fixture->getModel('customer2')->email); - $test->tearDown(); - } -} diff --git a/tests/unit/framework/test/ArrayFixtureTest.php b/tests/unit/framework/test/ArrayFixtureTest.php deleted file mode 100644 index 994dd9fcb3..0000000000 --- a/tests/unit/framework/test/ArrayFixtureTest.php +++ /dev/null @@ -1,58 +0,0 @@ -_fixture = new ArrayFixture(); - } - - public function testLoadUnloadParticularFile() - { - $this->_fixture->dataFile = '@yiiunit/framework/test/data/array_fixture.php'; - $this->assertEmpty($this->_fixture->data, 'fixture data should be empty'); - - $this->_fixture->load(); - - $this->assertCount(2, $this->_fixture->data, 'fixture data should match needed total count'); - $this->assertEquals('customer1', $this->_fixture['customer1']['name'], 'first fixture data should match'); - $this->assertEquals('customer2@example.com', $this->_fixture['customer2']['email'], 'second fixture data should match'); - } - - public function testNothingToLoad() - { - $this->_fixture->dataFile = false; - $this->assertEmpty($this->_fixture->data, 'fixture data should be empty'); - - $this->_fixture->load(); - $this->assertEmpty($this->_fixture->data, 'fixture data should not be loaded'); - } - - /** - * @expectedException \yii\base\InvalidConfigException - */ - public function testWrongDataFileException() - { - $this->_fixture->dataFile = 'wrong/fixtures/data/path/alias'; - $this->_fixture->load(); - } - -} diff --git a/tests/unit/framework/test/FixtureTest.php b/tests/unit/framework/test/FixtureTest.php deleted file mode 100644 index 9284d99243..0000000000 --- a/tests/unit/framework/test/FixtureTest.php +++ /dev/null @@ -1,169 +0,0 @@ -loadFixtures(); - } - - public function tearDown() - { - $this->unloadFixtures(); - } - - public function fetchFixture($name) - { - return $this->getFixture($name); - } - - public function fixtures() - { - switch ($this->scenario) { - case 0: return []; - case 1: return [ - 'fixture1' => Fixture1::className(), - ]; - case 2: return [ - 'fixture2' => Fixture2::className(), - ]; - case 3: return [ - 'fixture3' => Fixture3::className(), - ]; - case 4: return [ - 'fixture1' => Fixture1::className(), - 'fixture2' => Fixture2::className(), - ]; - case 5: return [ - 'fixture2' => Fixture2::className(), - 'fixture3' => Fixture3::className(), - ]; - case 6: return [ - 'fixture1' => Fixture1::className(), - 'fixture3' => Fixture3::className(), - ]; - case 7: - default: return [ - 'fixture1' => Fixture1::className(), - 'fixture2' => Fixture2::className(), - 'fixture3' => Fixture3::className(), - ]; - } - } -} - -class FixtureTest extends TestCase -{ - public function testDependencies() - { - foreach ($this->getDependencyTests() as $scenario => $result) { - $test = new MyTestCase(); - $test->scenario = $scenario; - $test->setUp(); - foreach ($result as $name => $loaded) { - $this->assertEquals($loaded, $test->fetchFixture($name) !== null, "Verifying scenario $scenario fixture $name"); - } - } - } - - public function testLoadSequence() - { - foreach ($this->getLoadSequenceTests() as $scenario => $result) { - $test = new MyTestCase(); - $test->scenario = $scenario; - MyTestCase::$load = ''; - MyTestCase::$unload = ''; - $test->setUp(); - $this->assertEquals($result[0], MyTestCase::$load, "Verifying scenario $scenario load sequence"); - $test->tearDown(); - $this->assertEquals($result[1], MyTestCase::$unload, "Verifying scenario $scenario unload sequence"); - } - } - - protected function getDependencyTests() - { - return [ - 0 => ['fixture1' => false, 'fixture2' => false, 'fixture3' => false], - 1 => ['fixture1' => true, 'fixture2' => false, 'fixture3' => false], - 2 => ['fixture1' => false, 'fixture2' => true, 'fixture3' => false], - 3 => ['fixture1' => false, 'fixture2' => false, 'fixture3' => true], - 4 => ['fixture1' => true, 'fixture2' => true, 'fixture3' => false], - 5 => ['fixture1' => false, 'fixture2' => true, 'fixture3' => true], - 6 => ['fixture1' => true, 'fixture2' => false, 'fixture3' => true], - 7 => ['fixture1' => true, 'fixture2' => true, 'fixture3' => true], - ]; - } - - protected function getLoadSequenceTests() - { - return [ - 0 => ['', ''], - 1 => ['321', '123'], - 2 => ['32', '23'], - 3 => ['3', '3'], - 4 => ['321', '123'], - 5 => ['32', '23'], - 6 => ['321', '123'], - 7 => ['321', '123'], - ]; - } -} diff --git a/tests/unit/framework/test/data/array_fixture.php b/tests/unit/framework/test/data/array_fixture.php deleted file mode 100644 index c5ccd4b384..0000000000 --- a/tests/unit/framework/test/data/array_fixture.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'email' => 'customer1@example.com', - 'name' => 'customer1', - 'address' => 'address1', - 'status' => 1, - ], - 'customer2' => [ - 'email' => 'customer2@example.com', - 'name' => 'customer2', - 'address' => 'address2', - 'status' => 2, - ], -]; diff --git a/tests/unit/framework/test/data/customer.php b/tests/unit/framework/test/data/customer.php deleted file mode 100644 index c5ccd4b384..0000000000 --- a/tests/unit/framework/test/data/customer.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'email' => 'customer1@example.com', - 'name' => 'customer1', - 'address' => 'address1', - 'status' => 1, - ], - 'customer2' => [ - 'email' => 'customer2@example.com', - 'name' => 'customer2', - 'address' => 'address2', - 'status' => 2, - ], -]; diff --git a/tests/unit/framework/validators/DateValidatorTest.php b/tests/unit/framework/validators/DateValidatorTest.php deleted file mode 100644 index 58e60a5032..0000000000 --- a/tests/unit/framework/validators/DateValidatorTest.php +++ /dev/null @@ -1,201 +0,0 @@ -mockApplication([ - 'timeZone' => 'UTC', - 'language' => 'ru-RU', - ]); - } - - protected function tearDown() - { - parent::tearDown(); - IntlTestHelper::resetIntlStatus(); - } - - public function testEnsureMessageIsSet() - { - $val = new DateValidator; - $this->assertTrue($val->message !== null && strlen($val->message) > 1); - } - - public function testIntlValidateValue() - { - $this->testValidateValue(); - - $this->mockApplication([ - 'language' => 'en-GB', - 'components' => [ - 'formatter' => [ - 'dateFormat' => 'short', - ] - ] - ]); - $val = new DateValidator(); - $this->assertTrue($val->validate('31/5/2017')); - $this->assertFalse($val->validate('5/31/2017')); - $val = new DateValidator(['format' => 'short', 'locale' => 'en-GB']); - $this->assertTrue($val->validate('31/5/2017')); - $this->assertFalse($val->validate('5/31/2017')); - - $this->mockApplication([ - 'language' => 'de-DE', - 'components' => [ - 'formatter' => [ - 'dateFormat' => 'short', - ] - ] - ]); - $val = new DateValidator(); - $this->assertTrue($val->validate('31.5.2017')); - $this->assertFalse($val->validate('5.31.2017')); - $val = new DateValidator(['format' => 'short', 'locale' => 'de-DE']); - $this->assertTrue($val->validate('31.5.2017')); - $this->assertFalse($val->validate('5.31.2017')); - } - - public function testValidateValue() - { - // test PHP format - $val = new DateValidator(['format' => 'php:Y-m-d']); - $this->assertFalse($val->validate('3232-32-32')); - $this->assertTrue($val->validate('2013-09-13')); - $this->assertFalse($val->validate('31.7.2013')); - $this->assertFalse($val->validate('31-7-2013')); - $this->assertFalse($val->validate('20121212')); - $this->assertFalse($val->validate('asdasdfasfd')); - $this->assertFalse($val->validate('2012-12-12foo')); - $this->assertFalse($val->validate('')); - $this->assertFalse($val->validate(time())); - $val->format = 'php:U'; - $this->assertTrue($val->validate(time())); - $val->format = 'php:d.m.Y'; - $this->assertTrue($val->validate('31.7.2013')); - $val->format = 'php:Y-m-!d H:i:s'; - $this->assertTrue($val->validate('2009-02-15 15:16:17')); - - // test ICU format - $val = new DateValidator(['format' => 'yyyy-MM-dd']); - $this->assertFalse($val->validate('3232-32-32')); - $this->assertTrue($val->validate('2013-09-13')); - $this->assertFalse($val->validate('31.7.2013')); - $this->assertFalse($val->validate('31-7-2013')); - $this->assertFalse($val->validate('20121212')); - $this->assertFalse($val->validate('asdasdfasfd')); - $this->assertFalse($val->validate('2012-12-12foo')); - $this->assertFalse($val->validate('')); - $this->assertFalse($val->validate(time())); - $val->format = 'dd.MM.yyyy'; - $this->assertTrue($val->validate('31.7.2013')); - $val->format = 'yyyy-MM-dd HH:mm:ss'; - $this->assertTrue($val->validate('2009-02-15 15:16:17')); - } - - public function testIntlValidateAttributePHPFormat() - { - $this->testValidateAttributePHPFormat(); - } - - public function testValidateAttributePHPFormat() - { - // error-array-add - $val = new DateValidator(['format' => 'php:Y-m-d']); - $model = new FakedValidationModel; - $model->attr_date = '2013-09-13'; - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - $model = new FakedValidationModel; - $model->attr_date = '1375293913'; - $val->validateAttribute($model, 'attr_date'); - $this->assertTrue($model->hasErrors('attr_date')); - //// timestamp attribute - $val = new DateValidator(['format' => 'php:Y-m-d', 'timestampAttribute' => 'attr_timestamp']); - $model = new FakedValidationModel; - $model->attr_date = '2013-09-13'; - $model->attr_timestamp = true; - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - $this->assertFalse($model->hasErrors('attr_timestamp')); - $this->assertEquals( - mktime(0, 0, 0, 9, 13, 2013), // 2013-09-13 -// DateTime::createFromFormat('Y-m-d', '2013-09-13')->getTimestamp(), - $model->attr_timestamp - ); - $val = new DateValidator(['format' => 'php:Y-m-d']); - $model = FakedValidationModel::createWithAttributes(['attr_date' => []]); - $val->validateAttribute($model, 'attr_date'); - $this->assertTrue($model->hasErrors('attr_date')); - - } - - public function testIntlValidateAttributeICUFormat() - { - $this->testValidateAttributeICUFormat(); - } - - public function testValidateAttributeICUFormat() - { - // error-array-add - $val = new DateValidator(['format' => 'yyyy-MM-dd']); - $model = new FakedValidationModel; - $model->attr_date = '2013-09-13'; - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - $model = new FakedValidationModel; - $model->attr_date = '1375293913'; - $val->validateAttribute($model, 'attr_date'); - $this->assertTrue($model->hasErrors('attr_date')); - //// timestamp attribute - $val = new DateValidator(['format' => 'yyyy-MM-dd', 'timestampAttribute' => 'attr_timestamp']); - $model = new FakedValidationModel; - $model->attr_date = '2013-09-13'; - $model->attr_timestamp = true; - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - $this->assertFalse($model->hasErrors('attr_timestamp')); - $this->assertEquals( - mktime(0, 0, 0, 9, 13, 2013), // 2013-09-13 -// DateTime::createFromFormat('Y-m-d', '2013-09-13')->getTimestamp(), - $model->attr_timestamp - ); - $val = new DateValidator(['format' => 'yyyy-MM-dd']); - $model = FakedValidationModel::createWithAttributes(['attr_date' => []]); - $val->validateAttribute($model, 'attr_date'); - $this->assertTrue($model->hasErrors('attr_date')); - $val = new DateValidator(['format' => 'yyyy-MM-dd']); - $model = FakedValidationModel::createWithAttributes(['attr_date' => '2012-12-12foo']); - $val->validateAttribute($model, 'attr_date'); - $this->assertTrue($model->hasErrors('attr_date')); - } - - public function testIntlMultibyteString() - { - $val = new DateValidator(['format' => 'dd MMM yyyy', 'locale' => 'de_DE']); - $model = FakedValidationModel::createWithAttributes(['attr_date' => '12 Mai 2014']); - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - - $val = new DateValidator(['format' => 'dd MMM yyyy', 'locale' => 'ru_RU']); - $model = FakedValidationModel::createWithAttributes(['attr_date' => '12 мая 2014']); - $val->validateAttribute($model, 'attr_date'); - $this->assertFalse($model->hasErrors('attr_date')); - } -} diff --git a/tests/unit/framework/validators/DefaultValueValidatorTest.php b/tests/unit/framework/validators/DefaultValueValidatorTest.php deleted file mode 100644 index 80a0fdcdb1..0000000000 --- a/tests/unit/framework/validators/DefaultValueValidatorTest.php +++ /dev/null @@ -1,40 +0,0 @@ -mockApplication(); - } - - public function testValidateAttribute() - { - $val = new DefaultValueValidator; - $val->value = 'test_value'; - $obj = new \stdclass; - $obj->attrA = 'attrA'; - $obj->attrB = null; - $obj->attrC = ''; - // original values to chek which attritubes where modified - $objB = clone $obj; - $val->validateAttribute($obj, 'attrB'); - $this->assertEquals($val->value, $obj->attrB); - $this->assertEquals($objB->attrA, $obj->attrA); - $val->value = 'new_test_value'; - $obj = clone $objB; // get clean object - $val->validateAttribute($obj, 'attrC'); - $this->assertEquals('new_test_value', $obj->attrC); - $this->assertEquals($objB->attrA, $obj->attrA); - $val->validateAttribute($obj, 'attrA'); - $this->assertEquals($objB->attrA, $obj->attrA); - } -} diff --git a/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php deleted file mode 100644 index af1d277f85..0000000000 --- a/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php +++ /dev/null @@ -1,12 +0,0 @@ -mockApplication(); - ActiveRecord::$db = $this->getConnection(); - } - - public function testValidateValueExpectedException() - { - try { - $val = new ExistValidator(); - $val->validate('ref'); - $this->fail('Exception should have been thrown at this time'); - } catch (Exception $e) { - $this->assertInstanceOf('yii\base\InvalidConfigException', $e); - $this->assertEquals('The "targetClass" property must be set.', $e->getMessage()); - } - // combine to save the time creating a new db-fixture set (likely ~5 sec) - try { - $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className()]); - $val->validate('ref'); - $this->fail('Exception should have been thrown at this time'); - } catch (Exception $e) { - $this->assertInstanceOf('yii\base\InvalidConfigException', $e); - $this->assertEquals('The "targetAttribute" property must be configured as a string.', $e->getMessage()); - } - } - - public function testValidateValue() - { - $val = new ExistValidator(['targetClass' => ValidatorTestRefModel::className(), 'targetAttribute' => 'id']); - $this->assertTrue($val->validate(2)); - $this->assertTrue($val->validate(5)); - $this->assertFalse($val->validate(99)); - $this->assertFalse($val->validate(['1'])); - } - - public function testValidateAttribute() - { - // existing value on different table - $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className(), 'targetAttribute' => 'id']); - $m = ValidatorTestRefModel::findOne(['id' => 1]); - $val->validateAttribute($m, 'ref'); - $this->assertFalse($m->hasErrors()); - // non-existing value on different table - $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className(), 'targetAttribute' => 'id']); - $m = ValidatorTestRefModel::findOne(['id' => 6]); - $val->validateAttribute($m, 'ref'); - $this->assertTrue($m->hasErrors('ref')); - // existing value on same table - $val = new ExistValidator(['targetAttribute' => 'ref']); - $m = ValidatorTestRefModel::findOne(['id' => 2]); - $val->validateAttribute($m, 'test_val'); - $this->assertFalse($m->hasErrors()); - // non-existing value on same table - $val = new ExistValidator(['targetAttribute' => 'ref']); - $m = ValidatorTestRefModel::findOne(['id' => 5]); - $val->validateAttribute($m, 'test_val_fail'); - $this->assertTrue($m->hasErrors('test_val_fail')); - // check for given value (true) - $val = new ExistValidator(); - $m = ValidatorTestRefModel::findOne(['id' => 3]); - $val->validateAttribute($m, 'ref'); - $this->assertFalse($m->hasErrors()); - // check for given defaults (false) - $val = new ExistValidator(); - $m = ValidatorTestRefModel::findOne(['id' => 4]); - $m->a_field = 'some new value'; - $val->validateAttribute($m, 'a_field'); - $this->assertTrue($m->hasErrors('a_field')); - // existing array - $val = new ExistValidator(['targetAttribute' => 'ref']); - $val->allowArray = true; - $m = new ValidatorTestRefModel(); - $m->test_val = [2, 3, 4, 5]; - $val->validateAttribute($m, 'test_val'); - $this->assertFalse($m->hasErrors('test_val')); - // non-existing array - $val = new ExistValidator(['targetAttribute' => 'ref']); - $val->allowArray = true; - $m = new ValidatorTestRefModel(); - $m->test_val = [95, 96, 97, 98]; - $val->validateAttribute($m, 'test_val'); - $this->assertTrue($m->hasErrors('test_val')); - // partial-existing array - $val = new ExistValidator(['targetAttribute' => 'ref']); - $val->allowArray = true; - $m = new ValidatorTestRefModel(); - $m->test_val = [2, 97, 3, 98]; - $val->validateAttribute($m, 'test_val'); - $this->assertTrue($m->hasErrors('test_val')); - // existing array (allowArray = false) - $val = new ExistValidator(['targetAttribute' => 'ref']); - $val->allowArray = false; - $m = new ValidatorTestRefModel(); - $m->test_val = [2, 3, 4, 5]; - $val->validateAttribute($m, 'test_val'); - $this->assertTrue($m->hasErrors('test_val')); - // non-existing array (allowArray = false) - $val = new ExistValidator(['targetAttribute' => 'ref']); - $val->allowArray = false; - $m = new ValidatorTestRefModel(); - $m->test_val = [95, 96, 97, 98]; - $val->validateAttribute($m, 'test_val'); - $this->assertTrue($m->hasErrors('test_val')); - } - - public function testValidateCompositeKeys() - { - $val = new ExistValidator([ - 'targetClass' => OrderItem::className(), - 'targetAttribute' => ['order_id', 'item_id'], - ]); - // validate old record - $m = OrderItem::findOne(['order_id' => 1, 'item_id' => 2]); - $val->validateAttribute($m, 'order_id'); - $this->assertFalse($m->hasErrors('order_id')); - - // validate new record - $m = new OrderItem(['order_id' => 1, 'item_id' => 2]); - $val->validateAttribute($m, 'order_id'); - $this->assertFalse($m->hasErrors('order_id')); - $m = new OrderItem(['order_id' => 10, 'item_id' => 2]); - $val->validateAttribute($m, 'order_id'); - $this->assertTrue($m->hasErrors('order_id')); - - $val = new ExistValidator([ - 'targetClass' => OrderItem::className(), - 'targetAttribute' => ['id' => 'order_id'], - ]); - // validate old record - $m = Order::findOne(1); - $val->validateAttribute($m, 'id'); - $this->assertFalse($m->hasErrors('id')); - $m = Order::findOne(1); - $m->id = 10; - $val->validateAttribute($m, 'id'); - $this->assertTrue($m->hasErrors('id')); - - $m = new Order(['id' => 1]); - $val->validateAttribute($m, 'id'); - $this->assertFalse($m->hasErrors('id')); - $m = new Order(['id' => 10]); - $val->validateAttribute($m, 'id'); - $this->assertTrue($m->hasErrors('id')); - } -} diff --git a/tests/unit/framework/validators/FilterValidatorTest.php b/tests/unit/framework/validators/FilterValidatorTest.php deleted file mode 100644 index 1b77047e50..0000000000 --- a/tests/unit/framework/validators/FilterValidatorTest.php +++ /dev/null @@ -1,66 +0,0 @@ -mockApplication(); - } - - public function testAssureExceptionOnInit() - { - $this->setExpectedException('yii\base\InvalidConfigException'); - new FilterValidator(); - } - - public function testValidateAttribute() - { - $m = FakedValidationModel::createWithAttributes([ - 'attr_one' => ' to be trimmed ', - 'attr_two' => 'set this to null', - 'attr_empty1' => '', - 'attr_empty2' => null, - 'attr_array' => ['Maria', 'Anna', 'Elizabeth'], - 'attr_array_skipped' => ['John', 'Bill'] - ]); - $val = new FilterValidator(['filter' => 'trim']); - $val->validateAttribute($m, 'attr_one'); - $this->assertSame('to be trimmed', $m->attr_one); - $val->filter = function ($value) { - return null; - }; - $val->validateAttribute($m, 'attr_two'); - $this->assertNull($m->attr_two); - $val->filter = [$this, 'notToBeNull']; - $val->validateAttribute($m, 'attr_empty1'); - $this->assertSame($this->notToBeNull(''), $m->attr_empty1); - $val->skipOnEmpty = true; - $val->validateAttribute($m, 'attr_empty2'); - $this->assertNotNull($m->attr_empty2); - $val->filter = function($value) { - - return implode(',', $value); - }; - $val->skipOnArray = false; - $val->validateAttribute($m, 'attr_array'); - $this->assertSame('Maria,Anna,Elizabeth', $m->attr_array); - $val->skipOnArray = true; - $val->validateAttribute($m, 'attr_array_skipped'); - $this->assertSame(['John', 'Bill'], $m->attr_array_skipped); - } - - public function notToBeNull($value) - { - return 'not null'; - } -} diff --git a/tests/unit/framework/validators/RegularExpressionValidatorTest.php b/tests/unit/framework/validators/RegularExpressionValidatorTest.php deleted file mode 100644 index e9c3ce7557..0000000000 --- a/tests/unit/framework/validators/RegularExpressionValidatorTest.php +++ /dev/null @@ -1,55 +0,0 @@ -mockApplication(); - } - - public function testValidateValue() - { - $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); - $this->assertTrue($val->validate('b.4')); - $this->assertFalse($val->validate('b./')); - $this->assertFalse($val->validate(['a', 'b'])); - $val->not = true; - $this->assertFalse($val->validate('b.4')); - $this->assertTrue($val->validate('b./')); - $this->assertFalse($val->validate(['a', 'b'])); - } - - public function testValidateAttribute() - { - $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); - $m = FakedValidationModel::createWithAttributes(['attr_reg1' => 'b.4']); - $val->validateAttribute($m, 'attr_reg1'); - $this->assertFalse($m->hasErrors('attr_reg1')); - $m->attr_reg1 = 'b./'; - $val->validateAttribute($m, 'attr_reg1'); - $this->assertTrue($m->hasErrors('attr_reg1')); - } - - public function testMessageSetOnInit() - { - $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); - $this->assertTrue(is_string($val->message)); - } - - public function testInitException() - { - $this->setExpectedException('yii\base\InvalidConfigException'); - $val = new RegularExpressionValidator(); - $val->validate('abc'); - } -} diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php deleted file mode 100644 index 3f6100ffc0..0000000000 --- a/tests/unit/framework/validators/StringValidatorTest.php +++ /dev/null @@ -1,118 +0,0 @@ -mockApplication(); - } - - public function testValidateValue() - { - $val = new StringValidator(); - $this->assertFalse($val->validate(['not a string'])); - $this->assertTrue($val->validate('Just some string')); - } - - public function testValidateValueLength() - { - $val = new StringValidator(['length' => 25]); - $this->assertTrue($val->validate(str_repeat('x', 25))); - $this->assertTrue($val->validate(str_repeat('€', 25))); - $this->assertFalse($val->validate(str_repeat('x', 125))); - $this->assertFalse($val->validate('')); - $val = new StringValidator(['length' => [25]]); - $this->assertTrue($val->validate(str_repeat('x', 25))); - $this->assertTrue($val->validate(str_repeat('x', 1250))); - $this->assertFalse($val->validate(str_repeat('Ä', 24))); - $this->assertFalse($val->validate('')); - $val = new StringValidator(['length' => [10, 20]]); - $this->assertTrue($val->validate(str_repeat('x', 15))); - $this->assertTrue($val->validate(str_repeat('x', 10))); - $this->assertTrue($val->validate(str_repeat('x', 20))); - $this->assertFalse($val->validate(str_repeat('x', 5))); - $this->assertFalse($val->validate(str_repeat('x', 25))); - $this->assertFalse($val->validate('')); - // make sure min/max are overridden - $val = new StringValidator(['length' => [10, 20], 'min' => 25, 'max' => 35]); - $this->assertTrue($val->validate(str_repeat('x', 15))); - $this->assertFalse($val->validate(str_repeat('x', 30))); - } - - public function testValidateValueMinMax() - { - $val = new StringValidator(['min' => 10]); - $this->assertTrue($val->validate(str_repeat('x', 10))); - $this->assertFalse($val->validate('xxxx')); - $val = new StringValidator(['max' => 10]); - $this->assertTrue($val->validate('xxxx')); - $this->assertFalse($val->validate(str_repeat('y', 20))); - $val = new StringValidator(['min' => 10, 'max' => 20]); - $this->assertTrue($val->validate(str_repeat('y', 15))); - $this->assertFalse($val->validate('abc')); - $this->assertFalse($val->validate(str_repeat('b', 25))); - } - - public function testValidateAttribute() - { - $val = new StringValidator(); - $model = new FakedValidationModel(); - $model->attr_string = 'a tet string'; - $val->validateAttribute($model, 'attr_string'); - $this->assertFalse($model->hasErrors()); - $val = new StringValidator(['length' => 20]); - $model = new FakedValidationModel(); - $model->attr_string = str_repeat('x', 20); - $val->validateAttribute($model, 'attr_string'); - $this->assertFalse($model->hasErrors()); - $model = new FakedValidationModel(); - $model->attr_string = 'abc'; - $val->validateAttribute($model, 'attr_string'); - $this->assertTrue($model->hasErrors('attr_string')); - $val = new StringValidator(['max' => 2]); - $model = new FakedValidationModel(); - $model->attr_string = 'a'; - $val->validateAttribute($model, 'attr_string'); - $this->assertFalse($model->hasErrors()); - $model = new FakedValidationModel(); - $model->attr_string = 'abc'; - $val->validateAttribute($model, 'attr_string'); - $this->assertTrue($model->hasErrors('attr_string')); - $val = new StringValidator(['max' => 1]); - $model = FakedValidationModel::createWithAttributes(['attr_str' => ['abc']]); - $val->validateAttribute($model, 'attr_str'); - $this->assertTrue($model->hasErrors('attr_str')); - } - - public function testEnsureMessagesOnInit() - { - $val = new StringValidator(['min' => 1, 'max' => 2]); - $this->assertTrue(is_string($val->message)); - $this->assertTrue(is_string($val->tooLong)); - $this->assertTrue(is_string($val->tooShort)); - } - - public function testCustomErrorMessageInValidateAttribute() - { - $val = new StringValidator([ - 'min' => 5, - 'tooShort' => '{attribute} to short. Min is {min}', - ]); - $model = new FakedValidationModel(); - $model->attr_string = 'abc'; - $val->validateAttribute($model, 'attr_string'); - $this->assertTrue($model->hasErrors('attr_string')); - $errorMsg = $model->getErrors('attr_string'); - $this->assertEquals('attr_string to short. Min is 5', $errorMsg[0]); - } -} diff --git a/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php deleted file mode 100644 index d8921937cb..0000000000 --- a/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php +++ /dev/null @@ -1,13 +0,0 @@ -mockApplication(); - } - - protected function getTestModel($additionalAttributes = []) - { - $attributes = array_merge( - ['attr_runMe1' => true, 'attr_runMe2' => true, 'attr_skip' => true], - $additionalAttributes - ); - - return FakedValidationModel::createWithAttributes($attributes); - } - - public function testCreateValidator() - { - $model = FakedValidationModel::createWithAttributes(['attr_test1' => 'abc', 'attr_test2' => '2013']); - /* @var $numberVal NumberValidator */ - $numberVal = TestValidator::createValidator('number', $model, ['attr_test1']); - $this->assertInstanceOf(NumberValidator::className(), $numberVal); - $numberVal = TestValidator::createValidator('integer', $model, ['attr_test2']); - $this->assertInstanceOf(NumberValidator::className(), $numberVal); - $this->assertTrue($numberVal->integerOnly); - $val = TestValidator::createValidator( - 'boolean', - $model, - ['attr_test1', 'attr_test2'], - ['on' => ['a', 'b']] - ); - $this->assertInstanceOf(BooleanValidator::className(), $val); - $this->assertSame(['a', 'b'], $val->on); - $this->assertSame(['attr_test1', 'attr_test2'], $val->attributes); - $val = TestValidator::createValidator( - 'boolean', - $model, - ['attr_test1', 'attr_test2'], - ['on' => ['a', 'b'], 'except' => ['c', 'd', 'e']] - ); - $this->assertInstanceOf(BooleanValidator::className(), $val); - $this->assertSame(['a', 'b'], $val->on); - $this->assertSame(['c', 'd', 'e'], $val->except); - $val = TestValidator::createValidator('inlineVal', $model, ['val_attr_a'], ['params' => ['foo' => 'bar']]); - $this->assertInstanceOf(InlineValidator::className(), $val); - $this->assertSame('inlineVal', $val->method); - $this->assertSame(['foo' => 'bar'], $val->params); - } - - public function testValidate() - { - $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2']]); - $model = $this->getTestModel(); - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertTrue($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - } - - public function testValidateWithAttributeIntersect() - { - $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2']]); - $model = $this->getTestModel(); - $val->validateAttributes($model, ['attr_runMe1']); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertFalse($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - } - - public function testValidateWithEmptyAttributes() - { - $val = new TestValidator(); - $model = $this->getTestModel(); - $val->validateAttributes($model, ['attr_runMe1']); - $this->assertFalse($val->isAttributeValidated('attr_runMe1')); - $this->assertFalse($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - $val->validateAttributes($model); - $this->assertFalse($val->isAttributeValidated('attr_runMe1')); - $this->assertFalse($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - } - - public function testValidateWithError() - { - $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2'], 'skipOnError' => false]); - $model = $this->getTestModel(); - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertTrue($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); - $val->validateAttributes($model, ['attr_runMe2']); - $this->assertEquals(2, $val->countAttributeValidations('attr_runMe2')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); - $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); - $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2'], 'skipOnError' => true]); - $model = $this->getTestModel(); - $val->enableErrorOnValidateAttribute(); - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertTrue($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_skip')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); - $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); - $val->validateAttributes($model, ['attr_runMe2']); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); - $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); - $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); - } - - public function testValidateWithEmpty() - { - $val = new TestValidator([ - 'attributes' => [ - 'attr_runMe1', - 'attr_runMe2', - 'attr_empty1', - 'attr_empty2' - ], - 'skipOnEmpty' => true, - ]); - $model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']); - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertTrue($val->isAttributeValidated('attr_runMe2')); - $this->assertFalse($val->isAttributeValidated('attr_empty1')); - $this->assertFalse($val->isAttributeValidated('attr_empty2')); - $model->attr_empty1 = 'not empty anymore'; - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_empty1')); - $this->assertFalse($val->isAttributeValidated('attr_empty2')); - $val = new TestValidator([ - 'attributes' => [ - 'attr_runMe1', - 'attr_runMe2', - 'attr_empty1', - 'attr_empty2' - ], - 'skipOnEmpty' => false, - ]); - $model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']); - $val->validateAttributes($model); - $this->assertTrue($val->isAttributeValidated('attr_runMe1')); - $this->assertTrue($val->isAttributeValidated('attr_runMe2')); - $this->assertTrue($val->isAttributeValidated('attr_empty1')); - $this->assertTrue($val->isAttributeValidated('attr_empty2')); - } - - public function testIsEmpty() - { - $val = new TestValidator(); - $this->assertTrue($val->isEmpty(null)); - $this->assertTrue($val->isEmpty([])); - $this->assertTrue($val->isEmpty('')); - $this->assertFalse($val->isEmpty(5)); - $this->assertFalse($val->isEmpty(0)); - $this->assertFalse($val->isEmpty(new \stdClass())); - $this->assertFalse($val->isEmpty(' ')); - } - - public function testValidateValue() - { - $this->setExpectedException( - 'yii\base\NotSupportedException', - TestValidator::className() . ' does not support validateValue().' - ); - $val = new TestValidator(); - $val->validate('abc'); - } - - public function testClientValidateAttribute() - { - $val = new TestValidator(); - $this->assertNull( - $val->clientValidateAttribute($this->getTestModel(), 'attr_runMe1', []) - ); //todo pass a view instead of array - } - - public function testIsActive() - { - $val = new TestValidator(); - $this->assertTrue($val->isActive('scenA')); - $this->assertTrue($val->isActive('scenB')); - $val->except = ['scenB']; - $this->assertTrue($val->isActive('scenA')); - $this->assertFalse($val->isActive('scenB')); - $val->on = ['scenC']; - $this->assertFalse($val->isActive('scenA')); - $this->assertFalse($val->isActive('scenB')); - $this->assertTrue($val->isActive('scenC')); - } - - public function testAddError() - { - $val = new TestValidator(); - $m = $this->getTestModel(['attr_msg_val' => 'abc']); - $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); - $errors = $m->getErrors('attr_msg_val'); - $this->assertEquals('attr_msg_val::abc', $errors[0]); - $m = $this->getTestModel(['attr_msg_val' => ['bcc']]); - $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); - $errors = $m->getErrors('attr_msg_val'); - $this->assertEquals('attr_msg_val::array()', $errors[0]); - $m = $this->getTestModel(['attr_msg_val' => 'abc']); - $val->addError($m, 'attr_msg_val', '{attribute}::{value}::{param}', ['param' => 'param_value']); - $errors = $m->getErrors('attr_msg_val'); - $this->assertEquals('attr_msg_val::abc::param_value', $errors[0]); - } -} diff --git a/tests/unit/framework/web/AssetConverterTest.php b/tests/unit/framework/web/AssetConverterTest.php deleted file mode 100644 index 15f699fae7..0000000000 --- a/tests/unit/framework/web/AssetConverterTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - */ - -namespace yiiunit\framework\web; - -use yii\helpers\FileHelper; -use yii\web\AssetConverter; - -/** - * @group web - */ -class AssetConverterTest extends \yiiunit\TestCase -{ - /** - * @var string temporary files path - */ - protected $tmpPath; - - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - $this->tmpPath = \Yii::$app->runtimePath . '/assetConverterTest_' . getmypid(); - if (!is_dir($this->tmpPath)) { - mkdir($this->tmpPath, 0777, true); - } - } - - protected function tearDown() - { - if (is_dir($this->tmpPath)) { - FileHelper::removeDirectory($this->tmpPath); - } - parent::tearDown(); - } - - // Tests : - - public function testConvert() - { - $tmpPath = $this->tmpPath; - file_put_contents($tmpPath . '/test.php', <<commands['php'] = ['txt', 'php {from} > {to}']; - $this->assertEquals('test.txt', $converter->convert('test.php', $tmpPath)); - - $this->assertTrue(file_exists($tmpPath . '/test.txt'), 'Failed asserting that asset output file exists.'); - $this->assertEquals("Hello World!\nHello Yii!", file_get_contents($tmpPath . '/test.txt')); - } - - /** - * @depends testConvert - */ - public function testForceConvert() - { - $tmpPath = $this->tmpPath; - file_put_contents($tmpPath . '/test.php', <<commands['php'] = ['txt', 'php {from} > {to}']; - - $converter->convert('test.php', $tmpPath); - $initialConvertTime = file_get_contents($tmpPath . '/test.txt'); - - usleep(1); - $converter->convert('test.php', $tmpPath); - $this->assertEquals($initialConvertTime, file_get_contents($tmpPath . '/test.txt')); - - $converter->forceConvert = true; - $converter->convert('test.php', $tmpPath); - $this->assertNotEquals($initialConvertTime, file_get_contents($tmpPath . '/test.txt')); - } -} diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php deleted file mode 100644 index c81e01a42e..0000000000 --- a/tests/unit/framework/web/CacheSessionTest.php +++ /dev/null @@ -1,36 +0,0 @@ -mockApplication(); - Yii::$app->set('cache', new FileCache()); - } - - public function testCacheSession() - { - $session = new CacheSession(); - - $session->writeSession('test', 'sessionData'); - $this->assertEquals('sessionData', $session->readSession('test')); - $session->destroySession('test'); - $this->assertEquals('', $session->readSession('test')); - } - - public function testInvalidCache() - { - $this->setExpectedException('\Exception'); - new CacheSession(['cache' => 'invalid']); - } -} diff --git a/tests/unit/framework/web/GroupUrlRuleTest.php b/tests/unit/framework/web/GroupUrlRuleTest.php deleted file mode 100644 index 29482d49f4..0000000000 --- a/tests/unit/framework/web/GroupUrlRuleTest.php +++ /dev/null @@ -1,216 +0,0 @@ -mockApplication(); - } - - public function testCreateUrl() - { - $manager = new UrlManager(['cache' => null]); - $suites = $this->getTestsForCreateUrl(); - foreach ($suites as $i => $suite) { - list ($name, $config, $tests) = $suite; - $rule = new GroupUrlRule($config); - foreach ($tests as $j => $test) { - list ($route, $params, $expected) = $test; - $url = $rule->createUrl($manager, $route, $params); - $this->assertEquals($expected, $url, "Test#$i-$j: $name"); - } - } - } - - public function testParseRequest() - { - $manager = new UrlManager(['cache' => null]); - $request = new Request(['hostInfo' => 'http://en.example.com']); - $suites = $this->getTestsForParseRequest(); - foreach ($suites as $i => $suite) { - list ($name, $config, $tests) = $suite; - $rule = new GroupUrlRule($config); - foreach ($tests as $j => $test) { - $request->pathInfo = $test[0]; - $route = $test[1]; - $params = isset($test[2]) ? $test[2] : []; - $result = $rule->parseRequest($manager, $request); - if ($route === false) { - $this->assertFalse($result, "Test#$i-$j: $name"); - } else { - $this->assertEquals([$route, $params], $result, "Test#$i-$j: $name"); - } - } - } - } - - protected function getTestsForCreateUrl() - { - // structure of each test - // message for the test - // config for the URL rule - // list of inputs and outputs - // route - // params - // expected output - return [ - [ - 'no prefix', - [ - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['user/login', [], 'login'], - ['user/logout', [], 'logout'], - ['user/create', [], false], - ], - ], - [ - 'prefix only', - [ - 'prefix' => 'admin', - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['admin/user/login', [], 'admin/login'], - ['admin/user/logout', [], 'admin/logout'], - ['user/create', [], false], - ], - ], - [ - 'prefix and routePrefix different', - [ - 'prefix' => '_', - 'routePrefix' => 'admin', - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['admin/user/login', [], '_/login'], - ['admin/user/logout', [], '_/logout'], - ['user/create', [], false], - ], - ], - [ - 'ruleConfig with suffix', - [ - 'prefix' => '_', - 'routePrefix' => 'admin', - 'ruleConfig' => [ - 'suffix' => '.html', - 'class' => 'yii\\web\\UrlRule' - ], - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['admin/user/login', [], '_/login.html'], - ['admin/user/logout', [], '_/logout.html'], - ['user/create', [], false], - ], - ], - ]; - } - - protected function getTestsForParseRequest() - { - // structure of each test - // message for the test - // config for the URL rule - // list of inputs and outputs - // pathInfo - // expected route, or false if the rule doesn't apply - // expected params, or not set if empty - return [ - [ - 'no prefix', - [ - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['login', 'user/login'], - ['logout', 'user/logout'], - ['create', false], - ], - ], - [ - 'prefix only', - [ - 'prefix' => 'admin', - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['admin/login', 'admin/user/login'], - ['admin/logout', 'admin/user/logout'], - ['admin/create', false], - ['create', false], - ], - ], - [ - 'prefix and routePrefix different', - [ - 'prefix' => '_', - 'routePrefix' => 'admin', - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['_/login', 'admin/user/login'], - ['_/logout', 'admin/user/logout'], - ['_/create', false], - ['create', false], - ], - ], - [ - 'ruleConfig with suffix', - [ - 'prefix' => '_', - 'routePrefix' => 'admin', - 'ruleConfig' => [ - 'suffix' => '.html', - 'class' => 'yii\\web\\UrlRule' - ], - 'rules' => [ - 'login' => 'user/login', - 'logout' => 'user/logout', - ], - ], - [ - ['_/login.html', 'admin/user/login'], - ['_/logout.html', 'admin/user/logout'], - ['_/logout', false], - ['_/create.html', false], - ], - ], - ]; - } -} diff --git a/tests/unit/framework/web/JsonResponseFormatterTest.php b/tests/unit/framework/web/JsonResponseFormatterTest.php deleted file mode 100644 index ca49f48499..0000000000 --- a/tests/unit/framework/web/JsonResponseFormatterTest.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @since 2.0.3 - * - * @group web - */ -class JsonResponseFormatterTest extends FormatterTest -{ - /** - * @return JsonResponseFormatter - */ - protected function getFormatterInstance() - { - return new JsonResponseFormatter(); - } - - public function formatScalarDataProvider() - { - return [ - [1, 1], - ['abc', '"abc"'], - [true, 'true'], - ["<>", '"<>"'], - ]; - } - - public function formatArrayDataProvider() - { - return [ - [[], "[]"], - [[1, 'abc'], '[1,"abc"]'], - [[ - 'a' => 1, - 'b' => 'abc', - ], '{"a":1,"b":"abc"}'], - [[ - 1, - 'abc', - [2, 'def'], - true, - ], '[1,"abc",[2,"def"],true]'], - [[ - 'a' => 1, - 'b' => 'abc', - 'c' => [2, '<>'], - true, - ], '{"a":1,"b":"abc","c":[2,"<>"],"0":true}'], - ]; - } - - public function formatObjectDataProvider() - { - return [ - [new Post(123, 'abc'), '{"id":123,"title":"abc"}'], - [[ - new Post(123, 'abc'), - new Post(456, 'def'), - ], '[{"id":123,"title":"abc"},{"id":456,"title":"def"}]'], - [[ - new Post(123, '<>'), - 'a' => new Post(456, 'def'), - ], '{"0":{"id":123,"title":"<>"},"a":{"id":456,"title":"def"}}'], - ]; - } -} diff --git a/tests/unit/framework/web/Post.php b/tests/unit/framework/web/Post.php deleted file mode 100644 index d6ebc68525..0000000000 --- a/tests/unit/framework/web/Post.php +++ /dev/null @@ -1,16 +0,0 @@ -id = $id; - $this->title = $title; - } -} \ No newline at end of file diff --git a/tests/unit/framework/web/RequestTest.php b/tests/unit/framework/web/RequestTest.php deleted file mode 100644 index 2d276f95da..0000000000 --- a/tests/unit/framework/web/RequestTest.php +++ /dev/null @@ -1,81 +0,0 @@ -assertEquals([], $request->parseAcceptHeader(' ')); - - $this->assertEquals([ - 'audio/basic' => ['q' => 1], - 'audio/*' => ['q' => 0.2], - ], $request->parseAcceptHeader('audio/*; q=0.2, audio/basic')); - - $this->assertEquals([ - 'application/json' => ['q' => 1, 'version' => '1.0'], - 'application/xml' => ['q' => 1, 'version' => '2.0', 'x'], - 'text/x-c' => ['q' => 1], - 'text/x-dvi' => ['q' => 0.8], - 'text/plain' => ['q' => 0.5], - ], $request->parseAcceptHeader('text/plain; q=0.5, - application/json; version=1.0, - application/xml; version=2.0; x, - text/x-dvi; q=0.8, text/x-c')); - } - - public function testPrefferedLanguage() - { - $this->mockApplication([ - 'language' => 'en', - ]); - - $request = new Request(); - $request->acceptableLanguages = []; - $this->assertEquals('en', $request->getPreferredLanguage()); - - $request = new Request(); - $request->acceptableLanguages = ['de']; - $this->assertEquals('en', $request->getPreferredLanguage()); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - $this->assertEquals('en', $request->getPreferredLanguage(['en'])); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - $this->assertEquals('de', $request->getPreferredLanguage(['ru', 'de'])); - $this->assertEquals('de-DE', $request->getPreferredLanguage(['ru', 'de-DE'])); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - $this->assertEquals('de', $request->getPreferredLanguage(['de', 'ru'])); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - $this->assertEquals('ru-ru', $request->getPreferredLanguage(['ru-ru'])); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de']; - $this->assertEquals('ru-ru', $request->getPreferredLanguage(['ru-ru', 'pl'])); - $this->assertEquals('ru-RU', $request->getPreferredLanguage(['ru-RU', 'pl'])); - - $request = new Request(); - $request->acceptableLanguages = ['en-us', 'de']; - $this->assertEquals('pl', $request->getPreferredLanguage(['pl', 'ru-ru'])); - } -} diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php deleted file mode 100644 index 8a9c22d988..0000000000 --- a/tests/unit/framework/web/ResponseTest.php +++ /dev/null @@ -1,104 +0,0 @@ -mockApplication(); - $this->response = new \yii\web\Response; - } - - public function rightRanges() - { - // TODO test more cases for range requests and check for rfc compatibility - // http://www.w3.org/Protocols/rfc2616/rfc2616.txt - return [ - ['0-5', '0-5', 6, '12ёж'], - ['2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'], - ['-12', '55-66', 12, '(ёжик)=?'], - ]; - } - - /** - * @dataProvider rightRanges - */ - public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedContent) - { - $dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt'); - $fullContent = file_get_contents($dataFile); - $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; - ob_start(); - $this->response->sendFile($dataFile)->send( ); - $content = ob_get_clean(); - - $this->assertEquals($expectedContent, $content); - $this->assertEquals(206, $this->response->statusCode); - $headers = $this->response->headers; - $this->assertEquals("bytes", $headers->get('Accept-Ranges')); - $this->assertEquals("bytes " . $expectedHeader . '/' . StringHelper::byteLength($fullContent), $headers->get('Content-Range')); - $this->assertEquals('text/plain', $headers->get('Content-Type')); - $this->assertEquals("$length", $headers->get('Content-Length')); - } - - public function wrongRanges() - { - // TODO test more cases for range requests and check for rfc compatibility - // http://www.w3.org/Protocols/rfc2616/rfc2616.txt - return [ - ['1-2,3-5,6-10'], // multiple range request not supported - ['5-1'], // last-byte-pos value is less than its first-byte-pos value - ['-100000'], // last-byte-pos bigger then content length - ['10000-'], // first-byte-pos bigger then content length - ]; - } - - /** - * @dataProvider wrongRanges - */ - public function testSendFileWrongRanges($rangeHeader) - { - $this->setExpectedException('yii\web\HttpException'); - - $dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt'); - $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; - $this->response->sendFile($dataFile); - } - - protected function generateTestFileContent() - { - return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'; - } - - /** - * https://github.com/yiisoft/yii2/issues/7529 - */ - public function testSendContentAsFile() - { - ob_start(); - $this->response->sendContentAsFile('test', 'test.txt')->send([ - 'mimeType' => 'text/plain' - ]); - $content = ob_get_clean(); - - static::assertEquals('test', $content); - static::assertEquals(200, $this->response->statusCode); - $headers = $this->response->headers; - static::assertEquals('application/octet-stream', $headers->get('Content-Type')); - static::assertEquals('attachment; filename="test.txt"', $headers->get('Content-Disposition')); - static::assertEquals(4, $headers->get('Content-Length')); - } -} diff --git a/tests/unit/framework/web/UserTest.php b/tests/unit/framework/web/UserTest.php deleted file mode 100644 index 9acb83b0e8..0000000000 --- a/tests/unit/framework/web/UserTest.php +++ /dev/null @@ -1,137 +0,0 @@ -session->removeAll(); - static::$time = null; - parent::tearDown(); - } - - public function testLoginExpires() - { - if (getenv('TRAVIS') == 'true') { - $this->markTestSkipped('Can not reliably test this on travis-ci.'); - } - - $appConfig = [ - 'components' => [ - 'user' => [ - 'identityClass' => UserIdentity::className(), - 'authTimeout' => 10, - ], - 'authManager' => [ - 'class' => PhpManager::className(), - 'itemFile' => '@runtime/user_test_rbac_items.php', - 'assignmentFile' => '@runtime/user_test_rbac_assignments.php', - 'ruleFile' => '@runtime/user_test_rbac_rules.php', - ] - ], - ]; - $this->mockWebApplication($appConfig); - - $am = Yii::$app->authManager; - $am->removeAll(); - $am->add($role = $am->createPermission('rUser')); - $am->add($perm = $am->createPermission('doSomething')); - $am->addChild($role, $perm); - $am->assign($role, 'user1'); - - Yii::$app->session->removeAll(); - static::$time = \time(); - Yii::$app->user->login(UserIdentity::findIdentity('user1')); - -// print_r(Yii::$app->session); -// print_r($_SESSION); - - $this->mockWebApplication($appConfig); - $this->assertFalse(Yii::$app->user->isGuest); - $this->assertTrue(Yii::$app->user->can('doSomething')); - - static::$time += 5; - $this->mockWebApplication($appConfig); - $this->assertFalse(Yii::$app->user->isGuest); - $this->assertTrue(Yii::$app->user->can('doSomething')); - - static::$time += 11; - $this->mockWebApplication($appConfig); - $this->assertTrue(Yii::$app->user->isGuest); - $this->assertFalse(Yii::$app->user->can('doSomething')); - - } - -} - -class UserIdentity extends Component implements IdentityInterface -{ - private static $ids = [ - 'user1', - 'user2', - 'user3', - ]; - - private $_id; - - public static function findIdentity($id) - { - if (in_array($id, static::$ids)) { - $identitiy = new static(); - $identitiy->_id = $id; - return $identitiy; - } - } - - public static function findIdentityByAccessToken($token, $type = null) - { - throw new NotSupportedException(); - } - - public function getId() - { - return $this->_id; - } - - public function getAuthKey() - { - throw new NotSupportedException(); - } - - public function validateAuthKey($authKey) - { - throw new NotSupportedException(); - } -} \ No newline at end of file diff --git a/tests/unit/framework/widgets/SpacelessTest.php b/tests/unit/framework/widgets/SpacelessTest.php deleted file mode 100644 index ae41b4c024..0000000000 --- a/tests/unit/framework/widgets/SpacelessTest.php +++ /dev/null @@ -1,41 +0,0 @@ -\n"; - - Spaceless::begin(); - echo "\t
        \n"; - - Spaceless::begin(); - echo "\t\t
        \n"; - echo "\t\t\t

        This is a left bar!

        \n"; - echo "\t\t
        \n\n"; - echo "\t\t
        \n"; - echo "\t\t\t

        This is a right bar!

        \n"; - echo "\t\t
        \n"; - Spaceless::end(); - - echo "\t
        \n"; - Spaceless::end(); - - echo "\t

        Bye!

        \n"; - echo "\n"; - - $expected = "\n

        This is a left bar!

        ". - "

        This is a right bar!

        \t

        Bye!

        \n\n"; - $this->assertEquals($expected, ob_get_clean()); - } -} diff --git a/tests/unit/runtime/.gitignore b/tests/unit/runtime/.gitignore deleted file mode 100644 index 5e31724df5..0000000000 --- a/tests/unit/runtime/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!/coveralls/.gitkeep diff --git a/tests/unit/runtime/coveralls/.gitkeep b/tests/unit/runtime/coveralls/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/web/app/assets/.gitignore b/tests/web/app/assets/.gitignore deleted file mode 100644 index 72e8ffc0db..0000000000 --- a/tests/web/app/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/tests/web/app/index.php b/tests/web/app/index.php deleted file mode 100644 index 72d3189431..0000000000 --- a/tests/web/app/index.php +++ /dev/null @@ -1,6 +0,0 @@ -run(); diff --git a/tests/web/app/protected/config/main.php b/tests/web/app/protected/config/main.php deleted file mode 100644 index 0b67a5fe47..0000000000 --- a/tests/web/app/protected/config/main.php +++ /dev/null @@ -1,3 +0,0 @@ - 'item 1', - 'value 2' => 'item 2', - 'value 3' => 'item 3', - ], isset($_POST['test']) ? $_POST['test'] : null, - function ($index, $label, $name, $value, $checked) { - return Html::label( - $label . ' ' . Html::checkbox($name, $value, $checked), - null, ['class' => 'inline checkbox'] - ); - }); - echo Html::submitButton(); - echo Html::endForm(); - print_r($_POST); - } -} diff --git a/tests/web/app/protected/runtime/.gitignore b/tests/web/app/protected/runtime/.gitignore deleted file mode 100644 index 72e8ffc0db..0000000000 --- a/tests/web/app/protected/runtime/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/tests/web/app/protected/views/site/index.php b/tests/web/app/protected/views/site/index.php deleted file mode 100644 index cc86af6fa4..0000000000 --- a/tests/web/app/protected/views/site/index.php +++ /dev/null @@ -1,8 +0,0 @@ -