diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..08b73bb --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,24 @@ +engines: + duplication: + enabled: true + config: + languages: + - javascript + - php + eslint: + enabled: true + fixme: + enabled: true + phpmd: + enabled: true + config: + rulesets: "codesize,design,unusedcode,tests/data/codeclimate/phpmd_ruleset.xml" +ratings: + paths: + - "**.js" + - "**.php" +exclude_paths: +- tests/ +- build/ +- docs/ +- framework/messages/ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5c15602 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +vendor +docs \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..96212a3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/*{.,-}min.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9faa375 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,213 @@ +ecmaFeatures: + modules: true + jsx: true + +env: + amd: true + browser: true + es6: true + jquery: true + node: true + +# http://eslint.org/docs/rules/ +rules: + # Possible Errors + comma-dangle: [2, never] + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: [2, functions] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 0 + valid-typeof: 2 + + # Best Practices + accessor-pairs: 2 + block-scoped-var: 0 + complexity: [2, 6] + consistent-return: 0 + curly: 0 + default-case: 0 + dot-location: 0 + dot-notation: 0 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 2 + no-caller: 2 + no-case-declarations: 2 + no-div-regex: 2 + no-else-return: 0 + no-empty-label: 2 + no-empty-pattern: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-fallthrough: 2 + no-floating-decimal: 0 + no-implicit-coercion: 0 + no-implied-eval: 2 + no-invalid-this: 0 + no-iterator: 2 + no-labels: 0 + no-lone-blocks: 2 + no-loop-func: 2 + no-magic-number: 0 + no-multi-spaces: 0 + no-multi-str: 0 + no-native-reassign: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-new: 2 + no-octal-escape: 2 + no-octal: 2 + no-proto: 2 + no-redeclare: 2 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 0 + no-throw-literal: 0 + no-unused-expressions: 2 + no-useless-call: 2 + no-useless-concat: 2 + no-void: 2 + no-warning-comments: 0 + no-with: 2 + radix: 2 + vars-on-top: 0 + wrap-iife: 2 + yoda: 0 + + # Strict + strict: 0 + + # Variables + init-declarations: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 0 + no-undef-init: 2 + no-undef: 0 + no-undefined: 0 + no-unused-vars: 0 + no-use-before-define: 0 + + # Node.js and CommonJS + callback-return: 2 + global-require: 2 + handle-callback-err: 2 + no-mixed-requires: 0 + no-new-require: 0 + no-path-concat: 2 + no-process-exit: 2 + no-restricted-modules: 0 + no-sync: 0 + + # Stylistic Issues + array-bracket-spacing: 0 + block-spacing: 0 + brace-style: 0 + camelcase: 0 + comma-spacing: 0 + comma-style: 0 + computed-property-spacing: 0 + consistent-this: 0 + eol-last: 0 + func-names: 0 + func-style: 0 + id-length: 0 + id-match: 0 + indent: 0 + jsx-quotes: 0 + key-spacing: 0 + linebreak-style: 0 + lines-around-comment: 0 + max-depth: 0 + max-len: 0 + max-nested-callbacks: 0 + max-params: 0 + max-statements: [2, 30] + new-cap: 0 + new-parens: 0 + newline-after-var: 0 + no-array-constructor: 0 + no-bitwise: 0 + no-continue: 0 + no-inline-comments: 0 + no-lonely-if: 0 + no-mixed-spaces-and-tabs: 0 + no-multiple-empty-lines: 0 + no-negated-condition: 0 + no-nested-ternary: 0 + no-new-object: 0 + no-plusplus: 0 + no-restricted-syntax: 0 + no-spaced-func: 0 + no-ternary: 0 + no-trailing-spaces: 0 + no-underscore-dangle: 0 + no-unneeded-ternary: 0 + object-curly-spacing: 0 + one-var: 0 + operator-assignment: 0 + operator-linebreak: 0 + padded-blocks: 0 + quote-props: 0 + quotes: 0 + require-jsdoc: 0 + semi-spacing: 0 + semi: 0 + sort-vars: 0 + space-after-keywords: 0 + space-before-blocks: 0 + space-before-function-paren: 0 + space-before-keywords: 0 + space-in-parens: 0 + space-infix-ops: 0 + space-return-throw-case: 0 + space-unary-ops: 0 + spaced-comment: 0 + wrap-regex: 0 + + # ECMAScript 6 + arrow-body-style: 0 + arrow-parens: 0 + arrow-spacing: 0 + constructor-super: 0 + generator-star-spacing: 0 + no-arrow-condition: 0 + no-class-assign: 0 + no-const-assign: 0 + no-dupe-class-members: 0 + no-this-before-super: 0 + no-var: 0 + object-shorthand: 0 + prefer-arrow-callback: 0 + prefer-const: 0 + prefer-reflect: 0 + prefer-spread: 0 + prefer-template: 0 + require-yield: 0 diff --git a/.gitattributes b/.gitattributes index feef00d..5fb509b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,17 +22,15 @@ *.gif binary *.ttf binary -# Ignore all test and documentation for archive +# Ignore some meta files when creating an archive of this repository +# We do not ignore any content, because this repo represents the +# `yiisoft/yii2-dev` package, which is expected to ship all tests and docs. /.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/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cf063e6..3813ff4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,5 +1,5 @@ -Contributing to Yii2 -==================== +Contributing to Yii 2 +===================== - [Report an issue](../docs/internals/report-an-issue.md) - [Translate documentation or messages](../docs/internals/translation-workflow.md) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 33d7ddf..020f890 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -9,6 +9,6 @@ | Q | A | ---------------- | --- -| Yii version | -| PHP version | -| Operating system | +| Yii version | 2.0.? +| PHP version | +| Operating system | diff --git a/.gitignore b/.gitignore index 6b729e6..ee89055 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,10 @@ phpunit.phar # local phpunit config /phpunit.xml -# ignore sub directory for dev installed apps and extensions +# ignore dev installed apps and extensions /apps /extensions + +# NPM packages +/node_modules +.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..37bc6d3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,118 @@ +before_script: + # set stack isolation + - export ISOLATION=buildpipeline${CI_PIPELINE_ID}${CI_BUILD_NAME} + - export COMPOSE_PROJECT_NAME=${ISOLATION} + - export TUPLE_C=$(expr ${CI_BUILD_ID} % 99) + - echo ${TUPLE_C} + # run docker-compose commands from tests environment + - cd tests + - cp .env-dist .env + - docker-compose config + +after_script: + - export ISOLATION=buildpipeline${CI_PIPELINE_ID}${CI_BUILD_NAME} + - export COMPOSE_PROJECT_NAME=${ISOLATION} + # run docker-compose commands from tests environment + - cd tests + - cp .env-dist .env + - docker-compose down -v --remove-orphans + - docker ps -f name=${ISOLATION} + +stages: + - travis + - test + - cleanup + +test: + stage: test + script: + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db,data --log-junit tests/_junit/test.xml + +caching: + stage: test + only: + - tests/caching + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --group caching --exclude db + +db: + stage: test + only: + - tests/mysql + - tests/full + script: + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid,oci + + +mysql: + stage: test + only: + - tests/mysql + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c "while ! curl mysql:3306; do ((c++)) && ((c==30)) && break; sleep 2; done" + - docker-compose run --rm php vendor/bin/phpunit -v --group mysql + + +pgsql: + stage: test + only: + - tests/pgsql + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl postgres:5432; if [ $? == 52 ]; then break; fi; ((c++)) && ((c==25)) && break; sleep 2; done' + - docker-compose run --rm php vendor/bin/phpunit -v --group pgsql + + +cubrid: + stage: test + only: + - tests/cubrid + - tests/extra + script: + - cd cubrid + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl cubrid:1523; if [ $? == 56 ]; then break; fi; ((c++)) && ((c==20)) && break; sleep 3; done' + - sleep 5 + - docker-compose run --rm php /project/vendor/bin/phpunit -v --group cubrid + + +mssql: + stage: test + only: + - tests/mssql + - tests/extra + script: + - cd mssql + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl mssql:1433; if [ $? == 52 ]; then break; fi; ((c++)) && ((c==15)) && break; sleep 5; done' + - sleep 3 + # Note: Password has to be the last parameter + - docker-compose run --rm sqlcmd sh -c 'sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Microsoft-12345' + - docker-compose run --rm php vendor/bin/phpunit -v --group mssql + + +travis: + stage: travis + only: + - travis + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml:docker-compose.pgsql.yml + - docker-compose up --build -d + # wait for dbs ... + - sleep 10 + - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid + diff --git a/.travis.yml b/.travis.yml index 7cf1797..f458b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,26 @@ +# +# Travis Setup +# + +# use ubuntu trusty for newer version of nodejs, used for JS testing +dist: trusty + +# faster builds on new travis setup not using sudo +sudo: false + +# build only on master branches +# commented as this prevents people from running builds on their forks: +# https://github.com/yiisoft/yii2/commit/bd87be990fa238c6d5e326d0a171f38d02dc253a +#branches: +# only: +# - master +# - 2.1 + + +# +# Test Matrix +# + language: php php: @@ -5,39 +28,107 @@ php: - 5.5 - 5.6 - 7.0 - - hhvm + - 7.1 + - nightly + +matrix: + fast_finish: true + include: + # Test against HHVM 3.12 LTS version by using trusty + - php: hhvm-3.12 + sudo: true + dist: trusty + group: edge # Use edge image until the next travis CI image update + addons: + code_climate: + repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b + apt: + packages: + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 + services: + - mysql + # test against the latest HHVM version by using a newer image + - php: hhvm + sudo: true + dist: trusty + group: edge # Use edge image until the next travis CI image update + addons: + code_climate: + repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b + postgresql: "9.3" + apt: + packages: + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 + services: + - mysql + - postgresql + # have a separate branch for javascript tests + - language: node_js + node_js: 6 + dist: trusty + # overwrite php related settings + php: + services: + addons: + install: + - travis_retry npm install + # disable xdebug for performance in composer + - phpenv config-rm xdebug.ini || echo "xdebug is not installed" + - travis_retry composer self-update && composer --version + - travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins + - travis_retry composer install --prefer-dist --no-interaction + before_script: + - node --version + - npm --version + - php --version + - composer --version + script: npm test + after_script: + + allow_failures: + - php: nightly -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 + - mysql + - postgresql # cache vendor dirs cache: directories: -# - cubrid/9.3.0 - vendor - $HOME/.composer/cache + - $HOME/.npm # try running against postgres 9.3 addons: postgresql: "9.3" + code_climate: + repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b install: + - | + if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != hhv* ]]; then + # disable xdebug for performance reasons when code coverage is not needed. note: xdebug on hhvm is disabled by default + phpenv config-rm xdebug.ini || echo "xdebug is not installed" + fi - travis_retry composer self-update && composer --version - - travis_retry composer global require "fxp/composer-asset-plugin:~1.1.1" + - travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins - export PATH="$HOME/.composer/vendor/bin:$PATH" -# core framework: + # 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 + - tests/data/travis/imagick-setup.sh before_script: + # Disable the HHVM JIT for faster Unit Testing + - if [[ $TRAVIS_PHP_VERSION = hhv* ]]; then echo 'hhvm.jit = 0' >> /etc/hhvm/php.ini; fi # show some versions and env information - php -r "echo INTL_ICU_VERSION . \"\n\";" - php -r "echo INTL_ICU_DATA_VERSION . \"\n\";" @@ -45,9 +136,12 @@ before_script: - psql --version # initialize databases - - mysql -e 'CREATE DATABASE yiitest;'; + - travis_retry mysql -e 'CREATE DATABASE `yiitest`;'; + - mysql -e "CREATE USER 'travis'@'localhost' IDENTIFIED WITH mysql_native_password;"; + - mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'localhost' WITH GRANT OPTION;"; - psql -U postgres -c 'CREATE DATABASE yiitest;'; + # enable code coverage on PHP 5.6, only one PHP version needs to generate coverage data - | if [ $TRAVIS_PHP_VERSION = '5.6' ]; then PHPUNIT_FLAGS="--coverage-clover=coverage.clover" @@ -55,6 +149,12 @@ before_script: script: + # ensure no files contain UTF-8 byte order mark + - if ! grep -rlI $'\xEF\xBB\xBF' framework/ ; then echo "no utf8 BOM found"; else echo "found utf8 BOM in some files. See above."; exit 1; fi + # validate composer.json + - composer validate --no-check-lock + - cd framework && composer validate --no-check-lock && cd .. + # run PHPUnit - vendor/bin/phpunit --verbose $PHPUNIT_FLAGS --exclude-group mssql,oci,wincache,xcache,zenddata,cubrid after_script: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b890986 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM dmstr/php-yii2:7.0-fpm-1.9-beta2-alpine-nginx + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer install --prefer-dist +ADD ./ /project +ENV PATH /project/vendor/bin:${PATH} diff --git a/README.md b/README.md index 9e8f9c2..5875553 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -Yii PHP Framework Version 2 -=========================== +

+ + Yii Framework + +

-Thank you for choosing Yii 2 - a modern PHP framework designed for professional Web development. - -Yii 2 is a complete rewrite of its previous version Yii 1.1 which is one of the most popular PHP frameworks. -Yii 2 inherits the main spirit behind Yii for being simple, fast and highly extensible. -Yii 2 requires PHP 5.4 and embraces the best practices and protocols found in modern Web application development. +Yii 2 is a modern framework designed to be a solid foundation for your PHP application. +It is fast, secure and efficient and works right out of the box pre-configured with reasonable defaults. +The framework is easy to adjust to meet your needs, because Yii has been designed to be flexible. [![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) @@ -18,49 +19,62 @@ Yii 2 requires PHP 5.4 and embraces the best practices and protocols found in mo [![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://img.shields.io/codeclimate/github/yiisoft/yii2.svg)](https://codeclimate.com/github/yiisoft/yii2) -DIRECTORY STRUCTURE -------------------- - -``` -build/ internally used build tools -docs/ documentation -framework/ core framework code -tests/ tests of the core framework code -``` - - -REQUIREMENTS +Installation ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.4. - +- The minimum required PHP version of Yii is PHP 5.4. +- It works best with PHP 7. +- [Follow the Definitive Guide](http://www.yiiframework.com/doc-2.0/guide-start-installation.html) +in order to get step by step instructions. -DOCUMENTATION +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. - -There is also a [PDF version](http://stuff.cebe.cc/yii2-guide.en.pdf) of the Definitive Guide +- 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) cover every detail +of the framework. +- There is 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. +- For Yii 1.1 users, there is [Upgrading from Yii 1.1](docs/guide/intro-upgrade-from-v1.md) +to get an idea of what has changed in 2.0. -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. +Community +--------- +- Participate in [discussions at forums](http://www.yiiframework.com/forum/). +- [Chat in IRC](http://www.yiiframework.com/chat/). +- Follow us on [Facebook](https://www.facebook.com/groups/yiitalk/), [Twitter](https://twitter.com/yiiframework) +and [GitHub](https://github.com/yiisoft/yii2). -HOW TO PARTICIPATE ------------------- +Contributing +------------ -### Your participation to Yii 2 development is very welcome! +The framework is [Open Source](LICENSE.md) powered by [an excellent community](https://github.com/yiisoft/yii2/graphs/contributors). -You may participate in the following ways: +You may join us and: - [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 +### Reporting Security issues + +Please refer to a [special page at the website](http://www.yiiframework.com/security/) +describing proper workflow for security issue reports. + +### Directory Structure + +``` +build/ internally used build tools +docs/ documentation +framework/ core framework code +tests/ tests of the core framework code +``` + +### Spreading the Word + +Acknowledging or citing Yii 2 is as important as direct contributions. **In presentations** diff --git a/build/controllers/DevController.php b/build/controllers/DevController.php index cc44ec1..42d8cf6 100644 --- a/build/controllers/DevController.php +++ b/build/controllers/DevController.php @@ -70,14 +70,14 @@ class DevController extends Controller return 1; } - foreach($this->extensions as $ext => $repo) { + foreach ($this->extensions as $ext => $repo) { $ret = $this->actionExt($ext); if ($ret !== 0) { return $ret; } } - foreach($this->apps as $app => $repo) { + foreach ($this->apps as $app => $repo) { $ret = $this->actionApp($app); if ($ret !== 0) { return $ret; @@ -107,7 +107,7 @@ class DevController extends Controller asort($dirs); $oldcwd = getcwd(); - foreach($dirs as $dir) { + foreach ($dirs as $dir) { $displayDir = substr($dir, strlen($base)); $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD); chdir($dir); @@ -252,7 +252,7 @@ class DevController extends Controller $this->unlink($link); } $extensions = $this->findDirs("$dir/vendor/yiisoft"); - foreach($extensions as $ext) { + foreach ($extensions as $ext) { if (is_link($link = "$dir/vendor/yiisoft/yii2-$ext")) { $this->stdout("Removing symlink $link.\n"); $this->unlink($link); @@ -261,7 +261,7 @@ class DevController extends Controller } /** - * Creates symlinks to freamework and extension sources for the application + * Creates symlinks to framework and extension sources for the application * @param string $dir application directory * @param string $base Yii sources base directory * @@ -276,7 +276,7 @@ class DevController extends Controller symlink("$base/framework", $link); } $extensions = $this->findDirs("$dir/vendor/yiisoft"); - foreach($extensions as $ext) { + foreach ($extensions as $ext) { if (is_dir($link = "$dir/vendor/yiisoft/yii2-$ext")) { $this->stdout("Removing dir $link.\n"); FileHelper::removeDirectory($link); @@ -359,7 +359,7 @@ class DevController extends Controller } closedir($handle); - foreach($list as $i => $e) { + foreach ($list as $i => $e) { if ($e === 'composer') { // skip composer to not break composer update unset($list[$i]); } diff --git a/build/controllers/MimeTypeController.php b/build/controllers/MimeTypeController.php index 028623c..768aac5 100644 --- a/build/controllers/MimeTypeController.php +++ b/build/controllers/MimeTypeController.php @@ -39,14 +39,14 @@ class MimeTypeController extends Controller $this->stdout("done.\n", Console::FG_GREEN); $this->stdout("generating file $outFile..."); $mimeMap = []; - foreach(explode("\n", $content) as $line) { + foreach (explode("\n", $content) as $line) { $line = trim($line); if (empty($line) || $line[0] == '#') { // skip comments and empty lines continue; } $parts = preg_split('/\s+/', $line); $mime = array_shift($parts); - foreach($parts as $ext) { + foreach ($parts as $ext) { if (!empty($ext)) { $mimeMap[$ext] = $mime; } diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index bd7f6ab..bb01fa5 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -23,7 +23,7 @@ class PhpDocController extends Controller { public $defaultAction = 'property'; /** - * @var boolean whether to update class docs directly. Setting this to false will just output docs + * @var bool whether to update class docs directly. Setting this to false will just output docs * for copy and paste. */ public $updateFiles = true; @@ -167,12 +167,12 @@ class PhpDocController extends Controller 'tests/', 'vendor/', ]; - foreach($extensionExcept as $ext => $paths) { - foreach($paths as $path) { + foreach ($extensionExcept as $ext => $paths) { + foreach ($paths as $path) { $except[] = "/extensions/$ext$path"; } } - } elseif (preg_match('~extensions/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { + } elseif (preg_match('~extensions/([\w-]+)[\\\\/]?$~', $root, $matches)) { $extensionPath = dirname(rtrim($root, '\\/')); $this->setUpExtensionAliases($extensionPath); @@ -184,7 +184,7 @@ class PhpDocController extends Controller } if (isset($extensionExcept[$extension])) { - foreach($extensionExcept[$extension] as $path) { + foreach ($extensionExcept[$extension] as $path) { $except[] = $path; } } @@ -196,7 +196,7 @@ class PhpDocController extends Controller // if ($extension === 'composer') { // return []; // } - } elseif (preg_match('~apps/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { + } elseif (preg_match('~apps/([\w-]+)[\\\\/]?$~', $root, $matches)) { $extensionPath = dirname(dirname(rtrim($root, '\\/'))) . '/extensions'; $this->setUpExtensionAliases($extensionPath); @@ -255,7 +255,7 @@ class PhpDocController extends Controller $namespace = false; $namespaceLine = ''; $contentAfterNamespace = false; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { $line = trim($line); if (!empty($line)) { if (strncmp($line, 'namespace', 9) === 0) { @@ -269,7 +269,7 @@ class PhpDocController extends Controller } if ($namespace !== false && $contentAfterNamespace !== false) { - while($contentAfterNamespace > 0) { + while ($contentAfterNamespace > 0) { array_shift($lines); $contentAfterNamespace--; } @@ -297,7 +297,7 @@ class PhpDocController extends Controller $listIndent = ''; $tag = false; $indent = ''; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { if (preg_match('~^(\s*)/\*\*$~', $line, $matches)) { $docBlock = true; $indent = $matches[1]; @@ -348,10 +348,10 @@ class PhpDocController extends Controller { 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; + foreach ($types as $i => $type) { + switch ($type) { + case 'integer': $types[$i] = 'int'; break; + case 'boolean': $types[$i] = 'bool'; break; } } return '@' . $matches[1] . ' ' . implode('|', $types); @@ -367,7 +367,7 @@ class PhpDocController extends Controller // remove blank lines between properties $skip = true; $level = 0; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { if (strpos($line, 'class ') !== false) { $skip = false; } @@ -402,7 +402,7 @@ class PhpDocController extends Controller $skip = true; $level = 0; // track array properties $property = ''; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { if (strpos($line, 'class ') !== false) { $skip = false; } @@ -428,10 +428,10 @@ class PhpDocController extends Controller $endofPrivate = $i; $property = 'Private'; $level = 0; - } elseif (substr($line,0 , 6) === 'const ') { + } elseif (substr($line, 0, 6) === 'const ') { $endofConst = $i; $property = false; - } elseif (substr($line,0 , 4) === 'use ') { + } elseif (substr($line, 0, 4) === 'use ') { $endofUse = $i; $property = false; } elseif (!empty($line) && $line[0] === '*') { @@ -447,7 +447,7 @@ class PhpDocController extends Controller } $endofAll = false; - foreach(['Private', 'Protected', 'Public', 'Const', 'Use'] as $var) { + foreach (['Private', 'Protected', 'Public', 'Const', 'Use'] as $var) { if (${'endof'.$var} !== false) { $endofAll = ${'endof'.$var}; break; @@ -456,7 +456,7 @@ class PhpDocController extends Controller // $this->checkPropertyOrder($lineInfo); $result = []; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { $result[] = $line; if (!($propertiesOnly && $i === $endofAll)) { if ($i === $endofUse || $i === $endofConst || $i === $endofPublic || @@ -479,9 +479,15 @@ class PhpDocController extends Controller protected function updateClassPropertyDocs($file, $className, $propertyDoc) { - $ref = new \ReflectionClass($className); + try { + $ref = new \ReflectionClass($className); + } catch (\Exception $e) { + $this->stderr("[ERR] Unable to create ReflectionClass for class '$className': " . $e->getMessage() . "\n", Console::FG_RED); + return false; + } if ($ref->getFileName() != $file) { $this->stderr("[ERR] Unable to create ReflectionClass for class: $className loaded class is not from file: $file\n", Console::FG_RED); + return false; } if (!$ref->isSubclassOf('yii\base\Object') && $className != 'yii\base\Object') { @@ -747,7 +753,7 @@ class PhpDocController extends Controller // example: yii\di\ServiceLocator setComponents() is not recognized in the whole but in // a part of the class. $parts = $split ? explode("\n\n", $subject) : [$subject]; - foreach($parts as $part) { + foreach ($parts as $part) { preg_match_all($pattern . 'suU', $part, $matches, PREG_SET_ORDER); foreach ($matches as &$set) { foreach ($set as $i => $match) diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index 8560b86..06674a3 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -56,6 +56,10 @@ class ReleaseController extends Controller * @var bool whether to fetch latest tags. */ public $update = false; + /** + * @var string override the default version. e.g. for major or patch releases. + */ + public $version; public function options($actionID) @@ -63,6 +67,7 @@ class ReleaseController extends Controller $options = ['basePath']; if ($actionID === 'release') { $options[] = 'dryRun'; + $options[] = 'version'; } elseif ($actionID === 'info') { $options[] = 'update'; } @@ -100,7 +105,7 @@ class ReleaseController extends Controller } if ($this->update) { - foreach($items as $item) { + foreach ($items as $item) { $this->stdout("fetching tags for $item..."); if ($item === 'framework') { $this->gitFetchTags("{$this->basePath}"); @@ -121,7 +126,7 @@ class ReleaseController extends Controller // 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) { + 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"); } @@ -131,7 +136,7 @@ class ReleaseController extends Controller private function minWidth($a) { $w = 1; - foreach($a as $s) { + foreach ($a as $s) { if (($l = mb_strlen($s)) > $w) { $w = $l; } @@ -190,20 +195,32 @@ class ReleaseController extends Controller $this->validateWhat($what); $versions = $this->getCurrentVersions($what); - $newVersions = $this->getNextVersions($versions, self::PATCH);// TODO add support for minor + + if ($this->version !== null) { + // if a version is explicitly given + $newVersions = []; + foreach ($versions as $k => $v) { + $newVersions[$k] = $this->version; + } + } else { + // otherwise get next patch or minor + $newVersions = $this->getNextVersions($versions, self::PATCH); + } $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); + $gitDir = reset($what) === 'framework' ? 'framework/' : ''; + $gitVersion = $versions[reset($what)]; 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"); + $this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n\n git diff $gitVersion.. ${gitDir}CHANGELOG.md\n\n"); + $this->stdout("- are all new `@since` tags for this release version?\n"); } + $this->stdout("- other issues with code changes?\n\n git diff -w $gitVersion.. ${gitDir}\n\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"); @@ -213,7 +230,7 @@ class ReleaseController extends Controller return 1; } - foreach($what as $ext) { + foreach ($what as $ext) { if ($ext === 'framework') { $this->releaseFramework("{$this->basePath}/framework", $newVersions['framework']); } elseif (strncmp('app-', $ext, 4) === 0) { @@ -247,7 +264,7 @@ class ReleaseController extends Controller $versions = $this->getCurrentVersions($what); $this->stdout("You are about to generate packages for the following things:\n\n"); - foreach($what as $ext) { + foreach ($what as $ext) { if (strncmp('app-', $ext, 4) === 0) { $this->stdout(" - "); $this->stdout(substr($ext, 4), Console::FG_RED); @@ -272,7 +289,7 @@ class ReleaseController extends Controller return 1; } - foreach($what as $ext) { + foreach ($what as $ext) { if ($ext === 'framework') { throw new Exception('Can not package framework.'); } elseif (strncmp('app-', $ext, 4) === 0) { @@ -287,9 +304,37 @@ class ReleaseController extends Controller return 0; } + /** + * Sorts CHANGELOG for framework or extension. + * + * @param array $what what do you want to resort changelog for? this can either be: + * + * - an extension name such as `redis` or `bootstrap`, + * - or `framework` if you want to release a new version of the framework itself. + */ + public function actionSortChangelog(array $what) + { + if (count($what) > 1) { + $this->stdout("Currently only one simultaneous release is supported.\n"); + return 1; + } + $this->validateWhat($what, ['framework', 'ext'], false); + + $version = array_values($this->getNextVersions($this->getCurrentVersions($what), self::PATCH))[0]; + $this->stdout('sorting CHANGELOG of '); + $this->stdout(reset($what), Console::BOLD); + $this->stdout(" for version "); + $this->stdout($version, Console::BOLD); + $this->stdout("..."); + + $this->resortChangelogs($what, $version); + + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + } + protected function printWhat(array $what, $newVersions, $versions) { - foreach($what as $ext) { + foreach ($what as $ext) { if (strncmp('app-', $ext, 4) === 0) { $this->stdout(" - "); $this->stdout(substr($ext, 4), Console::FG_RED); @@ -308,7 +353,7 @@ class ReleaseController extends Controller protected function printWhatUrls(array $what, $oldVersions) { - foreach($what as $ext) { + 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"); @@ -325,9 +370,9 @@ class ReleaseController extends Controller * @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 = []) + protected function validateWhat(array $what, $limit = [], $ensureGitClean = true) { - foreach($what as $w) { + 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"); @@ -335,7 +380,9 @@ class ReleaseController extends Controller if (!is_dir($appPath = "{$this->basePath}/apps/" . substr($w, 4))) { throw new Exception("Application path does not exist: \"{$appPath}\"\n"); } - $this->ensureGitClean($appPath); + if ($ensureGitClean) { + $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"); @@ -343,7 +390,9 @@ class ReleaseController extends Controller if (!is_dir($fwPath = "{$this->basePath}/framework")) { throw new Exception("Framework path does not exist: \"{$this->basePath}/framework\"\n"); } - $this->ensureGitClean($fwPath); + if ($ensureGitClean) { + $this->ensureGitClean($fwPath); + } } else { if (!empty($limit) && !in_array('ext', $limit)) { throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); @@ -351,7 +400,9 @@ class ReleaseController extends Controller 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); + if ($ensureGitClean) { + $this->ensureGitClean($extPath); + } } } } @@ -363,8 +414,10 @@ class ReleaseController extends Controller $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 + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $frameworkPath); // checks @@ -407,20 +460,22 @@ class ReleaseController extends Controller $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?")); + } 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->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $frameworkPath); + $this->runGit("git tag -s $version -m \"version $version\"", $frameworkPath); + $this->runGit("git push", $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("CONGRATULATIONS! You have just released ", 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); @@ -467,13 +522,19 @@ class ReleaseController extends Controller $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->runGit("git push", $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(" git clone git@github.com:yiisoft/yii2-framework.git\n"); + $this->stdout(" cd yii2-framework/\n"); + $this->stdout(" export RELEASECOMMIT=$(git log --oneline |grep $version |grep -Po \"^[0-9a-f]+\")\n"); + $this->stdout(" git tag -s $version -m \"version $version\" \$RELEASECOMMIT\n"); + $this->stdout(" git tag --verify $version\n"); + $this->stdout(" git push --tags\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"); @@ -492,8 +553,10 @@ class ReleaseController extends Controller $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 + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); // adjustments @@ -518,16 +581,18 @@ class ReleaseController extends Controller $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?")); + } 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->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $path); + $this->runGit("git tag -s $version -m \"version $version\"", $path); + $this->runGit("git push", $path); $this->runGit("git push --tags", $path); $this->stdout("\n\n"); @@ -551,7 +616,7 @@ class ReleaseController extends Controller $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->runGit("git push", $path); $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); @@ -568,7 +633,7 @@ class ReleaseController extends Controller protected function setAppAliases($app, $path) { $this->_oldAlias = Yii::getAlias('@app'); - switch($app) { + switch ($app) { case 'basic': Yii::setAlias('@app', $path); break; @@ -606,8 +671,10 @@ class ReleaseController extends Controller $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 + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); // adjustments @@ -632,16 +699,18 @@ class ReleaseController extends Controller $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?")); + } 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->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $path); + $this->runGit("git tag -s $version -m \"version $version\"", $path); + $this->runGit("git push", $path); $this->runGit("git push --tags", $path); $this->stdout("\n\n"); @@ -664,7 +733,7 @@ class ReleaseController extends Controller $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->runGit("git push", $path); $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); @@ -756,7 +825,7 @@ class ReleaseController extends Controller { $headline = "\n$version under development\n"; $headline .= str_repeat('-', strlen($headline) - 2) . "\n\n- no changes in this release.\n"; - foreach($this->getChangelogs($what) as $file) { + foreach ($this->getChangelogs($what) as $file) { $lines = explode("\n", file_get_contents($file)); $hl = [ array_shift($lines), @@ -770,7 +839,7 @@ class ReleaseController extends Controller protected function resortChangelogs($what, $version) { - foreach($this->getChangelogs($what) 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); @@ -791,7 +860,7 @@ class ReleaseController extends Controller $end = []; $state = 'start'; - foreach($lines as $l => $line) { + foreach ($lines as $l => $line) { // starting from the changelogs headline if (isset($lines[$l-2]) && strpos($lines[$l-2], $version) !== false && isset($lines[$l-1]) && strncmp($lines[$l-1], '---', 3) === 0) { @@ -800,7 +869,13 @@ class ReleaseController extends Controller if ($state === 'changelog' && isset($lines[$l+1]) && strncmp($lines[$l+1], '---', 3) === 0) { $state = 'end'; } - ${$state}[] = $line; + // add continued lines to the last item to keep them together + if (!empty(${$state}) && trim($line !== '') && strpos($line, '- ') !== 0) { + end(${$state}); + ${$state}[key(${$state})] .= "\n" . $line; + } else { + ${$state}[] = $line; + } } return [$start, $changelog, $end]; } @@ -811,14 +886,14 @@ class ReleaseController extends Controller protected function resortChangelog($changelog) { // cleanup whitespace - foreach($changelog as $i => $line) { + 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)) { + 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++); } @@ -851,7 +926,7 @@ class ReleaseController extends Controller protected function getExtensionChangelogs($what) { return array_filter(glob($this->basePath . '/extensions/*/CHANGELOG.md'), function($elem) use ($what) { - foreach($what as $ext) { + foreach ($what as $ext) { if (strpos($elem, "extensions/$ext/CHANGELOG.md") !== false) { return true; } @@ -904,7 +979,7 @@ class ReleaseController extends Controller protected function sed($pattern, $replace, $files) { - foreach((array) $files as $file) { + foreach ((array) $files as $file) { file_put_contents($file, preg_replace($pattern, $replace, file_get_contents($file))); } } @@ -912,7 +987,7 @@ class ReleaseController extends Controller protected function getCurrentVersions(array $what) { $versions = []; - foreach($what as $ext) { + foreach ($what as $ext) { if ($ext === 'framework') { chdir("{$this->basePath}/framework"); } elseif (strncmp('app-', $ext, 4) === 0) { @@ -936,18 +1011,25 @@ class ReleaseController extends Controller protected function getNextVersions(array $versions, $type) { - foreach($versions as $k => $v) { + foreach ($versions as $k => $v) { if (empty($v)) { $versions[$k] = '2.0.0'; continue; } $parts = explode('.', $v); - switch($type) { + switch ($type) { case self::MINOR: $parts[1]++; + $parts[2] = 0; + if (isset($parts[3])) { + unset($parts[3]); + } break; case self::PATCH: $parts[2]++; + if (isset($parts[3])) { + unset($parts[3]); + } break; default: throw new Exception('Unknown version type.'); diff --git a/build/controllers/TranslationController.php b/build/controllers/TranslationController.php index 75a4d7d..433c842 100644 --- a/build/controllers/TranslationController.php +++ b/build/controllers/TranslationController.php @@ -47,7 +47,7 @@ class TranslationController extends Controller $errors = $this->checkFiles($translatedFilePath); $diff = empty($errors) ? $this->getDiff($translatedFilePath, $sourceFilePath) : ''; - if(!empty($diff)) { + if (!empty($diff)) { $errors[] = 'Translation outdated.'; } @@ -68,7 +68,7 @@ class TranslationController extends Controller $translatedFilePath = $translationPath . '/' . $fileinfo->getFilename(); $errors = $this->checkFiles(null, $translatedFilePath); - if(!empty($errors)) { + if (!empty($errors)) { $results[$fileinfo->getFilename()]['errors'] = $errors; } } diff --git a/build/controllers/Utf8Controller.php b/build/controllers/Utf8Controller.php new file mode 100644 index 0000000..4cf3cbd --- /dev/null +++ b/build/controllers/Utf8Controller.php @@ -0,0 +1,130 @@ + + */ +class Utf8Controller extends Controller +{ + public $defaultAction = 'check-guide'; + + /** + * Check guide for non-printable characters that may break docs generation. + * + * @param string $directory the directory to check. If not specified, the default + * guide directory will be checked. + */ + public function actionCheckGuide($directory = null) + { + if ($directory === null) { + $directory = dirname(dirname(__DIR__)) . '/docs'; + } + if (is_file($directory)) { + $files = [$directory]; + } else { + $files = FileHelper::findFiles($directory, [ + 'only' => ['*.md'], + ]); + } + + foreach ($files as $file) { + + $content = file_get_contents($file); + $chars = preg_split('//u', $content, null, PREG_SPLIT_NO_EMPTY); + + $line = 1; + $pos = 0; + foreach ($chars as $c) { + + $ord = $this->unicodeOrd($c); + + $pos++; + if ($ord == 0x000A) { + $line++; + $pos = 0; + } + + if ($ord === false) { + $this->found("BROKEN UTF8", $c, $line, $pos, $file); + continue; + } + + // http://unicode-table.com/en/blocks/general-punctuation/ + if (0x2000 <= $ord && $ord <= 0x200F + || 0x2028 <= $ord && $ord <= 0x202E + || 0x205f <= $ord && $ord <= 0x206F + ) { + $this->found("UNSUPPORTED SPACE CHARACTER", $c, $line, $pos, $file); + continue; + } + if ($ord < 0x0020 && $ord != 0x000A && $ord != 0x0009 || + 0x0080 <= $ord && $ord < 0x009F) { + $this->found("CONTROL CHARARCTER", $c, $line, $pos, $file); + continue; + } +// if ($ord > 0x009F) { +// $this->found("NON ASCII CHARARCTER", $c, $line, $pos, $file); +// continue; +// } + + } + } + + } + + private $_foundFiles = []; + + private function found($what, $char, $line, $pos, $file) + { + if (!isset($this->_foundFiles[$file])) { + $this->stdout("$file: \n", Console::BOLD); + $this->_foundFiles[$file] = $file; + } + + $hexcode = dechex($this->unicodeOrd($char)); + $hexcode = str_repeat('0', max(4 - strlen($hexcode), 0)) . $hexcode; + + $this->stdout(" at $line:$pos FOUND $what: 0x$hexcode '$char' http://unicode-table.com/en/$hexcode/\n"); + } + + /** + * Equvalent for ord() just for unicode + * + * http://stackoverflow.com/a/10333324/1106908 + * + * @param $c + * @return bool|int + */ + private function unicodeOrd($c) + { + $h = ord($c{0}); + if ($h <= 0x7F) { + return $h; + } else if ($h < 0xC2) { + return false; + } else if ($h <= 0xDF) { + return ($h & 0x1F) << 6 | (ord($c{1}) & 0x3F); + } else if ($h <= 0xEF) { + return ($h & 0x0F) << 12 | (ord($c{1}) & 0x3F) << 6 + | (ord($c{2}) & 0x3F); + } else if ($h <= 0xF4) { + return ($h & 0x0F) << 18 | (ord($c{1}) & 0x3F) << 12 + | (ord($c{2}) & 0x3F) << 6 + | (ord($c{3}) & 0x3F); + } else { + return false; + } + } +} diff --git a/build/controllers/views/translation/report_html.php b/build/controllers/views/translation/report_html.php index c12a4d6..53e57a1 100644 --- a/build/controllers/views/translation/report_html.php +++ b/build/controllers/views/translation/report_html.php @@ -36,9 +36,9 @@ use yii\helpers\Html;
  • Translation:
  • - $result): ?> + $result): ?>

    - +

    diff --git a/code-of-conduct.md b/code-of-conduct.md index 0082eb6..82fdef1 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,45 +1,68 @@ Yii Contributor Code of Conduct ======================= +## Our Pledge + 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. +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery +* The use of sexualized language or imagery and unwelcome sexual attention or + advances * Personal attacks -* Trolling or insulting/derogatory comments +* Trolling or insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission -* Other unethical or unprofessional conduct +* Other conduct which could reasonably be considered inappropriate in + a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in response +to any instances of unacceptable behavior. + +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. + +## Scope -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. +This Code of Conduct applies both within project spaces and in public spaces when +an individual is representing the project or its community. Examples of representing +a project or community include posting via an official social media account, +within project GitHub, official forum or acting as an appointed representative at +an online or offline event. -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. +## Enforcement -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. +The project team is obligated to maintain confidentiality with regard to the reporter of +an incident. Further details of specific enforcement policies may be posted separately. -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. +Project maintainers who do not follow or enforce the Code of Conduct in good faith +may face temporary or permanent repercussions as determined by other members of +the project's leadership. +## Attribution 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] +version 1.4.0, available at +[http://contributor-covenant.org/version/1/4/][version] [homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/3/0/ +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/composer.json b/composer.json index 8d52e52..f74a82e 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,12 @@ "name": "Dmitry Naumenko", "email": "d.naumenko.a@gmail.com", "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" } ], "support": { @@ -57,6 +63,7 @@ "source": "https://github.com/yiisoft/yii2" }, "minimum-stability": "dev", + "prefer-stable": true, "replace": { "yiisoft/yii2": "self.version" }, @@ -68,14 +75,14 @@ "yiisoft/yii2-composer": "~2.0.4", "ezyang/htmlpurifier": "~4.6", "cebe/markdown": "~1.0.0 | ~1.1.0", - "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", - "bower-asset/jquery.inputmask": "~3.2.2", + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2 | ~3.3.3", "bower-asset/punycode": "1.3.*", "bower-asset/yii2-pjax": "~2.0.1" }, "require-dev": { "phpunit/phpunit": "~4.4", - "cebe/indent": "*" + "cebe/indent": "~1.0.2" }, "suggest": { "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii" @@ -85,6 +92,9 @@ "yii\\": "framework/" } }, + "config": { + "platform": {"php": "5.4"} + }, "bin": [ "framework/yii" ], @@ -95,6 +105,10 @@ "asset-installer-paths": { "npm-asset-library": "vendor/npm", "bower-asset-library": "vendor/bower" - } + }, + "asset-vcs-driver-options": { + "github-no-api": true + }, + "asset-pattern-skip-version": "(-build|-patch)" } } diff --git a/composer.lock b/composer.lock index bf95a51..8ab7bdc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "d2005b487c5ff761d806b9a94bfe4cac", - "content-hash": "de99885237d7d9364d74fb5f93389801", + "hash": "db4e038c0e8ca747784fb195c82bfdad", + "content-hash": "cc4b01a602c948040169ebbc1ac30186", "packages": [ { "name": "bower-asset/jquery", - "version": "2.2.3", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/jquery/jquery-dist.git", - "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198" + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198", - "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", "shasum": "" }, "type": "bower-asset-library", @@ -44,12 +44,12 @@ "source": { "type": "git", "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" + "reference": "ec7726993217ee7b01023ad4f7f1b6a51446a39d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", + "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/ec7726993217ee7b01023ad4f7f1b6a51446a39d", + "reference": "ec7726993217ee7b01023ad4f7f1b6a51446a39d", "shasum": "" }, "require": { @@ -148,16 +148,16 @@ }, { "name": "cebe/markdown", - "version": "dev-master", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/cebe/markdown.git", - "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5" + "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", - "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", + "url": "https://api.github.com/repos/cebe/markdown/zipball/c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", + "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", "shasum": "" }, "require": { @@ -204,20 +204,20 @@ "markdown", "markdown-extra" ], - "time": "2016-03-18 13:28:11" + "time": "2016-09-14 20:40:20" }, { "name": "ezyang/htmlpurifier", - "version": "v4.7.0", + "version": "v4.8.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "ae1828d955112356f7677c465f94f7deb7d27a40" + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40", - "reference": "ae1828d955112356f7677c465f94f7deb7d27a40", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", "shasum": "" }, "require": { @@ -248,25 +248,28 @@ "keywords": [ "html" ], - "time": "2015-08-05 01:03:42" + "time": "2016-07-16 12:58:58" }, { "name": "yiisoft/yii2-composer", - "version": "dev-master", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4" + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", - "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", "shasum": "" }, "require": { "composer-plugin-api": "^1.0" }, + "require-dev": { + "composer/composer": "^1.0" + }, "type": "composer-plugin", "extra": { "class": "yii\\composer\\Plugin", @@ -295,22 +298,22 @@ "extension installer", "yii2" ], - "time": "2016-04-14 08:46:37" + "time": "2016-12-20 13:26:02" } ], "packages-dev": [ { "name": "cebe/indent", - "version": "dev-master", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/cebe/indent.git", - "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0" + "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/indent/zipball/0f33ba3cb567726a726e7024072232839a0d7cd0", - "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0", + "url": "https://api.github.com/repos/cebe/indent/zipball/c500ed74d30ed2d7e085f9cf07f8092d32d70776", + "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776", "shasum": "" }, "bin": [ @@ -324,24 +327,26 @@ "authors": [ { "name": "Carsten Brandt", - "email": "mail@cebe.cc" + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" } ], "description": "a small tool to convert text file indentation", - "time": "2015-11-22 14:46:59" + "time": "2014-05-23 14:40:08" }, { "name": "doctrine/instantiator", - "version": "dev-master", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a" + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/416fb8ad1d095a87f1d21bc40711843cd122fd4a", - "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", "shasum": "" }, "require": { @@ -382,7 +387,7 @@ "constructor", "instantiate" ], - "time": "2016-03-31 10:24:22" + "time": "2015-06-14 21:17:01" }, { "name": "phpdocumentor/reflection-docblock", @@ -435,27 +440,28 @@ }, { "name": "phpspec/prophecy", - "version": "dev-master", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d" + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b02221e42163be673f9b44a0bc92a8b4907a7c6d", - "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", "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" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0|^2.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", "extra": { @@ -493,11 +499,11 @@ "spy", "stub" ], - "time": "2016-02-21 17:41:21" + "time": "2016-11-21 14:58:47" }, { "name": "phpunit/php-code-coverage", - "version": "2.2.x-dev", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", @@ -559,16 +565,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "dev-master", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "shasum": "" }, "require": { @@ -602,7 +608,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -647,21 +653,24 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.7", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, "type": "library", "autoload": { "classmap": [ @@ -684,20 +693,20 @@ "keywords": [ "timer" ], - "time": "2015-06-21 08:01:12" + "time": "2016-05-12 18:03:57" }, { "name": "phpunit/php-token-stream", - "version": "dev-master", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644" + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cab6c6fefee93d7b7c3a01292a0fe0884ea66644", - "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", "shasum": "" }, "require": { @@ -733,20 +742,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-09-23 14:46:55" + "time": "2016-11-15 14:06:22" }, { "name": "phpunit/phpunit", - "version": "4.8.x-dev", + "version": "4.8.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3c4becbce99732549949904c47b76ffe602a7595" + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c4becbce99732549949904c47b76ffe602a7595", - "reference": "3c4becbce99732549949904c47b76ffe602a7595", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/98b2b39a520766bec663ff5b7ff1b729db9dbfe3", + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3", "shasum": "" }, "require": { @@ -762,7 +771,7 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", + "sebastian/comparator": "~1.2.2", "sebastian/diff": "~1.2", "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", @@ -805,11 +814,11 @@ "testing", "xunit" ], - "time": "2016-04-25 09:17:33" + "time": "2016-12-09 02:45:31" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.x-dev", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", @@ -865,22 +874,22 @@ }, { "name": "sebastian/comparator", - "version": "dev-master", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", "shasum": "" }, "require": { "php": ">=5.3.3", "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" + "sebastian/exporter": "~1.2 || ~2.0" }, "require-dev": { "phpunit/phpunit": "~4.4" @@ -925,11 +934,11 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2016-11-19 09:18:40" }, { "name": "sebastian/diff", - "version": "dev-master", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", @@ -981,23 +990,23 @@ }, { "name": "sebastian/environment", - "version": "dev-master", + "version": "1.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^4.8 || ^5.0" }, "type": "library", "extra": { @@ -1027,20 +1036,20 @@ "environment", "hhvm" ], - "time": "2016-02-26 18:40:46" + "time": "2016-08-18 05:49:44" }, { "name": "sebastian/exporter", - "version": "dev-master", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "f88f8936517d54ae6d589166810877fb2015d0a2" + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f88f8936517d54ae6d589166810877fb2015d0a2", - "reference": "f88f8936517d54ae6d589166810877fb2015d0a2", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", "shasum": "" }, "require": { @@ -1094,7 +1103,7 @@ "export", "exporter" ], - "time": "2015-08-09 04:23:41" + "time": "2016-06-17 09:04:28" }, { "name": "sebastian/global-state", @@ -1149,16 +1158,16 @@ }, { "name": "sebastian/recursion-context", - "version": "dev-master", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7" + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", - "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", "shasum": "" }, "require": { @@ -1198,7 +1207,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-01-28 05:39:29" + "time": "2015-11-11 19:50:13" }, { "name": "sebastian/version", @@ -1237,16 +1246,16 @@ }, { "name": "symfony/yaml", - "version": "2.8.x-dev", + "version": "v2.8.16", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940" + "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940", - "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940", + "url": "https://api.github.com/repos/symfony/yaml/zipball/dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2", + "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2", "shasum": "" }, "require": { @@ -1282,7 +1291,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-03-29 19:00:15" + "time": "2017-01-03 13:49:52" } ], "aliases": [], @@ -1290,7 +1299,7 @@ "stability-flags": { "bower-asset/jquery": 0 }, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=5.4.0", @@ -1298,5 +1307,8 @@ "ext-ctype": "*", "lib-pcre": "*" }, - "platform-dev": [] + "platform-dev": [], + "platform-overrides": { + "php": "5.4" + } } diff --git a/contrib/completion/bash/yii b/contrib/completion/bash/yii new file mode 100644 index 0000000..084325a --- /dev/null +++ b/contrib/completion/bash/yii @@ -0,0 +1,58 @@ +# This file implements bash completion for the ./yii command file. +# It completes the commands available by the ./yii command. +# See also: +# - https://debian-administration.org/article/317/An_introduction_to_bash_completion_part_2 on how this works. +# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html +# - http://www.yiiframework.com/doc-2.0/guide-tutorial-console.html#bash-completion +# +# Usage: +# Temporarily you can source this file in you bash by typing: source yii +# For permanent availability, copy or link this file to /etc/bash_completion.d/ +# + +_yii() +{ + local cur opts yii command + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + yii="${COMP_WORDS[0]}" + + # exit if ./yii does not exist + test -f $yii || return 0 + + # lookup for command + for word in ${COMP_WORDS[@]:1}; do + if [[ $word != -* ]]; then + command=$word + break + fi + done + + [[ $cur == $command ]] && state="command" + [[ $cur != $command ]] && state="option" + [[ $cur = *=* ]] && state="value" + + case $state in + command) + # complete command/route if not given + # fetch available commands from ./yii help/list command + opts=$($yii help/list 2> /dev/null) + ;; + option) + # fetch available options from ./yii help/list-action-options command + opts=$($yii help/list-action-options $command 2> /dev/null | grep -o '^--[a-zA-Z0-9]*') + ;; + value) + # TODO allow normal file completion after an option, e.g. --migrationPath=... + ;; + esac + + # generate completion suggestions + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + +} + +# register completion for the ./yii command +# you may adjust this line if your command file is named differently +complete -F _yii ./yii yii diff --git a/contrib/completion/zsh/_yii b/contrib/completion/zsh/_yii new file mode 100644 index 0000000..e85ebc3 --- /dev/null +++ b/contrib/completion/zsh/_yii @@ -0,0 +1,38 @@ +#compdef yii + +_yii() { + local state command lastArgument commands options executive + lastArgument=${words[${#words[@]}]} + executive=$words[1] + + # lookup for command + for word in ${words[@]:1}; do + if [[ $word != -* ]]; then + command=$word + break + fi + done + + + [[ $lastArgument == $command ]] && state="command" + [[ $lastArgument != $command ]] && state="option" + + case $state in + command) + commands=("${(@f)$(${executive} help/list 2>/dev/null)}") + _describe 'command' commands + ;; + option) + options=("${(@f)$(${executive} help/usage ${command} 2>/dev/null)}") + _message -r "$options" + + suboptions=("${(@f)$(${executive} help/list-action-options ${command} 2>/dev/null)}") + _describe -V -o -t suboption 'action options' suboptions + ;; + *) + esac + +} + +compdef _yii yii + diff --git a/docs/documentation_style_guide.md b/docs/documentation_style_guide.md index 846fade..af7ae34 100644 --- a/docs/documentation_style_guide.md +++ b/docs/documentation_style_guide.md @@ -11,13 +11,16 @@ Guidelines to go by when writing or editing any Yii documentation. * Demonstrate ideas using code as much as possible. * Never use "we". It's the Yii development team or the Yii core team. Better yet to put things in terms of the framework or the guide. * Use the Oxford comma (e.g., "this, that, and the other" not "this, that and the other"). -* Numeric lists should be complete sentences that end with periods (or other punctuation). -* Bullet lists should be fragments that don't end with periods. ## Formatting * Use *italics* for emphasis, never capitalization, bold, or underlines. +## Lists + +* Numeric lists should be complete sentences that end with periods. +* Bullet lists should be fragments that end with semicolon except the last item, which should end with a period. + ## Blocks Blocks use the Markdown `> Type: `. There are four block types: @@ -32,7 +35,7 @@ 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: +containing the translations. The following shows an example for German: ```json { @@ -47,13 +50,18 @@ containing the translations. The following shows an example for german: * Yii 2.0 or Yii 2 (not Yii2 or Yii2.0) * Each "page" of the guide is referred to as a "section". +* References to Code objects: + - Refer to classes using the full namespace: `yii\base\Model` + - Refer to class properties using the static syntax even if they are not static: `yii\base\Model::$validators` + - Refer to class methods using the static syntax even if they are not static and include parenthesis to make it clear, that it is a method: `yii\base\Model::validate()` + - references to code objects should be writting in `[[]]` to generate links to the API documentation. E.g. `[[yii\base\Model]]`, `[[yii\base\Model::$validators]]`, or `[[yii\base\Model::validate()]]`. ## Capitalizations * Web, not web * the guide or this guide, not the Guide -## validating the docs +## Validating the docs The following are some scripts that help find broken links and other issues in the guide: @@ -62,4 +70,18 @@ Find broken links (some false-positives may occur): grep -rniP "\[\[[^\],']+?\][^\]]" docs/guide* grep -rniP "[^\[]\[[^\]\[,']+?\]\]" docs/guide* - \ No newline at end of file +## Attribution of Translators + +The names of the translators will be listed among the guide authors in the +rendered versions of the guide. +Therefor in each guide directory for a different language than english a `translators.json` file +should be created that contains an array of names of the people who have participated in the translation. + +```json +[ + "Jane Doe", + "John Doe" +] +``` + +If you have contributed a significant part to the translation, feel free to send a pull request adding your name. diff --git a/docs/guide-de/translators.json b/docs/guide-de/translators.json new file mode 100644 index 0000000..9e05c89 --- /dev/null +++ b/docs/guide-de/translators.json @@ -0,0 +1,3 @@ +[ + "Carsten Brandt" +] \ No newline at end of file diff --git a/docs/guide-es/caching-data.md b/docs/guide-es/caching-data.md index 7ee6a3c..6280c4b 100644 --- a/docs/guide-es/caching-data.md +++ b/docs/guide-es/caching-data.md @@ -182,7 +182,7 @@ $cache->set($key, $data, 30, $dependency); // La caché comprobará si los datos han expirado. // También comprobará si la dependencia ha cambiado. -// Devolverá false si se encuentran algunas de esas condiciones. +// Devolverá `false` si se encuentran algunas de esas condiciones. $data = $cache->get($key); ``` @@ -229,7 +229,7 @@ $result = Customer::getDb()->cache(function ($db) { Las consultas en caché tienen tres opciones configurables globales a través de [[yii\db\Connection]]: * [[yii\db\Connection::enableQueryCache|enableQueryCache]]: activa o desactiva el cacheo de consultas. - Por defecto es true. Tenga en cuenta que para activar el cacheo de consultas, también necesitas tener una caché válida, especificada por [[yii\db\Connection::queryCache|queryCache]]. + Por defecto es `true`. Tenga en cuenta que para activar el cacheo de consultas, también necesitas tener una caché válida, especificada por [[yii\db\Connection::queryCache|queryCache]]. * [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: representa el número de segundos que un resultado de la consulta permanecerá válida en la memoria caché. Puedes usar 0 para indicar que el resultado de la consulta debe permanecer en la caché para siempre. Esta propiedad es el valor usado por defecto cuando [[yii\db\Connection::cache()]] es llamada sin especificar una duración. * [[yii\db\Connection::queryCache|queryCache]]: representa el ID del componente de aplicación de caché. Por defecto es `'cache'`. El almacenamiento en caché de consultas se habilita sólo si hay un componente de la aplicación de caché válida. diff --git a/docs/guide-es/caching-http.md b/docs/guide-es/caching-http.md index 7ea1bfb..9280612 100644 --- a/docs/guide-es/caching-http.md +++ b/docs/guide-es/caching-http.md @@ -28,7 +28,7 @@ la página. El formato de la función de llamada de retorno debe ser el siguient /** * @param Action $action el objeto acción que se está controlando actualmente * @param array $params el valor de la propiedad "params" - * @return integer un sello de tiempo UNIX que representa el tiempo de modificación de la página + * @return int un sello de tiempo UNIX que representa el tiempo de modificación de la página */ function ($action, $params) ``` diff --git a/docs/guide-es/concept-autoloading.md b/docs/guide-es/concept-autoloading.md index 5c58e51..6c91329 100644 --- a/docs/guide-es/concept-autoloading.md +++ b/docs/guide-es/concept-autoloading.md @@ -3,7 +3,7 @@ Autocarga de clases Yii depende del [mecanismo de autocarga de clases](http://www.php.net/manual/es/language.oop5.autoload.php) para localizar e incluir los archivos de las clases requiridas. Proporciona un cargador de clases de alto rendimiento que cumple con el -[estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md). +[estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). El cargador se instala cuando incluyes el archivo `Yii.php`. > Note: Para simplificar la descripción, en esta sección sólo hablaremos de la carga automática de clases. Sin embargo, diff --git a/docs/guide-es/concept-events.md b/docs/guide-es/concept-events.md index c8faca5..f3eb872 100644 --- a/docs/guide-es/concept-events.md +++ b/docs/guide-es/concept-events.md @@ -96,7 +96,7 @@ $foo->on(Foo::EVENT_HELLO, function ($event) { De forma predeterminada, cada nuevo gestor añadido se pone a la cola de la lista de gestores del evento. Por lo tanto, el gestor se ejecutará en el último lugar cuando se lance el evento. Para insertar un nuevo gestor al principio de la cola de gestores para que sea ejecutado primero, se debe llamar a [[yii\base\Component::on()]], pasando al cuarto -parámetro `$append` el valor false: +parámetro `$append` el valor `false`: ```php $foo->on(Foo::EVENT_HELLO, function ($event) { @@ -237,7 +237,7 @@ invocación de los gestores de eventos a nivel de clase. use yii\base\Event; Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { - echo $event->sender; // displays "app\models\Foo" + var_dump($event->sender); // displays "null" }); Event::trigger(Foo::className(), Foo::EVENT_HELLO); diff --git a/docs/guide-es/db-dao.md b/docs/guide-es/db-dao.md index aa05c5e..309a796 100644 --- a/docs/guide-es/db-dao.md +++ b/docs/guide-es/db-dao.md @@ -14,7 +14,7 @@ Yii DAO soporta las siguientes bases de datos: - [MySQL](http://www.mysql.com/) - [MariaDB](https://mariadb.com/) - [SQLite](http://sqlite.org/) -- [PostgreSQL](http://www.postgresql.org/) +- [PostgreSQL](http://www.postgresql.org/): versión 8.4 o superior. - [CUBRID](http://www.cubrid.org/): versión 9.3 o superior. - [Oracle](http://www.oracle.com/us/products/database/overview/index.html) - [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): versión 2008 o superior. @@ -111,7 +111,7 @@ $posts = $db->createCommand('SELECT * FROM post') ->queryAll(); // retorna una sola fila (la primera fila) -// false es retornado si no hay resultados +// `false` es retornado si no hay resultados $post = $db->createCommand('SELECT * FROM post WHERE id=1') ->queryOne(); @@ -121,7 +121,7 @@ $titles = $db->createCommand('SELECT title FROM post') ->queryColumn(); // retorna un escalar -// false es retornado si no hay resultados +// `false` es retornado si no hay resultados $count = $db->createCommand('SELECT COUNT(*) FROM post') ->queryScalar(); ``` diff --git a/docs/guide-es/db-migrations.md b/docs/guide-es/db-migrations.md index 8387efa..c71fcf3 100644 --- a/docs/guide-es/db-migrations.md +++ b/docs/guide-es/db-migrations.md @@ -136,7 +136,7 @@ class m150101_185401_create_news_table extends Migration 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-). +[Trabajando con Esquemas de Base de Datos](db-dao.md#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 @@ -147,7 +147,7 @@ serán traducidos a los tipos físicos correspondientes. En el caso de MySQL, `T 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. +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`. @@ -185,14 +185,14 @@ Existe una lista de todos los métodos disponibles para la definición de tipos 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 +Si el nombre de la migración tiene una forma especial, por ejemplo `create_xxx_table` o `drop_xxx_table` 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 +yii migrate/create create_post_table ``` esto genera @@ -201,7 +201,7 @@ esto genera /** * Handles the creation for table `post`. */ -class m150811_220037_create_post extends Migration +class m150811_220037_create_post_table extends Migration { /** * @inheritdoc @@ -226,7 +226,7 @@ class m150811_220037_create_post extends Migration 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" +yii migrate/create create_post_table --fields="title:string,body:text" ``` genera @@ -235,7 +235,7 @@ genera /** * Handles the creation for table `post`. */ -class m150811_220037_create_post extends Migration +class m150811_220037_create_post_table extends Migration { /** * @inheritdoc @@ -263,7 +263,7 @@ class m150811_220037_create_post extends Migration Puedes especificar más parámetros para las columnas. ```php -yii migrate/create create_post --fields="title:string(12):notNull:unique,body:text" +yii migrate/create create_post_table --fields="title:string(12):notNull:unique,body:text" ``` genera @@ -272,7 +272,7 @@ genera /** * Handles the creation for table `post`. */ -class m150811_220037_create_post extends Migration +class m150811_220037_create_post_table extends Migration { /** * @inheritdoc @@ -304,7 +304,7 @@ class m150811_220037_create_post extends Migration 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" +yii migrate/create create_post_table --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" ``` genera @@ -317,7 +317,7 @@ genera * - `user` * - `category` */ -class m160328_040430_create_post extends Migration +class m160328_040430_create_post_table extends Migration { /** * @inheritdoc @@ -422,13 +422,13 @@ una columna llamada `author_id` con una clave foránea a la tabla `user` mientra ### Eliminar Tabla ```php -yii migrate/create drop_post --fields="title:string(12):notNull:unique,body:text" +yii migrate/create drop_post_table --fields="title:string(12):notNull:unique,body:text" ``` genera ```php -class m150811_220037_drop_post extends Migration +class m150811_220037_drop_post_table extends Migration { public function up() { @@ -448,19 +448,19 @@ class m150811_220037_drop_post extends Migration ### Agregar Columna -Si el nombre de la migración está en la forma `add_xxx_to_yyy` entonces el archivo generado contendrá +Si el nombre de la migración está en la forma `add_xxx_column_to_yyy_table` 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" +yii migrate/create add_position_column_to_post_table --fields="position:integer" ``` genera ```php -class m150811_220037_add_position_to_post extends Migration +class m150811_220037_add_position_column_to_post_table extends Migration { public function up() { @@ -476,17 +476,17 @@ class m150811_220037_add_position_to_post extends Migration ### Eliminar Columna -Si el nombre de la migración está en la forma `drop_xxx_from_yyy` entonces el archivo generado contendrá +Si el nombre de la migración está en la forma `drop_xxx_column_from_yyy_table` entonces el archivo generado contendrá las declaraciones `addColumn` y `dropColumn` necesarias. ```php -yii migrate/create drop_position_from_post --fields="position:integer" +yii migrate/create drop_position_column_from_post_table --fields="position:integer" ``` genera ```php -class m150811_220037_drop_position_from_post extends Migration +class m150811_220037_drop_position_column_from_post_table extends Migration { public function up() { @@ -502,11 +502,11 @@ class m150811_220037_drop_position_from_post extends Migration ### 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 +Si el nombre de la migración está en la forma `create_junction_table_for_xxx_and_yyy_tables` 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" +yii migrate/create create_junction_table_for_post_and_tag_tables --fields="created_at:dateTime" ``` genera @@ -519,7 +519,7 @@ genera * - `post` * - `tag` */ -class m160328_041642_create_junction_post_and_tag extends Migration +class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migration { /** * @inheritdoc @@ -818,9 +818,9 @@ Hay varias maneras de personalizar el comando de migración. 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. +* `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. @@ -843,13 +843,13 @@ El comando de migración trae algunas opciones de línea de comandos que pueden 'drop_table' => '@yii/views/dropTableMigration.php', 'add_column' => '@yii/views/addColumnMigration.php', 'drop_column' => '@yii/views/dropColumnMigration.php', - 'create_junction' => '@yii/views/createJunctionMigration.php' + 'create_junction' => '@yii/views/createTableMigration.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. + una columna string de tamaño 12 que es not `null`. El siguiente ejemplo muestra cómo se pueden utilizar estas opciones. diff --git a/docs/guide-es/helper-array.md b/docs/guide-es/helper-array.md index 5c8fe62..e1d524d 100644 --- a/docs/guide-es/helper-array.md +++ b/docs/guide-es/helper-array.md @@ -116,7 +116,7 @@ del objeto, o una función anónima que debe devolver el valor que será utiliza 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 +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. @@ -371,8 +371,9 @@ mediante la implementación de la interfaz [[yii\base\Arrayable|Arrayable]] en e 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()]]. +Para ayudar en este tipo de pruebas, [[yii\helpers\ArrayHelper]] provee [[yii\helpers\ArrayHelper::isIn()|isIn()]] +y [[yii\helpers\ArrayHelper::isSubset()|isSubset()]] con la misma firma del método +[in_array()](http://php.net/manual/en/function.in-array.php). ```php // true diff --git a/docs/guide-es/input-multiple-models.md b/docs/guide-es/input-multiple-models.md new file mode 100644 index 0000000..366a974 --- /dev/null +++ b/docs/guide-es/input-multiple-models.md @@ -0,0 +1,85 @@ +Obtención de datos para los modelos de múltiples +================================ + +Cuando se trata de algunos datos complejos, es posible que puede que tenga que utilizar varios modelos diferentes para recopilar +la entrada del usuario. Por ejemplo, suponiendo que la información de inicio de sesión del usuario se almacena en la tabla `user`, +mientras que el perfil de usuario la información se almacena en la tabla `Profile`, es posible que desee para recoger los datos +de entrada sobre un usuario a través de un modelo `User` y un modelo `Profile`. Con el modelo de Yii y apoyo formulario, +puede solucionar este problema de una manera que no es mucho diferente de la manipulación de un solo modelo. + +En lo que sigue, vamos a mostrar cómo se puede crear un formulario que permitirá recoger datos tanto para los modelos `User` y +`Profile`. + +En primer lugar, la acción del controlador para la recogida de los datos del usuario y del perfil se puede escribir de la +siguiente manera, + +```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; + +class UserController extends Controller +{ + public function actionUpdate($id) + { + $user = User::findOne($id); + if (!$user) { + throw new NotFoundHttpException("The user was not found."); + } + + $profile = Profile::findOne($user->profile_id); + + if (!$profile) { + throw new NotFoundHttpException("The user has no profile."); + } + + $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, + ]); + } +} +``` + +En la acción `update`, primero cargamos los modelos `User` y `Profile` que se actualicen desde la base de datos. Luego llamamos +[[yii\base\Model::load()]] para llenar estos dos modelos con la entrada del usuario. Si tiene éxito, se validará +los dos modelos y guardarlos. De lo contrario vamos a renderizar la vista `update` que tiene el siguiente contenido: + +```php + 'user-update-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($user, 'username') ?> + + ...other input fields... + + field($profile, 'website') ?> + + 'btn btn-primary']) ?> + +``` + +Como se puede ver, en el `update` vista que haría que los campos de entrada utilizando dos modelos `User` y `Profile`. diff --git a/docs/guide-es/input-validation.md b/docs/guide-es/input-validation.md index c2653aa..a64abef 100644 --- a/docs/guide-es/input-validation.md +++ b/docs/guide-es/input-validation.md @@ -97,6 +97,7 @@ es un atributo activo declarado en `scenarios()` y está asociado a una o varias declaradas en `rules()`. > Note: Es práctico darle nombre a las reglas, por ej: +> > ```php > public function rules() > { @@ -175,7 +176,7 @@ La propiedad [[yii\validators\Validator::when|when]] toma un método invocable P /** * @param Model $model el modelo siendo validado * @param string $attribute al atributo siendo validado - * @return boolean si la regla debe ser aplicada o no + * @return bool si la regla debe ser aplicada o no */ function ($model, $attribute) ``` @@ -198,7 +199,7 @@ cuyo valor de retorno determina si debe aplicarse la regla o no. Por ejemplo, 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 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 @@ -222,7 +223,7 @@ si estas están vacías. Puedes hacerlo utilizando el validador [default](tutori ```php return [ - // convierte "username" y "email" en null si estos están vacíos + // convierte "username" y "email" en `null` si estos están vacíos [['username', 'email'], 'default'], // convierte "level" a 1 si está vacío @@ -230,7 +231,7 @@ return [ ]; ``` -Por defecto, una entrada se considera vacía si su valor es un string vacío, un array vacío o null. +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, @@ -241,7 +242,7 @@ con una función PHP invocable. Por ejemplo, ``` > 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. + 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. @@ -374,7 +375,7 @@ class MyForm extends Model > 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: + como `false` en las declaraciones de las reglas. Por ejemplo: > > ```php > [ @@ -409,7 +410,7 @@ class CountryValidator extends Validator } ``` -Si quieres que tu validador soporte la validación de un valor sin modelo, deberías también sobrescribir +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()`. @@ -511,9 +512,9 @@ y generará el código JavaScript apropiado para los validadores que soportan va 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 +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, +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 @@ -665,7 +666,7 @@ Puedes utilizar validación basada en AJAX en este caso. Esta lanzará una petic 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: +de ese campo como `true` y especifica un único `id` de formulario: ```php use yii\widgets\ActiveForm; @@ -682,7 +683,7 @@ ActiveForm::end(); ``` Para habiliar la validación AJAX en el formulario entero, configura [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] -como true a nivel del formulario: +como `true` a nivel del formulario: ```php $form = ActiveForm::begin([ @@ -710,5 +711,5 @@ esta petición ejecutando la validación y devolviendo los errores en formato JS > 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 +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/rest-authentication.md b/docs/guide-es/rest-authentication.md index acb8a60..eef25a9 100644 --- a/docs/guide-es/rest-authentication.md +++ b/docs/guide-es/rest-authentication.md @@ -33,7 +33,7 @@ Para activar la autenticación para tus APIs, sigue los pasos siguientes: 3. Implementa [[yii\web\IdentityInterface::findIdentityByAccessToken()]] en tu [[yii\web\User::identityClass|clase de identidad de usuarios]]. El paso 1 no es necesario pero sí recomendable para las APIs RESTful, pues son sin estado (stateless). -Cuando [[yii\web\User::enableSession|enableSession]] es false, el estado de autenticación del usuario puede NO persistir entre peticiones usando sesiones. +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 diff --git a/docs/guide-es/rest-controllers.md b/docs/guide-es/rest-controllers.md index 7b55fe9..81d6bce 100644 --- a/docs/guide-es/rest-controllers.md +++ b/docs/guide-es/rest-controllers.md @@ -135,7 +135,7 @@ sobrescribiendo el método [[yii\rest\ActiveController::checkAccess()|checkAcces * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. * * @param string $action the ID of the action to be executed - * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed. + * @param \yii\base\Model $model the model to be accessed. If `null`, it means no specific model is being accessed. * @param array $params additional parameters * @throws ForbiddenHttpException if the user does not have access */ @@ -143,6 +143,10 @@ public function checkAccess($action, $model = null, $params = []) { // check if the user can access $action and $model // throw ForbiddenHttpException if access should be denied + if ($action === 'update' || $action === 'delete') { + if ($model->author_id !== \Yii::$app->user->id) + throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s articles that you\'ve created.', $action)); + } } ``` diff --git a/docs/guide-es/rest-rate-limiting.md b/docs/guide-es/rest-rate-limiting.md index 23ca26b..fffc810 100644 --- a/docs/guide-es/rest-rate-limiting.md +++ b/docs/guide-es/rest-rate-limiting.md @@ -40,5 +40,5 @@ información sobre el límite actual de rango: * `X-Rate-Limit-Remaining`, el número de peticiones restantes en el periodo de tiempo actual * `X-Rate-Limit-Reset`, el número de segundos a esperar para pedir el máximo número de peticiones permitidas -Puedes desactivar estas cabeceras configurando [[yii\filters\RateLimiter::enableRateLimitHeaders]] a false, +Puedes desactivar estas cabeceras configurando [[yii\filters\RateLimiter::enableRateLimitHeaders]] a `false`, tal y como en el anterior ejemplo. diff --git a/docs/guide-es/rest-routing.md b/docs/guide-es/rest-routing.md index 74d9dc9..974d795 100644 --- a/docs/guide-es/rest-routing.md +++ b/docs/guide-es/rest-routing.md @@ -75,7 +75,7 @@ Por ejemplo, para soportar una nueva acción `search` por el punto final `GET / Puedes haber notado que el ID del controlador `user` aparece en formato plural `users` en los puntos finales de las URLs. Esto se debe a que [[yii\rest\UrlRule]] automáticamente pluraliza los IDs de los controladores al crear reglas URL hijas. -Puedes desactivar este comportamiento definiendo la propiedad [[yii\rest\UrlRule::pluralize]] como false. +Puedes desactivar este comportamiento definiendo la propiedad [[yii\rest\UrlRule::pluralize]] como `false`. > Info: La pluralización de los IDs de los controladores es realizada por [[yii\helpers\Inflector::pluralize()]]. Este método respeta reglas especiales de pluralización. Por ejemplo, la palabra `box` (caja) será pluralizada como `boxes` en vez de `boxs`. diff --git a/docs/guide-es/runtime-handling-errors.md b/docs/guide-es/runtime-handling-errors.md index 2571fb2..f272207 100644 --- a/docs/guide-es/runtime-handling-errors.md +++ b/docs/guide-es/runtime-handling-errors.md @@ -11,7 +11,7 @@ anteriormente. En particular, el gestor de errores de Yii hace lo siguiente para * 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 diff --git a/docs/guide-es/runtime-routing.md b/docs/guide-es/runtime-routing.md index 6c12ebd..455d93b 100644 --- a/docs/guide-es/runtime-routing.md +++ b/docs/guide-es/runtime-routing.md @@ -247,7 +247,7 @@ amigable. El resto de propiedades son opcionales. Sin embargo, la anterior confi * [[yii\web\UrlManager::showScriptName|showScriptName]]: esta propiedad determina si el script de entrada debe ser incluido en las URLs generadas. Por ejemplo, en lugar de crear una URL `/index.php/post/100`, estableciendo la - propiedad con valor true, la URL que se generará sera `/post/100`. + propiedad con valor `true`, la URL que se generará sera `/post/100`. * [[yii\web\UrlManager::enableStrictParsing|enableStrictParsing]]: esta propiedad determina si se habilita la conversión de petición estricta, si se habilita, la URL solicitada tiene que encajar al menos con uno de las [[yii\web\UrlManager::rules|rules]] para poder ser tratada como una petición valida, o se lanzará una @@ -351,8 +351,8 @@ Cuando se usen las reglas para convertir URLs: - `/index.php/post/100` se convierte en la ruta `post/view` y el parámetro `id` cuyo valor es 100 usando la segunda regla; - `/index.php/posts/php` provocara una [[yii\web\NotFoundHttpException]] cuando - [[yii\web\UrlManager::enableStrictParsing]] sea true, ya que no coincide ninguno de los parámetros . Si - [[yii\web\UrlManager::enableStrictParsing]] es false (valor predeterminado), se devolverá como ruta la parte de + [[yii\web\UrlManager::enableStrictParsing]] sea `true`, ya que no coincide ninguno de los parámetros . Si + [[yii\web\UrlManager::enableStrictParsing]] es `false` (valor predeterminado), se devolverá como ruta la parte de información `posts/php`. Y cuando las se usen las reglas para crear URLs: diff --git a/docs/guide-es/runtime-sessions-cookies.md b/docs/guide-es/runtime-sessions-cookies.md index 0db302b..1995aee 100644 --- a/docs/guide-es/runtime-sessions-cookies.md +++ b/docs/guide-es/runtime-sessions-cookies.md @@ -179,7 +179,7 @@ $session->setFlash('postDeleted', 'You have successfully deleted your post.'); echo $session->getFlash('postDeleted'); // Petición #3 -// $result será false ya que el mensaje flash ha sido borrado automáticamente +// $result será `false` ya que el mensaje flash ha sido borrado automáticamente $result = $session->hasFlash('postDeleted'); ``` @@ -262,7 +262,7 @@ 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. -> 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. +> 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 @@ -271,7 +271,7 @@ Cuando estás leyendo y enviando cookies a través de los componentes `request` > 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. +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. > Note: Las cookies que son directamente leídas/enviadas vía `$_COOKIE` y `setcookie()` no serán validadas. diff --git a/docs/guide-es/security-authorization.md b/docs/guide-es/security-authorization.md index 00ed610..11d01a9 100644 --- a/docs/guide-es/security-authorization.md +++ b/docs/guide-es/security-authorization.md @@ -8,9 +8,9 @@ dos métodos de autorización: Filtro de Control de Acceso y Control Basado en R ## 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 +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]] +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`: @@ -48,7 +48,7 @@ class SiteController extends Controller 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 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` @@ -57,7 +57,7 @@ las [[yii\filters\AccessRule|reglas de acceso]], y se lee como a continuación: 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 +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. @@ -97,7 +97,7 @@ La comparación es sensible a mayúsculas. Si la opción está vacía o no defin - `?`: 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 + 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. @@ -231,7 +231,7 @@ return [ 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: +`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". @@ -304,11 +304,11 @@ class RbacController extends Controller ``` > 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`. + 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") +![Simple RBAC hierarchy](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". @@ -362,10 +362,10 @@ class AuthorRule extends Rule public $name = 'isAuthor'; /** - * @param string|integer $user el ID de usuario. + * @param string|int $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. + * @return bool un valor indicando si la regla permite al rol o permiso con el que está asociado. */ public function execute($user, $item, $params) { @@ -399,7 +399,7 @@ $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") +![RBAC hierarchy with a rule](images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") ### Comprobación de Acceso @@ -416,7 +416,7 @@ if (\Yii::$app->user->can('createPost')) { 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") +![Access check](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: @@ -429,7 +429,7 @@ if (\Yii::$app->user->can('updatePost', ['post' => $post])) { Aquí es lo que sucede si el usuario actual es John: -![Access check](../guide/images/rbac-access-check-2.png "Access check") +![Access check](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 @@ -437,7 +437,7 @@ debe devolver `true` desde su método `execute()`. El método recive `$params` d 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") +![Access check](images/rbac-access-check-3.png "Access check") ### Utilizar Roles por Defecto @@ -503,7 +503,7 @@ $auth->addChild($admin, $author); 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 +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]]: @@ -522,6 +522,6 @@ return [ ``` 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. +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/start-installation.md b/docs/guide-es/start-installation.md index 00799b0..48b4741 100644 --- a/docs/guide-es/start-installation.md +++ b/docs/guide-es/start-installation.md @@ -40,7 +40,7 @@ ejecutando el comando `composer self-update` Teniendo Composer instalado, puedes instalar Yii ejecutando los siguientes comandos en un directorio accesible vía Web: ```bash -composer global require "fxp/composer-asset-plugin:~1.1.1" +composer global require "fxp/composer-asset-plugin:^1.2.0" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-es/structure-applications.md b/docs/guide-es/structure-applications.md index 92e567f..2f5babf 100644 --- a/docs/guide-es/structure-applications.md +++ b/docs/guide-es/structure-applications.md @@ -215,12 +215,10 @@ para controladores específicos. En el siguiente ejemplo, `account` será mapead ```php [ 'controllerMap' => [ - [ - 'account' => 'app\controllers\UserController', - 'article' => [ - 'class' => 'app\controllers\PostController', - 'enableCsrfValidation' => false, - ], + 'account' => 'app\controllers\UserController', + 'article' => [ + 'class' => 'app\controllers\PostController', + 'enableCsrfValidation' => false, ], ], ] diff --git a/docs/guide-es/structure-extensions.md b/docs/guide-es/structure-extensions.md index d573c4b..e8d0b06 100644 --- a/docs/guide-es/structure-extensions.md +++ b/docs/guide-es/structure-extensions.md @@ -43,7 +43,7 @@ a continuación: "require": { // ... otras dependencias - "yiisoft/yii2-imagine": "*" + "yiisoft/yii2-imagine": "~2.0.0" } } ``` @@ -149,7 +149,7 @@ muestra el archivo `composer.json` para la extensión `yiisoft/yii2-imagine`: // dependencias del paquete "require": { - "yiisoft/yii2": "*", + "yiisoft/yii2": "~2.0.0", "imagine/imagine": "v0.5.0" }, @@ -420,6 +420,8 @@ se describe en la subsección [Uso de Extensiones](#using-extensions) [Faker](https://github.com/fzaninotto/Faker) y generar datos automáticamente. - [yiisoft/yii2-gii](https://github.com/yiisoft/yii2-gii): proporciona un generador de código basado den Web altamente extensible y que puede usarse para generar modelos, formularios, módulos, CRUD, etc. rápidamente. +- [yiisoft/yii2-httpclient](https://github.com/yiisoft/yii2-httpclient): + provides an HTTP client. - [yiisoft/yii2-imagine](https://github.com/yiisoft/yii2-imagine): proporciona funciones comunes de manipulación de imágenes basadas en [Imagine](http://imagine.readthedocs.org/). - [yiisoft/yii2-jui](https://github.com/yiisoft/yii2-jui): proporciona un conjunto de widgets que encapsulan las diff --git a/docs/guide-es/structure-models.md b/docs/guide-es/structure-models.md index b0cc3b0..49edd15 100644 --- a/docs/guide-es/structure-models.md +++ b/docs/guide-es/structure-models.md @@ -234,7 +234,7 @@ para ayudar al usuario a corregir estos errores. Se puede llamar a [[yii\base\Model::validate()]] para validar los datos recibidos. El método se usará para validar las reglas declaradas en [[yii\base\Model::rules()]] para validar cada atributo relevante. Si no se encuentran errores, se -devolverá true. De otro modo, este almacenará los errores en la propiedad [[yii\base\Model::errors]] y devolverá falso. +devolverá `true`. De otro modo, este almacenará los errores en la propiedad [[yii\base\Model::errors]] y devolverá falso. Por ejemplo: ```php diff --git a/docs/guide-es/test-fixtures.md b/docs/guide-es/test-fixtures.md index 445617d..238f636 100644 --- a/docs/guide-es/test-fixtures.md +++ b/docs/guide-es/test-fixtures.md @@ -310,7 +310,7 @@ yii fixture/load User yii fixture User // carga varios fixtures -yii fixture User UserProfile +yii fixture "User, UserProfile" // carga todos los fixtures yii fixture/load "*" @@ -319,7 +319,7 @@ yii fixture/load "*" yii fixture "*" // carga todos los fixtures excepto uno -yii fixture "*" -DoNotLoadThisOne +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' @@ -340,13 +340,13 @@ Para descargar un fixture, ejecuta el siguiente comando: yii fixture/unload User // descarga varios fixtures -yii fixture/unload User,UserProfile +yii fixture/unload "User, UserProfile" // descarga todos los fixtures yii fixture/unload "*" // descarga todos los fixtures excepto uno -yii fixture/unload "*" -DoNotUnloadThisOne +yii fixture/unload "*, -DoNotUnloadThisOne" ``` diff --git a/docs/guide-es/translators.json b/docs/guide-es/translators.json new file mode 100644 index 0000000..e5431ad --- /dev/null +++ b/docs/guide-es/translators.json @@ -0,0 +1,6 @@ +[ + "Antonio Ramirez", + "Daniel Gómez Pan", + "'larnu'", + "Luciano Baraglia" +] \ No newline at end of file diff --git a/docs/guide-es/tutorial-core-validators.md b/docs/guide-es/tutorial-core-validators.md index d56325b..4a5fced 100644 --- a/docs/guide-es/tutorial-core-validators.md +++ b/docs/guide-es/tutorial-core-validators.md @@ -25,19 +25,19 @@ A continuación, vamos a describir el uso principal y las propiedades de cada va // comprueba si "selected" es 0 o 1, sin mirar el tipo de dato ['selected', 'boolean'], - // comprueba si "deleted" es del tipo booleano, alguno entre true o false + // comprueba si "deleted" es del tipo booleano, alguno entre `true` o `false` ['deleted', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true], ] ``` Este validador comprueba si el valor de la entrada (input) es booleano. -- `trueValue`: El valor representando *true*. Valor por defecto a `'1'`. -- `falseValue`: El valor representando *false*. Valor por defecto a `'0'`. +- `trueValue`: El valor representando `true`. Valor por defecto a `'1'`. +- `falseValue`: El valor representando `false`. Valor por defecto a `'0'`. - `strict`: Si el tipo del valor de la entrada (input) debe corresponder con `trueValue` y `falseValue`. Valor por defecto 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. +> 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]] @@ -50,10 +50,10 @@ Este validador comprueba si el valor de la entrada (input) es booleano. Este validador es usualmente usado junto con [[yii\captcha\CaptchaAction]] y [[yii\captcha\Captcha]] para asegurarse que una entrada es la misma que lo es el código de verificación que enseña el widget [[yii\captcha\Captcha|CAPTCHA]]. -- `caseSensitive`: cuando la comparación del código de verificación depende de que sean mayúsculas y minúsculas (case sensitive). Por defecto a false. +- `caseSensitive`: cuando la comparación del código de verificación depende de que sean mayúsculas y minúsculas (case sensitive). Por defecto a `false`. - `captchaAction`: la [ruta](structure-controllers.md#routes) correspondiente a [[yii\captcha\CaptchaAction|CAPTCHA action]] que representa (render) la imagen CAPTCHA. Por defecto`'site/captcha'`. -- `skipOnEmpty`: cuando la validación puede saltarse si la entrada está vacía. Por defecto a false, lo caul permite que la entrada sea necesaria (required). +- `skipOnEmpty`: cuando la validación puede saltarse si la entrada está vacía. Por defecto a `false`, lo caul permite que la entrada sea necesaria (required). ## [[yii\validators\CompareValidator|compare]] @@ -158,12 +158,12 @@ Esta validador comprueba si el valor de entrada es un número de tipo doble. Es Este validador comprueba si el valor de entrada es una dirección válida de email. -- `allowName`: indica cuando permitir el nombre en la dirección de email (p.e. `John Smith `). Por defecto a false. +- `allowName`: indica cuando permitir el nombre en la dirección de email (p.e. `John Smith `). Por defecto a `false`. - `checkDNS`, comprobar cuando el dominio del email existe y tiene cualquier registro A o MX. Es necesario ser consciente que esta comprobación puede fallar debido a problemas temporales de DNS, incluso si el la dirección es válida actualmente. - Por defecto a false. + Por defecto a `false`. - `enableIDN`, indica cuando el proceso de validación debe tener en cuenta el informe de IDN (internationalized domain names). - Por defecto a false. Dese cuenta que para poder usar la validación de IDN has de instalar y activar la extensión de PHP `intl`, o será lanzada una excepción. + Por defecto a `false`. Dese cuenta que para poder usar la validación de IDN has de instalar y activar la extensión de PHP `intl`, o será lanzada una excepción. ## [[yii\validators\ExistValidator|exist]] @@ -197,7 +197,7 @@ Este validador comprueba si el valor de entrada puede ser encontrado en una colu Puede usar una array para validar la existencia de múltiples columnas al mismo tiempo. El array de valores son los atributos que pueden ser usados para validar la existencia, mientras que las claves del array son los atributos a ser validados. Si la clave y el valor son los mismos, solo en ese momento puedes especificar el valor. - `filter`: filtro adicional a aplicar a la consulta de la base de datos usado para comprobar la existencia de una valor de entrada. Esto puede ser una cadena o un array representando la condición de la consulta (referirse a [[yii\db\Query::where()]] sobre el formato de la condición de consulta), o una función anónima con la signatura `function ($query)`, donde `$query` es el objeto [[yii\db\Query|Query]] que puedes modificar en la función. -- `allowArray`: indica cuando permitir que el valor de entrada sea un array. Por defecto a false.Si la propiedad es true y la entrada es un array, cada elemento del array debe existir en la columna destino. Nota que esta propiedad no puede ser true si estás validando, por el contrario, múltiple columnas poniendo el valor del atributo `targetAttribute` como que es un array. +- `allowArray`: indica cuando permitir que el valor de entrada sea un array. Por defecto a `false`.Si la propiedad es `true` y la entrada es un array, cada elemento del array debe existir en la columna destino. Nota que esta propiedad no puede ser `true` si estás validando, por el contrario, múltiple columnas poniendo el valor del atributo `targetAttribute` como que es un array. ## [[yii\validators\FileValidator|file]] @@ -213,13 +213,13 @@ Este validador comprueba si el valor de entrada puede ser encontrado en una colu Este validador comprueba que el fichero subido es el adecuado. - `extensions`: una lista de extensiones de ficheros que pueden ser subidos. Esto puede ser tanto un array o una cadena conteniendo nombres de extensiones de ficheros separados por un espacio o coma (p.e. "gif, jpg"). - Los nombres de las extensiones no diferencian mayúsculas de minúsculas (case-insensitive). Por defecto a null, permitiendo todas los nombres de extensiones de fichero. + Los nombres de las extensiones no diferencian mayúsculas de minúsculas (case-insensitive). Por defecto a `null`, permitiendo todas los nombres de extensiones de fichero. - `mimeTypes`: una lista de tipos de ficheros MIME que están permitidos subir. Esto puede ser tanto un array como una cadena conteniendo tipos de fichero MIME separados por un espacio o una coma (p.e. "image/jpeg, image/png"). - Los tipos Mime no diferencian mayúsculas de minúsculas (case-insensitive). Por defecto a null, permitiendo todos los tipos MIME. -- `minSize`: el número de bytes mínimo requerido para el fichero subido. El tamaño del fichero ha de ser superior a este valor. Por defecto a null, lo que significa sin límite inferior. -- `maxSize`: El número máximo de bytes del fichero a subir. El tamaño del fichero ha de ser inferior a este valor. Por defecto a null, significando no tener límite superior. + Los tipos Mime no diferencian mayúsculas de minúsculas (case-insensitive). Por defecto a `null`, permitiendo todos los tipos MIME. +- `minSize`: el número de bytes mínimo requerido para el fichero subido. El tamaño del fichero ha de ser superior a este valor. Por defecto a `null`, lo que significa sin límite inferior. +- `maxSize`: El número máximo de bytes del fichero a subir. El tamaño del fichero ha de ser inferior a este valor. Por defecto a `null`, significando no tener límite superior. - `maxFiles`: el máximo número de ficheros que determinado atributo puede manejar. Por defecto a 1, lo que significa que la entrada debe de ser sólo un fichero. Si es mayor que 1, entonces la entrada tiene que ser un array conteniendo como máximo el número `maxFiles` de elementos que representan los ficheros a subir. -- `checkExtensionByMimeType`: cuando comprobar la extensión del fichero por el tipo MIME. Si la extensión producida por la comprobación del tipo MIME difiere la extensión del fichero subido, el fichero será considerado como no válido. Por defecto a true, significando que realiza este tipo de comprobación. +- `checkExtensionByMimeType`: cuando comprobar la extensión del fichero por el tipo MIME. Si la extensión producida por la comprobación del tipo MIME difiere la extensión del fichero subido, el fichero será considerado como no válido. Por defecto a `true`, significando que realiza este tipo de comprobación. `FileValidator` es usado con [[yii\web\UploadedFile]]. Por favor, refiérase a la sección [Subida de ficheros](input-file-upload.md) para una completa cobertura sobre la subida de ficheros y llevar a cabo la validación de los ficheros subidos. @@ -243,8 +243,8 @@ Este validador no valida datos. En su lugar, aplica un filtro sobre el valor de - `filter`: una retrollamada (callback) de PHP que define un filtro. Tiene que ser un nombre de función global, una función anónima, etc. La forma de la función ha de ser `function ($value) { return $newValue; }`. Tiene que contener un valor esta propiedad. -- `skipOnArray`: cuando evitar el filtro si el valor de la entrada es un array. Por defecto a false. - A tener en cuenta que si el filtro no puede manejar una entrada de un array, debes poner esta propiedad a true. En otro caso algún error PHP puede ocurrir. +- `skipOnArray`: cuando evitar el filtro si el valor de la entrada es un array. Por defecto a `false`. + A tener en cuenta que si el filtro no puede manejar una entrada de un array, debes poner esta propiedad a `true`. En otro caso algún error PHP puede ocurrir. > Consejo (Tip): Si quieres recortar los valores de entrada, puedes usar directamente el validador [Recorte (trim)](#trim). @@ -263,10 +263,10 @@ Este validador no valida datos. En su lugar, aplica un filtro sobre el valor de Este validador comprueba si el valor de entrada representa un fichero de imagen válido. Extiende al validador [Fichero (file)](#file) y, por lo tanto, hereda todas sus propiedades. Además, soporta las siguientes propiedades adicionales específicas para la validación de imágenes: -- `minWidth`: el mínimo ancho de la imagen. Por defecto a null, indicando que no hay límite inferior. -- `maxWidth`: el máximo ancho de la imagen. Por defecto a null, indicando que no hay límite superior. -- `minHeight`: el mínimo alto de la imagen. Por defecto a null, indicando que no hay límite inferior. -- `maxHeight`: el máximo alto de la imagen. Por defecto a null, indicando que no hay límite superior. +- `minWidth`: el mínimo ancho de la imagen. Por defecto a `null`, indicando que no hay límite inferior. +- `maxWidth`: el máximo ancho de la imagen. Por defecto a `null`, indicando que no hay límite superior. +- `minHeight`: el mínimo alto de la imagen. Por defecto a `null`, indicando que no hay límite inferior. +- `maxHeight`: el máximo alto de la imagen. Por defecto a `null`, indicando que no hay límite superior. ## [[yii\validators\RangeValidator|in]] @@ -281,9 +281,9 @@ Este validador comprueba si el valor de entrada representa un fichero de imagen Este validador comprueba si el valor de entrada puede encontrarse entre determinada lista de valores. - `range`: una lista de determinados valores dentro de los cuales el valor de entrada debe de ser mirado. -- `strict`: cuando la comparación entre el valor de entrada y los valores determinados debe de ser estricta (ambos el tipo y el valor han de ser iguales). Por defecto a false. -- `not`: cuando el resultado de la validación debe de ser invertido. Por defecto a false. Cuando esta propiedad está a true, el validador comprueba que el valor de entrada NO ESTÁ en la determinada lista de valores. -- `allowArray`: si se permite que el valor de entrada sea un array. Cuando es true y el valor de entrada es un array, cada elemento en el array debe de ser encontrado en la lista de valores determinada,o la validación fallará. +- `strict`: cuando la comparación entre el valor de entrada y los valores determinados debe de ser estricta (ambos el tipo y el valor han de ser iguales). Por defecto a `false`. +- `not`: cuando el resultado de la validación debe de ser invertido. Por defecto a `false`. Cuando esta propiedad está a `true`, el validador comprueba que el valor de entrada NO ESTÁ en la determinada lista de valores. +- `allowArray`: si se permite que el valor de entrada sea un array. Cuando es `true` y el valor de entrada es un array, cada elemento en el array debe de ser encontrado en la lista de valores determinada,o la validación fallará. ## [[yii\validators\NumberValidator|integer]] @@ -313,7 +313,7 @@ Esta validador comprueba si el valor de entrada es un entero. Este validador comprueba si el valor de entrada coincide con la expresión regular especificada. - `pattern`: la expresión regular conla que el valor de entrada debe coincidir. Esta propiedad no puede estar vacía, o se lanzará una excepción. -- `not`: indica cuando invertir el resultado de la validación. Por defecto a false, significando que la validación es exitosa solamente si el valor de entrada coincide con el patrón. Si esta propiedad está a true, la validación es exitosa solamente si el valor de entrada NO coincide con el patrón. +- `not`: indica cuando invertir el resultado de la validación. Por defecto a `false`, significando que la validación es exitosa solamente si el valor de entrada coincide con el patrón. Si esta propiedad está a `true`, la validación es exitosa solamente si el valor de entrada NO coincide con el patrón. ## [[yii\validators\NumberValidator|number]] @@ -343,9 +343,9 @@ Este validador comprueba si el valor de entrada es un número. Es equivalente al El validador comprueba si el valor de entrada es provisto y no está vacío. - `requiredValue`: el valor deseado que la entrada debería tener. Si no tiene valor, significa que la entrada no puede estar vacía. -- `strict`: indica como comprobar los tipos de los datos al validar un valor. Por defecto a false. - Cuando `requiredValue` no tiene valor, si esta propiedad es true, el validador comprueba si el valor de entrada no es estrictamente null; si la propiedad es false, el validador puede usar una regla suelta para determinar si el valor está vacío o no. - Cuando `requiredValue` tiene valor, la comparación entre la entrada y `requiredValue` comprobará tambien los tipos de los datos si esta propiedad es true. +- `strict`: indica como comprobar los tipos de los datos al validar un valor. Por defecto a `false`. + Cuando `requiredValue` no tiene valor, si esta propiedad es `true`, el validador comprueba si el valor de entrada no es estrictamente `null`; si la propiedad es `false`, el validador puede usar una regla suelta para determinar si el valor está vacío o no. + Cuando `requiredValue` tiene valor, la comparación entre la entrada y `requiredValue` comprobará tambien los tipos de los datos si esta propiedad es `true`. > Info: Como determinar si un valor está vacío o no es un tópico separado cubierto en la sección [Valores vacíos](input-validation.md#handling-empty-inputs). @@ -439,7 +439,7 @@ Este validador comprueba si el valor de entrada es una URL válida. - `validSchemes`: un array especificando el esquema URI que debe ser considerado válido. Por defecto contiene `['http', 'https']`, significando que ambas URLS `http` y `https` son consideradas válidas. - `defaultScheme`: el esquema de URI a poner como prefijo a la entrada si no tiene la parte del esquema. - Por defecto a null, significando que no modifica el valor de entrada. + Por defecto a `null`, significando que no modifica el valor de entrada. - `enableIDN`: Si el validador debe formar parte del registro IDN (internationalized domain names). - Por defecto a false. Nota que para usar la validación IDN tienes que instalar y activar la extensión PHP `intl`, en otro caso una excepción será lanzada. + Por defecto a `false`. Nota que para usar la validación IDN tienes que instalar y activar la extensión PHP `intl`, en otro caso una excepción será lanzada. diff --git a/docs/guide-es/tutorial-start-from-scratch.md b/docs/guide-es/tutorial-start-from-scratch.md index cd810ff..616d755 100644 --- a/docs/guide-es/tutorial-start-from-scratch.md +++ b/docs/guide-es/tutorial-start-from-scratch.md @@ -50,6 +50,6 @@ 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 global require "fxp/composer-asset-plugin:^1.2.0" 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 index addcf8f..931babc 100644 --- a/docs/guide-es/tutorial-template-engines.md +++ b/docs/guide-es/tutorial-template-engines.md @@ -38,8 +38,8 @@ En el código de arriba, tanto Smarty como Twig son configurados para ser utiliz tu archivo `composer.json` para incluirlos: ``` -"yiisoft/yii2-smarty": "*", -"yiisoft/yii2-twig": "*", +"yiisoft/yii2-smarty": "~2.0.0", +"yiisoft/yii2-twig": "~2.0.0", ``` 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. diff --git a/docs/guide-es/tutorial-yii-integration.md b/docs/guide-es/tutorial-yii-integration.md index 20739f1..f7787ba 100644 --- a/docs/guide-es/tutorial-yii-integration.md +++ b/docs/guide-es/tutorial-yii-integration.md @@ -82,7 +82,7 @@ instalar Yii , e iniciar Yii. Si el sistema de terceros usa Composer para manejar sus dependencias, simplemente ejecuta estos comandos para instalar Yii: - composer global require "fxp/composer-asset-plugin:~1.1.1" + composer global require "fxp/composer-asset-plugin:^1.2.0" composer require yiisoft/yii2 composer install diff --git a/docs/guide-fr/blocktypes.json b/docs/guide-fr/blocktypes.json new file mode 100644 index 0000000..5937635 --- /dev/null +++ b/docs/guide-fr/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Attention :", + "Note:": "Note :", + "Info:": "Info :", + "Tip:": "Conseil :" +} diff --git a/docs/guide-fr/caching-data.md b/docs/guide-fr/caching-data.md new file mode 100644 index 0000000..6302394 --- /dev/null +++ b/docs/guide-fr/caching-data.md @@ -0,0 +1,345 @@ +Mise en cache de données +======================== + +La mise en cache de données consiste à stocker quelques variables PHP dans un cache et à les y retrouver plus tard. +C'est également la base pour des fonctionnalités de mise en cache plus avancées, comme la [mise en cache de requêtes](#query-caching) et la [mise en cache de pages](caching-page.md). + +Le code qui suit est un modèle d'utilisation typique de la mise en cache de données, dans lequel `cache` fait référence à un [composant de mise en cache](#cache-components) : + + +```php +// tente de retrouver la donnée $data dans le cache +$data = $cache->get($key); + +if ($data === false) { + // la donnée $data n'a pas été trouvée dans le cache, on la recalcule + $data = $this->calculateSomething(); + + // stocke la donnée $data dans le cache de façon à la retrouver la prochaine fois + $cache->set($key, $data); +} + +// la donnée $data est disponible ici +``` + +Depuis la version 2.0.11, le [composant de mise en cache](#cache-components) fournit la méthode [[yii\caching\Cache::getOrSet()|getOrSet()]] qui simplifie le code pour l'obtention, le calcul et le stockage des données. Le code qui suit fait exactement la même chose que l'exemple précédent : + +```php +$data = $cache->getOrSet($key, function () { + return $this->calculateSomething(); +}); +``` + +Lorsque le cache possède une donnée associée à la clé `$key`, la valeur en cache est retournée. Autrement, la fonction anonyme passée est exécutée pour calculer cette valeur qui est mise en cache et retournée. + +Si la fonction anonyme a besoin de quelques données en dehors de la portée courante, vous pouvez les passer en utilisant l'instruction `use`. Par exemple : + +```php +$user_id = 42; +$data = $cache->getOrSet($key, function () use ($user_id) { + return $this->calculateSomething($user_id); +}); +``` + +> Note : la méthode [[yii\caching\Cache::getOrSet()|getOrSet()]] prend également en charge la durée et les dépendances. + Reportez-vous à [Expiration de la mise en cache](#cache-expiration) et à [Dépendances de mise en cache](#cache-dependencies) pour en savoir plus. + + +## Composants de mise en cache + +La mise en cache s'appuie sur ce qu'on appelle les *composants de mise en cache* qui représentent des supports de mise en cache tels que les mémoires, les fichiers et les bases de données. + +Les composants de mise en cache sont généralement enregistrés en tant que [composants d'application](structure-application-components.md) de façon à ce qu'ils puissent être configurables et accessibles globalement. Le code qui suit montre comment configurer le composant d'application `cache` pour qu'il utilise [memcached](http://memcached.org/) avec deux serveurs de cache : + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\MemCache', + 'servers' => [ + [ + 'host' => 'server1', + 'port' => 11211, + 'weight' => 100, + ], + [ + 'host' => 'server2', + 'port' => 11211, + 'weight' => 50, + ], + ], + ], +], +``` + +Vous pouvez accéder au composant de mise en cache configuré ci-dessus en utilisant l'expression `Yii::$app->cache`. + +Comme tous les composants de mise en cache prennent en charge le même jeux d'API, vous pouvez remplacer le composant de mise en cache sous-jacent par un autre en le reconfigurant dans la configuration de l'application, cela sans modifier le code qui utilise le cache. Par exemple, vous pouvez modifier le code ci-dessus pour utiliser [[yii\caching\ApcCache|APC cache]] : + + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\ApcCache', + ], +], +``` + +> Tip: vous pouvez enregistrer de multiples composants d'application de mise en cache. Le composant nommé `cache` est utilisé par défaut par de nombreuses classes dépendantes d'un cache (p. ex.[[yii\web\UrlManager]]). + + +### Supports de stockage pour cache pris en charge + +Yii prend en charge un large panel de supports de stockage pour cache. Ce qui suit est un résumé : + +* [[yii\caching\ApcCache]]: utilise l'extension PHP [APC](http://php.net/manual/en/book.apc.php). Cette option peut être considérée comme la plus rapide lorsqu'on utilise un cache pour une grosse application centralisée (p. ex. un serveur, pas d'équilibrage de charge dédié, etc.). +* [[yii\caching\DbCache]]: utilise une table de base de données pour stocker les données en cache. Pour utiliser ce cache, vous devez créer une table comme spécifié dans [[yii\caching\DbCache::cacheTable]]. +* [[yii\caching\DummyCache]]: tient lieu de cache à remplacer qui n'assure pas de mise en cache réelle. Le but de ce composant est de simplifier le code qui a besoin de vérifier la disponibilité du cache. Par exemple, lors du développement ou si le serveur ne dispose pas de la prise en charge d'un cache, vous pouvez configurer un composant de mise en cache pour qu'il utilise ce cache. Lorsque la prise en charge réelle de la mise en cache est activée, vous pouvez basculer sur le composant de mise en cache correspondant. Dans les deux cas, vous pouvez utiliser le même code `Yii::$app->cache->get($key)` pour essayer de retrouver les données du cache sans vous préoccuper du fait que `Yii::$app->cache` puisse être `null`. +* [[yii\caching\FileCache]]: utilise des fichiers standards pour stocker les données en cache. Cela est particulièrement adapté à la mise en cache de gros blocs de données, comme le contenu d'une page. +* [[yii\caching\MemCache]]: utilise le [memcache](http://php.net/manual/en/book.memcache.php) PHP et l'extension [memcached](http://php.net/manual/en/book.memcached.php). Cette option peut être considérée comme la plus rapide lorsqu'on utilise un cache dans des applications distribuées (p. ex. avec plusieurs serveurs, l'équilibrage de charge, etc.). +* [[yii\redis\Cache]]: met en œuvre un composant de mise en cache basé sur un stockage clé-valeur [Redis](http://redis.io/) + (une version de redis égale ou supérieure à 2.6.12 est nécessaire). +* [[yii\caching\WinCache]]: utilise le [WinCache](http://iis.net/downloads/microsoft/wincache-extension) PHP + ([voir aussi l'extension](http://php.net/manual/en/book.wincache.php)). +* [[yii\caching\XCache]]: utilise l'extension PHP [XCache](http://xcache.lighttpd.net/). +* [[yii\caching\ZendDataCache]]: utilise le + [cache de données Zend](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) + en tant que médium de cache sous-jacent. + + +> Tip: vous pouvez utiliser différents supports de stockage pour cache dans la même application. Une stratégie courante est d'utiliser un support de stockage pour cache basé sur la mémoire pour stocker des données de petite taille mais d'usage constant (p. ex. des données statistiques), et d'utiliser des supports de stockage pour cache basés sur des fichiers ou des bases de données pour stocker des données volumineuses et utilisées moins souvent (p. ex. des contenus de pages). + + +## Les API Cache + +Tous les composants de mise en cache dérivent de la même classe de base [[yii\caching\Cache]] et par conséquent prennent en charge les API suivantes : + +* [[yii\caching\Cache::get()|get()]]: retrouve une donnée dans le cache identifiée par une clé spécifiée. Une valeur `false` (faux) est retournée si la donnée n'est pas trouvée dans le cache ou si elle a expiré ou été invalidée. +* [[yii\caching\Cache::set()|set()]]: stocke une donnée sous une clé dans le cache. +* [[yii\caching\Cache::add()|add()]]: stocke une donnée identifiée par une clé dans le cache si la clé n'existe pas déjà dans le cache. +* [[yii\caching\Cache::getOrSet()|getOrSet()]]: retrouve une donnée dans le cache identifiée par une clé spécifiée ou exécute la fonction de rappel passée, stocke la valeur retournée par cette fonction dans le cache sous cette clé et retourne la donnée. +* [[yii\caching\Cache::multiGet()|multiGet()]]: retrouve de multiples données dans le cache identifiées par les clés spécifiées. +* [[yii\caching\Cache::multiSet()|multiSet()]]: stocke de multiples données dans le cache. Chaque donnée est identifiée par une clé. +* [[yii\caching\Cache::multiAdd()|multiAdd()]]: stocke de multiples données dans le cache. Chaque donnée est identifiée par une clé. Si la clé existe déjà dans le cache, la donnée est ignorée. +* [[yii\caching\Cache::exists()|exists()]]: retourne une valeur indiquant si la clé spécifiée existe dans le cache. +* [[yii\caching\Cache::delete()|delete()]]: retire du cache une donnée identifiée par une clé. +* [[yii\caching\Cache::flush()|flush()]]: retire toutes les données du cache. + +> Note : ne mettez pas directement en cache une valeur booléenne `false` parce que la méthode [[yii\caching\Cache::get()|get()]] utilise la valeur `false` pour indiquer que la donnée n'a pas été trouvée dans le cache. Au lieu de cela, vous pouvez placer cette donnée dans un tableau et mettre ce tableau en cache pour éviter le problème. + +Quelques supports de cache, tels que MemCache, APC, prennent en charge la récupération de multiples valeurs en cache en mode « batch » (lot), ce qui réduit la surcharge occasionnée par la récupération des données en cache. Les API [[yii\caching\Cache::multiGet()|multiGet()]] et [[yii\caching\Cache::multiAdd()|multiAdd()]] sont fournies pour exploiter cette fonctionnalité. Dans le cas où le support de cache sous-jacent ne prend pas en charge cette fonctionnalité, elle est simulée. +Comme [[yii\caching\Cache]] implémente `ArrayAccess`, un composant de mise en cache peut être utilisé comme un tableau. En voici quelques exemples : + +```php +$cache['var1'] = $value1; // équivalent à : $cache->set('var1', $value1); +$value2 = $cache['var2']; // équivalent à : $value2 = $cache->get('var2'); +``` + + +### Clés de cache + +Chacune des données stockée dans le cache est identifiée de manière unique par une clé. Lorsque vous stockez une donnée dans le cache, vous devez spécifier une clé qui lui est attribuée. Plus tard, pour récupérer la donnée, vous devez fournir cette clé. + +Vous pouvez utiliser une chaîne de caractères ou une valeur arbitraire en tant que clé de cache. Lorsqu'il ne s'agit pas d'une chaîne de caractères, elle est automatiquement sérialisée sous forme de chaîne de caractères. + +Une stratégie courante pour définir une clé de cache consiste à inclure tous les facteurs déterminants sous forme de tableau. Par exemple,[[yii\db\Schema]] utilise la clé suivante par mettre en cache les informations de schéma d'une table de base de données : + +```php +[ + __CLASS__, // schema class name + $this->db->dsn, // DB connection data source name + $this->db->username, // DB connection login user + $name, // table name +]; +``` + +Comme vous le constatez, la clé inclut toutes les informations nécessaires pour spécifier de manière unique une table de base de données. + +> Note : les valeurs stockées dans le cache via [[yii\caching\Cache::multiSet()|multiSet()]] ou [[yii\caching\Cache::multiAdd()|multiAdd()]] peuvent n'avoir que des clés sous forme de chaînes de caractères ou de nombres entiers. Si vous avez besoin de définir des clés plus complexes, stockez la valeur séparément via [[yii\caching\Cache::set()|set()]] ou [[yii\caching\Cache::add()|add()]]. + +Lorsque le même support de cache est utilisé par différentes applications, vous devriez spécifier un préfixe de clé de cache pour chacune des applications afin d'éviter les conflits de clés de cache. Cela peut être fait en configurant la propriété [[yii\caching\Cache::keyPrefix]]. Par exemple, dans la configuration de l'application vous pouvez entrer le code suivant : + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\ApcCache', + 'keyPrefix' => 'myapp', // a unique cache key prefix + ], +], +``` + +Pour garantir l'interopérabilité, vous ne devez utiliser que des caractères alpha-numériques. + + +### Expiration de la mise en cache + +Une donnée stockée dans le cache y restera à jamais sauf si elle en est retirée par l'application d'une quelconque politique de mise en cache (p. ex. l'espace de mise en cache est plein et les données les plus anciennes sont retirées). Pour modifier ce comportement, vous pouvez fournir un paramètre d'expiration lors de l'appel de la fonction [[yii\caching\Cache::set()|set()]] pour stocker une donnée. Le paramètre indique pour combien de secondes la donnée restera valide dans le cache. Lorsque vous appelez la fonction [[yii\caching\Cache::get()|get()]] pour récupérer une donnée, si cette dernière a expiré, la méthode retourne `false`, pour indiquer que la donnée n'a pas été trouvée dans le cache. Par exemple, + +```php +// conserve la donnée dans le cache pour un maximum de 45 secondes +$cache->set($key, $data, 45); + +sleep(50); + +$data = $cache->get($key); +if ($data === false) { + // $data a expiré ou n'a pas été trouvée dans le cache +} +``` + +Depuis la version 2.0.11, vous pouvez définir une valeur [[yii\caching\Cache::$defaultDuration|defaultDuration]] dans la configuration de votre composant de mise en cache si vous préférez utiliser une durée de mise en cache personnalisée au lieu de la durée illimitée par défaut. Cela vous évitera d'avoir à passer la durée personnalisée à la fonction [[yii\caching\Cache::set()|set()]] à chaque fois. + + +### Dépendances de mise en cache + +En plus de la définition du temps d'expiration, les données mises en cache peuvent également être invalidées par modification de ce qu'on appelle les *dépendances de mise en cache*. +Par exemple, [[yii\caching\FileDependency]] représente la dépendance à la date de modification d'un fichier. +Lorsque cette dépendance est modifiée, cela signifie que le fichier correspondant est modifié. En conséquence, tout contenu de fichier périmé trouvé dans le cache devrait être invalidé et l'appel de la fonction [[yii\caching\Cache::get()|get()]] devrait retourner `false`. + + +Les dépendances de mise en cache sont représentées sous forme d'objets dérivés de [[yii\caching\Dependency]]. Lorsque vous appelez la fonction [[yii\caching\Cache::set()|set()]] pour stocker une donnée dans le cache, vous pouvez lui passer un objet de dépendance (« Dependency ») associé. Par exemple, + +```php +// Crée une dépendance à la date de modification du fichier example.txt +$dependency = new \yii\caching\FileDependency(['fileName' => 'example.txt']); + +// La donnée expirera dans 30 secondes. +// Elle sera également invalidée plus tôt si le fichier example.txt est modifié. +$cache->set($key, $data, 30, $dependency); + +// Le cache vérifiera si la donnée a expiré. +// Il vérifiera également si la dépendance associée a été modifiée. +// Il retournera `false` si l'une de ces conditions est vérifiée. +$data = $cache->get($key); +``` + +Ci-dessous nous présentons un résumé des dépendances de mise en cache disponibles : + +- [[yii\caching\ChainedDependency]]: la dépendance est modifiée si l'une des dépendances de la chaîne est modifiée. +- [[yii\caching\DbDependency]]: la dépendance est modifiée si le résultat de le requête de l'instruction SQL spécifiée est modifié. +- [[yii\caching\ExpressionDependency]]: la dépendance est modifiée si le résultat de l'expression PHP spécifiée est modifié. +- [[yii\caching\FileDependency]]: la dépendance est modifiée si la date de dernière modification du fichier est modifiée. +- [[yii\caching\TagDependency]]: associe une donnée mise en cache à une ou plusieurs balises. Vous pouvez invalider la donnée mise en cache associée à la balise spécifiée en appelant [[yii\caching\TagDependency::invalidate()]]. + +> Note : évitez d'utiliser la méthode [[yii\caching\Cache::exists()|exists()]] avec des dépendances. Cela ne vérifie pas si la dépendance associée à la donnée mise en cache, s'il en existe une, a changé. Ainsi, un appel de la fonction [[yii\caching\Cache::get()|get()]] peut retourner `false` alors que l'appel de la fonction [[yii\caching\Cache::exists()|exists()]] retourne `true`. + + +## Mise en cache de requêtes + +La mise en cache de requêtes est une fonctionnalité spéciale de la mise en cache construite sur la base de la mise en cache de données. Elle est fournie pour permettre la mise en cache du résultat de requêtes de base de données. + +La mise en cache de requêtes nécessite une [[yii\db\Connection|connexion à une base de données]] et un [composant d'application](#cache-components)`cache` valide. +L'utilisation de base de la mise en cache de requêtes est la suivante, en supposant que `$db` est une instance de [[yii\db\Connection]] : + +```php +$result = $db->cache(function ($db) { + + // le résultat d'une requête SQL sera servi à partir du cache + // si la mise en cache de requêtes est activée et si le résultat de la requête est trouvé dans le cache + return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne(); + +}); +``` + +La mise en cache de requêtes peut être utilisée pour des [DAO](db-dao.md) ainsi que pour des [enregistrements actifs](db-active-record.md): + +```php +$result = Customer::getDb()->cache(function ($db) { + return Customer::find()->where(['id' => 1])->one(); +}); +``` + +> Info : quelques systèmes de gestion de bases de données (DBMS) (p. ex. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) + prennent également en charge la mise en cache de requêtes du côté serveur de base de données. Vous pouvez choisir d'utiliser l'un ou l'autre des ces mécanismes de mise en cache de requêtes. Les mises en cache de requêtes décrites ci-dessus offrent l'avantage de pouvoir spécifier des dépendances de mise en cache flexibles et sont potentiellement plus efficaces. + + +### Vidage du cache + +Lorsque vous avez besoin d'invalider toutes les données stockées dans le cache, vous pouvez appeler [[yii\caching\Cache::flush()]]. + +Vous pouvez aussi vider le cache depuis la console en appelant `yii cache/flush`. + - `yii cache`: liste les caches disponibles dans une application + - `yii cache/flush cache1 cache2`: vide les composants de mise en cache `cache1`, `cache2` (vous pouvez passer de multiples composants en les séparant par une virgule) + - `yii cache/flush-all`: vide tous les composants de mise en cache de l'application + +> Info : les applications en console utilisent un fichier de configuration séparé par défaut. Assurez-vous que vous utilisez le même composant de mise en cache dans les configurations de vos application web et console pour obtenir l'effet correct. + + +### Configurations + +La mise en cache de requêtes dispose de trois options globales configurables via [[yii\db\Connection]] : + +* [[yii\db\Connection::enableQueryCache|enableQueryCache]] : pour activer ou désactiver la mise en cache de requêtes. + Valeur par défaut : `true`. Notez que pour activer effectivement la mise en cache de requêtes, vous devez également disposer d'un cache valide, tel que spécifié par [[yii\db\Connection::queryCache|queryCache]]. +* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]] : ceci représente le nombre de secondes durant lesquelles le résultat d'une requête reste valide dans le cache. Vous pouvez utiliser 0 pour indiquer que le résultat de la requête doit rester valide indéfiniment dans le cache. Cette propriété est la valeur par défaut utilisée lors de l'appel [[yii\db\Connection::cache()]] sans spécifier de durée. +* [[yii\db\Connection::queryCache|queryCache]] : ceci représente l'identifiant du composant d'application de mise en cache. + Sa valeur par défaut est : `'cache'`. La mise en cache de requêtes est activée seulement s'il existe un composant d'application de mise en cache valide. + + +### Utilisations + +Vous pouvez utiliser [[yii\db\Connection::cache()]] si vous avez de multiples requêtes SQL qui doivent bénéficier de la mise en cache de requêtes. On l'utilise comme suit : + +```php +$duration = 60; // mettre le résultat de la requête en cache durant 60 secondes. +$dependency = ...; // dépendance optionnelle + +$result = $db->cache(function ($db) { + + // ... effectuer les requêtes SQL ici ... + + return $result; + +}, $duration, $dependency); +``` + +Toutes les requêtes SQL dans la fonction anonyme sont mises en cache pour la durée spécifiée avec la dépendance spécifiée. Si le résultat d'une requête est trouvé valide dans le cache, la requête est ignorée et, à la place, le résultat est servi à partir du cache. Si vous ne spécifiez pas le paramètre `$duration`, la valeur de [[yii\db\Connection::queryCacheDuration|queryCacheDuration]] est utilisée en remplacement. + +Parfois, dans `cache()`, il se peut que vous vouliez désactiver la mise en cache de requêtes pour des requêtes particulières. Dans un tel cas, vous pouvez utiliser [[yii\db\Connection::noCache()]]. + +```php +$result = $db->cache(function ($db) { + + // requêtes SQL qui utilisent la mise en cache de requêtes + + $db->noCache(function ($db) { + + // requêtes qui n'utilisent pas la mise en cache de requêtes + + }); + + // ... + + return $result; +}); +``` + +Si vous voulez seulement utiliser la mise en cache de requêtes pour une requête unique, vous pouvez appeler la fonction [[yii\db\Command::cache()]] lors de la construction de la commande. Par exemple : + +```php +// utilise la mise en cache de requêtes et définit la durée de mise en cache de la requête à 60 secondes +$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne(); +``` + +Vous pouvez aussi utiliser la fonction [[yii\db\Command::noCache()]] pour désactiver la mise en cache de requêtes pour une commande unique. Par exemple : + +```php +$result = $db->cache(function ($db) { + + // requêtes SQL qui utilisent la mise en cache de requêtes + + // ne pas utiliser la mise en cache de requêtes pour cette commande + $customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne(); + + // ... + + return $result; +}); +``` + + +### Limitations + +La mise en cache de requêtes ne fonctionne pas avec des résultats de requêtes qui contiennent des gestionnaires de ressources. Par exemple, lorsque vous utilisez de type de colonne `BLOB` dans certains systèmes de gestion de bases de données (DBMS), la requête retourne un gestionnaire de ressources pour la donnée de la colonne. + +Quelques supports de stockage pour cache sont limités en taille. Par exemple, avec memcache, chaque entrée est limitée en taille à 1 MO. En conséquence, si le résultat d'une requête dépasse cette taille, la mise en cache échoue. + diff --git a/docs/guide-fr/caching-fragment.md b/docs/guide-fr/caching-fragment.md new file mode 100644 index 0000000..0194a61 --- /dev/null +++ b/docs/guide-fr/caching-fragment.md @@ -0,0 +1,143 @@ +Mise en cache de fragments +========================== + +La mise en cache de fragments fait référence à la mise en cache de fragments de pages Web. Par exemple, si une page affiche un résumé des ventes annuelles dans un tableau, vous pouvez stocker ce tableau en cache pour éliminer le temps nécessaire à sa génération à chacune des requêtes. La mise en cache de fragments est construite au-dessus de la [mise en cache de données](caching-data.md). + +Pour utiliser la mise en cache de fragments, utilisez la construction qui suit dans une [vue](structure-views.md): + +```php +if ($this->beginCache($id)) { + + // ... générez le contenu ici ... + + $this->endCache(); +} +``` + +C'est à dire, insérez la logique de génération du contenu entre les appels [[yii\base\View::beginCache()|beginCache()]] et +[[yii\base\View::endCache()|endCache()]]. Si le contenu est trouvé dans le cache, [[yii\base\View::beginCache()|beginCache()]] +rendra le contenu en cache et retournera `false` (faux), ignorant la logique de génération de contenu. +Autrement, votre logique de génération de contenu sera appelée, et quand [[yii\base\View::endCache()|endCache()]] sera appelée, le contenu généré sera capturé et stocké dans le cache. + +Comme pour la [mise en cache de données](caching-data.md), un `$id` (identifiant) unique est nécessaire pour identifier un cache de contenu. + + +## Options de mise en cache + +Vous pouvez spécifier des options additionnelles sur la mise en cache de fragments en passant le tableau d'options comme second paramètre à la méthode [[yii\base\View::beginCache()|beginCache()]]. En arrière plan, ce tableau d'options est utilisé pour configurer un composant graphique [[yii\widgets\FragmentCache]] qui met en œuvre la fonctionnalité réelle de mise en cache de fragments. + +### Durée + +L'option [[yii\widgets\FragmentCache::duration|duration]] (durée) est peut-être l'option de la mise en cache de fragments la plus couramment utilisée. Elle spécifie pour combien de secondes le contenu peut demeurer valide dans le cache. Le code qui suit met le fragment de contenu en cache pour au maximum une heure : + +```php +if ($this->beginCache($id, ['duration' => 3600])) { + + // ... générez le contenu ici... + + $this->endCache(); +} +``` + +Si cette option n'est pas définie, la valeur utilisée par défaut est 60, ce qui veut dire que le contenu mise en cache expirera au bout de 60 secondes. + + +### Dépendances + +Comme pour la [mise en cache de données](caching-data.md#cache-dependencies), le fragment de contenu mis en cache peut aussi avoir des dépendances. Par exemple, le contenu d'un article affiché dépend du fait que l'article a été modifié ou pas. + +Pour spécifier une dépendance, définissez l'option [[yii\widgets\FragmentCache::dependency|dependency]], soit sous forme d'objet [[yii\caching\Dependency]], soit sous forme d'un tableau de configuration pour créer un objet [[yii\caching\Dependency]]. Le code qui suit spécifie que le fragment de contenu dépend du changement de la valeur de la colonne `updated_at` (mis à jour le) : + +```php +$dependency = [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT MAX(updated_at) FROM post', +]; + +if ($this->beginCache($id, ['dependency' => $dependency])) { + + // ... générez le contenu ici ... + + $this->endCache(); +} +``` + + +### Variations + +Le contenu mise en cache peut connaître quelques variations selon certains paramètres. Par exemple, pour une application Web prenant en charge plusieurs langues, le même morceau de code d'une vue peut générer le contenu dans différentes langues. Par conséquent, vous pouvez souhaitez que le contenu mis en cache varie selon la langue courante de l'application. + +Pour spécifier des variations de mise en cache, définissez l'option [[yii\widgets\FragmentCache::variations|variations]], qui doit être un tableau de valeurs scalaires, représentant chacune un facteur de variation particulier. Par exemple, pour que le contenu mis en cache varie selon la langue, vous pouvez utiliser le code suivant : + +```php +if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { + + // ... générez le contenu ici ... + + $this->endCache(); +} +``` + + +### Activation désactivation de la mise en cache + +Parfois, vous désirez activer la mise en cache de fragments seulement lorsque certaines conditions sont rencontrées. Par exemple, pour une page qui affiche un formulaire, vous désirez seulement mettre le formulaire en cache lorsqu'il est initialement demandé (via une requête GET). Tout affichage subséquent du formulaire (via des requêtes POST) ne devrait pas être mise en cache car il contient des données entrées par l'utilisateur. Pour mettre en œuvre ce mécanisme, vous pouvez définir l'option [[yii\widgets\FragmentCache::enabled|enabled]], comme suit : + +```php +if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) { + + // ... générez le contenu ici ... + + $this->endCache(); +} +``` + + +## Mises en cache imbriquées + +La mise en cache de fragments peut être imbriquée. C'est à dire qu'un fragment mis en cache peut être contenu dans un autre fragment lui aussi mis en cache. +Par exemple, les commentaires sont mis en cache dans un cache de fragment interne, et sont mis en cache en même temps et avec le contenu de l'article dans un cache de fragment externe. Le code qui suit montre comment deux caches de fragment peuvent être imbriqués : + +```php +if ($this->beginCache($id1)) { + + // ...logique de génération du contenu ... + + if ($this->beginCache($id2, $options2)) { + + // ...logique de génération du contenu... + + $this->endCache(); + } + + // ... logique de génération de contenu ... + + $this->endCache(); +} +``` + +Différentes options de mise en cache peuvent être définies pour les caches imbriqués. Par exemple, les caches internes et les caches externes peuvent utiliser des valeurs de durée différentes. Même lorsque les données mises en cache dans le cache externe sont invalidées, le cache interne peut continuer à fournir un fragment interne valide. Néanmoins, le réciproque n'est pas vraie ; si le cache externe est évalué comme valide, il continue à fournir la même copie mise en cache après que le contenu du cache interne a été invalidé. Par conséquent, vous devez être prudent lors de la définition des durées ou des dépendances des caches imbriqués, autrement des fragments internes périmés peuvent subsister dans le fragment externe. + + +## Contenu dynamique + +Lors de l'utilisation de la mise en cache de fragments, vous pouvez rencontrer une situation dans laquelle un gros fragment de contenu est relativement statique en dehors de quelques endroits particuliers. Par exemple, l'entête d'une page peut afficher le menu principal avec le nom de l'utilisateur courant. Un autre problème se rencontre lorsque le contenu mis en cache, contient du code PHP qui doit être exécuté à chacune des requêtes (p. ex. le code pour enregistrer un paquet de ressources). Ces deux problèmes peuvent être résolus par une fonctionnalité qu'on appelle *contenu dynamique*. + +Un contenu dynamique signifie un fragment de sortie qui ne doit jamais être mis en cache même s'il est contenu dans un fragment mis en cache. Pour faire en sorte que le contenu soit dynamique en permanence, il doit être généré en exécutant un code PHP à chaque requête, même si le contenu l'englobant est servi à partir du cache. + +Vous pouvez appeler la fonction [[yii\base\View::renderDynamic()]] dans un fragment mis en cache pour y insérer un contenu dynamique à l'endroit désiré, comme ceci : + +```php +if ($this->beginCache($id1)) { + + // ... logique de génération de contenu ... + + echo $this->renderDynamic('return Yii::$app->user->identity->name;'); + + // ... logique de génération de contenu ... + + $this->endCache(); +} +``` + +La méthode [[yii\base\View::renderDynamic()|renderDynamic()]] accepte un morceau de code PHP en paramètre. La valeur retournée est traitée comme un contenu dynamique. Le même code PHP est exécuté à chacune des requêtes, peu importe que le fragment englobant soit servi à partir du cache ou pas. diff --git a/docs/guide-fr/caching-http.md b/docs/guide-fr/caching-http.md new file mode 100644 index 0000000..f67260a --- /dev/null +++ b/docs/guide-fr/caching-http.md @@ -0,0 +1,108 @@ +Mise en cache HTTP +============ + +En plus de la mise en cache côté serveur que nous avons décrite dans les sections précédentes, les applications Web peuvent aussi exploiter la mise en cache côté client pour économiser le temps de génération et de transfert d'un contenu de page inchangé. + +Pour utiliser la mise en cache côté client, vous pouvez configurer [[yii\filters\HttpCache]] comme un filtre pour des actions de contrôleur dont le résultat rendu peut être mis en cache du côté du client. [[yii\filters\HttpCache|HttpCache]] +ne fonctionne que pour les requêtes `GET` et `HEAD`. Il peut gérer trois sortes d'entêtes HTTP relatifs à la mise en cache pour ces requêtes : + +* [[yii\filters\HttpCache::lastModified|Last-Modified]] +* [[yii\filters\HttpCache::etagSeed|Etag]] +* [[yii\filters\HttpCache::cacheControlHeader|Cache-Control]] + + +## Entête `Last-Modified` + +L'entête `Last-Modified` (dernière modification) utilise un horodatage pour indiquer si la page a été modifiée depuis sa mise en cache par le client. + +Vous pouvez configurer la propriété [[yii\filters\HttpCache::lastModified]] pour activer l'envoi de l'entête `Last-modified`. La propriété doit être une fonction de rappel PHP qui retourne un horodatage UNIX concernant la modification de la page. La signature de la fonction de rappel PHP doit être comme suit : + +```php +/** + * @param Action $action l'objet action qui est actuellement géré + * @param array $params la valeur de la propriété "params" + * @return int un horodatage UNIX représentant l'instant de modification de la page + */ +function ($action, $params) +``` + +Ce qui suit est un exemple d'utilisation de l'entête `Last-Modified` : + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['index'], + 'lastModified' => function ($action, $params) { + $q = new \yii\db\Query(); + return $q->from('post')->max('updated_at'); + }, + ], + ]; +} +``` + +Le code précédent établit que la mise en cache HTTP doit être activée pour l'action `index` seulement. Il doit générer un entête HTTP `Last-Modified` basé sur l'instant de la dernière mise à jour d'articles (posts). Lorsque le navigateur visite la page `index` pour la première fois, la page est générée par le serveur et envoyée au navigateur. Si le navigateur visite à nouveau la même page, et qu'aucun article n'a été modifié, le serveur ne régénère par la page, et le navigateur utilise la version mise en cache du côté du client. En conséquence, le rendu côté serveur et la transmission de la page sont tous deux évités. + + +## Entête `ETag` + +L'entête "Entity Tag" (or `ETag` en raccourci) utilise une valeur de hachage pour représenter le contenu d'une page. Si la page est modifiée, la valeur de hachage change également. En comparant la valeur de hachage conservée sur le client avec la valeur de hachage générée côté serveur, le cache peut déterminer si la page a été modifiée et doit être retransmise. + +Vous pouvez configurer la propriété [[yii\filters\HttpCache::etagSeed]] pour activer l'envoi de l'entête `ETag`. La propriété doit être une fonction de rappel PHP qui retourne un nonce (sel) pour la génération de la valeur de hachage Etag. La signature de la fonction de rappel PHP doit être comme suit : + +```php +/** + * @param Action $action l'objet action qui est actuellement géré + * @param array $params la valeur de la propriété "params" + * @return string une chaîne de caractères à utiliser comme nonce (sel) pour la génération d'une valeur de hachage ETag + */ +function ($action, $params) +``` + +Ce qui suit est un exemple d'utilisation de l'entête `ETag` : + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['view'], + 'etagSeed' => function ($action, $params) { + $post = $this->findModel(\Yii::$app->request->get('id')); + return serialize([$post->title, $post->content]); + }, + ], + ]; +} +``` + +Le code ci-dessus établit que la mise en cache HTTP doit être activée pour l'action `view` seulement. Il doit générer un entête HTTP `ETag` basé sur le titre et le contenu de l'article demandé. Lorsque le navigateur visite la page pour la première fois, la page est générée par le serveur et envoyée au navigateur. Si le navigateur visite à nouveau la même page et que ni le titre, ni le contenu de l'article n'ont changé, le serveur ne régénère pas la page et le navigateur utilise la version mise en cache côté client. En conséquence, le rendu par le serveur et la transmission de la page sont tous deux évités. + +ETags vous autorise des stratégies de mises en cache plus complexes et/ou plus précises que l'entête `Last-Modified`. Par exemple, un ETag peut être invalidé si on a commuté le site sur un nouveau thème. + +Des génération coûteuses d'ETag peuvent contrecarrer l'objectif poursuivi en utilisant `HttpCache` et introduire une surcharge inutile, car il faut les réévaluer à chacune des requêtes. Essayez de trouver une expression simple qui invalide le cache si le contenu de la page a été modifié. + +> Note : en conformité avec la norme [RFC 7232](http://tools.ietf.org/html/rfc7232#section-2.4), + `HttpCache` envoie les entêtes `ETag` et `Last-Modified` à la fois si ils sont tous deux configurés. Et si le client envoie les entêtes `If-None-Match` et `If-Modified-Since` à la fois, seul le premier est respecté. + + +## Entête `Cache-Control` + +L'entête `Cache-Control` spécifie la politique de mise en cache générale pour les pages. Vous pouvez l'envoyer en configurant la propriété [[yii\filters\HttpCache::cacheControlHeader]] avec la valeur de l'entête. Par défaut, l'entête suivant est envoyé : + +``` +Cache-Control: public, max-age=3600 +``` + +## Propriété "Session Cache Limiter" + +Lorsqu'une page utilise une session, PHP envoie automatiquement quelques entêtes HTTP relatifs à la mise en cache comme spécifié dans la propriété `session.cache_limiter` de PHP INI. Ces entêtes peuvent interférer ou désactiver la mise en cache que vous voulez obtenir de `HttpCache`. Pour éviter ce problème, par défaut, `HttpCache` désactive l'envoi de ces entêtes automatiquement. Si vous désirez modifier ce comportement, vous devez configurer la propriété [[yii\filters\HttpCache::sessionCacheLimiter]]. Cette propriété accepte une chaîne de caractères parmi `public`, `private`, `private_no_expire` et `nocache`. Reportez-vous au manuel de PHP à propos de [session_cache_limiter()](http://www.php.net/manual/en/function.session-cache-limiter.php) pour des explications sur ces valeurs. + + +## Implications SEO + +Les robots moteurs de recherche ont tendance à respecter les entêtes de mise en cache. Comme certains moteurs d'indexation du Web sont limités quant aux nombre de pages par domaine qu'ils sont à même de traiter dans un certain laps de temps, l'introduction d'entêtes de mise en cache peut aider à l'indexation de votre site car ils limitent le nombre de pages qui ont besoin d'être traitées. diff --git a/docs/guide-fr/caching-overview.md b/docs/guide-fr/caching-overview.md new file mode 100644 index 0000000..49f9fd4 --- /dev/null +++ b/docs/guide-fr/caching-overview.md @@ -0,0 +1,15 @@ +Mise en cache +============= + +La mise en cache est un moyen peu coûteux et efficace d'améliorer la performance d'une application Web. En stockant des données relativement statiques en cache et en les servant à partir de ce cache lorsqu'elles sont demandées, l'application économise le temps qu'il aurait fallu pour générer ces données à partir de rien à chaque demande. + +La mise en cache se produit à différents endroits et à différents niveaux dans une application Web. Du côté du serveur, au niveau le plus bas, le cache peut être utilisé pour stocker des données de base, telles qu'une liste des informations sur des articles recherchée dans une base de données ; et à un niveau plus élevé, il peut être utilisé pour stocker des fragments ou l'intégralité de pages Web, telles que le rendu des articles les plus récents. + +Du côté client, la mise en cache HTTP peut être utilisée pour conserver le contenu des pages visitées les plus récentes dans le cache du navigateur. + +Yii prend en charge tous ces mécanismes de mise en cache : + +* [Mise en cache de données](caching-data.md) +* [Mise en cache de fragments](caching-fragment.md) +* [Mise en cache de pages](caching-page.md) +* [Mise en cache HTTP](caching-http.md) diff --git a/docs/guide-fr/caching-page.md b/docs/guide-fr/caching-page.md new file mode 100644 index 0000000..47aaef5 --- /dev/null +++ b/docs/guide-fr/caching-page.md @@ -0,0 +1,33 @@ +Mise en cache de pages +====================== + +La mise en cache de pages fait référence à la mise en cache du contenu d'une page entière du côté serveur. Plus tard, lorsque la même page est demandée à nouveau, son contenu est servi à partir du cache plutôt que d'être régénéré entièrement. + +La mise en cache de pages est prise en charge par [[yii\filters\PageCache]], un [filtre d'action](structure-filters.md). On peut l'utiliser de la manière suivante dans une classe contrôleur : + +```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', + ], + ], + ]; +} +``` + +Le code ci-dessus établit que la mise en cache de pages doit être utilisée uniquement pour l'action `index`. Le contenu de la page doit être mis en cache pour au plus 60 secondes et doit varier selon la langue courante de l'application. De plus, le contenu de la page mis en cache doit être invalidé si le nombre total d'articles (post) change. + +Comme vous pouvez le constater, la mise en cache de pages est très similaire à la [mise en cache de fragments](caching-fragment.md). Les deux prennent en charge les options telles que `duration`, `dependencies`, `variations` et `enabled`. La différence principale est que la mise en cache de pages est mis en œuvre comme un [filtre d'action](structure-filters.md) alors que la mise en cache de framgents l'est comme un [composant graphique](structure-widgets.md). + +Vous pouvez utiliser la [mise en cache de fragments](caching-fragment.md) ainsi que le [contenu dynamique](caching-fragment.md#dynamic-content) en simultanéité avec la mise en cache de pages. + diff --git a/docs/guide-fr/concept-aliases.md b/docs/guide-fr/concept-aliases.md index fc20fdd..745390a 100644 --- a/docs/guide-fr/concept-aliases.md +++ b/docs/guide-fr/concept-aliases.md @@ -1,13 +1,14 @@ -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. +Alias +===== +Les alias sont utilisés pour représenter des chemins de fichier ou des URL de façon à ce que vous n'ayez pas besoin d'écrire ces chemins ou ces URL en entier dans votre code. Un alias doit commencer par le caractère arobase `@` pour être différentié des chemins de fichier et des URL normaux. Les alias définis sans ce caractère de tête `@` sont automatiquement préfixés avec ce dernier. +Yii possèdent de nombreux alias pré-définis déjà disponibles. Par exemple, l'alias `@yii` représente le chemin d'installation de la base structurée de développement PHP (*framework*), Yii ; L'alias `@web` représente l'URL de base de l'application Web en cours d'exécution. -Définir des alias ------------------ +Définition des alias +-------------------- -Vous pouvez définir un alias soit pour un chemin de fichier ou pour une URL en appelant [[Yii::setAlias()]]: +Vous pouvez définir un alias pour un chemin de fichier ou pour une URL en appelant [[Yii::setAlias()]]: ```php // un alias pour un chemin de fichier @@ -16,58 +17,62 @@ 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é. +> Note: le chemin de fichier ou l'URL pour qui un alias est créé peut *ne pas* nécessairement faire référence à un fichier ou une ressource existante. + +Étant donné un alias, vous pouvez dériver un autre alias – sans faire appel à [[Yii::setAlias()]]) – en y ajoutant une barre oblique de division `/` suivi d'un ou plusieurs segments de chemin. Les alias définis via [[Yii::setAlias()]] sont des *alias racines*, tandis que les alias qui en dérivent sont des *alias dérivés*. Par exemple, `@foo` est un alias racine, alors 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é): +Vous pouvez définir un 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: +Les alias racines sont ordinairement définis pendant l'étape d'[amorçage](runtime-bootstrapping.md). Par exemple, vous pouvez appeler [[Yii::setAlias()]] dans le [script d'entrée](structure-entry-scripts.md). Pour commodité, la classe [Application](structure-applications.md) fournit une propriété nommée `aliases` que vous pouvez configurer dans la [configuration](concept-configurations.md) de l'application : ```php return [ // ... 'aliases' => [ - '@foo' => '/chemin/vers/foo', + '@foo' => '/path/to/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: +Vous pouvez appeler [[Yii::getAlias()]] pour résoudre un alias racine en le chemin de fichier ou l'URL qu'il représente. La même méthode peut aussi résoudre un alias dérivé en le chemin de fichier ou l'URL correspondant : ```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 +echo Yii::getAlias('@foo'); // affiche : /path/to/foo +echo Yii::getAlias('@bar'); // affiche : http://www.example.com +echo Yii::getAlias('@foo/bar/file.php'); // affiche : /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. +Le chemin ou l'URL que représente un alias dérivé est déterminé en remplaçant l'alias racine par le chemin ou l'URL qui lui correspond dans l'alias dérivé. + +> Note: la méthode [[Yii::getAlias()]] ne vérifie pas que le chemin ou l'URL qui en résulte fait référence à un fichier existant 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: +Un alias racine peut également contenir des barres obliques de division `/`. La méthode [[Yii::getAlias()]] est suffisamment intelligente pour dire quelle partie d'un alias est un alias racine et, par conséquent, déterminer correctement le chemin de fichier ou l'URL qui correspond : ```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 +Yii::setAlias('@foo', '/path/to/foo'); +Yii::setAlias('@foo/bar', '/path2/bar'); +Yii::getAlias('@foo/test/file.php'); // affiche : /path/to/foo/test/file.php +Yii::getAlias('@foo/bar/file.php'); // affiche : /path2/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`. +Si `@foo/bar` n'est pas défini en tant qu'alias racine, la dernière instruction affiche `/path/to/foo/bar/file.php`. -Utilisation des alias ----------------------- +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. +Les alias sont reconnus en différents endroits dans Yii sans avoir besoin d'appeler [[Yii::getAlias()]] pour les convertir en chemin ou URL. Par exemple, [[yii\caching\FileCache::cachePath]] accepte soit un chemin de fichier, soit un alias représentant un chemin de fichier, grâce au préfixe `@` qui permet de différentier un chemin de fichier d'un alias. ```php use yii\caching\FileCache; @@ -76,31 +81,33 @@ $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. + +Reportez-vous à la documentation de l'API pour savoir si une propriété ou une méthode prend en charge les alias. -Alias prédéfinis +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`. +Yii prédéfinit un jeu d'alias pour faire référence à des chemins de fichier ou à des URL d'utilisation courante : + +- `@yii`, le dossier où le fichier `BaseYii.php` se trouve – aussi appelé dossier de la base structurée de développement PHP (*framework*). +- `@app`, le [[yii\base\Application::basePath|chemin de base]] de l'application en cours d'exécution. +- `@runtime`, le [[yii\base\Application::runtimePath|chemin du dossier runtime]] de l'application en cours d'exécution. Valeur par défaut `@app/runtime`. +- `@webroot`, le dossier Web racine de l'application en cours d'exécution. Il est déterminé en se basant sur le dossier qui contient le [script d'entrée](structure-entry-scripts.md). +- `@web`, l'URL de base de l'application en cours d'exécution. Cet alias a la même valeur que [[yii\web\Request::baseUrl]]. +- `@vendor`, le [[yii\base\Application::vendorPath|dossier vendor de Composer]]. Valeur par défaut `@app/vendor`. +- `@bower`, le dossier racine des [paquets bower](http://bower.io/). Valeur par défaut `@vendor/bower`. +- `@npm`, le dossier racine des [paquets npm](https://www.npmjs.org/). Valeur par défaut `@vendor/npm`. + +L'alias `@yii` est défini lorsque vous incluez le fichier `Yii.php` dans votre [script d'entrée](structure-entry-scripts.md). Les alias restants sont définis dans le constructeur de l'application au moment où la [configuration](concept-configurations.md) de l'application est appliquée. + . -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 à +Un alias est automatiquement défini par chacune des [extensions](structure-extensions.md) qui sont installées par Composer. Chaque alias est nommé d'après le nom de l'extension déclaré dans le fichier `composer.json`. Chaque alias représente le dossier racine du paquet. Par exemple, si vous installez l'extension `yiisoft/yii2-jui`, vous obtiendrez automatiquement l'alias `@yii/jui` défini durant l'étape d'[amorçage](runtime-bootstrapping.md), et équivalent à : ```php Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); -``` \ No newline at end of file +``` diff --git a/docs/guide-fr/concept-autoloading.md b/docs/guide-fr/concept-autoloading.md new file mode 100644 index 0000000..c5c6862 --- /dev/null +++ b/docs/guide-fr/concept-autoloading.md @@ -0,0 +1,64 @@ +Chargement automatique des classes +================================== + +Yii compte sur le [mécanisme de chargement automatique des classes](http://www.php.net/manual/en/language.oop5.autoload.php) pour localiser et inclure tous les fichiers de classes requis. Il fournit un chargeur automatique de classes de haute performance qui est conforme à la [norme PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). Le chargeur automatique est installé lorsque vous incluez le fichier `Yii.php`. + +> Note: pour simplifier la description, dans cette section, nous ne parlerons que du chargement automatique des classes. Néanmoins, gardez présent à l'esprit que le contenu que nous décrivons ici s'applique aussi au chargement automatique des interfaces et des traits. + + +Utilisation du chargeur automatique de Yii +------------------------------------------ + +Pour utiliser le chargeur automatique de classes de Yii, vous devez suivre deux règles simples lorsque vous créez et nommez vos classes : + +* Chaque classe doit être placée sous un [espace de noms](http://php.net/manual/en/language.namespaces.php) (p. ex. `foo\bar\MyClass`) +* Chaque classe doit être sauvegardée sous forme d'un fichier individuel dont le chemin est déterminé par l'algorithme suivant : + +```php +// $className est un nom de classe pleinement qualifié sans la barre oblique inversée de tête +$classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); +``` + +For exemple, si le nom de classe et l'espace de noms sont `foo\bar\MyClass`, l'[alias](concept-aliases.md) pour le chemin du fichier de classe correspondant est `@foo/bar/MyClass.php`. Pour que cet alias puisse être résolu en un chemin de fichier, soit `@foo`, soit `@foo/bar` doit être un [alias racine](concept-aliases.md#defining-aliases). + +Lorsque vous utilisez le [modèle de projet *basic*](start-installation.md), vous pouvez placer vos classes sous l'espace de noms de niveau le plus haut `app` afin qu'elles puissent être chargées automatiquement par Yii sans avoir besoin de définir un nouvel alias. Cela est dû au fait que `@app` est un [alias prédéfini](concept-aliases.md#predefined-aliases), et qu'un nom de classe comme `app\components\MyClass` peut être résolu en le fichier de classe `AppBasePath/components/MyClass.php`, en appliquant l'algorithme précédemment décrit. + +Dans le [modèle de projet avancé](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), chaque niveau possède son propre alias. Par exemple, le niveau « interface utilisateur » a l'alias `@frontend`, tandis que le niveau « interface d'administration » a l'alias `@backend`. En conséquence, vous pouvez mettre les classes de l'interface utilisateur sous l'espace de noms `frontend`, tandis que les classes de l'interface d'administration sont sous l'espace de noms `backend`. Cela permet à ces classes d'être chargées automatiquement par le chargeur automatique de Yii. + +Table de mise en correspondance des classes +------------------------------------------- + +Le chargeur automatique de classes de Yii prend en charge la fonctionnalité *table de mise en correspondance des classes*, qui met en correspondance les noms de classe avec les chemins de classe de fichiers. Lorsque le chargeur automatique charge une classe, il commence par chercher si la classe existe dans la table de mise en correspondance. Si c'est le cas, le chemin de fichier correspondant est inclus directement sans plus de recherche. Cela rend le chargement des classes très rapide. En fait, toutes les classes du noyau de Yii sont chargées de cette manière. + +Vous pouvez ajouter une classe à la table de mise en correspondance des classes, stockée dans `Yii::$classMap`, avec l'instruction : + +```php +Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php'; +``` + +Les [alias](concept-aliases.md) peuvent être utilisés pour spécifier des chemins de fichier de classe. Vous devez définir la table de mise en correspondance dans le processus d'[amorçage](runtime-bootstrapping.md) afin qu'elle soit prête avant l'utilisation de vos classes. + + +Utilisation d'autres chargeurs automatiques +------------------------------------------- + +Comme Yii utilise Composer comme gestionnaire de dépendances de paquets, il est recommandé que vous installiez aussi le chargeur automatique de Composer. Si vous utilisez des bibliothèques de tierces parties qui ont besoin de leurs propres chargeurs, vous devez installer ces chargeurs également. + +Lors de l'utilisation conjointe du chargeur automatique de Yii et d'autres chargeurs automatiques, vous devez inclure le fichier `Yii.php` *après* que tous les autres chargeurs automatiques sont installés. Cela fait du chargeur automatique de Yii le premier à répondre à une requête de chargement automatique de classe. Par exemple, le code suivant est extrait du [script d'entrée](structure-entry-scripts.md) du [modèle de projet *basic*](start-installation.md). La première ligne installe le chargeur automatique de Composer, tandis que la seconde installe le chargeur automatique de Yii : + +```php +require(__DIR__ . '/../vendor/autoload.php'); +require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); +``` + +Vous pouvez utiliser le chargeur automatique de Composer seul sans celui de Yii. Néanmoins, en faisant de cette manière, la performance de chargement de vos classes est dégradée et vous devez appliquer les règles de Composer pour que vos classes puissent être chargées automatiquement. + +> Info: si vous voulez ne pas utiliser le chargeur automatique de Yii, vous devez créer votre propre version du fichier `Yii.php` et l'inclure dans votre [script d'entrée](structure-entry-scripts.md). + + +Chargement automatique des classes d'extension +---------------------------------------------- + +Le chargeur automatique de Yii est capable de charger automatiquement des classes d'[extension](structure-extensions.md). La seule exigence est que cette extension spécifie la section `autoload` correctement dans son fichier `composer.json`. Reportez-vous à la [documentation de Composer](https://getcomposer.org/doc/04-schema.md#autoload) pour plus de détails sur la manière de spécifier `autoload`. + +Dans le cas où vous n'utilisez pas le chargeur automatique de Yii, le chargeur automatique de Composer peut toujours charger les classes d'extensions pour vous. diff --git a/docs/guide-fr/concept-behaviors.md b/docs/guide-fr/concept-behaviors.md new file mode 100644 index 0000000..97c5dc3 --- /dev/null +++ b/docs/guide-fr/concept-behaviors.md @@ -0,0 +1,322 @@ +Comportements +============= + +Les comportements (*behaviors* sont des instances de la classe [[yii\base\Behavior]], ou de ses classes filles. Les comportements, aussi connus sous le nom de [mixins](http://en.wikipedia.org/wiki/Mixin), vous permettent d'améliorer les fonctionnalités d'une classe de [[yii\base\Component|composant]] existante sans avoir à modifier les héritages de cette classe. Le fait d'attacher un comportement à un composant injecte les méthodes et les propriétés de ce comportement dans le composant, rendant ces méthodes et ces propriétés accessibles comme si elles avaient été définies dans la classe du composant lui-même. En outre, un comportement peut répondre aux [événements](concept-events.md) déclenchés par le composant, ce qui permet aux comportements de personnaliser l'exécution normale du code du composant. + + +Définition des comportements +--------------------------- + +Pour définir un comportement, vous devez créer une classe qui étend la classe [[yii\base\Behavior]], ou une des ses classes filles. Par exemple : + +```php +namespace app\components; + +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + public $prop1; + + private $_prop2; + + public function getProp2() + { + return $this->_prop2; + } + + public function setProp2($value) + { + $this->_prop2 = $value; + } + + public function foo() + { + // ... + } +} +``` + +Le code ci-dessus définit la classe de comportement `app\components\MyBehavior` avec deux propriété — `prop1` et `prop2` — et une méthode `foo()`. Notez que la propriété `prop2` est définie via la méthode d'obtention `getProp2` et la méthode d'assignation `setProp2`. Cela est le cas parce que la classe [[yii\base\Behavior]] étend la classe [[yii\base\Object]] et, par conséquent, prend en charge la définition des [propriétés](concept-properties.md) via les méthodes d'obtention et d'assignation. + +Comme cette classe est un comportement, lorsqu'elle est attachée à un composant, ce composant acquiert alors les propriétés `prop1` et `prop2`, ainsi que la méthode `foo()`. + +> Tip: depuis l'intérieur d'un comportement, vous avez accès au composant auquel le comportement est attaché via la propriété [[yii\base\Behavior::owner]]. + +> Note: dans le cas où les méthodes [[yii\base\Behavior::__get()]] et/ou [[yii\base\Behavior::__set()]] du comportement sont redéfinies, vous devez redéfinir les méthodes [[yii\base\Behavior::canGetProperty()]] et/ou [[yii\base\Behavior::canSetProperty()]] également. + +Gestion des événements du composant +----------------------------------- + +Si un comportement a besoin de répondre aux événements déclenchés par le composant auquel il est attaché, il doit redéfinir la méthode [[yii\base\Behavior::events()]]. Par exemple: + +```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) + { + // ... + } +} +``` + +La méthode [[yii\base\Behavior::events()|events()]] doit retourner une liste d'événements avec leur gestionnaire correspondant. L'exemple ci-dessus déclare que l'événement [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] existe et définit son gestionnaire `beforeValidate()`. En spécifiant un gestionnaire d'événement, vous pouvez utiliser un des formats suivants : +  +* une chaîne de caractères qui fait référence au nom d'une méthode de la classe du comportement, comme dans l'exemple ci-dessus ; +* un tableau constitué d'un nom d'objet ou de classe et d'un nom de méthode sous forme de chaîne de caractères (sans les parenthèses), p. ex. `[$object, 'methodName']`; +* une fonction anonyme. + +La signature d'un gestionnaire d'événement doit être similaire à ce qui suit, où `event` fait référence au paramètre événement. Reportez-vous à la section [Événements](concept-events.md) pour plus de détail sur les événements. + +```php +function ($event) { +} +``` + +Attacher des comportements +----------------------------- + +Vous pouvez attacher un comportement à un [[yii\base\Component|composant]] soit de manière statique, soit de manière dynamique. Le première manière est une pratique plus habituelle. + +Pour attacher un comportement de manière statique, redéfinissez la méthode [[yii\base\Component::behaviors()|behaviors()]] de la classe du composant auquel le comportement va être attaché. La méthode [[yii\base\Component::behaviors()|behaviors()]] doit retourner une liste de [configurations](concept-configurations.md) de comportements. Chaque comportement peut être soit un nom de classe de comportement, soit un tableau de configuration : + +```php +namespace app\models; + +use yii\db\ActiveRecord; +use app\components\MyBehavior; + +class User extends ActiveRecord +{ + public function behaviors() + { + return [ + // comportement anonyme, nom de la classe de comportement seulement + MyBehavior::className(), + + // comportement nommé, nom de classe de comportement seulement + 'myBehavior2' => MyBehavior::className(), + + // comportement anonyme, tableau de configuration + [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ], + + // comportement nommé, tableau de configuration + 'myBehavior4' => [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ] + ]; + } +} +``` + +Vous pouvez associer un nom au comportement en spécifiant la clé de tableau correspondant à la configuration du comportement. Dans ce cas, le comportement est appelé *comportement nommé*. Dans l'exemple ci-dessus, il y a deux comportements nommés : `myBehavior2` et `myBehavior4`. Si un comportement n'est pas associé à un nom, il est appelé *comportement anonyme*. + + +Pour attacher un comportement de manière dynamique, appelez la méthode [[yii\base\Component::attachBehavior()]] du composant auquel le comportement va être attaché : + +```php +use app\components\MyBehavior; + +// attache un objet comportement +$component->attachBehavior('myBehavior1', new MyBehavior); + +// attache un classe de comportement +$component->attachBehavior('myBehavior2', MyBehavior::className()); + +// attache un tableau de configuration +$component->attachBehavior('myBehavior3', [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', +]); +``` + +Vous pouvez attacher plusieurs comportements à la fois en utilisant la méthode [[yii\base\Component::attachBehaviors()]] : + +```php +$component->attachBehaviors([ + 'myBehavior1' => new MyBehavior, // un comportement nommé + MyBehavior::className(), // un comportement anonyme +]); +``` + +Vous pouvez aussi attacher des comportements via les [configurations](concept-configurations.md) comme ceci : + +```php +[ + 'as myBehavior2' => MyBehavior::className(), + + 'as myBehavior3' => [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ], +] +``` + +Pour plus de détails, reportez-vous à la section [Configurations](concept-configurations.md#configuration-format). + +Utilisation des comportements +----------------------------- + +Pour utiliser un comportement, commencez par l'attacher à un [[yii\base\Component|composant]] en suivant les instructions données ci-dessus. Une fois le comportement attaché au composant, son utilisation est évidente. + +Vous pouvez accéder à une variable membre *publique*, ou à une [propriété](concept-properties.md) définie par une méthode d'obtention et/ou une méthode d'assignation (*getter* et *setter*), du comportement, via le composant auquel ce comportement est attaché : + +```php +// "prop1" est une propriété définie dans la classe du comportement +echo $component->prop1; +$component->prop1 = $value; +``` + +Vous pouvez aussi appeler une méthode *publique* du comportement de façon similaire : + +```php +// foo() est une méthode publique définie dans la classe du comportement +$component->foo(); +``` + +Comme vous pouvez le voir, bien que le composant `$component` ne définissent pas `prop1` et`foo()`, elles peuvent être utilisées comme si elles faisaient partie de la définition du composant grâce au comportement attaché. + +Si deux comportement définissent la même propriété ou la même méthode, et que ces deux comportement sont attachés au même composant, le comportement qui a été attaché le *premier* prévaut lorsque la propriété ou la méthode est accédée. + +Un comportement peut être associé à un nom lorsqu'il est attaché à un composant. Dans un tel cas, vous pouvez accéder à l'objet comportement en utilisant ce nom : + +```php +$behavior = $component->getBehavior('myBehavior'); +``` + +Vous pouvez aussi obtenir tous les comportements attachés au composant : + +```php +$behaviors = $component->getBehaviors(); +``` + + +Détacher des comportements +-------------------------- + +Pour détacher un comportement, appelez [[yii\base\Component::detachBehavior()]] avec le nom associé au comportement : + +```php +$component->detachBehavior('myBehavior1'); +``` + +Vous pouvez aussi détacher *tous les* comportements : + +```php +$component->detachBehaviors(); +``` + + +Utilisation de `TimestampBehavior` +------------------------- + +Pour aller à l'essentiel, jetons un coup d'œil à [[yii\behaviors\TimestampBehavior]]. Ce comportement prend automatiquement en charge la mise à jour de l'attribut *timestamp* (horodate) d'un modèle [[yii\db\ActiveRecord|enregistrement actif]] à chaque fois qu'il est sauvegardé via les méthodes `insert()`, `update()` ou `save()`. + +Tout d'abord, attachez ce comportement à la classe [[yii\db\ActiveRecord|Active Record (enregistrement actif)]] que vous envisagez d'utiliser : + +```php +namespace app\models\User; + +use yii\db\ActiveRecord; +use yii\behaviors\TimestampBehavior; + +class User extends ActiveRecord +{ + // ... + + public function behaviors() + { + return [ + [ + 'class' => TimestampBehavior::className(), + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], + ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], + ], + // si vous utilisez datetime au lieur de l'UNIX timestamp: + // 'value' => new Expression('NOW()'), + ], + ]; + } +} +``` + +Le comportement ci-dessus spécifie que lorsque l'enregistrement est : + +* inséré, le comportement doit assigner l'horodate UNIX courante aux attributs `created_at` (créé le) et `updated_at` (mis à jour le) ; +* mis à jour, le comportement doit assigner l'horodate UNIX courante à l'attribut `updated_at` ; + +> Note: pour que la mise en œuvre ci-dessus fonctionne avec une base de données MySQL, vous devez déclarer les colonnes (`created_at`, `updated_at`) en tant que `int(11)` pour qu'elles puissent représenter des horodates UNIX. + +Avec ce code en place, si vous avez un objet `User` (utilisateur) et que vous essayez de le sauvegarder, il verra ses attributs `created_at` et `updated_at` automatiquement remplis avec l'horodate UNIX : + +```php +$user = new User; +$user->email = 'test@example.com'; +$user->save(); +echo $user->created_at; // affiche l'horodate courante +``` + +Le comportement [[yii\behaviors\TimestampBehavior|TimestampBehavior]] offre également une méthode utile [[yii\behaviors\TimestampBehavior::touch()|touch()]], qui assigne l'horodate courante à un attribut spécifié et le sauvegarde dans la base de données : + +```php +$user->touch('login_time'); +``` + +Autres comportements +-------------------- + +Il existe plusieurs comportements pré-inclus et extérieurs disponibles : + +- [[yii\behaviors\BlameableBehavior]] – remplit automatiquement les attributs spécifiés avec l'identifiant de l'utilisateur courant. +- [[yii\behaviors\SluggableBehavior]] – remplit automatiquement l'attribut spécifié avec une valeur utilisable en tant que chaîne purement ASCII (*slug*) dans une URL. +- [[yii\behaviors\AttributeBehavior]] – assigne automatiquement une valeur spécifiée à un ou plusieurs attributs d'un objet enregistrement actif lorsque certains événements se produisent. +- [yii2tech\ar\softdelete\SoftDeleteBehavior](https://github.com/yii2tech/ar-softdelete) – fournit des méthodes pour une suppression douce et une restauration douce d'un enregistrement actif c.-à-d. positionne un drapeau ou un état qui marque l'enregistrement comme étant effacé. +- [yii2tech\ar\position\PositionBehavior](https://github.com/yii2tech/ar-position) – permet la gestion de l'ordre des enregistrements dans un champ entier (*integer*) en fournissant les méthodes de remise dans l'ordre. + +Comparaison des comportement et des traits +------------------------------------------ + +Bien que les comportements soient similaires aux [traits](http://www.php.net/traits) par le fait qu'ils *injectent* tous deux leurs propriétés et leurs méthodes dans la classe primaire, ils diffèrent par de nombreux aspects. Comme nous l'expliquons ci-dessous, ils ont chacun leurs avantages et leurs inconvénients. Ils sont plus des compléments l'un envers l'autre, que des alternatives. + + +### Raisons d'utiliser des comportements + +Les classes de comportement, comme les classes normales, prennent en charge l'héritage. Les traits, par contre, peuvent être considérés comme des copier coller pris en charge par le langage. Ils ne prennent pas en charge l'héritage. + +Les comportements peuvent être attachés et détachés à un composant de manière dynamique sans qu'une modification de la classe du composant soit nécessaire. Pour utiliser un trait, vous devez modifier le code de la classe qui l'utilise. + +Les comportements sont configurables mais les traits ne le sont pas. + +Les comportement peuvent personnaliser l'exécution du code d'un composant en répondant à ses événements. + +Lorsqu'il se produit des conflits de noms entre les différents comportements attachés à un même composant, les conflits sont automatiquement résolus en donnant priorité au comportement attaché le premier. Les conflits de noms causés par différents traits nécessitent une résolution manuelle en renommant les propriétés et méthodes concernées. + + +### Raisons d'utiliser des traits + +Les traits sont beaucoup plus efficaces que les comportements car les comportements sont des objets qui requièrent plus de temps du processeur et plus de mémoire. + +Les environnement de développement intégrés (EDI) sont plus conviviaux avec les traits car ces derniers sont des constructions natives du langage. diff --git a/docs/guide-fr/concept-components.md b/docs/guide-fr/concept-components.md new file mode 100644 index 0000000..94a307f --- /dev/null +++ b/docs/guide-fr/concept-components.md @@ -0,0 +1,86 @@ +Composants +========== + +Les composants sont les blocs de constructions principaux de vos applications Yii. Les composants sont des instances de la classe [[yii\base\Component]], +ou de ses classes filles. Les trois fonctionnalités principales fournies par les composants aux autres classes sont : + +* [Les propriétés](concept-properties.md) ; +* [Les événements](concept-events.md) ; +* [Les comportements](concept-behaviors.md). + +Séparément et en combinaisons, ces fonctionnalités rendent les classes de Yii beaucoup plus personnalisables et faciles à utiliser. Par exemple, l'[[yii\jui\DatePicker|objet graphique de sélection de date]] inclus, un composant d'interface utilisateur, peut être utilisé dans une [vue](structure-view.md) pour générer un sélecteur de date interactif : + +```php +use yii\jui\DatePicker; + +echo DatePicker::widget([ + 'language' => 'ru', + 'name' => 'country', + 'clientOptions' => [ + 'dateFormat' => 'yy-mm-dd', + ], +]); +``` +Les propriétés de l'objet graphique sont faciles à écrire car la classe étend [[yii\base\Component]]. + +Tandis que les composants sont très puissants, ils sont un peu plus lourds que les objets normaux. Cela est dû au fait que, en particulier, la prise en charge des fonctionnalités [event](concept-events.md) et [behavior](concept-behaviors.md) requiert un peu plus de mémoire et de temps du processeur. Si vos composants n'ont pas besoin de ces deux fonctionnalités, vous devriez envisager d'étendre la classe [[yii\base\Object]] au lieu de la classe [[yii\base\Component]]. Ce faisant, votre composant sera aussi efficace que les objets PHP normaux, mais avec la prise en charge des [propriétés](concept-properties.md). + +Lorsque votre classe étend la classe [[yii\base\Component]] ou [[yii\base\Object]], il est recommandé que suiviez ces conventions : + +- Si vous redéfinissez le constructeur, spécifiez un paramètre `$config` en tant que *dernier* paramètre du constructeur est passez le au constructeur du parent. +- Appelez toujours le constructeur du parent *à la fin* de votre constructeur redéfini. +- Si vous redéfinissez la méthode [[yii\base\Object::init()]], assurez-vous que vous appelez la méthode `init()` mise en œuvre par le parent *au début* de votre méthodes `init()`. + +Par exemple : + +```php + 3, 'prop2' => 4]); +// alternatively +$component = \Yii::createObject([ + 'class' => MyClass::className(), + 'prop1' => 3, + 'prop2' => 4, +], [1, 2]); +``` + +> Info: bien que l'approche qui consiste à appeler la méthode [[Yii::createObject()]] semble plus compliquée, elle est plus puissante car elle est mise en œuvre sur un [conteneur d'injection de dépendances](concept-di-container.md). + + +La classe [[yii\base\Object]] fait appliquer le cycle de vie suivant de l'objet : + +1. Pré-initialisation dans le constructeur. Vous pouvez définir les propriétés par défaut à cet endroit. +2. Configuration de l'objet via `$config`. La configuration peut écraser les valeurs par défaut définies dans le constructeur. +3. Post-initialisation dans la méthode [[yii\base\Object::init()|init()]]. Vous pouvez redéfinir cette méthode pour effectuer des tests sanitaires et normaliser les propriétés. +4. Appel des méthodes de l'objet. + +Les trois premières étapes arrivent toutes durant la construction de l'objet. Cela signifie qu'une fois que vous avez obtenu une instance de la classe (c.-à-d. un objet), cet objet a déjà été initialisé dans un état propre et fiable. diff --git a/docs/guide-fr/concept-configurations.md b/docs/guide-fr/concept-configurations.md new file mode 100644 index 0000000..47da48b --- /dev/null +++ b/docs/guide-fr/concept-configurations.md @@ -0,0 +1,230 @@ +Configurations +============== + +Les configurations sont très largement utilisées dans Yii lors de la création d'objets ou l'initialisation d'objets existants. Les configurations contiennent généralement le nom de la classe de l'objet en cours de création, et une liste de valeurs initiales qui doivent être assignées aux [propriétés](concept-properties.md) de l'objet. Elles peuvent aussi comprendre une liste de gestionnaires qui doivent être attachés aux [événements](concept-events.md) de l'objet et/ou une liste de [comportements](concept-behaviors.md) qui doivent être attachés à l'objet. + +Dans ce qui suit, une configuration est utilisée pour créer et initialiser une connexion à une base de données : + +```php +$config = [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; + +$db = Yii::createObject($config); +``` + +La méthode [[Yii::createObject()]] prend un tableau de configuration en tant qu'argument et crée un objet en instanciant la classe nommée dans la configuration. Lorsque l'objet est instancié, le reste de la configuration est utilisé pour initialiser les propriétés de l'objet, ses gestionnaires d'événement et ses comportements. + +Si vous disposez déjà d'un objet, vous pouvez utiliser la méthode [[Yii::configure()]] pour initialiser les propriétés de l'objet avec un tableau de configuration : + +```php +Yii::configure($object, $config); +``` + +Notez bien que dans ce cas, le tableau de configuration ne doit pas contenir d'élément `class`. + + +## Format d'une configuration + +Le format d'une configuration peut être formellement décrit comme suit : + +```php +[ + 'class' => 'ClassName', + 'propertyName' => 'propertyValue', + 'on eventName' => $eventHandler, + 'as behaviorName' => $behaviorConfig, +] +``` + +où + +* L'élément `class` spécifie un nom de classe pleinement qualifié pour l'objet à créer. +* L'élément `propertyName` spécifie les valeurs initiales d'une propriété nommé property. Les clés sont les noms de propriété et les valeurs correspondantes les valeurs initiales. Seules les variables membres publiques et les [propriétés](concept-properties.md) définies par des méthodes d'obtention (*getters*) et/ou des méthodes d'assignation (*setters*) peuvent être configurées. +* Les éléments `on eventName` spécifient quels gestionnaires doivent être attachés aux [événements](concept-events.md) de l'objet. Notez que les clés du tableau sont formées en préfixant les noms d'événement par `on`. Reportez-vous à la section [événements](concept-events.md) pour connaître les formats des gestionnaires d'événement pris en charge. +* L'élément `as behaviorName` spécifie quels [comportements](concept-behaviors.md) doivent être attachés à l'objet. Notez que les clés du tableau sont formées en préfixant les noms de comportement par `as ` ; la valeur `$behaviorConfig` représente la configuration pour la création du comportement, comme une configuration normale décrite ici. + +Ci-dessous, nous présentons un exemple montrant une configuration avec des valeurs initiales de propriétés, des gestionnaires d'événement et des comportements. + +```php +[ + 'class' => 'app\components\SearchEngine', + 'apiKey' => 'xxxxxxxx', + 'on search' => function ($event) { + Yii::info("Keyword searched: " . $event->keyword); + }, + 'as indexer' => [ + 'class' => 'app\components\IndexerBehavior', + // ... property init values ... + ], +] +``` + + +## Utilisation des configurations + +Les configurations sont utilisées en de nombreux endroits dans Yii. Au début de cette section, nous avons montré comment créer un objet obéissant à une configuration en utilisant la méthode [[Yii::createObject()]]. Dans cette sous-section, nous allons décrire les configurations d'applications et les configurations d'objets graphiques (*widget*) – deux utilisations majeures des configurations. + + +### Configurations d'applications + +La configuration d'une [application](structure-applications.md) est probablement l'un des tableaux les plus complexes dans Yii. Cela est dû au fait que la classe [[yii\web\Application|application]] dispose d'un grand nombre de propriétés et événements configurables. De première importance, se trouve sa propriété [[yii\web\Application::components|components]] qui peut recevoir un tableau de configurations pour créer des composants qui sont enregistrés durant l'exécution de l'application. Ce qui suit est un résumé de la configuration de l'application du [modèle de projet *basic*](start-installation.md). + +```php +$config = [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + ], +]; +``` + +La configuration n'a pas de clé `class`. Cela tient au fait qu'elle est utilisée comme indiqué ci-dessous dans un [script d'entrée](structure-entry-scripts.md), dans lequel le nom de la classe est déjà donné : + +```php +(new yii\web\Application($config))->run(); +``` + +Plus de détails sur la configuration de la propriété `components` d'une application sont donnés dans la section [Applications](structure-applications.md) et dans la section [Localisateur de services](concept-service-locator.md). + + +### Configurations des objets graphiques + +Lorsque vous utilisez des [objets graphiques](structure-widgets.md), vous avez souvent besoin d'utiliser des configurations pour personnaliser les propriétés de ces objets graphiques. Les méthodes [[yii\base\Widget::widget()]] et [[yii\base\Widget::begin()]] peuvent toutes deux être utilisées pour créer un objet graphique. Elles acceptent un tableau de configuration, comme celui qui suit : + +```php +use yii\widgets\Menu; + +echo Menu::widget([ + 'activateItems' => false, + 'items' => [ + ['label' => 'Home', 'url' => ['site/index']], + ['label' => 'Products', 'url' => ['product/index']], + ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest], + ], +]); +``` + +La configuration ci-dessus crée un objet graphique nommé `Menu` et initialise sa propriété `activateItems` à `false` (faux). La propriété `items` est également configurée avec les items de menu à afficher. + +Notez que, comme le nom de classe est déjà donné, le tableau de configuration ne doit PAS contenir de clé `class`. + +## Fichiers de configuration + +Lorsqu'une configuration est très complexe, une pratique courante est de la stocker dans un ou plusieurs fichiers PHP appelés *fichiers de configuration*. Un fichier de configuration retourne un tableau PHP représentant la configuration. Par exemple, vous pouvez conserver une configuration d'application dans un fichier nommé `web.php`, comme celui qui suit : + +```php +return [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => require(__DIR__ . '/components.php'), +]; +``` + +Parce que la configuration `components` et elle aussi complexe, vous pouvez la stocker dans un fichier séparé appelé `components.php` et requérir ce fichier dans `web.php` comme c'est montré ci-dessus. Le contenu de `components.php` ressemble à ceci : + +```php +return [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], +]; +``` + +Pour obtenir une configuration stockée dans un fichier de configuration, il vous suffit requérir ce fichier avec "require", comme ceci : + +```php +$config = require('path/to/web.php'); +(new yii\web\Application($config))->run(); +``` + + +## Configurations par défaut + +La méthode [[Yii::createObject()]] est implémentée sur la base du [conteneur d'injection de dépendances](concept-di-container.md). Cela vous permet de spécifier un jeu de configurations dites *configurations par défaut* qui seront appliquées à TOUTES les instances des classes spécifiées lors de leur création en utilisant [[Yii::createObject()]]. Les configurations par défaut peuvent être spécifiées en appelant `Yii::$container->set()` dans le code d'[amorçage](runtime-bootstrapping.md). + +Par exemple, si vous voulez personnaliser l'objet graphique [[yii\widgets\LinkPager]] de façon à ce que TOUS les pagineurs affichent au plus 5 boutons de page (la valeur par défaut est 10), vous pouvez utiliser le code suivant pour atteindre ce but : + +```php +\Yii::$container->set('yii\widgets\LinkPager', [ + 'maxButtonCount' => 5, +]); +``` + +Sans les configurations par défaut, vous devez configurer la propriété `maxButtonCount` partout où vous utilisez un pagineur. + + +## Constantes d'environment + +Les configurations varient souvent en fonction de l'environnement dans lequel les applications s'exécutent. Par exemple, dans l'environnement de développement, vous désirez peut être utiliser la base de données nommée `mydb_dev`, tandis que sur un serveur en production, vous désirez utiliser la base de données nommée `mydb_prod`. Pour faciliter le changement d'environnement, Yii fournit une constante nommée `YII_ENV` que vous pouvez définir dans le [script d'entrée](structure-entry-scripts.md) de votre application. Par exemple : + +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +Vous pouvez assigner à `YII_ENV` une des valeurs suivantes : + +- `prod`: environnement de production. La constante `YII_ENV_PROD` est évaluée comme étant `true` (vrai). C'est la valeur par défaut de `YII_ENV`. +- `dev`: environnement de développement. La constante `YII_ENV_DEV` est évaluée comme étant `true` (vrai). +- `test`: environnement de test. La constante `YII_ENV_TEST` est évaluée comme étant `true` (vrai). + +Avec ces constantes d'environnement, vous pouvez spécifier les configurations en fonction de l'environnement courant. Par exemple, votre configuration d'application peut contenir le code suivant pour activer la [barre de débogage et le module de débogage](tool-debugger.md) dans l'environnement de développement seulement : + +```php +$config = [...]; + +if (YII_ENV_DEV) { + // ajustement de la configuration pour l'environnement 'dev' + $config['bootstrap'][] = 'debug'; + $config['modules']['debug'] = 'yii\debug\Module'; +} + +return $config; +``` diff --git a/docs/guide-fr/concept-di-container.md b/docs/guide-fr/concept-di-container.md new file mode 100644 index 0000000..7beaf0a --- /dev/null +++ b/docs/guide-fr/concept-di-container.md @@ -0,0 +1,340 @@ +Conteneur d'injection de dépendances +==================================== + +Un conteneur d'injection de dépendances (DI container) est un objet qui sait comment instancier et configurer des objets et tous leurs objets dépendants. [Cet article de Martin Fowler](http://martinfowler.com/articles/injection.html) explique très bien en quoi un conteneur d'injection de dépendances est utile. Ici nous expliquons essentiellement l'utilisation qui est faite du conteneur d'injection de dépendances que fournit Yii. + + +Injection de dépendances +------------------------ + +Yii fournit la fonctionnalité conteneur d'injection de dépendances via la classe [[yii\di\Container]]. Elle prend en charge les sortes d'injections de dépendance suivantes : + +* Injection par le constructeur ; +* Injection par les méthodes ; +* Injection par les méthodes d'assignation et les propriétés ; +* Injection par une méthode de rappel PHP ; + + +### Injection par le constructeur + +Le conteneur d'injection de dépendances prend en charge l'injection dans le constructeur grâce à l'allusion au type pour les paramètres du constructeur. L'allusion au type indique au conteneur de quelles classes ou de quelles interfaces dépend l'objet concerné par la construction. Le conteneur essaye de trouver les instances des classes dont l'objet dépend pour les injecter dans le nouvel objet via le constructeur. Par exemple : + +```php +class Foo +{ + public function __construct(Bar $bar) + { + } +} + +$foo = $container->get('Foo'); +// qui est équivalent à ce qui suit +$bar = new Bar; +$foo = new Foo($bar); +``` + + +### Injection par les méthodes + +Ordinairement, les classes dont une classe dépend sont passées à son constructeur et sont disponibles dans la classe durant tout son cycle de vie. Avec l'injection par les méthodes, il est possible de fournir une classe qui est seulement nécessaire à une unique méthode de la classe, et qu'il est impossible de passer au constructeur, ou qui pourrait entraîner trop de surplus de travail dans la majorité des classes qui l'utilisent. + +Une méthode de classe peut être définie comme la méthode `doSomething()` de l'exemple suivant : + +```php +class MyClass extends \yii\base\Component +{ + public function __construct(/*ici, quelques classes légères dont la classe dépend*/, $config = []) + { + // ... + } + + public function doSomething($param1, \ma\dependance\Lourde $something) + { + // faire quelque chose avec $something + } +} +``` + +Vous pouvez appeler la méthode, soit en passant une instance de `\ma\dependance\Lourde` vous-même, soit en utilisant [[yii\di\Container::invoke()]] comme ceci : + +```php +$obj = new MyClass(/*...*/); +Yii::$container->invoke([$obj, 'doSomething'], ['param1' => 42]); // $something est fournie par le conteneur d'injection de dépendances +``` + +### Injection par les méthodes d'assignation et les propriétés + +L'injection par les méthodes d'assignation et les propriétés est prise en charge via les [configurations](concept-configurations.md). Lors de l'enregistrement d'une dépendance ou lors de la création d'un nouvel objet, vous pouvez fournir une configuration qui est utilisée par le conteneur pour injecter les dépendances via les méthodes d'assignation ou les propriétés correspondantes. Par exemple : + +```php +use yii\base\Object; + +class Foo extends Object +{ + public $bar; + + private $_qux; + + public function getQux() + { + return $this->_qux; + } + + public function setQux(Qux $qux) + { + $this->_qux = $qux; + } +} + +$container->get('Foo', [], [ + 'bar' => $container->get('Bar'), + 'qux' => $container->get('Qux'), +]); +``` + +> Info: la méthode [[yii\di\Container::get()]] accepte un tableau de configuration qui peut être appliqué à l'objet en création comme troisième paramètre. Si la classe implémente l'interface [[yii\base\Configurable]] (p. ex. [[yii\base\Object]]), le tableau de configuration est passé en tant que dernier paramètre du constructeur de la classe ; autrement le tableau de configuration serait appliqué *après* la création de l'objet. + +### Injection par une méthode de rappel PHP + +Dans ce cas, le conteneur utilise une fonction de rappel PRP enregistrée pour construire de nouvelles instances d'une classe. À chaque fois que [[yii\di\Container::get()]] est appelée, la fonction de rappel correspondante est invoquée. Cette fonction de rappel est chargée de la résolution des dépendances et de leur injection appropriée dans les objets nouvellement créés. Par exemple : + +```php +$container->set('Foo', function () { + $foo = new Foo(new Bar); + // ... autres initialisations ... + return $foo; +}); + +$foo = $container->get('Foo'); +``` + +Pour cacher la logique complexe de construction des nouveaux objets, vous pouvez utiliser un méthode de classe statique en tant que fonction de rappel. Par exemple : + +```php +class FooBuilder +{ + public static function build() + { + $foo = new Foo(new Bar); + // ... autres initialisations ... + return $foo; + } +} + +$container->set('Foo', ['app\helper\FooBuilder', 'build']); + +$foo = $container->get('Foo'); +``` + +En procédant de cette manière, la personne qui désire configurer la classe `Foo` n'a plus besoin de savoir comment elle est construite. + + +Enregistrement des dépendances +------------------------------ + +Vous pouvez utiliser [[yii\di\Container::set()]] pour enregistrer les dépendances. L'enregistrement requiert un nom de dépendance et une définition de dépendance. Un nom de dépendance peut être un nom de classe, un nom d'interface, ou un nom d'alias ; et une définition de dépendance peut être une nom de classe, un tableau de configuration, ou une fonction de rappel PHP. + +```php +$container = new \yii\di\Container; + +// enregistre un nom de classe tel quel. Cela peut être sauté. +$container->set('yii\db\Connection'); + +// enregistre une interface +// Lorsqu'une classe dépend d'une interface, la classe correspondante +// est instanciée en tant qu'objet dépendant +$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); + +// enregistre un nom d'alias. Vous pouvez utiliser $container->get('foo') +// pour créer une instance de Connection +$container->set('foo', 'yii\db\Connection'); + +// enregistre une classe avec une configuration. La configuration +// est appliquée lorsque la classe est instanciée par get() +$container->set('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// enregistre un nom d'alias avec une configuration de classe +// Dans ce cas, un élément "class" est requis pour spécifier la classe +$container->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// enregistre une fonction de rappel PHP +// La fonction de rappel est exécutée à chaque fois que $container->get('db') est appelée +$container->set('db', function ($container, $params, $config) { + return new \yii\db\Connection($config); +}); + +// enregistre une interface de composant +// $container->get('pageCache') retourne la même instance à chaque fois qu'elle est appelée +$container->set('pageCache', new FileCache); +``` + +> Tip: si un nom de dépendance est identique à celui de la définition de dépendance correspondante, vous n'avez pas besoin de l'enregistrer dans le conteneur d'injection de dépendances. + +Une dépendance enregistrée via `set()` génère une instance à chaque fois que la dépendance est nécessaire. Vous pouvez utiliser [[yii\di\Container::setSingleton()]] pour enregistrer une dépendance qui ne génère qu'une seule instance : + +```php +$container->setSingleton('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); +``` + + +Résolution des dépendances +-------------------------- + +Une fois que vous avez enregistré des dépendances, vous pouvez utiliser le conteneur d'injection de dépendances pour créer de nouveau objets, et le conteneur résout automatiquement les dépendances en les instanciant et en les injectant dans les nouveaux objets. Le résolution des dépendances est récursive, ce qui signifie que si une dépendance a d'autres dépendances, ces dépendances sont aussi résolue automatiquement. + +Vous pouvez utiliser [[yii\di\Container::get()]] pour créer de nouveaux objets. La méthode accepte un nom de dépendance qui peut être un nom de classe, un nom d'interface ou un nom d'alias. Le nom de dépendance, peut être, ou ne pas être, enregistré via `set()` ou `setSingleton()`. En option, vous pouvez fournir une liste de paramètre du constructeur de la classe et une [configuration](concept-configurations.md) pour configurer l'objet nouvellement créé. Par exemple : + +```php +// "db" est un nom d'alias enregistré préalablement +$db = $container->get('db'); + +// équivalent à : $engine = new \app\components\SearchEngine($apiKey, $apiSecret, ['type' => 1]); +$engine = $container->get('app\components\SearchEngine', [$apiKey, $apiSecret], ['type' => 1]); +``` + +En coulisses, le conteneur d'injection de dépendances ne fait rien de plus que de créer l'objet. Le conteneur inspecte d'abord le constructeur de la classe pour trouver les classes dépendantes ou les noms d'interface et résout ensuite ces dépendances récursivement. + +Le code suivant montre un exemple plus sophistiqué. La classe `UserLister` dépend d'un objet implémentant l'interface `UserFinderInterface` ; la classe `UserFinder` implémente cet interface et dépend de l'objet `Connection`. Toutes ces dépendances sont déclarées via l'allusion au type des paramètres du constructeur de la classe. Avec l'enregistrement des dépendances de propriétés, le conteneur d'injection de dépendances est capable de résoudre ces dépendances automatiquement et de créer une nouvelle instance de `UserLister` par un simple appel à `get('userLister')`. + +```php +namespace app\models; + +use yii\base\Object; +use yii\db\Connection; +use yii\di\Container; + +interface UserFinderInterface +{ + function findUser(); +} + +class UserFinder extends Object implements UserFinderInterface +{ + public $db; + + public function __construct(Connection $db, $config = []) + { + $this->db = $db; + parent::__construct($config); + } + + public function findUser() + { + } +} + +class UserLister extends Object +{ + public $finder; + + public function __construct(UserFinderInterface $finder, $config = []) + { + $this->finder = $finder; + parent::__construct($config); + } +} + +$container = new Container; +$container->set('yii\db\Connection', [ + 'dsn' => '...', +]); +$container->set('app\models\UserFinderInterface', [ + 'class' => 'app\models\UserFinder', +]); +$container->set('userLister', 'app\models\UserLister'); + +$lister = $container->get('userLister'); + +// qui est équivalent à : + +$db = new \yii\db\Connection(['dsn' => '...']); +$finder = new UserFinder($db); +$lister = new UserLister($finder); +``` + + +Utilisation pratique +-------------------- + +Yii crée un conteneur d'injection de dépendances lorsque vous incluez le fichier `Yii.php` dans le [script d'entrée](structure-entry-scripts.md) de votre application. Le conteneur d'injection de dépendances est accessible via [[Yii::$container]]. Lorsque vous appelez [[Yii::createObject()]], la méthode appelle en réalité la méthode [[yii\di\Container::get()|get()]] du conteneur pour créer le nouvel objet. Comme c'est dit plus haut, le conteneur d'injection de dépendances résout automatiquement les dépendances (s'il en existe) et les injecte dans l'objet créé. Parce que Yii utilise [[Yii::createObject()]] dans la plus grande partie du code de son noyau pour créer de nouveaux objets, cela signifie que vous pouvez personnaliser ces objets globalement en utilisant [[Yii::$container]]. + +Par exemple, vous pouvez personnaliser globalement le nombre de boutons de pagination par défaut de l'objet graphique [[yii\widgets\LinkPager]] : + +```php +\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); +``` + +Maintenant, si vous utilisez l'objet graphique dans une vue avec le code suivant, la propriété `maxButtonCount` est initialisée à la valeur 5 au lieu de la valeur par défaut 10 qui est définie dans la classe. +```php +echo \yii\widgets\LinkPager::widget(); +``` + +Vous pouvez encore redéfinir la valeur définie par le conteneur d'injection de dépendances via : + +```php +echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); +``` + +> Tip: peu importe de quel type de valeur il s'agit, elle est redéfinie, c'est pourquoi vous devez vous montrer prudent avec les tableaux d'options. Ils ne sont pas fusionnés. + +Un autre exemple est de profiter de l'injection automatique par le constructeur du conteneur d'injection de dépendances. Supposons que votre classe de contrôleur dépende de quelques autres objets, comme un service de réservation d'hôtel. Vous pouvez déclarer la dépendance via un paramètre de constructeur et laisser le conteneur d'injection de dépendances la résoudre pour vous. + +```php +namespace app\controllers; + +use yii\web\Controller; +use app\components\BookingInterface; + +class HotelController extends Controller +{ + protected $bookingService; + + public function __construct($id, $module, BookingInterface $bookingService, $config = []) + { + $this->bookingService = $bookingService; + parent::__construct($id, $module, $config); + } +} +``` + +Si vous accédez au contrôleur à partir du navigateur, vous verrez un message d'erreur se plaignant que l'interface `BookingInterface` ne peut pas être instanciée. Cela est dû au fait que vous devez dire au conteneur d'injection de dépendances comment s'y prendre avec cette dépendance : + +```php +\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); +``` + +Maintenant, si vous accédez à nouveau au contrôleur, une instance de `app\components\BookingService` est créée et injectée en tant que troisième paramètre du constructeur. + + +À quel moment enregistrer les dépendances +----------------------------------------- + +Comme les dépendances sont nécessaires lorsque de nouveaux objets sont créés, leur enregistrement doit être fait aussi tôt que possible. Les pratiques recommandées sont : + +* Si vous êtes le développeur d'une application, vous pouvez enregistrer les dépendances dans le [script d'entrée](structure-entry-scripts.md) de votre application ou dans un script qui est inclus par le script d'entrée. +* Si vous êtes le développeur d'une [extension](structure-extensions.md) distribuable, vous pouvez enregistrer les dépendances dans la classe d'amorçage de l'extension. + + +Résumé +------- + +L'injection de dépendances et le [localisateur de services](concept-service-locator.md) sont tous deux des modèles de conception populaires qui permettent des construire des logiciels d'une manière faiblement couplée et plus testable. Nous vous recommandons fortement de lire [l'article de Martin](http://martinfowler.com/articles/injection.html) pour acquérir une compréhension plus profonde de l'injection de dépendances et du localisateur de services. + +Yii implémente son [localisateur de services](concept-service-locator.md) par dessus le conteneur d'injection de dépendances. Lorsqu'un localisateur de services essaye de créer une nouvelle instance d'un objet, il appelle le conteneur d'injection de dépendances. Ce dernier résout les dépendances automatiquement comme c'est expliqué plus haut. + diff --git a/docs/guide-fr/concept-events.md b/docs/guide-fr/concept-events.md new file mode 100644 index 0000000..d0e2dcb --- /dev/null +++ b/docs/guide-fr/concept-events.md @@ -0,0 +1,319 @@ +Événements +========== + +Les événement vous permettent d'injecter du code personnalisé dans le code existant à des points précis de son exécution. Vous pouvez attacher du code personnalisé à un événement de façon à ce que, lorsque l'événement est déclenché, le code s'exécute automatiquement. Par exemple, un objet serveur de courriel peut déclencher un événement `messageSent` (message envoyé) quand il réussit à envoyer un message. Si vous voulez conserver une trace des messages dont l'envoi a réussi, vous pouvez simplement attacher le code de conservation de la trace à l'événement `messageSent`. + +Yii introduit une classe de base appelée [[yii\base\Component]] pour prendre en charge les événements. Si une classe a besoin de déclencher des événements, elle doit êtendre la classe [[yii\base\Component]], ou une de ses classes filles. + + +Gestionnaires d'événements +-------------------------- + +Un gestionnaire d'événement est une [fonction de rappel PHP](http://www.php.net/manual/en/language.types.callable.php) qui est exécutée lorsque l'événement à laquelle elle est attachée est déclenché. Vous pouvez utiliser n'importe laquelle des fonctions de rappel suivantes : + +- une fonction PHP globale spécifiée sous forme de chaîne de caractères (sans les parenthèses) p. ex., `'trim'` ; +- une méthode d'objet spécifiée sous forme de tableau constitué d'un nom d'objet et d'un nom de méthode sous forme de chaîne de caractères (sans les parentèses), p. ex., `[$object, 'methodName']`; +- une méthode d'une classe statique spécifiée sous forme de tableau constitué d'un nom de classe et d'un nom de méthode sous forme de chaîne de caractères (sans les parenthèses), p. ex., `['ClassName', 'methodName']`; +- une fonction anonyme p. ex., `function ($event) { ... }`. + +La signature d'un gestionnaire d'événement est : + +```php +function ($event) { + // $event est un objet de la classe yii\base\Event ou des ses classes filles +} +``` + +Via le paramètre `$event`, un gestionnaire d'événement peut obtenir l'information suivante sur l'événement qui vient de se produire : + +- le [[yii\base\Event::name|nom de l'événement]]; +- l'[[yii\base\Event::sender|émetteur de l'événement]]: l'objet dont la méthode `trigger()` a été appelée ; +- les [[yii\base\Event::data|données personnalisées]]: les données fournies lorsque le gestionnaire d'événement est attaché (les explications arrivent bientôt). + + +Attacher des gestionnaires d'événements +--------------------------------------- + +Vous pouvez attacher un gestionnaire d'événement en appelant la méthode [[yii\base\Component::on()]] du composant. Par exemple : + +```php +$foo = new Foo; + +// le gestionnaire est une fonction globale +$foo->on(Foo::EVENT_HELLO, 'function_name'); + +// le gestionnaire est une méthode d'objet +$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); + +// le gestionnaire est une méthode d'une classe statique +$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// le gestionnaire est un fonction anonyme +$foo->on(Foo::EVENT_HELLO, function ($event) { + // event handling logic +}); +``` + +Vous pouvez aussi attacher des gestionnaires d'événements via les [configurations](concept-configurations.md). Pour plus de détails, reportez-vous à la section [Configurations](concept-configurations.md#configuration-format). + + +Losque vous attachez un gestionnaire d'événement, vous pouvez fournir des données additionnelles telles que le troisième paramètre de [[yii\base\Component::on()]]. Les données sont rendues disponibles au gestionnaire lorsque l'événement est déclenché et que le gestionnaire est appelé. Par exemple : + +```php +// Le code suivant affiche "abc" lorsque l'événement est déclenché +// parce que $event->data contient les données passées en tant que troisième argument à la méthode "on" +$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc'); + +function function_name($event) { + echo $event->data; +} +``` + +Ordre des gestionnaires d'événements +------------------------------------ + +Vous pouvez attacher un ou plusieurs gestionnaires à un seul événement. Lorsqu'un événement est déclenché, les gestionnaires attachés sont appelés dans l'ordre dans lequel ils ont été attachés à l'événement. Si un gestionnaire a besoin d'arrêter l'appel des gestionnaires qui viennent après lui, il doit définir la propriété [[yii\base\Event::handled (géré)]] du paramètre `$event` parameter à `true`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + $event->handled = true; +}); +``` + +Par défaut, un gestionnaire nouvellement attaché est ajouté à la file des gestionnaires de l'événement. En conséquence, le gestionnaire est appelé en dernier lorsque l'événement est déclenché. Pour insérer un événement nouvellement attaché en tête de file pour qu'il soit appelé le premier, vous devez appeler [[yii\base\Component::on()]], en lui passant `false` pour le quatrième paramètre `$append`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + // ... +}, $data, false); +``` + +Déclenchement des événements +---------------------------- + +Les événements sont déclenchés en appelant la méthode [[yii\base\Component::trigger()]]. La méthode requiert un *nom d'événement* et, en option, un objet événement qui décrit les paramètres à passer aux gestionnaires de cet événement. Par exemple : + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class Foo extends Component +{ + const EVENT_HELLO = 'hello'; + + public function bar() + { + $this->trigger(self::EVENT_HELLO); + } +} +``` +Avec le code précédent, tout appel à `bar()` déclenche un événement nommé `hello`. + +> Tip: il est recommandé d'utiliser des constantes de classe pour représenter les noms d'événement. Dans l'exemple qui précède, la constante `EVENT_HELLO` représente l'événement `hello`. Cette approche procure trois avantages. Primo, elle évite les erreurs de frappe. Secondo, elle permet aux événements d'être reconnus par le mécanisme d'auto-complètement des EDI. Tertio, vous pouvez dire quels événements sont pris en charge par une classe en vérifiant la déclaration de ses constantes. + +Parfois, lors du déclenchement d'un événement, vous désirez passer des informations additionnelles aux gestionnaires de cet événement. Par exemple, un serveur de courriels peut souhaiter passer les informations sur le message aux gestionnaires de l'événement `messageSent` pour que ces derniers soient informés de certaines particularités des messages envoyés. Pour ce faire, vous pouvez fournir un objet événement comme deuxième paramètre de la méthode [[yii\base\Component::trigger()]]. L'objet événement doit simplement être une instance de la classe [[yii\base\Event]] ou d'une de ses classes filles. Par exemple : + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class MessageEvent extends Event +{ + public $message; +} + +class Mailer extends Component +{ + const EVENT_MESSAGE_SENT = 'messageSent'; + + public function send($message) + { + // ...sending $message... + + $event = new MessageEvent; + $event->message = $message; + $this->trigger(self::EVENT_MESSAGE_SENT, $event); + } +} +``` + +Lorsque la méthode [[yii\base\Component::trigger()]] est appelée, elle appelle tous les gestionnaires attachés à l'événement nommé. + + +Détacher des gestionnaires d'événements +--------------------------------------- + +Pour détacher un gestionnaire d'un événement, appelez la méthode [[yii\base\Component::off()]]. Par exemple : + +```php +// le gestionnaire est une fonction globale +$foo->off(Foo::EVENT_HELLO, 'function_name'); + +// le gestionnaire est une méthode d'objet +$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); + +// le gestionnaire est une méthode d'une classe statique +$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// le gestionnaire est une fonction anonyme +$foo->off(Foo::EVENT_HELLO, $anonymousFunction); +``` + +Notez qu'en général, vous ne devez pas essayer de détacher une fonction anonyme sauf si vous l'avez stokée quelque part lorsque vous l'avez attachée à un événement. Dans l'exemple ci-dessus, on suppose que la fonctions anonyme est stockée dans une variable nommée `$anonymousFunction`. + +Pour détacher *tous* les gestionnaires d'un événement, appelez simplement la méthode [[yii\base\Component::off()]] sans le deuxième paramètre : + +```php +$foo->off(Foo::EVENT_HELLO); +``` + + +Gestionnaire d'événement au niveau de la classe +----------------------------------------------- + +Les sections précédent décrivent comment attacher un gestionnaire à un événement au *niveau d'une instance*. Parfois, vous désirez répondre à un événement déclenché par *chacune des* instances d'une classe plutôt que par une instance spécifique. Au lieu d'attacher l'événement à chacune des instances, vous pouvez attacher le gestionnaire au *niveau de la classe* en appelant la méthode statique [[yii\base\Event::on()]]. + +Par exemple, un objet [Active Record](db-active-record.md) déclenche un événement [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] +à chaque fois qu'il insère un nouvel enregistrement dans la base de données. Afin de suivre les insertions faites par tous les objets [Active Record](db-active-record.md), vous pouvez utiliser le code suivant : + +```php +use Yii; +use yii\base\Event; +use yii\db\ActiveRecord; + +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' is inserted'); +}); +``` + +Le gestionnaire d'événement est invoqué à chaque fois qu'une instance de la classe [[yii\db\ActiveRecord|ActiveRecord]], ou d'une de ses classes filles, déclenche l'événement [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]]. Dans le gestionnaire, vous pouvez obtenir l'objet qui a déclenché l'événement via `$event->sender`. + +Losqu'un objet déclenche un événement, il commence par appeler les gestionnaires attachés au niveau de l'instance, puis les gestionnaires attachés au niveau de la classe. + +Vous pouvez déclencher un événement au *niveau de la classe* en appelant la méthode statique [[yii\base\Event::trigger()]]. Un événement déclenché au niveau de la classe n'est associé à aucun objet en particulier. En conséquence, il provoque l'appel des gestionnaires attachés au niveau de la classe seulement. Par exemple : + +```php +use yii\base\Event; + +Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { + var_dump($event->sender); // displays "null" +}); + +Event::trigger(Foo::className(), Foo::EVENT_HELLO); +``` + +Notez que, dans ce cas, `$event->sender` fait référence au nom de la classe qui a déclenché l'événement plutôt qu'à une instance de classe. + +> Note: comme les gestionnaires attachés au niveau de la classe répondent aux événements déclenchés par n'importe quelle instance de cette classe, ou de ses classes filles, vous devez utiliser cette fonctionnalité avec précaution, en particulier si la classe est une classe de bas niveau comme la classe [[yii\base\Object]]. + +Pour détacher un gestionnaire attaché au niveau de la classe, appelez [[yii\base\Event::off()]]. Par exemple : + +```php +// détache $handler +Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); + +// détache tous les gestionnaires de Foo::EVENT_HELLO +Event::off(Foo::className(), Foo::EVENT_HELLO); +``` + + +Événement utilisant des interfaces +---------------------------------- + +Il y a encore une manière plus abstraite d'utiliser les événements. Vous pouvez créer une interface séparée pour un événement particulier et l'implémenter dans des classes dans lesquelles vous en avez besoin. + +Par exemple, vous pouvez créer l'interface suivante : + +```php +namespace app\interfaces; + +interface DanceEventInterface +{ + const EVENT_DANCE = 'dance'; +} +``` + +Et ajouter deux classes qui l'implémente : + +```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); + } +} +``` + +Pour gérer l'évenement `EVENT_DANCE` déclenché par n'importe laquelle de ces classes, appelez [[yii\base\Event::on()|Event::on()]] et passez-lui le nom de l'interface comme premier argument : + +```php +Event::on('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) { + Yii::trace(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced +}); +``` + +Vous pouvez déclencher l'événement de ces classes : + +```php +// trigger event for Dog class +Event::trigger(Dog::className(), DanceEventInterface::EVENT_DANCE); + +// trigger event for Developer class +Event::trigger(Developer::className(), DanceEventInterface::EVENT_DANCE); +``` + +Notez bien que vous ne pouvez pas déclencher l'événement de toutes les classes qui implémentent l'interface :, + +```php +// NE FONCTIONNE PAS +Event::trigger('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE); +``` + +Pour détacher le gestionnaire d'événement, appelez [[yii\base\Event::off()|Event::off()]]. Par exemple : + +```php +// détache $handler +Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler); + +// détache tous les gestionnaires de DanceEventInterface::EVENT_DANCE +Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE); +``` + + +Événements globaux +------------------ + +Yii prend en charge ce qu'on appelle les *événements globaux*, qui est une astuce basée sur le mécanisme des événements décrit ci-dessus. L'événement global requiert un singleton accessible globalement tel que l'instance de l'[application](structure-applications.md) elle-même. + +Pour créer l'événement global, un émetteur d'événement appelle la méthode `trigger()` du singleton pour déclencher l'événement au lieu d'appeler la méthode `trigger()` propre à l'émetteur. De façon similaire, les gestionnaires d'événement sont attachés à l'événement sur le singleton. Par exemple : + +```php +use Yii; +use yii\base\Event; +use app\components\Foo; + +Yii::$app->on('bar', function ($event) { + echo get_class($event->sender); // affiche "app\components\Foo" +}); + +Yii::$app->trigger('bar', new Event(['sender' => new Foo])); +``` + +Un avantage de l'utilisation d'événement globaux est que vous n'avez pas besoin d'un objet lorsque vous attachez un gestionnaire à l'événement qui est déclenché par l'objet. Au lieu de cela, vous attachez le gestionnaire et déclenchez l'événement via le singleton (p. ex. l'instance d'application). + +Néanmoins, parce que l'espace de noms des événements globaux est partagé par toutes les parties, vous devez nommer les événements globaux avec prudence, par exemple en introduisant une sorte d'espace de noms (p. ex. "frontend.mail.sent", "backend.mail.sent"). diff --git a/docs/guide-fr/concept-properties.md b/docs/guide-fr/concept-properties.md new file mode 100644 index 0000000..42b533a --- /dev/null +++ b/docs/guide-fr/concept-properties.md @@ -0,0 +1,61 @@ +Propriétés +========== + +En PHP, les variables membres des classes sont aussi appelées *propriétés*. Ces variables font partie de la définition de la classe et sont utilisées pour représenter l'état d'une instance de cette classe (c.-à-d. à différentier une instance de la classe d'une autre). En pratique, vous désirez souvent gérer la lecture et l'écriture de ces propriété d'une manière particulière. Par exemple, vous pouvez désirer qu'une chaîne de caractères soit toujours nettoyée avant de l'assigner à une propriété `label`. Vous *pouvez* utiliser le code suivant pour arriver à cette fin : + +```php +$object->label = trim($label); +``` + +Le revers du code ci-dessus est que vous devez appeler `trim()` partout ou vous voulez définir la propriété `label`. Si, plus tard, la propriété `label` devient sujette à de nouvelles exigences, telles que la première lettre doit être une capitale, vous auriez à modifier toutes les parties de code qui assigne une valeur à la propriété `label`. La répétition de code conduit à des bogues, et c'est une pratique courant de l'éviter autant que faire se peut. + +Pour résoudre ce problème, Yii introduit une classe de base nommée [[yii\base\Object]] qui prend en charge la définition de propriétés sur la base de méthodes d'obtention (*getter*) et de méthode d'assignation (*setters*). Si une classe a besoin de cette fonctionnalité, il suffit qu'elle étende la classe[[yii\base\Object]], ou une de ses classes filles. + +> Info: presque toutes les classes du noyau du framework Yii étendent la classe [[yii\base\Object]] ou une de ses classes filles. Cela veut dire, que chaque fois que vous trouvez une méthode d'obtention ou d'assignation dans une classe du noyau, vous pouvez l'utiliser comme une propriété. + +Une méthode d'obtention est une méthode dont le nom commence par le mot `get` (obtenir) et une méthode d'assignation est une méthode dont le nom commence par le mot `set` (assigner, définir). Le nom après les mots préfixes `get` ou `set` définit le nom d'une propriété. Par exemple, une méthode d'obtention `getLabel` et/ou une méthode d'assignation `setLabel` obtient et assigne, respectivement, une propriété nommée `label`, comme le montre le code suivant : + +```php +namespace app\components; + +use yii\base\Object; + +class Foo extends Object +{ + private $_label; + + public function getLabel() + { + return $this->_label; + } + + public function setLabel($value) + { + $this->_label = trim($value); + } +} +``` + +Pour être tout à fait exact, les méthodes d'obtention et d'assignation créent la propriété `label`, qui dans ce cas fait référence en interne à une propriété privée nommée `_label`. + +Les propriétés définies par les méthodes d'obtention et d'assignation peuvent être utilisées comme des variables membres de la classe. La différence principale est que, lorsqu'une telle propriété est lue, la méthode d'obtention correspondante est appelée ; lorsqu'une valeur est assignée à la propriété, la méthode d'assignation correspondante est appelée. Par exemple : + +```php +// équivalent à $label = $object->getLabel(); +$label = $object->label; + +// équivalent à $object->setLabel('abc'); +$object->label = 'abc'; +``` + +Une propriété définie par une méthode d'obtention (*getter*) sans méthode d'assignation (*setter*) est une propriété *en lecture seule*. Essayer d'assigner une valeur à une telle propriété provoque une exception [[yii\base\InvalidCallException|InvalidCallException]]. De façon similaire, une propriété définie par une méthode d'assignation sans méthode d'obtention est *en écriture seule*. Essayer de lire une telle propriété provoque une exception. Il n'est pas courant d'avoir des propriétés *en écriture seule*. + +Il existe plusieurs règles spéciales pour les propriétés définies via des méthodes d'obtention et d'assignation, ainsi que certaines limitations sur elles. + +* Le nom de telles propriétés sont *insensibles à la casse*. Par exempe, `$object->label` et `$object->Label` sont identiques. Cela est dû au fait que le nom des méthodes dans PHP est insensible à la casse. +* Si le nom d'uen telle propriété est identique à celui d'une variable membre de la classe, la dernier prévaut. Par exemple, si la classe ci-dessus `Foo` possède une variable mommée `label`, alors l'assignation `$object->label = 'abc'` affecte la *variable membre* `label` ; cette ligne ne fait pas appel à la méthode d'assignation `setLabel()`. +* Ces propriétés en prennent pas en charge la visibilité. Cela ne fait aucune différence pour les méthodes d'obtention et d'assignation qui définissent une propriété, que cette propriété soit publique, protégée ou privée. +* Les propriétés peuvent uniquement être définies par des méthodes d'obtention et d'assignation *non-statiques*. Les méthodes statiques ne sont pas traitées de la même manière. +* Un appel normal à la méthode `property_exists()` ne fonctionne pas pour déterminer des propriétés magiques. Vous devez appeler [[yii\base\Object::canGetProperty()|canGetProperty()]] ou [[yii\base\Object::canSetProperty()|canSetProperty()]] respectivement. + +En revenant au problème évoqué au début de ce guide, au lieu d'appeler `trim()` partout où une valeur est assignée à `label`, vous pouvez vous contenter d'appeler `trim()` dans la méthode d'assignation `setLabel()`. Et si une nouvelle exigence apparaît – comme celle de mettre la première lettre en capitale – la méthode `setLabel()` peut être rapidement modifiée sans avoir à toucher à d'autres parties du code. Cet unique modification affecte l'ensemble des assignation de `label`. diff --git a/docs/guide-fr/concept-service-locator.md b/docs/guide-fr/concept-service-locator.md new file mode 100644 index 0000000..17873ba --- /dev/null +++ b/docs/guide-fr/concept-service-locator.md @@ -0,0 +1,101 @@ +Localisateur de services +======================== + +Un localisateur de services est un objet que sait comment fournir toutes sortes de services (ou composants) dont une application peut avoir besoin. Dans le localisateur de services, chaque composant existe seulement sous forme d'une unique instance, identifiée de manière unique par un identifiant. Vous utilisez l'identifiant pour retrouver un composant du localisateur de services. + +Dans Yii, un localisateur de service est simplement une instance de [[yii\di\ServiceLocator]] ou d'une de ses classes filles. + +Le localisateur de service le plus couramment utilisé dans Yii est l'objet *application*, auquel vous avez accès via `\Yii::$app`. Les services qu'il procure, tels les composants `request`, `response` et `urlManager`, sont appelés *composants d'application*. Vous pouvez configurer ces trois composants, ou même les remplacer facilement avec votre propre implémentation, en utilisant les fonctionnalités procurées par le localisateur de services. + +En plus de l'objet application, chaque objet module est aussi un localisateur de services. + +Pour utiliser un localisateur de service, la première étape est d'enregistrer le composant auprès de lui. Un composant peut être enregistré via la méthode [[yii\di\ServiceLocator::set()]]. Le code suivant montre différentes manières d'enregistrer des composants : + +```php +use yii\di\ServiceLocator; +use yii\caching\FileCache; + +$locator = new ServiceLocator; + +// enregistre "cache" en utilisant un nom de classe qui peut être utilisé pour créer un composant +$locator->set('cache', 'yii\caching\ApcCache'); + +// enregistre "db" en utilisant un tableau de configuration qui peut être utilisé pour créer un composant +$locator->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', +]); + +// enregistre "search" en utilisant une fonction anonyme qui construit un composant +$locator->set('search', function () { + return new app\components\SolrService; +}); + +// enregistre "pageCache" en utilisant un composant +$locator->set('pageCache', new FileCache); +``` + +Une fois qu'un composant a été enregistré, vous pouvez y accéder via son identifiant, d'une des deux manières suivantes : + +```php +$cache = $locator->get('cache'); +// ou en alternative +$cache = $locator->cache; +``` + +Comme montré ci-dessus, [[yii\di\ServiceLocator]] vous permet d'accéder à un composant comme à une propriété en utilisant l'identifiant du composant. + +Lorsque vous accédez à un composant pour la première fois, [[yii\di\ServiceLocator]] utilise l'information d'enregistrement du composant pour créer une nouvelle instance du composant et la retourner. Par la suite, si on accède à nouveau au composant, le localisateur de service retourne la même instance. + +Vous pouvez utiliser [[yii\di\ServiceLocator::has()]] pour savoir si un identifiant de composant a déjà été enregistré. Si vous appelez [[yii\di\ServiceLocator::get()]] avec un identifiant invalide, une exception est levée. + + +Comme les localisateurs de services sont souvent créés avec des [configurations](concept-configurations.md), une propriété accessible en écriture, et nommée [[yii\di\ServiceLocator::setComponents()|components]], est fournie. Cela vous permet de configurer et d'enregistrer plusieurs composants à la fois. Le code suivant montre un tableau de configuration qui peut être utilisé pour configurer un localisateur de services (p. ex. une [application](structure-applications.md)) avec les composants `db`, `cache` et `search` : + +```php +return [ + // ... + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', + ], + 'cache' => 'yii\caching\ApcCache', + 'search' => function () { + $solr = new app\components\SolrService('127.0.0.1'); + // ... other initializations ... + return $solr; + }, + ], +]; +``` + +Dans ce qui précède, il y a une façon alternative de configurer le composant `search`. Au lieu d'écrire directement une fonction de rappel PHP qui construit une instance de `SolrService`, vous pouvez utiliser une méthode de classe statique pour retourner une telle fonction de rappel, comme c'est montré ci-dessous : + +```php +class SolrServiceBuilder +{ + public static function build($ip) + { + return function () use ($ip) { + $solr = new app\components\SolrService($ip); + // ... autres initialisations ... + return $solr; + }; + } +} + +return [ + // ... + 'components' => [ + // ... + 'search' => SolrServiceBuilder::build('127.0.0.1'), + ], +]; +``` + +Cette approche alternative est à utiliser de préférence lorsque vous publiez une composant Yii qui encapsule quelques bibliothèques de tierces parties. Vous utilisez la méthode statique comme c'est montré ci-dessus pour représenter la logique complexe de construction de l'objet de tierce partie, et l'utilisateur de votre composant doit seulement appeler la méthode statique pour configurer le composant. diff --git a/docs/guide-fr/db-active-record.md b/docs/guide-fr/db-active-record.md new file mode 100644 index 0000000..c10135e --- /dev/null +++ b/docs/guide-fr/db-active-record.md @@ -0,0 +1,1294 @@ +Enregistrement actif (*Active Record*) +===================================== + +L'[enregistrement actif](http://en.wikipedia.org/wiki/Active_record_pattern) fournit une interface orientée objet pour accéder aux données stockées dans une base de données et les manipuler. Une classe d'enregistrement actif est associée à une table de base de données, une instance de cette classe représente une ligne de cette table, et un *attribut* d'une instance d'enregistrement actif représente la valeur d'une colonne particulière dans cette ligne. Au lieu d'écrire des instructions SQL brutes, vous pouvez accéder aux attributs de l'objet enregistrement actif et appeler ses méthodes pour accéder aux données stockées dans les tables de la base de données et les manipuler. + +Par exemple, supposons que `Customer` soit une classe d'enregistrement actif associée à la table `customer` et que `name` soit une colonne de la table `customer`. Vous pouvez écrire le code suivant pour insérer une nouvelle ligne dans la table `customer` : + +```php +$customer = new Customer(); +$customer->name = 'Qiang'; +$customer->save(); +``` + +Le code ci-dessus est équivalent à l'utilisation de l'instruction SQL brute suivante pour MySQL, qui est moins intuitive, plus propice aux erreurs, et peut même poser des problèmes de compatibilité sur vous utilisez un système de gestion de base données différent. + +```php +$db->createCommand('INSERT INTO `customer` (`name`) VALUES (:name)', [ + ':name' => 'Qiang', +])->execute(); +``` + +Yii assure la prise en charge de l'enregistrement actif (*Active Record*) pour les bases de données relationnelles suivantes : + +* MySQL 4.1 ou versions postérieures : via [[yii\db\ActiveRecord]] +* PostgreSQL 7.3 ou versions postérieures : via [[yii\db\ActiveRecord]] +* SQLite 2 et 3 : via [[yii\db\ActiveRecord]] +* Microsoft SQL Server 2008 ou versions postérieures : via [[yii\db\ActiveRecord]] +* Oracle : via [[yii\db\ActiveRecord]] +* CUBRID 9.3 ou versions postérieures : via [[yii\db\ActiveRecord]] (Notez que, à cause d'un [bogue](http://jira.cubrid.org/browse/APIS-658) dans l'extension CUBRID 9:3, l'entourage des valeurs par des marques de citation ne fonctionne pas, c'est pourquoi vous avez besoin de CUBRID 9.3 à la fois comme client et comme serveur) +* Sphinx : via [[yii\sphinx\ActiveRecord]], requiert l'extension `yii2-sphinx` +* ElasticSearch : via [[yii\elasticsearch\ActiveRecord]], requiert l'extension `yii2-elasticsearch` + +De plus, Yii prend aussi en charge l'enregistrement actif (*Active Record*) avec les bases de données non SQL suivantes : + +* Redis 2.6.12 ou versions postérieures : via [[yii\redis\ActiveRecord]], requiert l'extension `yii2-redis` +* MongoDB 1.3.0 ou versions postérieures: via [[yii\mongodb\ActiveRecord]], requiert l'extension `yii2-mongodb` + +Dans ce tutoriel, nous décrivons essentiellement l'utilisation de l'enregistrement actif pour des bases de données relationnelles. Cependant, la majeure partie du contenu décrit ici est aussi applicable aux bases de données non SQL. + + +## Déclaration des classes d'enregistrement actif (*Active Record*) + +Pour commencer, déclarez une classe d'enregistrement actif en étendant la classe [[yii\db\ActiveRecord]]. Comme chacune des classes d'enregistrement actif est associée à une table de la base de données, dans cette classe, vous devez redéfinir la méthode [[yii\db\ActiveRecord::tableName()|tableName()]] +pour spécifier à quelle table cette classe est associée. Dans l'exemple qui suit, nous déclarons une classe d'enregistrement actif nommée `Customer` pour la table de base de données `customer`. + +```php +namespace app\models; + +use yii\db\ActiveRecord; + +class Customer extends ActiveRecord +{ + const STATUS_INACTIVE = 0; + const STATUS_ACTIVE = 1; + + /** + * @return string le nom de la table associée à cette classe d'enregistrement actif. + */ + public static function tableName() + { + return 'customer'; + } +} +``` + +Les instances d'une classe d'enregistrement actif (*Active Record*) sont considérées comme des [modèles](structure-models.md). Pour cette raison, nous plaçons les classes d'enregistrement actif dans l'espace de noms `app\models`(ou autres espaces de noms prévus pour contenir des classes de modèles). + +Comme la classe [[yii\db\ActiveRecord]] étend la classe [[yii\base\Model]], elle hérite de *toutes* les fonctionnalités d'un [modèle](structure-models.md), comme les attributs, les règles de validation, la sérialisation des données, etc. + + +## Connexion aux bases de données + +Par défaut, l'enregistrement actif utilise le [composant d'application](structure-application-components.md) `db` en tant que [[yii\db\Connection|DB connexion à une base de données]] pour accéder aux données de la base de données et les manipuler. Comme expliqué dans la section [Objets d'accès aux bases de données](db-dao.md), vous pouvez configurer le composant `db` dans la configuration de l'application comme montré ci-dessous : + +```php +return [ + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=testdb', + 'username' => 'demo', + 'password' => 'demo', + ], + ], +]; +``` + +Si vous voulez utiliser une connexion de base de données autre que le composant `db`, vous devez redéfinir la méthode [[yii\db\ActiveRecord::getDb()|getDb()]] : + + +```php +class Customer extends ActiveRecord +{ + // ... + + public static function getDb() + { + // utilise le composant d'application "db2" + return \Yii::$app->db2; + } +} +``` + + +## Requête de données + +Après avoir déclaré une classe d'enregistrement actif, vous pouvez l'utiliser pour faire une requête de données de la table correspondante dans la base de données. Ce processus s'accomplit en général en trois étapes : + +1. Créer un nouvel objet *query* (requête) en appelant la méthode [[yii\db\ActiveRecord::find()]] ; +2. Construire l'objet *query* en appelant des [méthodes de construction de requête](db-query-builder.md#building-queries); +3. Appeler une [méthode de requête](db-query-builder.md#query-methods) pour retrouver les données en terme d'instances d'enregistrement actif. + +Comme vous pouvez le voir, cela est très similaire à la procédure avec le [constructeur de requêtes](db-query-builder.md). La seule différence est que, au lieu d'utiliser l'opérateur `new` pour créer un objet *query* (requête), vous appelez la méthode [[yii\db\ActiveRecord::find()]] pour retourner un nouvel objet *query* qui est de la classe [[yii\db\ActiveQuery]]. + +Ce-dessous, nous donnons quelques exemples qui montrent comment utiliser l'*Active Query* (requête active) pour demander des données : + +```php +// retourne un client (*customer*) unique dont l'identifiant est 123 +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::find() + ->where(['id' => 123]) + ->one(); + +// retourne tous les clients actifs et les classes par leur identifiant +// SELECT * FROM `customer` WHERE `status` = 1 ORDER BY `id` +$customers = Customer::find() + ->where(['status' => Customer::STATUS_ACTIVE]) + ->orderBy('id') + ->all(); + +// retourne le nombre de clients actifs +// SELECT COUNT(*) FROM `customer` WHERE `status` = 1 +$count = Customer::find() + ->where(['status' => Customer::STATUS_ACTIVE]) + ->count(); + +// retourne tous les clients dans un tableau indexé par l'identifiant du client +// SELECT * FROM `customer` +$customers = Customer::find() + ->indexBy('id') + ->all(); +``` + +Dans le code ci-dessus, `$customer` est un objet `Customer` tandis que `$customers` est un tableau d'objets `Customer`. Ils sont tous remplis par les données retrouvées dans la table `customer`. + +> Info: comme la classe [[yii\db\ActiveQuery]] étend la classe [[yii\db\Query]], vous pouvez utiliser *toutes* les méthodes de construction et de requête comme décrit dans la section sur le [constructeur de requête](db-query-builder.md). + +Parce que faire une requête de données par les valeurs de clés primaires ou par jeu de valeurs de colonne est une tâche assez courante, Yii fournit une prise en charge de méthodes raccourcis pour cela : + +- [[yii\db\ActiveRecord::findOne()]]: retourne une instance d'enregistrement actif remplie avec la première ligne du résultat de la requête. +- [[yii\db\ActiveRecord::findAll()]]: retourne un tableau d'instances d'enregistrement actif rempli avec *tous* les résultats de la requête. + +Les deux méthodes acceptent un des formats de paramètres suivants : + +- une valeur scalaire : la valeur est traitée comme la valeur de la clé primaire à rechercher. Yii détermine automatiquement quelle colonne est la colonne de clé primaire en lisant les informations du schéma de la base de données. +- un tableau de valeurs scalaires : le tableau est traité comme les valeurs de clé primaire désirées à rechercher. +- un tableau associatif : les clés sont les noms de colonne et les valeurs sont les valeurs de colonne désirées à rechercher. Reportez-vous au [format haché](db-query-builder.md#hash-format) pour plus de détails. + +Le code qui suit montre comment ces méthodes peuvent être utilisées : + +```php +// retourne un client unique dont l'identifiant est 123 +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// retourne les clients dont les identifiants sont 100, 101, 123 ou 124 +// SELECT * FROM `customer` WHERE `id` IN (100, 101, 123, 124) +$customers = Customer::findAll([100, 101, 123, 124]); + +// retourne un client actif dont l'identifiant est 123 +// SELECT * FROM `customer` WHERE `id` = 123 AND `status` = 1 +$customer = Customer::findOne([ + 'id' => 123, + 'status' => Customer::STATUS_ACTIVE, +]); + +// retourne tous les clients inactifs +// SELECT * FROM `customer` WHERE `status` = 0 +$customers = Customer::findAll([ + 'status' => Customer::STATUS_INACTIVE, +]); +``` + +> Note: ni [[yii\db\ActiveRecord::findOne()]], ni [[yii\db\ActiveQuery::one()]] n'ajoutent `LIMIT 1` à l'instruction SQL générée. Si votre requête peut retourner plusieurs lignes de données, vous devez appeler `limit(1)` explicitement pour améliorer la performance, p. ex., `Customer::find()->limit(1)->one()`. + +En plus d'utiliser les méthodes de construction de requête, vous pouvez aussi écrire du SQL brut pour effectuer une requête de données et vous servir des résultats pour remplir des objets enregistrements actifs. Vous pouvez le faire en appelant la méthode [[yii\db\ActiveRecord::findBySql()]] : + +```php +// retourne tous les clients inactifs +$sql = 'SELECT * FROM customer WHERE status=:status'; +$customers = Customer::findBySql($sql, [':status' => Customer::STATUS_INACTIVE])->all(); +``` + +N'appelez pas de méthodes de construction de requêtes supplémentaires après avoir appelé [[yii\db\ActiveRecord::findBySql()|findBySql()]] car elles seront ignorées. + +## Accès aux données + +Comme nous l'avons mentionné plus haut, les données extraites de la base de données servent à remplir des instances de la classe d'enregistrement actif et chacune des lignes du résultat de la requête correspond à une instance unique de la classe d'enregistrement actif. Vous pouvez accéder accéder aux valeurs des colonnes en accédant aux attributs des instances de la classe d'enregistrement actif, par exemple : + +```php +// "id" et "email" sont les noms des colonnes de la table "customer" +$customer = Customer::findOne(123); +$id = $customer->id; +$email = $customer->email; +``` + +> Note: les attributs de l'instance de la classe d'enregistrement actif sont nommés d'après les noms des colonnes de la table associée en restant sensible à la casse. Yii définit automatiquement un attribut dans l'objet enregistrement actif pour chacune des colonnes de la table associée. Vous ne devez PAS déclarer à nouveau l'un quelconque des ces attributs. + +Comme les attributs de l'instance d'enregistrement actif sont nommés d'après le nom des colonnes, vous pouvez vous retrouver en train d'écrire du code PHP tel que `$customer->first_name`, qui utilise le caractère *souligné* pour séparer les mots dans les noms d'attributs si vos colonnes de table sont nommées de cette manière. Si vous êtes attaché à la cohérence du style de codage, vous devriez renommer vos colonnes de tables en conséquence (p. ex. en utilisant la notation en dos de chameau). + +### Transformation des données + +Il arrive souvent que les données entrées et/ou affichées soient dans un format qui diffère de celui utilisé pour stocker les données dans la base. Par exemple, dans la base de données, vous stockez la date d'anniversaire des clients sous la forme d'horodatages UNIX (bien que ce soit pas une conception des meilleures), tandis que dans la plupart des cas, vous avez envie de manipuler les dates d'anniversaire sous la forme de chaînes de caractères dans le format `'YYYY/MM/DD'`. Pour le faire, vous pouvez définir des méthodes de *transformation de données* dans la classe d'enregistrement actif comme ceci : + +```php +class Customer extends ActiveRecord +{ + // ... + + public function getBirthdayText() + { + return date('Y/m/d', $this->birthday); + } + + public function setBirthdayText($value) + { + $this->birthday = strtotime($value); + } +} +``` + +Désormais, dans votre code PHP, au lieu d'accéder à `$customer->birthday`, vous devez accéder à `$customer->birthdayText`, ce qui vous permet d'entrer et d'afficher les dates d'anniversaire dans le format `'YYYY/MM/DD'`. + +> Tip: l'exemple qui précède montre une manière générique de transformer des données dans différents formats. Si vous travaillez avec des valeurs de dates, vous pouvez utiliser [DateValidator](tutorial-core-validators.md#date) et [[yii\jui\DatePicker|DatePicker]], qui sont plus faciles à utiliser et plus puissants. + + +### Retrouver des données dans des tableaux + +Alors que retrouver des données en termes d'objets enregistrements actifs est souple et pratique, cela n'est pas toujours souhaitable lorsque vous devez extraire une grande quantité de données à cause de l'empreinte mémoire très importante. Dans ce cas, vous pouvez retrouver les données en utilisant des tableaux PHP en appelant [[yii\db\ActiveQuery::asArray()|asArray()]] avant d'exécuter une méthode de requête : + +```php +// retourne tous les clients +// chacun des clients est retourné sous forme de tableau associatif +$customers = Customer::find() + ->asArray() + ->all(); +``` + +> Note: bien que cette méthode économise de la mémoire et améliore la performance, elle est plus proche de la couche d'abstraction basse de la base de données et perd la plupart des fonctionnalité de l'objet enregistrement actif. Une distinction très importante réside dans le type de données des valeurs des colonnes. Lorsque vous retournez des données dans une instance d'enregistrement actif, les valeurs des colonnes sont automatiquement typées en fonction du type réel des colonnes ; par contre, lorsque vous retournez des données dans des tableaux, les valeurs des colonnes sont des chaînes de caractères (parce qu'elles résultent de PDO sans aucun traitement), indépendamment du type réel de ces colonnes. + +### Retrouver des données dans des lots + +Dans la section sur le [constructeur de requêtes](db-query-builder.md), nous avons expliqué que vous pouvez utiliser des *requêtes par lots* pour minimiser l'utilisation de la mémoire lorsque vous demandez de grandes quantités de données de la base de données. Vous pouvez utiliser la même technique avec l'enregistrement actif. Par exemple : + +```php +// va chercher 10 clients (customer) à la fois +foreach (Customer::find()->batch(10) as $customers) { + // $customers est un tableau de 10 (ou moins) objets Customer +} + +// va chercher 10 clients (customers) à la fois et itère sur chacun d'eux +foreach (Customer::find()->each(10) as $customer) { + // $customer est un objet Customer +} + +// requête par lots avec chargement précoce +foreach (Customer::find()->with('orders')->each() as $customer) { + // $customer est un objet Customer avec la relation 'orders' remplie +} +``` + + +## Sauvegarde des données + +En utilisant l'enregistrement actif, vous pouvez sauvegarder facilement les données dans la base de données en suivant les étapes suivantes : + +1. Préparer une instance de la classe d'enregistrement actif +2. Assigner de nouvelles valeurs aux attributs de cette instance +3. Appeler [[yii\db\ActiveRecord::save()]] pour sauvegarder les données dans la base de données. + +Par exemple : + +```php +// insère une nouvelle ligne de données +$customer = new Customer(); +$customer->name = 'James'; +$customer->email = 'james@example.com'; +$customer->save(); + +// met à jour une ligne de données existante +$customer = Customer::findOne(123); +$customer->email = 'james@newexample.com'; +$customer->save(); +``` + +La méthode [[yii\db\ActiveRecord::save()|save()]] peut soit insérer, soit mettre à jour une ligne de données, selon l'état de l'instance de l'enregistrement actif. Si l'instance est en train d'être créée via l'opérateur `new`, appeler [[yii\db\ActiveRecord::save()|save()]] provoque l'insertion d'une nouvelle ligne de données ; si l'instance est le résultat d'une méthode de requête, appeler [[yii\db\ActiveRecord::save()|save()]] met à jour la ligne associée à l'instance. + +Vous pouvez différentier les deux états d'une instance d'enregistrement actif en testant la valeur de sa propriété [[yii\db\ActiveRecord::isNewRecord|isNewRecord]]. Cette propriété est aussi utilisée par [[yii\db\ActiveRecord::save()|save()]] en interne, comme ceci : + +```php +public function save($runValidation = true, $attributeNames = null) +{ + if ($this->getIsNewRecord()) { + return $this->insert($runValidation, $attributeNames); + } else { + return $this->update($runValidation, $attributeNames) !== false; + } +} +``` + +> Tip: vous pouvez appeler [[yii\db\ActiveRecord::insert()|insert()]] ou [[yii\db\ActiveRecord::update()|update()]] directement pour insérer ou mettre à jour une ligne. + + +### Validation des données + +Comme la classe [[yii\db\ActiveRecord]] étend la classe [[yii\base\Model]], elle partage la même fonctionnalité de [validation des données](input-validation.md). Vous pouvez déclarer les règles de validation en redéfinissant la méthode [[yii\db\ActiveRecord::rules()|rules()]] et effectuer la validation des données en appelant la méthode [[yii\db\ActiveRecord::validate()|validate()]]. + +Lorsque vous appelez la méthode [[yii\db\ActiveRecord::save()|save()]], par défaut, elle appelle automatiquement la méthode [[yii\db\ActiveRecord::validate()|validate()]]. C'est seulement si la validation réussit, que les données sont effectivement sauvegardées ; autrement elle retourne simplement `false`, et vous pouvez tester la propriété [[yii\db\ActiveRecord::errors|errors]] pour retrouver les messages d'erreurs de validation. + +> Tip: si vous êtes sûr que vos données n'ont pas besoin d'être validées (p. ex. vos données proviennent de sources fiables), vous pouvez appeler `save(false)` pour omettre la validation. + + +### Assignation massive + +Comme les [modèles](structure-models.md) habituels, les instances d'enregistrement actif profitent de la [fonctionnalité d'assignation massive](structure-models.md#massive-assignment). L'utilisation de cette fonctionnalité vous permet d'assigner plusieurs attributs d'un enregistrement actif en une seule instruction PHP, comme c'est montré ci-dessous. N'oubliez cependant pas que, seuls les [attributs sûrs](structure-models.md#safe-attributes) sont assignables en masse. + +```php +$values = [ + 'name' => 'James', + 'email' => 'james@example.com', +]; + +$customer = new Customer(); + +$customer->attributes = $values; +$customer->save(); +``` + + +### Mise à jour des compteurs + +C'est une tâche courante que d'incrémenter ou décrémenter une colonne dans une table de base de données. Nous appelons ces colonnes « colonnes compteurs*. Vous pouvez utiliser la méthode [[yii\db\ActiveRecord::updateCounters()|updateCounters()]] pour mettre à jour une ou plusieurs colonnes de comptage. Par exemple : + +```php +$post = Post::findOne(100); + +// UPDATE `post` SET `view_count` = `view_count` + 1 WHERE `id` = 100 +$post->updateCounters(['view_count' => 1]); +``` + +> Note: si vous utilisez la méthode [[yii\db\ActiveRecord::save()]] pour mettre à jour une colonne compteur, vous pouvez vous retrouver avec un résultat erroné car il est probable que le même compteur soit sauvegardé par de multiples requêtes qui lisent et écrivent la même valeur de compteur. + + +### Attributs sales (*Dirty Attributes*) + +Lorsque vous appelez la méthode [[yii\db\ActiveRecord::save()|save()]] pour sauvegarder une instance d'enregistrement actif, seuls les attributs dit *attributs sales* sont sauvegardés. Un attribut est considéré comme *sale* si sa valeur a été modifiée depuis qu'il a été chargé depuis la base de données ou sauvegardé dans la base de données le plus récemment. Notez que la validation des données est assurée sans se préoccuper de savoir si l'instance d'enregistrement actif possède des attributs sales ou pas. + +L'enregistrement actif tient à jour la liste des attributs sales. Il le fait en conservant une version antérieure des valeurs d'attribut et en les comparant avec les dernières. Vous pouvez appeler la méthode [[yii\db\ActiveRecord::getDirtyAttributes()]] pour obtenir les attributs qui sont couramment sales. Vous pouvez aussi appeler la méthode [[yii\db\ActiveRecord::markAttributeDirty()]] pour marquer explicitement un attribut comme sale. + +Si vous êtes intéressé par les valeurs d'attribut antérieurs à leur plus récente modification, vous pouvez appeler la méthode [[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] ou la méthode [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]. + +> Note: la comparaison entre les anciennes et les nouvelles valeurs est faite en utilisant l'opérateur `===` , ainsi une valeur est considérée comme sale si le type est différent même si la valeur reste la même. Cela est souvent le cas lorsque le modèle reçoit des entrées utilisateur de formulaires HTML ou chacune des valeurs est représentée par une chaîne de caractères. Pour garantir le type correct pour p. ex. des valeurs entières, vous devez appliquer un [filtre de validation](input-validation.md#data-filtering): +> `['attributeName', 'filter', 'filter' => 'intval']`. Cela fonctionne pour toutes les fonctions de transformation de type de PHP comme [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... + +### Valeurs d'attribut par défaut + +Quelques unes de vos colonnes de tables peuvent avoir des valeurs par défaut définies dans la base de données. Parfois, vous voulez peut-être pré-remplir votre formulaire Web pour un enregistrement actif à partir des valeurs par défaut. Pour éviter d'écrire la même valeur par défaut à nouveau, vous pouvez appeler la méthode [[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] pour remplir les attributs de l'enregistrement actif avec les valeurs par défaut prédéfinies dans la base de données : + +```php +$customer = new Customer(); +$customer->loadDefaultValues(); +// $customer->xyz recevra la valeur par défaut déclarée lors de la définition de la colonne « xyz » column +``` + + +### Mise à jour de plusieurs lignes + +Les méthodes décrites ci-dessus fonctionnent toutes sur des instances individuelles d'enregistrement actif. Pour mettre à jour plusieurs lignes à la fois, vous devez appeler la méthode statique [[yii\db\ActiveRecord::updateAll()|updateAll()]]. + +```php +// UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com%` +Customer::updateAll(['status' => Customer::STATUS_ACTIVE], ['like', 'email', '@example.com']); +``` + +De façon similaire, vous pouvez appeler [[yii\db\ActiveRecord::updateAllCounters()|updateAllCounters()]] pour mettre à jour les colonnes compteurs de plusieurs lignes à la fois. + +```php +// UPDATE `customer` SET `age` = `age` + 1 +Customer::updateAllCounters(['age' => 1]); +``` + + +## Suppression de données + +Pour supprimer une ligne unique de données, commencez par retrouver l'instance d'enregistrement actif correspondant à cette ligne et appelez la méthode [[yii\db\ActiveRecord::delete()]]. + +```php +$customer = Customer::findOne(123); +$customer->delete(); +``` + +Vous pouvez appeler [[yii\db\ActiveRecord::deleteAll()]] pour effacer plusieurs ou toutes les lignes de données. Par exemple : + +```php +Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); +``` + +> Note: soyez très prudent lorsque vous appelez [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] parce que cela peut effacer totalement toutes les données de votre table si vous faites une erreur en spécifiant la condition. + + +## Cycles de vie de l'enregistrement actif + +Il est important que vous compreniez les cycles de vie d'un enregistrement actif lorsqu'il est utilisé à des fins différentes. Lors de chaque cycle de vie, une certaine séquence d'invocation de méthodes a lieu, et vous pouvez redéfinir ces méthodes pour avoir une chance de personnaliser le cycle de vie. Vous pouvez également répondre à certains événements de l'enregistrement actif déclenchés durant un cycle de vie pour injecter votre code personnalisé. Ces événements sont particulièrement utiles lorsque vous développez des [comportements](concept-behaviors.md) d'enregistrement actif qui ont besoin de personnaliser les cycles de vies d'enregistrement actifs. + +Dans l'exemple précédent, nous résumons les différents cycles de vie d'enregistrement actif et les méthodes/événements à qui il est fait appel dans ces cycles. + + +### Cycle de vie d'une nouvelle instance + +Losque vous créez un nouvel enregistrement actif via l'opérateur `new`, le cycle suivant se réalise : + +1. Constructeur de la classe. +2. [[yii\db\ActiveRecord::init()|init()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]]. + + +### Cycle de vie lors d'une requête de données + +Lorsque vous effectuez une requête de données via l'une des [méthodes de requête](#querying-data), chacun des enregistrements actifs nouvellement rempli entreprend le cycle suivant : + +1. Constructeur de la classe. +2. [[yii\db\ActiveRecord::init()|init()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]]. +3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]]. + + +### Cycle de vie lors d'une sauvegarde de données + +En appelant [[yii\db\ActiveRecord::save()|save()]] pour insérer ou mettre à jour une instance d'enregistrement actif, le cycle de vie suivant se réalise : + +1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] . Si la méthode retourne `false` (faux), ou si [[yii\base\ModelEvent::isValid]] est `false`, les étapes suivantes sont sautées. +2. Effectue la validation des données. Si la validation échoue, les étapes après l'étape 3 saut sautées. +3. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]]. +4. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] + ou un événement [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]]. Si la méthode retourne `false` ou si [[yii\base\ModelEvent::isValid]] est `false`, les étapes suivantes sont sautées. +5. Effectue l'insertion ou la mise à jour réelle. +6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] ou un événement [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]]. + + +### Cycle de vie lors d'une suppression de données + +En appelant [[yii\db\ActiveRecord::delete()|delete()]] pour supprimer une instance d'enregistrement actif, le cycle suivant se déroule : + +1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]]. Si la méthode retourne `false` ou si [[yii\base\ModelEvent::isValid]] est `false`, les étapes suivantes sont sautées. +2. Effectue la suppression réelle des données. +3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: déclenche un événement [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]]. + + +> Note: l'appel de l'une des méthodes suivantes n'initie AUCUN des cycles vus ci-dessus parce qu'elles travaillent directement sur la base de données et pas sur la base d'un enregistrement actif : +> +> - [[yii\db\ActiveRecord::updateAll()]] +> - [[yii\db\ActiveRecord::deleteAll()]] +> - [[yii\db\ActiveRecord::updateCounters()]] +> - [[yii\db\ActiveRecord::updateAllCounters()]] + +### Cycle de vie lors du rafraîchissement des données + +En appelant [[yii\db\ActiveRecord::refresh()|refresh()]] pour rafraîchir une instance d'enregistrement actif, l'événement [[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] est déclenché si le rafraîchissement réussit et si la méthode retourne `true`. + + +## Travail avec des transactions + +Il y a deux façons d'utiliser les [transactions](db-dao.md#performing-transactions) lorsque l'on travaille avec un enregistrement actif. + +La première façon consiste à enfermer explicitement les appels des différents méthodes dans un bloc transactionnel, comme ci-dessous : + +```php +$customer = Customer::findOne(123); + +Customer::getDb()->transaction(function($db) use ($customer) { + $customer->id = 200; + $customer->save(); + // ...autres opérations de base de données... +}); + +// ou en alternative + +$transaction = Customer::getDb()->beginTransaction(); +try { + $customer->id = 200; + $customer->save(); + // ...other DB operations... + $transaction->commit(); +} catch(\Exception $e) { + $transaction->rollBack(); + throw $e; +} +``` + +La deuxième façon consiste à lister les opérations de base de données qui nécessitent une prise en charge transactionnelle dans la méthode [[yii\db\ActiveRecord::transactions()]]. Par exemple : + +```php +class Customer extends ActiveRecord +{ + public function transactions() + { + return [ + 'admin' => self::OP_INSERT, + 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, + // ce qui précède est équivalent à ce qui suit : + // 'api' => self::OP_ALL, + ]; + } +} +``` + +La méthode [[yii\db\ActiveRecord::transactions()]] doit retourner un tableau dont les clés sont les noms de [scenario](structure-models.md#scenarios) et les valeurs les opérations correspondantes qui doivent être enfermées dans des transactions. Vous devez utiliser les constantes suivantes pour faire référence aux différentes opérations de base de données : + +* [[yii\db\ActiveRecord::OP_INSERT|OP_INSERT]]: opération d'insertion réalisée par [[yii\db\ActiveRecord::insert()|insert()]]; +* [[yii\db\ActiveRecord::OP_UPDATE|OP_UPDATE]]: opération de mise à jour réalisée par [[yii\db\ActiveRecord::update()|update()]]; +* [[yii\db\ActiveRecord::OP_DELETE|OP_DELETE]]: opération de suppression réalisée par [[yii\db\ActiveRecord::delete()|delete()]]. + +Utilisez l'opérateur `|` pour concaténer les constantes précédentes pour indiquer de multiples opérations. Vous pouvez également utiliser la constante raccourci [[yii\db\ActiveRecord::OP_ALL|OP_ALL]] pour faire référence à l'ensemble des trois opération ci-dessus. + +Les transactions qui sont créées en utilisant cette méthode sont démarrées avant d'appeler [[yii\db\ActiveRecord::beforeSave()|beforeSave()]] et sont entérinées après que la méthode [[yii\db\ActiveRecord::afterSave()|afterSave()]] a été exécutée. + +## Verrous optimistes + +Le verrouillage optimiste est une manière d'empêcher les conflits qui peuvent survenir lorsqu'une même ligne de données est mise à jour par plusieurs utilisateurs. Par exemple, les utilisateurs A et B sont tous deux, simultanément, en train de modifier le même article de wiki. Après que l'utilisateur A a sauvegardé ses modifications, l'utilisateur B clique sur le bouton « Sauvegarder » dans le but de sauvegarder ses modifications lui aussi. Comme l'utilisateur B est en train de travailler sur une version périmée de l'article, il serait souhaitable de disposer d'un moyen de l'empêcher de sauvegarder sa version de l'article et de lui montrer un message d'explication. + +Le verrouillage optimiste résout le problème évoqué ci-dessus en utilisant une colonne pour enregistrer le numéro de version de chacune des lignes. Lorsqu'une ligne est sauvegardée avec un numéro de version périmée, une exception [[yii\db\StaleObjectException]] est levée, ce qui empêche la sauvegarde de la ligne. Le verrouillage optimiste, n'est seulement pris en charge que lorsque vous mettez à jour ou supprimez une ligne de données existante en utilisant les méthodes [[yii\db\ActiveRecord::update()]] ou [[yii\db\ActiveRecord::delete()]],respectivement. + +Pour utiliser le verrouillage optimiste : + +1. Créez une colonne dans la table de base de données associée à la classe d'enregistrement actif pour stocker le numéro de version de chacune des lignes. Le colonne doit être du type *big integer* (dans MySQL ce doit être `BIGINT DEFAULT 0`). +2. Redéfinissez la méthode [[yii\db\ActiveRecord::optimisticLock()]] pour qu'elle retourne le nom de cette colonne. +3. Dans le formulaire Web qui reçoit les entrées de l'utilisateur, ajoutez un champ caché pour stocker le numéro de version courant de la ligne en modification. Assurez-vous que votre attribut *version* dispose de règles de validation et valide correctement. +4. Dans l'action de contrôleur qui met la ligne à jour en utilisant l'enregistrement actif, utiliser une structure *try-catch* pour l'exception [[yii\db\StaleObjectException]]. Mettez en œuvre la logique requise (p. ex. fusionner les modification, avertir des données douteuses) pour résoudre le conflit. +Par exemple, supposons que la colonne du numéro de version est nommée `version`. Vous pouvez mettre en œuvre le verrouillage optimiste avec un code similaire au suivant : + +```php +// ------ view code ------- + +use yii\helpers\Html; + +// ...autres champs de saisie +echo Html::activeHiddenInput($model, 'version'); + + +// ------ controller code ------- + +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) { + // logique de résolution du conflit + } +} +``` + + +## Travail avec des données relationnelles + +En plus de travailler avec des tables de base de données individuelles, l'enregistrement actif permet aussi de rassembler des données en relation, les rendant ainsi immédiatement accessibles via les données primaires. Par exemple, la donnée client est en relation avec les données commandes parce qu'un client peut avoir passé une ou plusieurs commandes. Avec les déclarations appropriées de cette relation, vous serez capable d'accéder aux commandes d'un client en utilisant l'expression `$customer->orders` qui vous renvoie les informations sur les commandes du client en terme de tableau d'instances `Order` (Commande) d'enregistrement actif. + + +### Déclaration de relations + +Pour travailler avec des données relationnelles en utilisant l'enregistrement actif, vous devez d'abord déclarer les relations dans les classes d'enregistrement actif. La tâche est aussi simple que de déclarer une *méthode de relation* pour chacune des relations concernées, comme ceci : + +```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']); + } +} +``` + +Dans le code ci-dessus, nous avons déclaré une relation `orders` (commandes) pour la classe `Customer` (client), et une relation `customer` (client) pour la classe `Order` (commande). + +Chacune des méthodes de relation doit être nommée sous la forme `getXyz`. Nous appelons `xyz` (la première lettre est en bas de casse) le *nom de la relation*. Notez que les noms de relation sont *sensibles à la casse*. + +En déclarant une relation, vous devez spécifier les informations suivantes : + +- la multiplicité de la relation : spécifiée en appelant soit la méthode [[yii\db\ActiveRecord::hasMany()|hasMany()]], soit la méthode [[yii\db\ActiveRecord::hasOne()|hasOne()]]. Dans l'exemple ci-dessus vous pouvez facilement déduire en lisant la déclaration des relations qu'un client a beaucoup de commandes, tandis qu'une commande n'a qu'un client. +- le nom de la classe d'enregistrement actif : spécifié comme le premier paramètre de [[yii\db\ActiveRecord::hasMany()|hasMany()]] ou de [[yii\db\ActiveRecord::hasOne()|hasOne()]]. Une pratique conseillée est d'appeler `Xyz::className()` pour obtenir la chaîne de caractères représentant le nom de la classe de manière à bénéficier de l'auto-complètement de l'EDI et de la détection d'erreur dans l'étape de compilation. +- Le lien entre les deux types de données : spécifie le(s) colonne(s) via lesquelles les deux types de données sont en relation. Les valeurs du tableau sont les colonnes des données primaires (représentées par la classe d'enregistrement actif dont vous déclarez les relations), tandis que les clés sont les colonnes des données en relation. + +Une règle simple pour vous rappeler cela est, comme vous le voyez dans l'exemple ci-dessus, d'écrire la colonne qui appartient à l'enregistrement actif en relation juste à coté de lui. Vous voyez là que l'identifiant du client (`customer_id`) est une propriété de `Order` et `id` est une propriété de `Customer`. + +### Accès aux données relationnelles + +Après avoir déclaré des relations, vous pouvez accéder aux données relationnelles via le nom des relations. Tout se passe comme si vous accédiez à une [propriété](concept-properties.md) d'un objet défini par la méthode de relation. Pour cette raison, nous appelons cette propriété *propriété de relation*. Par exemple : + +```php +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +// $orders est un tableau d'objets Order +$orders = $customer->orders; +``` + +> Info: lorsque vous déclarez une relation nommée `xyz` via une méthode d'obtention `getXyz()`, vous êtes capable d'accéder à `xyz` comme à un [objet property](concept-properties.md). Notez que le nom est sensible à la casse. + +Si une relation est déclarée avec la méthode [[yii\db\ActiveRecord::hasMany()|hasMany()]], l'accès à cette propriété de relation retourne un tableau des instances de l'enregistrement actif en relation ; si une relation est déclarée avec la méthode [[yii\db\ActiveRecord::hasOne()|hasOne()]], l'accès à la propriété de relation retourne l'instance de l'enregistrement actif en relation, ou `null` si aucune donnée en relation n'est trouvée. + +Lorsque vous accédez à une propriété de relation pour la première fois, une instruction SQL est exécutée comme le montre l'exemple précédent. Si la même propriété fait l'objet d'un nouvel accès, le résultat précédent est retourné sans exécuter à nouveau l'instruction SQL. Pour forcer l'exécution à nouveau de l'instruction SQL, vous devez d'abord annuler la définition de la propriété de relation : `unset($customer->orders)`. + +> Note: bien que ce concept semble similaire à la fonctionnalité [propriété d'objet](concept-properties.md), il y a une différence importante. Pour les propriétés normales d'objet, la valeur est du même type que la méthode d'obtention de définition. Une méthode de relation cependant retourne toujours une instance d'[[yii\db\ActiveRecord]] ou un tableau de telles instances. +> +> ```php +> $customer->orders; // est un tableau d'objets `Order` +> $customer->getOrders(); // retourne une instance d'ActiveQuery +> ``` +> +> Cela est utile for créer des requêtes personnalisées, ce qui est décrit dans la section suivante. + + +### Requête relationnelle dynamique + +Parce qu'une méthode de relation retourne une instance d'[[yii\db\ActiveQuery]], vous pouvez continuer à construire cette requête en utilisant les méthodes de construction avant de l'exécuter. Par exemple : + +```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(); +``` + +Contrairement à l'accès à une propriété de relation, chaque fois que vous effectuez une requête relationnelle dynamique via une méthode de relation, une instruction SQL est exécutée, même si la même requête relationnelle dynamique a été effectuée auparavant. + +Parfois, vous voulez peut-être paramétrer une déclaration de relation de manière à ce que vous puissiez effectuer des requêtes relationnelles dynamiques plus facilement. Par exemple, vous pouvez déclarer une relation `bigOrders` comme ceci :, + +```php +class Customer extends ActiveRecord +{ + public function getBigOrders($threshold = 100) + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']) + ->where('subtotal > :threshold', [':threshold' => $threshold]) + ->orderBy('id'); + } +} +``` + +Par la suite, vous serez en mesure d'effectuer les requêtes relationnelles suivantes : + +```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; +``` + + +### Relations via une table de jointure + +Dans la modélisation de base de données, lorsque la multiplicité entre deux tables en relation est *many-to-many* (de plusieurs à plusieurs), une [table de jointure](https://en.wikipedia.org/wiki/Junction_table) est en général introduite. Par exemple, la table `order` (commande) et la table `item` peuvent être en relation via une table de jointure nommée `order_item` (item_de_commande). Une commande correspond ensuite à de multiples items de commande, tandis qu'un item de produit correspond lui-aussi à de multiples items de commande (*order items*). + +Lors de la déclaration de telles relations, vous devez appeler soit [[yii\db\ActiveQuery::via()|via()]], soit [[yii\db\ActiveQuery::viaTable()|viaTable()]], pour spécifier la table de jointure. La différence entre [[yii\db\ActiveQuery::via()|via()]] et [[yii\db\ActiveQuery::viaTable()|viaTable()]] est que la première spécifie la table de jointure en termes de noms de relation existante, tandis que la deuxième utilise directement la table de jointure. Par exemple : + +```php +class Order extends ActiveRecord +{ + public function getItems() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->viaTable('order_item', ['order_id' => 'id']); + } +} +``` + +ou autrement, + +```php +class Order extends ActiveRecord +{ + public function getOrderItems() + { + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); + } + + public function getItems() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->via('orderItems'); + } +} +``` + +L'utilisation de relations déclarées avec une table de jointure est la même que celle de relations normales. Par exemple : + +```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 (...) +// retourne un tableau d'objets Item +$items = $order->items; +``` + + +### Chargement paresseux et chargement précoce + +Dans la sous-section [Accès aux données relationnelles](#accessing-relational-data), nous avons expliqué que vous pouvez accéder à une propriété de relation d'une instance d'enregistrement actif comme si vous accédiez à une propriété normale d'objet. Une instruction SQL est exécutée seulement lorsque vous accédez à cette propriété pour la première fois. Nous appelons une telle méthode d'accès à des données relationnelles, *chargement paresseux*. Par exemple : + +```php +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$orders = $customer->orders; + +// pas de SQL exécuté +$orders2 = $customer->orders; +``` + +Le chargement paresseux est très pratique à utiliser. Néanmoins, il peut souffrir d'un problème de performance lorsque vous avez besoin d'accéder à la même propriété de relation sur de multiples instances d'enregistrement actif. Examinons l'exemple de code suivant. Combien d'instruction SQL sont-elles exécutées ? + +```php +// SELECT * FROM `customer` LIMIT 100 +$customers = Customer::find()->limit(100)->all(); + +foreach ($customers as $customer) { + // SELECT * FROM `order` WHERE `customer_id` = ... + $orders = $customer->orders; +} +``` + +Comme vous pouvez le constater dans le fragment de code ci-dessus, 101 instruction SQL sont exécutées ! Cela tient au fait que, à chaque fois que vous accédez à la propriété de relation `orders` d'un objet client différent dans la boucle for, une instruction SQL est exécutée. + +Pour résoudre ce problème de performance, vous pouvez utiliser ce qu'on appelle le *chargement précoce* comme montré ci-dessous : + +```php +// SELECT * FROM `customer` LIMIT 100; +// SELECT * FROM `orders` WHERE `customer_id` IN (...) +$customers = Customer::find() + ->with('orders') + ->limit(100) + ->all(); + +foreach ($customers as $customer) { + // aucune instruction SQL exécutée + $orders = $customer->orders; +} +``` + +En appelant [[yii\db\ActiveQuery::with()]], vous donner comme instruction à l'enregistrement actif de rapporter les commandes (*orders*) pour les 100 premiers clients (*customers*) en une seule instruction SQL. En conséquence, vous réduisez le nombre d'instructions SQL de 101 à 2 ! + +Vous pouvez charger précocement une ou plusieurs relations. Vous pouvez même charger précocement des *relations imbriquées*. Une relation imbriquée est une relation qui est déclarée dans une classe d'enregistrement actif. Par exemple, `Customer` est en relation avec `Order` via la relation `orders`, et `Order` est en relation avec `Item` via la relation `items`. Lorsque vous effectuez une requête pour `Customer`, vous pouvez charger précocement `items` en utilisant la notation de relation imbriquée `orders.items`. + +Le code suivant montre différentes utilisations de [[yii\db\ActiveQuery::with()|with()]]. Nous supposons que la classe `Customer` possède deux relations `orders` (commandes) et `country` (pays), tandis que la classe `Order` possède une relation `items`. + +```php +// chargement précoce à la fois de "orders" et de "country" +$customers = Customer::find()->with('orders', 'country')->all(); +// équivalent au tableau de syntaxe ci-dessous +$customers = Customer::find()->with(['orders', 'country'])->all(); +// aucune instruction SQL exécutée +$orders= $customers[0]->orders; +// aucune instruction SQL exécutée +$country = $customers[0]->country; + +// chargement précoce de "orders" et de la relation imbriquée "orders.items" +$customers = Customer::find()->with('orders.items')->all(); +// accés aux items de la première commande du premier client +// aucune instruction SQL exécutée +$items = $customers[0]->orders[0]->items; +``` + +Vous pouvez charger précocement des relations imbriquées en profondeur, telles que `a.b.c.d`. Toutes les relations parentes sont chargées précocement. C'est à dire, que lorsque vous appelez [[yii\db\ActiveQuery::with()|with()]] en utilisant `a.b.c.d`, vous chargez précocement `a`, `a.b`, `a.b.c` et `a.b.c.d`. + +> Info: en général, lors du chargement précoce de `N` relations parmi lesquelles `M` relations sont définies par une [table de jointure](#junction-table), `N+M+1` instructions SQL sont exécutées au total. Notez qu'une relation imbriquée `a.b.c.d` possède 4 relations. + +Lorsque vous chargez précocement une relation, vous pouvez personnaliser le requête relationnelle correspondante en utilisant une fonction anonyme. Par exemple : + +```php +// trouve les clients et rapporte leur pays et leurs commandes actives +// SELECT * FROM `customer` +// SELECT * FROM `country` WHERE `id` IN (...) +// SELECT * FROM `order` WHERE `customer_id` IN (...) AND `status` = 1 +$customers = Customer::find()->with([ + 'country', + 'orders' => function ($query) { + $query->andWhere(['status' => Order::STATUS_ACTIVE]); + }, +])->all(); +``` + +Lors de la personnalisation de la requête relationnelle pour une relation, vous devez spécifier le nom de la relation comme une clé de tableau et utiliser une fonction anonyme comme valeur de tableau correspondante. La fonction anonyme accepte une paramètre `$query` qui représente l'objet [[yii\db\ActiveQuery]] utilisé pour effectuer la requête relationnelle pour la relation. Dans le code ci-dessus, nous modifions la requête relationnelle en ajoutant une condition additionnelle à propos de l'état de la commande (*order*). + +> Note: si vous appelez [[yii\db\Query::select()|select()]] tout en chargeant précocement les relations, vous devez vous assurer que les colonnes référencées dans la déclaration de la relation sont sélectionnées. Autrement, les modèles en relation peuvent ne pas être chargés correctement. Par exemple : +> +> ```php +> $orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); +> // $orders[0]->customer est toujours nul. Pour régler le problème, vous devez faire ce qui suit : +> $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +> ``` + + +### Jointure avec des relations + +> Note: le contenu décrit dans cette sous-section ne s'applique qu'aux bases de données relationnelles, telles que MySQL, PostgreSQL, etc. + +Les requêtes relationnelles que nous avons décrites jusqu'à présent ne font référence qu'aux colonnes de table primaires lorsque nous faisons une requête des données primaires. En réalité, nous avons souvent besoin de faire référence à des colonnes dans les tables en relation. Par exemple, vous désirez peut-être rapporter les clients qui ont au moins une commande active. Pour résoudre ce problème, nous pouvons construire une requête avec jointure comme suit : + +```php +// 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(); +``` + +> Note: il est important de supprimer les ambiguïtés sur les noms de colonnes lorsque vous construisez les requêtes relationnelles faisant appel à des instructions SQL JOIN. Une pratique courante est de préfixer les noms de colonnes par le nom des tables correspondantes. + +Néanmoins, une meilleure approche consiste à exploiter les déclarations de relations existantes en appelant [[yii\db\ActiveQuery::joinWith()]] : + +```php +$customers = Customer::find() + ->joinWith('orders') + ->where(['order.status' => Order::STATUS_ACTIVE]) + ->all(); +``` + +Les deux approches exécutent le même jeu d'instructions SQL. La deuxième approche est plus propre et plus légère cependant. + +Par défaut, [[yii\db\ActiveQuery::joinWith()|joinWith()]] utilise `LEFT JOIN` pour joindre la table primaire avec les tables en relation. Vous pouvez spécifier une jointure différente (p .ex. `RIGHT JOIN`) via sont troisième paramètre `$joinType`. Si le type de jointure que vous désirez est `INNER JOIN`, vous pouvez simplement appeler [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]], à la place. + +L'appel de [[yii\db\ActiveQuery::joinWith()|joinWith()]] [charge précocement](#lazy-eager-loading) les données en relation par défaut. Si vous ne voulez pas charger les données en relation, vous pouvez spécifier son deuxième paramètre `$eagerLoading` comme étant `false`. + +Comme avec [[yii\db\ActiveQuery::with()|with()]], vous pouvez joindre une ou plusieurs relations ; vous pouvez personnaliser les requêtes de relation à la volée ; vous pouvez joindre des relations imbriquées ; et vous pouvez mélanger l'utilisation de [[yii\db\ActiveQuery::with()|with()]] et celle de [[yii\db\ActiveQuery::joinWith()|joinWith()]]. Par exemple : + +```php +$customers = Customer::find()->joinWith([ + 'orders' => function ($query) { + $query->andWhere(['>', 'subtotal', 100]); + }, +])->with('country') + ->all(); +``` + +Parfois, en joignant deux tables, vous désirez peut-être spécifier quelques conditions supplémentaires dans la partie `ON` de la requête JOIN. Cela peut être réalisé en appelant la méthode [[yii\db\ActiveQuery::onCondition()]] comme ceci : + +```php +// 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(); +``` + +La requête ci-dessus retourne *tous* les clients, et pour chacun des clients, toutes les commandes actives. Notez que cela est différent de notre exemple précédent qui ne retourne que les clients qui ont au moins une commande active. + +> Info: quand [[yii\db\ActiveQuery]] est spécifiée avec une condition via une jointure [[yii\db\ActiveQuery::onCondition()|onCondition()]], la condition est placée dans la partie `ON` si la requête fait appel à une requête JOIN. Si la requête ne fait pas appel à JOIN, la *on-condition* est automatiquement ajoutée à la partie `WHERE` de la requête. Par conséquent elle peut ne contenir que des conditions incluant des colonnes de la table en relation. + +#### Alias de table de relation + +Comme noté auparavant, lorsque vous utilisez une requête JOIN, vous devez lever les ambiguïtés sur le nom des colonnes. Pour cela, un alias est souvent défini pour une table. Définir un alias pour la requête relationnelle serait possible en personnalisant le requête de relation de la manière suivante : + +```php +$query->joinWith([ + 'orders' => function ($q) { + $q->from(['o' => Order::tableName()]); + }, +]) +``` + +Cela paraît cependant très compliqué et implique soit de coder en dur les noms de tables des objets en relation, soit d'appeler `Order::tableName()`. Depuis la version 2.0.7, Yii fournit un raccourci pour cela. Vous pouvez maintenant définir et utiliser l'alias pour la table de relation comme ceci : + +```php +// joint la relation orders et trie les résultats par orders.id +$query->joinWith(['orders o'])->orderBy('o.id'); +``` + +### Relations inverses + +Les déclarations de relations sont souvent réciproques entre deux classes d'enregistrement actif. Par exemple, `Customer` est en relation avec `Order` via la relation `orders`, et `Order` est en relation inverse avec `Customer` via la relation `customer`. + +```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']); + } +} +``` + +Considérons maintenant ce fragment de code : + +```php +// 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'; +``` + +On aurait tendance à penser que `$customer` et `$customer2` sont identiques, mais ils ne le sont pas ! En réalité, ils contiennent les mêmes données de client, mais ce sont des objets différents. En accédant à `$order->customer`, une instruction SQL supplémentaire est exécutée pour remplir un nouvel objet `$customer2`. + +Pour éviter l'exécution redondante de la dernière instruction SQL dans l'exemple ci-dessus, nous devons dire à Yii que `customer` est une *relation inverse* de `orders` en appelant la méthode [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] comme ci-après : + + +```php +class Customer extends ActiveRecord +{ + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer'); + } +} +``` + +Avec cette déclaration de relation modifiée, nous avons : + +```php +// SELECT * FROM `customer` WHERE `id` = 123 +$customer = Customer::findOne(123); + +// SELECT * FROM `order` WHERE `customer_id` = 123 +$order = $customer->orders[0]; + +// aucune instruction SQL n'est exécutée +$customer2 = $order->customer; + +// affiche "same" +echo $customer2 === $customer ? 'same' : 'not the same'; +``` + +> Note: les relations inverses ne peuvent être définies pour des relations faisant appel à une [table de jointure](#junction-table). C'est à dire que, si une relation est définie avec [[yii\db\ActiveQuery::via()|via()]] ou avec [[yii\db\ActiveQuery::viaTable()|viaTable()]], vous ne devez pas appeler [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] ensuite. + + +## Sauvegarde des relations + +En travaillant avec des données relationnelles, vous avez souvent besoin d'établir de créer des relations entre différentes données ou de supprimer des relations existantes. Cela requiert de définir les valeurs appropriées pour les colonnes qui définissent ces relations. En utilisant l'enregistrement actif, vous pouvez vous retrouver en train d'écrire le code de la façon suivante : + +```php +$customer = Customer::findOne(123); +$order = new Order(); +$order->subtotal = 100; +// ... + +// défninition de l'attribut qui définit la relation "customer" dans Order +$order->customer_id = $customer->id; +$order->save(); +``` + +L'enregistrement actif fournit la méthode [[yii\db\ActiveRecord::link()|link()]]qui vous permet d'accomplir cette tâche plus élégamment : + +```php +$customer = Customer::findOne(123); +$order = new Order(); +$order->subtotal = 100; +// ... + +$order->link('customer', $customer); +``` + +La méthode [[yii\db\ActiveRecord::link()|link()]] requiert que vous spécifiiez le nom de la relation et l'instance d'enregistrement actif cible avec laquelle le relation doit être établie. La méthode modifie les valeurs des attributs qui lient deux instances d'enregistrement actif et les sauvegarde dans la base de données. Dans l'exemple ci-dessus, elle définit l'attribut `customer_id` de l'instance `Order` comme étant la valeur de l'attribut `id` de l'instance `Customer` et le sauvegarde ensuite dans la base de données. + +> Note: vous ne pouvez pas lier deux instances d'enregistrement actif nouvellement créées. + +L'avantage d'utiliser [[yii\db\ActiveRecord::link()|link()]] est même plus évident lorsqu'une relation est définie via une [table de jointure](#junction-table). Par exemple, vous pouvez utiliser le code suivant pour lier une instance de `Order` à une instance de `Item` : + +```php +$order->link('items', $item); +``` + +Le code ci-dessus insère automatiquement une ligne dans la table de jointure `order_item` pour mettre la commande en relation avec l'item. + +> Info: la méthode [[yii\db\ActiveRecord::link()|link()]] n'effectue AUCUNE validation de données lors de la sauvegarde de l'instance d'enregistrement actif affectée. Il est de votre responsabilité de valider toutes les données entrées avant d'appeler cette méthode. + +L'opération opposée à [[yii\db\ActiveRecord::link()|link()]] est [[yii\db\ActiveRecord::unlink()|unlink()]] qui casse une relation existante entre deux instances d'enregistrement actif. Par exemple : + +```php +$customer = Customer::find()->with('orders')->where(['id' => 123])->one(); +$customer->unlink('orders', $customer->orders[0]); +``` + +Par défaut, la méthode [[yii\db\ActiveRecord::unlink()|unlink()]] définit la valeur de la (des) clé(s) qui spécifie(nt) la relation existante à `null`. Vous pouvez cependant, choisir de supprimer la ligne de la table qui contient la valeur de clé étrangère en passant à la méthode la valeur `true` pour le paramètre `$delete`. + +Lorsqu'une table de jointure est impliquée dans une relation, appeler [[yii\db\ActiveRecord::unlink()|unlink()]] provoque l'effacement des clés étrangères dans la table de jointure, ou l'effacement de la ligne correspondante dans la table de jointure si `#delete` vaut `true`. + + +## Relations inter bases de données + +L'enregistrement actif vous permet de déclarer des relations entre les classes d'enregistrement actif qui sont mise en œuvre par différentes bases de données. Les bases de données peuvent être de types différents (p. ex. MySQL and PostgreSQL, ou MS SQL et MongoDB), et elles peuvent s'exécuter sur des serveurs différents. Vous pouvez utiliser la même syntaxe pour effectuer des requêtes relationnelles. Par exemple : + +```php +// Customer est associé à la table "customer" dans la base de données relationnelle (e.g. MySQL) +class Customer extends \yii\db\ActiveRecord +{ + public static function tableName() + { + return 'customer'; + } + + public function getComments() + { + // a customer has many comments + return $this->hasMany(Comment::className(), ['customer_id' => 'id']); + } +} + +// Comment est associé à la collection "comment" dans une base de données MongoDB +class Comment extends \yii\mongodb\ActiveRecord +{ + public static function collectionName() + { + return 'comment'; + } + + public function getCustomer() + { + // un commentaire (comment) a un client (customer) + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); + } +} + +$customers = Customer::find()->with('comments')->all(); +``` + +Vous pouvez utiliser la plupart des fonctionnalités de requêtes relationnelles qui ont été décrites dans cette section. + +> Note: l'utilisation de [[yii\db\ActiveQuery::joinWith()|joinWith()]] est limitée aux bases de données qui permettent les requête JOIN inter bases. Pour cette raison, vous ne pouvez pas utiliser cette méthode dans l'exemple ci-dessus car MongoDB ne prend pas JOIN en charge. + + +## Personnalisation des classes de requête + +Par défaut, toutes les requêtes d'enregistrement actif sont prises en charge par [[yii\db\ActiveQuery]]. Pour utiliser une classe de requête personnalisée dans une classe d'enregistrement actif, vous devez redéfinir la méthode [[yii\db\ActiveRecord::find()]] et retourner une instance de votre classe de requête personnalisée .Par exemple : + +```php +namespace app\models; + +use yii\db\ActiveRecord; +use yii\db\ActiveQuery; + +class Comment extends ActiveRecord +{ + public static function find() + { + return new CommentQuery(get_called_class()); + } +} + +class CommentQuery extends ActiveQuery +{ + // ... +} +``` + +Désormais, à chaque fois que vous effectuez une requête (p. ex. `find()`, `findOne()`) ou définissez une relation (p. ex. `hasOne()`) avec `Comment`, vous travaillez avec une instance de `CommentQuery` au lieu d'une instance d'`ActiveQuery`. + +> Tip: dans les gros projets, il est recommandé que vous utilisiez des classes de requête personnalisées pour contenir la majeure partie de code relatif aux requêtes de manière à ce que les classe d'enregistrement actif puissent être maintenues propres. + +Vous pouvez personnaliser une classe de requête de plusieurs manières créatives pour améliorer votre expérience de la construction de requêtes. Par exemple, vous pouvez définir de nouvelles méthodes de construction de requête dans des classes de requête personnalisées : + +```php +class CommentQuery extends ActiveQuery +{ + public function active($state = true) + { + return $this->andWhere(['active' => $state]); + } +} +``` + +> Note: au lieu d'appeler [[yii\db\ActiveQuery::where()|where()]], vous devez ordinairement appeler [[yii\db\ActiveQuery::andWhere()|andWhere()]] ou [[yii\db\ActiveQuery::orWhere()|orWhere()]] pour ajouter des conditions additionnelles lors de la définition de nouvelles méthodes de construction de requête afin que les conditions existantes ne soient pas redéfinies. + +Cela vous permet d'écrire le code de construction de requêtes comme suit : + +```php +$comments = Comment::find()->active()->all(); +$inactiveComments = Comment::find()->active(false)->all(); +``` + +Vous pouvez aussi utiliser les méthodes de construction de requêtes en définissant des relations avec `Comment` ou en effectuant une requête relationnelle : + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getActiveComments() + { + return $this->hasMany(Comment::className(), ['customer_id' => 'id'])->active(); + } +} + +$customers = Customer::find()->with('activeComments')->all(); + +// ou alternativement + +$customers = Customer::find()->with([ + 'comments' => function($q) { + $q->active(); + } +])->all(); +``` + +> Info: dans Yii 1.1, il existe un concept appelé *scope*. Scope n'est plus pris en charge directement par Yii 2.0, et vous devez utiliser des classes de requête personnalisée et des méthodes de requêtes pour remplir le même objectif. + + +## Sélection de champs supplémentaires + +Quand un enregistrement actif est rempli avec les résultats d'une requête, ses attributs sont remplis par les valeurs des colonnes correspondantes du jeu de données reçu. + +Il vous est possible d'aller chercher des colonnes ou des valeurs additionnelles à partir d'une requête et des les stocker dans l'enregistrement actif. Par exemple, supposons que nous ayons une table nommée `room`, qui contient des informations sur les chambres (rooms) disponibles dans l'hôtel. Chacune des chambres stocke des informations sur ses dimensions géométriques en utilisant des champs `length` (longueur), `width` (largeur) , `height` (hauteur). Imaginons que vous ayez besoin de retrouver une liste des chambres disponibles en les classant par volume décroissant. Vous ne pouvez pas calculer le volume en PHP parce que vous avez besoin de trier les enregistrements par cette valeur, mais vous voulez peut-être aussi que `volume` soit affiché dans la liste. Pour atteindre ce but, vous devez déclarer un champ supplémentaire dans la classe d'enregistrement actif `Room` qui contiendra la valeur de `volume` : + + +```php +class Room extends \yii\db\ActiveRecord +{ + public $volume; + + // ... +} +``` + +Ensuite, vous devez composer une requête qui calcule le volume de la chambre et effectue le tri : + +```php +$rooms = Room::find() + ->select([ + '{{room}}.*', // selectionne toutes les colonnes + '([[length]] * [[width]] * [[height]]) AS volume', // calcule un volume + ]) + ->orderBy('volume DESC') // applique le tri + ->all(); + +foreach ($rooms as $room) { + echo $room->volume; // contient la valeur calculée par SQL +} +``` + +La possibilité de sélectionner des champs supplémentaires peut être exceptionnellement utile pour l'agrégation de requêtes. Supposons que vous ayez besoin d'afficher une liste des clients avec le nombre total de commandes qu'ils ont passées. Tout d'abord, vous devez déclarer une classe `Customer` avec une relation `orders` et un champ supplémentaire pour le stockage du nombre de commandes : + +```php +class Customer extends \yii\db\ActiveRecord +{ + public $ordersCount; + + // ... + + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} +``` + +Ensuite vous pouvez composer une requête qui joint les commandes et calcule leur nombre : + +```php +$customers = Customer::find() + ->select([ + '{{customer}}.*', // selectionne tous les champs de customer + 'COUNT({{order}}.id) AS ordersCount' // calcule le nombre de commandes (orders) + ]) + ->joinWith('orders') // garantit la jointure de la table + ->groupBy('{{customer}}.id') // groupe les résultats pour garantir que la fonction d'agrégation fonctionne + ->all(); +``` + +Un inconvénient de l'utilisation de cette méthode est que si l'information n'est pas chargée dans la requête SQL, elle doit être calculée séparément, ce qui signifie aussi que l'enregistrement nouvellement sauvegardé ne contient les informations d'aucun champ supplémentaire : + +```php +$room = new Room(); +$room->length = 100; +$room->width = 50; +$room->height = 2; + +$room->volume; // cette valeur est `null` puisqu'elle n'a pas encore été déclarée +``` + +En utilisant les méthodes magiques [[yii\db\BaseActiveRecord::__get()|__get()]] et [[yii\db\BaseActiveRecord::__set()|__set()]] nous pouvons émuler le comportement d'une propriété : + +```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; + } + + // ... +} +``` + +Lorsque la requête *select* ne fournit pas le volume, le modèle est pas capable de le calculer automatiquement en utilisant les attributs du modèle. + +De façon similaire, il peut être utilisé sur des champs supplémentaires en fonction des données relationnelles : + +```php +class Customer extends \yii\db\ActiveRecord +{ + 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; + } + + // ... + + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} +``` + diff --git a/docs/guide-fr/db-dao.md b/docs/guide-fr/db-dao.md new file mode 100644 index 0000000..af899f1 --- /dev/null +++ b/docs/guide-fr/db-dao.md @@ -0,0 +1,562 @@ +Objets d'accès aux bases de données +=================================== + +Construits au dessus des [objets de bases de données PHP (PDO – PHP Data Objects)](http://www.php.net/manual/en/book.pdo.php), les objets d'accès aux bases de données de Yii (DAO – Database Access Objects) fournissent une API orientée objets pour accéder à des bases de données relationnelles. C'est la fondation pour d'autres méthodes d'accès aux bases de données plus avancées qui incluent le [constructeur de requêtes (*query builder*)](db-query-builder.md) et l'[enregistrement actif (*active record*)](db-active-record.md). + +Lorsque vous utilisez les objets d'accès aux bases de données de Yii, vous manipulez des requêtes SQL et des tableaux PHP. En conséquence, cela reste le moyen le plus efficace pour accéder aux bases de données. Néanmoins, étant donné que la syntaxe du langage SQL varie selon le type de base de données, l'utilisation des objets d'accès aux bases de données de Yii signifie également que vous avez à faire un travail supplémentaire pour créer une application indifférente au type de base de données. + +Les objets d'accès aux bases de données de Yii prennent en charge les bases de données suivantes sans installation supplémentaire : + +- [MySQL](http://www.mysql.com/) +- [MariaDB](https://mariadb.com/) +- [SQLite](http://sqlite.org/) +- [PostgreSQL](http://www.postgresql.org/): version 8.4 or higher. +- [CUBRID](http://www.cubrid.org/): version 9.3 or higher. +- [Oracle](http://www.oracle.com/us/products/database/overview/index.html) +- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): version 2008 or higher. + + +## Création de connexions à une base de données + +Pour accéder à une base de données, vous devez d'abord vous y connecter en créant une instance de la classe [[yii\db\Connection]] : + +```php +$db = new yii\db\Connection([ + 'dsn' => 'mysql:host=localhost;dbname=example', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); +``` + +Comme souvent vous devez accéder à une base de données en plusieurs endroits, une pratique commune est de la configurer en terme de [composant d'application ](structure-application-components.md) comme ci-après : + +```php +return [ + // ... + 'components' => [ + // ... + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=example', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + ], + // ... +]; +``` + +Vous pouvez ensuite accéder à la base de données via l'expression `Yii::$app->db`. + +> Tip: vous pouvez configurer plusieurs composants d'application « base de données » si votre application a besoin d'accéder à plusieurs bases de données. + +Lorsque vous conifigurez une connexion à une base de données, vous devez toujours spécifier le nom de sa source de données (DSN – Data Source Name) via la propriété [[yii\db\Connection::dsn|dsn]]. Les formats des noms de source de données varient selon le type de base de données. Reportez-vous au [manuel de PHP](http://www.php.net/manual/en/function.PDO-construct.php) pour plus de détails. Ci-dessous, nous donnons quelques exemples : + +* MySQL, MariaDB: `mysql:host=localhost;dbname=mydatabase` +* SQLite: `sqlite:/path/to/database/file` +* PostgreSQL: `pgsql:host=localhost;port=5432;dbname=mydatabase` +* CUBRID: `cubrid:dbname=demodb;host=localhost;port=33000` +* MS SQL Server (via sqlsrv driver): `sqlsrv:Server=localhost;Database=mydatabase` +* MS SQL Server (via dblib driver): `dblib:host=localhost;dbname=mydatabase` +* MS SQL Server (via mssql driver): `mssql:host=localhost;dbname=mydatabase` +* Oracle: `oci:dbname=//localhost:1521/mydatabase` + +Notez que si vous vous connectez à une base de données en utilisant ODBC (Open Database Connectivity), vous devez configurer la propriété [[yii\db\Connection::driverName]] afin que Yii connaisse le type réel de base de données. Par exemple : + +```php +'db' => [ + 'class' => 'yii\db\Connection', + 'driverName' => 'mysql', + 'dsn' => 'odbc:Driver={MySQL};Server=localhost;Database=test', + 'username' => 'root', + 'password' => '', +], +``` + +En plus de la propriété [[yii\db\Connection::dsn|dsn]], vous devez souvent configurer les propriétés [[yii\db\Connection::username|username (nom d'utilisateur)]] et [[yii\db\Connection::password|password (mot de passe)]]. Reportez-vous à [[yii\db\Connection]] pour une liste exhaustive des propriétés configurables. + +> Info: lorsque vous créez une instance de connexion à une base de données, la connexion réelle à la base de données n'est pas établie tant que vous n'avez pas exécuté la première requête SQL ou appelé la méthode [[yii\db\Connection::open()|open()]] explicitement. + +> Tip: parfois, vous désirez effectuer quelques requêtes juste après l'établissement de la connexion à la base de données pour initialiser quelques variables d'environnement (p. ex. pour définir le fuseau horaire ou le jeu de caractères). Vous pouvez le faire en enregistrant un gestionnaire d'événement pour l'événement [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] de la connexion à la base de données. Vous pouvez enregistrer le gestionnaire directement dans la configuration de l'application comme ceci : +> +> ```php +> 'db' => [ +> // ... +> 'on afterOpen' => function($event) { +> // $event->sender refers to the DB connection +> $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); +> } +> ], +> ``` + + +## Execution de requêtes SQL + +Une fois que vous avez une instance de connexion à la base de données, vous pouvez exécuter une requête SQL en suivant les étapes suivantes : + +1. Créer une [[yii\db\Command|commande]] avec une requête SQL simple ; +2. Lier les paramètres (facultatif); +3. Appeler l'une des méthodes d'exécution SQL dans la [[yii\db\Command|commande]]. + +L'exemple qui suit montre différentes façons d'aller chercher des données dans une base de données : + +```php +// retourne un jeu de lignes. Chaque ligne est un tableau associatif (couples clé-valeur) dont les clés sont des noms de colonnes +// retourne un tableau vide si la requête ne retourne aucun résultat +$posts = Yii::$app->db->createCommand('SELECT * FROM post') + ->queryAll(); + +// retourne une ligne unique (la première ligne) +// retourne false si la requête ne retourne aucun résultat +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1') + ->queryOne(); + +// retourne une colonne unique (la première colonne) +//retourne un tableau vide si la requête ne retourne aucun résultat +$titles = Yii::$app->db->createCommand('SELECT title FROM post') + ->queryColumn(); + +// retourne une valeur scalaire +// retourne false si la requête ne retourne aucun résultat +$count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post') + ->queryScalar(); +``` + +> Note: pour préserver la précision, les données extraites des bases de données sont toutes représentées sous forme de chaînes de caractères, même si les colonnes sont de type numérique. + + +### Liaison des paramètres + +Lorsque vous créez une commande de base de données à partir d'une requête SQL avec des paramètres, vous devriez presque toujours utiliser l'approche de liaison des paramètres pour éviter les attaques par injection SQL. Par exemple : + +```php +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') + ->bindValue(':id', $_GET['id']) + ->bindValue(':status', 1) + ->queryOne(); +``` + +Dans l'instruction SQL, vous pouvez incorporer une ou plusieurs valeurs à remplacer pour les paramètres (p. ex. `:id` dans l'exemple ci-dessus). Une valeur à remplacer pour un paramètre doit être une chaîne de caractères commençant par le caractère deux-points `:`. Vous pouvez ensuite appeler l'une des méthodes de liaison de paramètres suivantes pour lier les valeurs de paramètre : + +* [[yii\db\Command::bindValue()|bindValue()]]: lie une unique valeur de paramètre +* [[yii\db\Command::bindValues()|bindValues()]]: lie plusieurs valeurs de paramètre en un seul appel +* [[yii\db\Command::bindParam()|bindParam()]]: similaire à [[yii\db\Command::bindValue()|bindValue()]] mais prend aussi en charge la liaison de références à des paramètres + +L'exemple suivant montre les manières alternatives de lier des paramètres : + +```php +$params = [':id' => $_GET['id'], ':status' => 1]; + +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') + ->bindValues($params) + ->queryOne(); + +$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) + ->queryOne(); +``` + +La liaison des paramètres est implémentée via des [instructions préparées](http://php.net/manual/en/mysqli.quickstart.prepared-statements.php). En plus d'empêcher les attaques par injection SQL, cela peut aussi améliorer la performance en préparant l'instruction SQL une seule fois et l'exécutant de multiples fois avec des paramètres différents. Par exemple : + +```php +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id'); + +$post1 = $command->bindValue(':id', 1)->queryOne(); +$post2 = $command->bindValue(':id', 2)->queryOne(); +// ... +``` + +Comme la méthode [[yii\db\Command::bindParam()|bindParam()]] prend en charge la liaison des paramètres par référence, le code ci-dessus peut aussi être écrit comme suit : + +```php +$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id') + ->bindParam(':id', $id); + +$id = 1; +$post1 = $command->queryOne(); + +$id = 2; +$post2 = $command->queryOne(); +// ... +``` + +Notez que vous devez lier la valeur à remplacer à la variable `$id` avant l'exécution, et ensuite changer la valeur de cette variable avant chacune des exécutions subséquentes (cela est souvent réalisé dans une boucle). L'exécution de requêtes de cette façon peut être largement plus efficace que d'exécuter une nouvelle requête pour chacune des valeurs du paramètre). + + +### Exécution de requête sans sélection + +Les méthodes `queryXyz()` introduites dans les sections précédentes concernent toutes des requêtes SELECT qui retournent des données de la base de données. Pour les instructions qui ne retournent pas de donnée, vous devez appeler la méthode [[yii\db\Command::execute()]] à la place. Par exemple : + +```php +Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') + ->execute(); +``` + +La méthode [[yii\db\Command::execute()]] exécute retourne le nombre de lignes affectées par l'exécution de la requête SQL. + +Pour les requeêtes INSERT, UPDATE et DELETE, au lieu d'écrire des instructions SQL simples, vous pouvez appeler les méthodes [[yii\db\Command::insert()|insert()]], [[yii\db\Command::update()|update()]] ou [[yii\db\Command::delete()|delete()]], respectivement, pour construire les instructions SQL correspondantes. Ces méthodes entourent correctement les noms de tables et de colonnes par des marques de citation et lient les paramètres. Par exemple : + +```php +// INSERT (table name, column values) +Yii::$app->db->createCommand()->insert('user', [ + 'name' => 'Sam', + 'age' => 30, +])->execute(); + +// UPDATE (table name, column values, condition) +Yii::$app->db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); + +// DELETE (table name, condition) +Yii::$app->db->createCommand()->delete('user', 'status = 0')->execute(); +``` + +Vous pouvez aussi appeler [[yii\db\Command::batchInsert()|batchInsert()]] pour insérer plusieurs lignes en un seul coup, ce qui est bien plus efficace que d'insérer une ligne à la fois : + +```php +// noms de table, noms de colonne, valeurs de colonne +Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ + ['Tom', 30], + ['Jane', 20], + ['Linda', 25], +])->execute(); +``` + +Notez que les méthodes mentionnées ci-dessus ne font que créer les requêtes, vous devez toujours appeler [[yii\db\Command::execute()|execute()]] pour les exécuter réellement. + + +## Entourage de noms de table et de colonne par des marque de citation + +Lorsque l'on écrit du code indifférent au type de base de données, entourer correctement les noms table et de colonne avec des marques de citation et souvent un casse-tête parce que les différentes base de données utilisent des règles de marquage de citation différentes. Pour vous affranchir de cette difficulté, vous pouvez utiliser la syntaxe de citation introduite par Yii : + +* `[[column name]]`: entourez un nom de colonne qui doit recevoir les marques de citation par des doubles crochets ; +* `{{table name}}`: entourez un nom de table qui doit recevoir les marques de citation par des doubles accolades ; + +Les objets d'accès aux base de données de Yii convertissent automatiquement de telles constructions en les noms de colonne ou de table correspondants en utilisant la syntaxe spécifique au système de gestion de la base de données. Par exemple : + +```php +// exécute cette instruction SQL pour MySQL: SELECT COUNT(`id`) FROM `employee` +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") + ->queryScalar(); +``` + + +### Utilisation des préfixes de table + +La plupart des noms de table de base de données partagent un préfixe commun. Vous pouvez utiliser la fonctionnalité de gestion du préfixe de noms de table procurée par les objets d'accès aux bases de données de Yii. + +Tout d'abord, spécifiez un préfixe de nom de table via la propriété [[yii\db\Connection::tablePrefix]] dans la configuration de l'application : + +```php +return [ + // ... + 'components' => [ + // ... + 'db' => [ + // ... + 'tablePrefix' => 'tbl_', + ], + ], +]; +``` + +Ensuite dans votre code, à chaque fois que vous faites référence à une table dont le nom commence par ce préfixe, utilisez la syntaxe `{{%table_name}}`. Le caractère pourcentage `%` est automatiquement remplacé par le préfixe que vous avez spécifié dans la configuration de la connexion à la base de données. Par exemple : + +```php +// exécute cette instruction SQL pour MySQL: SELECT COUNT(`id`) FROM `tbl_employee` +$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") + ->queryScalar(); +``` + + +## Réalisation de transactions + +Lorsque vous exécutez plusieurs requêtes liées en séquence, il arrive que vous ayez besoin de les envelopper dans une transactions pour garantir l'intégrité et la cohérence de votre base de données. Si une des requêtes échoue, la base de données est ramenée en arrière dans l'état dans lequel elle se trouvait avant qu'aucune de ces requêtes n'ait été exécutée. + +Le code suivant montre une façon typique d'utiliser les transactions : + +```php +Yii::$app->db->transaction(function($db) { + $db->createCommand($sql1)->execute(); + $db->createCommand($sql2)->execute(); + // ... exécution des autres instruction SQL ... +}); +``` + +Le code précédent est équivalent à celui qui suit, et qui vous donne plus de contrôle sur le code de gestion des erreurs : + +```php +$db = Yii::$app->db; +$transaction = $db->beginTransaction(); + +try { + $db->createCommand($sql1)->execute(); + $db->createCommand($sql2)->execute(); + // ... exécution des autres instruction SQL ... + + $transaction->commit(); + +} catch(\Exception $e) { + + $transaction->rollBack(); + + throw $e; +} +``` + +En appelant la méthode [[yii\db\Connection::beginTransaction()|beginTransaction()]], une nouvelle transaction est démarrée. La transaction est représentée sous forme d'objet [[yii\db\Transaction]] stocké dans la variable `$transaction`. Ensuite, les requêtes à exécuter sont placées dans un bloc `try...catch...`. Si toutes les requêtes réussissent, la méthode [[yii\db\Transaction::commit()|commit()]] est appelée pour entériner la transaction. Autrement, si une exception a été levée et capturée, la méthode [[yii\db\Transaction::rollBack()|rollBack()]] est appelée pour défaire les changements faits par les requêtes de la transaction antérieures à celle qui a échoué. `throw $e` est alors à nouveau exécutée comme si l'exception n'avait jamais été capturée, ce qui permet au processus normal de gestion des erreurs de s'en occuper. + + +### Spécification de niveaux d'isolation + +Yii prend aussi en charge la définition de [niveaux d'isolation] pour vos transactions. Par défaut, lors du démarrage d'une nouvelle transaction, il utilise le niveau d'isolation par défaut défini par votre système de base de données. Vous pouvez redéfinir le niveau d'isolation comme indiqué ci-après : + +```php +$isolationLevel = \yii\db\Transaction::REPEATABLE_READ; + +Yii::$app->db->transaction(function ($db) { + .... +}, $isolationLevel); + +// ou alternativement + +$transaction = Yii::$app->db->beginTransaction($isolationLevel); +``` + +Yii fournit quatre constantes pour les niveaux d'isolation les plus courants : + +- [[\yii\db\Transaction::READ_UNCOMMITTED]] – le niveau le plus faible, des lectures sales (*dirty reads*) , des lectures non répétables) (*non-repeatable reads*) et des lectures phantomes (*phantoms*) peuvent se produire. +- [[\yii\db\Transaction::READ_COMMITTED]] – évite les lectures sales. +- [[\yii\db\Transaction::REPEATABLE_READ]] – évite les lectures sales et les lectures non répétables. +- [[\yii\db\Transaction::SERIALIZABLE]] – le niveau le plus élevé, évite tous les problèmes évoqués ci-dessus. + + + +En plus de l'utilisation des constantes présentées ci-dessus pour spécifier un niveau d'isolation, vous pouvez également utiliser des chaînes de caractères avec une syntaxe valide prise en charges par le système de gestion de base de données que vous utilisez. Par exemple, dans PostgreSQL, vous pouvez utiliser `SERIALIZABLE READ ONLY DEFERRABLE`. + +Notez que quelques systèmes de gestion de base de données autorisent la définition des niveaux d'isolation uniquement au niveau de la connexion tout entière. Toutes les transactions subséquentes auront donc le même niveau d'isolation même si vous n'en spécifiez aucun. En utilisant cette fonctionnalité, vous avez peut-être besoin de spécifier le niveau d'isolation de manière explicite pour éviter les conflits de définition. Au moment d'écrire ces lignes, seules MSSQL et SQLite sont affectées par cette limitation. + +> Note: SQLite ne prend en charge que deux niveaux d'isolation, c'est pourquoi vous ne pouvez utiliser que `READ UNCOMMITTED` et `SERIALIZABLE`. L'utilisation d'autres niveaux provoque la levée d'une exception. + +> Note: PostgreSQL n'autorise pas la définition du niveau d'isolation tant que la transaction n'a pas démarré, aussi ne pouvez-vous pas spécifier le niveau d'isolation directement en démarrant la transaction. Dans ce cas, vous devez appeler [[yii\db\Transaction::setIsolationLevel()]] après que la transaction a démarré. + +[isolation levels]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + + +### Imbrication des transactions + +Si votre système de gestion de base de données prend en charge Savepoint, vous pouvez imbriquer plusieurs transactions comme montré ci-dessous : + +```php +Yii::$app->db->transaction(function ($db) { + // transaction extérieure + + $db->transaction(function ($db) { + // transaction intérieure + }); +}); +``` + +Ou en alternative, + +```php +$db = Yii::$app->db; +$outerTransaction = $db->beginTransaction(); +try { + $db->createCommand($sql1)->execute(); + + $innerTransaction = $db->beginTransaction(); + try { + $db->createCommand($sql2)->execute(); + $innerTransaction->commit(); + } catch (\Exception $e) { + $innerTransaction->rollBack(); + throw $e; + } + + $outerTransaction->commit(); +} catch (\Exception $e) { + $outerTransaction->rollBack(); + throw $e; +} +``` + + +## Réplication et éclatement lecture-écriture + +Beaucoup de systèmes de gestion de bases de données prennent en charge la [réplication de la base de données](http://en.wikipedia.org/wiki/Replication_(computing)#Database_replication) pour obtenir une meilleure disponibilité et des temps de réponse de serveur plus courts. Avec la réplication de la base de données, les données sont répliquées depuis les serveurs dits *serveurs maîtres* vers les serveurs dit *serveurs esclaves*. Toutes les écritures et les mises à jour ont lieu sur les serveurs maîtres, tandis que les lectures ont lieu sur les serveurs esclaves. + +Pour tirer parti de la réplication des bases de données et réaliser l'éclatement lecture-écriture, vous pouvez configurer un composant [[yii\db\Connection]] comme le suivant : + +```php +[ + 'class' => 'yii\db\Connection', + + // configuration pour le maître + 'dsn' => 'dsn pour le serveur maître', + 'username' => 'master', + 'password' => '', + + // configuration commune pour les esclaves + 'slaveConfig' => [ + 'username' => 'slave', + 'password' => '', + 'attributes' => [ + // utilise un temps d'attente de connexion plus court + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // liste des configurations d'esclave + 'slaves' => [ + ['dsn' => 'dsn pour le serveur esclave 1'], + ['dsn' => 'dsn pour le serveur esclave 2'], + ['dsn' => 'dsn pour le serveur esclave 3'], + ['dsn' => 'dsn pour le serveur esclave 4'], + ], +] +``` + +La configuration ci-dessus spécifie une configuration avec un unique maître et de multiples esclaves. L'un des esclaves est connecté et utilisé pour effectuer des requêtes en lecture, tandis que le maître est utilisé pour effectuer les requêtes en écriture. Un tel éclatement lecture-écriture est accompli automatiquement avec cette configuration. Par exemple : + +```php +// crée une instance de Connection en utilisant la configuration ci-dessus +Yii::$app->db = Yii::createObject($config); + +// effectue une requête auprès d'un des esclaves +$rows = Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); + +// effectue une requête auprès du maître +Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); +``` + +> Info: les requêtes effectuées en appelant [[yii\db\Command::execute()]] sont considérées comme des requêtes en écriture, tandis que toutes les autres requêtes faites via l'une des méthodes « *query* » sont des requêtes en lecture. Vous pouvez obtenir la connexion couramment active à un des esclaves via `Yii::$app->db->slave`. + +Le composant `Connection` prend en charge l'équilibrage de charge et de basculement entre esclaves. Lorsque vous effectuez une requête en lecture par la première fois, le composant `Connection` sélectionne un esclave de façon aléatoire et essaye de s'y connecter. Si l'esclave set trouvé « mort », il en essaye un autre. Si aucun des esclaves n'est disponible, il se connecte au maître. En configurant un [[yii\db\Connection::serverStatusCache|cache d'état du serveur]], le composant mémorise le serveur « mort » et ainsi, pendant un [[yii\db\Connection::serverRetryInterval|certain intervalle de temps]], n'essaye plus de s'y connecter. + +> Info: dans la configuration précédente, un temps d'attente de connexion de 10 secondes est spécifié pour chacun des esclaves. Cela signifie que, si un esclave ne peut être atteint pendant ces 10 secondes, il est considéré comme « mort ». Vous pouvez ajuster ce paramètre en fonction de votre environnement réel. + + +Vous pouvez aussi configurer plusieurs maîtres avec plusieurs esclaves. Par exemple : + + +```php +[ + 'class' => 'yii\db\Connection', + + // configuration commune pour les maîtres + 'masterConfig' => [ + 'username' => 'master', + 'password' => '', + 'attributes' => [ + // utilise un temps d'attente de connexion plus court + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // liste des configurations de maître + 'masters' => [ + ['dsn' => 'dsn for master server 1'], + ['dsn' => 'dsn for master server 2'], + ], + + // configuration commune pour les esclaves + 'slaveConfig' => [ + 'username' => 'slave', + 'password' => '', + 'attributes' => [ + // use a smaller connection timeout + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // liste des configurations d'esclave + 'slaves' => [ + ['dsn' => 'dsn for slave server 1'], + ['dsn' => 'dsn for slave server 2'], + ['dsn' => 'dsn for slave server 3'], + ['dsn' => 'dsn for slave server 4'], + ], +] +``` + +La configuration ci-dessus spécifie deux maîtres et quatre esclaves. Le composant `Connection` prend aussi en charge l'équilibrage de charge et le basculement entre maîtres juste comme il le fait pour les esclaves. Une différence est que, si aucun des maîtres n'est disponible, une exception est levée. + +> Note: lorsque vous utilisez la propriété [[yii\db\Connection::masters|masters]] pour configurer un ou plusieurs maîtres, toutes les autres propriétés pour spécifier une connexion à une base de données (p. ex. `dsn`, `username`, `password`) avec l'objet `Connection` lui-même sont ignorées. + + +Par défaut, les transactions utilisent la connexion au maître. De plus, dans une transaction, toutes les opérations de base de données utilisent la connexion au maître. Par exemple : + +```php +$db = Yii::$app->db; +// la transaction est démarrée sur la connexion au maître +$transaction = $db->beginTransaction(); + +try { + // les deux requêtes sont effectuées auprès du maître + $rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); + $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); + + $transaction->commit(); +} catch(\Exception $e) { + $transaction->rollBack(); + throw $e; +} +``` + +Si vous voulez démarrer une transaction avec une connexion à un esclave, vous devez le faire explicitement, comme ceci : + +```php +$transaction = Yii::$app->db->slave->beginTransaction(); +``` + +Parfois, vous désirez forcer l'utilisation de la connexion au maître pour effectuer une requête en lecture . Cela est possible avec la méthode `useMaster()` : + +```php +$rows = Yii::$app->db->useMaster(function ($db) { + return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); +}); +``` + +Vous pouvez aussi définir directement `Yii::$app->db->enableSlaves` à `false` (faux) pour rediriger toutes les requêtes vers la connexion au maître. + + +## Travail avec le schéma de la base de données + +Les objets d'accès aux bases de données de Yii DAO fournissent un jeu complet de méthodes pour vous permettre de manipuler le schéma de la base de données, comme créer de nouvelles tables, supprimer une colonne d'une table, etc. Ces méthodes sont listées ci-après : + +* [[yii\db\Command::createTable()|createTable()]]: crée une table +* [[yii\db\Command::renameTable()|renameTable()]]: renomme une table +* [[yii\db\Command::dropTable()|dropTable()]]: supprime une table +* [[yii\db\Command::truncateTable()|truncateTable()]]: supprime toutes les lignes dans une table +* [[yii\db\Command::addColumn()|addColumn()]]: ajoute une colonne +* [[yii\db\Command::renameColumn()|renameColumn()]]: renomme une colonne +* [[yii\db\Command::dropColumn()|dropColumn()]]: supprime une colonne +* [[yii\db\Command::alterColumn()|alterColumn()]]: modifie une colonne +* [[yii\db\Command::addPrimaryKey()|addPrimaryKey()]]: ajoute une clé primaire +* [[yii\db\Command::dropPrimaryKey()|dropPrimaryKey()]]: supprime une clé primaire +* [[yii\db\Command::addForeignKey()|addForeignKey()]]: ajoute un clé étrangère +* [[yii\db\Command::dropForeignKey()|dropForeignKey()]]: supprime une clé étrangère +* [[yii\db\Command::createIndex()|createIndex()]]: crée un index +* [[yii\db\Command::dropIndex()|dropIndex()]]: supprime un index + +Ces méthodes peuvent être utilisées comme suit : + +```php +// CREATE TABLE +Yii::$app->db->createCommand()->createTable('post', [ + 'id' => 'pk', + 'title' => 'string', + 'text' => 'text', +]); +``` + +Le tableau ci-dessus décrit le nom et le type des colonnes à créer. Pour les types de colonne, Yii fournit un jeu de types de donnée abstraits, qui permettent de définir un schéma de base de données indifférent au type de base de données. Ces types sont convertis en définition de types spécifiques au système de gestion de base de données qui dépendent de la base de données dans laquelle la table est créée. Reportez-vous à la documentation de l'API de la méthode [[yii\db\Command::createTable()|createTable()]] pour plus d'informations. + +En plus de changer le schéma de la base de données, vous pouvez aussi retrouver les informations de définition d'une table via la méthode [[yii\db\Connection::getTableSchema()|getTableSchema()]] d'une connexion à une base de données. Par exemple : + +```php +$table = Yii::$app->db->getTableSchema('post'); +``` + +La méthode retourne un objet [[yii\db\TableSchema]] qui contient les information sur les colonnes de la table, les clés primaires, les clés étrangères, etc. Toutes ces informations sont essentiellement utilisées par le [constructeur de requêtes](db-query-builder.md) et par l'[enregistrement actif](db-active-record.md) pour vous aider à écrire du code indifférent au type de la base de données. diff --git a/docs/guide-fr/db-migrations.md b/docs/guide-fr/db-migrations.md new file mode 100644 index 0000000..e44a4ac --- /dev/null +++ b/docs/guide-fr/db-migrations.md @@ -0,0 +1,823 @@ +Migrations de base de données +============================= + +Durant la période de développement et de maintenance d'une application s'appuyant sur une base de données, la structure de la base de données évolue tout comme le code source. Par exemple, durant développement une nouvelle table peut devenir nécessaire ; après que l'application est déployée en production, on peut s'apercevoir qu'un index doit être créé pour améliorer la performance des requêtes ; et ainsi de suite. Comme un changement dans la base de données nécessite souvent des changements dans le code, Yii prend en charge une fonctionnalité qu'on appelle *migrations de base de données*. Cette fonctionnalité permet de conserver la trace des changements de la base de données en termes de *migrations de base de données* dont les versions sont contrôlées avec celles du code. + +Les étapes suivantes montrent comment des migrations de base de données peuvent être utilisées par une équipe durant la phase de développement : + +1. Tim crée une nouvelle migration (p. ex. créer une nouvelle table, changer la définition d'une colonne, etc.). +2. Tim entérine (commit) la nouvelle migration dans le système de contrôle de version (p. ex. Git, Mercurial). +3. Doug met à jour son dépôt depuis le système de contrôle de version et reçoit la nouvelle migration. +4. Doug applique la migration à sa base de données de développement locale, et ce faisant synchronise sa base de données pour refléter les changements que Tim a faits. + +Les étapes suivantes montrent comment déployer une nouvelle version avec les migrations de base de données en production : + +1. Scott crée une balise de version pour le dépôt du projet qui contient quelques nouvelles migrations de base de données. +2. Scott met à jour le code source sur le serveur de production à la version balisée. +3. Scott applique toutes les migrations accumulées à la base de données de production. + +Yii fournit un jeu de commandes de migration en ligne de commande qui vous permet de : + +* créer de nouvelles migrations; +* appliquer les migrations; +* défaire les migrations; +* ré-appliquer les migrations; +* montrer l'historique de l'état des migrations. + +Tous ces outils sont accessibles via la commande `yii migrate`. Dans cette section nous décrivons en détails comment accomplir des tâches variées en utilisant ces outils. Vous pouvez aussi obtenir les conseils d'utilisation de chacun des outils via la commande d'aide `yii help migrate`. + +> Tip: les migrations peuvent non seulement affecter le schéma de base de données mais aussi ajuster les données existantes pour s'adapter au nouveau schéma, créer la hiérarchie RBAC (Role Based Acces Control - Contrôle d'accès basé sur les rôles), ou vider le cache. + + +## Création de migrations + +Pour créer une nouvelle migration, exécutez la commande suivante : + +``` +yii migrate/create +``` + +L'argument `name` requis donne une brève description de la nouvelle migration. Par exemple, si la création concerne la création d'une nouvelle table nommée *news*, vous pouvez utiliser le nom `create_news_table` et exécuter la commande suivante : + +``` +yii migrate/create create_news_table +``` + +> Note: comme l'argument `name` est utilisé comme partie du nom de la classe migration générée, il ne doit contenir que des lettres, des chiffre et/ou des caractères *souligné*. + +La commande ci-dessus crée une nouvelle classe PHP nommée `m150101_185401_create_news_table.php` dans le dossier `@app/migrations`. Le fichier contient le code suivant qui déclare principalement une classe de migration `m150101_185401_create_news_table` avec le squelette de code suivant : + +```php +_`, dans lequel : + +* `` fait référence à l'horodatage UTC auquel la commande de création de la migration a été exécutée. +* `` est le même que la valeur que vous donnez à l'argument `name` dans la commande. + +Dans la classe de migration, vous devez écrire du code dans la méthode `up()` qui effectue les modifications dans la structure de la base de données. Vous désirez peut-être écrire du code dans la méthode `down()` pour défaire les changements apportés par `up()`. La méthode `up()` est invoquée lorsque vous mettez à jour la base de données avec la migration, tandis que la méthode `down()` est invoquée lorsque vous ramenez la base de données à l'état antérieur. Le code qui suit montre comment mettre en œuvre la classe de migration pour créer une table `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: toutes les migrations ne sont pas réversibles. Par exemple, si la méthode `up()` supprime une ligne dans une table, il se peut que vous soyez incapable de récupérer cette ligne dans la méthode `down()`. Parfois, vous pouvez simplement être trop paresseux pour implémenter la méthode `down`, parce que défaire une migration de base de données n'est pas chose courante. Dans ce cas, vous devriez retourner `false` dans la méthode `down()` pour indiquer que la migration n'est pas réversible. + +La classe de migration de base [[yii\db\Migration]] expose une connexion à une base de données via la propriété [[yii\db\Migration::db|db]]. Vous pouvez utiliser cette connexion pour manipuler le schéma en utilisant les méthodes décrites dans la sous-section [Travail avec le schéma de base de données](db-dao.md#database-schema). + +Plutôt que d'utiliser des types physiques, lors de la création d'une table ou d'une colonne, vous devez utiliser des *types abstraits* afin que vos migrations soient indépendantes d'un système de gestion de base de données en particulier. La classe [[yii\db\Schema]] définit une jeu de constantes pour représenter les types abstraits pris en charge. Ces constantes sont nommées dans le format `TYPE_`. Par exemple, `TYPE_PK` fait référence au type clé primaire à auto-incrémentation ; `TYPE_STRING` fait référence au type chaîne de caractères. Lorsqu'une migration est appliquée à une base de données particulière, le type abstrait est converti dans le type physique correspondant. Dans le cas de MySQL, `TYPE_PK` est transformé en `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, tandis que `TYPE_STRING` est transformé en `varchar(255)`. + +Vous pouvez ajouter des contraintes additionnelles lors de l'utilisation des types abstraits. Dans l'exemple ci-dessus,` NOT NULL` est ajouté à `Schema::TYPE_STRING` pour spécifier que la colonne ne peut être *null* (nulle). + +> Info: la mise en correspondance entre les types abstraits et les types physiques est spécifiée par la propriété [[yii\db\QueryBuilder::$typeMap|$typeMap]] dans chacune des classes `QueryBuilder` concrètes. + +Depuis la version 2.0.6, vous pouvez utiliser le constructeur de schéma récemment introduit qui procure un moyen plus pratique de définir le schéma d'une colonne. Ainsi, la migration ci-dessus pourrait s'écrire comme ceci : + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +Une liste de toutes les méthodes disponibles pour définir les types de colonne est disponible dans la documentation de l'API de [[yii\db\SchemaBuilderTrait]]. + + +## Génération des migrations + +Depuis la version 2.0.7, la commande de migration procure un moyen pratique de créer des migrations. + +Si le nom de la migration est d'une forme spéciale, par exemple, `create_xxx_table` ou `drop_xxx_table` alors le fichier de la migration générée contient du code supplémentaire, dans ce cas pour créer/supprimer des tables. Dans ce qui suit, toutes les variantes de cette fonctionnalité sont décrites. + +### Création d'une table + +```php +yii migrate/create create_post_table +``` + +génère + +```php +/** + * prend en charge la création de la table `post`. + */ +class m150811_220037_create_post_table extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +Pour créer les champs de table tout de suite, spécifiez les via l'option `--fields`. + +```php +yii migrate/create create_post_table --fields="title:string,body:text" +``` + +génère + +```php +/** + * prend en charge la création de la table `post`. + */ +class m150811_220037_create_post_table 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'); + } +} + +``` + +Vous pouvez spécifier plus de paramètres de champs. + +```php +yii migrate/create create_post_table --fields="title:string(12):notNull:unique,body:text" +``` + +génère + +```php +/** + * prend en charge la création de la table `post`. + */ +class m150811_220037_create_post_table 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: par défaut, une clé primaire nommée `id` est ajoutée automatiquement. Si vous voulez utiliser un autre nom, vous devez le spécifier explicitement comme dans `--fields="name:primaryKey"`. + +#### Clés étrangères + +Depuis 2.0.8 le générateur prend en charge les clés étrangères en utilisant le mot clé `foreignKey`. + +```php +yii migrate/create create_post_table --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" +``` + +génère + +```php +/** + * prend en charge la création de la table `post`. + * possède des clés étrangères vers les tables + * + * - `user` + * - `category` + */ +class m160328_040430_create_post_table 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(), + ]); + + // crée un index pour la colonne `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); + + // ajoute une clé étrangère vers la table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + + // crée un index pour la colonne `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); + + // ajoute une clé étrangère vers la table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // supprime la clé étrangère vers la table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // supprime l'index pour la colonne `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // supprime la clé étrangère vers la table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // supprime l'index pour la colonne `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} +``` + +La position du mot clé `foreignKey` dans la description de la colonne ne change pas le code généré. Ce qui signifie que les expressions : + +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +génèrent toutes le même code. + +Le mot clé `foreignKey` accepte un paramètre entre parenthèses qui est le nom de la table en relation pour la clé étrangère générée. Si aucun paramètre n'est passé, le nom de table est déduit du nom de la colonne. + +Dans l'exemple ci-dessus `author_id:integer:notNull:foreignKey(user)` génère une colonne nommée `author_id` avec une clé étrangère pointant sur la table `user`, tandis que `category_id:integer:defaultValue(1):foreignKey` génère une colonne `category_id` avec une clé étrangère pointant sur la table `category`. + +### Suppression de tables + +```php +yii migrate/create drop_post_table --fields="title:string(12):notNull:unique,body:text" +``` + +génère + +```php +class m150811_220037_drop_post_table 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() + ]); + } +} +``` + +### Ajout de colonnes + +Si le nom de la migration est de la forme `add_xxx_column_to_yyy_table` alors le fichier doit contenir les instructions `addColumn` et `dropColumn` nécessaires. + +Pour ajouter une colonne : + +```php +yii migrate/create add_position_column_to_post_table --fields="position:integer" +``` + +génère + +```php +class m150811_220037_add_position_column_to_post_table extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### Supprimer une colonne + +Si le nom de la migration est de la forme `drop_xxx_column_from_yyy_table` alors le fichier doit contenir les instructions `addColumn` et `dropColumn` néessaires. + +```php +yii migrate/create drop_position_column_from_post_table --fields="position:integer" +``` + +génère + +```php +class m150811_220037_drop_position_column_from_post_table extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +### Ajout d'une table de jointure + +Si le nom de la migration est de la forme `create_junction_table_for_xxx_and_yyy_tables` ou `create_junction_xxx_and_yyy_tables`, alors le code nécessaire à la création de la table de jointure est généré. + +```php +yii migrate/create create_junction_table_for_post_and_tag_tables --fields="created_at:dateTime" +``` + +génère + +```php +/** + * prend en charge la création de la table `post_tag`. + * possède des clés étrangères vers les tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_table_for_post_and_tag_tables 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)', + ]); + + // crée un index pour la colonne`post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // ajoute un clé étrangère vers la table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // crée un index pour la colonne `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // ajoute une clé étrangère vers la table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // supprime la clé étrangère vers la table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // supprime l'index pour la colonne `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // supprime la clé étrangère vers la table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // supprime l'index pour la column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +``` + +### Migrations transactionnelles + +En effectuant des migration de base de données complexes, il est important de garantir que chacune des migrations soit réussisse, soit échoue dans son ensemble, de manière à ce que la base de données reste cohérente et intègre. Pour atteindre ce but, il est recommandé que vous englobiez les opérations de base de données de chacune des migrations dans une [transaction](db-dao.md#performing-transactions). + +Une manière encore plus aisée pour mettre en œuvre des migrations transactionnelles est de placer le code de migration dans les méthodes `safeUp()` et `safeDown()`. Ces deux méthodes diffèrent de `up()` et `down()` par le fait qu'elles sont implicitement englobées dans une transaction. En conséquence, si n'importe quelle opération de ces méthodes échoue, toutes les opérations antérieures à elle sont automatiquement défaites. + +Dans l'exemple suivant, en plus de créer la table `news`, nous insérons une ligne initiale dans cette 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'); + } +} +``` + +Notez que, généralement, si vous effectuez de multiples opérations de base de données dans `safeUp()`, vous devriez les défaire dans `safeDown()`. Dans l'exemple ci-dessus, dans `safeUp()`, nous créons d'abord la table puis nous insérons une ligne, tandis que, dans `safeDown`, nous commençons par supprimer la ligne, puis nous supprimons la table. + +> Note: tous les systèmes de gestion de bases de données NE prennent PAS en charge les transactions. De plus, quelques requêtes de base de données ne peuvent être placées dans une transaction. Pour quelques exemples, reportez-vous à [entérinement implicite](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). Si c'est le cas, vous devez simplement mettre en œuvre `up()` et`down()`, à la place. + + +### Méthodes d'accès aux bases de données + +La classe de base de migration [[yii\db\Migration]] fournit un jeu de méthodes pour vous permettre d'accéder aux bases de données et de les manipuler. Vous vous apercevrez que ces méthodes sont nommées de façon similaires aux [méthodes d'objets d'accès aux données](db-dao.md) fournies par la classe [[yii\db\Command]]. Par exemple, la méthode [[yii\db\Migration::createTable()]] vous permet de créer une nouvelle table, tout comme [[yii\db\Command::createTable()]]. + +L'avantage d'utiliser les méthodes fournies par [[yii\db\Migration]] est que vous n'avez pas besoin de créer explicitement des instances de [[yii\db\Command]] et que l'exécution de chacune des méthodes affiche automatiquement des messages utiles vous indiquant que les opérations de base de données sont effectuées et combien de temps ces opérations ont pris. + +Ci-dessous, nous présentons la liste de toutes les méthodes d'accès aux bases de données : + +* [[yii\db\Migration::execute()|execute()]]: exécute une instruction SQL +* [[yii\db\Migration::insert()|insert()]]: insère une unique ligne +* [[yii\db\Migration::batchInsert()|batchInsert()]]: insère de multiples lignes +* [[yii\db\Migration::update()|update()]]: met à jour des lignes +* [[yii\db\Migration::delete()|delete()]]: supprime des lignes +* [[yii\db\Migration::createTable()|createTable()]]: crée une table +* [[yii\db\Migration::renameTable()|renameTable()]]: renomme une table +* [[yii\db\Migration::dropTable()|dropTable()]]: supprime une table +* [[yii\db\Migration::truncateTable()|truncateTable()]]: supprime toutes les lignes d'une table +* [[yii\db\Migration::addColumn()|addColumn()]]: ajoute une colonne +* [[yii\db\Migration::renameColumn()|renameColumn()]]: renomme une colonne +* [[yii\db\Migration::dropColumn()|dropColumn()]]: supprime une colonne +* [[yii\db\Migration::alterColumn()|alterColumn()]]: modifie une colonne +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: ajoute une clé primaire +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: supprime une clé primaire +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: ajoute une clé étrangère +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: supprime une clé étrangère +* [[yii\db\Migration::createIndex()|createIndex()]]: crée un index +* [[yii\db\Migration::dropIndex()|dropIndex()]]: supprime un index +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: ajoute un commentaire à une colonne +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: supprime un commentaire d'une colonne +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: ajoute un commentaire à une table +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: supprime un commentaire d'une table + +> Info: [[yii\db\Migration]] ne fournit pas une méthode de requête de base de données. C'est parce que, normalement, vous n'avez pas besoin d'afficher de messages supplémentaire à propos de l'extraction de données dans une base de données. C'est aussi parce que vous pouvez utiliser le puissant [constructeur de requêtes](db-query-builder.md) pour construire et exécuter des requêtes complexes. + +> Note: lors de la manipulation des données en utilisant une migration, vous pouvez trouver qu'utiliser vos classes d'[enregistrement actif](db-active-record.md) pour cela peut être utile parce qu'une partie de la logique y est déjà mise en œuvre. Soyez conscient cependant que, par contraste avec le code écrit dans les migrations, dont la nature est de rester constant à jamais, la logique d'application est sujette à des changements. Ainsi, en utilisant un enregistrement actif dans du code de migration, les changements apportés à la logique dans la couche enregistrement actif peuvent casser accidentellement des migrations existantes. Pour cette raison, le code doit être gardé indépendant de toute autre logique d'application telle que les classes d'enregistrement actif. + + +## Application des migrations + +Pour mettre une base de données à jour à sa dernière structure, vous devez appliquer toutes les nouvelles migrations disponibles en utilisant la commande suivante : + +``` +yii migrate +``` + +Cette commande liste toutes les migrations qui n'ont pas encore été appliquées. Si vous confirmez que vous voulez appliquer ces migrations, cela provoque l'exécution des méthodes `up()` ou `safeUp()` de chacune des nouvelles migrations, l'une après l'autre, dans l'ordre de leur horodatage. Si l'une de ces migrations échoue, la commande se termine sans appliquer les migrations qui restent. + +> Tip: dans le cas où votre serveur ne vous offre pas de ligne de commande, vous pouvez essayer [Web shell](https://github.com/samdark/yii2-webshell). + +Pour chaque migration qui n'a pas été appliqué avec succès, la commande insère une ligne dans une table de base de données nommée `migration` pour enregistrer les applications réussies de la migration. Cela permet à l'outil de migration d'identifier les migrations qui ont été appliquées et celles qui ne l'ont pas été. + +> Info: l'outil de migration crée automatiquement la table de `migration` dans la base de données spécifiée par l'option [[yii\console\controllers\MigrateController::db|db]] de la commande. Par défaut, la base de données est spécifiée dans le [composant d'application](structure-application-components.md) `db`. + +Parfois, vous désirez peut-être appliquer une ou quelques migrations plutôt que toutes les migrations disponibles. Vous pouvez le faire en spécifiant le nombre de migrations que vous voulez appliquer en exécutant la commande. Par exemple, la commande suivante essaye d'appliquer les trois prochaines migrations disponibles : + +``` +yii migrate 3 +``` + +Vous pouvez également spécifier explicitement une migration particulière à laquelle la base de données doit être amenée en utilisant la commande `migrate/to` dans l'un des formats suivants : + +``` +yii migrate/to 150101_185401 # utiliser l'horodatage pour spécifier la migration +yii migrate/to "2015-01-01 18:54:01" # utilise une chaîne de caractères qui peut être analysée par strtotime() +yii migrate/to m150101_185401_create_news_table # utilise le nom complet +yii migrate/to 1392853618 # utilise un horodatage UNIX +``` + +S'il existe des migrations non appliquée antérieures à celle spécifiée, elles sont toutes appliquées avant que la migration spécifiée ne le soit. + +Si la migration spécifiée a déjà été appliquée auparavant, toutes les migrations postérieures qui ont été appliquées sont défaites. + + +## Défaire des migrations + +Pour défaire une ou plusieurs migrations que ont été appliquées auparavant, vous pouvez exécuter la commande suivante : + +``` +yii migrate/down # défait la migration appliquée le plus récemment +yii migrate/down 3 # défait les 3 migrations appliquées le plus récemment + +> Note: toutes les migrations ne sont PAS réversibles. Essayer de défaire de telles migrations provoque une erreur et arrête tout le processus de retour à l'état initial. + + +## Refaire des migrations + +Refaire (ré-appliquer) des migrations signifie d'abord défaire les migrations spécifiées puis les appliquer à nouveau. Cela peut être fait comme suit : + +``` +yii migrate/redo # refait la dernière migration appliquée +yii migrate/redo 3 # refait les 3 dernière migrations appliquées +``` + +> Note: si une migration n'est pas réversible, vous ne serez pas en mesure de la refaire. + + +## Lister des migrations + +Pour lister quelles migrations ont été appliquées et quelles migrations ne l'ont pas été, vous pouvez utiliser les commandes suivantes : + +``` +yii migrate/history # montre les 10 dernières migrations appliquées +yii migrate/history 5 # montre les 5 dernières migrations appliquées +yii migrate/history all # montre toutes les migrations appliquées + +yii migrate/new # montre les 10 premières nouvelles migrations +yii migrate/new 5 # montre les 5 premières nouvelles migrations +yii migrate/new all # montre toutes les nouvelles migrations +``` + + +## Modification de l'historique des migrations + +Au lieu d'appliquer ou défaire réellement des migrations, parfois, vous voulez peut-être simplement marquer que votre base de données a été portée à une certaine migration. Cela arrive souvent lorsque vous changer manuellement la base de données pour l'amener à un état particulier et que vous ne voulez pas que la migration correspondant à ce changement soit appliquée de nouveau par la suite. Vous pouvez faire cela avec la commande suivante : +``` +yii migrate/mark 150101_185401 # utilise un horodatage pour spécifier la migration +yii migrate/mark "2015-01-01 18:54:01" # utilise une chaîne de caractères qui peut être analysée par strtotime() +yii migrate/mark m150101_185401_create_news_table # utilise le nom complet +yii migrate/mark 1392853618 # utilise un horodatage UNIX +``` + +La commande modifie la table `migration` en ajoutant ou en supprimant certaines lignes pour indiquer que la base de données s'est vue appliquer toutes les migrations jusqu'à celle spécifiée. Aucune migration n'est appliquée ou défaite par cette commande. + +## Personnalisation des migrations + +Il y a plusieurs manières de personnaliser la commande de migration. + + +### Utilisation des options de ligne de commande + +La commande de migration possède quelques options en ligne de commande qui peuvent être utilisées pour personnaliser son comportement : + +* `interactive`: boolean (valeur par défaut `true`), spécifie si la migration doit être effectuées en mode interactif. Lorsque cette option est `true`, l'utilisateur reçoit un message avant que la commande n'effectue certaines actions. Vous désirez peut-être définir cette valeur à `false` si la commande s'exécute en arrière plan. + +* `migrationPath`: string (valeur par défaut `@app/migrations`), spécifie le dossier qui stocke tous les fichiers de classe de migration. Cela peut être spécifié soit comme un chemin de dossier, soit comme un [alias](concept-aliases.md) de chemin. Notez que le dossier doit exister sinon la commande déclenche une erreur. + +* `migrationTable`: string (valeur par défaut `migration`), spécifie le nom de la table de base de données pour stocker l'historique de migration. La table es créée automatiquement par la commande si elle n'existe pas encore. Vous pouvez aussi la créer à la main en utilisant la structure `version varchar(255) primary key, apply_time integer`. + +* `db`: string (valeur par défaut `db`), spécifie l'identifiant du [composant d'application](structure-application-components.md) base de données. Il représente la base de données à laquelle les migrations sont appliquées avec cette commande. + +* `templateFile`: string (valeur par défaut `@yii/views/migration.php`), spécifie le chemin vers le fichier modèle qui est utilisé pour générer le squelette des fichiers de classe de migration. Cela peut être spécifié soit sous forme de chemin de fichier, soit sous forme d'[alias](concept-aliases.md).de chemin. Le fichier modèle est un script PHP dans lequel vous pouvez utiliser une variable prédéfinie nommée `$className` pour obtenir le nom de la classe de migration. + +* `generatorTemplateFiles`: array (valeur par défaut `[ + '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' + ]`), spécifie les fichiers modèles pour générer le code de migration. Voir "[Génération des migrations](#generating-migrations)" pour plus de détails. + +* `fields`: array (tableau) de chaîne de caractères de définition de colonnes utilisées pour créer le code de migration. Valeur par défaut `[]`. Le format de chacune des définitions est `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Par exemple, `--fields=name:string(12):notNull` produit une colonne chaîne de caractères de taille 12 qui n'est pas nulle. + +L'exemple suivant montre comment vous pouvez utiliser ces options. + +Par exemple, si vous voulez appliquer des migrations à un module `forum` dont les fichiers de migration sont situés dans le dossier `migrations` du module, vous pouvez utiliser la commande suivante : + +``` +# appliquer les migrations d'un module forum sans interactivité +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 +``` + + +### Configuration globale des commandes + +Au lieu de répéter les mêmes valeurs d'option à chaque fois que vous exécutez une commande de migration, vous pouvez la configurer une fois pour toute dans la configuration de l'application comme c'est montré ci-après : + +```php +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], + ], +]; +``` + +Avec la configuration ci-dessus, à chaque fois que vous exécutez la commande de migration, la table `backend_migration` est utilisée pour enregistrer l'historique de migration. Vous n'avez plus besoin de le spécifier via l'option en ligne de commande `migrationTable`. + + +## Migration de multiples base de données + +Par défaut, les migrations sont appliquées à la même base de données spécifiée par le [composant d'application](structure-application-components.md) `db`. Si vous voulez que celles-ci soient appliquées à des bases de données différentes, vous pouvez spécifier l'option en ligne de commande `db` comme indiqué ci-dessous : + +``` +yii migrate --db=db2 +``` + +La commande ci-dessus applique les migration à la base de données `db2`. + +Parfois, il est possible que vous vouliez appliquer *quelques unes* des migrations à une base de données, et *quelques autres* à une autre base de données. Pour y parvenir, lorsque vous implémentez une classe de migration, vous devez spécifier explicitement l'identifiant du composant base de données que la migration doit utiliser, comme ceci : + +```php +db = 'db2'; + parent::init(); + } +} +``` + +La migration ci-dessus est appliquée à `db2`, même si vous spécifiez une autre base via l'option en ligne de commande `db`. Notez que l'historique de migration est toujours enregistré dans la base de données spécifiée par l'option en ligne de commande `db`. + +Si vous avez de multiples migrations qui utilisent la même base de données, il est recommandé que vous créiez une classe de migration de base avec le code `init()` ci-dessus. Ensuite, chaque classe de migration peut étendre cette classe de base. + +> Tip: en plus de définir la propriété [[yii\db\Migration::db|db]], vous pouvez aussi opérer sur différentes bases de données en créant de nouvelles connexions à ces bases de données dans vos classes de migration. Ensuite,vous utilisez les [méthodes des objets d'accès aux bases de données](db-dao.md) avec ces connexions pour manipuler différentes bases de données. + +Une autre stratégie que vous pouvez adopter pour appliquer des migrations à de multiples bases de données est de tenir ces migrations de différentes bases de données dans des chemins différents. Ensuite vous pouvez appliquer les migrations à ces bases de données dans des commandes séparées comme ceci : + +``` +yii migrate --migrationPath=@app/migrations/db1 --db=db1 +yii migrate --migrationPath=@app/migrations/db2 --db=db2 +... +``` + +La première commande applique les migrations dans `@app/migrations/db1` à la base de données `db1`, la seconde commande applique les migrations dans `@app/migrations/db2` à `db2`, et ainsi de suite. diff --git a/docs/guide-fr/db-query-builder.md b/docs/guide-fr/db-query-builder.md new file mode 100644 index 0000000..c9062fc --- /dev/null +++ b/docs/guide-fr/db-query-builder.md @@ -0,0 +1,583 @@ +Le constructeur de requêtes +=========================== + +Construit sur la base des [objets d'accès aux bases de données (DAO)](db-dao.md), le constructeur de requêtes vous permet de construire des requêtes SQL par programme qui sont indifférentes au système de gestion de base de données utilisé. Comparé à l'écriture d'instructions SQL brutes, l'utilisation du constructeur de requêtes vous aide à écrire du code relatif à SQL plus lisible et à générer des instructions SQL plus sûres. + +L'utilisation du constructeur de requêtes comprend ordinairement deux étapes : + +1. Construire un objet [[yii\db\Query]] pour représenter différentes parties (p. ex. `SELECT`, `FROM`) d'une instruction SQL. +2. Exécuter une méthode de requête (p. ex. `all()`) de [[yii\db\Query]] pour retrouver des données dans la base de données. + +Le code suivant montre une manière typique d'utiliser le constructeur de requêtes. + +```php +$rows = (new \yii\db\Query()) + ->select(['id', 'email']) + ->from('user') + ->where(['last_name' => 'Smith']) + ->limit(10) + ->all(); +``` + +Le code ci-dessus génère et exécute la requête SQL suivante, dans laquelle le paramètre `:last_name` est lié à la chaîne de caractères `'Smith'`. + +```sql +SELECT `id`, `email` +FROM `user` +WHERE `last_name` = :last_name +LIMIT 10 +``` + +> Info: génélalement vous travaillez essentiellement avec [[yii\db\Query]] plutôt qu'avec [[yii\db\QueryBuilder]]. Le dernier est implicitement invoqué par le premier lorsque vous appelez une des méthodes de requête. [[yii\db\QueryBuilder]] est la classe en charge de la génération des instructions SQL dépendantes du système de gestion de base de données (p. ex. entourer les noms de table/colonne par des marques de citation différemment) à partir d'objets [[yii\db\Query]] indifférents au système de gestion de base de données. + + +## Construction des requêtes + +Pour construire un objet [[yii\db\Query]], vous appelez différentes méthodes de construction de requêtes pour spécifier différentes parties de la requête SQL. Les noms de ces méthodes ressemblent aux mots clés de SQL utilisés dans les parties correspondantes de l'instruction SQL. Par exemple, pour spécifier la partie `FROM` d'une requête SQL, vous appelez la méthode [[yii\db\Query::from()|from()]]. Toutes les méthodes de construction de requêtes retournent l'objet *query* lui-même, ce qui vous permet d'enchaîner plusieurs appels. + +Dans ce qui suit, nous décrivons l'utilisation de chacune des méthodes de requête. + +### [[yii\db\Query::select()|select()]] + +La méthode [[yii\db\Query::select()|select()]] spécifie le fragment `SELECT` d'une instruction SQL. Vous pouvez spécifier les colonnes à sélectionner soit sous forme de chaînes de caractères, soit sous forme de tableaux, comme ci-apès. Les noms de colonne sélectionnées sont automatiquement entourés des marques de citation lorsque l'instruction SQL est générée à partir de l'objet *query* (requête). + +```php +$query->select(['id', 'email']); + +// équivalent à: + +$query->select('id, email'); +``` + +Les noms des colonnes sélectionnées peuvent inclure des préfixes de table et/ou de alias de colonne, comme vous le faites en écrivant une requête SQL brute. Par exemple : + +```php +$query->select(['user.id AS user_id', 'email']); + +// équivalent à: + +$query->select('user.id AS user_id, email'); +``` + +Si vous utilisez le format tableau pour spécifier les colonnes, vous pouvez aussi utiliser les clés du tableau pour spécifier les alias de colonne. Par exemple, le code ci-dessus peut être réécrit comme ceci : + +```php +$query->select(['user_id' => 'user.id', 'email']); +``` + +Si vous n'appelez pas la méthode [[yii\db\Query::select()|select()]] en construisant une requête, `*` est sélectionné, ce qui signifie la sélection de *toutes* les colonnes. + +En plus des noms de colonne, vous pouvez aussi sélectionner des expression de base de données. Vous devez utiliser le format tableau en sélectionnant une expression de base de données qui contient des virgules pour éviter des entourages automatiques incorrects des noms par des marques de citation. Par exemple : + +```php +$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); +``` + +Comme en tout lieu où il est fait appel à du SQL brut, vous devez utiliser la [syntaxe des marques de citation indifférentes au système de gestion de base de données](db-dao.md#quoting-table-and-column-names) pour les noms de table et de colonne lorsque vous écrivez les expressions de base de données dans `select`. + +Depuis la version 2.0.1, vous pouvez aussi sélectionner des sous-requêtes. Vous devez spécifier chacune des sous-requêtes en termes d'objet [[yii\db\Query]]. Par exemple : + +```php +$subQuery = (new Query())->select('COUNT(*)')->from('user'); + +// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post` +$query = (new Query())->select(['id', 'count' => $subQuery])->from('post'); +``` + +Pour sélectionner des lignes distinctes, vous pouvez appeler [[yii\db\Query::distinct()|distinct()]], comme ceci : + +```php +// SELECT DISTINCT `user_id` ... +$query->select('user_id')->distinct(); +``` + +Vous pouvez appeler [[yii\db\Query::addSelect()|addSelect()]] pour sélectionner des colonnes additionnelles. Par exemple : + +```php +$query->select(['id', 'username']) + ->addSelect(['email']); +``` + + +### [[yii\db\Query::from()|from()]] + +La méthode [[yii\db\Query::from()|from()]] spécifie le fragment `FROM`d'une instruction. Par exemple : + +```php +// SELECT * FROM `user` +$query->from('user'); +``` + +Vous pouvez spécifier les tables à sélectionner soit sous forme de chaînes de caractères, soit sous forme de tableaux. Les noms de table peuvent contenir des préfixes et/ou des alias de table. Par exemple : + +```php +$query->from(['public.user u', 'public.post p']); + +// équivalent à : + +$query->from('public.user u, public.post p'); +``` + +Si vous utilisez le format tableau, vous pouvez aussi utiliser les clés du tableau pour spécifier les alias de table, comme suit : + +```php +$query->from(['u' => 'public.user', 'p' => 'public.post']); +``` + +En plus des noms de table, vous pouvez aussi sélectionner à partir de sous-requêtes en les spécifiant en termes d'objets [[yii\db\Query]]. +Par exemple : + +```php +$subQuery = (new Query())->select('id')->from('user')->where('status=1'); + +// SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u +$query->from(['u' => $subQuery]); +``` + + +### [[yii\db\Query::where()|where()]] + +La méthode [[yii\db\Query::where()|where()]] spécifie le fragment `WHERE`d'une requête SQL. Vous pouvez utiliser un des trois formats suivants pour spécifier une condition `WHERE` : + +- format chaîne de caractères, p. ex. `'status=1'` +- format haché, p. ex. `['status' => 1, 'type' => 2]` +- format opérateur, p. ex. `['like', 'name', 'test']` + + +#### Format chaîne de caractères + +Le format chaîne de caractères est celui qui convient le mieux pour spécifier des conditions très simples ou si vous avez besoin d'utiliser les fonctions incorporées au système de gestion de base de données. Il fonctionne comme si vous écriviez une requête SQL brute. Par exemple : + +```php +$query->where('status=1'); + +// ou utilisez la liaison des paramètres pour lier des valeurs dynamiques des paramètres. +$query->where('status=:status', [':status' => $status]); + +// SQL brute utilisant la fonction MySQL YEAR() sur un champ de date +``` + +N'imbriquez PAS les variables directement dans la condition comme ce qui suit, spécialement si les valeurs des variables proviennent d'entrées utilisateur, parce que cela rendrait votre application SQL sujette aux attaques par injections SQL. + +```php +// Dangereux! Ne faites PAS cela sauf si vous êtes tout à fait sûr que $status est un entier +$query->where("status=$status"); +``` + +Lorsque vous utilisez la liaison des paramètres, vous pouvez appeler [[yii\db\Query::params()|params()]] ou [[yii\db\Query::addParams()|addParams()]] pour spécifier les paramètres séparément. + +```php +$query->where('status=:status') + ->addParams([':status' => $status]); +``` + +Comme dans tous les endroits ou il est fait appel à du SQL, vous pouvez utiliser la [syntaxe d'entourage par des marques de citation indifférente au système de gestion de base de données](db-dao.md#quoting-table-and-column-names) pour les noms de table et de colonne lorsque vous écrivez les conditions au format chaîne de caractères. + +#### Format haché + +Le format valeur de hachage convient le mieux pour spécifier de multiples sous-conditions `AND` concaténées, chacune étant une simple assertion d'égalité. Il se présente sous forme de tableau dont les clés sont les noms des colonnes et les valeurs les valeurs correspondantes que les valeurs des colonnes devraient avoir. Par exemple : + +```php +// ...WHERE (`status` = 10) AND (`type` IS NULL) AND (`id` IN (4, 8, 15)) +$query->where([ + 'status' => 10, + 'type' => null, + 'id' => [4, 8, 15], +]); +``` + +Comme vous pouvez le voir, le constructeur de requêtes est assez intelligent pour manipuler correctement les valeurs qui sont soit nulles, soit des tableaux. + +Vous pouvez utiliser aussi des sous-requêtes avec le format haché comme suit : + +```php +$userQuery = (new Query())->select('id')->from('user'); + +// ...WHERE `id` IN (SELECT `id` FROM `user`) +$query->where(['id' => $userQuery]); +``` + +En utilisant le format haché, Yii, en interne, utilise la liaison des paramètres de façon à ce que, contrairement au [format chaîne de caractères](#string-format), vous n'ayez pas à ajouter les paramètres à la main. + + +#### Format opérateur + +Le format opérateur vous permet de spécifier des conditions arbitraires par programmation. Il accepte les formats suivants : a + +```php +[operator, operand1, operand2, ...] +``` + +dans lequel chacun des opérandes peut être spécifié au format chaîne de caractères, au format haché ou au format opérateur de façon récursive, tandis que l'opérateur peut être un de ceux qui suivent : + +- `and`: les opérandes doivent être concaténés en utilisant `AND`. Par exemple, `['and', 'id=1', 'id=2']` génère `id=1 AND id=2`. Si l'opérande est un tableau, il est converti en une chaîne de caractères en utilisant les règles décrites ici. Par exemple, `['and', 'type=1', ['or', 'id=1', 'id=2']]` génère `type=1 AND (id=1 OR id=2)`. La méthode ne procède à aucun entourage par des marques de citation, ni à aucun échappement. + +- `or`: similaire à l'opérateur `and` sauf que les opérandes sont concaténés en utilisant `OR`. + +- `between`: l'opérande 1 doit être le nom de la colonne, et les opérandes 2 et 3 doivent être les valeurs de départ et de fin de la plage dans laquelle la colonne doit être. Par exemple, `['between', 'id', 1, 10]` génère `id BETWEEN 1 AND 10`. + +- `not between`: similaire à `between` sauf que `BETWEEN` est remplacé par `NOT BETWEEN` dans la condition générée. + +- `in`: l'opérande 1 doit être une colonne ou une expression de base de données. L'opérande 2 peut être soit un tableau, soit un objet `Query`. Il génère une condition `IN`. Si l'opérande 2 est un tableau, il représente la plage des valeurs que la colonne ou l'expression de base de données peut prendre. Si l'opérande 2 est un objet `Query`, une sous-requête est générée et utilisée comme plage pour la colonne ou l'expression de base de données. Par exemple, `['in', 'id', [1, 2, 3]]` génère `id IN (1, 2, 3)`. La méthode assure correctement l'entourage des noms de colonnes par des marques de citation et l'échappement des valeurs de la plage. L'opérateur `in` prend aussi en charge les colonnes composites. Dans ce case, l'opérande 1 doit être un tableau des colonnes, tandis que l'opérateur 2 doit être un tableau de tableaux, ou un objet `Query` représentant la plage de colonnes. + +- `not in`: similaire à l'opérateur `in` sauf que `IN` est remplacé par `NOT IN` dans la condition générée. + +- `like`: l'opérande 1 doit être une colonne ou une expression de base de données, tandis que l'opérande 2 doit être une chaîne de caractères ou un tableau représentant les valeurs que cette colonne ou cette expression de base de données peuvent être. Par exemple, `['like', 'name', 'tester']` génère `name LIKE '%tester%'`. Lorsque la plage de valeurs est donnée sous forme de tableau, de multiples prédicats `LIKE` sont générés et concaténés en utilisant `AND`. Par exemple, `['like', 'name', ['test', 'sample']]` génère `name LIKE '%test%' AND name LIKE '%sample%'`. Vous pouvez également fournir un troisième paramètre facultatif pour spécifier comment échapper les caractères spéciaux dans les valeurs. Les opérandes doivent être un tableau de correspondance entre les caractères spéciaux et les contre-parties échappées. Si cet opérande n'est pas fourni, une mise en correspondance par défaut est utilisée. Vous pouvez utiliser `false` ou un tableau vide pour indiquer que les valeurs sont déjà échappées et qu'aucun échappement ne doit être appliqué. Notez que lorsqu'un tableau de mise en correspondance pour l'échappement est utilisé (ou quand le troisième opérande n'est pas fourni), les valeurs sont automatiquement entourées par une paire de caractères `%`. + + > Note: lors de l'utilisation de PostgreSQL vous pouvez aussi utiliser [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) à la place de `like` pour une mise en correspondance insensible à la casse. + +- `or like`: similaire à l'opérateur `like` sauf que `OR`est utilisé pour concaténer les prédicats `LIKE` quand l'opérande 2 est un tableau. + +- `not like`: similaire à l'opérateur `like` sauf que `LIKE` est remplacé par `NOT LIKE` dans la condition générée. + +- `or not like`: similaire à l'opérateur `not like` sauf que `OR` est utilisé pour concaténer les prédicats `NOT LIKE`. + +- `exists`: requiert un opérande que doit être une instance de [[yii\db\Query]] représentant la sous-requête. Il construit une expression `EXISTS (sub-query)`. + +- `not exists`: similaire à l'opérateur `exists` et construit une expression `NOT EXISTS (sub-query)`. + +- `>`, `<=`, ou tout autre opérateur de base de données valide qui accepte deux opérandes : le premier opérande doit être un nom de colonne, tandis que le second doit être une valeur. Par exemple, `['>', 'age', 10]` génère `age>10`. + +En utilisant le format opérateur, Yii, en interne, utilise la liaison des paramètres afin, que contrairement au [format chaîne de caractères](#string-format), ici, vous n'avez pas besoin d'ajouter les paramètres à la main. + + +#### Ajout de conditions + +Vous pouvez utiliser [[yii\db\Query::andWhere()|andWhere()]] ou [[yii\db\Query::orWhere()|orWhere()]] pour ajouter des conditions supplémentaires à une condition existante. Vous pouvez les appeler plusieurs fois pour ajouter plusieurs conditions séparément. Par exemple : + +```php +$status = 10; +$search = 'yii'; + +$query->where(['status' => $status]); + +if (!empty($search)) { + $query->andWhere(['like', 'title', $search]); +} +``` + +Si `$search` n'est pas vide, la condition `WHERE` suivante est générée : + +```sql +WHERE (`status` = 10) AND (`title` LIKE '%yii%') +``` + + +#### Filtrage des conditions + +Lors de la construction de conditions `WHERE` basées sur des entrées de l'utilisateur final, vous voulez généralement ignorer les valeurs entrées qui sont vides. Par exemple, dans un formulaire de recherche par nom d'utilisateur ou par adresse de courriel, vous aimeriez ignorer la condition nom d'utilisateur/adresse de courriel si l'utilisateur n'a rien saisi les champs correspondants. Vous pouvez faire cela en utilisant la méthode [[yii\db\Query::filterWhere()|filterWhere()]] : + +```php +// $username et $email sont entrées par l'utilisateur +$query->filterWhere([ + 'username' => $username, + 'email' => $email, +]); +``` + +La seule différence entre [[yii\db\Query::filterWhere()|filterWhere()]] et [[yii\db\Query::where()|where()]] est que la première ignore les valeurs vides fournies dans la condition au [format haché](#hash-format). Ainsi si `$email` est vide alors que `$username` ne l'est pas, le code ci dessus produit la condition SQL `WHERE username=:username`. + +> Info: une valeur est considérée comme vide si elle est nulle, un tableau vide, ou un chaîne de caractères vide, ou un chaîne de caractères constituée d'espaces uniquement. + +Comme avec [[yii\db\Query::andWhere()|andWhere()]] et [[yii\db\Query::orWhere()|orWhere()]], vous pouvez utiliser [[yii\db\Query::andFilterWhere()|andFilterWhere()]] et [[yii\db\Query::orFilterWhere()|orFilterWhere()]] pour ajouter des conditions de filtrage supplémentaires à une condition existante. + +En outre, il y a [[yii\db\Query::andFilterCompare()]] qui peut déterminer intelligemment l'opérateur en se basant sur ce qu'il y a dans les valeurs : + +```php +$query->andFilterCompare('name', 'John Doe'); +$query->andFilterCompare('rating', '>9'); +$query->andFilterCompare('value', '<=100'); +``` + +Vous pouvez aussi utiliser un opérateur explicitement : + +```php +$query->andFilterCompare('name', 'Doe', 'like'); +``` + +### [[yii\db\Query::orderBy()|orderBy()]] + +La méthode [[yii\db\Query::orderBy()|orderBy()]] spécifie le fragment `ORDER BY` d'une requête SQL. Par exemple : + +```php +// ... ORDER BY `id` ASC, `name` DESC +$query->orderBy([ + 'id' => SORT_ASC, + 'name' => SORT_DESC, +]); +``` + +Dans le code ci-dessus, les clés du tableau sont des noms de colonnes, tandis que les valeurs sont les instructions de direction de tri. La constante PHP `SORT_ASC` spécifie un tri ascendant et `SORT_DESC`, un tri descendant. + +Si `ORDER BY` ne fait appel qu'à des noms de colonnes simples, vous pouvez le spécifier en utilisant une chaîne de caractères, juste comme vous le faites en écrivant des instructions SQL brutes. Par exemple : + +```php +$query->orderBy('id ASC, name DESC'); +``` + +> Note: vous devez utiliser le format tableau si `ORDER BY` fait appel à une expression de base de données. + +Vous pouvez appeler [[yii\db\Query::addOrderBy()|addOrderBy()]] pour ajouter des colonnes supplémentaires au fragment `ORDER BY`. Par exemple : + +```php +$query->orderBy('id ASC') + ->addOrderBy('name DESC'); +``` + + +### [[yii\db\Query::groupBy()|groupBy()]] + +La méthode [[yii\db\Query::groupBy()|groupBy()]] spécifie le fragment `GROUP BY` d'une requête SQL. Par exemple : + +```php +// ... GROUP BY `id`, `status` +$query->groupBy(['id', 'status']); +``` + +Si `GROUP BY` ne fait appel qu'à des noms de colonnes simples, vous pouvez le spécifier en utilisant un chaîne de caractères, juste comme vous le faîtes en écrivant des instructions SQL brutes. Par exemple : + +```php +$query->groupBy('id, status'); +``` + +> Note: vous devez utiliser le format tableau si `GROUP BY` fait appel à une expression de base de données. + +Vous pouvez appeler [[yii\db\Query::addGroupBy()|addGroupBy()]] pour ajouter des colonnes au fragment `GROUP BY`. Par exemple : + +```php +$query->groupBy(['id', 'status']) + ->addGroupBy('age'); +``` + + +### [[yii\db\Query::having()|having()]] + +La méthode [[yii\db\Query::having()|having()]] spécifie le fragment `HAVING` d'un requête SQL. Elle accepte une condition qui peut être spécifiée de la même manière que celle pour [where()](#where). Par exemple : + +```php +// ... HAVING `status` = 1 +$query->having(['status' => 1]); +``` + +Reportez-vous à la documentation de [where()](#where) pour plus de détails sur la manière de spécifier une condition. + +Vous pouvez appeler [[yii\db\Query::andHaving()|andHaving()]] ou [[yii\db\Query::orHaving()|orHaving()]] pour ajouter des conditions supplémentaires au fragment `HAVING` fragment. Par exemple : + +```php +// ... HAVING (`status` = 1) AND (`age` > 30) +$query->having(['status' => 1]) + ->andHaving(['>', 'age', 30]); +``` + + +### [[yii\db\Query::limit()|limit()]] et [[yii\db\Query::offset()|offset()]] + +Les méthodes [[yii\db\Query::limit()|limit()]] et [[yii\db\Query::offset()|offset()]] spécifient les fragments `LIMIT` et `OFFSET` d'une requête SQL. Par exemple : + +```php +// ... LIMIT 10 OFFSET 20 +$query->limit(10)->offset(20); +``` + +Si vous spécifiez une limite ou un décalage (p. ex. une valeur négative), il est ignoré. + +> Info: pour les systèmes de gestion de base de données qui prennent en charge `LIMIT` et `OFFSET` (p. ex. MSSQL), le constructeur de requêtes génère une instruction SQL qui émule le comportement `LIMIT`/`OFFSET`. + + +### [[yii\db\Query::join()|join()]] + +La méthode [[yii\db\Query::join()|join()]] spécifie le fragment `JOIN` d'une requête SQL. Par exemple : + +```php +// ... LEFT JOIN `post` ON `post`.`user_id` = `user`.`id` +$query->join('LEFT JOIN', 'post', 'post.user_id = user.id'); +``` + +La méthode [[yii\db\Query::join()|join()]] accepte quatre paramètres : + +- `$type`: type de jointure , p. ex. `'INNER JOIN'`, `'LEFT JOIN'`. +- `$table`: le nom de la table à joindre. +- `$on`: facultatif, la condition de jointure, c.-à-d. le fragment `ON`. Reportez-vous à [where()](#where) pour des détails sur la manière de spécifier une condition. Notez, que la syntaxe tableau ne fonctionne **PAS** pour spécifier une condition basée sur une colonne, p. ex. `['user.id' => 'comment.userId']` conduit à une condition où l'identifiant utilisateur doit être égal à la chaîne de caractères `'comment.userId'`. Vous devez utiliser la syntaxe chaîne de caractères à la place et spécifier la condition `'user.id = comment.userId'`. +- `$params`: facultatif, les paramètres à lier à la condition de jointure. + +Vous pouvez utiliser les méthodes raccourcies suivantes pour spécifier `INNER JOIN`, `LEFT JOIN` et `RIGHT JOIN`, respectivement. + +- [[yii\db\Query::innerJoin()|innerJoin()]] +- [[yii\db\Query::leftJoin()|leftJoin()]] +- [[yii\db\Query::rightJoin()|rightJoin()]] + +Par exemple : + +```php +$query->leftJoin('post', 'post.user_id = user.id'); +``` + +Pour joindre plusieurs tables, appelez les méthodes join ci-dessus plusieurs fois, une fois pour chacune des tables. + +En plus de joindre des tables, vous pouvez aussi joindre des sous-requêtes. Pour faire cela, spécifiez les sous-requêtes à joindre sous forme d'objets [[yii\db\Query]]. Par exemple : + +```php +$subQuery = (new \yii\db\Query())->from('post'); +$query->leftJoin(['u' => $subQuery], 'u.id = author_id'); +``` + +Dans ce cas, vous devez mettre la sous-requête dans un tableau et utiliser les clés du tableau pour spécifier les alias. + + +### [[yii\db\Query::union()|union()]] + +La méthode [[yii\db\Query::union()|union()]] spécifie le fragment `UNION` d'une requête SQL. Par exemple : + +```php +$query1 = (new \yii\db\Query()) + ->select("id, category_id AS type, name") + ->from('post') + ->limit(10); + +$query2 = (new \yii\db\Query()) + ->select('id, type, name') + ->from('user') + ->limit(10); + +$query1->union($query2); +``` + +Vous pouvez appeler [[yii\db\Query::union()|union()]] plusieurs fois pour ajouter plus de fragments `UNION`. + + +## Méthodes de requête + +L'objet [[yii\db\Query]] fournit un jeu complet de méthodes pour différents objectifs de requêtes : + +- [[yii\db\Query::all()|all()]]: retourne un tableau de lignes dont chacune des lignes est un tableau associatif de paires clé-valeur. +- [[yii\db\Query::one()|one()]]: retourne la première ligne du résultat. +- [[yii\db\Query::column()|column()]]: retourne la première colonne du résultat. +- [[yii\db\Query::scalar()|scalar()]]: retourne une valeur scalaire située au croisement de la première ligne et de la première colonne du résultat. +- [[yii\db\Query::exists()|exists()]]: retourne une valeur précisant si le résultat de la requête contient un résultat. +- [[yii\db\Query::count()|count()]]: retourne le résultat d'une requête `COUNT`.. +- D'autres méthodes d'agrégation de requêtes, y compris [[yii\db\Query::sum()|sum($q)]], [[yii\db\Query::average()|average($q)]], [[yii\db\Query::max()|max($q)]], [[yii\db\Query::min()|min($q)]]. Le paramètre `$q` est obligatoire pour ces méthodes et peut être soit un nom de colonne, soit une expression de base de données. + +Par exemple : + +```php +// SELECT `id`, `email` FROM `user` +$rows = (new \yii\db\Query()) + ->select(['id', 'email']) + ->from('user') + ->all(); + +// SELECT * FROM `user` WHERE `username` LIKE `%test%` +$row = (new \yii\db\Query()) + ->from('user') + ->where(['like', 'username', 'test']) + ->one(); +``` + +> Note: la méthode [[yii\db\Query::one()|one()]] retourne seulement la première ligne du résultat de la requête. Elle n'ajoute PAS `LIMIT 1` à l'instruction SQL générée. Cela est bon et préférable si vous savez que la requête ne retourne qu'une seule ou quelques lignes de données (p. ex. si vous effectuez une requête avec quelques clés primaires). Néanmoins, si la requête peut potentiellement retourner de nombreuses lignes de données, vous devriez appeler `limit(1)` explicitement pour améliorer la performance, p. ex. `(new \yii\db\Query())->from('user')->limit(1)->one()`. + +Toutes ces méthodes de requête accepte un paramètre supplémentaire `$db` représentant la [[yii\db\Connection|connexion à la base de données]] qui doit être utilisée pour effectuer la requête. Si vous omettez ce paramètre, le [composant d'application](structure-application-components.md) `db` est utilisé en tant que connexion à la base de données. Ci-dessous, nous présentons un autre exemple utilisant la méthode [[yii\db\Query::count()|count()]] : + +```php +// exécute SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name +$count = (new \yii\db\Query()) + ->from('user') + ->where(['last_name' => 'Smith']) + ->count(); +``` + +Lorsque vous appelez une méthode de requête de [[yii\db\Query]], elle effectue réellement le travail suivant en interne : + +* Appelle [[yii\db\QueryBuilder]] pour générer une instruction SQL basée sur la construction courante de [[yii\db\Query]] ; +* Crée un objet [[yii\db\Command]] avec l'instruction SQL générée ; +* Appelle une méthode de requête (p. ex. [[yii\db\Command::queryAll()|queryAll()]]) de [[yii\db\Command]] pour exécuter une instruction SQL et retrouver les données. + +Parfois, vous voulez peut-être examiner ou utiliser une instruction SQL construite à partir d'un objet [[yii\db\Query]]. Vous pouvez faire cela avec le code suivant : + +```php +$command = (new \yii\db\Query()) + ->select(['id', 'email']) + ->from('user') + ->where(['last_name' => 'Smith']) + ->limit(10) + ->createCommand(); + +// affiche l'instruction SQL +echo $command->sql; +// affiche les paramètres à lier +print_r($command->params); + +// retourne toutes les lignes du résultat de la requête +$rows = $command->queryAll(); +``` + + +### Indexation des résultats de la requête + +Lorsque vous appelez [[yii\db\Query::all()|all()]], elle retourne un tableau de lignes qui sont indexées par des entiers consécutifs. Parfois, vous désirez peut-être les indexer différemment, comme les indexer par une colonne particulière ou par des expressions donnant une valeur. Vous pouvez le faire en appelant [[yii\db\Query::indexBy()|indexBy()]] avant [[yii\db\Query::all()|all()]]. Par exemple : + +```php +// retourne [100 => ['id' => 100, 'username' => '...', ...], 101 => [...], 103 => [...], ...] +$query = (new \yii\db\Query()) + ->from('user') + ->limit(10) + ->indexBy('id') + ->all(); +``` + +Pour indexer par des valeurs d'expressions, passez une fonction anonyme à la méthode [[yii\db\Query::indexBy()|indexBy()]] : + +```php +$query = (new \yii\db\Query()) + ->from('user') + ->indexBy(function ($row) { + return $row['id'] . $row['username']; + })->all(); +``` + +Le fonction anonyme accepte un paramètre `$row` qui contient les données de la ligne courante et retourne une valeur scalaire qui est utilisée comme la valeur d'index de la ligne courante. + +> Note: contrairement aux méthodes de requête telles que [[yii\db\Query::groupBy()|groupBy()]] ou [[yii\db\Query::orderBy()|orderBy()]] qui sont converties en SQL et font partie de la requête, cette méthode ne fait son travail qu'après que les données ont été retrouvées dans la base de données. Cela signifie que seules les noms de colonne qui on fait partie du fragement SELECT dans votre requête peuvent être utilisés. De plus, si vous avez sélectionné une colonne avec un préfixe de table, p. ex. `customer.id`, le jeu de résultats ne contient que `id` c'est pourquoi vous devez appeler `->indexBy('id')` sans préfixe de table. + + +### Requêtes par lots + +Lorsque vous travaillez sur de grandes quantités de données, des méthodes telles que [[yii\db\Query::all()]] ne conviennent pas car elles requièrent le chargement de toutes les données en mémoire. Pour conserver l'exigence de capacité mémoire basse, Yii fournit la prise en charge appelé requêtes par lots. + +Les requêtes par lots peuvent être utilisées comme suit : + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->orderBy('id'); + +foreach ($query->batch() as $users) { + // $users est dans un tableau de 100 ou moins lignes du la table user. +} + +// ou si vous voulez itérer les lignes une par une +foreach ($query->each() as $user) { + // $user représente une ligne de données de la table user. +``` + +Les méthodes [[yii\db\Query::batch()]] et [[yii\db\Query::each()]] retournent un objet [[yii\db\BatchQueryResult]] qui implémente l'interface `Iterator` et qui, par conséquent, peut être utilisé dans une construction `foreach`. +Durant la première iteration, une requête SQL est faite à la base de données. Les données sont retrouvées en lots dans les itérations suivantes. Par défaut, la taille du lot est 100, ce qui signifie que 100 lignes sont retrouvées dans chacun des lots. Vous pouvez changer la taille du lot en passant le premier paramètre des méthodes `batch()` ou `each()`. + +Comparée à la requête [[yii\db\Query::all()]], la requête par lots ne charge que 100 lignes de données à la fois en mémoire. Si vous traitez les données et les détruisez tout de suite, la requête par lots réduit l'utilisation de la mémoire. + +Si vous spécifiez l'indexation du résultat de la requête par une colonne via [[yii\db\Query::indexBy()]], la requête par lots conserve l'index approprié. Par exemple : + +```php +$query = (new \yii\db\Query()) + ->from('user') + ->indexBy('username'); + +foreach ($query->batch() as $users) { + // $users est indexé par la colonne "username" +} + +foreach ($query->each() as $username => $user) { + // ... +} +``` diff --git a/docs/guide-fr/helper-array.md b/docs/guide-fr/helper-array.md new file mode 100644 index 0000000..f70c53f --- /dev/null +++ b/docs/guide-fr/helper-array.md @@ -0,0 +1,404 @@ +Classe assistante ArrayHelper +============================= + +En plus du jeu riche de [fonctions de tableaux](http://php.net/manual/en/book.array.php) qu'offre PHP, la classe assistante traitant les tableaux dans Yii fournit des méthodes statiques supplémentaires qui vous permettent de traiter les tableaux avec plus d'efficacité. + + +## Obtention de valeurs + +Récupérer des valeurs d'un tableau ou d'un objet ou une structure complexe écrits tous deux en PHP standard est un processus assez répétitif. Vous devez d'abord vérifier que la clé existe avec `isset`, puis si c'est le cas, vous récupérez la valeur associée, sinon il vous faut fournir une valeur par défaut : + +```php +class User +{ + public $name = 'Alex'; +} + +$array = [ + 'foo' => [ + 'bar' => new User(), + ] +]; + +$value = isset($array['foo']['bar']->name) ? $array['foo']['bar']->name : null; +``` + +Yii fournit une méthode très pratique pour faire cela : + +```php +$value = ArrayHelper::getValue($array, 'foo.bar.name'); +``` + +Le premier argument de la méthode indique de quelle source nous voulons récupérer une valeur. Le deuxième spécifie comment récupérer la donnée. Il peut s'agir d'un des éléments suivants : + +- Nom d'une clé de tableau ou de la propriété d'un objet de laquelle récupérer une valeur. +- Un jeu de noms de clé de tableau ou de propriétés d'objet séparées par des points, comme dans l'exemple que nous venons de présenter ci-dessus. +- Une fonction de rappel qui retourne une valeur. + +Le fonction de rappel doit être la suivante : + +```php +$fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) { + return $user->firstName . ' ' . $user->lastName; +}); +``` + +Le troisième argument facultatif est la valeur par défaut qui est `null` si on ne la spécifie pas. Il peut être utilisé comme ceci : + +```php +$username = ArrayHelper::getValue($comment, 'user.username', 'Unknown'); +``` + +Dans le cas où vous voulez récupérer la valeur tout en la retirant immédiatement du tableau, vous pouvez utiliser la méthode `remove` : + +```php +$array = ['type' => 'A', 'options' => [1, 2]]; +$type = ArrayHelper::remove($array, 'type'); +``` + +Après exécution du code, `$array` contiendra `['options' => [1, 2]]` et `$type` sera `A`. Notez que contrairement à la méthode `getValue`, `remove` accepte seulement les noms de clé. + + +## Tester l'existence des clés + +`ArrayHelper::keyExists` fonctionne comme [array_key_exists](http://php.net/manual/en/function.array-key-exists.php) sauf qu'elle prend également en charge la comparaison de clés insensible à la casse. Par exemple, + +```php +$data1 = [ + 'userName' => 'Alex', +]; + +$data2 = [ + 'username' => 'Carsten', +]; + +if (!ArrayHelper::keyExists('username', $data1, false) || !ArrayHelper::keyExists('username', $data2, false)) { + echo "Veuillez fournir un nom d'utilisateur (username)."; +} +``` + +## Récupération de colonnes + +Il arrive souvent que vous ayez à récupérer une colonne de valeurs d'un tableau de lignes de données ou d'objets. Un exemple courant est l'obtention d'une liste d'identifiants. + +```php +$array = [ + ['id' => '123', 'data' => 'abc'], + ['id' => '345', 'data' => 'def'], +]; +$ids = ArrayHelper::getColumn($array, 'id'); +``` + +Le résultat sera `['123', '345']`. + +Si des transformations supplémentaires sont nécessaires ou si la manière de récupérer les valeurs est complexe, le second argument peut être formulé sous forme de fonction anonyme : + +```php +$result = ArrayHelper::getColumn($array, function ($element) { + return $element['id']; +}); +``` + + +## Réindexation de tableaux + +La méthode `index` peut être utilisées pour indexer un tableau selon une clé spécifiée. L'entrée doit être soit un tableau multidimensionnel, soit un tableau d'objets. `$key` peut être un nom de clé du sous-tableau, un nom de propriété d'objet ou une fonction anonyme qui doit retourner la valeur à utiliser comme clé. + +L'attribut `$groups` est un tableau de clés qui est utilisé pour regrouper le tableau d'entrée en un ou plusieurs sous-tableaux basés sur les clés spécifiées. + +Si l'argument `$key` ou sa valeur pour l'élément particulier est `null` alors que `$groups` n'est pas défini, l'élément du tableau est écarté. Autrement, si `$groups` est spécifié, l'élément du tableau est ajouté au tableau résultant sans aucune clé. + +Par exemple : + +```php +$array = [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], +]; +$result = ArrayHelper::index($array, 'id'); +``` + +Le résultat est un tableau associatif, dans lequel la clé est la valeur de l'attribut `id` : + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // Le second élément du tableau d'origine est écrasé par le dernier élément parce que les identifiants sont identiques. +] +``` + +Une fonction anonyme passée en tant que `$key`, conduit au même résultat : + +```php +$result = ArrayHelper::index($array, function ($element) { + return $element['id']; +}); +``` + +Passer `id` comme troisième argument regroupe `$array` par `id`: + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +Le résultat est un tableau multidimensionnel regroupé par `id` au premier niveau et non indexé au deuxième niveau : + +```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'], + ] +] +``` + +Une fonction anonyme peut également être utilisée dans le tableau de regroupement : + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +Le résultat est un tableau multidimensionnel regroupé par `id` au premier niveau, par `device` au deuxième niveau et par `data` au troisième niveau : + +```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'] + ] + ] +] +``` + +## Construction de tableaux de mise en correspondance + +Afin de construire un tableau de mise en correspondance (paires clé-valeur) sur la base d'un tableau multidimensionnel ou d'un tableau d'objets, vous pouvez utiliser la méthode `map`. +Les paramètres `$from` et `$to` spécifient les noms de clé ou les noms des propriétés pour construire le tableau de mise en correspondance. Le paramètre facultatif `$group` est un nom de clé ou de propriété qui permet de regrouper les éléments du tableau au premier niveau. Par exemple : + +```php +$array = [ + ['id' => '123', 'name' => 'aaa', 'class' => 'x'], + ['id' => '124', 'name' => 'bbb', 'class' => 'x'], + ['id' => '345', 'name' => 'ccc', 'class' => 'y'], +]; + +$result = ArrayHelper::map($array, 'id', 'name'); +// le résultat est : +// [ +// '123' => 'aaa', +// '124' => 'bbb', +// '345' => 'ccc', +// ] + +$result = ArrayHelper::map($array, 'id', 'name', 'class'); +// le résultat est : +// [ +// 'x' => [ +// '123' => 'aaa', +// '124' => 'bbb', +// ], +// 'y' => [ +// '345' => 'ccc', +// ], +// ] +``` + + +## Tri multidimensionnel + +La méthode `multisort` facilite le tri d'un tableau d'objets ou de tableaux imbriqués selon une ou plusieurs clés. Par exemple : + +```php +$data = [ + ['age' => 30, 'name' => 'Alexander'], + ['age' => 30, 'name' => 'Brian'], + ['age' => 19, 'name' => 'Barney'], +]; +ArrayHelper::multisort($data, ['age', 'name'], [SORT_ASC, SORT_DESC]); +``` + +Après le tri, `data` contient ce qui suit : + +```php +[ + ['age' => 19, 'name' => 'Barney'], + ['age' => 30, 'name' => 'Brian'], + ['age' => 30, 'name' => 'Alexander'], +]; +``` + +Le deuxième argument, qui spécifie les clés de tri peut être une chaîne de caractères si la clé est unique, un tableau dans le cas de clés multiples, ou une fonction anonyme telle que celle qui suit : + +```php +ArrayHelper::multisort($data, function($item) { + return isset($item['age']) ? ['age', 'name'] : 'name'; +}); +``` + +Le troisième argument précise la direction. Dans le cas d'un tri selon une clé unique, il s'agit soit de `SORT_ASC`, soit de `SORT_DESC`. Si le tri se fait selon des valeurs multiples, vous pouvez préciser des directions de tri différentes pour chacune des clés en présentant ces directions sous forme de tableau. + +Le dernier argument est une option de tri de PHP qui peut prendre les mêmes valeurs que celles acceptées par la fonction [sort()](http://php.net/manual/en/function.sort.php) de PHP. + + +## Détection des types de tableau + +Il est pratique de savoir si un tableau est indexé ou associatif. Voici un exemple : + +```php +// aucune clé spécifiée +$indexed = ['Qiang', 'Paul']; +echo ArrayHelper::isIndexed($indexed); + +// toutes les clés sont des chaînes de caractères +$associative = ['framework' => 'Yii', 'version' => '2.0']; +echo ArrayHelper::isAssociative($associative); +``` + + +## Encodage et décodage de valeurs HTML + +Afin d'encoder ou décoder des caractères spéciaux dans un tableau de chaînes de caractères en/depuis des entités HTML, vous pouvez utiliser les fonctions suivantes : + +```php +$encoded = ArrayHelper::htmlEncode($data); +$decoded = ArrayHelper::htmlDecode($data); +``` + +Seules les valeurs sont encodées par défaut. En passant un deuxième argument comme `false` vous pouvez également encoder les clés d'un tableau. L'encodage utilise le jeu de caractères de l'application et on peut le changer via un troisième argument. + + +## Fusion de tableaux + +La fonction [[yii\helpers\ArrayHelper::merge()|ArrayHelper::merge()]] vous permet de fusionner deux, ou plus, tableaux en un seul de manière récursive. Si chacun des tableaux possède un élément avec la même chaîne clé valeur, le dernier écrase le premier (ce qui est un fonctionnement différent de [array_merge_recursive()](http://php.net/manual/en/function.array-merge-recursive.php)). +La fusion récursive est entreprise si les deux tableaux possèdent un élément de type tableau avec la même clé. Pour des éléments dont la clé est un entier, les éléments du deuxième tableau sont ajoutés aux éléments du premier tableau. Vous pouvez utiliser l'objet [[yii\helpers\UnsetArrayValue]] pour supprimer la valeur du premier tableau ou [[yii\helpers\ReplaceArrayValue]] pour forcer le remplacement de la première valeur au lieu de la fusion récursive. + +Par exemple : + +```php +$array1 = [ + 'name' => 'Yii', + 'version' => '1.1', + 'ids' => [ + 1, + ], + 'validDomains' => [ + 'example.com', + 'www.example.com', + ], + 'emails' => [ + 'admin' => 'admin@example.com', + 'dev' => 'dev@example.com', + ], +]; + +$array2 = [ + 'version' => '2.0', + 'ids' => [ + 2, + ], + 'validDomains' => new \yii\helpers\ReplaceArrayValue([ + 'yiiframework.com', + 'www.yiiframework.com', + ]), + 'emails' => [ + 'dev' => new \yii\helpers\UnsetArrayValue(), + ], +]; + +$result = ArrayHelper::merge($array1, $array2); +``` + +Le résultat est : + +```php +[ + 'name' => 'Yii', + 'version' => '2.0', + 'ids' => [ + 1, + 2, + ], + 'validDomains' => [ + 'yiiframework.com', + 'www.yiiframework.com', + ], + 'emails' => [ + 'admin' => 'admin@example.com', + ], +] +``` + + +## Conversion d'objets en tableaux + +Il arrive souvent que vous ayez besoin de convertir un objet, ou un tableau d'objets, en tableau. Le cas le plus courant est la conversion de modèles d'enregistrements actifs afin de servir des tableaux de données via une API REST ou pour un autre usage. Le code suivant peut alors être utilisé : + +```php +$posts = Post::find()->limit(10)->all(); +$data = ArrayHelper::toArray($posts, [ + 'app\models\Post' => [ + 'id', + 'title', + // the key name in array result => property name + 'createTime' => 'created_at', + // the key name in array result => anonymous function + 'length' => function ($post) { + return strlen($post->content); + }, + ], +]); +``` + +Le premier argument contient les données à convertir. Dans notre cas, nous convertissons un modèle d'enregistrements actifs `Post`. + +The second argument est un tableau de mise en correspondance de conversions par classe. Nous définissons une mise en correspondance pour le modèle `Post`. Chaque tableau de mise en correspondance contient un jeu de mise en correspondance. Chaque mise en correspondance peut être : + +- Un nom de champ à inclure tel quel. +- Une paire clé-valeur dans laquelle la clé est donnée sous forme de chaîne de caractères et la valeur sous forme du nom de la colonne dont on doit prendre la valeur. +- Une paire clé-valeur dans laquelle la clé est donnée sous forme de chaîne de caractères et la valeur sous forme de fonction de rappel qui la retourne. + +Le résultat de la conversion ci-dessus pour un modèle unique est : + + +```php +[ + 'id' => 123, + 'title' => 'test', + 'createTime' => '2013-01-01 12:00AM', + 'length' => 301, +] +``` + +Il est possible de fournir une manière par défaut de convertir un objet en tableau pour une classe spécifique en implémentant l'interface [[yii\base\Arrayable|Arrayable]] dans cette classe. + +## Test de l'appartenance à un tableau + +Souvent, vous devez savoir si un élément se trouve dans un tableau ou si un jeu d'éléments est un sous-ensemble d'un autre. Bien que PHP offre la fonction `in_array()`, cette dernière ne prend pas en charge les sous-ensembles ou les objets `\Traversable`. + +Pour faciliter ce genre de tests, [[yii\helpers\ArrayHelper]] fournit les méthodes [[yii\helpers\ArrayHelper::isIn()|isIn()]] +et [[yii\helpers\ArrayHelper::isSubset()|isSubset()]] avec la même signature que [in_array()](http://php.net/manual/en/function.in-array.php). + +```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-fr/helper-html.md b/docs/guide-fr/helper-html.md new file mode 100644 index 0000000..ee69b33 --- /dev/null +++ b/docs/guide-fr/helper-html.md @@ -0,0 +1,396 @@ +Classe assistante Html +====================== + +Toutes les applications Web génèrent un grand nombre de balises HTML. Si le code HTML est statique, il peut être créé efficacement sous forme de [mélange de code PHP et de code HTML dans un seul fichier](http://php.net/manual/en/language.basic-syntax.phpmode.php), mais lorsqu'il est généré dynamiquement, cela commence à être compliqué à gérer sans une aide supplémentaire. Yii fournit une telle aide sous la forme de la classe assistante Html, qui offre un jeu de méthodes statiques pour manipuler les balises Html les plus courantes, leurs options et leur contenu. + +> Note: si votre code HTML est presque statique, il vaut mieux utiliser HTML directement. Il n'est pas nécessaire d'envelopper tout dans des appels aux méthodes de la classe assistante Html. + + +## Les bases + +Comme la construction de code HTML dynamique en concaténant des chaînes de caractère peut très vite tourner à la confusion, Yii fournit un jeu de méthodes pour manipuler les options de balises et construire des balises s'appuyant sur ces options. + + +### Génération de balises + +Le code pour générer une balise ressemble à ceci : + +```php +name), ['class' => 'username']) ?> +``` + +Le premier argument est le nom de la balise. Le deuxième est le contenu qui apparaît entre l'ouverture de la balise et sa fermeture. +Notez que nous utilisons `Html::encode` – c'est parce que le contenu n'est pas encodé automatiquement pour permetre l'utilisation de HTML quand c'est nécessaire. +Le troisième est un tableau d'options HTML ou, en d'autres mots, les attributs de la balise. +Dans ce tableau, la clé est le nom de l'attribut (comme `class`, `href` ou `target`) et la valeur est sa valeur. + +Le code ci-dessus génère le code HTML suivant : + +```html +

    samdark

    +``` + +Dans le cas où vous avez simplement besoin d'ouvrir ou de fermer la balise, vous pouvez utiliser les méthodes `Html::beginTag()` et `Html::endTag()`. + +Des options sont utilisées dans de nombreuses méthodes de la classe assistante Html et de nombreux composants graphiques (widgets). Dans tous ces cas, il y a quelques manipulations supplémentaires à connaître : + +- Si une valeur est `null`, l'attribut correspondant n'est pas rendu. +- Les attributs du type booléen sont traités comme des + [attributs booléens ](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). +- Les valeurs des attributs sont encodés HTML à l'aide de la méthode [[yii\helpers\Html::encode()|Html::encode()]]. +- Si la valeur d'un attribut est un tableau, il est géré comme suit : + + * Si l'attribut est un attribut de donnée tel que listé dans [[yii\helpers\Html::$dataAttributes]], tel que `data` ou `ng`, + une liste d'attributs est rendue, un pour chacun des élément dans le tableau de valeurs. Par exemple, + `'data' => ['id' => 1, 'name' => 'yii']` génère `data-id="1" data-name="yii"`; et + `'data' => ['params' => ['id' => 1, 'name' => 'yii'], 'status' => 'ok']` génère + `data-params='{"id":1,"name":"yii"}' data-status="ok"`. Notez que dans le dernier exemple le format JSON est utilisé pour rendre le sous-tableau. + * Si l'attribut n'est PAS un attribut de donnée, la valeur est encodée JSON. Par exemple, + `['params' => ['id' => 1, 'name' => 'yii']` génère `params='{"id":1,"name":"yii"}'`. + + +### Formation des classes et des styles CSS + +Lors de la construction des options pour des balises HTML, nous démarrons souvent avec des valeurs par défaut qu'il faut modifier. Afin d'ajouter ou de retirer une classe, vous pouvez utiliser ce qui suit : + +```php +$options = ['class' => 'btn btn-default']; + +if ($type === 'success') { + Html::removeCssClass($options, 'btn-default'); + Html::addCssClass($options, 'btn-success'); +} + +echo Html::tag('div', 'Pwede na', $options); + +// si la valeur de $type est 'success' le rendu sera +//
    Pwede na
    +``` + +Vous pouvez spécifier de multiples classe CSS en utilisant le tableau de styles également : + +```php +$options = ['class' => ['btn', 'btn-default']]; + +echo Html::tag('div', 'Save', $options); +// rend '
    Save
    ' +``` + +Vous pouvez aussi utiliser le tableau de styles pour ajouter ou retirer des classes : + +```php +$options = ['class' => 'btn']; + +if ($type === 'success') { + Html::addCssClass($options, ['btn-success', 'btn-lg']); +} + +echo Html::tag('div', 'Save', $options); +// rend '
    Save
    ' +``` + +`Html::addCssClass()` empêche la duplication, vous n'avez donc pas à vous préoccuper de savoir si une classe apparaît deux fois : + +```php +$options = ['class' => 'btn btn-default']; + +Html::addCssClass($options, 'btn-default'); // class 'btn-default' is already present + +echo Html::tag('div', 'Save', $options); +// rend '
    Save
    ' +``` + +Si l'option classe CSS est spécifiée en utilisant le tableau de styles, vous pouvez utiliser une clé nommée pour indiquer le but logique de la classe. Dans ce cas, une classe utilisant la même clé dans le tableau de styles passé à `Html::addClass()` est ignorée : + +```php +$options = [ + 'class' => [ + 'btn', + 'theme' => 'btn-default', + ] +]; + +Html::addCssClass($options, ['theme' => 'btn-success']); // la clé 'theme' est déjà utilisée + +echo Html::tag('div', 'Save', $options); +// rend '
    Save
    ' +``` + +Les styles CSS peuvent être définis d'une façon similaire en utilisant l'attribut `style` : + +```php +$options = ['style' => ['width' => '100px', 'height' => '100px']]; + +// donne style="width: 100px; height: 200px; position: absolute;" +Html::addCssStyle($options, 'height: 200px; position: absolute;'); + +// gives style="position: absolute;" +Html::removeCssStyle($options, ['width', 'height']); +``` + +Lors de l'utilisation de [[yii\helpers\Html::addCssStyle()|addCssStyle()]], vous pouvez spécifier soit un tableau de paires clé-valeur qui correspond aux propriétés CSS noms et valeurs, soit une chaîne de caractères telle que `width: 100px; height: 200px;`. Ces formats peuvent être convertis de l'un en l'autre en utilisant les méthodes [[yii\helpers\Html::cssStyleFromArray()|cssStyleFromArray()]] et +[[yii\helpers\Html::cssStyleToArray()|cssStyleToArray()]]. La méthode [[yii\helpers\Html::removeCssStyle()|removeCssStyle()]] +accepte un tableau de propriétés à retirer. S'il s'agit d'une propriété unique, elle peut être spécifiée sous forme de chaîne de caractères. + +### Encodage et décodage du contenu + +Pour que le contenu puisse être affiché en HTML de manière propre et en toute sécurité, les caractères spéciaux du contenu doivent être encodés. En PHP, cela s'obtient avec [htmlspecialchars](http://www.php.net/manual/en/function.htmlspecialchars.php) et +[htmlspecialchars_decode](http://www.php.net/manual/en/function.htmlspecialchars-decode.php). Le problème rencontré en utilisant ces méthodes directement est que vous devez spécifier l'encodage et des options supplémentaires tout le temps. Comme ces options restent toujours les mêmes et que l'encodage doit correspondre à celui de l'application pour éviter les problèmes de sécurité, Yii fournit deux méthodes compactes et faciles à utiliser : + +```php +$userName = Html::encode($user->name); +echo $userName; + +$decodedUserName = Html::decode($userName); +``` + + +## Formulaires + +Manipuler des formulaires dans le code HTML est tout à fait répétitif et sujet à erreurs. À cause de cela, il existe un groupe de méthodes pour aider à les manipuler. + +> Note : envisagez d'utiliser [[yii\widgets\ActiveForm|ActiveForm]] dans le cas où vous avez affaire à des modèles et que ces derniers doivent être validés. + +### Création de formulaires + +Les formulaires peut être ouverts avec la méthode [[yii\helpers\Html::beginForm()|beginForm()]] comme ceci : + +```php + $id], 'post', ['enctype' => 'multipart/form-data']) ?> +``` + +Le premier argument est l'URL à laquelle le formulaire sera soumis. Il peut être spécifié sous la forme d'une route Yii et de paramètres acceptés par [[yii\helpers\Url::to()|Url::to()]]. +Le deuxième est la méthode à utiliser. `post` est la méthode par défaut. Le troisième est un tableau d'options pour la balise form. Dans ce cas, nous modifions l'encodage des données du formulaire dans la requête POST en `multipart/form-data`, ce qui est requis pour envoyer des fichiers. + +La fermeture du formulaire se fait simplement par : + +```php + +``` + + +### Boutons + +Pour générer des boutons, vous pouvez utiliser le code suivant : + +```php + 'teaser']) ?> + 'submit']) ?> + 'reset']) ?> +``` + +Le premier argument pour les trois méthodes est l'intitulé du bouton, le deuxième est un tableau d'options. +L'intitulé n'est pas encodé, mais si vous affichez des données en provenance de l'utilisateur, encodez les avec [[yii\helpers\Html::encode()|Html::encode()]]. + + +### Champs d'entrée + +Il y a deux groupes de méthodes d'entrée de données. Celles qui commencent par `active`, est qui sont appelées entrées actives, et celles qui ne commencent pas par ce mot. Les entrées actives prennent leurs données dans le modèle à partir des attributs spécifiés, tandis que pour les entrées régulières, les données sont spécifiées directement. + +Les méthodes les plus génériques sont : + +```php +type, nom de l'entrée, valeur de l'entrée, options +name, ['class' => $username]) ?> + +type, modèle, nom de l'attribut du modèle, options + $username]) ?> +``` + +Si vous connaissez le type de l'entrée à l'avance, il est plus commode d'utiliser les méthodes raccourcis : + +- [[yii\helpers\Html::buttonInput()]] +- [[yii\helpers\Html::submitInput()]] +- [[yii\helpers\Html::resetInput()]] +- [[yii\helpers\Html::textInput()]], [[yii\helpers\Html::activeTextInput()]] +- [[yii\helpers\Html::hiddenInput()]], [[yii\helpers\Html::activeHiddenInput()]] +- [[yii\helpers\Html::passwordInput()]] / [[yii\helpers\Html::activePasswordInput()]] +- [[yii\helpers\Html::fileInput()]], [[yii\helpers\Html::activeFileInput()]] +- [[yii\helpers\Html::textarea()]], [[yii\helpers\Html::activeTextarea()]] + +Les listes radio et les boîtes à cocher sont un peu différentes en matière de signature de méthode : + +```php + 'I agree']); + 'agreement']) + + 'I agree']); + 'agreement']) +``` + +Les listes déroulantes et les boîtes listes peuvent être rendues comme suit : + +```php + + + + + +``` + +Le premier argument est le nom de l'entrée, le deuxième est la valeur sélectionnée actuelle et le troisième est un tableau de paires clé-valeur, dans lequel la clé est la valeur d'entrée dans la liste et la valeur est l'étiquette qui correspond à cette valeur dans la liste. + +Si vous désirez que des choix multiples soient sélectionnables, vous pouvez utiliser la liste à sélection multiples (checkbox list) : + +```php + + +``` + +Sinon utilisez la liste radio : + +```php + + +``` + + +### Étiquettes et erreurs + +Comme pour les entrées, il existe deux méthodes pour générer les étiquettes de formulaire. Celles pour les entrées « actives » qui prennent leurs étiquettes dans le modèle, et celles « non actives » qui sont étiquetées directement : + +```php + 'label username']) ?> + 'label username']) ?> +``` + +Pour afficher les erreurs de formulaire à partir d'un modèle ou sous forme de résumé pour un modèle, vous pouvez utiliser : + +```php + 'errors']) ?> +``` + +Pour afficher une erreur individuellement : + +```php + 'error']) ?> +``` + + +### Nom et valeur des entrées + +Il existe deux méthodes pour obtenir des noms, des identifiants et des valeurs pour des champs d'entrée basés sur un modèle. Elles sont essentiellement utilisées en interne, mais peuvent être pratiques quelques fois : + +```php +// Post[title] +echo Html::getInputName($post, 'title'); + +// post-title +echo Html::getInputId($post, 'title'); + +// my first post +echo Html::getAttributeValue($post, 'title'); + +// $post->authors[0] +echo Html::getAttributeValue($post, '[0]authors[0]'); +``` + +Dans ce qui précède, le premier argument est le modèle, tandis que le deuxième est l'expression d'attribut. Dans sa forme la plus simple, l'expression est juste un nom d'attribut, mais il peut aussi s'agir d'un nom d'attribut préfixé et-ou suffixé par des index de tableau, ce qui est essentiellement le cas pour des entrées tabulaires : + +- `[0]content` est utilisé dans des entrées de données tabulaires pour représenter l'attribut `content` pour le premier modèle des entrées tabulaires ; +- `dates[0]` représente le premier élément du tableau de l'attribut `dates` ; +- `[0]dates[0]` représente le premier élément du tableau de l'attribut `dates` pour le premier modèle des entrées tabulaires. + +Afin d'obtenir le nom de l'attribut sans suffixe ou préfixe, vous pouvez utiliser ce qui suit : + +```php +// dates +echo Html::getAttributeName('dates[0]'); +``` + + +## Styles et scripts + +Il existe deux méthodes pour générer les balises enveloppes des styles et des scripts : + +```php + + +Produit + + + + + true]); + +Produit + + +``` + +Si vous désirez utiliser utiliser un style externe d'un fichier CSS : + +```php + 'IE 5']) ?> + +génère + + +``` + +Le premier argument est l'URL. Le deuxième est un tableau d'options. En plus des options normales, vous pouvez spécifier : + +- `condition` pour envelopper `` de façon à ce qu'elle soit incluse seulement si le navigateur ne prend pas en charge JavaScript ou si l'utilisateur l'a désactivé. + +Pour lier un fichier JavaScript : + +```php + +``` + +Se passe comme avec CSS, le premier argument spécifie l'URL du fichier à inclure. Les options sont passées via le deuxième argument. Dans les options vous pouvez spécifier `condition` de la même manière que dans les options pour un fichier CSS (méthode `cssFile`). + + +## Hyperliens + +Il y a une méthode commode pour générer les hyperliens : + +```php + $id], ['class' => 'profile-link']) ?> +``` + +Le premier argument est le titre. Il n'est pas encodé, mais si vous utilisez des données entrées par l'utilisateur, vous devez les encoder avec `Html::encode()`. Le deuxième argument est ce qui se retrouvera dans l'attribut `href` de la balise ` +``` + + +## Images + +Pour générer une balise image, utilisez le code suivant : + +```php + 'My logo']) ?> + +qui génère + +My logo +``` + +En plus des [alias](concept-aliases.md), le premier argument accepte les routes, les paramètres et les URL, tout comme [Url::to()](helper-url.md). + + +## Listes + +Les listes non ordonnées peuvent être générées comme suit : + +```php + function($item, $index) { + return Html::tag( + 'li', + $this->render('post', ['item' => $item]), + ['class' => 'post'] + ); +}]) ?> +``` + +Pour une liste ordonnée, utilisez plutôt `Html::ol()`. diff --git a/docs/guide-fr/helper-overview.md b/docs/guide-fr/helper-overview.md new file mode 100644 index 0000000..2ebd0d9 --- /dev/null +++ b/docs/guide-fr/helper-overview.md @@ -0,0 +1,68 @@ +Classes assistantes +=================== + +> Note: cette section est en cours de développement. + +Yii procure de nombreuses classes qui vous aident à simplifier le code de tâches courantes, telles que la manipulation de chaînes de caractères ou de tableaux, la génération de code HTML, et ainsi de suite. Ces classes assistantes sont organisées dans l'espace de noms `yii\helpers` et sont toutes des classes statiques (ce qui signifie qu'elles ne contiennent que des propriétés et des méthodes statiques et ne doivent jamais être instanciées). + +Vous utilisez une classe assistante en appelant directement une de ses méthodes statiques, comme ceci : + +```php +use yii\helpers\Html; + +echo Html::encode('Test > test'); +``` + +> Note: pour prendre en charge la [personnalisation des classes assistantes](#customizing-helper-classes), Yii éclate chacune des classes assistantes du noyau en deux classes : une classe de base (p. ex. `BaseArrayHelper`) et une classe concrète (p. ex. `ArrayHelper`). Lorsque vous utilisez une classe assistante, vous devez utiliser la version concrète uniquement et ne jamais utiliser la classe de base. + + +Classes assistantes du noyau +---------------------------- + +Les versions de Yii fournissent les classes assistantes du noyau suivantes : + +- [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 + + +Personnalisation des classes assistantes +---------------------------------------- + +Pour personnaliser une classe assistante du noyau (p. ex. [[yii\helpers\ArrayHelper]]), vous devez créer une nouvelle classe qui étend la classe de base correspondant à la classe assistante (p. ex. [[yii\helpers\ArrayHelper]]), y compris son espace de noms. Cette classe sera ensuite configurée pour remplacer l'implémentation originale de Yii. + +L'exemple qui suit montre comment personnaliser la méthode [[yii\helpers\ArrayHelper::merge()|merge()]] de la classe [[yii\helpers\ArrayHelper]] : + +```php +
    + +Vous pouvez utiliser deux méthodes pour obtenir des URL communes : l'URL de la page d'accueil et l'URL de base de la requête courante. Pour obtenir l'URL de la page d'accueil, utilisez ce qui suit : + +```php +$relativeHomeUrl = Url::home(); +$absoluteHomeUrl = Url::home(true); +$httpsAbsoluteHomeUrl = Url::home('https'); +``` + +Si aucun paramètre n'est passé, l'URL générée est relative. Vous pouvez passer `true` pour obtenir une URL absolue pour le schéma courant ou spécifier un schéma explicitement (`https`, `http`). + +Pour obtenir l'URL de base de la requête courante utilisez ceci : + +```php +$relativeBaseUrl = Url::base(); +$absoluteBaseUrl = Url::base(true); +$httpsAbsoluteBaseUrl = Url::base('https'); +``` + +L'unique paramètre de la méthode fonctionne comme pour `Url::home()`. + + +## Création d'URL + +En vue de créer une URL pour une route donnée, utilisez la méthode `Url::toRoute()`. La méthode utilise [[\yii\web\UrlManager]] pour créer une URL : + +```php +$url = Url::toRoute(['product/view', 'id' => 42]); +``` + +Vous pouvez spécifier la route sous forme de chaîne de caractère, p. ex. `site/index`. Vous pouvez également utiliser un tableau si vous désirez spécifier des paramètres de requête supplémentaires pour l'URL créée. Le format du tableau doit être : + +```php +// génère : /index.php?r=site%2Findex¶m1=value1¶m2=value2 +['site/index', 'param1' => 'value1', 'param2' => 'value2'] +``` + +Si vous voulez créer une URL avec une ancre, vous pouvez utiliser le format de tableau avec un paramètre `#`. Par exemple : + +```php +// génère: /index.php?r=site%2Findex¶m1=value1#name +['site/index', 'param1' => 'value1', '#' => 'name'] +``` + +Une route peut être ,soit absolue, soit relative. Une route absolue commence par une barre oblique de division (p. ex. `/site/index`) tandis que route relative commence sans ce caractère (p. ex. `site/index` ou `index`). Une route relative peut être convertie en une route absolue en utilisant une des règles suivantes : +- Si la route est une chaîne de caractères vide, la [[\yii\web\Controller::route|route]] est utilisée ; +- Si la route ne contient aucune barre oblique de division (p. ex. `index`), elle est considérée être un identifiant d'action dans le contrôleur courant et sera préfixée par l'identifiant du contrôleur ([[\yii\web\Controller::uniqueId]]); +- Si la route ne commence pas par une barre oblique de division (p. ex. `site/index`), elle est considérée être une route relative au module courant et sera préfixée par l'identifiant du module ([[\yii\base\Module::uniqueId|uniqueId]]). + +Depuis la version 2.0.2, vous pouvez spécifier une route sous forme d'[alias](concept-aliases.md). Si c'est le cas, l'alias sera d'abord converti en la route réelle puis transformé en une route absolue en respectant les règles ci-dessus. + +Voci quelques exemple d'utilisation de cette méthode : + +```php +// /index.php?r=site%2Findex +echo Url::toRoute('site/index'); + +// /index.php?r=site%2Findex&src=ref1#name +echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /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%2Findex +echo Url::toRoute('site/index', true); + +// https://www.example.com/index.php?r=site%2Findex +echo Url::toRoute('site/index', 'https'); +``` + +Il existe une autre méthode `Url::to()` très similaire à [[toRoute()]]. La seule différence est que cette méthode requiert la spécification d'une route sous forme de tableau seulement. Si une chaîne de caractères est données, elle est traitée comme une URL. + +Le premier argument peut être : + +- un tableau : [[toRoute()]] sera appelée pour générer l'URL. Par exemple : + `['site/index']`, `['post/index', 'page' => 2]`. Reportez-vous à la méthode [[toRoute()]] pour plus de détails sur la manière de spécifier une route. +- une chaîne de caractères commençant par `@`: elle est traitée commme un alias, et la chaine aliasée correspondante est retournée ; +- une chaîne de caractères vide : l'URL couramment requise est retournée ; +- une chaîne de caractères normale : elle est retournée telle que. + +Lorsque `$scheme` est spécifié (soit une chaîne de caractères, soit `true`), une URL absolue avec l'information hôte tirée de [[\yii\web\UrlManager::hostInfo]]) est retournée. Si`$url` est déjà une URL absolue, son schéma est remplacé par celui qui est spécifié. + +Voici quelques exemples d'utilisation : + +```php +// /index.php?r=site%2Findex +echo Url::to(['site/index']); + +// /index.php?r=site%2Findex&src=ref1#name +echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /index.php?r=post%2Fedit&id=100 assume the alias "@postEdit" is defined as "post/edit" +echo Url::to(['@postEdit', 'id' => 100]); + +// l'URL couramment requise +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'); +``` + +Depuis la version 2.0.3, vous pouvez utiliser [[yii\helpers\Url::current()]] pour créer une URL basée sur la route couramment requise et sur les paramètres de la méthode GET. Vous pouvez modifier ou retirer quelques uns des paramètres GET et en ajouter d'autres en passant le paramètre `$params` à la méthode. Par exemple : + +```php +// suppose que $_GET = ['id' => 123, 'src' => 'google'],et que la route courante est "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]); +``` + + +## Se souvenir d'URL + +Il y a des cas dans lesquels vous avez besoin de mémoriser une URL et ensuite de l'utiliser durant le traitement d'une des requêtes séquentielles. Cela peut être fait comme suit : + +```php +// se souvenir de l'URL courante +Url::remember(); + +// Se souvenir de l'URL spécifiée. Voir Url::to() pour le format des arguments. +Url::remember(['product/view', 'id' => 42]); + +// Se souvenir de l'URL spécifiée avec un nom +Url::remember(['product/view', 'id' => 42], 'product'); +``` + +Dans la prochaine requête, vous pouvez récupérer l'URL mémorisée comme ceci : + +```php +$url = Url::previous(); +$productUrl = Url::previous('product'); +``` + +## Vérification des URL relatives + +Pour savoir si une URL est relative, c.-à-d. n'a pas de partie « hôte », vous pouvez utiliser le code suivant : + +```php +$isRelative = Url::isRelative('test/it'); +``` diff --git a/docs/guide-fr/images/application-lifecycle.graphml b/docs/guide-fr/images/application-lifecycle.graphml new file mode 100644 index 0000000..850863a --- /dev/null +++ b/docs/guide-fr/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-fr/images/application-lifecycle.png b/docs/guide-fr/images/application-lifecycle.png new file mode 100644 index 0000000..6a505cc Binary files /dev/null and b/docs/guide-fr/images/application-lifecycle.png differ diff --git a/docs/guide-fr/images/request-lifecycle.png b/docs/guide-fr/images/request-lifecycle.png index 22ce9d5..19a975b 100644 Binary files a/docs/guide-fr/images/request-lifecycle.png and b/docs/guide-fr/images/request-lifecycle.png differ diff --git a/docs/guide-fr/input-file-upload.md b/docs/guide-fr/input-file-upload.md new file mode 100644 index 0000000..f21a947 --- /dev/null +++ b/docs/guide-fr/input-file-upload.md @@ -0,0 +1,189 @@ +Chargement de fichiers sur le serveur +===================================== + +Le chargement de fichiers sur le serveur dans Yii est ordinairement effectué avec l'aide de [[yii\web\UploadedFile]] qui encapsule chaque fichier chargé dans un objet `UploadedFile`. Combiné avec les [[yii\widgets\ActiveForm]] et les [modèles](structure-models.md), vous pouvez aisément mettre en œuvre un mécanisme sûr de chargement de fichiers sur le serveur. + + +## Création de modèles + +Comme on le ferait avec des entrées de texte simple, pour charger un unique fichier sur le serveur, vous devez créer une classe de modèle et utliser un attribut du modèle pour conserver un instance du fichier chargé. Vous devez également déclarer une règle de validation pour valider le fichier chargé. Par exemple : + +```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; + } + } +} +``` + +Dans le code ci-dessus, l'attribut `imageFile` est utilisé pur conserver une instance du fichier chargé. Il est associé à une règle de validation de fichier (`file`) qui utilise [[yii\validators\FileValidator]] pour garantir que l'extension du nom de fichier chargé est `png` ou `jpg`. La méthode `upload()` effectue l'examen de validation et sauvegarde le fichier sur le serveur. + +Le validateur `file` vous permet de vérifier l'extension du fichier, sa taille, son type MIME, etc. Reportez-vous à la section [Validateurs de noyau](tutorial-core-validators.md#file) pour plus de détails. + +> Tip: si vous chargez une image sur le serveur, vous pouvez envisager l'utilisation du validateur `image` au lieu de `file`. Le validateur `image` est mis en œuvre via [[yii\validators\ImageValidator]] qui vérifie si un attribut a reçu une image valide qui peut être, soit sauvegardée, soit traitée en utilisant l'[extension Imagine](https://github.com/yiisoft/yii2-imagine). + + +## Rendu d'une entrée de fichier + +Ensuite, créez une entrée de fichier dans une vue : + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFile')->fileInput() ?> + + + + +``` + +Il est important de se rappeler que vous devez ajouter l'option `enctype` au formulaire afin que le fichier soit proprement chargé sur le serveur. L'appel de `fileInput()` rend une balise `` qui permet à l'utilisateur de sélectionner un fichier à charger sur le serveur. + +> Tip: depuis la version 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] ajoute l'option `enctype` au formulaire automatiquement lorsqu'un champ d'entrée de fichier est utilisé. + +## Câblage + +Maintenant dans une action de contrôleur, écrivez le code de câblage entre le modèle et la vue pour mettre en œuvre le chargement sur le serveur : + +```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()) { + // le fichier a été chargé avec succès sur le serveur + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +Dans le code ci-dessus, lorsque le formulaire est soumis, la méthode [[yii\web\UploadedFile::getInstance()]] est appelée pour représenter le fichier chargé sous forme d'instance de `UploadedFile`. Nous comptons ensuite sur la validation du modèle pour garantir que le fichier chargé est valide et le sauvegarder sur le serveur. + + +## Chargement sur le serveur de plusieurs fichiers + +Vous pouvez également charger sur le serveur plusieurs fichiers à la fois, avec quelques ajustements au code présenté dans les sous-sections précédentes. + +Tout d'abord, vous devez ajuster la classe du modèle en ajoutant l'option `maxFiles` dans la règle de validation de `file` pour limiter le nombre maximum de fichiers à charger simultanément. Définir `maxFiles` à `0` signifie que ce nombre n'est pas limité. Le nombre maximal de fichiers que l'on peut charger simultanément est aussi limité par la directive PHP [`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads), dont la valeur par défaut est 20. La méthode `upload()` doit aussi être modifiée pour permettre la sauvegarde des fichiers un à un. + +```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; + } + } +} +``` + +Dans le fichier de vue, vous devez ajouter l'option `multiple` à l'appel de `fileInput()` afin que le champ d'entrée puisse recevoir plusieurs fichiers : + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> + + + + +``` + +Pour finir, dans l'action du contrôleur, vous devez appeler `UploadedFile::getInstances()` au lieu de `UploadedFile::getInstance()` pour assigner un tableau d'instances de `UploadedFile` à `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()) { + // file is uploaded successfully + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` diff --git a/docs/guide-fr/input-forms.md b/docs/guide-fr/input-forms.md new file mode 100644 index 0000000..7db82ec --- /dev/null +++ b/docs/guide-fr/input-forms.md @@ -0,0 +1,155 @@ +Création de formulaires +======================= + +La manière primaire d'utiliser des formulaires dans Yii de faire appel aux [[yii\widgets\ActiveForm]]. Cette approche doit être privilégiée lorsque le formulaire est basé sur un modèle. En plus, il existe quelques méthodes utiles dans [[yii\helpers\Html]] qui sont typiquement utilisées pour ajouter des boutons et des textes d'aides de toute forme. + +Un formulaire, qui est affiché du côté client, possède dans la plupart des cas, un [modèle](structure-models.md) correspondant qui est utilisé pour valider ses entrées du côté serveur (lisez la section [Validation des entrées](input-validation.md) pour plus de détails sur la validation). Lors de la création de formulaires basés sur un modèle, la première étape est de définir le modèle lui-même. Le modèle peut être soit basé sur une classe d'[enregistrement actif](db-active-record.md) représentant quelques données de la base de données, soit sur une classe de modèle générique qui étend la classe [[yii\base\Model]]) pour capturer des entrées arbitraires, par exemple un formulaire de connexion. Dans l'exemple suivant, nous montrons comment utiliser un modèle générique pour un formulaire de connexion : + +```php + 'login-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + +
    +
    + 'btn btn-primary']) ?> +
    +
    + +``` + +Dans le code précédent, [[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] ne crée pas seulement une instance de formulaire, mais il marque également le début du formulaire. Tout le contenu placé entre [[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] et [[yii\widgets\ActiveForm::end()|ActiveForm::end()]] sera enveloppé dans la balise HTML `
    `. Comme avec tout composant graphique, vous pouvez spécifier quelques options sur la façon dont le composant graphique est configuré en passant un tableau à la méthode `begin`. Dans ce cas précis, une classe CSS supplémentaire et un identifiant sont passés pour être utilisés dans l'ouverture de balise ``. Pour connaître toutes les options disponibles, reportez-vous à la documentation de l'API de [[yii\widgets\ActiveForm]]. + +Afin de créer un élément *form* dans le formulaire, avec l'élément *label* et toute validation JavaScript applicable, la méthode [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] est appelée. Elle retourne une instance de [[yii\widgets\ActiveField]]. Lorsque le résultat de cette méthode est renvoyé en écho directement, le résultat est un champ de saisie de texte régulier. Pour personnaliser la sortie, vous pouvez enchaîner des méthodes additionnelles de [[yii\widgets\ActiveField|ActiveField]] à cet appel : + +```php +// un champ de saisie du mot de passe +field($model, 'password')->passwordInput() ?> +// ajoute une invite et une étiquette personnalisée +field($model, 'username')->textInput()->hint('Please enter your name')->label('Name') ?> +// crée un élément HTML5 de saisie d'une adresse de courriel +field($model, 'email')->input('email') ?> +``` + +Cela crée toutes les balises `