From f5ab85c393f7c745e7e7232630f47e6e1a0ef998 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Tue, 10 Jan 2017 11:42:26 +0100 Subject: [PATCH 001/184] updated debug settings (+56 squashed commits) Squashed commits: [c42f30c] updated base image, added docs & local test script [88f0c40] debug [6220c94] fixed network isolation [c63c7c3] test mssql only on test/mssql branch [74efc78] fixed isolation in after_script [02b895b] updated test setup [3335f39] updated retry [bd123b2] updated service checks [45e4c90] updated build [d54da7a] updated after_script [5a4c726] :factory: wait for mysql [08db878] fixed typo [ea53c1e] updated build stages [9807ce3] fixed typos [cf9f64e] fixed mssql testing [08001d6] added db create for mssql [62f6b65] run travis (gitlab simulation) only in travis branch [cf63da4] streamlined build [76808ac] updated test jobs [18d79b5] fixed test error [7b2bce6] updated build & composer.lock [244623a] updated build [86bd71b] fixed cleanup [86ab2e8] fixed cleanup [091d4b8] fixed tests [2d315b5] fixed build config [2913644] fixed project names [f53b823] refactored build config [5a791fb] refactored docker db-tests [b4479b0] revert [a975fa5] updated gitlab build [4e4e5e4] updated mssql setup [d6ff03b] added sleep workaround [578b102] removed host volumes in test [928f50b] fixed path [967ab10] updated tests [520f317] bootstrap cubrid [5f245e1] :factory: fixed cubrid tests [940dbbc] :factory: pinned cubrid version 9.3.6.0002 [8d5ea69] :memo: dockerized test commands [9954b54] updated cubrid [fb3afac] updated docs [3f63ced] updated isolation [bdc3c83] :factory: build cubrid [b777911] :factory: disabled mssql, updated cubrid [36534e7] added travis-a-like stage [6546f02] updated GitLab build [c046096] updated test setup - added mssql and cubrid stack [5a0e635] disabled host-volume [75cf342] fixed test [8de0794] fixed testing [465d27a] added stages [63aa950] added script [68eecef] fixed typo [00e4b88] updated Docker build [f9072cc] added dockerized test setup --- .dockerignore | 2 + .gitlab-ci.yml | 128 +++++++++++++++++++++++++ Dockerfile | 45 +++++++++ Dockerfile-cubrid | 32 +++++++ Dockerfile-mssql | 52 ++++++++++ composer.lock | 109 ++++++++++----------- tests/README.md | 82 ++++++++++++++++ tests/cubrid/docker-compose.yml | 17 ++++ tests/data/config.php | 14 +-- tests/data/cubrid.sql | 14 +-- tests/docker-compose.yml | 41 ++++++++ tests/framework/db/sqlite/QueryBuilderTest.php | 2 +- tests/mssql/docker-compose.yml | 34 +++++++ tests/test-local.sh | 60 ++++++++++++ 14 files changed, 563 insertions(+), 69 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitlab-ci.yml create mode 100644 Dockerfile create mode 100644 Dockerfile-cubrid create mode 100644 Dockerfile-mssql create mode 100644 tests/cubrid/docker-compose.yml create mode 100644 tests/docker-compose.yml create mode 100644 tests/mssql/docker-compose.yml create mode 100644 tests/test-local.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..946a21f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +vendor +docs \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..166307f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,128 @@ +before_script: + # run docker-compose commands from tests environment + - cd tests + # set stack isolation + - export ISOLATION=buildpipeline${CI_PIPELINE_ID} + - export COMPOSE_PROJECT_NAME=${ISOLATION} + - export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + - echo ${TUPLE_C} + +after_script: + - export ISOLATION=buildpipeline${CI_PIPELINE_ID} + - docker ps -f name=${ISOLATION} + +stages: + - build + - test + - cleanup + +build: + stage: build + script: + - docker-compose build + +test: + stage: test + script: + - docker-compose up -d + - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db + - docker-compose down -v + +test:caching: + stage: test + script: + - export COMPOSE_PROJECT_NAME=${ISOLATION}caching + - docker-compose up -d + # wait for mysql (retry 30 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 caching + - docker-compose down -v + +test:db: + stage: test + script: + - export COMPOSE_PROJECT_NAME=${ISOLATION}db + - docker-compose up -d + - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid + - docker-compose down -v + +test:db:mysql: + stage: test + script: + - export COMPOSE_PROJECT_NAME=${ISOLATION}mysql + - docker-compose up -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 + - docker-compose down -v + +test:db:pgsql: + stage: test + script: + - export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql + - docker-compose up -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 + - docker-compose down -v + +test:db:mssql: + stage: test + only: + - test/mssql + script: + - cd mssql + - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql + # TODO: retry/wait for db + - 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 10 + # Note: Password has to be the last parameter + - docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Mircosoft-12345 + - docker-compose run --rm php vendor/bin/phpunit -v --group mssql + - docker-compose down -v + +test:db:cubrid: + stage: test + script: + - cd cubrid + - export COMPOSE_PROJECT_NAME=${ISOLATION}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 + - docker-compose down -v + +test:travis: + stage: test + only: + - travis + script: + - export COMPOSE_PROJECT_NAME=${ISOLATION}travis + - docker-compose up -d + # TODO: retry/wait for db + - sleep 10 + - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid + - docker-compose down -v + +cleanup: + stage: cleanup + when: always + script: + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}caching + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}db + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}mysql + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}cubrid + - docker-compose down -v + - export COMPOSE_PROJECT_NAME=${ISOLATION}travis + - docker-compose down -v diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8c1d9c5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +FROM codemix/yii2-base:2.0-apache + +# Install system packages for PHP extensions recommended for Yii 2.0 Framework +RUN apt-key update && \ + apt-get update && \ + apt-get -y install \ + g++ \ + git \ + libicu-dev \ + libmcrypt-dev \ + libfreetype6-dev \ + libjpeg-dev \ + libjpeg62-turbo-dev \ + libmcrypt-dev \ + libpng12-dev \ + libpq5 \ + libpq-dev \ + zlib1g-dev \ + mysql-client \ + openssh-client \ + libxml2-dev \ + --no-install-recommends && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Install PHP extensions required for Yii 2.0 Framework +RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ && \ + docker-php-ext-configure bcmath && \ + docker-php-ext-install \ + gd \ + intl \ + pdo_mysql \ + pdo_pgsql \ + mbstring \ + mcrypt \ + zip \ + bcmath \ + soap + + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer install --prefer-dist +ADD ./ /project diff --git a/Dockerfile-cubrid b/Dockerfile-cubrid new file mode 100644 index 0000000..6270c11 --- /dev/null +++ b/Dockerfile-cubrid @@ -0,0 +1,32 @@ +FROM php:5-fpm + +# /usr/local/lib/php/extensions/no-debug-non-zts-20131226/cubrid.so +RUN pecl install pdo_cubrid-9.3.0.0001 +RUN echo "extension=pdo_cubrid.so" > /usr/local/etc/php/conf.d/cubrid.ini + + +# TODO: temporary section ---> +# Install system packages for composer (git) +RUN apt-get update && \ + apt-get -y install \ + git \ + --no-install-recommends && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Register the COMPOSER_HOME environment variable +ENV COMPOSER_HOME /composer +# Add global binary directory to PATH and make sure to re-export it +ENV PATH /usr/local/bin:$PATH +# Allow Composer to be run as root +ENV COMPOSER_ALLOW_SUPERUSER 1 +# Install composer +RUN curl -sS https://getcomposer.org/installer | php -- \ + --filename=composer.phar \ + --install-dir=/usr/local/bin +# TODO: <--- end + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer.phar install --prefer-dist +ADD ./ /project diff --git a/Dockerfile-mssql b/Dockerfile-mssql new file mode 100644 index 0000000..6a23120 --- /dev/null +++ b/Dockerfile-mssql @@ -0,0 +1,52 @@ +FROM bylexus/apache-php7 + +# https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu +RUN apt-get update +RUN apt-get install -y curl apt-transport-https + +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + +RUN apt-get update +RUN ACCEPT_EULA=Y apt-get install -y msodbcsql +RUN apt-get install -y unixodbc-dev-utf16 + +RUN apt-get install -y php-dev +RUN pecl install sqlsrv pdo_sqlsrv + +RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini +RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini +RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini +RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini + +# TODO: temporary section ---> +# Install system packages for composer (git) +RUN apt-get update && \ + apt-get -y install \ + git \ + --no-install-recommends && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Register the COMPOSER_HOME environment variable +ENV COMPOSER_HOME /composer +# Add global binary directory to PATH and make sure to re-export it +ENV PATH /usr/local/bin:$PATH +# Allow Composer to be run as root +ENV COMPOSER_ALLOW_SUPERUSER 1 +# Install composer +RUN curl -sS https://getcomposer.org/installer | php -- \ + --filename=composer.phar \ + --install-dir=/usr/local/bin +# TODO: <--- end + + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer.phar install --prefer-dist +ADD ./ /project + +# https://github.com/Microsoft/msphpsql/issues/161 +RUN apt-get install -y locales \ + && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ + && locale-gen \ No newline at end of file diff --git a/composer.lock b/composer.lock index 6ab9625..8ab7bdc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "30a97926034335c40273795ce316f36c", - "content-hash": "58ccdf60f6da90c6d1623d9755c0b830", + "hash": "db4e038c0e8ca747784fb195c82bfdad", + "content-hash": "cc4b01a602c948040169ebbc1ac30186", "packages": [ { "name": "bower-asset/jquery", @@ -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": { @@ -114,16 +114,16 @@ }, { "name": "bower-asset/yii2-pjax", - "version": "dev-master", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0" + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/3f20897307cca046fca5323b318475ae9dac0ca0", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", "shasum": "" }, "require": { @@ -136,18 +136,15 @@ ".travis.yml", "Gemfile", "Gemfile.lock", + "CONTRIBUTING.md", "vendor/", "script/", "test/" - ], - "branch-alias": { - "dev-master": "2.0.3-dev" - } + ] }, "license": [ "MIT" - ], - "time": "2015-03-08 21:03:11" + ] }, { "name": "cebe/markdown", @@ -255,21 +252,24 @@ }, { "name": "yiisoft/yii2-composer", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", + "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", @@ -298,7 +298,7 @@ "extension installer", "yii2" ], - "time": "2016-02-06 00:49:24" + "time": "2016-12-20 13:26:02" } ], "packages-dev": [ @@ -440,16 +440,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", "shasum": "" }, "require": { @@ -457,10 +457,11 @@ "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" + "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": { @@ -498,7 +499,7 @@ "spy", "stub" ], - "time": "2016-06-07 08:13:47" + "time": "2016-11-21 14:58:47" }, { "name": "phpunit/php-code-coverage", @@ -564,16 +565,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "1.4.1", + "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": { @@ -607,7 +608,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -696,16 +697,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", "shasum": "" }, "require": { @@ -741,20 +742,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-09-15 10:49:45" + "time": "2016-11-15 14:06:22" }, { "name": "phpunit/phpunit", - "version": "4.8.27", + "version": "4.8.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/98b2b39a520766bec663ff5b7ff1b729db9dbfe3", + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3", "shasum": "" }, "require": { @@ -770,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", @@ -813,7 +814,7 @@ "testing", "xunit" ], - "time": "2016-07-21 06:48:14" + "time": "2016-12-09 02:45:31" }, { "name": "phpunit/phpunit-mock-objects", @@ -873,22 +874,22 @@ }, { "name": "sebastian/comparator", - "version": "1.2.0", + "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" @@ -933,7 +934,7 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2016-11-19 09:18:40" }, { "name": "sebastian/diff", @@ -1245,16 +1246,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.13", + "version": "v2.8.16", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "396784cd06b91f3db576f248f2402d547a077787" + "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/396784cd06b91f3db576f248f2402d547a077787", - "reference": "396784cd06b91f3db576f248f2402d547a077787", + "url": "https://api.github.com/repos/symfony/yaml/zipball/dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2", + "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2", "shasum": "" }, "require": { @@ -1290,7 +1291,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-10-21 20:59:10" + "time": "2017-01-03 13:49:52" } ], "aliases": [], diff --git a/tests/README.md b/tests/README.md index 7b71ff5..363352a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -53,3 +53,85 @@ contain the following: $config['databases']['mysql']['username'] = 'yiitest'; $config['databases']['mysql']['password'] = 'changeme'; ``` + + +DOCKERIZED TESTING +------------------ + +*This section is under construction* + +Start test stack and enter PHP container + + cd tests + docker-compose up -d + docker-compose run --rm php bash + +Run a group of unit tests + + $ vendor/bin/phpunit -v --group base --debug + +Run phpunit directly + + cd tests + docker-compose run --rm php vendor/bin/phpunit -v --group caching,db + docker-compose run --rm php vendor/bin/phpunit -v --exclude base,caching,db,i18n,log,mutex,rbac,validators,web + +### Cubrid + + cd tests + docker-compose -f docker-compose.cubrid.yml up -d + docker-compose -f docker-compose.cubrid.yml run --rm php vendor/bin/phpunit -v --group cubrid + +### MSSQL + +**experimental** + +- needs 3.5 GB RAM, Docker-host with >4.5 GB is recommended for testing +- database CLI `tsgkadot/mssql-tools` + +Example commands + + cd tests + +Using a shell + + docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -P Microsoft-12345 + +Create database with sqlcmd + + $ sqlcmd -S mssql -U sa -P Microsoft-12345 -Q "CREATE DATABASE yii2test" + +Create database (one-liner) + + docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -P Mircosoft-12345 -Q "CREATE DATABASE yii2test" + +Run MSSQL tests + + docker-compose run --rm php + $ vendor/bin/phpunit --group mssql + +### Run tests locally + +#### Via shell script + + cd tests + sh test-local.sh default + +#### Via runner (experimental) + + runner: + image: schmunk42/gitlab-runner + entrypoint: bash + working_dir: /project + volumes: + - ../:/project + - /var/run/docker.sock:/var/run/docker.sock + environment: + - RUNNER_BUILDS_DIR=${PWD}/.. + + docker-compose -f docker-compose.runner.yml run runner + + $ gitlab-runner exec shell build + $ gitlab-runner exec shell test + + \ No newline at end of file diff --git a/tests/cubrid/docker-compose.yml b/tests/cubrid/docker-compose.yml new file mode 100644 index 0000000..d2fbb8f --- /dev/null +++ b/tests/cubrid/docker-compose.yml @@ -0,0 +1,17 @@ +version: '2' +services: + + php: + build: + context: ../.. + dockerfile: Dockerfile-cubrid + ports: + - 80 + #volumes: + # - ../..:/project + depends_on: + - cubrid + + cubrid: + image: lighthopper/cubrid:9.3.6.0002 + command: ./create-start-demodb.sh \ No newline at end of file diff --git a/tests/data/config.php b/tests/data/config.php index 1072586..606f984 100644 --- a/tests/data/config.php +++ b/tests/data/config.php @@ -16,15 +16,15 @@ $config['databases']['mysql']['password'] = 'changeme'; $config = [ 'databases' => [ 'cubrid' => [ - 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', + 'dsn' => 'cubrid:dbname=demodb;host=cubrid;port=33000', 'username' => 'dba', 'password' => '', 'fixture' => __DIR__ . '/cubrid.sql', ], 'mysql' => [ - 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', + 'dsn' => 'mysql:host=mysql;dbname=yiitest', 'username' => 'travis', - 'password' => '', + 'password' => 'travis', 'fixture' => __DIR__ . '/mysql.sql', ], 'sqlite' => [ @@ -32,13 +32,13 @@ $config = [ 'fixture' => __DIR__ . '/sqlite.sql', ], 'sqlsrv' => [ - 'dsn' => 'sqlsrv:Server=localhost;Database=test', - 'username' => '', - 'password' => '', + 'dsn' => 'sqlsrv:Server=mssql;Database=yii2test', + 'username' => 'sa', + 'password' => 'Microsoft-12345', 'fixture' => __DIR__ . '/mssql.sql', ], 'pgsql' => [ - 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', + 'dsn' => 'pgsql:host=postgres;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', 'fixture' => __DIR__ . '/postgres.sql', diff --git a/tests/data/cubrid.sql b/tests/data/cubrid.sql index 0946cac..fa133f2 100644 --- a/tests/data/cubrid.sql +++ b/tests/data/cubrid.sql @@ -55,7 +55,7 @@ CREATE TABLE "item" ( "name" varchar(128) NOT NULL, "category_id" int(11) NOT NULL, PRIMARY KEY ("id"), - CONSTRAINT "FK_item_category_id" FOREIGN KEY ("category_id") REFERENCES "category" ("id") ON DELETE CASCADE + CONSTRAINT "FK_item_category_id" FOREIGN KEY ("category_id") REFERENCES "category" ("id") ); CREATE TABLE "order" ( @@ -64,7 +64,7 @@ CREATE TABLE "order" ( "created_at" int(11) NOT NULL, "total" decimal(10,0) NOT NULL, PRIMARY KEY ("id"), - CONSTRAINT "FK_order_customer_id" FOREIGN KEY ("customer_id") REFERENCES "customer" ("id") ON DELETE CASCADE + CONSTRAINT "FK_order_customer_id" FOREIGN KEY ("customer_id") REFERENCES "customer" ("id") ); CREATE TABLE "order_with_null_fk" ( @@ -81,8 +81,8 @@ CREATE TABLE "order_item" ( "quantity" int(11) NOT NULL, "subtotal" decimal(10,0) NOT NULL, PRIMARY KEY ("order_id","item_id"), - CONSTRAINT "FK_order_item_order_id" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON DELETE CASCADE, - CONSTRAINT "FK_order_item_item_id" FOREIGN KEY ("item_id") REFERENCES "item" ("id") ON DELETE CASCADE + CONSTRAINT "FK_order_item_order_id" FOREIGN KEY ("order_id") REFERENCES "order" ("id"), + CONSTRAINT "FK_order_item_item_id" FOREIGN KEY ("item_id") REFERENCES "item" ("id") ); CREATE TABLE "order_item_with_null_fk" ( @@ -134,7 +134,7 @@ CREATE TABLE "composite_fk" ( "order_id" int(11) NOT NULL, "item_id" int(11) NOT NULL, PRIMARY KEY ("id"), - CONSTRAINT "FK_composite_fk_order_item" FOREIGN KEY ("order_id","item_id") REFERENCES "order_item" ("order_id","item_id") ON DELETE CASCADE + CONSTRAINT "FK_composite_fk_order_item" FOREIGN KEY ("order_id","item_id") REFERENCES "order_item" ("order_id","item_id") ); CREATE TABLE "animal" ( @@ -204,12 +204,12 @@ INSERT INTO "document" (title, content, version) VALUES ('Yii 2.0 guide', 'This /* bit test, see https://github.com/yiisoft/yii2/issues/9006 */ -DROP TABLE IF EXISTS `bit_values` CASCADE; +DROP TABLE IF EXISTS `bit_values`; CREATE TABLE `bit_values` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `val` bit(1) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +); INSERT INTO `bit_values` (id, val) VALUES (1, b'0'), (2, b'1'); diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 0000000..9eeec89 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,41 @@ +version: '2' +services: + + php: + build: .. + working_dir: /project + depends_on: + - postgres + - mysql + #volumes: + # Enable for debugging + #- ../tests:/project/tests + # - ../framework:/project/framework + + mysql: + image: percona:5.7 + environment: + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_DATABASE=yiitest + - MYSQL_USER=travis + - MYSQL_PASSWORD=travis + + postgres: + image: postgres + environment: + - POSTGRES_DB=yiitest + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + + redis: + image: redis + + memcached: + image: memcached + +networks: + default: + ipam: + driver: default + config: + - subnet: 10.99.${TUPLE_C}.1/24 \ No newline at end of file diff --git a/tests/framework/db/sqlite/QueryBuilderTest.php b/tests/framework/db/sqlite/QueryBuilderTest.php index ec1934a..b1fe52a 100644 --- a/tests/framework/db/sqlite/QueryBuilderTest.php +++ b/tests/framework/db/sqlite/QueryBuilderTest.php @@ -59,7 +59,7 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest $this->markTestSkipped('Comments are not supported in SQLite'); } - public function testBatchInsert() + public function testBatchInsert($table, $columns, $value, $expected) { $db = $this->getConnection(); if (version_compare($db->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '3.7.11', '>=')) { diff --git a/tests/mssql/docker-compose.yml b/tests/mssql/docker-compose.yml new file mode 100644 index 0000000..dde8cad --- /dev/null +++ b/tests/mssql/docker-compose.yml @@ -0,0 +1,34 @@ +version: '2' +services: + + php: + build: + context: ../.. + dockerfile: Dockerfile-mssql + # Alternative pre-built image (TODO: evaluate) + #image: ppoffice/apache-php-mssql-odbc + ports: + - 80 + # Enable for debugging, Note: File-cache tests may be VERY slow or fail + #volumes: + # - ../../..:/project + depends_on: + - mssql + # Enable for debugging + #entrypoint: ['bash'] + + mssql: + image: microsoft/mssql-server-linux + # Alternative pre-built image (TODO: evaluate) + #image: microsoft/mssql-server-windows + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=Microsoft-12345 + + sqlcmd: + image: tsgkadot/mssql-tools + # Mount project for accessing SQL dump (TODO) + volumes: + - ../..:/project + # Enable for debugging + #entrypoint: ['bash'] diff --git a/tests/test-local.sh b/tests/test-local.sh new file mode 100644 index 0000000..8b0f3dc --- /dev/null +++ b/tests/test-local.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +# Generate a "hash" from 0-200 for subnet tuple +echo "Creating CI_BUILD_ID and CI_PIPELINE_ID from first argument..." +export CI_BUILD_ID=$(expr $((32#${1})) % 200) +export CI_PIPELINE_ID=${1} + +# Copy snippets from https://git.hrzg.de/ci/lint +case $1 in +'default') + export ISOLATION=buildpipeline${CI_PIPELINE_ID} + export COMPOSE_PROJECT_NAME=${ISOLATION} + export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + echo ${TUPLE_C} + docker-compose up -d + docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db + docker-compose down -v + ;; +'caching') + export ISOLATION=buildpipeline${CI_PIPELINE_ID} + export COMPOSE_PROJECT_NAME=${ISOLATION} + export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + echo ${TUPLE_C} + export COMPOSE_PROJECT_NAME=${ISOLATION}caching + docker-compose up -d + 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 caching + docker-compose down -v + ;; +'mssql') + export ISOLATION=buildpipeline${CI_PIPELINE_ID} + export COMPOSE_PROJECT_NAME=${ISOLATION} + export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + echo ${TUPLE_C} + cd mssql + export COMPOSE_PROJECT_NAME=${ISOLATION}mssql + docker-compose up --build -d + 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 10 + docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Mircosoft-12345 + docker-compose run --rm php vendor/bin/phpunit -v --group mssql + docker-compose down -v + ;; +'pgsql') + export ISOLATION=buildpipeline${CI_PIPELINE_ID} + export COMPOSE_PROJECT_NAME=${ISOLATION} + export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + echo ${TUPLE_C} + export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql + docker-compose up -d + 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 + docker-compose down -v + ;; +*) + echo "Warning: No job argument specified" + ;; +esac + +echo "Done." \ No newline at end of file From cc1f398946e69eb31312b394d04170ba9194821a Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 19 Jan 2017 22:47:17 +0100 Subject: [PATCH 002/184] updated docs --- tests/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 363352a..a2c4686 100644 --- a/tests/README.md +++ b/tests/README.md @@ -117,7 +117,11 @@ Run MSSQL tests cd tests sh test-local.sh default -#### Via runner (experimental) +#### Via runner + +**experimental** + +docker-compose configuration runner: image: schmunk42/gitlab-runner @@ -128,8 +132,12 @@ Run MSSQL tests - /var/run/docker.sock:/var/run/docker.sock environment: - RUNNER_BUILDS_DIR=${PWD}/.. + +Start runner bash docker-compose -f docker-compose.runner.yml run runner + +Execute jobs via shell runner (with docker-compose support) $ gitlab-runner exec shell build $ gitlab-runner exec shell test From 8893ecaf7957715861f39f8e349d0fcbbf0bb951 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 19 Jan 2017 22:50:11 +0100 Subject: [PATCH 003/184] fixed cleanup --- .gitlab-ci.yml | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 166307f..1b45751 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,7 +26,7 @@ test: script: - docker-compose up -d - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db - - docker-compose down -v + - docker-compose down -v --remove-orphans test:caching: stage: test @@ -36,7 +36,7 @@ test:caching: # wait for mysql (retry 30 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 caching - - docker-compose down -v + - docker-compose down -v --remove-orphans test:db: stage: test @@ -44,7 +44,7 @@ test:db: - export COMPOSE_PROJECT_NAME=${ISOLATION}db - docker-compose up -d - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid - - docker-compose down -v + - docker-compose down -v --remove-orphans test:db:mysql: stage: test @@ -54,7 +54,7 @@ test:db:mysql: # 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 - - docker-compose down -v + - docker-compose down -v --remove-orphans test:db:pgsql: stage: test @@ -64,7 +64,7 @@ test:db:pgsql: # 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 - - docker-compose down -v + - docker-compose down -v --remove-orphans test:db:mssql: stage: test @@ -81,7 +81,7 @@ test:db:mssql: # Note: Password has to be the last parameter - docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Mircosoft-12345 - docker-compose run --rm php vendor/bin/phpunit -v --group mssql - - docker-compose down -v + - docker-compose down -v --remove-orphans test:db:cubrid: stage: test @@ -93,7 +93,7 @@ test:db:cubrid: - 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 - - docker-compose down -v + - docker-compose down -v --remove-orphans test:travis: stage: test @@ -105,24 +105,28 @@ test:travis: # TODO: retry/wait for db - sleep 10 - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid - - docker-compose down -v + - docker-compose down -v --remove-orphans cleanup: stage: cleanup when: always script: - - docker-compose down -v + - docker-compose down -v --remove-orphans - export COMPOSE_PROJECT_NAME=${ISOLATION}caching - - docker-compose down -v + - docker-compose down -v --remove-orphans - export COMPOSE_PROJECT_NAME=${ISOLATION}db - - docker-compose down -v + - docker-compose down -v --remove-orphans - export COMPOSE_PROJECT_NAME=${ISOLATION}mysql - - docker-compose down -v + - docker-compose down -v --remove-orphans - export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql - - docker-compose down -v + - docker-compose down -v --remove-orphans + - export COMPOSE_PROJECT_NAME=${ISOLATION}travis + - docker-compose down -v --remove-orphans + - pushd mssql - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql - - docker-compose down -v + - docker-compose down -v --remove-orphans + - popd + - pushd cubrid - export COMPOSE_PROJECT_NAME=${ISOLATION}cubrid - - docker-compose down -v - - export COMPOSE_PROJECT_NAME=${ISOLATION}travis - - docker-compose down -v + - docker-compose down -v --remove-orphans + - popd From 25cc36c524653842351f66b04c6270c3ccb8814a Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Fri, 20 Jan 2017 00:16:12 +0100 Subject: [PATCH 004/184] fixed network setup --- tests/docker-compose.yml | 2 +- tests/test-local.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 9eeec89..a383fb3 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -38,4 +38,4 @@ networks: ipam: driver: default config: - - subnet: 10.99.${TUPLE_C}.1/24 \ No newline at end of file + - subnet: 10.100.1${TUPLE_C}.1/24 \ No newline at end of file diff --git a/tests/test-local.sh b/tests/test-local.sh index 8b0f3dc..6edd620 100644 --- a/tests/test-local.sh +++ b/tests/test-local.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Generate a "hash" from 0-200 for subnet tuple +# Generate a "hash" from 0-99 for subnet tuple (10, 11, ..., 199) - it's a hack echo "Creating CI_BUILD_ID and CI_PIPELINE_ID from first argument..." -export CI_BUILD_ID=$(expr $((32#${1})) % 200) +export CI_BUILD_ID=$(expr $((32#${1})) % 99) export CI_PIPELINE_ID=${1} # Copy snippets from https://git.hrzg.de/ci/lint From 4db2ff971ab6f44cf6273d4ccb67ced5db4c06c7 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Fri, 20 Jan 2017 00:20:21 +0100 Subject: [PATCH 005/184] fixed tuple --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b45751..dff93f8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ before_script: # set stack isolation - export ISOLATION=buildpipeline${CI_PIPELINE_ID} - export COMPOSE_PROJECT_NAME=${ISOLATION} - - export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) + - export TUPLE_C=$(expr ${CI_BUILD_ID} % 99) - echo ${TUPLE_C} after_script: From bd956aa98ddc2cf3050af60240e50472302393e8 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Fri, 20 Jan 2017 01:41:05 +0100 Subject: [PATCH 006/184] run cubrid only on branch tests/cubrid --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dff93f8..d103293 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,7 +69,7 @@ test:db:pgsql: test:db:mssql: stage: test only: - - test/mssql + - tests/mssql script: - cd mssql - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql @@ -85,6 +85,8 @@ test:db:mssql: test:db:cubrid: stage: test + only: + - tests/cubrid script: - cd cubrid - export COMPOSE_PROJECT_NAME=${ISOLATION}cubrid From 478331efe8a4d8eea496f1dd7eb5e9b800035985 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Fri, 20 Jan 2017 01:58:50 +0100 Subject: [PATCH 007/184] updated build - added git files to dockerignore - updated local testing - updated build on branches --- .dockerignore | 1 + .gitignore | 1 + .gitlab-ci.yml | 111 +++++++++++++++++++++------------------ Dockerfile | 41 +-------------- Dockerfile-mssql | 26 +++++---- tests/.env-dist | 10 ++++ tests/README.md | 2 +- tests/docker-compose.caching.yml | 13 +++++ tests/docker-compose.mysql.yml | 15 ++++++ tests/docker-compose.pgsql.yml | 14 +++++ tests/docker-compose.yml | 30 ++--------- tests/test-local.sh | 11 ++-- 12 files changed, 146 insertions(+), 129 deletions(-) create mode 100644 tests/.env-dist create mode 100644 tests/docker-compose.caching.yml create mode 100644 tests/docker-compose.mysql.yml create mode 100644 tests/docker-compose.pgsql.yml diff --git a/.dockerignore b/.dockerignore index 946a21f..5c15602 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ +.git vendor docs \ No newline at end of file diff --git a/.gitignore b/.gitignore index e887db8..ee89055 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ phpunit.phar # NPM packages /node_modules +.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d103293..a45b417 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,114 +1,125 @@ before_script: - # run docker-compose commands from tests environment - - cd tests # set stack isolation - - export ISOLATION=buildpipeline${CI_PIPELINE_ID} + - 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 after_script: - - export ISOLATION=buildpipeline${CI_PIPELINE_ID} + - 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: - - build + - travis - test - cleanup -build: - stage: build - script: - - docker-compose build - test: stage: test + only: + - tests/base + - tests/all script: - - docker-compose up -d - - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db - - docker-compose down -v --remove-orphans + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db,data --log-junit tests/_junit/test.xml -test:caching: +caching: stage: test + only: + - tests/caching + - tests/all script: - - export COMPOSE_PROJECT_NAME=${ISOLATION}caching - - docker-compose up -d - # wait for mysql (retry 30 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 caching - - docker-compose down -v --remove-orphans + - 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 -test:db: +db: stage: test + only: + - tests/mysql + - tests/all script: - - export COMPOSE_PROJECT_NAME=${ISOLATION}db - - docker-compose up -d + - docker-compose up --build -d - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid - docker-compose down -v --remove-orphans -test:db:mysql: +mysql: stage: test + only: + - tests/mysql + - tests/all script: - - export COMPOSE_PROJECT_NAME=${ISOLATION}mysql - - docker-compose up -d + - 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 - docker-compose down -v --remove-orphans -test:db:pgsql: +pgsql: stage: test + only: + - tests/pgsql + - tests/all script: - - export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql - - docker-compose up -d + - 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 - docker-compose down -v --remove-orphans -test:db:mssql: +cubrid: stage: test only: - - tests/mssql + - tests/cubrid + - tests/all script: - - cd mssql - - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql - # TODO: retry/wait for db + - cd cubrid - 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 10 - # Note: Password has to be the last parameter - - docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Mircosoft-12345 - - docker-compose run --rm php vendor/bin/phpunit -v --group mssql + - 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 - docker-compose down -v --remove-orphans -test:db:cubrid: +mssql: stage: test only: - - tests/cubrid + - tests/mssql + - tests/all script: - - cd cubrid - - export COMPOSE_PROJECT_NAME=${ISOLATION}cubrid + - cd mssql - 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 + - 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 - docker-compose down -v --remove-orphans -test:travis: - stage: test +travis: + stage: travis only: - travis script: - - export COMPOSE_PROJECT_NAME=${ISOLATION}travis - - docker-compose up -d + - export COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml:docker-compose.pgsql.yml + - docker-compose up --build -d # TODO: retry/wait for db - sleep 10 - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid - docker-compose down -v --remove-orphans +# temporary cleanup stage cleanup: stage: cleanup when: always diff --git a/Dockerfile b/Dockerfile index 8c1d9c5..b890986 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,45 +1,8 @@ -FROM codemix/yii2-base:2.0-apache - -# Install system packages for PHP extensions recommended for Yii 2.0 Framework -RUN apt-key update && \ - apt-get update && \ - apt-get -y install \ - g++ \ - git \ - libicu-dev \ - libmcrypt-dev \ - libfreetype6-dev \ - libjpeg-dev \ - libjpeg62-turbo-dev \ - libmcrypt-dev \ - libpng12-dev \ - libpq5 \ - libpq-dev \ - zlib1g-dev \ - mysql-client \ - openssh-client \ - libxml2-dev \ - --no-install-recommends && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install PHP extensions required for Yii 2.0 Framework -RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ && \ - docker-php-ext-configure bcmath && \ - docker-php-ext-install \ - gd \ - intl \ - pdo_mysql \ - pdo_pgsql \ - mbstring \ - mcrypt \ - zip \ - bcmath \ - soap - +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/Dockerfile-mssql b/Dockerfile-mssql index 6a23120..5e9f9a5 100644 --- a/Dockerfile-mssql +++ b/Dockerfile-mssql @@ -1,5 +1,6 @@ FROM bylexus/apache-php7 + # https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu RUN apt-get update RUN apt-get install -y curl apt-transport-https @@ -7,26 +8,25 @@ RUN apt-get install -y curl apt-transport-https RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list -RUN apt-get update -RUN ACCEPT_EULA=Y apt-get install -y msodbcsql -RUN apt-get install -y unixodbc-dev-utf16 - -RUN apt-get install -y php-dev -RUN pecl install sqlsrv pdo_sqlsrv +RUN apt-get update \ + && apt-get install -y unixodbc-dev-utf16 php-dev \ + && pecl install sqlsrv pdo_sqlsrv RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini -# TODO: temporary section ---> +# IMPORTANT NOTICE! Install `msodbcsql` after `unixodbc-dev-utf16` and `pdo_sqlsrv`, due to dependency & build issues +RUN ACCEPT_EULA=Y apt-get install -y msodbcsql + # Install system packages for composer (git) RUN apt-get update && \ apt-get -y install \ git \ + php-curl \ --no-install-recommends && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + rm -rf /tmp/* /var/tmp/* # Register the COMPOSER_HOME environment variable ENV COMPOSER_HOME /composer # Add global binary directory to PATH and make sure to re-export it @@ -37,7 +37,8 @@ ENV COMPOSER_ALLOW_SUPERUSER 1 RUN curl -sS https://getcomposer.org/installer | php -- \ --filename=composer.phar \ --install-dir=/usr/local/bin -# TODO: <--- end +RUN composer.phar global require --optimize-autoloader \ + "hirak/prestissimo" # Project source-code @@ -49,4 +50,7 @@ ADD ./ /project # https://github.com/Microsoft/msphpsql/issues/161 RUN apt-get install -y locales \ && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ - && locale-gen \ No newline at end of file + && locale-gen + +# Debug installation +RUN dpkg -L msodbcsql diff --git a/tests/.env-dist b/tests/.env-dist new file mode 100644 index 0000000..ce6d2bd --- /dev/null +++ b/tests/.env-dist @@ -0,0 +1,10 @@ +# docker-compose test environment + +# Choose a flavour +#COMPOSE_FILE=docker-compose.yml:docker-compose.caching.yml +#COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml +#COMPOSE_FILE=docker-compose.yml:docker-compose.postgres.yml + +# Choose a version +DOCKER_MYSQL_IMAGE=percona:5.7 +DOCKER_POSTGRES_IMAGE=postgres diff --git a/tests/README.md b/tests/README.md index a2c4686..6adb857 100644 --- a/tests/README.md +++ b/tests/README.md @@ -103,7 +103,7 @@ Create database with sqlcmd Create database (one-liner) - docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -P Mircosoft-12345 -Q "CREATE DATABASE yii2test" + docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -P Microsoft-12345 -Q "CREATE DATABASE yii2test" Run MSSQL tests diff --git a/tests/docker-compose.caching.yml b/tests/docker-compose.caching.yml new file mode 100644 index 0000000..ecbad3e --- /dev/null +++ b/tests/docker-compose.caching.yml @@ -0,0 +1,13 @@ +version: '2' +services: + + php: + depends_on: + - redis + - memcached + + redis: + image: redis + + memcached: + image: memcached diff --git a/tests/docker-compose.mysql.yml b/tests/docker-compose.mysql.yml new file mode 100644 index 0000000..3ae7398 --- /dev/null +++ b/tests/docker-compose.mysql.yml @@ -0,0 +1,15 @@ +version: '2' +services: + + php: + depends_on: + - mysql + + mysql: + image: ${DOCKER_MYSQL_IMAGE} + environment: + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_DATABASE=yiitest + - MYSQL_USER=travis + - MYSQL_PASSWORD=travis + diff --git a/tests/docker-compose.pgsql.yml b/tests/docker-compose.pgsql.yml new file mode 100644 index 0000000..34c481b --- /dev/null +++ b/tests/docker-compose.pgsql.yml @@ -0,0 +1,14 @@ +version: '2' + +services: + + php: + depends_on: + - postgres + + postgres: + image: ${DOCKER_POSTGRES_IMAGE} + environment: + - POSTGRES_DB=yiitest + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres \ No newline at end of file diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index a383fb3..37870f6 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -4,38 +4,18 @@ services: php: build: .. working_dir: /project - depends_on: - - postgres - - mysql #volumes: - # Enable for debugging + # Enable for debugging, enabling tests might be slow for file access (data) on host-volume #- ../tests:/project/tests # - ../framework:/project/framework - - mysql: - image: percona:5.7 - environment: - - MYSQL_ROOT_PASSWORD=secret - - MYSQL_DATABASE=yiitest - - MYSQL_USER=travis - - MYSQL_PASSWORD=travis - - postgres: - image: postgres - environment: - - POSTGRES_DB=yiitest - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - redis: - image: redis - - memcached: - image: memcached + # Tmpfs volume (experimental, asset tests may fail) + #tmpfs: + # - /project/tests/runtime networks: default: ipam: driver: default config: + # prevent overlapping/duplicated networks, since docker assigns /16 subnets by default ranging from 10.x over 172.x to 192.168.x - subnet: 10.100.1${TUPLE_C}.1/24 \ No newline at end of file diff --git a/tests/test-local.sh b/tests/test-local.sh index 6edd620..e4465c2 100644 --- a/tests/test-local.sh +++ b/tests/test-local.sh @@ -33,13 +33,18 @@ case $1 in export TUPLE_C=$(expr ${CI_BUILD_ID} % 255) echo ${TUPLE_C} cd mssql - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql docker-compose up --build -d 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 10 - docker-compose run --rm sqlcmd sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Mircosoft-12345 + docker-compose run --rm sqlcmd sh -c 'sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Microsoft-12345' + sleep 10 + docker-compose logs mssql + docker-compose config + pwd + docker-compose ps + docker-compose run --rm php php -i docker-compose run --rm php vendor/bin/phpunit -v --group mssql - docker-compose down -v + docker-compose down -v --remove-orphans ;; 'pgsql') export ISOLATION=buildpipeline${CI_PIPELINE_ID} From 3bcbee38fed85a3719741c98d80be92d97b88b6b Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Tue, 24 Jan 2017 20:45:29 +0100 Subject: [PATCH 008/184] updated build branches --- .gitlab-ci.yml | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a45b417..76555b3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,7 @@ before_script: # 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} @@ -24,9 +25,6 @@ stages: test: stage: test - only: - - tests/base - - tests/all 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 @@ -35,7 +33,7 @@ caching: stage: test only: - tests/caching - - tests/all + - tests/full script: - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml - docker-compose up --build -d @@ -45,7 +43,7 @@ db: stage: test only: - tests/mysql - - tests/all + - 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 @@ -55,7 +53,7 @@ mysql: stage: test only: - tests/mysql - - tests/all + - tests/full script: - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml - docker-compose up --build -d @@ -68,7 +66,7 @@ pgsql: stage: test only: - tests/pgsql - - tests/all + - tests/full script: - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml - docker-compose up --build -d @@ -81,7 +79,7 @@ cubrid: stage: test only: - tests/cubrid - - tests/all + - tests/extra script: - cd cubrid - docker-compose up --build -d @@ -95,7 +93,7 @@ mssql: stage: test only: - tests/mssql - - tests/all + - tests/extra script: - cd mssql - docker-compose up --build -d @@ -118,28 +116,3 @@ travis: - sleep 10 - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid - docker-compose down -v --remove-orphans - -# temporary cleanup stage -cleanup: - stage: cleanup - when: always - script: - - docker-compose down -v --remove-orphans - - export COMPOSE_PROJECT_NAME=${ISOLATION}caching - - docker-compose down -v --remove-orphans - - export COMPOSE_PROJECT_NAME=${ISOLATION}db - - docker-compose down -v --remove-orphans - - export COMPOSE_PROJECT_NAME=${ISOLATION}mysql - - docker-compose down -v --remove-orphans - - export COMPOSE_PROJECT_NAME=${ISOLATION}pgsql - - docker-compose down -v --remove-orphans - - export COMPOSE_PROJECT_NAME=${ISOLATION}travis - - docker-compose down -v --remove-orphans - - pushd mssql - - export COMPOSE_PROJECT_NAME=${ISOLATION}mssql - - docker-compose down -v --remove-orphans - - popd - - pushd cubrid - - export COMPOSE_PROJECT_NAME=${ISOLATION}cubrid - - docker-compose down -v --remove-orphans - - popd From 7b43cf09f1e65778d5908cf0016e9eb0cc6ad33f Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 9 Feb 2017 16:27:40 +0100 Subject: [PATCH 009/184] added separate docker config for testing --- tests/data/config-docker.php | 59 ++++++++++++++++++++++++++++++++++++++++++++ tests/data/config.php | 14 +++++------ 2 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tests/data/config-docker.php diff --git a/tests/data/config-docker.php b/tests/data/config-docker.php new file mode 100644 index 0000000..606f984 --- /dev/null +++ b/tests/data/config-docker.php @@ -0,0 +1,59 @@ + [ + 'cubrid' => [ + 'dsn' => 'cubrid:dbname=demodb;host=cubrid;port=33000', + 'username' => 'dba', + 'password' => '', + 'fixture' => __DIR__ . '/cubrid.sql', + ], + 'mysql' => [ + 'dsn' => 'mysql:host=mysql;dbname=yiitest', + 'username' => 'travis', + 'password' => 'travis', + 'fixture' => __DIR__ . '/mysql.sql', + ], + 'sqlite' => [ + 'dsn' => 'sqlite::memory:', + 'fixture' => __DIR__ . '/sqlite.sql', + ], + 'sqlsrv' => [ + 'dsn' => 'sqlsrv:Server=mssql;Database=yii2test', + 'username' => 'sa', + 'password' => 'Microsoft-12345', + 'fixture' => __DIR__ . '/mssql.sql', + ], + 'pgsql' => [ + 'dsn' => 'pgsql:host=postgres;dbname=yiitest;port=5432;', + 'username' => 'postgres', + 'password' => 'postgres', + 'fixture' => __DIR__ . '/postgres.sql', + ], + 'oci' => [ + 'dsn' => 'oci:dbname=LOCAL_XE;charset=AL32UTF8;', + 'username' => '', + 'password' => '', + 'fixture' => __DIR__ . '/oci.sql', + ], + ], +]; + +if (is_file(__DIR__ . '/config.local.php')) { + include(__DIR__ . '/config.local.php'); +} + +return $config; \ No newline at end of file diff --git a/tests/data/config.php b/tests/data/config.php index 606f984..1072586 100644 --- a/tests/data/config.php +++ b/tests/data/config.php @@ -16,15 +16,15 @@ $config['databases']['mysql']['password'] = 'changeme'; $config = [ 'databases' => [ 'cubrid' => [ - 'dsn' => 'cubrid:dbname=demodb;host=cubrid;port=33000', + 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', 'username' => 'dba', 'password' => '', 'fixture' => __DIR__ . '/cubrid.sql', ], 'mysql' => [ - 'dsn' => 'mysql:host=mysql;dbname=yiitest', + 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', 'username' => 'travis', - 'password' => 'travis', + 'password' => '', 'fixture' => __DIR__ . '/mysql.sql', ], 'sqlite' => [ @@ -32,13 +32,13 @@ $config = [ 'fixture' => __DIR__ . '/sqlite.sql', ], 'sqlsrv' => [ - 'dsn' => 'sqlsrv:Server=mssql;Database=yii2test', - 'username' => 'sa', - 'password' => 'Microsoft-12345', + 'dsn' => 'sqlsrv:Server=localhost;Database=test', + 'username' => '', + 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', ], 'pgsql' => [ - 'dsn' => 'pgsql:host=postgres;dbname=yiitest;port=5432;', + 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', 'fixture' => __DIR__ . '/postgres.sql', From 0dd1ed65942a466cc9e81c53ae7a205e03c20cc8 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 9 Feb 2017 16:56:41 +0100 Subject: [PATCH 010/184] use custom docker config in dockerized tests --- tests/docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 37870f6..570abcb 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -4,7 +4,8 @@ services: php: build: .. working_dir: /project - #volumes: + volumes: + - ../tests/data/config-docker.php:/project/tests/data/config.php # Enable for debugging, enabling tests might be slow for file access (data) on host-volume #- ../tests:/project/tests # - ../framework:/project/framework From f96ea4c03267675e61a95169903220c0c0ac0c69 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 9 Feb 2017 16:58:22 +0100 Subject: [PATCH 011/184] removed TODOs --- .gitlab-ci.yml | 2 +- Dockerfile-cubrid | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76555b3..fb606cd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -112,7 +112,7 @@ travis: script: - export COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml:docker-compose.pgsql.yml - docker-compose up --build -d - # TODO: retry/wait for db + # wait for dbs ... - sleep 10 - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid - docker-compose down -v --remove-orphans diff --git a/Dockerfile-cubrid b/Dockerfile-cubrid index 6270c11..6d3de1a 100644 --- a/Dockerfile-cubrid +++ b/Dockerfile-cubrid @@ -5,7 +5,6 @@ RUN pecl install pdo_cubrid-9.3.0.0001 RUN echo "extension=pdo_cubrid.so" > /usr/local/etc/php/conf.d/cubrid.ini -# TODO: temporary section ---> # Install system packages for composer (git) RUN apt-get update && \ apt-get -y install \ @@ -23,7 +22,7 @@ ENV COMPOSER_ALLOW_SUPERUSER 1 RUN curl -sS https://getcomposer.org/installer | php -- \ --filename=composer.phar \ --install-dir=/usr/local/bin -# TODO: <--- end + # Project source-code WORKDIR /project From 4e29c44cbfd6bbf5a2d1372b3e5f80f9aab0f8fc Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 16 Feb 2017 15:29:00 +0100 Subject: [PATCH 012/184] cleaned cleanup (done in after_script) --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fb606cd..32c6447 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,7 +47,7 @@ db: script: - docker-compose up --build -d - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid - - docker-compose down -v --remove-orphans + mysql: stage: test @@ -60,7 +60,7 @@ mysql: # 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 - - docker-compose down -v --remove-orphans + pgsql: stage: test @@ -73,7 +73,7 @@ pgsql: # 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 - - docker-compose down -v --remove-orphans + cubrid: stage: test @@ -87,7 +87,7 @@ cubrid: - 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 - - docker-compose down -v --remove-orphans + mssql: stage: test @@ -103,7 +103,7 @@ mssql: # 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 - - docker-compose down -v --remove-orphans + travis: stage: travis @@ -115,4 +115,4 @@ travis: # wait for dbs ... - sleep 10 - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,xcache,zenddata,cubrid - - docker-compose down -v --remove-orphans + From 7a4666fe3a38f1f8044721ad8c9a912dfb85b476 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 16 Feb 2017 15:29:14 +0100 Subject: [PATCH 013/184] added build trigger example --- tests/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/README.md b/tests/README.md index 6adb857..d1cee04 100644 --- a/tests/README.md +++ b/tests/README.md @@ -110,6 +110,15 @@ Run MSSQL tests docker-compose run --rm php $ vendor/bin/phpunit --group mssql +### Build triggers + + curl -X POST \ + -F token=${TOKEN} \ + -F ref=travis \ + -F "variables[DOCKER_MYSQL_IMAGE]=mysql:5.6" \ + -F "variables[DOCKER_POSTGRES_IMAGE]=postgres:9.5" \ + ${TRIGGER_URL} + ### Run tests locally #### Via shell script From ec8daa0d77c61fe8eccda161f4aa6052f62076e0 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Thu, 16 Feb 2017 15:42:24 +0100 Subject: [PATCH 014/184] added db group to test --- tests/framework/console/controllers/DbMessageControllerTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/framework/console/controllers/DbMessageControllerTest.php b/tests/framework/console/controllers/DbMessageControllerTest.php index f426532..24117f3 100644 --- a/tests/framework/console/controllers/DbMessageControllerTest.php +++ b/tests/framework/console/controllers/DbMessageControllerTest.php @@ -1,6 +1,10 @@ Date: Thu, 16 Feb 2017 16:00:24 +0100 Subject: [PATCH 015/184] added groups to tests --- .gitlab-ci.yml | 2 +- tests/framework/console/controllers/CacheControllerTest.php | 2 ++ tests/framework/console/controllers/DbMessageControllerTest.php | 7 +++---- tests/framework/console/controllers/MigrateControllerTest.php | 1 + tests/framework/mutex/MysqlMutexTest.php | 1 + tests/framework/mutex/PgsqlMutexTest.php | 1 + tests/framework/rbac/DbManagerTestCase.php | 3 +++ tests/framework/rbac/MySQLManagerCacheTest.php | 3 ++- tests/framework/rbac/MySQLManagerTest.php | 1 + tests/framework/rbac/PgSQLManagerTest.php | 1 + 10 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 32c6447..37bc6d3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,7 +46,7 @@ db: - 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 + - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid,oci mysql: diff --git a/tests/framework/console/controllers/CacheControllerTest.php b/tests/framework/console/controllers/CacheControllerTest.php index 73e00d1..ccdf935 100644 --- a/tests/framework/console/controllers/CacheControllerTest.php +++ b/tests/framework/console/controllers/CacheControllerTest.php @@ -11,6 +11,8 @@ use yii\console\controllers\CacheController; * @see CacheController * * @group console + * @group db + * @group mysql */ class CacheControllerTest extends TestCase { diff --git a/tests/framework/console/controllers/DbMessageControllerTest.php b/tests/framework/console/controllers/DbMessageControllerTest.php index 24117f3..02de2bd 100644 --- a/tests/framework/console/controllers/DbMessageControllerTest.php +++ b/tests/framework/console/controllers/DbMessageControllerTest.php @@ -1,15 +1,14 @@ Date: Wed, 1 Mar 2017 19:29:30 +0200 Subject: [PATCH 016/184] Added `yii\di\Instance::__set_state()` method --- framework/CHANGELOG.md | 1 + framework/di/Instance.php | 18 ++++++++++++++++++ tests/framework/di/InstanceTest.php | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b2de0b3..3db93dc 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -40,6 +40,7 @@ Yii Framework 2 Change Log - Enh #13650: Improved `yii\base\Security::hkdf()` to take advantage of native `hash_hkdf()` implementation in PHP >= 7.1.2 (charlesportwoodii) - Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire) - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) +- Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) 2.0.11.2 February 08, 2017 diff --git a/framework/di/Instance.php b/framework/di/Instance.php index a11491a..6a5e567 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -161,4 +161,22 @@ class Instance return Yii::$container->get($this->id); } } + + /** + * Restores class state after using `var_export()` + * + * @param array $state + * @return Instance + * @throws InvalidConfigException when $state property does not contain `id` parameter + * @see var_export() + * @since 2.0.12 + */ + public static function __set_state($state) + { + if (!isset($state['id'])) { + throw new InvalidConfigException('Failed to instantiate class "Instance". Required parameter "id" is missing'); + } + + return new self($state['id']); + } } diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 2ac448d..349b55a 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -156,4 +156,31 @@ class InstanceTest extends TestCase $this->assertInstanceOf('yii\db\Connection', $db = $cache->db); $this->assertEquals('sqlite:path/to/file.db', $db->dsn); } + + public function testRestoreAfterVarExport() + { + $instance = Instance::of('something'); + $export = var_export($instance, true); + + $this->assertEquals(<<<'PHP' +yii\di\Instance::__set_state(array( + 'id' => 'something', +)) +PHP + , $export); + + $this->assertEquals($instance, Instance::__set_state([ + 'id' => 'something', + ])); + } + + public function testRestoreAfterVarExportRequiresId() + { + $this->setExpectedException( + 'yii\base\InvalidConfigException', + 'Failed to instantiate class "Instance". Required parameter "id" is missing' + ); + + Instance::__set_state([]); + } } From 5e422fd27d4ca1097a316cb56d6fbcbe939f8d91 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 2 Mar 2017 12:58:08 +0200 Subject: [PATCH 017/184] Fixed InstanceTest::testRestoreAfterVarExport() to work on HHVM --- tests/framework/di/InstanceTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 349b55a..4a660a9 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -162,10 +162,10 @@ class InstanceTest extends TestCase $instance = Instance::of('something'); $export = var_export($instance, true); - $this->assertEquals(<<<'PHP' -yii\di\Instance::__set_state(array( - 'id' => 'something', -)) + $this->assertRegExp(<<<'PHP' +@yii\\di\\Instance::__set_state\(array\( +\s+'id' => 'something', +\)\)@ PHP , $export); From 1cb1a978f2a4f01060cb918b708e52bb168c9949 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 2 Mar 2017 20:49:31 +0300 Subject: [PATCH 018/184] Remove imports of inexistent classes --- tests/framework/web/ControllerTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/framework/web/ControllerTest.php b/tests/framework/web/ControllerTest.php index 6dbce18..5f32b61 100644 --- a/tests/framework/web/ControllerTest.php +++ b/tests/framework/web/ControllerTest.php @@ -10,8 +10,6 @@ namespace yiiunit\framework\web; use Yii; use yii\base\InlineAction; use yii\web\Response; -use yiiunit\framework\web\stubs\Bar; -use yiiunit\framework\web\stubs\OtherQux; use yiiunit\TestCase; /** From 8f88780104cc59e9af26219b259f653d9c00bc26 Mon Sep 17 00:00:00 2001 From: Dmitry Naumenko Date: Fri, 3 Mar 2017 09:30:03 +0200 Subject: [PATCH 019/184] Fixed typo in warning message in Cache::getOrSet() Fixes #13690 --- framework/caching/Cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index 4693d18..4efc473 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -574,7 +574,7 @@ abstract class Cache extends Component implements \ArrayAccess $value = call_user_func($closure, $this); if (!$this->set($key, $value, $duration, $dependency)) { - Yii::warning('Failed to set cache value for key ' . json_encode($value), __METHOD__); + Yii::warning('Failed to set cache value for key ' . json_encode($key), __METHOD__); } return $value; From 608013f3e01ff16af4d6b846cf5a9b63219f0625 Mon Sep 17 00:00:00 2001 From: Kyle McCarthy Date: Fri, 3 Mar 2017 11:28:58 -0600 Subject: [PATCH 020/184] fixed spelling error in main README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4825d3d..5875553 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ You may join us and: - [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) -### Reporting Secirity issues +### Reporting Security issues Please refer to a [special page at the website](http://www.yiiframework.com/security/) describing proper workflow for security issue reports. From 0b3c066bd20b870a8db3e9a0a06a84e64c9d7184 Mon Sep 17 00:00:00 2001 From: Kyle McCarthy Date: Sat, 4 Mar 2017 09:55:17 -0600 Subject: [PATCH 021/184] Fixes #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself --- framework/CHANGELOG.md | 1 + framework/web/Response.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3db93dc..8afa5a5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -41,6 +41,7 @@ Yii Framework 2 Change Log - Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire) - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) +- Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) 2.0.11.2 February 08, 2017 diff --git a/framework/web/Response.php b/framework/web/Response.php index dc63fc9..723d8c7 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -271,6 +271,7 @@ class Response extends \yii\base\Response * @param int $value the status code * @param string $text the status text. If not set, it will be set automatically based on the status code. * @throws InvalidParamException if the status code is invalid. + * @return $this the response object itself */ public function setStatusCode($value, $text = null) { @@ -286,6 +287,7 @@ class Response extends \yii\base\Response } else { $this->statusText = $text; } + return $this; } /** From e66722aa4397c33518f6bbafa7953df0efc70505 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 4 Mar 2017 23:44:57 +0100 Subject: [PATCH 022/184] Update .travis.yml allow building on all branches. /cc @Silverfire --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 404d5a3..f458b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,12 @@ dist: trusty sudo: false # build only on master branches -branches: - only: - - master - - 2.1 +# commented as this prevents people from running builds on their forks: +# https://github.com/yiisoft/yii2/commit/bd87be990fa238c6d5e326d0a171f38d02dc253a +#branches: +# only: +# - master +# - 2.1 # From f9041646ddaac6760b727a4fdc6d3e37705e69ef Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Sun, 5 Mar 2017 14:27:13 +0300 Subject: [PATCH 023/184] Improve string validator test coverage --- tests/framework/validators/StringValidatorTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/framework/validators/StringValidatorTest.php b/tests/framework/validators/StringValidatorTest.php index 3f6100f..eadf4fa 100644 --- a/tests/framework/validators/StringValidatorTest.php +++ b/tests/framework/validators/StringValidatorTest.php @@ -22,6 +22,8 @@ class StringValidatorTest extends TestCase $val = new StringValidator(); $this->assertFalse($val->validate(['not a string'])); $this->assertTrue($val->validate('Just some string')); + $this->assertFalse($val->validate(true)); + $this->assertFalse($val->validate(false)); } public function testValidateValueLength() @@ -70,6 +72,12 @@ class StringValidatorTest extends TestCase $model->attr_string = 'a tet string'; $val->validateAttribute($model, 'attr_string'); $this->assertFalse($model->hasErrors()); + $model->attr_string = true; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors()); + $model->attr_string = false; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors()); $val = new StringValidator(['length' => 20]); $model = new FakedValidationModel(); $model->attr_string = str_repeat('x', 20); From a6d266405d53be96e4d189acbbaec1bc6362626d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 6 Mar 2017 15:24:18 +0300 Subject: [PATCH 024/184] Fixes #13671: Fixed error handler trace to work correctly with XDebug --- framework/CHANGELOG.md | 1 + framework/views/errorHandler/exception.php | 8 +------- framework/web/ErrorHandler.php | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 8afa5a5..13f5f7a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) - Bug #7946 Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) - Bug #13087: Fixed getting active validators for safe attribute (developeruz) diff --git a/framework/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php index 03a384c..db3e88b 100644 --- a/framework/views/errorHandler/exception.php +++ b/framework/views/errorHandler/exception.php @@ -378,13 +378,7 @@ body.mousedown pre {
-
    - renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1) ?> - getTrace(), $length = count($trace); $i < $length; ++$i): ?> - renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, - @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, @$trace[$i]['args'] ?: [], $i + 2) ?> - -
+ renderCallStack($exception) ?>
diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index b43d235..dfeca72 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -309,6 +309,30 @@ class ErrorHandler extends \yii\base\ErrorHandler } /** + * Renders call stack. + * @param \Exception $exception exception to get call stack from + * @return string HTML content of the rendered call stack. + */ + public function renderCallStack(\Exception $exception) + { + $out = '
    '; + $out .= $this->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1); + for ($i = 0, $trace = $exception->getTrace(), $length = count($trace); $i < $length; ++$i) { + $file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null; + $line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null; + $class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null; + $function = null; + if (!empty($trace[$i]['function']) && $trace[$i]['function'] !== 'unknown') { + $function = $trace[$i]['function']; + } + $args = !empty($trace[$i]['args']) ? $trace[$i]['args'] : []; + $out .= $this->renderCallStackItem($file, $line, $class, $function, $args, $i + 2); + } + $out .= '
'; + return $out; + } + + /** * Renders the global variables of the request. * List of global variables is defined in [[displayVars]]. * @return string the rendering result From acf7c8592afa20b64287989385b240c3d3710313 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Mon, 6 Mar 2017 14:16:15 +0100 Subject: [PATCH 025/184] moved Dockerfile(s) to test folders --- Dockerfile-cubrid | 31 ----------------------- Dockerfile-mssql | 56 ----------------------------------------- tests/cubrid/Dockerfile | 31 +++++++++++++++++++++++ tests/cubrid/docker-compose.yml | 2 +- tests/mssql/Dockerfile | 56 +++++++++++++++++++++++++++++++++++++++++ tests/mssql/docker-compose.yml | 2 +- 6 files changed, 89 insertions(+), 89 deletions(-) delete mode 100644 Dockerfile-cubrid delete mode 100644 Dockerfile-mssql create mode 100644 tests/cubrid/Dockerfile create mode 100644 tests/mssql/Dockerfile diff --git a/Dockerfile-cubrid b/Dockerfile-cubrid deleted file mode 100644 index 6d3de1a..0000000 --- a/Dockerfile-cubrid +++ /dev/null @@ -1,31 +0,0 @@ -FROM php:5-fpm - -# /usr/local/lib/php/extensions/no-debug-non-zts-20131226/cubrid.so -RUN pecl install pdo_cubrid-9.3.0.0001 -RUN echo "extension=pdo_cubrid.so" > /usr/local/etc/php/conf.d/cubrid.ini - - -# Install system packages for composer (git) -RUN apt-get update && \ - apt-get -y install \ - git \ - --no-install-recommends && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Register the COMPOSER_HOME environment variable -ENV COMPOSER_HOME /composer -# Add global binary directory to PATH and make sure to re-export it -ENV PATH /usr/local/bin:$PATH -# Allow Composer to be run as root -ENV COMPOSER_ALLOW_SUPERUSER 1 -# Install composer -RUN curl -sS https://getcomposer.org/installer | php -- \ - --filename=composer.phar \ - --install-dir=/usr/local/bin - - -# Project source-code -WORKDIR /project -ADD composer.* /project/ -RUN /usr/local/bin/composer.phar install --prefer-dist -ADD ./ /project diff --git a/Dockerfile-mssql b/Dockerfile-mssql deleted file mode 100644 index 5e9f9a5..0000000 --- a/Dockerfile-mssql +++ /dev/null @@ -1,56 +0,0 @@ -FROM bylexus/apache-php7 - - -# https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu -RUN apt-get update -RUN apt-get install -y curl apt-transport-https - -RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - -RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list - -RUN apt-get update \ - && apt-get install -y unixodbc-dev-utf16 php-dev \ - && pecl install sqlsrv pdo_sqlsrv - -RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini -RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini -RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini -RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini - -# IMPORTANT NOTICE! Install `msodbcsql` after `unixodbc-dev-utf16` and `pdo_sqlsrv`, due to dependency & build issues -RUN ACCEPT_EULA=Y apt-get install -y msodbcsql - -# Install system packages for composer (git) -RUN apt-get update && \ - apt-get -y install \ - git \ - php-curl \ - --no-install-recommends && \ - rm -rf /tmp/* /var/tmp/* -# Register the COMPOSER_HOME environment variable -ENV COMPOSER_HOME /composer -# Add global binary directory to PATH and make sure to re-export it -ENV PATH /usr/local/bin:$PATH -# Allow Composer to be run as root -ENV COMPOSER_ALLOW_SUPERUSER 1 -# Install composer -RUN curl -sS https://getcomposer.org/installer | php -- \ - --filename=composer.phar \ - --install-dir=/usr/local/bin -RUN composer.phar global require --optimize-autoloader \ - "hirak/prestissimo" - - -# Project source-code -WORKDIR /project -ADD composer.* /project/ -RUN /usr/local/bin/composer.phar install --prefer-dist -ADD ./ /project - -# https://github.com/Microsoft/msphpsql/issues/161 -RUN apt-get install -y locales \ - && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ - && locale-gen - -# Debug installation -RUN dpkg -L msodbcsql diff --git a/tests/cubrid/Dockerfile b/tests/cubrid/Dockerfile new file mode 100644 index 0000000..6d3de1a --- /dev/null +++ b/tests/cubrid/Dockerfile @@ -0,0 +1,31 @@ +FROM php:5-fpm + +# /usr/local/lib/php/extensions/no-debug-non-zts-20131226/cubrid.so +RUN pecl install pdo_cubrid-9.3.0.0001 +RUN echo "extension=pdo_cubrid.so" > /usr/local/etc/php/conf.d/cubrid.ini + + +# Install system packages for composer (git) +RUN apt-get update && \ + apt-get -y install \ + git \ + --no-install-recommends && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Register the COMPOSER_HOME environment variable +ENV COMPOSER_HOME /composer +# Add global binary directory to PATH and make sure to re-export it +ENV PATH /usr/local/bin:$PATH +# Allow Composer to be run as root +ENV COMPOSER_ALLOW_SUPERUSER 1 +# Install composer +RUN curl -sS https://getcomposer.org/installer | php -- \ + --filename=composer.phar \ + --install-dir=/usr/local/bin + + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer.phar install --prefer-dist +ADD ./ /project diff --git a/tests/cubrid/docker-compose.yml b/tests/cubrid/docker-compose.yml index d2fbb8f..8085334 100644 --- a/tests/cubrid/docker-compose.yml +++ b/tests/cubrid/docker-compose.yml @@ -4,7 +4,7 @@ services: php: build: context: ../.. - dockerfile: Dockerfile-cubrid + dockerfile: tests/cubrid/Dockerfile ports: - 80 #volumes: diff --git a/tests/mssql/Dockerfile b/tests/mssql/Dockerfile new file mode 100644 index 0000000..5e9f9a5 --- /dev/null +++ b/tests/mssql/Dockerfile @@ -0,0 +1,56 @@ +FROM bylexus/apache-php7 + + +# https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu +RUN apt-get update +RUN apt-get install -y curl apt-transport-https + +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + +RUN apt-get update \ + && apt-get install -y unixodbc-dev-utf16 php-dev \ + && pecl install sqlsrv pdo_sqlsrv + +RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini +RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini +RUN echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini +RUN echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini + +# IMPORTANT NOTICE! Install `msodbcsql` after `unixodbc-dev-utf16` and `pdo_sqlsrv`, due to dependency & build issues +RUN ACCEPT_EULA=Y apt-get install -y msodbcsql + +# Install system packages for composer (git) +RUN apt-get update && \ + apt-get -y install \ + git \ + php-curl \ + --no-install-recommends && \ + rm -rf /tmp/* /var/tmp/* +# Register the COMPOSER_HOME environment variable +ENV COMPOSER_HOME /composer +# Add global binary directory to PATH and make sure to re-export it +ENV PATH /usr/local/bin:$PATH +# Allow Composer to be run as root +ENV COMPOSER_ALLOW_SUPERUSER 1 +# Install composer +RUN curl -sS https://getcomposer.org/installer | php -- \ + --filename=composer.phar \ + --install-dir=/usr/local/bin +RUN composer.phar global require --optimize-autoloader \ + "hirak/prestissimo" + + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +RUN /usr/local/bin/composer.phar install --prefer-dist +ADD ./ /project + +# https://github.com/Microsoft/msphpsql/issues/161 +RUN apt-get install -y locales \ + && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ + && locale-gen + +# Debug installation +RUN dpkg -L msodbcsql diff --git a/tests/mssql/docker-compose.yml b/tests/mssql/docker-compose.yml index dde8cad..a68eb35 100644 --- a/tests/mssql/docker-compose.yml +++ b/tests/mssql/docker-compose.yml @@ -4,7 +4,7 @@ services: php: build: context: ../.. - dockerfile: Dockerfile-mssql + dockerfile: tests/mssql/Dockerfile # Alternative pre-built image (TODO: evaluate) #image: ppoffice/apache-php-mssql-odbc ports: From a1fd4d30695efb97f4bdb40ff8a5cd315f0e3c06 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Mon, 6 Mar 2017 14:16:24 +0100 Subject: [PATCH 026/184] updated changelog --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c4824f4..0028798 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Enh #13360: Dockerized test setup (schmunk42) - Enh #13523: Plural rule for pasta (developeruz) - Bug #13538: Fixed `yii\db\BaseActiveRecord::deleteAll()` changes method signature declared by `yii\db\ActiveRecordInterface::deleteAll()` (klimov-paul) - Enh #13278: `yii\caching\DbQueryDependency` created allowing specification of the cache dependency via `yii\db\QueryInterface` (klimov-paul) From 572b27035d166c64b7cb94be718eed52cb4ecc1a Mon Sep 17 00:00:00 2001 From: Alexey Rogachev Date: Mon, 6 Mar 2017 23:57:28 +0600 Subject: [PATCH 027/184] Added missing colon in 2.0.12 section [skip ci] --- framework/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 12a3c78..94d170a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -6,7 +6,7 @@ Yii Framework 2 Change Log - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) -- Bug #7946 Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) +- Bug #7946: Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) - Bug #13087: Fixed getting active validators for safe attribute (developeruz) - Bug #13571: Fix `yii\db\mssql\QueryBuilder::checkIntegrity` for all tables (boboldehampsink) - Bug #11230: Include `defaultRoles` in `yii\rbac\DbManager->getRolesByUser()` results (developeruz) From b39e50ecd866cda8316c2c6fc123380292ab6a47 Mon Sep 17 00:00:00 2001 From: Alexey Rogachev Date: Tue, 7 Mar 2017 13:15:28 +0600 Subject: [PATCH 028/184] Removed duplicate line [skip ci] --- framework/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 94d170a..bb9c066 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -40,7 +40,6 @@ Yii Framework 2 Change Log - Enh #13360: Added Dockerized test setup for the framework tests (schmunk42) - Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev) - Enh #13650: Improved `yii\base\Security::hkdf()` to take advantage of native `hash_hkdf()` implementation in PHP >= 7.1.2 (charlesportwoodii) -- Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire) - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) From 55dd16f1f7878344d0cf0464cb264f8e165034a3 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 8 Mar 2017 03:58:46 +0800 Subject: [PATCH 029/184] Fixes #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception --- framework/CHANGELOG.md | 1 + framework/db/Connection.php | 28 +- .../framework/db/mysql/connection/DeadLockTest.php | 325 +++++++++++++++++++++ 3 files changed, 348 insertions(+), 6 deletions(-) create mode 100644 tests/framework/db/mysql/connection/DeadLockTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index bb9c066..2faf8d7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -25,6 +25,7 @@ Yii Framework 2 Change Log - Bug #13592: Fixes Oracle’s `yii\db\oci\Schema::setTransactionIsolationLevel()` (sergeymakinen) - Bug #13594: Fixes insufficient quoting in `yii\db\QueryBuilder::prepareInsertSelectSubQuery()` (sergeymakinen) - Bug #8120: Fixes LIKE special characters escaping for Cubrid/MSSQL/Oracle/SQLite in `yii\db\QueryBuilder` (sergeymakinen) +- Bug #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception (Vovan-VE) - Enh #8641: Enhanced `yii\console\Request::resolve()` to prevent passing parameters, that begin from digits (silverfire) - Enh #13278: `yii\caching\DbQueryDependency` created allowing specification of the cache dependency via `yii\db\QueryInterface` (klimov-paul) - Enh #13467: `yii\data\ActiveDataProvider` no longer queries models if models count is zero (kLkA, Kolyunya) diff --git a/framework/db/Connection.php b/framework/db/Connection.php index ef4fc10..0ed9610 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -713,14 +713,10 @@ class Connection extends Component $transaction->commit(); } } catch (\Exception $e) { - if ($transaction->isActive && $transaction->level === $level) { - $transaction->rollBack(); - } + $this->rollbackTransactionOnLevel($transaction, $level); throw $e; } catch (\Throwable $e) { - if ($transaction->isActive && $transaction->level === $level) { - $transaction->rollBack(); - } + $this->rollbackTransactionOnLevel($transaction, $level); throw $e; } @@ -728,6 +724,26 @@ class Connection extends Component } /** + * Rolls back given [[Transaction]] object if it's still active and level match. + * In some cases rollback can fail, so this method is fail safe. Exception thrown + * from rollback will be caught and just logged with [[\Yii::error()]]. + * @param Transaction $transaction Transaction object given from [[beginTransaction()]]. + * @param int $level Transaction level just after [[beginTransaction()]] call. + */ + protected function rollbackTransactionOnLevel($transaction, $level) + { + if ($transaction->isActive && $transaction->level === $level) { + // https://github.com/yiisoft/yii2/pull/13347 + try { + $transaction->rollBack(); + } catch (\Exception $e) { + \Yii::error($e, __METHOD__); + // hide this exception to be able to continue throwing original exception outside + } + } + } + + /** * Returns the schema information for the database opened by this connection. * @return Schema the schema information for the database opened by this connection. * @throws NotSupportedException if there is no support for the current driver type diff --git a/tests/framework/db/mysql/connection/DeadLockTest.php b/tests/framework/db/mysql/connection/DeadLockTest.php new file mode 100644 index 0000000..9922b84 --- /dev/null +++ b/tests/framework/db/mysql/connection/DeadLockTest.php @@ -0,0 +1,325 @@ +markTestSkipped('pcntl_fork() is not available'); + } + if (!function_exists('posix_kill')) { + $this->markTestSkipped('posix_kill() is not available'); + } + // HHVM does not support this (?) + if (!function_exists('pcntl_sigtimedwait')) { + $this->markTestSkipped('pcntl_sigtimedwait() is not available'); + } + + $this->setLogFile(sys_get_temp_dir() . '/deadlock_' . posix_getpid()); + $this->deleteLog(); + + try { + // to cause deadlock we do: + // + // 1. FIRST errornously forgot "FOR UPDATE" while read the row for next update. + // 2. SECOND does update the row and locks it exclusively. + // 3. FIRST tryes to update the row too, but it already has shared lock. Here comes deadlock. + + // FIRST child will send the signal to the SECOND child. + // So, SECOND child should be forked at first to obtain its PID. + + $pidSecond = pcntl_fork(); + if (-1 === $pidSecond) { + $this->markTestIncomplete('cannot fork'); + } + if (0 === $pidSecond) { + // SECOND child + $this->setErrorHandler(); + exit($this->childrenUpdateLocked()); + } + + $pidFirst = pcntl_fork(); + if (-1 === $pidFirst) { + $this->markTestIncomplete('cannot fork second child'); + } + if (0 === $pidFirst) { + // FIRST child + $this->setErrorHandler(); + exit($this->childrenSelectAndAccidentUpdate($pidSecond)); + } + + // PARENT + // nothing to do + } catch (\Exception $e) { + // wait all children + while (-1 !== pcntl_wait($status)) { + // nothing to do + } + $this->deleteLog(); + throw $e; + } catch (\Throwable $e) { + // wait all children + while (-1 !== pcntl_wait($status)) { + // nothing to do + } + $this->deleteLog(); + throw $e; + } + + // wait all children + // all must exit with success + $errors = []; + $deadlockHitCount = 0; + while (-1 !== pcntl_wait($status)) { + if (!pcntl_wifexited($status)) { + $errors[] = 'child did not exit itself'; + } else { + $exitStatus = pcntl_wexitstatus($status); + if (self::CHILD_EXIT_CODE_DEADLOCK === $exitStatus) { + ++$deadlockHitCount; + } elseif (0 !== $exitStatus) { + $errors[] = 'child exited with error status'; + } + } + } + $logContent = $this->getLogContentAndDelete(); + if ($errors) { + $this->fail( + join('; ', $errors) + . ($logContent ? ". Shared children log:\n$logContent" : '') + ); + } + $this->assertEquals(1, $deadlockHitCount, "exactly one child must hit deadlock; shared children log:\n" . $logContent); + } + + /** + * Main body of first child process. + * First child initializes test row and runs two nested [[Connection::transaction()]] + * to perform following operations: + * 1. `SELECT ... LOCK IN SHARE MODE` the test row with shared lock instead of needed exclusive lock. + * 2. Send signal to SECOND child identified by PID [[$pidSecond]]. + * 3. Waits few seconds. + * 4. `UPDATE` the test row. + * @param int $pidSecond + * @return int Exit code. In case of deadlock exit code is [[CHILD_EXIT_CODE_DEADLOCK]]. + * In case of success exit code is 0. Other codes means an error. + */ + private function childrenSelectAndAccidentUpdate($pidSecond) + { + try { + $this->log("child 1: connect"); + /** @var Connection $first */ + $first = $this->getConnection(false, false); + + $this->log("child 1: delete"); + $first->createCommand() + ->delete('{{customer}}', ['id' => 97]) + ->execute(); + + $this->log("child 1: insert"); + // insert test row + $first->createCommand() + ->insert('{{customer}}', [ + 'id' => 97, + 'email' => 'deadlock@example.com', + 'name' => 'test', + 'address' => 'test address', + ]) + ->execute(); + + $this->log("child 1: transaction"); + $first->transaction(function (Connection $first) use ($pidSecond) { + $first->transaction(function (Connection $first) use ($pidSecond) { + $this->log("child 1: select"); + // SELECT with shared lock + $first->createCommand("SELECT id FROM {{customer}} WHERE id = 97 LOCK IN SHARE MODE") + ->execute(); + + $this->log("child 1: send signal to child 2"); + // let child to continue + if (!posix_kill($pidSecond, SIGUSR1)) { + throw new \RuntimeException('Cannot send signal'); + } + + // now child 2 tries to do the 2nd update, and hits the lock and waits + + // delay to let child hit the lock + sleep(2); + + $this->log("child 1: update"); + // now do the 3rd update for deadlock + $first->createCommand() + ->update('{{customer}}', ['name' => 'first'], ['id' => 97]) + ->execute(); + $this->log("child 1: commit"); + }); + }, Transaction::REPEATABLE_READ); + } catch (Exception $e) { + list ($sqlError, $driverError, $driverMessage) = $e->errorInfo; + // Deadlock found when trying to get lock; try restarting transaction + if ('40001' === $sqlError && 1213 === $driverError) { + return self::CHILD_EXIT_CODE_DEADLOCK; + } + $this->log("child 1: ! sql error $sqlError: $driverError: $driverMessage"); + return 1; + } catch (\Exception $e) { + $this->log("child 1: ! exit <<" . get_class($e) . " #" . $e->getCode() . ": " . $e->getMessage() . "\n" . $e->getTraceAsString() . ">>"); + return 1; + } catch (\Throwable $e) { + $this->log("child 1: ! exit <<" . get_class($e) . " #" . $e->getCode() . ": " . $e->getMessage() . "\n" . $e->getTraceAsString() . ">>"); + return 1; + } + $this->log("child 1: exit"); + return 0; + } + + /** + * Main body of second child process. + * Second child at first will wait the signal from the first child in some seconds. + * After receiving the signal it runs two nested [[Connection::transaction()]] + * to perform `UPDATE` with the test row. + * @return int Exit code. In case of deadlock exit code is [[CHILD_EXIT_CODE_DEADLOCK]]. + * In case of success exit code is 0. Other codes means an error. + */ + private function childrenUpdateLocked() + { + // install no-op signal handler to prevent termination + if (!pcntl_signal(SIGUSR1, function () {}, false)) { + $this->log("child 2: cannot install signal handler"); + return 1; + } + + try { + // at first, parent should do 1st select + $this->log("child 2: wait signal from child 1"); + if (pcntl_sigtimedwait([SIGUSR1], $info, 10) <= 0) { + $this->log("child 2: wait timeout exceeded"); + return 1; + } + + $this->log("child 2: connect"); + /** @var Connection $second */ + $second = $this->getConnection(true, false); + $second->open(); + //sleep(1); + $this->log("child 2: transaction"); + $second->transaction(function (Connection $second) { + $second->transaction(function (Connection $second) { + $this->log("child 2: update"); + // do the 2nd update + $second->createCommand() + ->update('{{customer}}', ['name' => 'second'], ['id' => 97]) + ->execute(); + + $this->log("child 2: commit"); + }); + }, Transaction::REPEATABLE_READ); + } catch (Exception $e) { + list ($sqlError, $driverError, $driverMessage) = $e->errorInfo; + // Deadlock found when trying to get lock; try restarting transaction + if ('40001' === $sqlError && 1213 === $driverError) { + return self::CHILD_EXIT_CODE_DEADLOCK; + } + $this->log("child 2: ! sql error $sqlError: $driverError: $driverMessage"); + return 1; + } catch (\Exception $e) { + $this->log("child 2: ! exit <<" . get_class($e) . " #" . $e->getCode() . ": " . $e->getMessage() . "\n" . $e->getTraceAsString() . ">>"); + return 1; + } catch (\Throwable $e) { + $this->log("child 2: ! exit <<" . get_class($e) . " #" . $e->getCode() . ": " . $e->getMessage() . "\n" . $e->getTraceAsString() . ">>"); + return 1; + } + $this->log("child 2: exit"); + return 0; + } + + /** + * Set own error handler. + * In case of error in child process its execution bubbles up to phpunit to continue + * all the rest tests. So, all the rest tests in this case will run both in the child + * and parent processes. Such mess must be prevented with child's own error handler. + */ + private function setErrorHandler() + { + if (version_compare(PHP_VERSION, '7', '<')) { + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, $errno, $errno, $errfile, $errline); + }); + } + } + + /** + * Sets filename for log file shared between children processes + * @param string $filename + */ + private function setLogFile($filename) + { + $this->logFile = $filename; + } + + /** + * Deletes shared log file. + * Deletes the file [[logFile]] if it exists. + */ + private function deleteLog() + { + if (null !== $this->logFile && is_file($this->logFile)) { + unlink($this->logFile); + } + } + + /** + * Reads shared log content and deletes the log file. + * Reads content of log file [[logFile]] and returns it deleting the file. + * @return string|null String content of the file [[logFile]]. `false` is returned + * when file cannot be read. `null` is returned when file does not exist + * or [[logFile]] is not set. + */ + private function getLogContentAndDelete() + { + if (null !== $this->logFile && is_file($this->logFile)) { + $content = file_get_contents($this->logFile); + unlink($this->logFile); + return $content; + } + return null; + } + + /** + * Append message to shared log. + * @param string $message Message to append to the log. The message will be prepended + * with timestamp and appended with new line. + */ + private function log($message) + { + if (null !== $this->logFile) { + $time = microtime(true); + $timeInt = floor($time); + $timeFrac = $time - $timeInt; + $timestamp = date('Y-m-d H:i:s', $timeInt) . '.' . round($timeFrac * 1000); + file_put_contents($this->logFile, "[$timestamp] $message\n", FILE_APPEND | LOCK_EX); + } + } +} From 33929704cfe5b22d8314e07e228670f17621731f Mon Sep 17 00:00:00 2001 From: jaaf Date: Wed, 8 Mar 2017 10:13:07 +0100 Subject: [PATCH 030/184] Adding 4 files helper-* FR [skip ci] (#13725) --- docs/guide-fr/helper-array.md | 404 +++++++++++++++++++++++++++++++++++++++ docs/guide-fr/helper-html.md | 396 ++++++++++++++++++++++++++++++++++++++ docs/guide-fr/helper-overview.md | 68 +++++++ docs/guide-fr/helper-url.md | 161 ++++++++++++++++ 4 files changed, 1029 insertions(+) create mode 100644 docs/guide-fr/helper-array.md create mode 100644 docs/guide-fr/helper-html.md create mode 100644 docs/guide-fr/helper-overview.md create mode 100644 docs/guide-fr/helper-url.md 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'); +``` From c5296242b147cf03bbcbd0fa377fcf5db31edd23 Mon Sep 17 00:00:00 2001 From: Bizley Date: Mon, 6 Mar 2017 19:28:23 +0100 Subject: [PATCH 031/184] Fixes #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` --- framework/CHANGELOG.md | 1 + framework/UPGRADE.md | 3 ++ framework/grid/DataColumn.php | 6 ++++ tests/framework/grid/DataColumnTest.php | 60 +++++++++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2faf8d7..6195de6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -44,6 +44,7 @@ Yii Framework 2 Change Log - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) +- Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) 2.0.11.2 February 08, 2017 diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 0862756..9f51979 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -56,6 +56,9 @@ Upgrade from Yii 2.0.11 * `yii\i18n\Formatter::normalizeDatetimeValue()` returns now array with additional third boolean element indicating whether the timestamp has date information or it is just time value. +* `yii\grid\DataColumn` filter is now automatically generated as dropdown list with localized `Yes` and `No` strings + in case of `format` being set to `boolean`. + Upgrade from Yii 2.0.10 ----------------------- diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index 246271d..f3749e2 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -189,6 +189,12 @@ class DataColumn extends Column if (is_array($this->filter)) { $options = array_merge(['prompt' => ''], $this->filterInputOptions); return Html::activeDropDownList($model, $this->attribute, $this->filter, $options) . $error; + } elseif ($this->format === 'boolean') { + $options = array_merge(['prompt' => ''], $this->filterInputOptions); + return Html::activeDropDownList($model, $this->attribute, [ + $this->grid->formatter->booleanFormat[0], + $this->grid->formatter->booleanFormat[1], + ], $options) . $error; } else { return Html::activeTextInput($model, $this->attribute, $this->filterInputOptions) . $error; } diff --git a/tests/framework/grid/DataColumnTest.php b/tests/framework/grid/DataColumnTest.php index b346dfc..454fe08 100644 --- a/tests/framework/grid/DataColumnTest.php +++ b/tests/framework/grid/DataColumnTest.php @@ -29,7 +29,7 @@ class DataColumnTest extends \yiiunit\TestCase 'totalCount' => 0, 'modelClass' => Order::className() ]), - 'columns' => ['customer_id', 'total'] + 'columns' => ['customer_id', 'total'], ]); $labels = []; foreach ($grid->columns as $column) { @@ -52,7 +52,7 @@ class DataColumnTest extends \yiiunit\TestCase 'totalCount' => 0, ]), 'columns' => ['customer_id', 'total'], - 'filterModel' => new Order + 'filterModel' => new Order, ]); $labels = []; foreach ($grid->columns as $column) { @@ -79,8 +79,8 @@ class DataColumnTest extends \yiiunit\TestCase 'columns' => [ 0 => [ 'attribute' => 'customer_id', - 'filter' => $filterInput - ] + 'filter' => $filterInput, + ], ], ]); @@ -123,10 +123,10 @@ class DataColumnTest extends \yiiunit\TestCase 'columns' => [ 0 => [ 'attribute' => 'customer_id', - 'filter' => $filterInput + 'filter' => $filterInput, ] ], - 'filterModel' => new Order + 'filterModel' => new Order, ]); $dataColumn = $grid->columns[0]; @@ -144,5 +144,53 @@ HTML , $result); } + /** + * @see DataColumn::$filter + * @see DataColumn::renderFilterCellContent() + */ + public function testFilterInput_FormatBoolean() + { + $this->mockApplication([ + 'components' => [ + 'db' => [ + 'class' => '\yii\db\Connection', + 'dsn' => 'sqlite::memory:', + ] + ] + ]); + $columns = [ + 'id' => 'pk', + 'customer_id' => 'integer', + ]; + ActiveRecord::$db = Yii::$app->getDb(); + Yii::$app->getDb()->createCommand()->createTable(Order::tableName(), $columns)->execute(); + + $grid = new GridView([ + 'dataProvider' => new ArrayDataProvider([ + 'allModels' => [], + 'totalCount' => 0, + ]), + 'columns' => [ + 0 => [ + 'attribute' => 'customer_id', + 'format' => 'boolean', // does not make sense for this column but should still output proper dropdown list + ], + ], + 'filterModel' => new Order, + ]); + + $dataColumn = $grid->columns[0]; + $method = new \ReflectionMethod($dataColumn, 'renderFilterCellContent'); + $method->setAccessible(true); + $result = $method->invoke($dataColumn); + $this->assertEqualsWithoutLE(<< + + + + +HTML + , $result); + } } From d37b82d505d604f8627f52ae22e6b40c3f10cb70 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 9 Mar 2017 02:15:40 +0300 Subject: [PATCH 032/184] Added tip about using PSR-3 loggers to docs [skip ci] --- docs/guide/runtime-logging.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guide/runtime-logging.md b/docs/guide/runtime-logging.md index ebfd053..ebbcce4 100644 --- a/docs/guide/runtime-logging.md +++ b/docs/guide/runtime-logging.md @@ -340,6 +340,9 @@ sending the content of the [[yii\log\Target::messages]] array to a designated me [[yii\log\Target::formatMessage()]] method to format each message. For more details, you may refer to any of the log target classes included in the Yii release. +> Tip: Instead of creating your own loggers you may try any PSR-3 compatible logger such + as [Monolog](https://github.com/Seldaek/monolog) by using + [PSR log target extension](https://github.com/samdark/yii2-psr-log-target). ## Performance Profiling From 9fa66b23a53e8268b1808977146056b92ac60a8a Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 9 Mar 2017 02:52:36 +0300 Subject: [PATCH 033/184] Remove an empty expression (#13731) --- framework/requirements/YiiRequirementChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/requirements/YiiRequirementChecker.php b/framework/requirements/YiiRequirementChecker.php index 4baa2b7..8df5a41 100644 --- a/framework/requirements/YiiRequirementChecker.php +++ b/framework/requirements/YiiRequirementChecker.php @@ -386,7 +386,7 @@ class YiiRequirementChecker */ function getServerInfo() { - return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';; + return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ''; } /** From 7d4b33a80584109197ff41f98b26012308709991 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 10 Mar 2017 02:30:16 +0100 Subject: [PATCH 034/184] changed method visibility this should not belong to exposed interface as it is only used internally by transaction() method. additional change to #13346 --- framework/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 0ed9610..877049c 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -730,7 +730,7 @@ class Connection extends Component * @param Transaction $transaction Transaction object given from [[beginTransaction()]]. * @param int $level Transaction level just after [[beginTransaction()]] call. */ - protected function rollbackTransactionOnLevel($transaction, $level) + private function rollbackTransactionOnLevel($transaction, $level) { if ($transaction->isActive && $transaction->level === $level) { // https://github.com/yiisoft/yii2/pull/13347 From ad1ce9e498aee97daf5ffafc2df95b77003d155f Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Fri, 10 Mar 2017 17:19:24 +0300 Subject: [PATCH 035/184] Improve sluggable behavior documentation (#13747) [skip ci] --- framework/behaviors/SluggableBehavior.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 22077b7..350be44 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -65,12 +65,14 @@ class SluggableBehavior extends AttributeBehavior */ public $slugAttribute = 'slug'; /** - * @var string|array the attribute or list of attributes whose value will be converted into a slug + * @var string|array|null the attribute or list of attributes whose value will be converted into a slug + * or `null` meaning that the `$value` property will be used to generate a slug. */ public $attribute; /** - * @var string|callable the value that will be used as a slug. This can be an anonymous function - * or an arbitrary value. If the former, the return value of the function will be used as a slug. + * @var callable|string|null the value that will be used as a slug. This can be an anonymous function + * or an arbitrary value or null. If the former, the return value of the function will be used as a slug. + * If `null` then the `$attribute` property will be used to generate a slug. * The signature of the function should be as follows, * * ```php From e12dd895e9a495faa552941cf8b10a3467ffc764 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 10 Mar 2017 16:01:24 +0100 Subject: [PATCH 036/184] improved docs for #13576 --- framework/helpers/BaseHtml.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 4702aff..4d41821 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -432,9 +432,10 @@ class BaseHtml * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * See [[renderTagAttributes()]] for details on how attributes are being rendered. - * @since 2.0.12 It is possible to pass the "srcset" option as an array which keys are + * + * Since version 2.0.12 It is possible to pass the `srcset` option as an array which keys are * descriptors and values are URLs. All URLs will be processed by [[Url::to()]]. - * @return string the generated image tag + * @return string the generated image tag. */ public static function img($src, $options = []) { From 0b36b27caf09caa6ff53de12cd79a08631dcf780 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 10 Mar 2017 21:16:12 +0300 Subject: [PATCH 037/184] Fixed version number in `@since` --- framework/helpers/BaseStringHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index ecf228e..e63a729 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -319,7 +319,7 @@ class BaseStringHelper * * @param string $input * @return string - * @since 2.0.11 + * @since 2.0.12 */ public static function base64UrlEncode($input) { @@ -332,7 +332,7 @@ class BaseStringHelper * * @param string $input * @return string - * @since 2.0.11 + * @since 2.0.12 */ public static function base64UrlDecode($input) { From 2c9278391cce8f0a52df512e9dbd9d54032a46d4 Mon Sep 17 00:00:00 2001 From: gagatust Date: Sat, 11 Mar 2017 07:44:05 +0900 Subject: [PATCH 038/184] Used short syntax for arrays (#13743) --- framework/messages/bg/yii.php | 4 ++-- framework/messages/da/yii.php | 4 ++-- framework/messages/et/yii.php | 4 ++-- framework/messages/hu/yii.php | 4 ++-- tests/framework/behaviors/SluggableBehaviorTest.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/messages/bg/yii.php b/framework/messages/bg/yii.php index be63e9c..748cc27 100644 --- a/framework/messages/bg/yii.php +++ b/framework/messages/bg/yii.php @@ -16,7 +16,7 @@ * * NOTE: this file must be saved in UTF-8 encoding. */ -return array ( +return [ '(not set)' => '(не е попълнено)', 'An internal server error occurred.' => 'Възникна вътрешна грешка в сървъра.', 'Are you sure you want to delete this item?' => 'Сигурни ли сте, че искате да изтриете записа?', @@ -103,4 +103,4 @@ return array ( '{n} MB' => '{n} МБ', '{n} PB' => '{n} ПБ', '{n} TB' => '{n} ТБ', -); +]; diff --git a/framework/messages/da/yii.php b/framework/messages/da/yii.php index fe3c8d6..ac2cc25 100644 --- a/framework/messages/da/yii.php +++ b/framework/messages/da/yii.php @@ -16,7 +16,7 @@ * * NOTE: this file must be saved in UTF-8 encoding. */ -return array ( +return [ '(not set)' => '(ikke defineret)', 'An internal server error occurred.' => 'Der opstod en intern server fejl.', 'Are you sure you want to delete this item?' => 'Er du sikker på, at du vil slette dette element?', @@ -103,4 +103,4 @@ return array ( '{n} MB' => '{n} MB', '{n} PB' => '{n} PB', '{n} TB' => '{n} TB', -); +]; diff --git a/framework/messages/et/yii.php b/framework/messages/et/yii.php index cf362f6..34ca300 100644 --- a/framework/messages/et/yii.php +++ b/framework/messages/et/yii.php @@ -16,7 +16,7 @@ * * NOTE: this file must be saved in UTF-8 encoding. */ -return array ( +return [ '(not set)' => '(määramata)', 'An internal server error occurred.' => 'Ilmnes serveri sisemine viga.', 'Are you sure you want to delete this item?' => 'Kas olete kindel, et soovite selle üksuse kustutada?', @@ -103,4 +103,4 @@ return array ( '{n} MB' => '{n} MB', '{n} PB' => '{n} PB', '{n} TB' => '{n} TB', -); +]; diff --git a/framework/messages/hu/yii.php b/framework/messages/hu/yii.php index 1877a4a..d2f6ebe 100644 --- a/framework/messages/hu/yii.php +++ b/framework/messages/hu/yii.php @@ -16,7 +16,7 @@ * * NOTE: this file must be saved in UTF-8 encoding. */ -return array ( +return [ '(not set)' => '(nincs beállítva)', 'An internal server error occurred.' => 'Egy belső szerver hiba történt.', 'Are you sure you want to delete this item?' => 'Biztos benne, hogy törli ezt az elemet?', @@ -104,4 +104,4 @@ return array ( '{n} MB' => '{n} MB', '{n} PB' => '{n} PB', '{n} TB' => '{n} TB', -); +]; diff --git a/tests/framework/behaviors/SluggableBehaviorTest.php b/tests/framework/behaviors/SluggableBehaviorTest.php index 8226171..484cae4 100644 --- a/tests/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/framework/behaviors/SluggableBehaviorTest.php @@ -78,7 +78,7 @@ class SluggableBehaviorTest extends TestCase public function testSlugSeveralAttributes() { $model = new ActiveRecordSluggable(); - $model->getBehavior('sluggable')->attribute = array('name', 'category_id'); + $model->getBehavior('sluggable')->attribute = ['name', 'category_id']; $model->name = 'test'; $model->category_id = 10; From a9fb0171f77fbb744293e84004db1b540413f721 Mon Sep 17 00:00:00 2001 From: Sergey Makinen Date: Sat, 11 Mar 2017 22:36:01 +0300 Subject: [PATCH 039/184] Fixes #13745: `SQLSTATE[HY093]: Invalid parameter number: parameter was not defined` in MSSQL and bug fixes in `yii\db\QueryBuilder::buildLikeCondition()` --- framework/db/cubrid/QueryBuilder.php | 10 +++- framework/db/mssql/QueryBuilder.php | 14 ++--- framework/db/oci/QueryBuilder.php | 27 +++++++++- tests/framework/db/QueryTest.php | 71 ++++++++++++++++++++++++++ tests/framework/db/cubrid/QueryBuilderTest.php | 8 ++- tests/framework/db/mssql/QueryBuilderTest.php | 7 ++- tests/framework/db/oci/QueryBuilderTest.php | 18 ++++++- 7 files changed, 140 insertions(+), 15 deletions(-) diff --git a/framework/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php index c83a279..ce3ffce 100644 --- a/framework/db/cubrid/QueryBuilder.php +++ b/framework/db/cubrid/QueryBuilder.php @@ -47,7 +47,15 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @inheritdoc */ - protected $likeEscapeCharacter = '\\'; + protected $likeEscapeCharacter = '!'; + /** + * @inheritdoc + */ + protected $likeEscapingReplacements = [ + '%' => '!%', + '_' => '!_', + '!' => '!!', + ]; /** * Creates a SQL statement for resetting the sequence value of a table's primary key. diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 98db757..30a7ac1 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -49,16 +49,12 @@ class QueryBuilder extends \yii\db\QueryBuilder * @inheritdoc */ protected $likeEscapingReplacements = [ - '%' => '\%', - '_' => '\_', - '[' => '\[', - ']' => '\]', - '\\' => '\\\\', + '%' => '[%]', + '_' => '[_]', + '[' => '[[]', + ']' => '[]]', + '\\' => '[\\]', ]; - /** - * @inheritdoc - */ - protected $likeEscapeCharacter = '\\'; /** * @inheritdoc diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index e6dfafd..ca9e45f 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -49,7 +49,17 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @inheritdoc */ - protected $likeEscapeCharacter = '\\'; + protected $likeEscapeCharacter = '!'; + /** + * `\` is initialized in [[buildLikeCondition()]] method since + * we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]]. + * @inheritdoc + */ + protected $likeEscapingReplacements = [ + '%' => '!%', + '_' => '!_', + '!' => '!!', + ]; /** * @inheritdoc @@ -306,4 +316,19 @@ EOD; { return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . " IS ''"; } + + /** + * @inheritDoc + */ + public function buildLikeCondition($operator, $operands, &$params) + { + if (!isset($this->likeEscapingReplacements['\\'])) { + /* + * Different pdo_oci8 versions may or may not implement PDO::quote(), so + * yii\db\Schema::quoteValue() may or may not quote \. + */ + $this->likeEscapingReplacements['\\'] = substr($this->db->quoteValue('\\'), 1, -1); + } + return parent::buildLikeCondition($operator, $operands, $params); + } } diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index d895fd1..1adf680 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -5,6 +5,7 @@ namespace yiiunit\framework\db; use yii\db\Connection; use yii\db\Expression; use yii\db\Query; +use yii\db\Schema; abstract class QueryTest extends DatabaseTestCase { @@ -458,4 +459,74 @@ abstract class QueryTest extends DatabaseTestCase ->column($db); $this->assertSame([], $column); } + + /** + * @param Connection $db + * @param string $tableName + * @param string $columnName + * @param array $condition + * @param string $operator + * @return int + */ + protected function countLikeQuery(Connection $db, $tableName, $columnName, array $condition, $operator = 'or') + { + $whereCondition = [$operator]; + foreach ($condition as $value) { + $whereCondition[] = ['like', $columnName, $value]; + } + $result = (new Query()) + ->from($tableName) + ->where($whereCondition) + ->count('*', $db); + if (is_numeric($result)) { + $result = (int) $result; + } + return $result; + } + + /** + * @see https://github.com/yiisoft/yii2/issues/13745 + */ + public function testMultipleLikeConditions() + { + $db = $this->getConnection(); + $tableName = 'like_test'; + $columnName = 'col'; + + if($db->getSchema()->getTableSchema($tableName) !== null){ + $db->createCommand()->dropTable($tableName)->execute(); + } + $db->createCommand()->createTable($tableName, [ + $columnName => $db->getSchema()->createColumnSchemaBuilder(Schema::TYPE_STRING, 64) + ])->execute(); + $db->createCommand()->batchInsert($tableName, ['col'], [ + ['test0'], + ['test\1'], + ['test\2'], + ['foo%'], + ['%bar'], + ['%baz%'], + ])->execute(); + + // Basic tests + $this->assertSame(1, $this->countLikeQuery($db, $tableName, $columnName, ['test0'])); + $this->assertSame(2, $this->countLikeQuery($db, $tableName, $columnName, ['test\\'])); + $this->assertSame(0, $this->countLikeQuery($db, $tableName, $columnName, ['test%'])); + $this->assertSame(3, $this->countLikeQuery($db, $tableName, $columnName, ['%'])); + + // Multiple condition tests + $this->assertSame(2, $this->countLikeQuery($db, $tableName, $columnName, [ + 'test0', + 'test\1', + ])); + $this->assertSame(3, $this->countLikeQuery($db, $tableName, $columnName, [ + 'test0', + 'test\1', + 'test\2', + ])); + $this->assertSame(3, $this->countLikeQuery($db, $tableName, $columnName, [ + 'foo', + '%ba', + ])); + } } diff --git a/tests/framework/db/cubrid/QueryBuilderTest.php b/tests/framework/db/cubrid/QueryBuilderTest.php index d42025f..40cda79 100644 --- a/tests/framework/db/cubrid/QueryBuilderTest.php +++ b/tests/framework/db/cubrid/QueryBuilderTest.php @@ -12,7 +12,13 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest { public $driverName = 'cubrid'; - protected $likeEscapeCharSql = " ESCAPE '\\'"; + protected $likeEscapeCharSql = " ESCAPE '!'"; + protected $likeParameterReplacements = [ + '\%' => '!%', + '\_' => '!_', + '\!' => '!!', + '\\\\' => '\\', + ]; /** * this is not used as a dataprovider for testGetColumnType to speed up the test diff --git a/tests/framework/db/mssql/QueryBuilderTest.php b/tests/framework/db/mssql/QueryBuilderTest.php index 32ac85d..e0df4b6 100644 --- a/tests/framework/db/mssql/QueryBuilderTest.php +++ b/tests/framework/db/mssql/QueryBuilderTest.php @@ -14,9 +14,12 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest { public $driverName = 'sqlsrv'; - protected $likeEscapeCharSql = " ESCAPE '\\'"; protected $likeParameterReplacements = [ - '[abc]' => '\[abc\]', + '\%' => '[%]', + '\_' => '[_]', + '[' => '[[]', + ']' => '[]]', + '\\\\' => '[\\]', ]; public function testOffsetLimit() diff --git a/tests/framework/db/oci/QueryBuilderTest.php b/tests/framework/db/oci/QueryBuilderTest.php index ab9f220..b012498 100644 --- a/tests/framework/db/oci/QueryBuilderTest.php +++ b/tests/framework/db/oci/QueryBuilderTest.php @@ -12,7 +12,12 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest { public $driverName = 'oci'; - protected $likeEscapeCharSql = " ESCAPE '\\'"; + protected $likeEscapeCharSql = " ESCAPE '!'"; + protected $likeParameterReplacements = [ + '\%' => '!%', + '\_' => '!_', + '!' => '!!', + ]; /** * this is not used as a dataprovider for testGetColumnType to speed up the test @@ -70,4 +75,15 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest $sql = $qb->resetSequence('item', 4); $this->assertEquals($expected, $sql); } + + public function likeConditionProvider() + { + /* + * Different pdo_oci8 versions may or may not implement PDO::quote(), so + * yii\db\Schema::quoteValue() may or may not quote \. + */ + $encodedBackslash = substr($this->getDb()->quoteValue('\\'), 1, -1); + $this->likeParameterReplacements[$encodedBackslash] = '\\'; + return parent::likeConditionProvider(); + } } From ee644ee997c3f9622904a7c472a1f031684021b4 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 13 Mar 2017 13:25:56 +0300 Subject: [PATCH 040/184] Added more composer autoload optimization strategies to performance tuning guide [skip ci] --- docs/guide/tutorial-performance-tuning.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guide/tutorial-performance-tuning.md b/docs/guide/tutorial-performance-tuning.md index e674ff7..460c3fc 100644 --- a/docs/guide/tutorial-performance-tuning.md +++ b/docs/guide/tutorial-performance-tuning.md @@ -187,6 +187,11 @@ by executing the following command: composer dumpautoload -o ``` +Additionally you may consider using +[authoritative class maps](https://getcomposer.org/doc/articles/autoloader-optimization.md#optimization-level-2-a-authoritative-class-maps) +and [APCu cache](https://getcomposer.org/doc/articles/autoloader-optimization.md#optimization-level-2-b-apcu-cache). +Note that both opmizations may or may not be suitable for your particular case. + ## Processing Data Offline From b32db9fb286d4de820a6abfbdf3420503a96ebad Mon Sep 17 00:00:00 2001 From: gagatust Date: Mon, 13 Mar 2017 19:49:45 +0800 Subject: [PATCH 041/184] Fixed path in unit tests README.md [skip ci] --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index d1cee04..ef3d0e8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -41,7 +41,7 @@ TEST CONFIGURATION PHPUnit configuration is in `phpunit.xml.dist` in repository root folder. You can create your own phpunit.xml to override dist config. -Database and other backend system configuration can be found in `unit/data/config.php` +Database and other backend system configuration can be found in `tests/data/config.php` adjust them to your needs to allow testing databases and caching in your environment. You can override configuration values by creating a `config.local.php` file and manipulate the `$config` variable. From 29b5f33722d8df55d4b56e5e1ed1fd9c1bbc5356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D0=B4=D0=B8=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20=D0=AE?= =?UTF-8?q?=D1=80=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Mar 2017 20:30:09 +0500 Subject: [PATCH 042/184] Adjusted conditions nesting in yii\base\Event --- framework/base/Event.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/framework/base/Event.php b/framework/base/Event.php index d5cca0c..99dfe49 100644 --- a/framework/base/Event.php +++ b/framework/base/Event.php @@ -211,13 +211,15 @@ class Event extends Object ); foreach ($classes as $class) { - if (!empty(self::$_events[$name][$class])) { - foreach (self::$_events[$name][$class] as $handler) { - $event->data = $handler[1]; - call_user_func($handler[0], $event); - if ($event->handled) { - return; - } + if (empty(self::$_events[$name][$class])) { + continue; + } + + foreach (self::$_events[$name][$class] as $handler) { + $event->data = $handler[1]; + call_user_func($handler[0], $event); + if ($event->handled) { + return; } } } From 6a7c01de7250656d2244f3f758c4e62c5ee0d2fd Mon Sep 17 00:00:00 2001 From: Skiptir Engu Date: Mon, 13 Mar 2017 16:12:32 -0300 Subject: [PATCH 043/184] Fixed broken link --- framework/db/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Command.php b/framework/db/Command.php index 14d8978..86538b4 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -66,7 +66,7 @@ class Command extends Component public $pdoStatement; /** * @var int the default fetch mode for this command. - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + * @see http://www.php.net/manual/en/pdostatement.setfetchmode.php */ public $fetchMode = \PDO::FETCH_ASSOC; /** From 6397791513de0eb45af38125cd1128a664186d63 Mon Sep 17 00:00:00 2001 From: gagatust Date: Tue, 14 Mar 2017 14:42:13 +0800 Subject: [PATCH 044/184] Fixed whitespaces to match code style [skip ci] --- build/controllers/DevController.php | 12 +++--- build/controllers/MimeTypeController.php | 4 +- build/controllers/PhpDocController.php | 30 +++++++------- build/controllers/ReleaseController.php | 46 +++++++++++----------- build/controllers/TranslationController.php | 4 +- build/controllers/Utf8Controller.php | 4 +- .../controllers/views/translation/report_html.php | 4 +- framework/db/ActiveRecord.php | 4 +- framework/di/Instance.php | 2 +- framework/validators/UniqueValidator.php | 4 +- tests/framework/base/ModelTest.php | 2 +- .../console/controllers/CacheControllerTest.php | 2 +- tests/framework/db/ActiveRecordTest.php | 6 +-- tests/framework/db/CommandTest.php | 10 ++--- tests/framework/db/QueryBuilderTest.php | 8 ++-- tests/framework/db/SchemaTest.php | 8 ++-- tests/framework/di/ContainerTest.php | 4 +- tests/framework/grid/ActionColumnTest.php | 4 +- tests/framework/i18n/FormatterDateTest.php | 2 +- tests/framework/log/FileTargetTest.php | 4 +- tests/framework/rest/UrlRuleTest.php | 2 +- tests/framework/validators/DateValidatorTest.php | 2 +- tests/framework/validators/EmailValidatorTest.php | 2 +- tests/framework/validators/FileValidatorTest.php | 2 +- tests/framework/validators/UniqueValidatorTest.php | 2 +- tests/framework/web/DbSessionTest.php | 2 +- tests/framework/web/RequestTest.php | 12 +++--- tests/framework/web/UserTest.php | 4 +- tests/framework/widgets/ActiveFieldTest.php | 2 +- 29 files changed, 97 insertions(+), 97 deletions(-) diff --git a/build/controllers/DevController.php b/build/controllers/DevController.php index d4d0e76..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); @@ -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 f8cee3f..bb01fa5 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -167,8 +167,8 @@ 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"; } } @@ -184,7 +184,7 @@ class PhpDocController extends Controller } if (isset($extensionExcept[$extension])) { - foreach($extensionExcept[$extension] as $path) { + foreach ($extensionExcept[$extension] as $path) { $except[] = $path; } } @@ -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,8 +348,8 @@ 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){ + foreach ($types as $i => $type) { + switch ($type) { case 'integer': $types[$i] = 'int'; break; case 'boolean': $types[$i] = 'bool'; break; } @@ -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 || @@ -753,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 dd3987c..06674a3 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -105,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}"); @@ -126,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"); } @@ -136,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; } @@ -199,7 +199,7 @@ class ReleaseController extends Controller if ($this->version !== null) { // if a version is explicitly given $newVersions = []; - foreach($versions as $k => $v) { + foreach ($versions as $k => $v) { $newVersions[$k] = $this->version; } } else { @@ -230,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) { @@ -264,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); @@ -289,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) { @@ -334,7 +334,7 @@ class ReleaseController extends Controller 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); @@ -353,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"); @@ -372,7 +372,7 @@ class ReleaseController extends Controller */ 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"); @@ -460,7 +460,7 @@ 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); @@ -581,7 +581,7 @@ 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); @@ -633,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; @@ -699,7 +699,7 @@ 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); @@ -825,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), @@ -839,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); @@ -860,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) { @@ -886,7 +886,7 @@ 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); @@ -926,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; } @@ -979,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))); } } @@ -987,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) { @@ -1011,13 +1011,13 @@ 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; 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 index 3465843..4cf3cbd 100644 --- a/build/controllers/Utf8Controller.php +++ b/build/controllers/Utf8Controller.php @@ -39,14 +39,14 @@ class Utf8Controller extends Controller ]); } - foreach($files as $file) { + 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) { + foreach ($chars as $c) { $ord = $this->unicodeOrd($c); 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/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 3c1942a..212277d 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -205,7 +205,7 @@ class ActiveRecord extends BaseActiveRecord * * ```php * $models = Customer::find()->where('status = 2')->all(); - * foreach($models as $model) { + * foreach ($models as $model) { * $model->status = 1; * $model->update(false); // skipping validation as no user input is involved * } @@ -276,7 +276,7 @@ class ActiveRecord extends BaseActiveRecord * * ```php * $models = Customer::find()->where('status = 3')->all(); - * foreach($models as $model) { + * foreach ($models as $model) { * $model->delete(); * } * ``` diff --git a/framework/di/Instance.php b/framework/di/Instance.php index 6a5e567..b4d6dea 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -130,7 +130,7 @@ class Instance if ($reference instanceof self) { try { $component = $reference->get($container); - } catch(\ReflectionException $e) { + } catch (\ReflectionException $e) { throw new InvalidConfigException('Failed to instantiate component or class "' . $reference->id . '".', 0, $e); } if ($type === null || $component instanceof $type) { diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 2b15821..0a7c868 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -173,7 +173,7 @@ class UniqueValidator extends Validator $dbModel = reset($models); $pks = $targetClass::primaryKey(); $pk = []; - foreach($pks as $pkAttribute) { + foreach ($pks as $pkAttribute) { $pk[$pkAttribute] = $dbModel[$pkAttribute]; } $exists = ($pk != $model->getOldPrimaryKey(true)); @@ -247,7 +247,7 @@ class UniqueValidator extends Validator $attributeCombo = []; $valueCombo = []; foreach ($this->targetAttribute as $key => $value) { - if(is_int($key)) { + if (is_int($key)) { $attributeCombo[] = $model->getAttributeLabel($value); $valueCombo[] = '"' . $model->$value . '"'; } else { diff --git a/tests/framework/base/ModelTest.php b/tests/framework/base/ModelTest.php index 726bfc2..351e07d 100644 --- a/tests/framework/base/ModelTest.php +++ b/tests/framework/base/ModelTest.php @@ -480,7 +480,7 @@ class WriteOnlyModel extends Model public function rules() { return [ - [['password'],'safe'], + [['password'], 'safe'], ]; } diff --git a/tests/framework/console/controllers/CacheControllerTest.php b/tests/framework/console/controllers/CacheControllerTest.php index ccdf935..44dd310 100644 --- a/tests/framework/console/controllers/CacheControllerTest.php +++ b/tests/framework/console/controllers/CacheControllerTest.php @@ -58,7 +58,7 @@ class CacheControllerTest extends TestCase ], ]); - if(isset($config['fixture'])) { + if (isset($config['fixture'])) { Yii::$app->db->open(); $lines = explode(';', file_get_contents($config['fixture'])); foreach ($lines as $line) { diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index ec77411..89b0b30 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -1053,7 +1053,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $this->assertEquals(2, count($order->booksWithNullFKViaTable)); $orderItemCount = $orderItemsWithNullFKClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); - $order->unlinkAll('booksWithNullFKViaTable',false); + $order->unlinkAll('booksWithNullFKViaTable', false); $this->assertEquals(0, count($order->booksWithNullFKViaTable)); $this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count()); @@ -1186,7 +1186,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->all(); $this->assertCount(2, $aggregation); $this->assertContainsOnlyInstancesOf(Customer::className(), $aggregation); - foreach($aggregation as $item) { + foreach ($aggregation as $item) { if ($item->status == 1) { $this->assertEquals(183, $item->sumTotal); } elseif ($item->status == 2) { @@ -1227,7 +1227,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->all(); $this->assertCount(3, $aggregation); $this->assertContainsOnlyInstancesOf(OrderItem::className(), $aggregation); - foreach($aggregation as $item) { + foreach ($aggregation as $item) { if ($item->order_id == 1) { $this->assertEquals(70, $item->subtotal); } elseif ($item->order_id == 2) { diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index 5ce2ae2..fb107bd 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -463,7 +463,7 @@ SQL; $db = $this->getConnection(); $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute(); - switch($this->driverName){ + switch ($this->driverName) { case 'pgsql': $expression = "EXTRACT(YEAR FROM TIMESTAMP 'now')"; break; @@ -531,7 +531,7 @@ SQL; { $db = $this->getConnection(); - if($db->getSchema()->getTableSchema('testCreateTable') !== null){ + if ($db->getSchema()->getTableSchema('testCreateTable') !== null) { $db->createCommand()->dropTable('testCreateTable')->execute(); } @@ -545,13 +545,13 @@ SQL; public function testAlterTable() { - if ($this->driverName === 'sqlite'){ + if ($this->driverName === 'sqlite') { $this->markTestSkipped('Sqlite does not support alterTable'); } $db = $this->getConnection(); - if($db->getSchema()->getTableSchema('testAlterTable') !== null){ + if ($db->getSchema()->getTableSchema('testAlterTable') !== null) { $db->createCommand()->dropTable('testAlterTable')->execute(); } @@ -596,7 +596,7 @@ SQL; $fromTableName = 'type'; $toTableName = 'new_type'; - if($db->getSchema()->getTableSchema($toTableName) !== null){ + if ($db->getSchema()->getTableSchema($toTableName) !== null) { $db->createCommand()->dropTable($toTableName)->execute(); } diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 1defde6..96308dc 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -1145,7 +1145,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase } // adjust dbms specific escaping - foreach($conditions as $i => $condition) { + foreach ($conditions as $i => $condition) { $conditions[$i][1] = $this->replaceQuotes($condition[1]); } return $conditions; @@ -1193,7 +1193,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase ]; // adjust dbms specific escaping - foreach($conditions as $i => $condition) { + foreach ($conditions as $i => $condition) { $conditions[$i][1] = $this->replaceQuotes($condition[1]); } return $conditions; @@ -1464,7 +1464,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase $this->assertEquals([ 'id' => 1, 'abc' => 'abc', - ],$params); + ], $params); // simple subquery $subquery = "(SELECT * FROM user WHERE account_id = accounts.id)"; @@ -1697,7 +1697,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase ]; // adjust dbms specific escaping - foreach($conditions as $i => $condition) { + foreach ($conditions as $i => $condition) { $conditions[$i][1] = $this->replaceQuotes($condition[1]); if (!empty($this->likeEscapeCharSql)) { preg_match_all('/(?PLIKE.+?)( AND| OR|$)/', $conditions[$i][1], $matches, PREG_SET_ORDER); diff --git a/tests/framework/db/SchemaTest.php b/tests/framework/db/SchemaTest.php index 959f9b6..4ee6cef 100644 --- a/tests/framework/db/SchemaTest.php +++ b/tests/framework/db/SchemaTest.php @@ -24,7 +24,7 @@ abstract class SchemaTest extends DatabaseTestCase public function testGetTableNames($pdoAttributes) { $connection = $this->getConnection(); - foreach($pdoAttributes as $name => $value) { + foreach ($pdoAttributes as $name => $value) { $connection->pdo->setAttribute($name, $value); } /* @var $schema Schema */ @@ -47,7 +47,7 @@ abstract class SchemaTest extends DatabaseTestCase public function testGetTableSchemas($pdoAttributes) { $connection = $this->getConnection(); - foreach($pdoAttributes as $name => $value) { + foreach ($pdoAttributes as $name => $value) { $connection->pdo->setAttribute($name, $value); } /* @var $schema Schema */ @@ -364,7 +364,7 @@ abstract class SchemaTest extends DatabaseTestCase sort($colNames); $this->assertEquals($expectedColNames, $colNames); - foreach($table->columns as $name => $column) { + foreach ($table->columns as $name => $column) { $expected = $columns[$name]; $this->assertSame($expected['dbType'], $column->dbType, "dbType of column $name does not match. type is $column->type, dbType is $column->dbType."); $this->assertSame($expected['phpType'], $column->phpType, "phpType of column $name does not match. type is $column->type, dbType is $column->dbType."); @@ -396,7 +396,7 @@ abstract class SchemaTest extends DatabaseTestCase try { $db->createCommand()->dropTable('uniqueIndex')->execute(); - } catch(\Exception $e) { + } catch (\Exception $e) { } $db->createCommand()->createTable('uniqueIndex', [ 'somecol' => 'string', diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php index fe632a8..920103c 100644 --- a/tests/framework/di/ContainerTest.php +++ b/tests/framework/di/ContainerTest.php @@ -159,11 +159,11 @@ class ContainerTest extends TestCase $this->assertEquals(['ok', 'yii\validators\NumberValidator', 'value_of_c'], $result); // use native php function - $this->assertEquals(Yii::$container->invoke('trim',[' M2792684 ']), 'M2792684'); + $this->assertEquals(Yii::$container->invoke('trim', [' M2792684 ']), 'M2792684'); // use helper function $array = ['M36', 'D426', 'Y2684']; - $this->assertFalse(Yii::$container->invoke(['yii\helpers\ArrayHelper', 'isAssociative'],[$array])); + $this->assertFalse(Yii::$container->invoke(['yii\helpers\ArrayHelper', 'isAssociative'], [$array])); $myFunc = function (\yii\console\Request $request, \yii\console\Response $response) { diff --git a/tests/framework/grid/ActionColumnTest.php b/tests/framework/grid/ActionColumnTest.php index a40812d..ce551b2 100644 --- a/tests/framework/grid/ActionColumnTest.php +++ b/tests/framework/grid/ActionColumnTest.php @@ -69,7 +69,7 @@ class ActionColumnTest extends \yiiunit\TestCase //test visible button (condition is callback) $column->visibleButtons = [ - 'update' => function($model, $key, $index){return $model['id'] == 1;} + 'update' => function($model, $key, $index) {return $model['id'] == 1;} ]; $columnContents = $column->renderDataCell(['id' => 1], 1, 0); $this->assertContains('update_button', $columnContents); @@ -83,7 +83,7 @@ class ActionColumnTest extends \yiiunit\TestCase //test invisible button (condition is callback) $column->visibleButtons = [ - 'update' => function($model, $key, $index){return $model['id'] != 1;} + 'update' => function($model, $key, $index) {return $model['id'] != 1;} ]; $columnContents = $column->renderDataCell(['id' => 1], 1, 0); $this->assertNotContains('update_button', $columnContents); diff --git a/tests/framework/i18n/FormatterDateTest.php b/tests/framework/i18n/FormatterDateTest.php index f4ee601..354f854 100644 --- a/tests/framework/i18n/FormatterDateTest.php +++ b/tests/framework/i18n/FormatterDateTest.php @@ -548,7 +548,7 @@ class FormatterDateTest extends TestCase $utc = new \DateTimeZone('UTC'); $berlin = new \DateTimeZone('Europe/Berlin'); $result = []; - foreach($this->provideTimezones() as $tz) { + foreach ($this->provideTimezones() as $tz) { $result[] = [$tz[0], 1407674460, 1388580060]; $result[] = [$tz[0], '2014-08-10 12:41:00', '2014-01-01 12:41:00']; $result[] = [$tz[0], '2014-08-10 12:41:00 UTC', '2014-01-01 12:41:00 UTC']; diff --git a/tests/framework/log/FileTargetTest.php b/tests/framework/log/FileTargetTest.php index 122fdee..ed7ba16 100644 --- a/tests/framework/log/FileTargetTest.php +++ b/tests/framework/log/FileTargetTest.php @@ -69,7 +69,7 @@ class FileTargetTest extends TestCase $this->assertFalse(file_exists($logFile . '.4')); // exceed max size - for($i = 0; $i < 1024; $i++) { + for ($i = 0; $i < 1024; $i++) { $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); } $logger->flush(true); @@ -89,7 +89,7 @@ class FileTargetTest extends TestCase // second rotate - for($i = 0; $i < 1024; $i++) { + for ($i = 0; $i < 1024; $i++) { $logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING); } $logger->flush(true); diff --git a/tests/framework/rest/UrlRuleTest.php b/tests/framework/rest/UrlRuleTest.php index 4129235..bf28803 100644 --- a/tests/framework/rest/UrlRuleTest.php +++ b/tests/framework/rest/UrlRuleTest.php @@ -340,7 +340,7 @@ class UrlRuleTest extends TestCase */ public function testCreateUrl($rule, $tests) { - foreach($tests as $test) { + foreach ($tests as $test) { list($params, $expected) = $test; $this->mockWebApplication(); diff --git a/tests/framework/validators/DateValidatorTest.php b/tests/framework/validators/DateValidatorTest.php index 3516e64..2fe6363 100644 --- a/tests/framework/validators/DateValidatorTest.php +++ b/tests/framework/validators/DateValidatorTest.php @@ -235,7 +235,7 @@ class DateValidatorTest extends TestCase public function timestampFormatProvider() { $return = []; - foreach($this->provideTimezones() as $appTz) { + foreach ($this->provideTimezones() as $appTz) { foreach ($this->provideTimezones() as $tz) { $return[] = ['yyyy-MM-dd', '2013-09-13', '2013-09-13', $tz[0], $appTz[0]]; // regardless of timezone, a simple date input should always result in 00:00:00 time diff --git a/tests/framework/validators/EmailValidatorTest.php b/tests/framework/validators/EmailValidatorTest.php index 1b49ed8..6e59dee 100644 --- a/tests/framework/validators/EmailValidatorTest.php +++ b/tests/framework/validators/EmailValidatorTest.php @@ -114,7 +114,7 @@ class EmailValidatorTest extends TestCase 'ipetrov@gmail.com', 'Ivan Petrov ', ]; - foreach($emails as $email) { + foreach ($emails as $email) { $this->assertTrue($validator->validate($email),"Email: '$email' failed to validate(checkDNS=true, allowName=true)"); } } diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php index aff946e..f6a406b 100644 --- a/tests/framework/validators/FileValidatorTest.php +++ b/tests/framework/validators/FileValidatorTest.php @@ -71,7 +71,7 @@ class FileValidatorTest extends TestCase public function testGetSizeLimit() { - $size = min($this->sizeToBytes(ini_get('upload_max_filesize')),$this->sizeToBytes(ini_get('post_max_size'))); + $size = min($this->sizeToBytes(ini_get('upload_max_filesize')), $this->sizeToBytes(ini_get('post_max_size'))); $val = new FileValidator(); $this->assertEquals($size, $val->getSizeLimit()); $val->maxSize = $size + 1; // set and test if value is overridden diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index bf8c4e9..6d25d60 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -338,7 +338,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase $schema = $this->getConnection()->schema; $model = new ValidatorTestMainModel(); - $query = $this->invokeMethod(new UniqueValidator(), 'prepareQuery', [$model,['val_attr_b' => 'test value a']]); + $query = $this->invokeMethod(new UniqueValidator(), 'prepareQuery', [$model, ['val_attr_b' => 'test value a']]); $expected = "SELECT * FROM {$schema->quoteTableName('validator_main')} WHERE {$schema->quoteColumnName('val_attr_b')}=:qp0"; $this->assertEquals($expected, $query->createCommand()->getSql()); diff --git a/tests/framework/web/DbSessionTest.php b/tests/framework/web/DbSessionTest.php index 2ab01c9..eefc0ad 100644 --- a/tests/framework/web/DbSessionTest.php +++ b/tests/framework/web/DbSessionTest.php @@ -113,7 +113,7 @@ class DbSessionTest extends TestCase $migrate->run($action, $params); ob_get_clean(); - return array_map(function($version){ + return array_map(function($version) { return substr($version, 15); }, (new Query())->select(['version'])->from('migration')->column()); } diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index b9d5c49..d671f50 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -101,7 +101,7 @@ class RequestTest extends TestCase $request->enableCsrfValidation = true; // accept any value on GET request - foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertTrue($request->validateCsrfToken($token . 'a')); @@ -112,7 +112,7 @@ class RequestTest extends TestCase } // only accept valid token on POST - foreach(['POST', 'PUT', 'DELETE'] as $method) { + foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertFalse($request->validateCsrfToken($token . 'a')); @@ -136,13 +136,13 @@ class RequestTest extends TestCase $token = $request->getCsrfToken(); // accept no value on GET request - foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken()); } // only accept valid token on POST - foreach(['POST', 'PUT', 'DELETE'] as $method) { + foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $request->setBodyParams([]); $this->assertFalse($request->validateCsrfToken()); @@ -165,13 +165,13 @@ class RequestTest extends TestCase $token = $request->getCsrfToken(); // accept no value on GET request - foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken()); } // only accept valid token on POST - foreach(['POST', 'PUT', 'DELETE'] as $method) { + foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $request->setBodyParams([]); //$request->headers->remove(Request::CSRF_HEADER); diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 8b2838c..e82693b 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -151,8 +151,8 @@ class UserTest extends TestCase } $_SERVER = $server; - Yii::$app->set('response',['class' => 'yii\web\Response']); - Yii::$app->set('request',[ + Yii::$app->set('response', ['class' => 'yii\web\Response']); + Yii::$app->set('request', [ 'class' => 'yii\web\Request', 'scriptFile' => __DIR__ .'/index.php', 'scriptUrl' => '/index.php', diff --git a/tests/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php index c55ff7a..35256a1 100644 --- a/tests/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php @@ -395,7 +395,7 @@ EOD; $this->activeField->enableAjaxValidation = true; $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); - foreach($this->activeField->model->validators as $validator) { + foreach ($this->activeField->model->validators as $validator) { $validator->whenClient = "function (attribute, value) { return 'yii2' == 'yii2'; }"; // js } From 3f8e8a89ebff17c9da4d0b0b36bf9ae355593b91 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 14 Mar 2017 09:45:31 +0300 Subject: [PATCH 045/184] Used more specific unit test assertions in framework tests --- tests/framework/BaseYiiTest.php | 2 +- tests/framework/ar/ActiveRecordTestTrait.php | 229 +++++++++--------- tests/framework/base/ComponentTest.php | 10 +- tests/framework/base/ObjectTest.php | 12 +- tests/framework/base/SecurityTest.php | 16 +- .../behaviors/AttributeTypecastBehaviorTest.php | 4 +- .../console/controllers/AssetControllerTest.php | 10 +- .../controllers/BaseMessageControllerTest.php | 14 +- tests/framework/data/ActiveDataProviderTest.php | 50 ++-- tests/framework/data/SortTest.php | 8 +- tests/framework/db/ActiveRecordTest.php | 258 +++++++++++---------- tests/framework/db/BatchQueryResultTest.php | 26 +-- tests/framework/db/CommandTest.php | 8 +- tests/framework/db/ConnectionTest.php | 2 +- tests/framework/db/QueryBuilderTest.php | 8 +- tests/framework/db/QueryTest.php | 12 +- tests/framework/db/SchemaTest.php | 2 +- tests/framework/db/oci/ActiveRecordTest.php | 2 +- tests/framework/db/oci/SchemaTest.php | 2 +- tests/framework/db/pgsql/ActiveRecordTest.php | 24 +- tests/framework/db/pgsql/SchemaTest.php | 6 +- tests/framework/db/sqlite/ConnectionTest.php | 16 +- tests/framework/db/sqlite/QueryTest.php | 2 +- tests/framework/di/ContainerTest.php | 34 +-- tests/framework/di/InstanceTest.php | 29 ++- tests/framework/di/ServiceLocatorTest.php | 16 +- tests/framework/filters/HttpCacheTest.php | 2 +- tests/framework/helpers/FileHelperTest.php | 8 +- tests/framework/helpers/HtmlTest.php | 2 +- tests/framework/log/FileTargetTest.php | 30 +-- tests/framework/log/LoggerTest.php | 8 +- tests/framework/mail/BaseMailerTest.php | 2 +- tests/framework/rbac/ManagerTestCase.php | 26 +-- .../requirements/YiiRequirementCheckerTest.php | 4 +- tests/framework/test/ActiveFixtureTest.php | 2 +- tests/framework/validators/EachValidatorTest.php | 2 +- tests/framework/validators/FileValidatorTest.php | 14 +- tests/framework/validators/NumberValidatorTest.php | 2 +- tests/framework/validators/RangeValidatorTest.php | 2 +- .../framework/validators/RequiredValidatorTest.php | 4 +- tests/framework/validators/UrlValidatorTest.php | 2 +- tests/framework/web/AssetBundleTest.php | 24 +- tests/framework/web/AssetConverterTest.php | 6 +- .../framework/web/MultipartFormDataParserTest.php | 10 +- tests/framework/web/RequestTest.php | 4 +- tests/framework/web/UploadedFileTest.php | 8 +- tests/framework/web/UserTest.php | 6 +- tests/framework/widgets/ActiveFieldTest.php | 24 +- tests/framework/widgets/LinkSorterTest.php | 12 +- 49 files changed, 519 insertions(+), 487 deletions(-) diff --git a/tests/framework/BaseYiiTest.php b/tests/framework/BaseYiiTest.php index 9f61c1d..d045dff 100644 --- a/tests/framework/BaseYiiTest.php +++ b/tests/framework/BaseYiiTest.php @@ -100,7 +100,7 @@ class BaseYiiTest extends TestCase BaseYii::setLogger(null); $defaultLogger = BaseYii::getLogger(); - $this->assertTrue($defaultLogger instanceof Logger); + $this->assertInstanceOf(Logger::className(), $defaultLogger); } /** diff --git a/tests/framework/ar/ActiveRecordTestTrait.php b/tests/framework/ar/ActiveRecordTestTrait.php index 898a560..0eed00d 100644 --- a/tests/framework/ar/ActiveRecordTestTrait.php +++ b/tests/framework/ar/ActiveRecordTestTrait.php @@ -67,31 +67,32 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // find one $result = $customerClass::find(); - $this->assertTrue($result instanceof ActiveQueryInterface); + $this->assertInstanceOf('\\yii\\db\\ActiveQueryInterface', $result); $customer = $result->one(); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); // find all $customers = $customerClass::find()->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers[0] instanceof $customerClass); - $this->assertTrue($customers[1] instanceof $customerClass); - $this->assertTrue($customers[2] instanceof $customerClass); + $this->assertCount(3, $customers); + $this->assertInstanceOf($customerClass, $customers[0]); + $this->assertInstanceOf($customerClass, $customers[1]); + $this->assertInstanceOf($customerClass, $customers[2]); // find by a single primary key $customer = $customerClass::findOne(2); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals('user2', $customer->name); $customer = $customerClass::findOne(5); $this->assertNull($customer); $customer = $customerClass::findOne(['id' => [5, 6, 1]]); + // can't use assertCount() here since it will count model attributes instead $this->assertEquals(1, count($customer)); $customer = $customerClass::find()->where(['id' => [5, 6, 1]])->one(); $this->assertNotNull($customer); // find by column values $customer = $customerClass::findOne(['id' => 2, 'name' => 'user2']); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals('user2', $customer->name); $customer = $customerClass::findOne(['id' => 2, 'name' => 'user1']); $this->assertNull($customer); @@ -102,11 +103,11 @@ trait ActiveRecordTestTrait // find by attributes $customer = $customerClass::find()->where(['name' => 'user2'])->one(); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals(2, $customer->id); // scope - $this->assertEquals(2, count($customerClass::find()->active()->all())); + $this->assertCount(2, $customerClass::find()->active()->all()); $this->assertEquals(2, $customerClass::find()->active()->count()); } @@ -128,7 +129,7 @@ trait ActiveRecordTestTrait // find all asArray $customers = $customerClass::find()->asArray()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers[0]); $this->assertArrayHasKey('name', $customers[0]); $this->assertArrayHasKey('email', $customers[0]); @@ -200,19 +201,19 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy $customers = $customerClass::find()->indexBy('name')->orderBy('id')->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['user1'] instanceof $customerClass); - $this->assertTrue($customers['user2'] instanceof $customerClass); - $this->assertTrue($customers['user3'] instanceof $customerClass); + $this->assertCount(3, $customers); + $this->assertInstanceOf($customerClass, $customers['user1']); + $this->assertInstanceOf($customerClass, $customers['user2']); + $this->assertInstanceOf($customerClass, $customers['user3']); // indexBy callable $customers = $customerClass::find()->indexBy(function ($customer) { return $customer->id . '-' . $customer->name; })->orderBy('id')->all(); - $this->assertEquals(3, count($customers)); - $this->assertTrue($customers['1-user1'] instanceof $customerClass); - $this->assertTrue($customers['2-user2'] instanceof $customerClass); - $this->assertTrue($customers['3-user3'] instanceof $customerClass); + $this->assertCount(3, $customers); + $this->assertInstanceOf($customerClass, $customers['1-user1']); + $this->assertInstanceOf($customerClass, $customers['2-user2']); + $this->assertInstanceOf($customerClass, $customers['3-user3']); } public function testFindIndexByAsArray() @@ -223,7 +224,7 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy + asArray $customers = $customerClass::find()->asArray()->indexBy('name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['user1']); $this->assertArrayHasKey('name', $customers['user1']); $this->assertArrayHasKey('email', $customers['user1']); @@ -244,7 +245,7 @@ trait ActiveRecordTestTrait $customers = $customerClass::find()->indexBy(function ($customer) { return $customer['id'] . '-' . $customer['name']; })->asArray()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['1-user1']); $this->assertArrayHasKey('name', $customers['1-user1']); $this->assertArrayHasKey('email', $customers['1-user1']); @@ -332,27 +333,27 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // all() $customers = $customerClass::find()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $customers = $customerClass::find()->orderBy('id')->limit(1)->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $this->assertEquals('user1', $customers[0]->name); $customers = $customerClass::find()->orderBy('id')->limit(1)->offset(1)->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $this->assertEquals('user2', $customers[0]->name); $customers = $customerClass::find()->orderBy('id')->limit(1)->offset(2)->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $this->assertEquals('user3', $customers[0]->name); $customers = $customerClass::find()->orderBy('id')->limit(2)->offset(1)->all(); - $this->assertEquals(2, count($customers)); + $this->assertCount(2, $customers); $this->assertEquals('user2', $customers[0]->name); $this->assertEquals('user3', $customers[1]->name); $customers = $customerClass::find()->limit(2)->offset(3)->all(); - $this->assertEquals(0, count($customers)); + $this->assertCount(0, $customers); // one() $customer = $customerClass::find()->orderBy('id')->one(); @@ -379,13 +380,13 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ $this->assertEquals(2, $customerClass::find()->where(['OR', ['name' => 'user1'], ['name' => 'user2']])->count()); - $this->assertEquals(2, count($customerClass::find()->where(['OR', ['name' => 'user1'], ['name' => 'user2']])->all())); + $this->assertCount(2, $customerClass::find()->where(['OR', ['name' => 'user1'], ['name' => 'user2']])->all()); $this->assertEquals(2, $customerClass::find()->where(['name' => ['user1', 'user2']])->count()); - $this->assertEquals(2, count($customerClass::find()->where(['name' => ['user1', 'user2']])->all())); + $this->assertCount(2, $customerClass::find()->where(['name' => ['user1', 'user2']])->all()); $this->assertEquals(1, $customerClass::find()->where(['AND', ['name' => ['user2', 'user3']], ['BETWEEN', 'status', 2, 4]])->count()); - $this->assertEquals(1, count($customerClass::find()->where(['AND', ['name' => ['user2', 'user3']], ['BETWEEN', 'status', 2, 4]])->all())); + $this->assertCount(1, $customerClass::find()->where(['AND', ['name' => ['user2', 'user3']], ['BETWEEN', 'status', 2, 4]])->all()); } public function testFindNullValues() @@ -400,7 +401,7 @@ trait ActiveRecordTestTrait $this->afterSave(); $result = $customerClass::find()->where(['name' => null])->all(); - $this->assertEquals(1, count($result)); + $this->assertCount(1, $result); $this->assertEquals(2, reset($result)->primaryKey); } @@ -430,8 +431,8 @@ trait ActiveRecordTestTrait $this->assertFalse($customer->isRelationPopulated('orders')); $orders = $customer->orders; $this->assertTrue($customer->isRelationPopulated('orders')); - $this->assertEquals(2, count($orders)); - $this->assertEquals(1, count($customer->relatedRecords)); + $this->assertCount(2, $orders); + $this->assertCount(1, $customer->relatedRecords); // unset unset($customer['orders']); @@ -442,9 +443,9 @@ trait ActiveRecordTestTrait $this->assertFalse($customer->isRelationPopulated('orders')); $orders = $customer->getOrders()->where(['id' => 3])->all(); $this->assertFalse($customer->isRelationPopulated('orders')); - $this->assertEquals(0, count($customer->relatedRecords)); + $this->assertCount(0, $customer->relatedRecords); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(3, $orders[0]->id); } @@ -458,29 +459,29 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ $customers = $customerClass::find()->with('orders')->indexBy('id')->all(); ksort($customers); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertTrue($customers[1]->isRelationPopulated('orders')); $this->assertTrue($customers[2]->isRelationPopulated('orders')); $this->assertTrue($customers[3]->isRelationPopulated('orders')); - $this->assertEquals(1, count($customers[1]->orders)); - $this->assertEquals(2, count($customers[2]->orders)); - $this->assertEquals(0, count($customers[3]->orders)); + $this->assertCount(1, $customers[1]->orders); + $this->assertCount(2, $customers[2]->orders); + $this->assertCount(0, $customers[3]->orders); // unset unset($customers[1]->orders); $this->assertFalse($customers[1]->isRelationPopulated('orders')); $customer = $customerClass::find()->where(['id' => 1])->with('orders')->one(); $this->assertTrue($customer->isRelationPopulated('orders')); - $this->assertEquals(1, count($customer->orders)); - $this->assertEquals(1, count($customer->relatedRecords)); + $this->assertCount(1, $customer->orders); + $this->assertCount(1, $customer->relatedRecords); // multiple with() calls $orders = $orderClass::find()->with('customer', 'items')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[0]->isRelationPopulated('items')); $orders = $orderClass::find()->with('customer')->with('items')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[0]->isRelationPopulated('items')); } @@ -494,7 +495,7 @@ trait ActiveRecordTestTrait /* @var $order Order */ $order = $orderClass::findOne(1); $this->assertEquals(1, $order->id); - $this->assertEquals(2, count($order->items)); + $this->assertCount(2, $order->items); $this->assertEquals(1, $order->items[0]->id); $this->assertEquals(2, $order->items[1]->id); } @@ -518,11 +519,11 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ $orders = $orderClass::find()->with('items')->orderBy('id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertTrue($order->isRelationPopulated('items')); - $this->assertEquals(2, count($order->items)); + $this->assertCount(2, $order->items); $this->assertEquals(1, $order->items[0]->id); $this->assertEquals(2, $order->items[1]->id); } @@ -535,19 +536,19 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ $customers = $customerClass::find()->with('orders', 'orders.items')->indexBy('id')->all(); ksort($customers); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertTrue($customers[1]->isRelationPopulated('orders')); $this->assertTrue($customers[2]->isRelationPopulated('orders')); $this->assertTrue($customers[3]->isRelationPopulated('orders')); - $this->assertEquals(1, count($customers[1]->orders)); - $this->assertEquals(2, count($customers[2]->orders)); - $this->assertEquals(0, count($customers[3]->orders)); + $this->assertCount(1, $customers[1]->orders); + $this->assertCount(2, $customers[2]->orders); + $this->assertCount(0, $customers[3]->orders); $this->assertTrue($customers[1]->orders[0]->isRelationPopulated('items')); $this->assertTrue($customers[2]->orders[0]->isRelationPopulated('items')); $this->assertTrue($customers[2]->orders[1]->isRelationPopulated('items')); - $this->assertEquals(2, count($customers[1]->orders[0]->items)); - $this->assertEquals(3, count($customers[2]->orders[0]->items)); - $this->assertEquals(1, count($customers[2]->orders[1]->items)); + $this->assertCount(2, $customers[1]->orders[0]->items); + $this->assertCount(3, $customers[2]->orders[0]->items); + $this->assertCount(1, $customers[2]->orders[1]->items); $customers = $customerClass::find()->where(['id' => 1])->with('ordersWithItems')->one(); $this->assertTrue($customers->isRelationPopulated('ordersWithItems')); @@ -601,19 +602,19 @@ trait ActiveRecordTestTrait Item 3: 'Ice Age', 2 */ $orders = $orderClass::find()->with('itemsInOrder1')->orderBy('created_at')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder1')); - $this->assertEquals(2, count($order->itemsInOrder1)); + $this->assertCount(2, $order->itemsInOrder1); $this->assertEquals(1, $order->itemsInOrder1[0]->id); $this->assertEquals(2, $order->itemsInOrder1[1]->id); $order = $orders[1]; $this->assertEquals(2, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder1')); - $this->assertEquals(3, count($order->itemsInOrder1)); + $this->assertCount(3, $order->itemsInOrder1); $this->assertEquals(5, $order->itemsInOrder1[0]->id); $this->assertEquals(3, $order->itemsInOrder1[1]->id); $this->assertEquals(4, $order->itemsInOrder1[2]->id); @@ -621,7 +622,7 @@ trait ActiveRecordTestTrait $order = $orders[2]; $this->assertEquals(3, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder1')); - $this->assertEquals(1, count($order->itemsInOrder1)); + $this->assertCount(1, $order->itemsInOrder1); $this->assertEquals(2, $order->itemsInOrder1[0]->id); } @@ -632,19 +633,19 @@ trait ActiveRecordTestTrait $orderClass = $this->getOrderClass(); $orders = $orderClass::find()->with('itemsInOrder2')->orderBy('created_at')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder2')); - $this->assertEquals(2, count($order->itemsInOrder2)); + $this->assertCount(2, $order->itemsInOrder2); $this->assertEquals(1, $order->itemsInOrder2[0]->id); $this->assertEquals(2, $order->itemsInOrder2[1]->id); $order = $orders[1]; $this->assertEquals(2, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder2')); - $this->assertEquals(3, count($order->itemsInOrder2)); + $this->assertCount(3, $order->itemsInOrder2); $this->assertEquals(5, $order->itemsInOrder2[0]->id); $this->assertEquals(3, $order->itemsInOrder2[1]->id); $this->assertEquals(4, $order->itemsInOrder2[2]->id); @@ -652,7 +653,7 @@ trait ActiveRecordTestTrait $order = $orders[2]; $this->assertEquals(3, $order->id); $this->assertTrue($order->isRelationPopulated('itemsInOrder2')); - $this->assertEquals(1, count($order->itemsInOrder2)); + $this->assertCount(1, $order->itemsInOrder2); $this->assertEquals(2, $order->itemsInOrder2[0]->id); } @@ -668,7 +669,7 @@ trait ActiveRecordTestTrait $itemClass = $this->getItemClass(); /* @var $this TestCase|ActiveRecordTestTrait */ $customer = $customerClass::findOne(2); - $this->assertEquals(2, count($customer->orders)); + $this->assertCount(2, $customer->orders); // has many $order = new $orderClass; @@ -676,9 +677,9 @@ trait ActiveRecordTestTrait $this->assertTrue($order->isNewRecord); $customer->link('orders', $order); $this->afterSave(); - $this->assertEquals(3, count($customer->orders)); + $this->assertCount(3, $customer->orders); $this->assertFalse($order->isNewRecord); - $this->assertEquals(3, count($customer->getOrders()->all())); + $this->assertCount(3, $customer->getOrders()->all()); $this->assertEquals(2, $order->customer_id); // belongs to @@ -694,17 +695,17 @@ trait ActiveRecordTestTrait // via model $order = $orderClass::findOne(1); - $this->assertEquals(2, count($order->items)); - $this->assertEquals(2, count($order->orderItems)); + $this->assertCount(2, $order->items); + $this->assertCount(2, $order->orderItems); $orderItem = $orderItemClass::findOne(['order_id' => 1, 'item_id' => 3]); $this->assertNull($orderItem); $item = $itemClass::findOne(3); $order->link('items', $item, ['quantity' => 10, 'subtotal' => 100]); $this->afterSave(); - $this->assertEquals(3, count($order->items)); - $this->assertEquals(3, count($order->orderItems)); + $this->assertCount(3, $order->items); + $this->assertCount(3, $order->orderItems); $orderItem = $orderItemClass::findOne(['order_id' => 1, 'item_id' => 3]); - $this->assertTrue($orderItem instanceof $orderItemClass); + $this->assertInstanceOf($orderItemClass, $orderItem); $this->assertEquals(10, $orderItem->quantity); $this->assertEquals(100, $orderItem->subtotal); } @@ -725,10 +726,10 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // has many without delete $customer = $customerClass::findOne(2); - $this->assertEquals(2, count($customer->ordersWithNullFK)); + $this->assertCount(2, $customer->ordersWithNullFK); $customer->unlink('ordersWithNullFK', $customer->ordersWithNullFK[1], false); - $this->assertEquals(1, count($customer->ordersWithNullFK)); + $this->assertCount(1, $customer->ordersWithNullFK); $orderWithNullFK = $orderWithNullFKClass::findOne(3); $this->assertEquals(3,$orderWithNullFK->id); @@ -736,30 +737,30 @@ trait ActiveRecordTestTrait // has many with delete $customer = $customerClass::findOne(2); - $this->assertEquals(2, count($customer->orders)); + $this->assertCount(2, $customer->orders); $customer->unlink('orders', $customer->orders[1], true); $this->afterSave(); - $this->assertEquals(1, count($customer->orders)); + $this->assertCount(1, $customer->orders); $this->assertNull($orderClass::findOne(3)); // via model with delete $order = $orderClass::findOne(2); - $this->assertEquals(3, count($order->items)); - $this->assertEquals(3, count($order->orderItems)); + $this->assertCount(3, $order->items); + $this->assertCount(3, $order->orderItems); $order->unlink('items', $order->items[2], true); $this->afterSave(); - $this->assertEquals(2, count($order->items)); - $this->assertEquals(2, count($order->orderItems)); + $this->assertCount(2, $order->items); + $this->assertCount(2, $order->orderItems); // via model without delete - $this->assertEquals(3, count($order->itemsWithNullFK)); + $this->assertCount(3, $order->itemsWithNullFK); $order->unlink('itemsWithNullFK', $order->itemsWithNullFK[2], false); $this->afterSave(); - $this->assertEquals(2, count($order->itemsWithNullFK)); - $this->assertEquals(2, count($order->orderItems)); + $this->assertCount(2, $order->itemsWithNullFK); + $this->assertCount(2, $order->orderItems); } public function testUnlinkAll() @@ -780,12 +781,12 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // has many with delete $customer = $customerClass::findOne(2); - $this->assertEquals(2, count($customer->orders)); + $this->assertCount(2, $customer->orders); $this->assertEquals(3, $orderClass::find()->count()); $customer->unlinkAll('orders', true); $this->afterSave(); $this->assertEquals(1, $orderClass::find()->count()); - $this->assertEquals(0, count($customer->orders)); + $this->assertCount(0, $customer->orders); $this->assertNull($orderClass::findOne(2)); $this->assertNull($orderClass::findOne(3)); @@ -793,11 +794,11 @@ trait ActiveRecordTestTrait // has many without delete $customer = $customerClass::findOne(2); - $this->assertEquals(2, count($customer->ordersWithNullFK)); + $this->assertCount(2, $customer->ordersWithNullFK); $this->assertEquals(3, $orderWithNullFKClass::find()->count()); $customer->unlinkAll('ordersWithNullFK', false); $this->afterSave(); - $this->assertEquals(0, count($customer->ordersWithNullFK)); + $this->assertCount(0, $customer->ordersWithNullFK); $this->assertEquals(3, $orderWithNullFKClass::find()->count()); $this->assertEquals(2, $orderWithNullFKClass::find()->where(['AND', ['id' => [2, 3]], ['customer_id' => null]])->count()); @@ -805,22 +806,22 @@ trait ActiveRecordTestTrait // via model with delete /* @var $order Order */ $order = $orderClass::findOne(1); - $this->assertEquals(2, count($order->books)); + $this->assertCount(2, $order->books); $orderItemCount = $orderItemClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('books', true); $this->afterSave(); $this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count()); - $this->assertEquals(0, count($order->books)); + $this->assertCount(0, $order->books); // via model without delete - $this->assertEquals(2, count($order->booksWithNullFK)); + $this->assertCount(2, $order->booksWithNullFK); $orderItemCount = $orderItemsWithNullFKClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('booksWithNullFK',false); $this->afterSave(); - $this->assertEquals(0, count($order->booksWithNullFK)); + $this->assertCount(0, $order->booksWithNullFK); $this->assertEquals(2, $orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count()); @@ -842,16 +843,16 @@ trait ActiveRecordTestTrait $this->afterSave(); $customer = $customerClass::findOne(1); - $this->assertEquals(3, count($customer->ordersWithNullFK)); - $this->assertEquals(1, count($customer->expensiveOrdersWithNullFK)); + $this->assertCount(3, $customer->ordersWithNullFK); + $this->assertCount(1, $customer->expensiveOrdersWithNullFK); $this->assertEquals(3, $orderClass::find()->count()); $customer->unlinkAll('expensiveOrdersWithNullFK'); - $this->assertEquals(3, count($customer->ordersWithNullFK)); - $this->assertEquals(0, count($customer->expensiveOrdersWithNullFK)); + $this->assertCount(3, $customer->ordersWithNullFK); + $this->assertCount(0, $customer->expensiveOrdersWithNullFK); $this->assertEquals(3, $orderClass::find()->count()); $customer = $customerClass::findOne(1); - $this->assertEquals(2, count($customer->ordersWithNullFK)); - $this->assertEquals(0, count($customer->expensiveOrdersWithNullFK)); + $this->assertCount(2, $customer->ordersWithNullFK); + $this->assertCount(0, $customer->expensiveOrdersWithNullFK); } public function testUnlinkAllAndConditionDelete() @@ -868,16 +869,16 @@ trait ActiveRecordTestTrait $this->afterSave(); $customer = $customerClass::findOne(1); - $this->assertEquals(3, count($customer->orders)); - $this->assertEquals(1, count($customer->expensiveOrders)); + $this->assertCount(3, $customer->orders); + $this->assertCount(1, $customer->expensiveOrders); $this->assertEquals(3, $orderClass::find()->count()); $customer->unlinkAll('expensiveOrders', true); - $this->assertEquals(3, count($customer->orders)); - $this->assertEquals(0, count($customer->expensiveOrders)); + $this->assertCount(3, $customer->orders); + $this->assertCount(0, $customer->expensiveOrders); $this->assertEquals(2, $orderClass::find()->count()); $customer = $customerClass::findOne(1); - $this->assertEquals(2, count($customer->orders)); - $this->assertEquals(0, count($customer->expensiveOrders)); + $this->assertCount(2, $customer->orders); + $this->assertCount(0, $customer->expensiveOrders); } public static $afterSaveNewRecord; @@ -934,7 +935,7 @@ trait ActiveRecordTestTrait // save /* @var $customer Customer */ $customer = $customerClass::findOne(2); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals('user2', $customer->name); $this->assertFalse($customer->isNewRecord); static::$afterSaveNewRecord = null; @@ -976,7 +977,7 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ /* @var $customer Customer */ $customer = $customerClass::findOne(2); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals('user2', $customer->name); $this->assertFalse($customer->isNewRecord); static::$afterSaveNewRecord = null; @@ -1042,7 +1043,7 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ // delete $customer = $customerClass::findOne(2); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertEquals('user2', $customer->name); $customer->delete(); $this->afterSave(); @@ -1051,12 +1052,12 @@ trait ActiveRecordTestTrait // deleteAll $customers = $customerClass::find()->all(); - $this->assertEquals(2, count($customers)); + $this->assertCount(2, $customers); $ret = $customerClass::deleteAll(); $this->afterSave(); $this->assertEquals(2, $ret); $customers = $customerClass::find()->all(); - $this->assertEquals(0, count($customers)); + $this->assertCount(0, $customers); $ret = $customerClass::deleteAll(); $this->afterSave(); @@ -1088,10 +1089,10 @@ trait ActiveRecordTestTrait $this->assertEquals(0, $customer->status); $customers = $customerClass::find()->where(['status' => true])->all(); - $this->assertEquals(2, count($customers)); + $this->assertCount(2, $customers); $customers = $customerClass::find()->where(['status' => false])->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); } public function testAfterFind() @@ -1179,16 +1180,16 @@ trait ActiveRecordTestTrait /* @var $this TestCase|ActiveRecordTestTrait */ $customers = $customerClass::find()->where(['id' => [1]])->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $customers = $customerClass::find()->where(['id' => []])->all(); - $this->assertEquals(0, count($customers)); + $this->assertCount(0, $customers); $customers = $customerClass::find()->where(['IN', 'id', [1]])->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $customers = $customerClass::find()->where(['IN', 'id', []])->all(); - $this->assertEquals(0, count($customers)); + $this->assertCount(0, $customers); } public function testFindEagerIndexBy() @@ -1202,7 +1203,7 @@ trait ActiveRecordTestTrait $order = $orderClass::find()->with('itemsIndexed')->where(['id' => 1])->one(); $this->assertTrue($order->isRelationPopulated('itemsIndexed')); $items = $order->itemsIndexed; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); @@ -1210,7 +1211,7 @@ trait ActiveRecordTestTrait $order = $orderClass::find()->with('itemsIndexed')->where(['id' => 2])->one(); $this->assertTrue($order->isRelationPopulated('itemsIndexed')); $items = $order->itemsIndexed; - $this->assertEquals(3, count($items)); + $this->assertCount(3, $items); $this->assertTrue(isset($items[3])); $this->assertTrue(isset($items[4])); $this->assertTrue(isset($items[5])); @@ -1239,7 +1240,7 @@ trait ActiveRecordTestTrait /* @var $customer ActiveRecord */ $customer = new $customerClass(); - $this->assertTrue($customer instanceof $customerClass); + $this->assertInstanceOf($customerClass, $customer); $this->assertTrue($customer->canGetProperty('id')); $this->assertTrue($customer->canSetProperty('id')); diff --git a/tests/framework/base/ComponentTest.php b/tests/framework/base/ComponentTest.php index e95d387..da04122 100644 --- a/tests/framework/base/ComponentTest.php +++ b/tests/framework/base/ComponentTest.php @@ -95,7 +95,7 @@ class ComponentTest extends TestCase public function testGetProperty() { - $this->assertTrue('default' === $this->component->Text); + $this->assertSame('default', $this->component->Text); $this->setExpectedException('yii\base\UnknownPropertyException'); $value2 = $this->component->Caption; } @@ -112,15 +112,15 @@ class ComponentTest extends TestCase public function testIsset() { $this->assertTrue(isset($this->component->Text)); - $this->assertFalse(empty($this->component->Text)); + $this->assertNotEmpty($this->component->Text); $this->component->Text = ''; $this->assertTrue(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); + $this->assertEmpty($this->component->Text); $this->component->Text = null; $this->assertFalse(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); + $this->assertEmpty($this->component->Text); $this->assertFalse(isset($this->component->p2)); $this->component->attachBehavior('a', new NewBehavior()); @@ -138,7 +138,7 @@ class ComponentTest extends TestCase { unset($this->component->Text); $this->assertFalse(isset($this->component->Text)); - $this->assertTrue(empty($this->component->Text)); + $this->assertEmpty($this->component->Text); $this->component->attachBehavior('a', new NewBehavior()); $this->component->setP2('test'); diff --git a/tests/framework/base/ObjectTest.php b/tests/framework/base/ObjectTest.php index 06968f0..401d39d 100644 --- a/tests/framework/base/ObjectTest.php +++ b/tests/framework/base/ObjectTest.php @@ -60,7 +60,7 @@ class ObjectTest extends TestCase public function testGetProperty() { - $this->assertTrue('default' === $this->object->Text); + $this->assertSame('default', $this->object->Text); $this->setExpectedException('yii\base\UnknownPropertyException'); $value2 = $this->object->Caption; } @@ -83,15 +83,15 @@ class ObjectTest extends TestCase public function testIsset() { $this->assertTrue(isset($this->object->Text)); - $this->assertFalse(empty($this->object->Text)); + $this->assertNotEmpty($this->object->Text); $this->object->Text = ''; $this->assertTrue(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); + $this->assertEmpty($this->object->Text); $this->object->Text = null; $this->assertFalse(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); + $this->assertEmpty($this->object->Text); $this->assertFalse(isset($this->object->unknownProperty)); $this->assertTrue(empty($this->object->unknownProperty)); @@ -101,7 +101,7 @@ class ObjectTest extends TestCase { unset($this->object->Text); $this->assertFalse(isset($this->object->Text)); - $this->assertTrue(empty($this->object->Text)); + $this->assertEmpty($this->object->Text); } public function testUnsetReadOnlyProperty() @@ -128,7 +128,7 @@ class ObjectTest extends TestCase public function testObjectProperty() { - $this->assertTrue($this->object->object instanceof NewObject); + $this->assertInstanceOf(NewObject::className(), $this->object->object); $this->assertEquals('object text', $this->object->object->text); $this->object->object->text = 'new text'; $this->assertEquals('new text', $this->object->object->text); diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php index b7150e2..e9026d2 100644 --- a/tests/framework/base/SecurityTest.php +++ b/tests/framework/base/SecurityTest.php @@ -96,7 +96,7 @@ class SecurityTest extends TestCase $data = 'known data'; $key = 'secret'; $hashedData = $this->security->hashData($data, $key); - $this->assertFalse($data === $hashedData); + $this->assertNotSame($data, $hashedData); $this->assertEquals($data, $this->security->validateData($hashedData, $key)); $hashedData[strlen($hashedData) - 1] = 'A'; $this->assertFalse($this->security->validateData($hashedData, $key)); @@ -118,14 +118,14 @@ class SecurityTest extends TestCase $key = 'secret'; $encryptedData = $this->security->encryptByPassword($data, $key); - $this->assertFalse($data === $encryptedData); + $this->assertNotSame($data, $encryptedData); $decryptedData = $this->security->decryptByPassword($encryptedData, $key); $this->assertEquals($data, $decryptedData); $tampered = $encryptedData; $tampered[20] = ~$tampered[20]; $decryptedData = $this->security->decryptByPassword($tampered, $key); - $this->assertTrue(false === $decryptedData); + $this->assertFalse($decryptedData); } public function testEncryptByKey() @@ -134,7 +134,7 @@ class SecurityTest extends TestCase $key = $this->security->generateRandomKey(80); $encryptedData = $this->security->encryptByKey($data, $key); - $this->assertFalse($data === $encryptedData); + $this->assertNotSame($data, $encryptedData); $decryptedData = $this->security->decryptByKey($encryptedData, $key); $this->assertEquals($data, $decryptedData); @@ -145,10 +145,10 @@ class SecurityTest extends TestCase $tampered = $encryptedData; $tampered[20] = ~$tampered[20]; $decryptedData = $this->security->decryptByKey($tampered, $key); - $this->assertTrue(false === $decryptedData); + $this->assertFalse($decryptedData); $decryptedData = $this->security->decryptByKey($encryptedData, $key, $key . "\0"); - $this->assertTrue(false === $decryptedData); + $this->assertFalse($decryptedData); } /** @@ -951,7 +951,7 @@ TEXT; $this->assertInternalType('string', $key2); $this->assertEquals($length, strlen($key2)); if ($length >= 7) { // avoid random test failure, short strings are likely to collide - $this->assertTrue($key1 != $key2); + $this->assertNotEquals($key1, $key2); } } @@ -963,7 +963,7 @@ TEXT; $key2 = $this->security->generateRandomKey($length); $this->assertInternalType('string', $key2); $this->assertEquals($length, strlen($key2)); - $this->assertTrue($key1 != $key2); + $this->assertNotEquals($key1, $key2); // force /dev/urandom reading loop to deal with chunked data // the above test may have read everything in one run. diff --git a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php index 8b9764d..b73d500 100644 --- a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php +++ b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php @@ -68,7 +68,7 @@ class AttributeTypecastBehaviorTest extends TestCase $this->assertSame('123', $model->name); $this->assertSame(58, $model->amount); $this->assertSame(100.8, $model->price); - $this->assertSame(true, $model->isActive); + $this->assertTrue($model->isActive); $this->assertSame('callback: foo', $model->callback); } @@ -100,7 +100,7 @@ class AttributeTypecastBehaviorTest extends TestCase $this->assertSame('', $model->name); $this->assertSame(0, $model->amount); $this->assertSame(0.0, $model->price); - $this->assertSame(false, $model->isActive); + $this->assertFalse($model->isActive); $this->assertSame('callback: ', $model->callback); } diff --git a/tests/framework/console/controllers/AssetControllerTest.php b/tests/framework/console/controllers/AssetControllerTest.php index d3a38e0..7e5f2eb 100644 --- a/tests/framework/console/controllers/AssetControllerTest.php +++ b/tests/framework/console/controllers/AssetControllerTest.php @@ -248,7 +248,7 @@ EOL; { $configFileName = $this->testFilePath . DIRECTORY_SEPARATOR . 'config.php'; $this->runAssetControllerAction('template', [$configFileName]); - $this->assertTrue(file_exists($configFileName), 'Unable to create config file template!'); + $this->assertFileExists($configFileName, 'Unable to create config file template!'); $config = require($configFileName); $this->assertTrue(is_array($config), 'Invalid config created!'); } @@ -294,7 +294,7 @@ EOL; $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); // Then : - $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); + $this->assertFileExists($bundleFile, 'Unable to create output bundle file!'); $compressedBundleConfig = require($bundleFile); $this->assertTrue(is_array($compressedBundleConfig), 'Output bundle file has incorrect format!'); $this->assertCount(2, $compressedBundleConfig, 'Output bundle config contains wrong bundle count!'); @@ -306,9 +306,9 @@ EOL; $this->assertNotEmpty($compressedAssetBundleConfig['depends'], 'Compressed bundle dependency is invalid!'); $compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css'; - $this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!'); + $this->assertFileExists($compressedCssFileName, 'Unable to compress CSS files!'); $compressedJsFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.js'; - $this->assertTrue(file_exists($compressedJsFileName), 'Unable to compress JS files!'); + $this->assertFileExists($compressedJsFileName, 'Unable to compress JS files!'); $compressedCssFileContent = file_get_contents($compressedCssFileName); foreach ($cssFiles as $name => $content) { @@ -374,7 +374,7 @@ EOL; $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); // Then : - $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); + $this->assertFileExists($bundleFile, 'Unable to create output bundle file!'); $compressedBundleConfig = require($bundleFile); $this->assertTrue(is_array($compressedBundleConfig), 'Output bundle file has incorrect format!'); $this->assertArrayHasKey($externalAssetBundleClassName, $compressedBundleConfig, 'External bundle is lost!'); diff --git a/tests/framework/console/controllers/BaseMessageControllerTest.php b/tests/framework/console/controllers/BaseMessageControllerTest.php index 3b4a2a1..b22af5b 100644 --- a/tests/framework/console/controllers/BaseMessageControllerTest.php +++ b/tests/framework/console/controllers/BaseMessageControllerTest.php @@ -140,7 +140,8 @@ abstract class BaseMessageControllerTest extends TestCase { $configFileName = $this->configFileName; $out = $this->runMessageControllerAction('config', [$configFileName]); - $this->assertTrue(file_exists($configFileName), "Unable to create config file from template. Command output:\n\n" . $out); + $this->assertFileExists($configFileName, + "Unable to create config file from template. Command output:\n\n" . $out); } public function testConfigFileNotExist() @@ -177,7 +178,8 @@ abstract class BaseMessageControllerTest extends TestCase $out = $this->runMessageControllerAction('extract', [$this->configFileName]); $out .= $this->runMessageControllerAction('extract', [$this->configFileName]); - $this->assertTrue(strpos($out, 'Nothing to save') !== false, "Controller should respond with \"Nothing to save\" if there's nothing to update. Command output:\n\n" . $out); + $this->assertNotFalse(strpos($out, 'Nothing to save'), + "Controller should respond with \"Nothing to save\" if there's nothing to update. Command output:\n\n" . $out); } /** @@ -280,8 +282,12 @@ abstract class BaseMessageControllerTest extends TestCase $out = $this->runMessageControllerAction('extract', [$this->configFileName]); $messages = $this->loadMessages($category); - $this->assertTrue($zeroMessageContent === $messages[$zeroMessage], "Message content \"0\" is lost. Command output:\n\n" . $out); - $this->assertTrue($falseMessageContent === $messages[$falseMessage], "Message content \"false\" is lost. Command output:\n\n" . $out); + $this->assertSame($zeroMessageContent, + $messages[$zeroMessage], + "Message content \"0\" is lost. Command output:\n\n" . $out); + $this->assertSame($falseMessageContent, + $messages[$falseMessage], + "Message content \"false\" is lost. Command output:\n\n" . $out); } /** diff --git a/tests/framework/data/ActiveDataProviderTest.php b/tests/framework/data/ActiveDataProviderTest.php index ae74ee5..024ef39 100644 --- a/tests/framework/data/ActiveDataProviderTest.php +++ b/tests/framework/data/ActiveDataProviderTest.php @@ -38,10 +38,10 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'query' => Order::find()->orderBy('id'), ]); $orders = $provider->getModels(); - $this->assertEquals(3, count($orders)); - $this->assertTrue($orders[0] instanceof Order); - $this->assertTrue($orders[1] instanceof Order); - $this->assertTrue($orders[2] instanceof Order); + $this->assertCount(3, $orders); + $this->assertInstanceOf(Order::className(), $orders[0]); + $this->assertInstanceOf(Order::className(), $orders[1]); + $this->assertInstanceOf(Order::className(), $orders[2]); $this->assertEquals([1, 2, 3], $provider->getKeys()); $provider = new ActiveDataProvider([ @@ -51,7 +51,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase ] ]); $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); } public function testActiveRelation() @@ -62,9 +62,9 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'query' => $customer->getOrders(), ]); $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); - $this->assertTrue($orders[0] instanceof Order); - $this->assertTrue($orders[1] instanceof Order); + $this->assertCount(2, $orders); + $this->assertInstanceOf(Order::className(), $orders[0]); + $this->assertInstanceOf(Order::className(), $orders[1]); $this->assertEquals([2, 3], $provider->getKeys()); $provider = new ActiveDataProvider([ @@ -74,7 +74,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase ] ]); $orders = $provider->getModels(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); } public function testActiveRelationVia() @@ -85,10 +85,10 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'query' => $order->getItems(), ]); $items = $provider->getModels(); - $this->assertEquals(3, count($items)); - $this->assertTrue($items[0] instanceof Item); - $this->assertTrue($items[1] instanceof Item); - $this->assertTrue($items[2] instanceof Item); + $this->assertCount(3, $items); + $this->assertInstanceOf(Item::className(), $items[0]); + $this->assertInstanceOf(item::className(), $items[1]); + $this->assertInstanceOf(Item::className(), $items[2]); $this->assertEquals([3, 4, 5], $provider->getKeys()); $provider = new ActiveDataProvider([ @@ -98,7 +98,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase ] ]); $items = $provider->getModels(); - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); } public function testActiveRelationViaTable() @@ -109,9 +109,9 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'query' => $order->getBooks(), ]); $items = $provider->getModels(); - $this->assertEquals(2, count($items)); - $this->assertTrue($items[0] instanceof Item); - $this->assertTrue($items[1] instanceof Item); + $this->assertCount(2, $items); + $this->assertInstanceOf(Item::className(), $items[0]); + $this->assertInstanceOf(Item::className(), $items[1]); $provider = new ActiveDataProvider([ 'query' => $order->getBooks(), @@ -120,7 +120,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase ] ]); $items = $provider->getModels(); - $this->assertEquals(1, count($items)); + $this->assertCount(1, $items); } public function testQuery() @@ -131,7 +131,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'query' => $query->from('order')->orderBy('id'), ]); $orders = $provider->getModels(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertTrue(is_array($orders[0])); $this->assertEquals([0, 1, 2], $provider->getKeys()); @@ -144,7 +144,7 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase ] ]); $orders = $provider->getModels(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); } public function testRefresh() @@ -154,12 +154,12 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase 'db' => $this->getConnection(), 'query' => $query->from('order')->orderBy('id'), ]); - $this->assertEquals(3, count($provider->getModels())); + $this->assertCount(3, $provider->getModels()); $provider->getPagination()->pageSize = 2; - $this->assertEquals(3, count($provider->getModels())); + $this->assertCount(3, $provider->getModels()); $provider->refresh(); - $this->assertEquals(2, count($provider->getModels())); + $this->assertCount(2, $provider->getModels()); } public function testPaginationBeforeModels() @@ -175,9 +175,9 @@ abstract class ActiveDataProviderTest extends DatabaseTestCase $this->assertEquals(1, $pagination->getPageCount()); $provider->getPagination()->pageSize = 2; - $this->assertEquals(3, count($provider->getModels())); + $this->assertCount(3, $provider->getModels()); $provider->refresh(); - $this->assertEquals(2, count($provider->getModels())); + $this->assertCount(2, $provider->getModels()); } public function testDoesNotPerformQueryWhenHasNoModels() diff --git a/tests/framework/data/SortTest.php b/tests/framework/data/SortTest.php index 965abd5..d0fd505 100644 --- a/tests/framework/data/SortTest.php +++ b/tests/framework/data/SortTest.php @@ -42,14 +42,14 @@ class SortTest extends TestCase ]); $orders = $sort->getOrders(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(SORT_ASC, $orders['age']); $this->assertEquals(SORT_DESC, $orders['first_name']); $this->assertEquals(SORT_DESC, $orders['last_name']); $sort->enableMultiSort = false; $orders = $sort->getOrders(true); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(SORT_ASC, $orders['age']); } @@ -73,13 +73,13 @@ class SortTest extends TestCase ]); $orders = $sort->getAttributeOrders(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(SORT_ASC, $orders['age']); $this->assertEquals(SORT_DESC, $orders['name']); $sort->enableMultiSort = false; $orders = $sort->getAttributeOrders(true); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(SORT_ASC, $orders['age']); } diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index 89b0b30..7350bca 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -33,35 +33,57 @@ abstract class ActiveRecordTest extends DatabaseTestCase ActiveRecord::$db = $this->getConnection(); } + /** + * @inheritdoc + */ public function getCustomerClass() { return Customer::className(); } + /** + * @inheritdoc + */ public function getItemClass() { return Item::className(); } + /** + * @inheritdoc + */ public function getOrderClass() { return Order::className(); } + /** + * @inheritdoc + */ public function getOrderItemClass() { return OrderItem::className(); } + /** + * @return string + */ public function getCategoryClass() { return Category::className(); } + /** + * @inheritdoc + */ public function getOrderWithNullFKClass() { return OrderWithNullFK::className(); } + + /** + * @inheritdoc + */ public function getOrderItemWithNullFKmClass() { return OrderItemWithNullFK::className(); @@ -119,16 +141,16 @@ abstract class ActiveRecordTest extends DatabaseTestCase { // find one $customer = Customer::findBySql('SELECT * FROM {{customer}} ORDER BY [[id]] DESC')->one(); - $this->assertTrue($customer instanceof Customer); + $this->assertInstanceOf(Customer::className(), $customer); $this->assertEquals('user3', $customer->name); // find all $customers = Customer::findBySql('SELECT * FROM {{customer}}')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); // find with parameter binding $customer = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2])->one(); - $this->assertTrue($customer instanceof Customer); + $this->assertInstanceOf(Customer::className(), $customer); $this->assertEquals('user2', $customer->name); } @@ -150,13 +172,13 @@ abstract class ActiveRecordTest extends DatabaseTestCase /* @var $order Order */ $order = Order::findOne(1); $this->assertEquals(1, $order->id); - $this->assertEquals(2, count($order->books)); + $this->assertCount(2, $order->books); $this->assertEquals(1, $order->items[0]->id); $this->assertEquals(2, $order->items[1]->id); $order = Order::findOne(2); $this->assertEquals(2, $order->id); - $this->assertEquals(0, count($order->books)); + $this->assertCount(0, $order->books); $order = Order::find()->where(['id' => 1])->asArray()->one(); $this->assertTrue(is_array($order)); @@ -165,32 +187,32 @@ abstract class ActiveRecordTest extends DatabaseTestCase public function testFindEagerViaTable() { $orders = Order::find()->with('books')->orderBy('id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); - $this->assertEquals(2, count($order->books)); + $this->assertCount(2, $order->books); $this->assertEquals(1, $order->books[0]->id); $this->assertEquals(2, $order->books[1]->id); $order = $orders[1]; $this->assertEquals(2, $order->id); - $this->assertEquals(0, count($order->books)); + $this->assertCount(0, $order->books); $order = $orders[2]; $this->assertEquals(3, $order->id); - $this->assertEquals(1, count($order->books)); + $this->assertCount(1, $order->books); $this->assertEquals(2, $order->books[0]->id); // https://github.com/yiisoft/yii2/issues/1402 $orders = Order::find()->with('books')->orderBy('id')->asArray()->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertTrue(is_array($orders[0]['orderItems'][0])); $order = $orders[0]; $this->assertTrue(is_array($order)); $this->assertEquals(1, $order['id']); - $this->assertEquals(2, count($order['books'])); + $this->assertCount(2, $order['books']); $this->assertEquals(1, $order['books'][0]['id']); $this->assertEquals(2, $order['books'][1]['id']); } @@ -204,7 +226,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $items = $customer->orderItems; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertInstanceOf(Item::className(), $items[0]); $this->assertInstanceOf(Item::className(), $items[1]); $this->assertEquals(1, $items[0]->id); @@ -223,7 +245,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $category = Category::findOne(1); $this->assertNotNull($category); $orders = $category->orders; - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertInstanceOf(Order::className(), $orders[0]); $this->assertInstanceOf(Order::className(), $orders[1]); $ids = [$orders[0]->id, $orders[1]->id]; @@ -233,7 +255,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $category = Category::findOne(2); $this->assertNotNull($category); $orders = $category->orders; - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertInstanceOf(Order::className(), $orders[0]); $this->assertEquals(2, $orders[0]->id); @@ -301,8 +323,8 @@ abstract class ActiveRecordTest extends DatabaseTestCase $this->assertTrue($record->refresh()); // https://github.com/yiisoft/yii2/commit/34945b0b69011bc7cab684c7f7095d837892a0d4#commitcomment-4458225 - $this->assertTrue($record->var1 === $record->var2); - $this->assertTrue($record->var2 === $record->var3); + $this->assertSame($record->var1, $record->var2); + $this->assertSame($record->var2, $record->var3); } public function testIsPrimaryKey() @@ -326,7 +348,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase { // left join and eager loading $orders = Order::find()->joinWith('customer')->orderBy('customer.id DESC, order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); @@ -340,7 +362,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $query->where('{{customer}}.[[id]]=2'); }, ])->orderBy('order.id')->all(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); @@ -352,7 +374,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $query->where(['customer.id' => 2]); }, ])->where(['order.id' => [1, 2]])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); @@ -362,7 +384,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $query->where('{{customer}}.[[id]]=2'); }, ], false)->orderBy('order.id')->all(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); @@ -374,19 +396,19 @@ abstract class ActiveRecordTest extends DatabaseTestCase $query->where(['customer.id' => 2]); }, ], false)->where(['order.id' => [1, 2]])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); // join with via-relation $orders = Order::find()->innerJoinWith('books')->orderBy('order.id')->all(); - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('books')); $this->assertTrue($orders[1]->isRelationPopulated('books')); - $this->assertEquals(2, count($orders[0]->books)); - $this->assertEquals(1, count($orders[1]->books)); + $this->assertCount(2, $orders[0]->books); + $this->assertCount(1, $orders[1]->books); // join with sub-relation $orders = Order::find()->innerJoinWith([ @@ -397,10 +419,10 @@ abstract class ActiveRecordTest extends DatabaseTestCase $q->where('{{category}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); - $this->assertEquals(3, count($orders[0]->items)); + $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); @@ -410,7 +432,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $q->from('customer c'); } ])->orderBy('c.id DESC, order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); @@ -420,7 +442,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase // join with table alias $orders = Order::find()->joinWith('customer as c')->orderBy('c.id DESC, order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); @@ -437,53 +459,53 @@ abstract class ActiveRecordTest extends DatabaseTestCase $q->where('{{c}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); - $this->assertEquals(3, count($orders[0]->items)); + $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); // join with ON condition $orders = Order::find()->joinWith('books2')->orderBy('order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('books2')); $this->assertTrue($orders[1]->isRelationPopulated('books2')); $this->assertTrue($orders[2]->isRelationPopulated('books2')); - $this->assertEquals(2, count($orders[0]->books2)); - $this->assertEquals(0, count($orders[1]->books2)); - $this->assertEquals(1, count($orders[2]->books2)); + $this->assertCount(2, $orders[0]->books2); + $this->assertCount(0, $orders[1]->books2); + $this->assertCount(1, $orders[2]->books2); // lazy loading with ON condition $order = Order::findOne(1); - $this->assertEquals(2, count($order->books2)); + $this->assertCount(2, $order->books2); $order = Order::findOne(2); - $this->assertEquals(0, count($order->books2)); + $this->assertCount(0, $order->books2); $order = Order::findOne(3); - $this->assertEquals(1, count($order->books2)); + $this->assertCount(1, $order->books2); // eager loading with ON condition $orders = Order::find()->with('books2')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('books2')); $this->assertTrue($orders[1]->isRelationPopulated('books2')); $this->assertTrue($orders[2]->isRelationPopulated('books2')); - $this->assertEquals(2, count($orders[0]->books2)); - $this->assertEquals(0, count($orders[1]->books2)); - $this->assertEquals(1, count($orders[2]->books2)); + $this->assertCount(2, $orders[0]->books2); + $this->assertCount(0, $orders[1]->books2); + $this->assertCount(1, $orders[2]->books2); // join with count and query $query = Order::find()->joinWith('customer'); $count = $query->count(); $this->assertEquals(3, $count); $orders = $query->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); // https://github.com/yiisoft/yii2/issues/2880 $query = Order::findOne(1); @@ -509,10 +531,10 @@ abstract class ActiveRecordTest extends DatabaseTestCase ]); }, ])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); - $this->assertEquals(3, count($orders[0]->items)); + $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } @@ -521,13 +543,13 @@ abstract class ActiveRecordTest extends DatabaseTestCase { // hasOne inner join $customers = Customer::find()->active()->innerJoinWith('profile')->orderBy('customer.id')->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); $this->assertEquals(1, $customers[0]->id); $this->assertTrue($customers[0]->isRelationPopulated('profile')); // hasOne outer join $customers = Customer::find()->active()->joinWith('profile')->orderBy('customer.id')->all(); - $this->assertEquals(2, count($customers)); + $this->assertCount(2, $customers); $this->assertEquals(1, $customers[0]->id); $this->assertEquals(2, $customers[1]->id); $this->assertTrue($customers[0]->isRelationPopulated('profile')); @@ -541,7 +563,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $q->orderBy('order.id'); } ])->orderBy('customer.id DESC, order.id')->all(); - $this->assertEquals(2, count($customers)); + $this->assertCount(2, $customers); $this->assertEquals(2, $customers[0]->id); $this->assertEquals(1, $customers[1]->id); $this->assertTrue($customers[0]->isRelationPopulated('orders')); @@ -589,7 +611,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase } elseif ($aliasMethod === 'applyAlias') { $orders = $query->orderBy($query->applyAlias('customer', 'id') . ' DESC,' . $query->applyAlias('order', 'id'))->all(); } - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); @@ -606,7 +628,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('customer', 'id') => 2])->orderBy($query->applyAlias('order', 'id'))->all(); } - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); @@ -621,7 +643,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('customer', 'id') => 2])->orderBy($query->applyAlias('order', 'id'))->all(); } - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); @@ -636,13 +658,13 @@ abstract class ActiveRecordTest extends DatabaseTestCase } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('book', 'name') => 'Yii 1.1 Application Development Cookbook'])->orderBy($query->applyAlias('order', 'id'))->all(); } - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('books')); $this->assertTrue($orders[1]->isRelationPopulated('books')); - $this->assertEquals(2, count($orders[0]->books)); - $this->assertEquals(1, count($orders[1]->books)); + $this->assertCount(2, $orders[0]->books); + $this->assertCount(1, $orders[1]->books); // joining sub relations @@ -675,10 +697,10 @@ abstract class ActiveRecordTest extends DatabaseTestCase } elseif ($aliasMethod === 'applyAlias') { $orders = $query->orderBy($query->applyAlias('item', 'id'))->all(); } - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); - $this->assertEquals(3, count($orders[0]->items)); + $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); @@ -686,32 +708,32 @@ abstract class ActiveRecordTest extends DatabaseTestCase if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') { $relationName = 'books' . ucfirst($aliasMethod); $orders = Order::find()->joinWith(["$relationName b"])->orderBy('order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated($relationName)); $this->assertTrue($orders[1]->isRelationPopulated($relationName)); $this->assertTrue($orders[2]->isRelationPopulated($relationName)); - $this->assertEquals(2, count($orders[0]->$relationName)); - $this->assertEquals(0, count($orders[1]->$relationName)); - $this->assertEquals(1, count($orders[2]->$relationName)); + $this->assertCount(2, $orders[0]->$relationName); + $this->assertCount(0, $orders[1]->$relationName); + $this->assertCount(1, $orders[2]->$relationName); } // join with ON condition and alias in relation definition if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') { $relationName = 'books' . ucfirst($aliasMethod) . 'A'; $orders = Order::find()->joinWith(["$relationName"])->orderBy('order.id')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated($relationName)); $this->assertTrue($orders[1]->isRelationPopulated($relationName)); $this->assertTrue($orders[2]->isRelationPopulated($relationName)); - $this->assertEquals(2, count($orders[0]->$relationName)); - $this->assertEquals(0, count($orders[1]->$relationName)); - $this->assertEquals(1, count($orders[2]->$relationName)); + $this->assertCount(2, $orders[0]->$relationName); + $this->assertCount(0, $orders[1]->$relationName); + $this->assertCount(1, $orders[2]->$relationName); } // join with count and query @@ -726,7 +748,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase } $this->assertEquals(3, $count); $orders = $query->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); // relational query /** @var $order Order */ @@ -757,10 +779,10 @@ abstract class ActiveRecordTest extends DatabaseTestCase } }, ])->orderBy('order.id')->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); - $this->assertEquals(3, count($orders[0]->items)); + $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); @@ -776,7 +798,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->joinWith('movieItems', false) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); - $this->assertEquals(1, count($orders), $query->createCommand()->rawSql . print_r($orders, true)); + $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('bookItems')); $this->assertFalse($orders[0]->isRelationPopulated('movieItems')); @@ -786,12 +808,12 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->joinWith('movieItems', true) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); - $this->assertEquals(1, count($orders), $query->createCommand()->rawSql . print_r($orders, true)); + $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('bookItems')); $this->assertTrue($orders[0]->isRelationPopulated('movieItems')); - $this->assertEquals(0, count($orders[0]->bookItems)); - $this->assertEquals(3, count($orders[0]->movieItems)); + $this->assertCount(0, $orders[0]->bookItems); + $this->assertCount(3, $orders[0]->movieItems); // join with the same table but different aliases // alias is defined in the call to joinWith() @@ -801,7 +823,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->joinWith(['itemsIndexed movies' => function($q) { $q->onCondition('movies.category_id = 2'); }], false) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); - $this->assertEquals(1, count($orders), $query->createCommand()->rawSql . print_r($orders, true)); + $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('itemsIndexed')); // with eager loading, only for one relation as it would be overwritten otherwise. @@ -810,20 +832,20 @@ abstract class ActiveRecordTest extends DatabaseTestCase ->joinWith(['itemsIndexed movies' => function($q) { $q->onCondition('movies.category_id = 2'); }], true) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); - $this->assertEquals(1, count($orders), $query->createCommand()->rawSql . print_r($orders, true)); + $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed')); - $this->assertEquals(3, count($orders[0]->itemsIndexed)); + $this->assertCount(3, $orders[0]->itemsIndexed); // with eager loading, and the other relation $query = Order::find() ->joinWith(['itemsIndexed books' => function($q) { $q->onCondition('books.category_id = 1'); }], true) ->joinWith(['itemsIndexed movies' => function($q) { $q->onCondition('movies.category_id = 2'); }], false) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); - $this->assertEquals(1, count($orders), $query->createCommand()->rawSql . print_r($orders, true)); + $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed')); - $this->assertEquals(0, count($orders[0]->itemsIndexed)); + $this->assertCount(0, $orders[0]->itemsIndexed); } /** @@ -919,55 +941,55 @@ abstract class ActiveRecordTest extends DatabaseTestCase { // eager loading: find one and all $customer = Customer::find()->with('orders2')->where(['id' => 1])->one(); - $this->assertTrue($customer->orders2[0]->customer2 === $customer); + $this->assertSame($customer->orders2[0]->customer2, $customer); $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->all(); - $this->assertTrue($customers[0]->orders2[0]->customer2 === $customers[0]); - $this->assertTrue(empty($customers[1]->orders2)); + $this->assertSame($customers[0]->orders2[0]->customer2, $customers[0]); + $this->assertEmpty($customers[1]->orders2); // lazy loading $customer = Customer::findOne(2); $orders = $customer->orders2; - $this->assertTrue(count($orders) === 2); - $this->assertTrue($customer->orders2[0]->customer2 === $customer); - $this->assertTrue($customer->orders2[1]->customer2 === $customer); + $this->assertCount(2, $orders); + $this->assertSame($customer->orders2[0]->customer2, $customer); + $this->assertSame($customer->orders2[1]->customer2, $customer); // ad-hoc lazy loading $customer = Customer::findOne(2); $orders = $customer->getOrders2()->all(); - $this->assertTrue(count($orders) === 2); + $this->assertCount(2, $orders); $this->assertTrue($orders[0]->isRelationPopulated('customer2'), 'inverse relation did not populate the relation'); $this->assertTrue($orders[1]->isRelationPopulated('customer2'), 'inverse relation did not populate the relation'); - $this->assertTrue($orders[0]->customer2 === $customer); - $this->assertTrue($orders[1]->customer2 === $customer); + $this->assertSame($orders[0]->customer2, $customer); + $this->assertSame($orders[1]->customer2, $customer); // the other way around $customer = Customer::find()->with('orders2')->where(['id' => 1])->asArray()->one(); - $this->assertTrue($customer['orders2'][0]['customer2']['id'] === $customer['id']); + $this->assertSame($customer['orders2'][0]['customer2']['id'], $customer['id']); $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->asArray()->all(); - $this->assertTrue($customer['orders2'][0]['customer2']['id'] === $customers[0]['id']); - $this->assertTrue(empty($customers[1]['orders2'])); + $this->assertSame($customer['orders2'][0]['customer2']['id'], $customers[0]['id']); + $this->assertEmpty($customers[1]['orders2']); $orders = Order::find()->with('customer2')->where(['id' => 1])->all(); - $this->assertTrue($orders[0]->customer2->orders2 === [$orders[0]]); + $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]); $order = Order::find()->with('customer2')->where(['id' => 1])->one(); - $this->assertTrue($order->customer2->orders2 === [$order]); + $this->assertSame($order->customer2->orders2, [$order]); $orders = Order::find()->with('customer2')->where(['id' => 1])->asArray()->all(); - $this->assertTrue($orders[0]['customer2']['orders2'][0]['id'] === $orders[0]['id']); + $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']); $order = Order::find()->with('customer2')->where(['id' => 1])->asArray()->one(); - $this->assertTrue($order['customer2']['orders2'][0]['id'] === $orders[0]['id']); + $this->assertSame($order['customer2']['orders2'][0]['id'], $orders[0]['id']); $orders = Order::find()->with('customer2')->where(['id' => [1, 3]])->all(); - $this->assertTrue($orders[0]->customer2->orders2 === [$orders[0]]); - $this->assertTrue($orders[1]->customer2->orders2 === [$orders[1]]); + $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]); + $this->assertSame($orders[1]->customer2->orders2, [$orders[1]]); $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->all(); - $this->assertTrue($orders[0]->customer2->orders2 === $orders); - $this->assertTrue($orders[1]->customer2->orders2 === $orders); + $this->assertSame($orders[0]->customer2->orders2, $orders); + $this->assertSame($orders[1]->customer2->orders2, $orders); $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->asArray()->all(); - $this->assertTrue($orders[0]['customer2']['orders2'][0]['id'] === $orders[0]['id']); - $this->assertTrue($orders[0]['customer2']['orders2'][1]['id'] === $orders[1]['id']); - $this->assertTrue($orders[1]['customer2']['orders2'][0]['id'] === $orders[0]['id']); - $this->assertTrue($orders[1]['customer2']['orders2'][1]['id'] === $orders[1]['id']); + $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']); + $this->assertSame($orders[0]['customer2']['orders2'][1]['id'], $orders[1]['id']); + $this->assertSame($orders[1]['customer2']['orders2'][0]['id'], $orders[0]['id']); + $this->assertSame($orders[1]['customer2']['orders2'][1]['id'], $orders[1]['id']); } public function testInverseOfDynamic() @@ -1040,21 +1062,21 @@ abstract class ActiveRecordTest extends DatabaseTestCase // via table with delete /* @var $order Order */ $order = $orderClass::findOne(1); - $this->assertEquals(2, count($order->booksViaTable)); + $this->assertCount(2, $order->booksViaTable); $orderItemCount = $orderItemClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('booksViaTable', true); $this->afterSave(); $this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count()); - $this->assertEquals(0, count($order->booksViaTable)); + $this->assertCount(0, $order->booksViaTable); // via table without delete - $this->assertEquals(2, count($order->booksWithNullFKViaTable)); + $this->assertCount(2, $order->booksWithNullFKViaTable); $orderItemCount = $orderItemsWithNullFKClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); - $order->unlinkAll('booksWithNullFKViaTable', false); - $this->assertEquals(0, count($order->booksWithNullFKViaTable)); + $order->unlinkAll('booksWithNullFKViaTable', false); + $this->assertCount(0, $order->booksWithNullFKViaTable); $this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count()); @@ -1093,22 +1115,22 @@ abstract class ActiveRecordTest extends DatabaseTestCase { // https://github.com/yiisoft/yii2/issues/4938 $category = Category::findOne(2); - $this->assertTrue($category instanceof Category); + $this->assertInstanceOf(Category::className(), $category); $this->assertEquals(3, $category->getItems()->count()); $this->assertEquals(1, $category->getLimitedItems()->count()); $this->assertEquals(1, $category->getLimitedItems()->distinct(true)->count()); // https://github.com/yiisoft/yii2/issues/3197 $orders = Order::find()->with('orderItems')->orderBy('id')->all(); - $this->assertEquals(3, count($orders)); - $this->assertEquals(2, count($orders[0]->orderItems)); - $this->assertEquals(3, count($orders[1]->orderItems)); - $this->assertEquals(1, count($orders[2]->orderItems)); + $this->assertCount(3, $orders); + $this->assertCount(2, $orders[0]->orderItems); + $this->assertCount(3, $orders[1]->orderItems); + $this->assertCount(1, $orders[2]->orderItems); $orders = Order::find()->with(['orderItems' => function ($q) { $q->indexBy('item_id'); }])->orderBy('id')->all(); - $this->assertEquals(3, count($orders)); - $this->assertEquals(2, count($orders[0]->orderItems)); - $this->assertEquals(3, count($orders[1]->orderItems)); - $this->assertEquals(1, count($orders[2]->orderItems)); + $this->assertCount(3, $orders); + $this->assertCount(2, $orders[0]->orderItems); + $this->assertCount(3, $orders[1]->orderItems); + $this->assertCount(1, $orders[2]->orderItems); // https://github.com/yiisoft/yii2/issues/8149 $model = new Customer(); @@ -1312,12 +1334,12 @@ abstract class ActiveRecordTest extends DatabaseTestCase $row = Customer::find() ->emulateExecution() ->one(); - $this->assertSame(null, $row); + $this->assertNull($row); $exists = Customer::find() ->emulateExecution() ->exists(); - $this->assertSame(false, $exists); + $this->assertFalse($exists); $count = Customer::find() ->emulateExecution() @@ -1337,18 +1359,18 @@ abstract class ActiveRecordTest extends DatabaseTestCase $max = Customer::find() ->emulateExecution() ->max('id'); - $this->assertSame(null, $max); + $this->assertNull($max); $min = Customer::find() ->emulateExecution() ->min('id'); - $this->assertSame(null, $min); + $this->assertNull($min); $scalar = Customer::find() ->select(['id']) ->emulateExecution() ->scalar(); - $this->assertSame(null, $scalar); + $this->assertNull($scalar); $column = Customer::find() ->select(['id']) diff --git a/tests/framework/db/BatchQueryResultTest.php b/tests/framework/db/BatchQueryResultTest.php index 9a41173..5fab5ce 100644 --- a/tests/framework/db/BatchQueryResultTest.php +++ b/tests/framework/db/BatchQueryResultTest.php @@ -24,9 +24,9 @@ abstract class BatchQueryResultTest extends DatabaseTestCase $query = new Query(); $query->from('customer')->orderBy('id'); $result = $query->batch(2, $db); - $this->assertTrue($result instanceof BatchQueryResult); + $this->assertInstanceOf(BatchQueryResult::className(), $result); $this->assertEquals(2, $result->batchSize); - $this->assertTrue($result->query === $query); + $this->assertSame($result->query, $query); // normal query $query = new Query(); @@ -36,7 +36,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($batch as $rows) { $allRows = array_merge($allRows, $rows); } - $this->assertEquals(3, count($allRows)); + $this->assertCount(3, $allRows); $this->assertEquals('user1', $allRows[0]['name']); $this->assertEquals('user2', $allRows[1]['name']); $this->assertEquals('user3', $allRows[2]['name']); @@ -45,7 +45,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($batch as $rows) { $allRows = array_merge($allRows, $rows); } - $this->assertEquals(3, count($allRows)); + $this->assertCount(3, $allRows); // reset $batch->reset(); @@ -57,7 +57,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($batch as $rows) { $allRows = array_merge($allRows, $rows); } - $this->assertEquals(0, count($allRows)); + $this->assertCount(0, $allRows); // query with index $query = new Query(); @@ -66,7 +66,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($query->batch(2, $db) as $rows) { $allRows = array_merge($allRows, $rows); } - $this->assertEquals(3, count($allRows)); + $this->assertCount(3, $allRows); $this->assertEquals('address1', $allRows['user1']['address']); $this->assertEquals('address2', $allRows['user2']['address']); $this->assertEquals('address3', $allRows['user3']['address']); @@ -78,7 +78,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($query->each(100, $db) as $rows) { $allRows[] = $rows; } - $this->assertEquals(3, count($allRows)); + $this->assertCount(3, $allRows); $this->assertEquals('user1', $allRows[0]['name']); $this->assertEquals('user2', $allRows[1]['name']); $this->assertEquals('user3', $allRows[2]['name']); @@ -90,7 +90,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($query->each(100, $db) as $key => $row) { $allRows[$key] = $row; } - $this->assertEquals(3, count($allRows)); + $this->assertCount(3, $allRows); $this->assertEquals('address1', $allRows['user1']['address']); $this->assertEquals('address2', $allRows['user2']['address']); $this->assertEquals('address3', $allRows['user3']['address']); @@ -105,7 +105,7 @@ abstract class BatchQueryResultTest extends DatabaseTestCase foreach ($query->batch(2, $db) as $models) { $customers = array_merge($customers, $models); } - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertEquals('user1', $customers[0]->name); $this->assertEquals('user2', $customers[1]->name); $this->assertEquals('user3', $customers[2]->name); @@ -119,9 +119,9 @@ abstract class BatchQueryResultTest extends DatabaseTestCase $this->assertTrue($model->isRelationPopulated('orders')); } } - $this->assertEquals(3, count($customers)); - $this->assertEquals(1, count($customers[0]->orders)); - $this->assertEquals(2, count($customers[1]->orders)); - $this->assertEquals(0, count($customers[2]->orders)); + $this->assertCount(3, $customers); + $this->assertCount(1, $customers[0]->orders); + $this->assertCount(2, $customers[1]->orders); + $this->assertCount(0, $customers[2]->orders); } } diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index fb107bd..80804bd 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -82,11 +82,11 @@ abstract class CommandTest extends DatabaseTestCase // query $sql = 'SELECT * FROM {{customer}}'; $reader = $db->createCommand($sql)->query(); - $this->assertTrue($reader instanceof DataReader); + $this->assertInstanceOf(DataReader::className(), $reader); // queryAll $rows = $db->createCommand('SELECT * FROM {{customer}}')->queryAll(); - $this->assertEquals(3, count($rows)); + $this->assertCount(3, $rows); $row = $rows[2]; $this->assertEquals(3, $row['id']); $this->assertEquals('user3', $row['name']); @@ -583,10 +583,10 @@ SQL; $db = $this->getConnection(); $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll(); - $this->assertEquals(2, count($rows)); + $this->assertCount(2, $rows); $db->createCommand()->truncateTable('animal')->execute(); $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll(); - $this->assertEquals(0, count($rows)); + $this->assertCount(0, $rows); } public function testRenameTable() diff --git a/tests/framework/db/ConnectionTest.php b/tests/framework/db/ConnectionTest.php index 5bb6444..c31d6e3 100644 --- a/tests/framework/db/ConnectionTest.php +++ b/tests/framework/db/ConnectionTest.php @@ -27,7 +27,7 @@ abstract class ConnectionTest extends DatabaseTestCase $connection->open(); $this->assertTrue($connection->isActive); - $this->assertTrue($connection->pdo instanceof \PDO); + $this->assertInstanceOf('\\PDO', $connection->pdo); $connection->close(); $this->assertFalse($connection->isActive); diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 96308dc..b34081c 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -1230,25 +1230,25 @@ abstract class QueryBuilderTest extends DatabaseTestCase $qb = $this->getQueryBuilder(); $qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, ['id'])->execute(); $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(1, count($tableSchema->primaryKey)); + $this->assertCount(1, $tableSchema->primaryKey); // DROP $qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute(); $qb = $this->getQueryBuilder(); // resets the schema $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(0, count($tableSchema->primaryKey)); + $this->assertCount(0, $tableSchema->primaryKey); // ADD (2 columns) $qb = $this->getQueryBuilder(); $qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, 'id, field1')->execute(); $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(2, count($tableSchema->primaryKey)); + $this->assertCount(2, $tableSchema->primaryKey); // DROP (2 columns) $qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute(); $qb = $this->getQueryBuilder(); // resets the schema $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); - $this->assertEquals(0, count($tableSchema->primaryKey)); + $this->assertCount(0, $tableSchema->primaryKey); } public function existsParamsProvider() diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index 1adf680..f230c86 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -267,7 +267,7 @@ abstract class QueryTest extends DatabaseTestCase ); $result = $query->all($connection); $this->assertNotEmpty($result); - $this->assertSame(4, count($result)); + $this->assertCount(4, $result); } public function testOne() @@ -407,13 +407,13 @@ abstract class QueryTest extends DatabaseTestCase ->from('customer') ->emulateExecution() ->one($db); - $this->assertSame(false, $row); + $this->assertFalse($row); $exists = (new Query()) ->from('customer') ->emulateExecution() ->exists($db); - $this->assertSame(false, $exists); + $this->assertFalse($exists); $count = (new Query()) ->from('customer') @@ -437,20 +437,20 @@ abstract class QueryTest extends DatabaseTestCase ->from('customer') ->emulateExecution() ->max('id', $db); - $this->assertSame(null, $max); + $this->assertNull($max); $min = (new Query()) ->from('customer') ->emulateExecution() ->min('id', $db); - $this->assertSame(null, $min); + $this->assertNull($min); $scalar = (new Query()) ->select(['id']) ->from('customer') ->emulateExecution() ->scalar($db); - $this->assertSame(null, $scalar); + $this->assertNull($scalar); $column = (new Query()) ->select(['id']) diff --git a/tests/framework/db/SchemaTest.php b/tests/framework/db/SchemaTest.php index 4ee6cef..f80d7ae 100644 --- a/tests/framework/db/SchemaTest.php +++ b/tests/framework/db/SchemaTest.php @@ -101,7 +101,7 @@ abstract class SchemaTest extends DatabaseTestCase $schema->refreshTableSchema('type'); $refreshedTable = $schema->getTableSchema('type', false); - $this->assertFalse($noCacheTable === $refreshedTable); + $this->assertNotSame($noCacheTable, $refreshedTable); } public function testCompositeFk() diff --git a/tests/framework/db/oci/ActiveRecordTest.php b/tests/framework/db/oci/ActiveRecordTest.php index 6cf0475..34e3447 100644 --- a/tests/framework/db/oci/ActiveRecordTest.php +++ b/tests/framework/db/oci/ActiveRecordTest.php @@ -88,7 +88,7 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest // find all asArray $customers = $customerClass::find()->asArray()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers[0]); $this->assertArrayHasKey('name', $customers[0]); $this->assertArrayHasKey('email', $customers[0]); diff --git a/tests/framework/db/oci/SchemaTest.php b/tests/framework/db/oci/SchemaTest.php index 267ec24..8198230 100644 --- a/tests/framework/db/oci/SchemaTest.php +++ b/tests/framework/db/oci/SchemaTest.php @@ -91,6 +91,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest public function testAutoincrementDisabled() { $table = $this->getConnection(false)->schema->getTableSchema('order', true); - $this->assertSame(false, $table->columns['id']->autoIncrement); + $this->assertFalse($table->columns['id']->autoIncrement); } } diff --git a/tests/framework/db/pgsql/ActiveRecordTest.php b/tests/framework/db/pgsql/ActiveRecordTest.php index 22ecfeb..261e0ef 100644 --- a/tests/framework/db/pgsql/ActiveRecordTest.php +++ b/tests/framework/db/pgsql/ActiveRecordTest.php @@ -29,19 +29,19 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest $customer->save(false); $customer->refresh(); - $this->assertSame(false, $customer->bool_status); + $this->assertFalse($customer->bool_status); $customer->bool_status = true; $customer->save(false); $customer->refresh(); - $this->assertSame(true, $customer->bool_status); + $this->assertTrue($customer->bool_status); $customers = $customerClass::find()->where(['bool_status' => true])->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $customers = $customerClass::find()->where(['bool_status' => false])->all(); - $this->assertEquals(1, count($customers)); + $this->assertCount(1, $customers); } public function testFindAsArray() @@ -63,7 +63,7 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest // find all asArray $customers = $customerClass::find()->asArray()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers[0]); $this->assertArrayHasKey('name', $customers[0]); $this->assertArrayHasKey('email', $customers[0]); @@ -106,8 +106,8 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db)); $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db)); - $this->assertSame(true, BoolAR::find()->where(['bool_col' => true])->one($db)->bool_col); - $this->assertSame(false, BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col); + $this->assertTrue(BoolAR::find()->where(['bool_col' => true])->one($db)->bool_col); + $this->assertFalse(BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col); } /** @@ -141,9 +141,9 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest $user->email = 'test@example.com'; $user->save(false); - $this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => false])->all($db))); - $this->assertEquals(0, count(UserAR::find()->where(['is_deleted' => true])->all($db))); - $this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => [true, false]])->all($db))); + $this->assertCount(1, UserAR::find()->where(['is_deleted' => false])->all($db)); + $this->assertCount(0, UserAR::find()->where(['is_deleted' => true])->all($db)); + $this->assertCount(1, UserAR::find()->where(['is_deleted' => [true, false]])->all($db)); } public function testBooleanDefaultValues() @@ -154,8 +154,8 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest $this->assertNull($model->default_false); $model->loadDefaultValues(); $this->assertNull($model->bool_col); - $this->assertSame(true, $model->default_true); - $this->assertSame(false, $model->default_false); + $this->assertTrue($model->default_true); + $this->assertFalse($model->default_false); $this->assertTrue($model->save(false)); } diff --git a/tests/framework/db/pgsql/SchemaTest.php b/tests/framework/db/pgsql/SchemaTest.php index b7b4ff6..f4451dd 100644 --- a/tests/framework/db/pgsql/SchemaTest.php +++ b/tests/framework/db/pgsql/SchemaTest.php @@ -124,15 +124,15 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest $schema = $this->getConnection()->schema; $table = $schema->getTableSchema('bool_values'); - $this->assertSame(true, $table->getColumn('default_true')->defaultValue); - $this->assertSame(false, $table->getColumn('default_false')->defaultValue); + $this->assertTrue($table->getColumn('default_true')->defaultValue); + $this->assertFalse($table->getColumn('default_false')->defaultValue); } public function testFindSchemaNames() { $schema = $this->getConnection()->schema; - $this->assertEquals(3, count($schema->getSchemaNames())); + $this->assertCount(3, $schema->getSchemaNames()); } public function bigintValueProvider() diff --git a/tests/framework/db/sqlite/ConnectionTest.php b/tests/framework/db/sqlite/ConnectionTest.php index d91ef1b..fbbc97a 100644 --- a/tests/framework/db/sqlite/ConnectionTest.php +++ b/tests/framework/db/sqlite/ConnectionTest.php @@ -51,7 +51,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $db = $this->prepareMasterSlave($masterCount, $slaveCount); - $this->assertTrue($db->getSlave() instanceof Connection); + $this->assertInstanceOf(Connection::className(), $db->getSlave()); $this->assertTrue($db->getSlave()->isActive); $this->assertFalse($db->isActive); @@ -63,7 +63,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $db->createCommand("UPDATE profile SET description='test' WHERE id=1")->execute(); $this->assertTrue($db->isActive); if ($masterCount > 0) { - $this->assertTrue($db->getMaster() instanceof Connection); + $this->assertInstanceOf(Connection::className(), $db->getMaster()); $this->assertTrue($db->getMaster()->isActive); } else { $this->assertNull($db->getMaster()); @@ -79,7 +79,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $this->assertFalse($db->isActive); $customer = Customer::findOne(1); - $this->assertTrue($customer instanceof Customer); + $this->assertInstanceOf(Customer::className(), $customer); $this->assertEquals('user1', $customer->name); $this->assertFalse($db->isActive); @@ -87,7 +87,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $customer->save(); $this->assertTrue($db->isActive); $customer = Customer::findOne(1); - $this->assertTrue($customer instanceof Customer); + $this->assertInstanceOf(Customer::className(), $customer); $this->assertEquals('user1', $customer->name); $result = $db->useMaster(function () { return Customer::findOne(1)->name; @@ -114,8 +114,8 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $hit_masters[$db->getMaster()->dsn] = true; } - $this->assertEquals($mastersCount, count($hit_masters), 'all masters hit'); - $this->assertEquals($slavesCount, count($hit_slaves), 'all slaves hit'); + $this->assertCount($mastersCount, $hit_masters, 'all masters hit'); + $this->assertCount($slavesCount, $hit_slaves, 'all slaves hit'); } public function testMastersSequential() @@ -136,9 +136,9 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest $hit_masters[$db->getMaster()->dsn] = true; } - $this->assertEquals(1, count($hit_masters), 'same master hit'); + $this->assertCount(1, $hit_masters, 'same master hit'); // slaves are always random - $this->assertEquals($slavesCount, count($hit_slaves), 'all slaves hit'); + $this->assertCount($slavesCount, $hit_slaves, 'all slaves hit'); } public function testRestoreMasterAfterException() diff --git a/tests/framework/db/sqlite/QueryTest.php b/tests/framework/db/sqlite/QueryTest.php index 17baad4..37cb553 100644 --- a/tests/framework/db/sqlite/QueryTest.php +++ b/tests/framework/db/sqlite/QueryTest.php @@ -24,6 +24,6 @@ class QueryTest extends \yiiunit\framework\db\QueryTest ); $result = $query->all($connection); $this->assertNotEmpty($result); - $this->assertSame(7, count($result)); + $this->assertCount(7, $result); } } diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php index 920103c..211d336 100644 --- a/tests/framework/di/ContainerTest.php +++ b/tests/framework/di/ContainerTest.php @@ -40,11 +40,11 @@ class ContainerTest extends TestCase $container = new Container; $container->set($QuxInterface, $Qux); $foo = $container->get($Foo); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); + $this->assertInstanceOf($Foo, $foo); + $this->assertInstanceOf($Bar, $foo->bar); + $this->assertInstanceOf($Qux, $foo->bar->qux); $foo2 = $container->get($Foo); - $this->assertFalse($foo === $foo2); + $this->assertNotSame($foo, $foo2); // full wiring $container = new Container; @@ -53,9 +53,9 @@ class ContainerTest extends TestCase $container->set($Qux); $container->set($Foo); $foo = $container->get($Foo); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); + $this->assertInstanceOf($Foo, $foo); + $this->assertInstanceOf($Bar, $foo->bar); + $this->assertInstanceOf($Qux, $foo->bar->qux); // wiring by closure $container = new Container; @@ -65,9 +65,9 @@ class ContainerTest extends TestCase return new Foo($bar); }); $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); + $this->assertInstanceOf($Foo, $foo); + $this->assertInstanceOf($Bar, $foo->bar); + $this->assertInstanceOf($Qux, $foo->bar->qux); // wiring by closure which uses container $container = new Container; @@ -76,9 +76,9 @@ class ContainerTest extends TestCase return $c->get(Foo::className()); }); $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); + $this->assertInstanceOf($Foo, $foo); + $this->assertInstanceOf($Bar, $foo->bar); + $this->assertInstanceOf($Qux, $foo->bar->qux); // predefined constructor parameters $container = new Container; @@ -86,16 +86,16 @@ class ContainerTest extends TestCase $container->set('bar', $Bar, [Instance::of('qux')]); $container->set('qux', $Qux); $foo = $container->get('foo'); - $this->assertTrue($foo instanceof $Foo); - $this->assertTrue($foo->bar instanceof $Bar); - $this->assertTrue($foo->bar->qux instanceof $Qux); + $this->assertInstanceOf($Foo, $foo); + $this->assertInstanceOf($Bar, $foo->bar); + $this->assertInstanceOf($Qux, $foo->bar->qux); // wiring by closure $container = new Container; $container->set('qux', new Qux); $qux1 = $container->get('qux'); $qux2 = $container->get('qux'); - $this->assertTrue($qux1 === $qux2); + $this->assertSame($qux1, $qux2); // config $container = new Container; diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 4a660a9..3f91f48 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -27,10 +27,10 @@ class InstanceTest extends TestCase $className = Component::className(); $instance = Instance::of($className); - $this->assertTrue($instance instanceof Instance); - $this->assertTrue($instance->get($container) instanceof Component); - $this->assertTrue(Instance::ensure($instance, $className, $container) instanceof Component); - $this->assertTrue($instance->get($container) !== Instance::ensure($instance, $className, $container)); + $this->assertInstanceOf('\\yii\\di\\Instance', $instance); + $this->assertInstanceOf(Component::className(), $instance->get($container)); + $this->assertInstanceOf(Component::className(), Instance::ensure($instance, $className, $container)); + $this->assertNotSame($instance->get($container), Instance::ensure($instance, $className, $container)); } public function testEnsure() @@ -41,9 +41,9 @@ class InstanceTest extends TestCase 'dsn' => 'test', ]); - $this->assertTrue(Instance::ensure('db', 'yii\db\Connection', $container) instanceof Connection); - $this->assertTrue(Instance::ensure(new Connection, 'yii\db\Connection', $container) instanceof Connection); - $this->assertTrue(Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], 'yii\db\Connection', $container) instanceof Connection); + $this->assertInstanceOf(Connection::className(), Instance::ensure('db', 'yii\db\Connection', $container)); + $this->assertInstanceOf(Connection::className(), Instance::ensure(new Connection, 'yii\db\Connection', $container)); + $this->assertInstanceOf('\\yii\\db\\Connection', Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], 'yii\db\Connection', $container)); } /** @@ -74,9 +74,9 @@ class InstanceTest extends TestCase 'dsn' => 'test', ]); - $this->assertTrue(Instance::ensure('db', null, $container) instanceof Connection); - $this->assertTrue(Instance::ensure(new Connection, null, $container) instanceof Connection); - $this->assertTrue(Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], null, $container) instanceof Connection); + $this->assertInstanceOf(Connection::className(), Instance::ensure('db', null, $container)); + $this->assertInstanceOf(Connection::className(), Instance::ensure(new Connection, null, $container)); + $this->assertInstanceOf('\\yii\\db\\Connection', Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], null, $container)); } public function testEnsure_MinimalSettings() @@ -86,10 +86,9 @@ class InstanceTest extends TestCase 'dsn' => 'test', ]); - $this->assertTrue(Instance::ensure('db') instanceof Connection); - $this->assertTrue(Instance::ensure(new Connection) instanceof Connection); - $this->assertTrue(Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test']) instanceof Connection); - + $this->assertInstanceOf(Connection::className(), Instance::ensure('db')); + $this->assertInstanceOf(Connection::className(), Instance::ensure(new Connection)); + $this->assertInstanceOf(Connection::className(), Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'])); Yii::$container = new Container; } @@ -132,7 +131,7 @@ class InstanceTest extends TestCase $container = Instance::of('db'); - $this->assertTrue($container->get() instanceof Connection); + $this->assertInstanceOf(Connection::className(), $container->get()); $this->destroyApplication(); } diff --git a/tests/framework/di/ServiceLocatorTest.php b/tests/framework/di/ServiceLocatorTest.php index 5e3dd5f..0e8411b 100644 --- a/tests/framework/di/ServiceLocatorTest.php +++ b/tests/framework/di/ServiceLocatorTest.php @@ -44,7 +44,7 @@ class ServiceLocatorTest extends TestCase ]); }); $object = $container->get($className); - $this->assertTrue($object instanceof $className); + $this->assertInstanceOf($className, $object); $this->assertEquals(100, $object->prop1); $this->assertEquals(200, $object->prop2); @@ -53,7 +53,7 @@ class ServiceLocatorTest extends TestCase $className = TestClass::className(); $container->set($className, [__NAMESPACE__ . "\\Creator", 'create']); $object = $container->get($className); - $this->assertTrue($object instanceof $className); + $this->assertInstanceOf($className, $object); $this->assertEquals(1, $object->prop1); $this->assertNull($object->prop2); } @@ -64,7 +64,7 @@ class ServiceLocatorTest extends TestCase $className = TestClass::className(); $container = new ServiceLocator; $container->set($className, $object); - $this->assertTrue($container->get($className) === $object); + $this->assertSame($container->get($className), $object); } public function testShared() @@ -80,11 +80,11 @@ class ServiceLocatorTest extends TestCase $object = $container->get($className); $this->assertEquals(10, $object->prop1); $this->assertEquals(20, $object->prop2); - $this->assertTrue($object instanceof $className); + $this->assertInstanceOf($className, $object); // check shared $object2 = $container->get($className); - $this->assertTrue($object2 instanceof $className); - $this->assertTrue($object === $object2); + $this->assertInstanceOf($className, $object2); + $this->assertSame($object, $object2); } /** @@ -104,11 +104,11 @@ class ServiceLocatorTest extends TestCase $app = new ServiceLocator($config); $this->assertTrue(isset($app->captcha->name)); - $this->assertFalse(empty($app->captcha->name)); + $this->assertNotEmpty($app->captcha->name); $this->assertEquals('foo bar', $app->captcha->name); $this->assertTrue(isset($app->captcha->name)); - $this->assertFalse(empty($app->captcha->name)); + $this->assertNotEmpty($app->captcha->name); } } diff --git a/tests/framework/filters/HttpCacheTest.php b/tests/framework/filters/HttpCacheTest.php index 791e209..deafe97 100644 --- a/tests/framework/filters/HttpCacheTest.php +++ b/tests/framework/filters/HttpCacheTest.php @@ -37,7 +37,7 @@ class HttpCacheTest extends \yiiunit\TestCase $httpCache->beforeAction(null); $response = Yii::$app->getResponse(); $this->assertFalse($response->getHeaders()->offsetExists('Pragma')); - $this->assertFalse($response->getHeaders()->get('Pragma') === ''); + $this->assertNotSame($response->getHeaders()->get('Pragma'), ''); } /** diff --git a/tests/framework/helpers/FileHelperTest.php b/tests/framework/helpers/FileHelperTest.php index 90b3b6e..082d473 100644 --- a/tests/framework/helpers/FileHelperTest.php +++ b/tests/framework/helpers/FileHelperTest.php @@ -163,7 +163,7 @@ class FileHelperTest extends TestCase foreach ($files as $name => $content) { $fileName = $dstDirName . DIRECTORY_SEPARATOR . $name; $this->assertFileExists($fileName); - $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); + $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!'); } } @@ -200,7 +200,7 @@ class FileHelperTest extends TestCase } else { $fileName = $dstDirName . DIRECTORY_SEPARATOR . $name; $this->assertFileExists($fileName); - $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); + $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!'); } } }; @@ -241,7 +241,7 @@ class FileHelperTest extends TestCase $this->assertFileNotExists($fileName); } else { $this->assertFileExists($fileName); - $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); + $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!'); } } } @@ -780,7 +780,7 @@ class FileHelperTest extends TestCase foreach ($dataFiles as $name => $content) { $fileName = $dstDirName . DIRECTORY_SEPARATOR . $name; $this->assertFileExists($fileName); - $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); + $this->assertStringEqualsFile($fileName, $content, 'Incorrect file content!'); } } } diff --git a/tests/framework/helpers/HtmlTest.php b/tests/framework/helpers/HtmlTest.php index 55e74ec..3377d13 100644 --- a/tests/framework/helpers/HtmlTest.php +++ b/tests/framework/helpers/HtmlTest.php @@ -900,7 +900,7 @@ EOD; $options = []; Html::removeCssStyle($options, ['color', 'background']); - $this->assertTrue(!array_key_exists('style', $options)); + $this->assertNotTrue(array_key_exists('style', $options)); $options = [ 'style' => [ 'color' => 'red', diff --git a/tests/framework/log/FileTargetTest.php b/tests/framework/log/FileTargetTest.php index ed7ba16..9e0e90e 100644 --- a/tests/framework/log/FileTargetTest.php +++ b/tests/framework/log/FileTargetTest.php @@ -62,11 +62,11 @@ class FileTargetTest extends TestCase clearstatcache(); - $this->assertTrue(file_exists($logFile)); - $this->assertFalse(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); + $this->assertFileExists($logFile); + $this->assertFileNotExists($logFile . '.1'); + $this->assertFileNotExists($logFile . '.2'); + $this->assertFileNotExists($logFile . '.3'); + $this->assertFileNotExists($logFile . '.4'); // exceed max size for ($i = 0; $i < 1024; $i++) { @@ -81,11 +81,11 @@ class FileTargetTest extends TestCase clearstatcache(); - $this->assertTrue(file_exists($logFile)); - $this->assertTrue(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); + $this->assertFileExists($logFile); + $this->assertFileExists($logFile . '.1'); + $this->assertFileNotExists($logFile . '.2'); + $this->assertFileNotExists($logFile . '.3'); + $this->assertFileNotExists($logFile . '.4'); // second rotate @@ -96,10 +96,10 @@ class FileTargetTest extends TestCase clearstatcache(); - $this->assertTrue(file_exists($logFile)); - $this->assertTrue(file_exists($logFile . '.1')); - $this->assertFalse(file_exists($logFile . '.2')); - $this->assertFalse(file_exists($logFile . '.3')); - $this->assertFalse(file_exists($logFile . '.4')); + $this->assertFileExists($logFile); + $this->assertFileExists($logFile . '.1'); + $this->assertFileNotExists($logFile . '.2'); + $this->assertFileNotExists($logFile . '.3'); + $this->assertFileNotExists($logFile . '.4'); } } \ No newline at end of file diff --git a/tests/framework/log/LoggerTest.php b/tests/framework/log/LoggerTest.php index ed572c7..b9441e7 100644 --- a/tests/framework/log/LoggerTest.php +++ b/tests/framework/log/LoggerTest.php @@ -36,7 +36,7 @@ class LoggerTest extends TestCase { $memory = memory_get_usage(); $this->logger->log('test1', Logger::LEVEL_INFO); - $this->assertEquals(1, count($this->logger->messages)); + $this->assertCount(1, $this->logger->messages); $this->assertEquals('test1', $this->logger->messages[0][0]); $this->assertEquals(Logger::LEVEL_INFO, $this->logger->messages[0][1]); $this->assertEquals('application', $this->logger->messages[0][2]); @@ -44,7 +44,7 @@ class LoggerTest extends TestCase $this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][5]); $this->logger->log('test2', Logger::LEVEL_ERROR, 'category'); - $this->assertEquals(2, count($this->logger->messages)); + $this->assertCount(2, $this->logger->messages); $this->assertEquals('test2', $this->logger->messages[1][0]); $this->assertEquals(Logger::LEVEL_ERROR, $this->logger->messages[1][1]); $this->assertEquals('category', $this->logger->messages[1][2]); @@ -60,7 +60,7 @@ class LoggerTest extends TestCase $memory = memory_get_usage(); $this->logger->traceLevel = 3; $this->logger->log('test3', Logger::LEVEL_INFO); - $this->assertEquals(1, count($this->logger->messages)); + $this->assertCount(1, $this->logger->messages); $this->assertEquals('test3', $this->logger->messages[0][0]); $this->assertEquals(Logger::LEVEL_INFO, $this->logger->messages[0][1]); $this->assertEquals('application', $this->logger->messages[0][2]); @@ -71,7 +71,7 @@ class LoggerTest extends TestCase 'class' => get_class($this->logger), 'type' => '->' ], $this->logger->messages[0][4][0]); - $this->assertEquals(3, count($this->logger->messages[0][4])); + $this->assertCount(3, $this->logger->messages[0][4]); $this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][5]); } diff --git a/tests/framework/mail/BaseMailerTest.php b/tests/framework/mail/BaseMailerTest.php index b6d166f..2e457bb 100644 --- a/tests/framework/mail/BaseMailerTest.php +++ b/tests/framework/mail/BaseMailerTest.php @@ -285,7 +285,7 @@ TEXT $this->assertTrue($mailer->send($message)); $file = Yii::getAlias($mailer->fileTransportPath) . '/message.txt'; $this->assertTrue(is_file($file)); - $this->assertEquals($message->toString(), file_get_contents($file)); + $this->assertStringEqualsFile($file, $message->toString()); } public function testBeforeSendEvent() diff --git a/tests/framework/rbac/ManagerTestCase.php b/tests/framework/rbac/ManagerTestCase.php index 2d77399..6bd5df7 100644 --- a/tests/framework/rbac/ManagerTestCase.php +++ b/tests/framework/rbac/ManagerTestCase.php @@ -25,7 +25,7 @@ abstract class ManagerTestCase extends TestCase public function testCreateRole() { $role = $this->auth->createRole('admin'); - $this->assertTrue($role instanceof Role); + $this->assertInstanceOf(Role::className(), $role); $this->assertEquals(Item::TYPE_ROLE, $role->type); $this->assertEquals('admin', $role->name); } @@ -33,7 +33,7 @@ abstract class ManagerTestCase extends TestCase public function testCreatePermission() { $permission = $this->auth->createPermission('edit post'); - $this->assertTrue($permission instanceof Permission); + $this->assertInstanceOf(Permission::className(), $permission); $this->assertEquals(Item::TYPE_PERMISSION, $permission->type); $this->assertEquals('edit post', $permission->name); } @@ -266,7 +266,7 @@ abstract class ManagerTestCase extends TestCase $expectedPermissions = ['createPost', 'updatePost', 'readPost', 'updateAnyPost']; $this->assertEquals(count($expectedPermissions), count($permissions)); foreach ($expectedPermissions as $permissionName) { - $this->assertTrue($permissions[$permissionName] instanceof Permission); + $this->assertInstanceOf(Permission::className(), $permissions[$permissionName]); } } @@ -277,7 +277,7 @@ abstract class ManagerTestCase extends TestCase $expectedPermissions = ['deletePost', 'createPost', 'updatePost', 'readPost']; $this->assertEquals(count($expectedPermissions), count($permissions)); foreach ($expectedPermissions as $permissionName) { - $this->assertTrue($permissions[$permissionName] instanceof Permission); + $this->assertInstanceOf(Permission::className(), $permissions[$permissionName]); } } @@ -289,15 +289,15 @@ abstract class ManagerTestCase extends TestCase $this->auth->assign($reader, 123); $roles = $this->auth->getRolesByUser('reader A'); - $this->assertTrue(reset($roles) instanceof Role); + $this->assertInstanceOf(Role::className(), reset($roles)); $this->assertEquals($roles['reader']->name, 'reader'); $roles = $this->auth->getRolesByUser(0); - $this->assertTrue(reset($roles) instanceof Role); + $this->assertInstanceOf(Role::className(), reset($roles)); $this->assertEquals($roles['reader']->name, 'reader'); $roles = $this->auth->getRolesByUser(123); - $this->assertTrue(reset($roles) instanceof Role); + $this->assertInstanceOf(Role::className(), reset($roles)); $this->assertEquals($roles['reader']->name, 'reader'); $this->assertContains('myDefaultRole', array_keys($roles)); @@ -310,12 +310,12 @@ abstract class ManagerTestCase extends TestCase $roles = $this->auth->getChildRoles('withoutChildren'); $this->assertCount(1, $roles); $this->assertInstanceOf(Role::className(), reset($roles)); - $this->assertTrue(reset($roles)->name === 'withoutChildren'); + $this->assertSame(reset($roles)->name, 'withoutChildren'); $roles = $this->auth->getChildRoles('reader'); $this->assertCount(1, $roles); $this->assertInstanceOf(Role::className(), reset($roles)); - $this->assertTrue(reset($roles)->name === 'reader'); + $this->assertSame(reset($roles)->name, 'reader'); $roles = $this->auth->getChildRoles('author'); $this->assertCount(2, $roles); @@ -362,9 +362,9 @@ abstract class ManagerTestCase extends TestCase $this->auth = $this->createManager(); - $this->assertEquals(0, count($this->auth->getAssignments(0))); - $this->assertEquals(1, count($this->auth->getAssignments(42))); - $this->assertEquals(2, count($this->auth->getAssignments(1337))); + $this->assertCount(0, $this->auth->getAssignments(0)); + $this->assertCount(1, $this->auth->getAssignments(42)); + $this->assertCount(2, $this->auth->getAssignments(1337)); } public function testGetAssignmentsByRole() @@ -508,6 +508,6 @@ abstract class ManagerTestCase extends TestCase /** @var ActionRule $rule */ $rule = $this->auth->getRule('action_rule'); - $this->assertTrue($rule instanceof ActionRule); + $this->assertInstanceOf(ActionRule::className(), $rule); } } diff --git a/tests/framework/requirements/YiiRequirementCheckerTest.php b/tests/framework/requirements/YiiRequirementCheckerTest.php index b99b44d..7e74e7b 100644 --- a/tests/framework/requirements/YiiRequirementCheckerTest.php +++ b/tests/framework/requirements/YiiRequirementCheckerTest.php @@ -42,7 +42,7 @@ class YiiRequirementCheckerTest extends TestCase $checkResult = $requirementsChecker->check($requirements)->getResult(); $summary = $checkResult['summary']; - $this->assertEquals(count($requirements), $summary['total'], 'Wrong summary total!'); + $this->assertCount($summary['total'], $requirements, 'Wrong summary total!'); $this->assertEquals(1, $summary['errors'], 'Wrong summary errors!'); $this->assertEquals(1, $summary['warnings'], 'Wrong summary warnings!'); @@ -121,7 +121,7 @@ class YiiRequirementCheckerTest extends TestCase $mergedRequirements = array_merge($requirements1, $requirements2); - $this->assertEquals(count($mergedRequirements), $checkResult['summary']['total'], 'Wrong total checks count!'); + $this->assertCount($checkResult['summary']['total'], $mergedRequirements, 'Wrong total checks count!'); foreach ($mergedRequirements as $key => $mergedRequirement) { $this->assertEquals($mergedRequirement['name'], $checkResult['requirements'][$key]['name'], 'Wrong requirements list!'); } diff --git a/tests/framework/test/ActiveFixtureTest.php b/tests/framework/test/ActiveFixtureTest.php index 9618cd9..1947a98 100644 --- a/tests/framework/test/ActiveFixtureTest.php +++ b/tests/framework/test/ActiveFixtureTest.php @@ -64,7 +64,7 @@ abstract class ActiveFixtureTest extends DatabaseTestCase $test->setUp(); $fixture = $test->getFixture('customers'); $this->assertEquals(CustomerFixture::className(), get_class($fixture)); - $this->assertEquals(2, count($fixture)); + $this->assertCount(2, $fixture); $this->assertEquals(1, $fixture['customer1']['id']); $this->assertEquals('customer1@example.com', $fixture['customer1']['email']); $this->assertEquals(2, $fixture['customer2']['id']); diff --git a/tests/framework/validators/EachValidatorTest.php b/tests/framework/validators/EachValidatorTest.php index 8a12c5d..7430527 100644 --- a/tests/framework/validators/EachValidatorTest.php +++ b/tests/framework/validators/EachValidatorTest.php @@ -141,7 +141,7 @@ class EachValidatorTest extends TestCase $validator = new EachValidator(['rule' => ['compare', 'compareAttribute' => 'attr_two']]); $validator->validateAttribute($model, 'attr_one'); $this->assertNotEmpty($model->getErrors('attr_one')); - $this->assertEquals(3, count($model->attr_one)); + $this->assertCount(3, $model->attr_one); $model = FakedValidationModel::createWithAttributes([ 'attr_one' => [ diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php index f6a406b..8775bd5 100644 --- a/tests/framework/validators/FileValidatorTest.php +++ b/tests/framework/validators/FileValidatorTest.php @@ -137,7 +137,7 @@ class FileValidatorTest extends TestCase ]); $val->validateAttribute($m, 'attr_files'); $this->assertTrue($m->hasErrors()); - $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'you can upload at most') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_files')), 'you can upload at most')); $val->maxFiles = 0; $m->clearErrors(); @@ -168,8 +168,8 @@ class FileValidatorTest extends TestCase ); $m->setScenario('validateMultipleFiles'); $this->assertFalse($m->validate()); - $this->assertTrue(stripos(current($m->getErrors('attr_images')), - 'Only files with these extensions are allowed') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_images')), + 'Only files with these extensions are allowed')); $m = FakedValidationModel::createWithAttributes( [ @@ -306,19 +306,19 @@ class FileValidatorTest extends TestCase $val = new FileValidator(['maxSize' => 128]); $val->validateAttribute($m, 'attr_files'); $this->assertTrue($m->hasErrors('attr_files')); - $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'too big') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_files')), 'too big')); // to Small $m = $this->createModelForAttributeTest(); $val = new FileValidator(['minSize' => 2048]); $val->validateAttribute($m, 'attr_files'); $this->assertTrue($m->hasErrors('attr_files')); - $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'too small') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_files')), 'too small')); // UPLOAD_ERR_INI_SIZE/UPLOAD_ERR_FORM_SIZE $m = $this->createModelForAttributeTest(); $val = new FileValidator(); $val->validateAttribute($m, 'attr_err_ini'); $this->assertTrue($m->hasErrors('attr_err_ini')); - $this->assertTrue(stripos(current($m->getErrors('attr_err_ini')), 'too big') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_err_ini')), 'too big')); // UPLOAD_ERR_PARTIAL $m = $this->createModelForAttributeTest(); $val = new FileValidator(); @@ -343,7 +343,7 @@ class FileValidatorTest extends TestCase $this->assertFalse($m->hasErrors('attr_jpg')); $val->validateAttribute($m, 'attr_exe'); $this->assertTrue($m->hasErrors('attr_exe')); - $this->assertTrue(stripos(current($m->getErrors('attr_exe')), 'Only files with these extensions ') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_exe')), 'Only files with these extensions ')); } public function testIssue11012() diff --git a/tests/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php index ebd4f02..7959d1a 100644 --- a/tests/framework/validators/NumberValidatorTest.php +++ b/tests/framework/validators/NumberValidatorTest.php @@ -237,7 +237,7 @@ class NumberValidatorTest extends TestCase $model->attr_number = 0; $val->validateAttribute($model, 'attr_number'); $this->assertTrue($model->hasErrors('attr_number')); - $this->assertEquals(1, count($model->getErrors('attr_number'))); + $this->assertCount(1, $model->getErrors('attr_number')); $msgs = $model->getErrors('attr_number'); $this->assertSame('attr_number is to small.', $msgs[0]); } diff --git a/tests/framework/validators/RangeValidatorTest.php b/tests/framework/validators/RangeValidatorTest.php index e105513..15b9eff 100644 --- a/tests/framework/validators/RangeValidatorTest.php +++ b/tests/framework/validators/RangeValidatorTest.php @@ -103,7 +103,7 @@ class RangeValidatorTest extends TestCase $val->validateAttribute($m, 'attr_r2'); $this->assertTrue($m->hasErrors('attr_r2')); $err = $m->getErrors('attr_r2'); - $this->assertTrue(stripos($err[0], 'attr_r2') !== false); + $this->assertNotFalse(stripos($err[0], 'attr_r2')); } public function testValidateSubsetArrayable() diff --git a/tests/framework/validators/RequiredValidatorTest.php b/tests/framework/validators/RequiredValidatorTest.php index 8e5cccc..0c206e4 100644 --- a/tests/framework/validators/RequiredValidatorTest.php +++ b/tests/framework/validators/RequiredValidatorTest.php @@ -47,12 +47,12 @@ class RequiredValidatorTest extends TestCase $m = FakedValidationModel::createWithAttributes(['attr_val' => null]); $val->validateAttribute($m, 'attr_val'); $this->assertTrue($m->hasErrors('attr_val')); - $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'blank') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_val')), 'blank')); $val = new RequiredValidator(['requiredValue' => 55]); $m = FakedValidationModel::createWithAttributes(['attr_val' => 56]); $val->validateAttribute($m, 'attr_val'); $this->assertTrue($m->hasErrors('attr_val')); - $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'must be') !== false); + $this->assertNotFalse(stripos(current($m->getErrors('attr_val')), 'must be')); $val = new RequiredValidator(['requiredValue' => 55]); $m = FakedValidationModel::createWithAttributes(['attr_val' => 55]); $val->validateAttribute($m, 'attr_val'); diff --git a/tests/framework/validators/UrlValidatorTest.php b/tests/framework/validators/UrlValidatorTest.php index b959167..063d172 100644 --- a/tests/framework/validators/UrlValidatorTest.php +++ b/tests/framework/validators/UrlValidatorTest.php @@ -111,7 +111,7 @@ class UrlValidatorTest extends TestCase $obj->attr_url = 'google.de'; $val->validateAttribute($obj, 'attr_url'); $this->assertFalse($obj->hasErrors('attr_url')); - $this->assertTrue(stripos($obj->attr_url, 'http') !== false); + $this->assertNotFalse(stripos($obj->attr_url, 'http')); $obj = new FakedValidationModel; $obj->attr_url = 'gttp;/invalid string'; $val->validateAttribute($obj, 'attr_url'); diff --git a/tests/framework/web/AssetBundleTest.php b/tests/framework/web/AssetBundleTest.php index afd4875..430da08 100644 --- a/tests/framework/web/AssetBundleTest.php +++ b/tests/framework/web/AssetBundleTest.php @@ -165,9 +165,9 @@ class AssetBundleTest extends \yiiunit\TestCase $this->assertEmpty($view->assetBundles); TestSimpleAsset::register($view); - $this->assertEquals(1, count($view->assetBundles)); + $this->assertCount(1, $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestSimpleAsset', $view->assetBundles); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestSimpleAsset'] instanceof AssetBundle); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestSimpleAsset']); $expected = <<4 @@ -181,13 +181,13 @@ EOF; $this->assertEmpty($view->assetBundles); TestAssetBundle::register($view); - $this->assertEquals(3, count($view->assetBundles)); + $this->assertCount(3, $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetBundle', $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestJqueryAsset', $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetLevel3', $view->assetBundles); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle'] instanceof AssetBundle); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset'] instanceof AssetBundle); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3'] instanceof AssetBundle); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset']); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3']); $expected = <<23 @@ -226,14 +226,14 @@ EOF; TestJqueryAsset::register($view); } TestAssetBundle::register($view); - $this->assertEquals(3, count($view->assetBundles)); + $this->assertCount(3, $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetBundle', $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestJqueryAsset', $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetLevel3', $view->assetBundles); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle'] instanceof AssetBundle); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset'] instanceof AssetBundle); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3'] instanceof AssetBundle); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset']); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3']); $this->assertArrayHasKey('position', $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']->jsOptions); $this->assertEquals($pos, $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']->jsOptions['position']); @@ -315,9 +315,9 @@ EOF; $this->assertEmpty($view->assetBundles); TestSimpleAsset::register($view); - $this->assertEquals(1, count($view->assetBundles)); + $this->assertCount(1, $view->assetBundles); $this->assertArrayHasKey('yiiunit\\framework\\web\\TestSimpleAsset', $view->assetBundles); - $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestSimpleAsset'] instanceof AssetBundle); + $this->assertInstanceOf(AssetBundle::className(), $view->assetBundles['yiiunit\\framework\\web\\TestSimpleAsset']); // register TestJqueryAsset which also has the jquery.js TestJqueryAsset::register($view); diff --git a/tests/framework/web/AssetConverterTest.php b/tests/framework/web/AssetConverterTest.php index 15f699f..97fe56e 100644 --- a/tests/framework/web/AssetConverterTest.php +++ b/tests/framework/web/AssetConverterTest.php @@ -53,8 +53,8 @@ EOF $converter->commands['php'] = ['txt', 'php {from} > {to}']; $this->assertEquals('test.txt', $converter->convert('test.php', $tmpPath)); - $this->assertTrue(file_exists($tmpPath . '/test.txt'), 'Failed asserting that asset output file exists.'); - $this->assertEquals("Hello World!\nHello Yii!", file_get_contents($tmpPath . '/test.txt')); + $this->assertFileExists($tmpPath . '/test.txt', 'Failed asserting that asset output file exists.'); + $this->assertStringEqualsFile($tmpPath . '/test.txt', "Hello World!\nHello Yii!"); } /** @@ -78,7 +78,7 @@ EOF usleep(1); $converter->convert('test.php', $tmpPath); - $this->assertEquals($initialConvertTime, file_get_contents($tmpPath . '/test.txt')); + $this->assertStringEqualsFile($tmpPath . '/test.txt', $initialConvertTime); $converter->forceConvert = true; $converter->convert('test.php', $tmpPath); diff --git a/tests/framework/web/MultipartFormDataParserTest.php b/tests/framework/web/MultipartFormDataParserTest.php index 31bf7f1..3f9e867 100644 --- a/tests/framework/web/MultipartFormDataParserTest.php +++ b/tests/framework/web/MultipartFormDataParserTest.php @@ -33,18 +33,18 @@ class MultipartFormDataParserTest extends TestCase ]; $this->assertEquals($expectedBodyParams, $bodyParams); - $this->assertFalse(empty($_FILES['someFile'])); + $this->assertNotEmpty($_FILES['someFile']); $this->assertEquals(UPLOAD_ERR_OK, $_FILES['someFile']['error']); $this->assertEquals('some-file.txt', $_FILES['someFile']['name']); $this->assertEquals('text/plain', $_FILES['someFile']['type']); - $this->assertEquals('some file content', file_get_contents($_FILES['someFile']['tmp_name'])); + $this->assertStringEqualsFile($_FILES['someFile']['tmp_name'], 'some file content'); - $this->assertFalse(empty($_FILES['Item'])); - $this->assertFalse(empty($_FILES['Item']['name']['file'])); + $this->assertNotEmpty($_FILES['Item']); + $this->assertNotEmpty($_FILES['Item']['name']['file']); $this->assertEquals(UPLOAD_ERR_OK, $_FILES['Item']['error']['file']); $this->assertEquals('item-file.txt', $_FILES['Item']['name']['file']); $this->assertEquals('text/plain', $_FILES['Item']['type']['file']); - $this->assertEquals('item file content', file_get_contents($_FILES['Item']['tmp_name']['file'])); + $this->assertStringEqualsFile($_FILES['Item']['tmp_name']['file'], 'item file content'); } /** diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index d671f50..c225c03 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -245,8 +245,8 @@ class RequestTest extends TestCase $request = new Request(); unset($_SERVER['SERVER_NAME'], $_SERVER['HTTP_HOST']); - $this->assertSame(null, $request->getHostInfo()); - $this->assertSame(null, $request->getHostName()); + $this->assertNull($request->getHostInfo()); + $this->assertNull($request->getHostName()); $request->setHostInfo('http://servername.com:80'); $this->assertSame('http://servername.com:80', $request->getHostInfo()); diff --git a/tests/framework/web/UploadedFileTest.php b/tests/framework/web/UploadedFileTest.php index 2e164f4..64d4def 100644 --- a/tests/framework/web/UploadedFileTest.php +++ b/tests/framework/web/UploadedFileTest.php @@ -50,8 +50,8 @@ class UploadedFileTest extends TestCase $productImage = UploadedFile::getInstance(new ModelStub(), 'prod_image'); $vendorImage = VendorImage::getInstance(new ModelStub(), 'vendor_image'); - $this->assertTrue($productImage instanceof UploadedFile); - $this->assertTrue($vendorImage instanceof VendorImage); + $this->assertInstanceOf(UploadedFile::className(), $productImage); + $this->assertInstanceOf(VendorImage::className(), $vendorImage); } public function testGetInstances() @@ -60,11 +60,11 @@ class UploadedFileTest extends TestCase $vendorImages = VendorImage::getInstances(new ModelStub(), 'vendor_images'); foreach ($productImages as $productImage) { - $this->assertTrue($productImage instanceof UploadedFile); + $this->assertInstanceOf(UploadedFile::className(), $productImage); } foreach ($vendorImages as $vendorImage) { - $this->assertTrue($vendorImage instanceof VendorImage); + $this->assertInstanceOf(VendorImage::className(), $vendorImage); } } } \ No newline at end of file diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index e82693b..f9dcc35 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -126,17 +126,17 @@ class UserTest extends TestCase $cookie->value = 'junk'; $cookiesMock->add($cookie); Yii::$app->user->getIdentity(); - $this->assertTrue(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + $this->assertEquals(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])), 0); Yii::$app->user->login(UserIdentity::findIdentity('user1'),3600); $this->assertFalse(Yii::$app->user->isGuest); $this->assertSame(Yii::$app->user->id, 'user1'); - $this->assertFalse(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + $this->assertNotEquals(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])), 0); Yii::$app->user->login(UserIdentity::findIdentity('user2'),0); $this->assertFalse(Yii::$app->user->isGuest); $this->assertSame(Yii::$app->user->id, 'user2'); - $this->assertTrue(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + $this->assertEquals(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])), 0); } /** diff --git a/tests/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php index 35256a1..3b0fa34 100644 --- a/tests/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php @@ -342,7 +342,7 @@ EOD; // expected empty $actualValue = $this->activeField->getClientOptions(); - $this->assertTrue(empty($actualValue) === true); + $this->assertEmpty($actualValue); } public function testGetClientOptionsWithActiveAttributeInScenario() @@ -354,7 +354,7 @@ EOD; // expected empty $actualValue = $this->activeField->getClientOptions(); - $this->assertTrue(empty($actualValue) === true); + $this->assertEmpty($actualValue); } @@ -368,11 +368,11 @@ EOD; $expectedJsExpression = "function (attribute, value, messages, deferred, \$form) {return true;}"; $this->assertEquals($expectedJsExpression, $actualValue['validate']); - $this->assertTrue(!isset($actualValue['validateOnChange'])); - $this->assertTrue(!isset($actualValue['validateOnBlur'])); - $this->assertTrue(!isset($actualValue['validateOnType'])); - $this->assertTrue(!isset($actualValue['validationDelay'])); - $this->assertTrue(!isset($actualValue['enableAjaxValidation'])); + $this->assertNotTrue(isset($actualValue['validateOnChange'])); + $this->assertNotTrue(isset($actualValue['validateOnBlur'])); + $this->assertNotTrue(isset($actualValue['validateOnType'])); + $this->assertNotTrue(isset($actualValue['validationDelay'])); + $this->assertNotTrue(isset($actualValue['enableAjaxValidation'])); $this->activeField->validateOnChange = $expectedValidateOnChange = false; $this->activeField->validateOnBlur = $expectedValidateOnBlur = false; @@ -382,11 +382,11 @@ EOD; $actualValue = $this->activeField->getClientOptions(); - $this->assertTrue($expectedValidateOnChange === $actualValue['validateOnChange']); - $this->assertTrue($expectedValidateOnBlur === $actualValue['validateOnBlur']); - $this->assertTrue($expectedValidateOnType === $actualValue['validateOnType']); - $this->assertTrue($expectedValidationDelay === $actualValue['validationDelay']); - $this->assertTrue($expectedEnableAjaxValidation === $actualValue['enableAjaxValidation']); + $this->assertSame($expectedValidateOnChange, $actualValue['validateOnChange']); + $this->assertSame($expectedValidateOnBlur, $actualValue['validateOnBlur']); + $this->assertSame($expectedValidateOnType, $actualValue['validateOnType']); + $this->assertSame($expectedValidationDelay, $actualValue['validationDelay']); + $this->assertSame($expectedEnableAjaxValidation, $actualValue['enableAjaxValidation']); } public function testGetClientOptionsValidatorWhenClientSet() diff --git a/tests/framework/widgets/LinkSorterTest.php b/tests/framework/widgets/LinkSorterTest.php index 50bac46..9377c5f 100644 --- a/tests/framework/widgets/LinkSorterTest.php +++ b/tests/framework/widgets/LinkSorterTest.php @@ -47,8 +47,10 @@ class LinkSorterTest extends DatabaseTestCase ]); $actualHtml = ob_get_clean(); - $this->assertTrue(strpos($actualHtml, 'Customer') !== false); - $this->assertTrue(strpos($actualHtml, 'Invoice Total') !== false); + $this->assertNotFalse(strpos($actualHtml, + 'Customer')); + $this->assertNotFalse(strpos($actualHtml, + 'Invoice Total')); } public function testLabelsExplicit() @@ -70,8 +72,10 @@ class LinkSorterTest extends DatabaseTestCase ]); $actualHtml = ob_get_clean(); - $this->assertFalse(strpos($actualHtml, 'Customer') !== false); - $this->assertTrue(strpos($actualHtml, 'Invoice Total') !== false); + $this->assertFalse(strpos($actualHtml, + 'Customer')); + $this->assertNotFalse(strpos($actualHtml, + 'Invoice Total')); } } From 18b3fa10c53896b337a9b7584b29898a72002ef9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 14 Mar 2017 09:47:56 +0300 Subject: [PATCH 046/184] Fixed ICU tests take varieties of data into account (#13751) --- tests/framework/helpers/InflectorTest.php | 16 ++++++++++++---- tests/framework/i18n/FormatterNumberTest.php | 12 ++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/framework/helpers/InflectorTest.php b/tests/framework/helpers/InflectorTest.php index 713b3c7..8d6c345 100644 --- a/tests/framework/helpers/InflectorTest.php +++ b/tests/framework/helpers/InflectorTest.php @@ -234,6 +234,14 @@ class InflectorTest extends TestCase } } + /** + * @return boolean if ICU version is >= 57.1 + */ + private function hasNewICUData() + { + return version_compare(INTL_ICU_DATA_VERSION, '57.1', '>='); + } + public function testTransliterateMedium() { if (!extension_loaded('intl')) { @@ -252,12 +260,12 @@ class InflectorTest extends TestCase 'العربي' => 'alʿrby', 'عرب' => 'ʿrb', // Hebrew - 'עִבְרִית' => version_compare(INTL_ICU_DATA_VERSION, '57.1', '>=') ? '\'iberiyt' : 'ʻiberiyt', + 'עִבְרִית' => $this->hasNewICUData() ? '\'iberiyt' : 'ʻiberiyt', // Turkish 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', // Russian - 'недвижимость' => 'nedvizimostʹ', + 'недвижимость' => $this->hasNewICUData() ? 'nedvizimost\'' : 'nedvizimostʹ', 'Контакты' => 'Kontakty', // Ukrainian @@ -295,12 +303,12 @@ class InflectorTest extends TestCase 'العربي' => 'alrby', 'عرب' => 'rb', // Hebrew - 'עִבְרִית' => version_compare(INTL_ICU_DATA_VERSION, '57.1', '>=') ? '\'iberiyt' : 'iberiyt', + 'עִבְרִית' => $this->hasNewICUData() ? '\'iberiyt' : 'iberiyt', // Turkish 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', // Russian - 'недвижимость' => 'nedvizimost', + 'недвижимость' => $this->hasNewICUData() ? 'nedvizimost\'' : 'nedvizimost', 'Контакты' => 'Kontakty', // Ukrainian diff --git a/tests/framework/i18n/FormatterNumberTest.php b/tests/framework/i18n/FormatterNumberTest.php index e5e5d55..9cbc936 100644 --- a/tests/framework/i18n/FormatterNumberTest.php +++ b/tests/framework/i18n/FormatterNumberTest.php @@ -264,7 +264,7 @@ class FormatterNumberTest extends TestCase // default russian currency symbol $this->formatter->locale = 'ru-RU'; $this->formatter->currencyCode = null; - if (version_compare(INTL_ICU_DATA_VERSION, '57.1', '>=')) { + if ($this->hasNewICUData()) { $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); } else { $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); @@ -342,7 +342,7 @@ class FormatterNumberTest extends TestCase $this->formatter->locale = 'ru-RU'; $this->formatter->currencyCode = null; - if (version_compare(INTL_ICU_DATA_VERSION, '57.1', '>=')) { + if ($this->hasNewICUData()) { $this->assertSame('123 ₽', $this->formatter->asCurrency('123')); } else { $this->assertSame('123 руб.', $this->formatter->asCurrency('123')); @@ -673,4 +673,12 @@ class FormatterNumberTest extends TestCase $this->assertSame("1023 bytes", $this->formatter->asSize(1023)); $this->assertSame("1023 B", $this->formatter->asShortSize(1023)); } + + /** + * @return boolean if ICU version is >= 57.1 + */ + private function hasNewICUData() + { + return version_compare(INTL_ICU_DATA_VERSION, '57.1', '>='); + } } From 06967f90187ae4f6cb3c7e6e5470b1bd4105257b Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 14 Mar 2017 13:37:54 +0100 Subject: [PATCH 047/184] Fixes #13254: Core validators no longer require Yii::$app to be set --- framework/CHANGELOG.md | 1 + framework/validators/BooleanValidator.php | 4 ++-- framework/validators/CompareValidator.php | 4 ++-- framework/validators/DateValidator.php | 2 +- framework/validators/EmailValidator.php | 4 ++-- framework/validators/FileValidator.php | 28 +++++++++++----------- framework/validators/ImageValidator.php | 20 ++++++++-------- framework/validators/IpValidator.php | 2 +- framework/validators/NumberValidator.php | 12 +++++----- framework/validators/RangeValidator.php | 4 ++-- .../validators/RegularExpressionValidator.php | 4 ++-- framework/validators/RequiredValidator.php | 8 +++---- framework/validators/StringValidator.php | 16 ++++++------- framework/validators/UrlValidator.php | 4 ++-- framework/validators/Validator.php | 25 +++++++++++++++++-- 15 files changed, 80 insertions(+), 58 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6195de6..7b7de64 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -45,6 +45,7 @@ Yii Framework 2 Change Log - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) +- Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) 2.0.11.2 February 08, 2017 diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php index cff9fc9..b5c8c1b 100644 --- a/framework/validators/BooleanValidator.php +++ b/framework/validators/BooleanValidator.php @@ -84,11 +84,11 @@ class BooleanValidator extends Validator $options = [ 'trueValue' => $this->trueValue, 'falseValue' => $this->falseValue, - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), 'true' => $this->trueValue === true ? 'true' : $this->trueValue, 'false' => $this->falseValue === false ? 'false' : $this->falseValue, - ], Yii::$app->language), + ]), ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php index c9284f0..de9d707 100644 --- a/framework/validators/CompareValidator.php +++ b/framework/validators/CompareValidator.php @@ -255,12 +255,12 @@ class CompareValidator extends Validator $options['skipOnEmpty'] = 1; } - $options['message'] = Yii::$app->getI18n()->format($this->message, [ + $options['message'] = $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), 'compareAttribute' => $compareLabel, 'compareValue' => $compareValue, 'compareValueOrAttribute' => $compareValueOrAttribute, - ], Yii::$app->language); + ]); return $options; } diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php index 53e605f..a6cdeea 100644 --- a/framework/validators/DateValidator.php +++ b/framework/validators/DateValidator.php @@ -139,7 +139,7 @@ class DateValidator extends Validator * * If not set, [[timestampAttribute]] will receive a UNIX timestamp. * If [[timestampAttribute]] is not set, this property will be ignored. - * @see format + * @see formatMessage * @see timestampAttribute * @since 2.0.4 */ diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php index 9262b56..bf72754 100644 --- a/framework/validators/EmailValidator.php +++ b/framework/validators/EmailValidator.php @@ -127,9 +127,9 @@ class EmailValidator extends Validator 'pattern' => new JsExpression($this->pattern), 'fullPattern' => new JsExpression($this->fullPattern), 'allowName' => $this->allowName, - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language), + ]), 'enableIDN' => (bool)$this->enableIDN, ]; if ($this->skipOnEmpty) { diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index 64774b3..8018360 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -392,17 +392,17 @@ class FileValidator extends Validator $options = []; if ($this->message !== null) { - $options['message'] = Yii::$app->getI18n()->format($this->message, [ + $options['message'] = $this->formatMessage($this->message, [ 'attribute' => $label, - ], Yii::$app->language); + ]); } $options['skipOnEmpty'] = $this->skipOnEmpty; if (!$this->skipOnEmpty) { - $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [ + $options['uploadRequired'] = $this->formatMessage($this->uploadRequired, [ 'attribute' => $label, - ], Yii::$app->language); + ]); } if ($this->mimeTypes !== null) { @@ -411,44 +411,44 @@ class FileValidator extends Validator $mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($this->buildMimeTypeRegexp($mimeType))); } $options['mimeTypes'] = $mimeTypes; - $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ + $options['wrongMimeType'] = $this->formatMessage($this->wrongMimeType, [ 'attribute' => $label, 'mimeTypes' => implode(', ', $this->mimeTypes), - ], Yii::$app->language); + ]); } if ($this->extensions !== null) { $options['extensions'] = $this->extensions; - $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ + $options['wrongExtension'] = $this->formatMessage($this->wrongExtension, [ 'attribute' => $label, 'extensions' => implode(', ', $this->extensions), - ], Yii::$app->language); + ]); } if ($this->minSize !== null) { $options['minSize'] = $this->minSize; - $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + $options['tooSmall'] = $this->formatMessage($this->tooSmall, [ 'attribute' => $label, 'limit' => $this->minSize, 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), - ], Yii::$app->language); + ]); } if ($this->maxSize !== null) { $options['maxSize'] = $this->maxSize; - $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + $options['tooBig'] = $this->formatMessage($this->tooBig, [ 'attribute' => $label, 'limit' => $this->getSizeLimit(), 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), - ], Yii::$app->language); + ]); } if ($this->maxFiles !== null) { $options['maxFiles'] = $this->maxFiles; - $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ + $options['tooMany'] = $this->formatMessage($this->tooMany, [ 'attribute' => $label, 'limit' => $this->maxFiles, - ], Yii::$app->language); + ]); } return $options; diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php index 6d72812..f9067fa 100644 --- a/framework/validators/ImageValidator.php +++ b/framework/validators/ImageValidator.php @@ -179,41 +179,41 @@ class ImageValidator extends FileValidator $label = $model->getAttributeLabel($attribute); if ($this->notImage !== null) { - $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ + $options['notImage'] = $this->formatMessage($this->notImage, [ 'attribute' => $label, - ], Yii::$app->language); + ]); } if ($this->minWidth !== null) { $options['minWidth'] = $this->minWidth; - $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ + $options['underWidth'] = $this->formatMessage($this->underWidth, [ 'attribute' => $label, 'limit' => $this->minWidth, - ], Yii::$app->language); + ]); } if ($this->maxWidth !== null) { $options['maxWidth'] = $this->maxWidth; - $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ + $options['overWidth'] = $this->formatMessage($this->overWidth, [ 'attribute' => $label, 'limit' => $this->maxWidth, - ], Yii::$app->language); + ]); } if ($this->minHeight !== null) { $options['minHeight'] = $this->minHeight; - $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ + $options['underHeight'] = $this->formatMessage($this->underHeight, [ 'attribute' => $label, 'limit' => $this->minHeight, - ], Yii::$app->language); + ]); } if ($this->maxHeight !== null) { $options['maxHeight'] = $this->maxHeight; - $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ + $options['overHeight'] = $this->formatMessage($this->overHeight, [ 'attribute' => $label, 'limit' => $this->maxHeight, - ], Yii::$app->language); + ]); } return $options; diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php index 6533e75..b257695 100644 --- a/framework/validators/IpValidator.php +++ b/framework/validators/IpValidator.php @@ -606,7 +606,7 @@ class IpValidator extends Validator 'hasSubnet' => $this->hasSubnet, ]; foreach ($messages as &$message) { - $message = Yii::$app->getI18n()->format($message, [ + $message = $this->formatMessage($message, [ 'attribute' => $model->getAttributeLabel($attribute), ], Yii::$app->language); } diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index 01bbba3..d5b4fb9 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -138,28 +138,28 @@ class NumberValidator extends Validator $options = [ 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern), - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $label, - ], Yii::$app->language), + ]), ]; if ($this->min !== null) { // ensure numeric value to make javascript comparison equal to PHP comparison // https://github.com/yiisoft/yii2/issues/3118 $options['min'] = is_string($this->min) ? (float) $this->min : $this->min; - $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + $options['tooSmall'] = $this->formatMessage($this->tooSmall, [ 'attribute' => $label, 'min' => $this->min, - ], Yii::$app->language); + ]); } if ($this->max !== null) { // ensure numeric value to make javascript comparison equal to PHP comparison // https://github.com/yiisoft/yii2/issues/3118 $options['max'] = is_string($this->max) ? (float) $this->max : $this->max; - $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + $options['tooBig'] = $this->formatMessage($this->tooBig, [ 'attribute' => $label, 'max' => $this->max, - ], Yii::$app->language); + ]); } if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php index 63be6ea..bbc344a 100644 --- a/framework/validators/RangeValidator.php +++ b/framework/validators/RangeValidator.php @@ -126,9 +126,9 @@ class RangeValidator extends Validator $options = [ 'range' => $range, 'not' => $this->not, - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language), + ]), ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php index 60130e2..7f17432 100644 --- a/framework/validators/RegularExpressionValidator.php +++ b/framework/validators/RegularExpressionValidator.php @@ -81,9 +81,9 @@ class RegularExpressionValidator extends Validator $options = [ 'pattern' => new JsExpression($pattern), 'not' => $this->not, - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language), + ]), ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php index 21966c5..bba8a15 100644 --- a/framework/validators/RequiredValidator.php +++ b/framework/validators/RequiredValidator.php @@ -101,9 +101,9 @@ class RequiredValidator extends Validator { $options = []; if ($this->requiredValue !== null) { - $options['message'] = Yii::$app->getI18n()->format($this->message, [ + $options['message'] = $this->formatMessage($this->message, [ 'requiredValue' => $this->requiredValue, - ], Yii::$app->language); + ]); $options['requiredValue'] = $this->requiredValue; } else { $options['message'] = $this->message; @@ -112,9 +112,9 @@ class RequiredValidator extends Validator $options['strict'] = 1; } - $options['message'] = Yii::$app->getI18n()->format($options['message'], [ + $options['message'] = $this->formatMessage($options['message'], [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language); + ]); return $options; } diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php index b41ae55..5a0fc3a 100644 --- a/framework/validators/StringValidator.php +++ b/framework/validators/StringValidator.php @@ -164,31 +164,31 @@ class StringValidator extends Validator $label = $model->getAttributeLabel($attribute); $options = [ - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $label, - ], Yii::$app->language), + ]), ]; if ($this->min !== null) { $options['min'] = $this->min; - $options['tooShort'] = Yii::$app->getI18n()->format($this->tooShort, [ + $options['tooShort'] = $this->formatMessage($this->tooShort, [ 'attribute' => $label, 'min' => $this->min, - ], Yii::$app->language); + ]); } if ($this->max !== null) { $options['max'] = $this->max; - $options['tooLong'] = Yii::$app->getI18n()->format($this->tooLong, [ + $options['tooLong'] = $this->formatMessage($this->tooLong, [ 'attribute' => $label, 'max' => $this->max, - ], Yii::$app->language); + ]); } if ($this->length !== null) { $options['is'] = $this->length; - $options['notEqual'] = Yii::$app->getI18n()->format($this->notEqual, [ + $options['notEqual'] = $this->formatMessage($this->notEqual, [ 'attribute' => $label, 'length' => $this->length, - ], Yii::$app->language); + ]); } if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php index 8f6ef5b..0c2151e 100644 --- a/framework/validators/UrlValidator.php +++ b/framework/validators/UrlValidator.php @@ -135,9 +135,9 @@ class UrlValidator extends Validator $options = [ 'pattern' => new JsExpression($pattern), - 'message' => Yii::$app->getI18n()->format($this->message, [ + 'message' => $this->formatMessage($this->message, [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language), + ]), 'enableIDN' => (bool) $this->enableIDN, ]; if ($this->skipOnEmpty) { diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index 99f83a5..f654380 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -310,7 +310,7 @@ class Validator extends Component } else { $params['value'] = $value; } - $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language); + $error = $this->formatMessage($message, $params); return false; } @@ -417,7 +417,7 @@ class Validator extends Component $params['value'] = $value; } } - $model->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language)); + $model->addError($attribute, $this->formatMessage($message, $params)); } /** @@ -437,6 +437,27 @@ class Validator extends Component } /** + * Formats a mesage using the I18N, or simple strtr if `\Yii::$app` is not available. + * @param string $message + * @param array $params + * @since 2.0.12 + * @return string + */ + protected function formatMessage($message, $params) + { + if (Yii::$app !== null) { + return \Yii::$app->getI18n()->format($message, $params, Yii::$app->language); + } + + $placeholders = []; + foreach ((array) $params as $name => $value) { + $placeholders['{' . $name . '}'] = $value; + } + + return ($placeholders === []) ? $message : strtr($message, $placeholders); + } + + /** * Returns cleaned attribute names without the `!` character at the beginning * @return array * @since 2.0.12 From 35f7c61f776a5dd53dd5c77114df9391e121f386 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 15 Mar 2017 10:15:13 +0300 Subject: [PATCH 048/184] Added tip about preferring separate form models for any non-trivial cases [skip ci] --- docs/guide/input-forms.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guide/input-forms.md b/docs/guide/input-forms.md index 22bb673..f14413a 100644 --- a/docs/guide/input-forms.md +++ b/docs/guide/input-forms.md @@ -12,6 +12,10 @@ to validate its input on the server-side (Check the [Validating Input](input-val When creating model-based forms, the first step is to define the model itself. The model can be either based upon an [Active Record](db-active-record.md) class, representing some data from the database, or a generic Model class (extending from [[yii\base\Model]]) to capture arbitrary input, for example a login form. + +> Tip: If the form fields are different from database columns or there are formatting and logic that is specific to that +> form only, prefer creating a separate model extended from [[yii\base\Model]]. + In the following example, we show how a generic model can be used for a login form: ```php From 8192f84850e5a6dcd3aa4b518568598dd06a42a0 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 15 Mar 2017 13:04:37 +0100 Subject: [PATCH 049/184] Fix SQLite resetSequence inconsistencies (#13755, #13630) --- framework/db/sqlite/QueryBuilder.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 0be9bc8..3cdb28a 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -137,20 +137,17 @@ class QueryBuilder extends \yii\db\QueryBuilder $db = $this->db; $table = $db->getTableSchema($tableName); if ($table !== null && $table->sequenceName !== null) { + $tableName = $db->quoteTableName($tableName); if ($value === null) { - $key = reset($table->primaryKey); - $tableName = $db->quoteTableName($tableName); + $key = $this->db->quoteColumnName(reset($table->primaryKey)); $value = $this->db->useMaster(function (Connection $db) use ($key, $tableName) { return $db->createCommand("SELECT MAX($key) FROM $tableName")->queryScalar(); }); } else { $value = (int) $value - 1; } - try { - return "UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'"; - } catch (Exception $e) { - // it's possible that sqlite_sequence does not exist - } + + return "UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'"; } elseif ($table === null) { throw new InvalidParamException("Table not found: $tableName"); } else { From 9459eaa277d56baac3300bfb22ea2f669abf9b0d Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 15 Mar 2017 13:47:21 +0100 Subject: [PATCH 050/184] Fixes #10372: Fixed console controller including complex typed arguments in help --- framework/CHANGELOG.md | 2 +- framework/console/Controller.php | 3 +++ tests/framework/console/ControllerTest.php | 14 ++++++++++++++ tests/framework/console/FakeController.php | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 7b7de64..51a9a53 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -46,7 +46,7 @@ Yii Framework 2 Change Log - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) - Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) - +- Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/console/Controller.php b/framework/console/Controller.php index 2225e02..7cd4999 100644 --- a/framework/console/Controller.php +++ b/framework/console/Controller.php @@ -467,6 +467,9 @@ class Controller extends \yii\base\Controller /** @var \ReflectionParameter $reflection */ foreach ($method->getParameters() as $i => $reflection) { + if ($reflection->getClass() !== null) { + continue; + } $name = $reflection->getName(); $tag = isset($params[$i]) ? $params[$i] : ''; if (preg_match('/^(\S+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) { diff --git a/tests/framework/console/ControllerTest.php b/tests/framework/console/ControllerTest.php index 00343f7..041bc84 100644 --- a/tests/framework/console/ControllerTest.php +++ b/tests/framework/console/ControllerTest.php @@ -132,4 +132,18 @@ class ControllerTest extends TestCase $this->assertFalse(FakeController::getWasActionIndexCalled()); $this->assertEquals(FakeHelpController::getActionIndexLastCallParams(), ['news/posts/index']); } + + + /** + * Tests if action help does not include (class) type hinted arguments. + * @see #10372 + */ + public function testHelpSkipsTypeHintedArguments() + { + $controller = new FakeController('fake', Yii::$app); + $help = $controller->getActionArgsHelp($controller->createAction('with-complex-type-hint')); + + $this->assertArrayNotHasKey('typedArgument', $help); + $this->assertArrayHasKey('simpleArgument', $help); + } } diff --git a/tests/framework/console/FakeController.php b/tests/framework/console/FakeController.php index 8f00b2e..2fc6ab1 100644 --- a/tests/framework/console/FakeController.php +++ b/tests/framework/console/FakeController.php @@ -84,6 +84,10 @@ class FakeController extends Controller return $this->testArray; } + public function actionWithComplexTypeHint(self $typedArgument, $simpleArgument) { + return $simpleArgument; + } + public function actionStatus($status = 0) { return $status; From b238afaceea1a4798a5b653a466b4b8929555568 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 15 Mar 2017 14:19:06 +0100 Subject: [PATCH 051/184] Fixes #4408: Add support for unicode word characters and `+` character in attribute names --- framework/CHANGELOG.md | 1 + framework/helpers/BaseHtml.php | 12 +++++-- tests/framework/helpers/HtmlTest.php | 66 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 51a9a53..016a378 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -46,6 +46,7 @@ Yii Framework 2 Change Log - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) - Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) +- Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) 2.0.11.2 February 08, 2017 diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 4d41821..0b1d092 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -24,6 +24,12 @@ use yii\base\Model; */ class BaseHtml { + + /** + * @var string Regular expression used for attribute name validation. + * @since 2.0.12 + */ + public static $attributeRegex = '/(^|.*\])([\w\.\+]+)(\[.*|$)/u'; /** * @var array list of void elements (element name => 1) * @see http://www.w3.org/TR/html-markup/syntax.html#void-element @@ -2073,7 +2079,7 @@ class BaseHtml */ public static function getAttributeName($attribute) { - if (preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + if (preg_match(static::$attributeRegex, $attribute, $matches)) { return $matches[2]; } else { throw new InvalidParamException('Attribute name must contain word characters only.'); @@ -2096,7 +2102,7 @@ class BaseHtml */ public static function getAttributeValue($model, $attribute) { - if (!preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + if (!preg_match(static::$attributeRegex, $attribute, $matches)) { throw new InvalidParamException('Attribute name must contain word characters only.'); } $attribute = $matches[2]; @@ -2146,7 +2152,7 @@ class BaseHtml public static function getInputName($model, $attribute) { $formName = $model->formName(); - if (!preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + if (!preg_match(static::$attributeRegex, $attribute, $matches)) { throw new InvalidParamException('Attribute name must contain word characters only.'); } $prefix = $matches[1]; diff --git a/tests/framework/helpers/HtmlTest.php b/tests/framework/helpers/HtmlTest.php index 3377d13..443f416 100644 --- a/tests/framework/helpers/HtmlTest.php +++ b/tests/framework/helpers/HtmlTest.php @@ -1260,6 +1260,72 @@ EOD; $model->checkbox = $value; $this->assertEquals($expectedHtml, Html::activeCheckbox($model, 'checkbox', $options)); } + + /** + * Data provider for [[testAttributeNameValidation()]] + * @return array test data + */ + public function validAttributeNamesProvider() + { + return [ + ["asd]asdf.asdfa[asdfa", "asdf.asdfa"], + ["a", "a"], + ["[0]a", "a"], + ["a[0]", "a"], + ["[0]a[0]", "a"], + ["[0]a.[0]", "a."], + + // Unicode checks. + ["ä", "ä"], + ["ä", "ä"], + ["asdf]öáöio..[asdfasdf", "öáöio.."], + ["öáöio", "öáöio"], + ["[0]test.ööößß.d", "test.ööößß.d"], + ["ИІК", "ИІК"], + ["]ИІК[", "ИІК"], + ["[0]ИІК[0]", "ИІК"] + ]; + } + + /** + * Data provider for [[testAttributeNameValidation()]] + * @return array test data + */ + public function invalidAttributeNamesProvider() + { + return [ + ['. ..'], + ['a +b'], + ['a,b'] + ]; + } + + /** + * @dataProvider validAttributeNamesProvider + * + * @param string $name + * @param string $expected + */ + public function testAttributeNameValidation($name, $expected) + { + if (!isset($expected)) { + $this->setExpectedException('yii\base\InvalidParamException'); + Html::getAttributeName($name); + } else { + $this->assertEquals($expected, Html::getAttributeName($name)); + } + } + + /** + * @dataProvider invalidAttributeNamesProvider + * + * @param string $name + */ + public function testAttributeNameException($name) + { + $this->setExpectedException('yii\base\InvalidParamException'); + Html::getAttributeName($name); + } } /** From bc59d5da850f85adf0a79e5df6cf08befbb95536 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 16 Mar 2017 12:03:23 +0300 Subject: [PATCH 052/184] Fixes #13707: Fixed `\yii\web\ErrorHandler` and `\yii\web\ErrorAction` not setting correct response code to response object before rendering error view --- framework/CHANGELOG.md | 1 + framework/web/ErrorAction.php | 2 ++ framework/web/ErrorHandler.php | 19 +++++++++----- framework/web/Response.php | 18 +++++++++++++ tests/data/views/error.php | 2 ++ tests/data/views/errorHandler.php | 12 +++++++++ tests/framework/web/ErrorActionTest.php | 24 ++++++++++------- tests/framework/web/ErrorHandlerTest.php | 45 ++++++++++++++++++++++++++++++++ 8 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 tests/data/views/errorHandler.php create mode 100644 tests/framework/web/ErrorHandlerTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 016a378..b4ae607 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -44,6 +44,7 @@ Yii Framework 2 Change Log - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) +- Bug #13707: Fixed `\yii\web\ErrorHandler` and `\yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) - Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) diff --git a/framework/web/ErrorAction.php b/framework/web/ErrorAction.php index ff3d5fa..8709a10 100644 --- a/framework/web/ErrorAction.php +++ b/framework/web/ErrorAction.php @@ -98,6 +98,8 @@ class ErrorAction extends Action */ public function run() { + Yii::$app->getResponse()->setStatusCodeByException($this->exception); + if (Yii::$app->getRequest()->getIsAjax()) { return $this->renderAjaxResponse(); } diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index dfeca72..d7192c8 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -89,6 +89,8 @@ class ErrorHandler extends \yii\base\ErrorHandler $response = new Response(); } + $response->setStatusCodeByException($exception); + $useErrorView = $response->format === Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException); if ($useErrorView && $this->errorAction !== null) { @@ -99,7 +101,7 @@ class ErrorHandler extends \yii\base\ErrorHandler $response->data = $result; } } elseif ($response->format === Response::FORMAT_HTML) { - if (YII_ENV_TEST || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { + if ($this->shouldRenderSimpleHtml()) { // AJAX request $response->data = '
    ' . $this->htmlEncode(static::convertExceptionToString($exception)) . '
    '; } else { @@ -119,12 +121,6 @@ class ErrorHandler extends \yii\base\ErrorHandler $response->data = $this->convertExceptionToArray($exception); } - if ($exception instanceof HttpException) { - $response->setStatusCode($exception->statusCode); - } else { - $response->setStatusCode(500); - } - $response->send(); } @@ -475,4 +471,13 @@ class ErrorHandler extends \yii\base\ErrorHandler } return null; } + + /** + * @return bool if simple HTML should be rendered + * @since 2.0.12 + */ + protected function shouldRenderSimpleHtml() + { + return YII_ENV_TEST || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'; + } } diff --git a/framework/web/Response.php b/framework/web/Response.php index 723d8c7..9e1eca9 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -291,6 +291,24 @@ class Response extends \yii\base\Response } /** + * Sets the response status code based on the exception. + * @param \Exception $e + * @throws InvalidParamException if the status code is invalid. + * @return $this the response object itself + * + * @since 2.0.12 + */ + public function setStatusCodeByException(\Exception $e) + { + if ($e instanceof HttpException) { + $this->setStatusCode($e->statusCode); + } else { + $this->setStatusCode(500); + } + return $this; + } + + /** * Returns the header collection. * The header collection contains the currently registered HTTP headers. * @return HeaderCollection the header collection diff --git a/tests/data/views/error.php b/tests/data/views/error.php index 91c4ddc..df44ed3 100644 --- a/tests/data/views/error.php +++ b/tests/data/views/error.php @@ -9,6 +9,8 @@ ?> Name: +Code: response->statusCode ?> + Message: Exception: diff --git a/tests/data/views/errorHandler.php b/tests/data/views/errorHandler.php new file mode 100644 index 0000000..79e5566 --- /dev/null +++ b/tests/data/views/errorHandler.php @@ -0,0 +1,12 @@ + +Code: response->statusCode ?> + +Message: getMessage() ?> + +Exception: \ No newline at end of file diff --git a/tests/framework/web/ErrorActionTest.php b/tests/framework/web/ErrorActionTest.php index 3ec4337..10d7368 100644 --- a/tests/framework/web/ErrorActionTest.php +++ b/tests/framework/web/ErrorActionTest.php @@ -7,7 +7,6 @@ use yii\base\InvalidConfigException; use yii\base\UserException; use yii\web\Controller; use yii\web\ErrorAction; -use yii\web\Request; use yiiunit\TestCase; /** @@ -18,16 +17,15 @@ class ErrorActionTest extends TestCase protected function setUp() { parent::setUp(); - - $this->mockApplication([ - 'components' => [ - 'request' => [ - 'class' => Request::className(), - ], - ], - ]); + $this->mockWebApplication(); } + /** + * Creates a controller instance + * + * @param array $actionConfig + * @return TestController + */ public function getController($actionConfig = []) { return new TestController('test', Yii::$app, ['layout' => false, 'actionConfig' => $actionConfig]); @@ -38,6 +36,7 @@ class ErrorActionTest extends TestCase Yii::$app->getErrorHandler()->exception = new InvalidConfigException('This message will not be shown to the user'); $this->assertEquals('Name: Invalid Configuration +Code: 500 Message: An internal server error occurred. Exception: yii\base\InvalidConfigException', $this->getController()->runAction('error')); } @@ -47,6 +46,7 @@ Exception: yii\base\InvalidConfigException', $this->getController()->runAction(' Yii::$app->getErrorHandler()->exception = new UserException('User can see this error message'); $this->assertEquals('Name: Exception +Code: 500 Message: User can see this error message Exception: yii\base\UserException', $this->getController()->runAction('error')); } @@ -63,6 +63,7 @@ Exception: yii\base\UserException', $this->getController()->runAction('error')); Yii::$app->getErrorHandler()->exception = new \InvalidArgumentException('This message will not be shown to the user'); $this->assertEquals('Name: Error +Code: 500 Message: An internal server error occurred. Exception: InvalidArgumentException', $this->getController()->runAction('error')); } @@ -77,6 +78,7 @@ Exception: InvalidArgumentException', $this->getController()->runAction('error') ]); $this->assertEquals('Name: Oops... +Code: 500 Message: The system is drunk Exception: InvalidArgumentException', $controller->runAction('error')); } @@ -84,6 +86,7 @@ Exception: InvalidArgumentException', $controller->runAction('error')); public function testNoExceptionInHandler() { $this->assertEquals('Name: Not Found (#404) +Code: 404 Message: Page not found. Exception: yii\web\NotFoundHttpException', $this->getController()->runAction('error')); } @@ -95,7 +98,8 @@ Exception: yii\web\NotFoundHttpException', $this->getController()->runAction('er // Unset view name. Class should try to load view that matches action name by default $action->view = null; - $this->setExpectedExceptionRegExp('yii\base\ViewNotFoundException', '#The view file does not exist: .*?views/test/error.php#'); + $ds = preg_quote(DIRECTORY_SEPARATOR, '\\'); + $this->setExpectedExceptionRegExp('yii\base\ViewNotFoundException', '#The view file does not exist: .*?views' . $ds . 'test' . $ds . 'error.php#'); $this->invokeMethod($action, 'renderHtmlResponse'); } diff --git a/tests/framework/web/ErrorHandlerTest.php b/tests/framework/web/ErrorHandlerTest.php new file mode 100644 index 0000000..6054c98 --- /dev/null +++ b/tests/framework/web/ErrorHandlerTest.php @@ -0,0 +1,45 @@ +mockWebApplication([ + 'components' => [ + 'errorHandler' => [ + 'class' => 'yiiunit\framework\web\ErrorHandler', + 'errorView' => '@yiiunit/data/views/errorHandler.php', + ], + ], + ]); + } + + public function testCorrectResponseCodeInErrorView() + { + /** @var ErrorHandler $handler */ + $handler = Yii::$app->getErrorHandler(); + $this->invokeMethod($handler, 'renderException', [new NotFoundHttpException('This message is displayed to end user')]); + $out = Yii::$app->response->data; + $this->assertEquals('Code: 404 +Message: This message is displayed to end user +Exception: yii\web\NotFoundHttpException', $out); + } +} + +class ErrorHandler extends \yii\web\ErrorHandler +{ + /** + * @return bool if simple HTML should be rendered + */ + protected function shouldRenderSimpleHtml() + { + return false; + } +} \ No newline at end of file From b00cd65ef3f16f40baf83d5649a54cd5c8fe8854 Mon Sep 17 00:00:00 2001 From: Vladislav Lyshenko Date: Thu, 16 Mar 2017 13:15:05 +0200 Subject: [PATCH 053/184] Fixes #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters --- framework/CHANGELOG.md | 1 + framework/assets/yii.js | 6 ++++-- tests/js/tests/yii.gridView.test.js | 6 ++++++ tests/js/tests/yii.test.js | 2 ++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b4ae607..c231784 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -49,6 +49,7 @@ Yii Framework 2 Change Log - Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) +- Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/assets/yii.js b/framework/assets/yii.js index 8873069..f561663 100644 --- a/framework/assets/yii.js +++ b/framework/assets/yii.js @@ -273,8 +273,10 @@ window.yii = (function ($) { return {}; } - var pairs = url.substring(pos + 1).split('#')[0].split('&'), - params = {}; + var pairs = $.grep(url.substring(pos + 1).split('#')[0].split('&'), function (value) { + return value !== ''; + }); + var params = {}; for (var i = 0, len = pairs.length; i < len; i++) { var pair = pairs[i].split('='); diff --git a/tests/js/tests/yii.gridView.test.js b/tests/js/tests/yii.gridView.test.js index d166d33..f5e1711 100644 --- a/tests/js/tests/yii.gridView.test.js +++ b/tests/js/tests/yii.gridView.test.js @@ -303,6 +303,12 @@ describe('yii.gridView', function () { describe('with different urls', function () { describe('with no filter data sent', function () { withData({ + // https://github.com/yiisoft/yii2/issues/13738 + 'question mark, no query parameters': [ + '/posts/index?', + '/posts/index', + 'PostSearch[name]=&PostSearch[category_id]=' + ], 'query parameters': [ '/posts/index?foo=1&bar=2', '/posts/index', diff --git a/tests/js/tests/yii.test.js b/tests/js/tests/yii.test.js index bf7da4d..07aae73 100644 --- a/tests/js/tests/yii.test.js +++ b/tests/js/tests/yii.test.js @@ -739,6 +739,8 @@ describe('yii', function () { describe('getQueryParams method', function () { withData({ 'no query parameters': ['/posts/index', {}], + // https://github.com/yiisoft/yii2/issues/13738 + 'question mark, no query parameters': ['/posts/index?', {}], 'query parameters': ['/posts/index?foo=1&bar=2', {foo: '1', bar: '2'}], 'query parameter with multiple values (not array)': ['/posts/index?foo=1&foo=2', {'foo': ['1', '2']}], 'query parameter with multiple values (array)': ['/posts/index?foo[]=1&foo[]=2', {'foo[]': ['1', '2']}], From 3c1f3e20cfa260d1484fece29b255103b3bb6f5e Mon Sep 17 00:00:00 2001 From: vladis84 Date: Tue, 14 Mar 2017 20:37:52 +0500 Subject: [PATCH 054/184] Fixes #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name --- framework/CHANGELOG.md | 1 + framework/validators/UniqueValidator.php | 28 +++++++++++++++++++--- tests/framework/validators/UniqueValidatorTest.php | 26 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c231784..dcc1149 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -50,6 +50,7 @@ Yii Framework 2 Change Log - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) +- Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 0a7c868..7446484 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -10,9 +10,9 @@ namespace yii\validators; use Yii; use yii\base\Model; use yii\db\ActiveQuery; +use yii\db\ActiveRecord; use yii\db\ActiveQueryInterface; use yii\db\ActiveRecordInterface; -use yii\db\Query; use yii\helpers\Inflector; /** @@ -119,7 +119,7 @@ class UniqueValidator extends Validator public function validateAttribute($model, $attribute) { /* @var $targetClass ActiveRecordInterface */ - $targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass; + $targetClass = $this->getTargetClass($model); $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; $rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute); $conditions[] = $this->targetAttributeJunction === 'or' ? 'or' : 'and'; @@ -142,6 +142,15 @@ class UniqueValidator extends Validator } /** + * @param Model $model the data model to be validated + * @return string Target class name + */ + private function getTargetClass($model) + { + return $this->targetClass === null ? get_class($model) : $this->targetClass; + } + + /** * Checks whether the $model exists in the database. * * @param string $targetClass the name of the ActiveRecord class that should be used to validate the uniqueness @@ -234,7 +243,20 @@ class UniqueValidator extends Validator $conditions = [$targetAttribute => $model->$attribute]; } - return $conditions; + if (!$model instanceof ActiveRecord) { + return $conditions; + } + + // Add table prefix for column + $targetClass = $this->getTargetClass($model); + $tableName = $targetClass::tableName(); + $conditionsWithTableName = []; + foreach ($conditions as $columnName => $columnValue) { + $prefixedColumnName = "{$tableName}.$columnName"; + $conditionsWithTableName[$prefixedColumnName] = $columnValue; + } + + return $conditionsWithTableName; } /** diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index 6d25d60..c77369d 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -331,6 +331,32 @@ abstract class UniqueValidatorTest extends DatabaseTestCase $result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]); $expected = ['val_attr_b' => 'test value b', 'val_attr_c' => 'test value a']; $this->assertEquals($expected, $result); + + // Add table prefix for column name + $model = Profile::findOne(1); + $attribute = 'id'; + $targetAttribute = 'id'; + $result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]); + $expected = [Profile::tableName() . '.' . $attribute => $model->id]; + $this->assertEquals($expected, $result); + } + + public function testGetTargetClassWithFilledTargetClassProperty() + { + $validator = new UniqueValidator(['targetClass' => Profile::className()]); + $model = new FakedValidationModel(); + $actualTargetClass = $this->invokeMethod($validator, 'getTargetClass', [$model]); + + $this->assertEquals(Profile::className(), $actualTargetClass); + } + + public function testGetTargetClassWithNotFilledTargetClassProperty() + { + $validator = new UniqueValidator(); + $model = new FakedValidationModel(); + $actualTargetClass = $this->invokeMethod($validator, 'getTargetClass', [$model]); + + $this->assertEquals(FakedValidationModel::className(), $actualTargetClass); } public function testPrepareQuery() From 1b322f519f76ad927490ab911d564493e128d11c Mon Sep 17 00:00:00 2001 From: Artur Fursa Date: Thu, 16 Mar 2017 18:38:56 +0200 Subject: [PATCH 055/184] Fixes #13776: Fixed setting precision and scale for decimal columns in MSSQL --- framework/CHANGELOG.md | 1 + framework/db/mssql/QueryBuilder.php | 2 +- tests/framework/db/QueryBuilderTest.php | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index dcc1149..0a4c828 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -50,6 +50,7 @@ Yii Framework 2 Change Log - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) +- Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) 2.0.11.2 February 08, 2017 diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 30a7ac1..693e0ab 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -35,7 +35,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BIGINT => 'bigint', Schema::TYPE_FLOAT => 'float', Schema::TYPE_DOUBLE => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(18,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'datetime', Schema::TYPE_TIME => 'time', diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index b34081c..3312bd7 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -299,7 +299,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'postgres' => 'numeric(10,0) CHECK (value > 5.6)', 'sqlite' => 'decimal(10,0) CHECK (value > 5.6)', 'oci' => 'NUMBER CHECK (value > 5.6)', - 'sqlsrv' => 'decimal CHECK (value > 5.6)', + 'sqlsrv' => 'decimal(18,0) CHECK (value > 5.6)', 'cubrid' => 'decimal(10,0) CHECK (value > 5.6)', ], ], @@ -311,7 +311,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'postgres' => 'numeric(10,0) NOT NULL', 'sqlite' => 'decimal(10,0) NOT NULL', 'oci' => 'NUMBER NOT NULL', - 'sqlsrv' => 'decimal NOT NULL', + 'sqlsrv' => 'decimal(18,0) NOT NULL', 'cubrid' => 'decimal(10,0) NOT NULL', ], ], @@ -323,7 +323,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'postgres' => 'numeric(12,4) CHECK (value > 5.6)', 'sqlite' => 'decimal(12,4) CHECK (value > 5.6)', 'oci' => 'NUMBER CHECK (value > 5.6)', - 'sqlsrv' => 'decimal CHECK (value > 5.6)', + 'sqlsrv' => 'decimal(12,4) CHECK (value > 5.6)', 'cubrid' => 'decimal(12,4) CHECK (value > 5.6)', ], ], @@ -335,7 +335,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'postgres' => 'numeric(12,4)', 'sqlite' => 'decimal(12,4)', 'oci' => 'NUMBER', - 'sqlsrv' => 'decimal', + 'sqlsrv' => 'decimal(12,4)', 'cubrid' => 'decimal(12,4)', ], ], @@ -347,7 +347,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'postgres' => 'numeric(10,0)', 'sqlite' => 'decimal(10,0)', 'oci' => 'NUMBER', - 'sqlsrv' => 'decimal', + 'sqlsrv' => 'decimal(18,0)', 'cubrid' => 'decimal(10,0)', ], ], From cd64fb2dc67d92d3fb8a05b402b273b07192ba1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D0=B4=D0=B8=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20=D0=AE?= =?UTF-8?q?=D1=80=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Fri, 17 Mar 2017 03:59:11 +0500 Subject: [PATCH 056/184] Refactored BooleanValidator (#13791) --- framework/validators/BooleanValidator.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php index b5c8c1b..2a6830a 100644 --- a/framework/validators/BooleanValidator.php +++ b/framework/validators/BooleanValidator.php @@ -52,8 +52,11 @@ class BooleanValidator extends Validator */ protected function validateValue($value) { - $valid = !$this->strict && ($value == $this->trueValue || $value == $this->falseValue) - || $this->strict && ($value === $this->trueValue || $value === $this->falseValue); + if ($this->strict) { + $valid = $value === $this->trueValue || $value === $this->falseValue; + } else { + $valid = $value == $this->trueValue || $value == $this->falseValue; + } if (!$valid) { return [$this->message, [ From 13c2f5370f46a095d8fb2cf31e15ca4d9350df7c Mon Sep 17 00:00:00 2001 From: usix298 Date: Sat, 18 Mar 2017 00:13:08 +0300 Subject: [PATCH 057/184] Fixed formatting in tests [skip ci] --- tests/framework/ar/ActiveRecordTestTrait.php | 2 +- tests/framework/db/ActiveQueryTest.php | 2 +- tests/framework/db/ActiveRecordTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/framework/ar/ActiveRecordTestTrait.php b/tests/framework/ar/ActiveRecordTestTrait.php index 0eed00d..bab2d42 100644 --- a/tests/framework/ar/ActiveRecordTestTrait.php +++ b/tests/framework/ar/ActiveRecordTestTrait.php @@ -732,7 +732,7 @@ trait ActiveRecordTestTrait $this->assertCount(1, $customer->ordersWithNullFK); $orderWithNullFK = $orderWithNullFKClass::findOne(3); - $this->assertEquals(3,$orderWithNullFK->id); + $this->assertEquals(3, $orderWithNullFK->id); $this->assertNull($orderWithNullFK->customer_id); // has many with delete diff --git a/tests/framework/db/ActiveQueryTest.php b/tests/framework/db/ActiveQueryTest.php index 6825aa7..4ab1502 100644 --- a/tests/framework/db/ActiveQueryTest.php +++ b/tests/framework/db/ActiveQueryTest.php @@ -142,7 +142,7 @@ abstract class ActiveQueryTest extends DatabaseTestCase public function testGetQueryTableName_from_set() { $options = ['from' => ['alias'=>'customer']]; - $query = new ActiveQuery(Customer::className(),$options); + $query = new ActiveQuery(Customer::className(), $options); $result = $this->invokeMethod($query,'getTableNameAndAlias'); $this->assertEquals(['customer','alias'], $result); } diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index 7350bca..1e9a6b5 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -1077,7 +1077,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('booksWithNullFKViaTable', false); $this->assertCount(0, $order->booksWithNullFKViaTable); - $this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); + $this->assertEquals(2, $orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count()); } From a600e165aab0e41833d2f2e908eb63b79d4f882b Mon Sep 17 00:00:00 2001 From: Skiptir Engu Date: Fri, 17 Mar 2017 18:17:42 -0300 Subject: [PATCH 058/184] Added RadiobuttonColumnTest to the grid test group (#13795) --- tests/framework/grid/RadiobuttonColumnTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/framework/grid/RadiobuttonColumnTest.php b/tests/framework/grid/RadiobuttonColumnTest.php index c247bf4..c011e9e 100644 --- a/tests/framework/grid/RadiobuttonColumnTest.php +++ b/tests/framework/grid/RadiobuttonColumnTest.php @@ -17,6 +17,7 @@ use yiiunit\TestCase; /** * Class RadiobuttonColumnTest * @package yiiunit\framework\grid + * @group grid * @since 2.0.11 */ class RadiobuttonColumnTest extends TestCase From cc44339a452ed101d047c016b9a11df90c8c7b5b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 18 Mar 2017 10:53:43 +0300 Subject: [PATCH 059/184] Improved intl tests to tolerate ICU data differences - Added new assertion. - Adjusted tests not to rely on intl version allowing multiple valid variants for Russian and Ukrainian currencies and transliteration. --- tests/IsOneOfAssert.php | 41 +++++++++++++++++ tests/TestCase.php | 15 ++++-- tests/bootstrap.php | 1 + tests/compatibility.php | 8 ++++ tests/framework/helpers/InflectorTest.php | 69 ++++++++++++---------------- tests/framework/i18n/FormatterNumberTest.php | 34 +++++--------- 6 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 tests/IsOneOfAssert.php create mode 100644 tests/compatibility.php diff --git a/tests/IsOneOfAssert.php b/tests/IsOneOfAssert.php new file mode 100644 index 0000000..f813ab2 --- /dev/null +++ b/tests/IsOneOfAssert.php @@ -0,0 +1,41 @@ +allowedValues = $allowedValues; + } + + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + $expectedAsString = "'" . implode("', '", $this->allowedValues) . "'"; + return "is one of $expectedAsString"; + } + + /** + * @inheritdoc + */ + protected function matches($other) + { + return in_array($other, $this->allowedValues, false); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 5175aaf..0551fd2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -166,8 +166,15 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase } - - - - + /** + * Asserts that value is one of expected values + * + * @param mixed $actual + * @param array $expected + * @param string $message + */ + public function assertIsOneOf($actual, array $expected, $message = '') + { + self::assertThat($actual, new IsOneOfAssert($expected), $message); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cbff93b..a1aebe7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -18,4 +18,5 @@ require_once(__DIR__ . '/../framework/Yii.php'); Yii::setAlias('@yiiunit', __DIR__); +require_once(__DIR__ . '/compatibility.php'); require_once(__DIR__ . '/TestCase.php'); diff --git a/tests/compatibility.php b/tests/compatibility.php new file mode 100644 index 0000000..47a5e52 --- /dev/null +++ b/tests/compatibility.php @@ -0,0 +1,8 @@ += 57.1 - */ - private function hasNewICUData() - { - return version_compare(INTL_ICU_DATA_VERSION, '57.1', '>='); - } - public function testTransliterateMedium() { if (!extension_loaded('intl')) { @@ -251,37 +242,37 @@ class InflectorTest extends TestCase // Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius! $data = [ // Korean - '해동검도' => 'haedong-geomdo', + '해동검도' => ['haedong-geomdo'], // Hiragana - 'ひらがな' => 'hiragana', + 'ひらがな' => ['hiragana'], // Georgian - 'საქართველო' => 'sakartvelo', + 'საქართველო' => ['sakartvelo'], // Arabic - 'العربي' => 'alʿrby', - 'عرب' => 'ʿrb', + 'العربي' => ['alʿrby'], + 'عرب' => ['ʿrb'], // Hebrew - 'עִבְרִית' => $this->hasNewICUData() ? '\'iberiyt' : 'ʻiberiyt', + 'עִבְרִית' => ['\'iberiyt', 'ʻiberiyt'], // Turkish - 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => ['Sanirim hepimiz ayni seyi dusunuyoruz.'], // Russian - 'недвижимость' => $this->hasNewICUData() ? 'nedvizimost\'' : 'nedvizimostʹ', - 'Контакты' => 'Kontakty', + 'недвижимость' => ['nedvizimost\'', 'nedvizimostʹ'], + 'Контакты' => ['Kontakty'], // Ukrainian - 'Українська: ґанок, європа' => 'Ukrainsʹka: ganok, evropa', + 'Українська: ґанок, європа' => ['Ukrainsʹka: ganok, evropa', 'Ukrains\'ka: ganok, evropa'], // Serbian - 'Српска: ђ, њ, џ!' => 'Srpska: d, n, d!', + 'Српска: ђ, њ, џ!' => ['Srpska: d, n, d!'], // Spanish - '¿Español?' => '¿Espanol?', + '¿Español?' => ['¿Espanol?'], // Chinese - '美国' => 'mei guo', + '美国' => ['mei guo'], ]; - foreach ($data as $source => $expected) { - $this->assertEquals($expected, Inflector::transliterate($source, Inflector::TRANSLITERATE_MEDIUM)); + foreach ($data as $source => $allowed) { + $this->assertIsOneOf(Inflector::transliterate($source, Inflector::TRANSLITERATE_MEDIUM), $allowed); } } @@ -294,37 +285,37 @@ class InflectorTest extends TestCase // Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius! $data = [ // Korean - '해동검도' => 'haedong-geomdo', + '해동검도' => ['haedong-geomdo'], // Hiragana - 'ひらがな' => 'hiragana', + 'ひらがな' => ['hiragana'], // Georgian - 'საქართველო' => 'sakartvelo', + 'საქართველო' => ['sakartvelo'], // Arabic - 'العربي' => 'alrby', - 'عرب' => 'rb', + 'العربي' => ['alrby'], + 'عرب' => ['rb'], // Hebrew - 'עִבְרִית' => $this->hasNewICUData() ? '\'iberiyt' : 'iberiyt', + 'עִבְרִית' => ['\'iberiyt', 'iberiyt'], // Turkish - 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanirim hepimiz ayni seyi dusunuyoruz.', + 'Sanırım hepimiz aynı şeyi düşünüyoruz.' => ['Sanirim hepimiz ayni seyi dusunuyoruz.'], // Russian - 'недвижимость' => $this->hasNewICUData() ? 'nedvizimost\'' : 'nedvizimost', - 'Контакты' => 'Kontakty', + 'недвижимость' => ['nedvizimost\'', 'nedvizimost'], + 'Контакты' => ['Kontakty'], // Ukrainian - 'Українська: ґанок, європа' => 'Ukrainska: ganok, evropa', + 'Українська: ґанок, європа' => ['Ukrainska: ganok, evropa', 'Ukrains\'ka: ganok, evropa'], // Serbian - 'Српска: ђ, њ, џ!' => 'Srpska: d, n, d!', + 'Српска: ђ, њ, џ!' => ['Srpska: d, n, d!'], // Spanish - '¿Español?' => 'Espanol?', + '¿Español?' => ['Espanol?'], // Chinese - '美国' => 'mei guo', + '美国' => ['mei guo'], ]; - foreach ($data as $source => $expected) { - $this->assertEquals($expected, Inflector::transliterate($source, Inflector::TRANSLITERATE_LOOSE)); + foreach ($data as $source => $allowed) { + $this->assertIsOneOf(Inflector::transliterate($source, Inflector::TRANSLITERATE_LOOSE), $allowed); } } diff --git a/tests/framework/i18n/FormatterNumberTest.php b/tests/framework/i18n/FormatterNumberTest.php index 9cbc936..bc83686 100644 --- a/tests/framework/i18n/FormatterNumberTest.php +++ b/tests/framework/i18n/FormatterNumberTest.php @@ -264,13 +264,9 @@ class FormatterNumberTest extends TestCase // default russian currency symbol $this->formatter->locale = 'ru-RU'; $this->formatter->currencyCode = null; - if ($this->hasNewICUData()) { - $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); - } else { - $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); - } + $this->assertIsOneOf($this->formatter->asCurrency('123'), ['123,00 ₽', '123,00 руб.']); $this->formatter->currencyCode = 'RUB'; - $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); + $this->assertIsOneOf($this->formatter->asCurrency('123'), ['123,00 ₽', '123,00 руб.']); // custom currency symbol $this->formatter->currencyCode = null; @@ -279,16 +275,19 @@ class FormatterNumberTest extends TestCase ]; $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); $this->formatter->numberFormatterSymbols = [ - NumberFormatter::CURRENCY_SYMBOL => '₽', + NumberFormatter::CURRENCY_SYMBOL => 'RUR', ]; - $this->assertSame('123,00 ₽', $this->formatter->asCurrency('123')); + $this->assertSame('123,00 RUR', $this->formatter->asCurrency('123')); + + /* See https://github.com/yiisoft/yii2/issues/13629 // setting the currency code overrides the symbol $this->formatter->currencyCode = 'RUB'; - $this->assertSame('123,00 руб.', $this->formatter->asCurrency('123')); + $this->assertIsOneOf($this->formatter->asCurrency('123'), ['123,00 ₽', '123,00 руб.']); $this->formatter->numberFormatterSymbols = [NumberFormatter::CURRENCY_SYMBOL => '₽']; $this->assertSame('123,00 $', $this->formatter->asCurrency('123', 'USD')); $this->formatter->numberFormatterSymbols = [NumberFormatter::CURRENCY_SYMBOL => '₽']; $this->assertSame('123,00 €', $this->formatter->asCurrency('123', 'EUR')); + */ // custom separators $this->formatter->locale = 'de-DE'; @@ -342,11 +341,8 @@ class FormatterNumberTest extends TestCase $this->formatter->locale = 'ru-RU'; $this->formatter->currencyCode = null; - if ($this->hasNewICUData()) { - $this->assertSame('123 ₽', $this->formatter->asCurrency('123')); - } else { - $this->assertSame('123 руб.', $this->formatter->asCurrency('123')); - } + $this->assertIsOneOf($this->formatter->asCurrency('123'), ['123 ₽', '123 руб.']); + $this->formatter->numberFormatterSymbols = [ NumberFormatter::CURRENCY_SYMBOL => '₽', ]; @@ -354,7 +350,7 @@ class FormatterNumberTest extends TestCase $this->formatter->numberFormatterSymbols = []; $this->formatter->currencyCode = 'RUB'; - $this->assertSame('123 руб.', $this->formatter->asCurrency('123')); + $this->assertIsOneOf($this->formatter->asCurrency('123'), ['123 ₽', '123 руб.']); } /** @@ -673,12 +669,4 @@ class FormatterNumberTest extends TestCase $this->assertSame("1023 bytes", $this->formatter->asSize(1023)); $this->assertSame("1023 B", $this->formatter->asShortSize(1023)); } - - /** - * @return boolean if ICU version is >= 57.1 - */ - private function hasNewICUData() - { - return version_compare(INTL_ICU_DATA_VERSION, '57.1', '>='); - } } From 01dc29705fa31d05cc177f6724cbccf6b52da91a Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Fri, 17 Mar 2017 15:15:42 +0200 Subject: [PATCH 060/184] Fixed compatibility with PHPUnit 6.x --- tests/compatibility.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/compatibility.php b/tests/compatibility.php index 47a5e52..7fad669 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -6,3 +6,7 @@ if (!class_exists('PHPUnit_Framework_Constraint') && class_exists('PHPUnit\Framework\Constraint\Constraint')) { class PHPUnit_Framework_Constraint extends \PHPUnit\Framework\Constraint\Constraint {} } + +if (!class_exists('PHPUnit_Framework_TestCase') && class_exists('PHPUnit\Framework\TestCase')) { + class PHPUnit_Framework_TestCase extends \PHPUnit\Framework\TestCase {} +} From 07708632093785167105dcda7c9792caa36bb803 Mon Sep 17 00:00:00 2001 From: yyxx9988 Date: Wed, 22 Mar 2017 18:29:40 +0800 Subject: [PATCH 061/184] Fixes #13820: Add new HTTP status code 451 --- framework/CHANGELOG.md | 1 + framework/web/Response.php | 1 + 2 files changed, 2 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 0a4c828..b540ea2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) - Bug #7946: Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) diff --git a/framework/web/Response.php b/framework/web/Response.php index 9e1eca9..47782df 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -216,6 +216,7 @@ class Response extends \yii\base\Response 431 => 'Request Header Fields Too Large', 449 => 'Retry With', 450 => 'Blocked by Windows Parental Controls', + 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway or Proxy Error', From cf4eea1878e6490ba9db7b5b028559e42ec14390 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Wed, 22 Mar 2017 18:28:33 +0300 Subject: [PATCH 062/184] Fixes #13823: Refactored migrations template --- framework/CHANGELOG.md | 1 + framework/views/migration.php | 6 +++++- tests/data/console/migrate_create/default.php | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b540ea2..0d0c0b6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -53,6 +53,7 @@ Yii Framework 2 Change Log - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) +- Enh #13823: Refactored migrations template (Kolyunya) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/views/migration.php b/framework/views/migration.php index 243b41f..7c1d330 100644 --- a/framework/views/migration.php +++ b/framework/views/migration.php @@ -29,13 +29,17 @@ class extends Migration } /* - // Use safeUp/safeDown to run migration code within a transaction + // Use safeUp/safeDown to run migration code within a transaction. public function safeUp() { + } public function safeDown() { + echo " cannot be reverted.\n"; + + return false; } */ } diff --git a/tests/data/console/migrate_create/default.php b/tests/data/console/migrate_create/default.php index cdef5dc..721e78c 100644 --- a/tests/data/console/migrate_create/default.php +++ b/tests/data/console/migrate_create/default.php @@ -20,13 +20,17 @@ class {$class} extends Migration } /* - // Use safeUp/safeDown to run migration code within a transaction + // Use safeUp/safeDown to run migration code within a transaction. public function safeUp() { + } public function safeDown() { + echo "{$class} cannot be reverted.\\n"; + + return false; } */ } From 9d6c4bd41b5f8eb607b86611a629b7589f119df7 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Wed, 22 Mar 2017 19:33:58 +0200 Subject: [PATCH 063/184] Enhanced PHPUnit 6 compatibility --- tests/compatibility.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compatibility.php b/tests/compatibility.php index 7fad669..9141b4f 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -4,9 +4,9 @@ */ if (!class_exists('PHPUnit_Framework_Constraint') && class_exists('PHPUnit\Framework\Constraint\Constraint')) { - class PHPUnit_Framework_Constraint extends \PHPUnit\Framework\Constraint\Constraint {} + abstract class PHPUnit_Framework_Constraint extends \PHPUnit\Framework\Constraint\Constraint {} } if (!class_exists('PHPUnit_Framework_TestCase') && class_exists('PHPUnit\Framework\TestCase')) { - class PHPUnit_Framework_TestCase extends \PHPUnit\Framework\TestCase {} + abstract class PHPUnit_Framework_TestCase extends \PHPUnit\Framework\TestCase {} } From 7a2f04ae32f4fa47cc6e83f973e16601712b1daf Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 23 Mar 2017 00:18:39 +0300 Subject: [PATCH 064/184] Fixes #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` --- framework/CHANGELOG.md | 1 + framework/web/User.php | 4 ++-- tests/framework/web/UserTest.php | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 0d0c0b6..d4e7386 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -54,6 +54,7 @@ Yii Framework 2 Change Log - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13823: Refactored migrations template (Kolyunya) +- Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/web/User.php b/framework/web/User.php index d671d38..55d2eb5 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -404,7 +404,7 @@ class User extends Component * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8. * @return Response the redirection response if [[loginUrl]] is set - * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is + * @throws UnauthorizedHttpException the "Unauthorized" HTTP exception if [[loginUrl]] is not set or a redirect is * not applicable. */ public function loginRequired($checkAjax = true, $checkAcceptHeader = true) @@ -424,7 +424,7 @@ class User extends Component return Yii::$app->getResponse()->redirect($this->loginUrl); } } - throw new ForbiddenHttpException(Yii::t('yii', 'Login Required')); + throw new UnauthorizedHttpException(Yii::t('yii', 'Login Required')); } /** diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index f9dcc35..0fd7826 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -17,7 +17,7 @@ namespace yiiunit\framework\web; use yii\base\NotSupportedException; use yii\base\Component; use yii\rbac\PhpManager; -use yii\web\ForbiddenHttpException; +use yii\web\UnauthorizedHttpException; use yii\web\Cookie; use yii\web\CookieCollection; use yii\web\IdentityInterface; @@ -226,7 +226,7 @@ class UserTest extends TestCase $_SERVER['HTTP_ACCEPT'] = 'text/json, */*; q=0.1'; try { $user->loginRequired(); - } catch (ForbiddenHttpException $e) {} + } catch (UnauthorizedHttpException $e) {} $this->assertFalse(Yii::$app->response->getIsRedirection()); $this->reset(); @@ -263,12 +263,12 @@ class UserTest extends TestCase $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; try { $user->loginRequired(); - } catch (ForbiddenHttpException $e) {} + } catch (UnauthorizedHttpException $e) {} $this->assertNotEquals('json-only', $user->getReturnUrl()); $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; - $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + $this->setExpectedException('yii\\web\\UnauthorizedHttpException'); $user->loginRequired(); } @@ -291,7 +291,7 @@ class UserTest extends TestCase $this->mockWebApplication($appConfig); $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json,q=0.1'; - $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + $this->setExpectedException('yii\\web\\UnauthorizedHttpException'); Yii::$app->user->loginRequired(); } From f906452f8e91c0ad075fd05dd83215cbf6a3bf51 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 23 Mar 2017 00:51:34 +0300 Subject: [PATCH 065/184] Added UPGRADE note for #13829 --- framework/UPGRADE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 9f51979..c99cade 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -59,6 +59,9 @@ Upgrade from Yii 2.0.11 * `yii\grid\DataColumn` filter is now automatically generated as dropdown list with localized `Yes` and `No` strings in case of `format` being set to `boolean`. +* `yii\web\User::loginRequired()` now throws an `UnauthorizedHttpException` instead of a `ForbiddenHttpException`. + Check and adjust your try-catch blocks. + Upgrade from Yii 2.0.10 ----------------------- From 558f2aa02a02b085eab2d8917809f6834738eb76 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 23 Mar 2017 00:53:14 +0300 Subject: [PATCH 066/184] Fixes #13827 Generate "safe" methods uncommented by default --- framework/views/migration.php | 10 +++++----- tests/data/console/migrate_create/default.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/framework/views/migration.php b/framework/views/migration.php index 7c1d330..98c6e09 100644 --- a/framework/views/migration.php +++ b/framework/views/migration.php @@ -16,12 +16,12 @@ use yii\db\Migration; class extends Migration { - public function up() + public function safeUp() { } - public function down() + public function safeDown() { echo " cannot be reverted.\n"; @@ -29,13 +29,13 @@ class extends Migration } /* - // Use safeUp/safeDown to run migration code within a transaction. - public function safeUp() + // Use up()/down() to run migration code without a transaction. + public function up() { } - public function safeDown() + public function down() { echo " cannot be reverted.\n"; diff --git a/tests/data/console/migrate_create/default.php b/tests/data/console/migrate_create/default.php index 721e78c..cc9e8d4 100644 --- a/tests/data/console/migrate_create/default.php +++ b/tests/data/console/migrate_create/default.php @@ -7,12 +7,12 @@ use yii\db\Migration; class {$class} extends Migration { - public function up() + public function safeUp() { } - public function down() + public function safeDown() { echo "{$class} cannot be reverted.\\n"; @@ -20,13 +20,13 @@ class {$class} extends Migration } /* - // Use safeUp/safeDown to run migration code within a transaction. - public function safeUp() + // Use up()/down() to run migration code without a transaction. + public function up() { } - public function safeDown() + public function down() { echo "{$class} cannot be reverted.\\n"; From ea75068a920ae0a697dd3004a88639beffbc3129 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 23 Mar 2017 01:20:42 +0300 Subject: [PATCH 067/184] Fixes #13813: Fixed PHP 7 compatibiltiy by adding support for passing instances of `Error` to the `yii\web\Response::setStatusCodeByException()` --- framework/CHANGELOG.md | 1 + framework/base/ErrorHandler.php | 2 +- framework/web/ErrorHandler.php | 4 +-- framework/web/Response.php | 4 +-- tests/framework/web/ResponseTest.php | 52 ++++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index d4e7386..a863282 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -53,6 +53,7 @@ Yii Framework 2 Change Log - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) +- Bug #13813: Fixed PHP 7 compatibiltiy by adding support for passing instances of `Error` to the `yii\web\Response::setStatusCodeByException()` (Kolyunya) - Enh #13823: Refactored migrations template (Kolyunya) - Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index ae6a0bf..1176343 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -316,7 +316,7 @@ abstract class ErrorHandler extends Component /** * Converts an exception into a simple string. - * @param \Exception $exception the exception being converted + * @param \Exception|\Error $exception the exception being converted * @return string the string representation of the exception. */ public static function convertExceptionToString($exception) diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index d7192c8..f051b6e 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -73,7 +73,7 @@ class ErrorHandler extends \yii\base\ErrorHandler /** * Renders the exception. - * @param \Exception $exception the exception to be rendered. + * @param \Exception|\Error $exception the exception to be rendered. */ protected function renderException($exception) { @@ -126,7 +126,7 @@ class ErrorHandler extends \yii\base\ErrorHandler /** * Converts an exception into an array. - * @param \Exception $exception the exception being converted + * @param \Exception|\Error $exception the exception being converted * @return array the array representation of the exception. */ protected function convertExceptionToArray($exception) diff --git a/framework/web/Response.php b/framework/web/Response.php index 47782df..f846f61 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -293,13 +293,13 @@ class Response extends \yii\base\Response /** * Sets the response status code based on the exception. - * @param \Exception $e + * @param \Exception|\Error $e * @throws InvalidParamException if the status code is invalid. * @return $this the response object itself * * @since 2.0.12 */ - public function setStatusCodeByException(\Exception $e) + public function setStatusCodeByException($e) { if ($e instanceof HttpException) { $this->setStatusCode($e->statusCode); diff --git a/tests/framework/web/ResponseTest.php b/tests/framework/web/ResponseTest.php index d57f76d..a7fd83c 100644 --- a/tests/framework/web/ResponseTest.php +++ b/tests/framework/web/ResponseTest.php @@ -2,8 +2,12 @@ namespace yiiunit\framework\web; +use Exception; +use RuntimeException; +use Error; use Yii; use yii\helpers\StringHelper; +use yii\web\HttpException; /** * @group web @@ -118,4 +122,52 @@ class ResponseTest extends \yiiunit\TestCase $this->assertEquals($this->response->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->headers->get('location'), '/index.php?r=controller%2Findex&id_1=3&id_2=4'); $this->assertEquals($this->response->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->headers->get('location'), '/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29'); } + + /** + * @dataProvider dataProviderSetStatusCodeByException + * + */ + public function testSetStatusCodeByException($exception, $statusCode) + { + $this->response->setStatusCodeByException($exception); + $this->assertEquals($statusCode, $this->response->getStatusCode()); + } + + public function dataProviderSetStatusCodeByException() + { + return [ + [ + new Exception(), + 500, + ], + [ + new RuntimeException(), + 500, + ], + [ + new Error(), + 500, + ], + [ + new HttpException(500), + 500, + ], + [ + new HttpException(403), + 403, + ], + [ + new HttpException(404), + 404, + ], + [ + new HttpException(301), + 301, + ], + [ + new HttpException(200), + 200, + ], + ]; + } } From da3bb5aa429f3f75af88694b25bb1e5b1ed97878 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 23 Mar 2017 01:56:26 +0300 Subject: [PATCH 068/184] Removed change from changelog since it's for code which wasn't yet released [skip ci] --- framework/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index a863282..d4e7386 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -53,7 +53,6 @@ Yii Framework 2 Change Log - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) -- Bug #13813: Fixed PHP 7 compatibiltiy by adding support for passing instances of `Error` to the `yii\web\Response::setStatusCodeByException()` (Kolyunya) - Enh #13823: Refactored migrations template (Kolyunya) - Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) From a35eada199b8fbd85d416d07d3f7f4d7ef6b297c Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Thu, 23 Mar 2017 09:27:06 +0300 Subject: [PATCH 069/184] Fix tests of a web response --- tests/framework/web/ResponseTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/framework/web/ResponseTest.php b/tests/framework/web/ResponseTest.php index a7fd83c..56967df 100644 --- a/tests/framework/web/ResponseTest.php +++ b/tests/framework/web/ResponseTest.php @@ -135,7 +135,7 @@ class ResponseTest extends \yiiunit\TestCase public function dataProviderSetStatusCodeByException() { - return [ + $data = [ [ new Exception(), 500, @@ -145,10 +145,6 @@ class ResponseTest extends \yiiunit\TestCase 500, ], [ - new Error(), - 500, - ], - [ new HttpException(500), 500, ], @@ -169,5 +165,14 @@ class ResponseTest extends \yiiunit\TestCase 200, ], ]; + + if (class_exists('Error')) { + $data[] = [ + new Error(), + 500, + ]; + } + + return $data; } } From 79e4201e57f98c58a4fad18769a0336a2b823380 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Mar 2017 17:21:25 +0300 Subject: [PATCH 070/184] Fixed Russian translation of core-code-style.md (#13832) [skip ci] --- docs/internals-ru/core-code-style.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals-ru/core-code-style.md b/docs/internals-ru/core-code-style.md index 02b4bdf..d405287 100644 --- a/docs/internals-ru/core-code-style.md +++ b/docs/internals-ru/core-code-style.md @@ -473,7 +473,7 @@ public function getEventHandlers($name) ### Каталоги/пространства имен - Используйте нижний регистр; -- используйте множественную форму для существительных, представляющих объекты (например валидаторы); -- используйте единичную форму для имен, представляющих соответствующий функционал (например web); +- используйте множественное число для существительных, представляющих объекты (например валидаторы); +- используйте единственное число для имен, представляющих соответствующий функционал (например web); - предпочтительнее использовать однословные пространства имён; - если одно слово использовать не удаётся, используйте camelCase стиль. From 7e1a050a16a529840b2d1869f7c26ea14959ceea Mon Sep 17 00:00:00 2001 From: Pavel Dovlatov Date: Thu, 23 Mar 2017 16:35:47 +0200 Subject: [PATCH 071/184] Fixes #13821: fixed docs and added more tests for Url helper --- framework/helpers/BaseUrl.php | 9 ++++++--- tests/framework/helpers/UrlTest.php | 23 ++++++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/framework/helpers/BaseUrl.php b/framework/helpers/BaseUrl.php index 5623aec..740c19c 100644 --- a/framework/helpers/BaseUrl.php +++ b/framework/helpers/BaseUrl.php @@ -291,8 +291,9 @@ class BaseUrl * @param string|array $url the URL to remember. Please refer to [[to()]] for acceptable formats. * If this parameter is not specified, the currently requested URL will be used. * @param string $name the name associated with the URL to be remembered. This can be used - * later by [[previous()]]. If not set, it will use [[\yii\web\User::returnUrlParam]]. + * later by [[previous()]]. If not set, [[\yii\web\User::setReturnUrl()]] will be used with passed URL. * @see previous() + * @see \yii\web\User::setReturnUrl() */ public static function remember($url = '', $name = null) { @@ -309,9 +310,11 @@ class BaseUrl * Returns the URL previously [[remember()|remembered]]. * * @param string $name the named associated with the URL that was remembered previously. - * If not set, it will use [[\yii\web\User::returnUrlParam]]. - * @return string|null the URL previously remembered. Null is returned if no URL was remembered with the given name. + * If not set, [[\yii\web\User::getReturnUrl()]] will be used to obtain remembered URL. + * @return string|null the URL previously remembered. Null is returned if no URL was remembered with the given name + * and `$name` is not specified. * @see remember() + * @see \yii\web\User::getReturnUrl() */ public static function previous($name = null) { diff --git a/tests/framework/helpers/UrlTest.php b/tests/framework/helpers/UrlTest.php index 1be2ffe..543d3d3 100644 --- a/tests/framework/helpers/UrlTest.php +++ b/tests/framework/helpers/UrlTest.php @@ -9,6 +9,7 @@ use yii\web\Controller; use yii\web\UrlManager; use yii\widgets\Menu; use yiiunit\TestCase; +use yiiunit\framework\filters\stubs\UserIdentity; /** * UrlTest @@ -32,11 +33,20 @@ class UrlTest extends TestCase 'baseUrl' => '/base', 'scriptUrl' => '/base/index.php', 'hostInfo' => 'http://example.com/', - ] + ], + 'user' => [ + 'identityClass' => UserIdentity::className(), + ], ], ], '\yii\web\Application'); } + protected function tearDown() + { + Yii::$app->getSession()->removeAll(); + parent::tearDown(); + } + /** * Mocks controller action with parameters * @@ -114,6 +124,17 @@ class UrlTest extends TestCase $this->assertEquals('/base/index.php?r=page%2Fview&name=test', Url::current(['id' => null])); } + public function testPrevious() + { + Yii::$app->getUser()->login(UserIdentity::findIdentity('user1')); + + $this->assertNull(Url::previous('notExistedPage')); + + $this->assertNull(Url::previous(Yii::$app->getUser()->returnUrlParam)); + + $this->assertEquals('/base/index.php', Url::previous()); + } + public function testTo() { // is an array: the first array element is considered a route, while the rest of the name-value From 6da1ec6fb2b6367cb30d8c581575d09f0072812d Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Fri, 24 Mar 2017 06:14:48 +0100 Subject: [PATCH 072/184] Fixes #13845: `mt_rand()` is not used instead of `rand()` in `yii\captcha\CaptchaAction` + minor code improvements --- framework/CHANGELOG.md | 1 + framework/captcha/CaptchaAction.php | 8 ++++---- framework/console/controllers/MessageController.php | 4 ++-- framework/db/mssql/QueryBuilder.php | 2 +- framework/helpers/BaseHtml.php | 2 +- framework/helpers/BaseInflector.php | 6 +++--- framework/web/CookieCollection.php | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index d4e7386..2807704 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -55,6 +55,7 @@ Yii Framework 2 Change Log - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13823: Refactored migrations template (Kolyunya) - Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) +- Enh #13845: `mt_rand()` is not used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php index 483cdd1..679fc25 100644 --- a/framework/captcha/CaptchaAction.php +++ b/framework/captcha/CaptchaAction.php @@ -297,8 +297,8 @@ class CaptchaAction extends Action $x = 10; $y = round($this->height * 27 / 40); for ($i = 0; $i < $length; ++$i) { - $fontSize = (int) (rand(26, 32) * $scale * 0.8); - $angle = rand(-10, 10); + $fontSize = (int) (mt_rand(26, 32) * $scale * 0.8); + $angle = mt_rand(-10, 10); $letter = $code[$i]; $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter); $x = $box[2] + $this->offset; @@ -340,9 +340,9 @@ class CaptchaAction extends Action for ($i = 0; $i < $length; ++$i) { $draw = new \ImagickDraw(); $draw->setFont($this->fontFile); - $draw->setFontSize((int) (rand(26, 32) * $scale * 0.8)); + $draw->setFontSize((int) (mt_rand(26, 32) * $scale * 0.8)); $draw->setFillColor($foreColor); - $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]); + $image->annotateImage($draw, $x, $y, mt_rand(-10, 10), $code[$i]); $fontMetrics = $image->queryFontMetrics($draw, $code[$i]); $x += (int) $fontMetrics['textWidth'] + $this->offset; } diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index 5ace795..b81405b 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -534,11 +534,11 @@ EOD; if (isset($buffer[0][0], $buffer[1], $buffer[2][0]) && $buffer[0][0] === T_CONSTANT_ENCAPSED_STRING && $buffer[1] === ',' && $buffer[2][0] === T_CONSTANT_ENCAPSED_STRING) { // is valid call we can extract $category = stripcslashes($buffer[0][1]); - $category = mb_substr($category, 1, mb_strlen($category) - 2); + $category = mb_substr($category, 1, -1); if (!$this->isCategoryIgnored($category, $ignoreCategories)) { $message = stripcslashes($buffer[2][1]); - $message = mb_substr($message, 1, mb_strlen($message) - 2); + $message = mb_substr($message, 1, -1); $messages[$category][] = $message; } diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 693e0ab..6cccd40 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -215,7 +215,7 @@ class QueryBuilder extends \yii\db\QueryBuilder public function checkIntegrity($check = true, $schema = '', $table = '') { $enable = $check ? 'CHECK' : 'NOCHECK'; - $schema = $schema ? $schema : $this->db->getSchema()->defaultSchema; + $schema = $schema ?: $this->db->getSchema()->defaultSchema; $tableNames = $this->db->getTableSchema($table) ? [$table] : $this->db->getSchema()->getTableNames($schema); $viewNames = $this->db->getSchema()->getViewNames($schema); $tableNames = array_diff($tableNames, $viewNames); diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 0b1d092..52513e7 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -1221,7 +1221,7 @@ class BaseHtml foreach ($model->getErrors() as $errors) { foreach ($errors as $error) { $line = $encode ? Html::encode($error) : $error; - if (array_search($line, $lines) === false) { + if (!in_array($line, $lines, true)) { $lines[] = $line; } if (!$showAllErrors) { diff --git a/framework/helpers/BaseInflector.php b/framework/helpers/BaseInflector.php index d8754be..38f9e06 100644 --- a/framework/helpers/BaseInflector.php +++ b/framework/helpers/BaseInflector.php @@ -362,7 +362,7 @@ class BaseInflector */ public static function camel2words($name, $ucwords = true) { - $label = trim(strtolower(str_replace([ + $label = strtolower(trim(str_replace([ '-', '_', '.', @@ -384,9 +384,9 @@ class BaseInflector { $regex = $strict ? '/[A-Z]/' : '/(? Date: Fri, 24 Mar 2017 14:18:05 +0300 Subject: [PATCH 073/184] Fixed typo [skip ci] --- framework/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2807704..bb80a0a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -55,7 +55,7 @@ Yii Framework 2 Change Log - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13823: Refactored migrations template (Kolyunya) - Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) -- Enh #13845: `mt_rand()` is not used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) +- Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) 2.0.11.2 February 08, 2017 -------------------------- From f5f31c7fe16c5ef55325c98eb932372395e92586 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 28 Mar 2017 20:10:16 +0300 Subject: [PATCH 074/184] Fixes #13864: Added info about configuring sub-rules of yii\rest\UrlRule [skip ci] --- docs/guide/rest-routing.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/guide/rest-routing.md b/docs/guide/rest-routing.md index d927625..5948f92 100644 --- a/docs/guide/rest-routing.md +++ b/docs/guide/rest-routing.md @@ -90,3 +90,21 @@ a controller ID. For example, the following code maps the name `u` to the contro 'controller' => ['u' => 'user'], ] ``` + +## Extra configuration for contained rules + +It could be useful to specify extra configuration that is applied to each rule contained within [[yii\rest\UrlRule]]. +A good example would be specifying defaults for `expand` parameter: + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => ['user'], + 'ruleConfig' => [ + 'class' => 'yii\web\UrlRule', + 'defaults' => [ + 'expand' => 'profile', + ] + ], +], +``` From a626440761fa05d3f92ba8ab42403481103dc03c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 30 Mar 2017 16:50:18 +0300 Subject: [PATCH 075/184] Reverted #13822, clarified exception descriptions --- framework/CHANGELOG.md | 1 - framework/web/ForbiddenHttpException.php | 11 +++++------ framework/web/UnauthorizedHttpException.php | 12 +++++++----- framework/web/User.php | 4 ++-- tests/framework/web/UserTest.php | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index bb80a0a..f3392f5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -54,7 +54,6 @@ Yii Framework 2 Change Log - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13823: Refactored migrations template (Kolyunya) -- Bug #13822: Fixed `yii\web\User::loginRequired()` to throw an `UnauthorizedHttpException` instead of a `ForbiddenHttpException` (Kolyunya) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) 2.0.11.2 February 08, 2017 diff --git a/framework/web/ForbiddenHttpException.php b/framework/web/ForbiddenHttpException.php index ca3adf5..f08b12e 100644 --- a/framework/web/ForbiddenHttpException.php +++ b/framework/web/ForbiddenHttpException.php @@ -10,13 +10,12 @@ namespace yii\web; /** * ForbiddenHttpException represents a "Forbidden" HTTP exception with status code 403. * - * Use this exception when a user has been authenticated but is not allowed to - * perform the requested action. If the user is not authenticated, consider - * using a 401 [[UnauthorizedHttpException]]. If you do not want to - * expose authorization information to the user, it is valid to respond with a - * 404 [[NotFoundHttpException]]. + * Use this exception when a user is not allowed to perform the requested action. + * Using different credentials might or might not allow performing the requested action. + * If you do not want to expose authorization information to the user, it is valid + * to respond with a 404 [[NotFoundHttpException]]. * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 + * @link https://tools.ietf.org/html/rfc7231#section-6.5.3 * @author Dan Schmidt * @since 2.0 */ diff --git a/framework/web/UnauthorizedHttpException.php b/framework/web/UnauthorizedHttpException.php index 0770dff..db26548 100644 --- a/framework/web/UnauthorizedHttpException.php +++ b/framework/web/UnauthorizedHttpException.php @@ -10,12 +10,14 @@ namespace yii\web; /** * UnauthorizedHttpException represents an "Unauthorized" HTTP exception with status code 401 * - * Use this exception to indicate that a client needs to authenticate or login - * to perform the requested action. If the client is already authenticated and - * is simply not allowed to perform the action, consider using a 403 - * [[ForbiddenHttpException]] or 404 [[NotFoundHttpException]] instead. + * Use this exception to indicate that a client needs to authenticate via WWW-Authenticate header + * to perform the requested action. * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 + * If the client is already authenticated and is simply not allowed to + * perform the action, consider using a 403 [[ForbiddenHttpException]] + * or 404 [[NotFoundHttpException]] instead. + * + * @link https://tools.ietf.org/html/rfc7235#section-3.1 * @author Dan Schmidt * @since 2.0 */ diff --git a/framework/web/User.php b/framework/web/User.php index 55d2eb5..d671d38 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -404,7 +404,7 @@ class User extends Component * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8. * @return Response the redirection response if [[loginUrl]] is set - * @throws UnauthorizedHttpException the "Unauthorized" HTTP exception if [[loginUrl]] is not set or a redirect is + * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is * not applicable. */ public function loginRequired($checkAjax = true, $checkAcceptHeader = true) @@ -424,7 +424,7 @@ class User extends Component return Yii::$app->getResponse()->redirect($this->loginUrl); } } - throw new UnauthorizedHttpException(Yii::t('yii', 'Login Required')); + throw new ForbiddenHttpException(Yii::t('yii', 'Login Required')); } /** diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 0fd7826..f9dcc35 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -17,7 +17,7 @@ namespace yiiunit\framework\web; use yii\base\NotSupportedException; use yii\base\Component; use yii\rbac\PhpManager; -use yii\web\UnauthorizedHttpException; +use yii\web\ForbiddenHttpException; use yii\web\Cookie; use yii\web\CookieCollection; use yii\web\IdentityInterface; @@ -226,7 +226,7 @@ class UserTest extends TestCase $_SERVER['HTTP_ACCEPT'] = 'text/json, */*; q=0.1'; try { $user->loginRequired(); - } catch (UnauthorizedHttpException $e) {} + } catch (ForbiddenHttpException $e) {} $this->assertFalse(Yii::$app->response->getIsRedirection()); $this->reset(); @@ -263,12 +263,12 @@ class UserTest extends TestCase $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; try { $user->loginRequired(); - } catch (UnauthorizedHttpException $e) {} + } catch (ForbiddenHttpException $e) {} $this->assertNotEquals('json-only', $user->getReturnUrl()); $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; - $this->setExpectedException('yii\\web\\UnauthorizedHttpException'); + $this->setExpectedException('yii\\web\\ForbiddenHttpException'); $user->loginRequired(); } @@ -291,7 +291,7 @@ class UserTest extends TestCase $this->mockWebApplication($appConfig); $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json,q=0.1'; - $this->setExpectedException('yii\\web\\UnauthorizedHttpException'); + $this->setExpectedException('yii\\web\\ForbiddenHttpException'); Yii::$app->user->loginRequired(); } From 729767b1635408aa975991ca670098754c01aee4 Mon Sep 17 00:00:00 2001 From: phongtt3 Date: Thu, 30 Mar 2017 21:09:42 +0700 Subject: [PATCH 076/184] improve vi lang for intro-yii.md (#13886) [skip ci] --- docs/guide-vi/intro-yii.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/guide-vi/intro-yii.md b/docs/guide-vi/intro-yii.md index 185bb48..2fa6b34 100644 --- a/docs/guide-vi/intro-yii.md +++ b/docs/guide-vi/intro-yii.md @@ -1,28 +1,28 @@ Yii là gì =========== -Yii có hiệu suất cao, được dựa trên các thành phần PHP framework cho việc phát triển các ứng dụng Web hiện đại. +Yii là một PHP Framework mã nguồn mở và hoàn toàn miễn phí, có hiệu năng xử lý cao, phát triển tốt nhất trên các ứng dụng Web 2.0, sử dụng tối đa các thành phần (component-based PHP framework) để tăng tốc độ viết ứng dụng. Tên Yii (được phát âm là `Yee` hoặc `[ji:]`) ở Trung Quốc có nghĩa là "thật đơn giản và luôn phát triển". Nghĩa thứ hai có thể đọc ngắn gọn là **Yes It Is**! Yii thích hợp nhất để làm gì? --------------------- -Yii là một framework lập trình Web chung chung mà có thể được sử dụng để phát triển một loạt các ứng dụng Web -được xây dựng với PHP. Bởi vì dựa trên kiến trúc thành phần và có bộ nhớ đệm hoàn hảo, -nó là đặc biệt thích hợp cho việc phát triển các ứng dụng quy mô lớn, chẳng hạn như các cổng thông tin, -diễn đàn, hệ thống quản lý nội dung (CMS), các dự án thương mại điện tử và các dịch vụ Web RESTful. +Yii, nói chung, là một framework phát triển ứng dụng Web nên có thể dùng để viết mọi loại ứng dụng Web +và sử dụng ngôn ngữ lập trình PHP. Yii rất nhẹ và được trang bị giải pháp cache tối ưu nên đặc biệt +hữu dụng cho ứng dụng web có dung lượng dữ liệu trên đường truyền lớn như web portal, forum, CMS, e-commerce, +các dự án thương mại điện tử và các dịch vụ Web RESTful.. So sánh Yii Với các Frameworks khác? ------------------------------------------- -Nếu bạn có kinh nghiệm với các framework khác, bạn sẽ rất vui mừng khi thấy những nỗ lực của Yii: +Nếu bạn có kinh nghiệm làm việc với các framework khác, bạn sẽ rất vui mừng khi thấy những nỗ lực của Yii: -- Giống như những PHP frameworks khác, Yii sử dụng mô hình MVC (Model-View-Controller) tổ chức code một cách hợp lý và có hệ thống hơn . +- Giống như những PHP frameworks khác, Yii sử dụng mô hình MVC (Model-View-Controller) tổ chức code một cách hợp lý và có hệ thống. - Yii tạo ra code đơn giản và thanh lịch, đây là triết lý trong chương trình. Yii sẽ không bao giờ cố gắng tạo ra những mấu thiết kế quá an toàn và ít có sự thay đổi. -- Yii là framework hoàn chình, cung cấp nhiều tính năng và được xác minh như: query builders, thao tác dữ liệu với +- Yii là framework hoàn chỉnh, cung cấp nhiều tính năng và được xác minh như: query builders, thao tác dữ liệu với ActiveRecord được dùng cho CSDL quan hệ và NoSQL; hỗ trợ phát triển RESTful API; sự hỗ trợ đa bộ nhớ cache; và nhiều hơn. - Yii rất dễ mở rộng. Bạn có thể tùy chình hoặc thay thế bất kỳ một trong những bộ code chuẩn. Bạn cũng có thể tận dụng lợi thế của kiến trúc mở rộng chuẩn Yii để sử dụng hoặc phát triển mở rộng phân phối.. @@ -39,7 +39,7 @@ Các phiên bản Yii ------------ Yii Hiện nay có hai phiên bản chính: 1.1 và 2.0. Phiên bản 1.1 là phiên bản cũ và bây giờ là trong chế độ bảo trì. Tiếp đến, phiên bản 2.0 là phiên bản đuọc viết lại hoàn toàn Yii, sử dụng các -công nghệ mới và giao thức, bao gồm trình quản lý gói Composer, các tiêu chuẩn code PHP PSR, namespaces, traits, và như vậy. Phiên bản 2.0 đại diện cho sự hình thành của framework +công nghệ mới và giao thức mới, bao gồm trình quản lý gói Composer, các tiêu chuẩn code PHP PSR, namespaces, traits, và như vậy. Phiên bản 2.0 đại diện cho sự hình thành của framework và sẽ nhận được những nỗ lực phát triển chính trong vài năm tới. Hướng dẫn này chủ yếu là về phiên bản 2.0. From fb50273c1758c017296cf2bd0cd6c458d6668442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Jerosimi=C4=87?= Date: Thu, 30 Mar 2017 16:28:26 +0200 Subject: [PATCH 077/184] Fixed Serbian translation (#13882) [skip ci] Fixed Serbian translation issue with totalCount appearing twice in GridView summary --- framework/messages/sr-Latn/yii.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/messages/sr-Latn/yii.php b/framework/messages/sr-Latn/yii.php index c2d5280..f73313d 100644 --- a/framework/messages/sr-Latn/yii.php +++ b/framework/messages/sr-Latn/yii.php @@ -63,7 +63,7 @@ return [ 'Page not found.' => 'Stranica nije pronađena.', 'Please fix the following errors:' => 'Molimo vas ispravite sledeće greške:', 'Please upload a file.' => 'Molimo vas postavite fajl.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, =1{# stavke} one{# stavke} few{# stavke} many{# stavki} other{# stavki}}.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, number} {totalCount, plural, =1{stavke} one{stavke} few{stavke} many{stavki} other{stavki}}.', 'The file "{file}" is not an image.' => 'Fajl "{file}" nije slika.', 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Fajl "{file}" je prevelik. Veličina ne može biti veća od {formattedLimit}.', 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Fajl "{file}" je premali. Veličina ne može biti manja od {formattedLimit}.', From df33760865c2f3b9efe2e0c5a64be7bd70bc0122 Mon Sep 17 00:00:00 2001 From: Tran Tien Phong Date: Fri, 31 Mar 2017 15:01:23 +0700 Subject: [PATCH 078/184] Guide vi improvement (#13894) [skip ci] * improve vi lang for intro-yii.md * update Readme & start installation more readable for Vietnamese --- docs/guide-vi/README.md | 26 +++++++++++++------------- docs/guide-vi/start-installation.md | 37 ++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/docs/guide-vi/README.md b/docs/guide-vi/README.md index 9ebf4c8..954213c 100644 --- a/docs/guide-vi/README.md +++ b/docs/guide-vi/README.md @@ -3,7 +3,7 @@ The Definitive Guide to Yii 2.0 Các hướng dẫn được phát hành theo [Các điều khoản về tài liệu Yii](http://www.yiiframework.com/doc/terms/). -All Rights Reserved. +Tất cả bản quyền đã được bảo hộ (All Rights Reserved). 2014 (c) Yii Software LLC. @@ -12,7 +12,7 @@ Giới thiệu ------------ * [Về Yii](intro-yii.md) -* [Nâng cấp lên từ phiên bản 1.1](intro-upgrade-from-v1.md) +* [Hướng dẫn nâng cấp lên từ phiên bản 1.1](intro-upgrade-from-v1.md) Bắt đầu @@ -20,11 +20,11 @@ Bắt đầu * [Cài đặt Yii](start-installation.md) * [Thực hiện chạy ứng dụng](start-workflow.md) -* [Viết lời chào đầu tiên](start-hello.md) +* [Viết chương trình đầu tiên](start-hello.md) * [Làm việc với Forms](start-forms.md) * [Làm việc với Databases](start-databases.md) -* [Sử dụng Gii để sinh code](start-gii.md) -* [Mức cao hơn](start-looking-ahead.md) +* [Sử dụng Gii để sinh mã tự động](start-gii.md) +* [Nâng cao](start-looking-ahead.md) Kiến trúc ứng dụng (Application Structure) @@ -33,8 +33,8 @@ Kiến trúc ứng dụng (Application Structure) * [Tổng quan về kiến trúc ứng dụng](structure-overview.md) * [Mục Scripts](structure-entry-scripts.md) * [Ứng dụng (Applications)](structure-applications.md) -* [Thành phần ứng dụng](structure-application-components.md) -* [Bộ điều khiển (Controllers)](structure-controllers.md) +* [Các thành phần bên trong ứng dụng](structure-application-components.md) +* [Controllers](structure-controllers.md) * [Models](structure-models.md) * [Views](structure-views.md) * [Modules](structure-modules.md) @@ -44,14 +44,14 @@ Kiến trúc ứng dụng (Application Structure) * [Phần mở rộng (Extensions)](structure-extensions.md) -Yêu cầu xử lý (Handling Requests) +Xử lý yêu cầu (Handling Requests) ----------------- * [Tổng quan](runtime-overview.md) -* [Bootstrapping](runtime-bootstrapping.md) -* [Routing và URL Creation](runtime-routing.md) +* [Khởi động](runtime-bootstrapping.md) +* [Định tuyến (Routing) và khởi tạo đường dẫn (URL Creation)](runtime-routing.md) * [Yêu cầu (Requests)](runtime-requests.md) -* [Responses](runtime-responses.md) +* [Kết quả (Responses)](runtime-responses.md) * [Sessions và Cookies](runtime-sessions-cookies.md) * [Xử lý lỗi (Handling Error)](runtime-handling-errors.md) * [Logging](runtime-logging.md) @@ -77,7 +77,7 @@ Làm việc với Databases * [Data Access Objects](db-dao.md): Kết nối cơ sở dữ liệu, truy vấn cơ bản, giao dịch và phương thức hoạt động * [Query Builder](db-query-builder.md): Sử dụng một truy vấn đơn giản, các lớp cơ sở dữ liệu trừu tượng * [Active Record](db-active-record.md): The Active Record ORM, truy vấn và thao tác với dữ liệu, định nghĩa các mối quan hệ giữa các bảng -* [Migrations](db-migrations.md): Cung cấp cho đội dự án dễ dàng trong việc quản lý những schema CSDL trong ứng dụng +* [Migrations](db-migrations.md): Cung cấp cho đội dự án một công cụ dễ dàng trong việc quản lý những schema CSDL trong ứng dụng * **TBD** [Sphinx](db-sphinx.md) * **TBD** [Redis](db-redis.md) * **TBD** [MongoDB](db-mongodb.md) @@ -171,7 +171,7 @@ Chủ đề năng cao * [Tối ưu hiệu năng ứng dụng (Performance Tuning)](tutorial-performance-tuning.md) * [Shared Hosting Environment](tutorial-shared-hosting.md) * [Template Engines](tutorial-template-engines.md) -* [Working with Third-Party Code](tutorial-yii-integration.md) +* [Tích hợp mã nguồn của bên thứ ba (Working with Third-Party Code)](tutorial-yii-integration.md) Widgets diff --git a/docs/guide-vi/start-installation.md b/docs/guide-vi/start-installation.md index 0adf2a5..923080b 100644 --- a/docs/guide-vi/start-installation.md +++ b/docs/guide-vi/start-installation.md @@ -1,20 +1,19 @@ Cài đặt Yii ============== -Bạn có thể cài đặt Yii theo hai cách, dùng trình quản lý gói [Composer](http://getcomposer.org/) hoặc tải về một tập tin lưu trữ. -Cách thứ nhất thường được hay dùng hơn, vì nó cho phép bạn cài đặt các [Gói mở rộng (extensions)](structure-extensions.md) mới hoặc cập nhật Yii đơn giản chỉ mới một câu lệnh. +Bạn có thể cài đặt Yii theo hai cách, dùng trình quản lý gói [Composer](http://getcomposer.org/) hoặc tải toàn bộ mã nguồn Yii về. +Cách thứ nhất thường được khuyến khích dùng hơn, vì nó cho phép bạn cài đặt thêm các [Gói mở rộng (extensions)](structure-extensions.md) hoặc cập nhật Yii đơn giản chỉ mới một dòng lệnh. -Ứng dụng Yii cần được tải về và cài đặt, điều này có kết quả giống nhau khi thực hiện cài đặt theo chuẩn. -Những ứng dụng Yii khi đã cài đặt đều được triển khai một số tính năng cơ bản, như đăng nhập (login), form liên hệ (contact form), vv. -Những tính năng trên đều được khuyến khích. Vì thế, nó có thể hữu ích như là một điểm bắt đầu tốt cho các dự án của bạn. +Mặc định, sau khi cài đặt Yii sẽ cung cấp cho bạn một số tính năng cơ bản, như đăng nhập (login), form liên hệ (contact form), vv. +Những tính năng trên đều được khuyến khích và sử dụng rộng rãi, vì thế, nó có thể hữu ích cho các dự án của bạn. -Trong bài hướng dẫn này và các phần tiếp theo, chúng ta sẽ tìm hiều cách cài ứng dụng Yii với tên *Basic Application Template* và -làm thế nào để thực hiện các tính năng mới trên mẫu ứng dụng này. Yii đồng thời cũng cung cấp mẫu ứng dụng tên là [Advanced Application Template](tutorial-advanced-app.md) -mẫu này được dùng tốt hơn cho những đội dự án cần phát triển ứng dụng có nhiều tầng. +Trong bài hướng dẫn này và các phần tiếp theo, chúng ta sẽ tìm hiều cách cài ứng dụng Yii với tên *Basic Application Template* và +làm thế nào để triển khai các tính năng mới trên mẫu ứng dụng này. Yii đồng thời cũng cung cấp mẫu ứng dụng tên là [Advanced Application Template](tutorial-advanced-app.md) +Template này hướng đến những đội dự án cần phát triển ứng dụng có nhiều tầng (multiple tiers). -> Lưu ý: Các mẫu ứng dụng *Basic Application Template* là thích hợp cho việc phát triển 90% của các ứng dụng Web. nó khác -với các mẫu ứng dụng [Advanced Application Template](tutorial-advanced-app.md). Nếu bạn là người mới tìm hiều về Yii, chúng tôi khuyến khích -bạn bắt đầu với các ứng dụng *Basic Application Template* , ứng dụng này đơn giản và ít chức năng thích hợp hơn cho việc tìm hiểu về Yii. +> Lưu ý: *Basic Application Template* thích hợp đến 90% cho việc phát triển web. Nó khác +với [Advanced Application Template](tutorial-advanced-app.md) trong cách tổ chức mã nguồn. Nếu bạn là người mới tìm hiều về Yii, chúng tôi khuyến khích +bạn bắt đầu với *Basic Application Template* , ứng dụng này đơn giản và ít chức năng. Thích hợp hơn cho việc tìm hiểu về Yii. @@ -22,14 +21,14 @@ Cài đặt qua trinh quản lý gói Composer -------------------------- -Sau khi cài đặt, bạn có thể sử dụng trình duyệt để truy cập các ứng dụng Yii được cài đặt với các URL sau đây: +Sau khi cài đặt, bạn có thể sử dụng trình duyệt để truy cập ứng dụng Yii được cài đặt với URL dưới đây: ``` http://localhost/basic/web/index.php From 79f16b149239c0d1314102999a2fc8353ee8cb0b Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Fri, 31 Mar 2017 10:20:43 +0200 Subject: [PATCH 079/184] Fixes #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified --- framework/CHANGELOG.md | 1 + framework/data/SqlDataProvider.php | 3 ++- tests/framework/data/SqlDataProviderTest.php | 35 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/framework/data/SqlDataProviderTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index f3392f5..2d8c2a5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -55,6 +55,7 @@ Yii Framework 2 Change Log - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13823: Refactored migrations template (Kolyunya) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) +- Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php index 765ad7d..e3f93c4 100644 --- a/framework/data/SqlDataProvider.php +++ b/framework/data/SqlDataProvider.php @@ -11,6 +11,7 @@ use Yii; use yii\base\InvalidConfigException; use yii\db\Connection; use yii\db\Expression; +use yii\db\Query; use yii\di\Instance; /** @@ -162,6 +163,6 @@ class SqlDataProvider extends BaseDataProvider */ protected function prepareTotalCount() { - return 0; + return (new Query())->from(['sub' => "({$this->sql})"])->count('*', $this->db); } } diff --git a/tests/framework/data/SqlDataProviderTest.php b/tests/framework/data/SqlDataProviderTest.php new file mode 100644 index 0000000..0be1882 --- /dev/null +++ b/tests/framework/data/SqlDataProviderTest.php @@ -0,0 +1,35 @@ + 'select * from `customer`', + 'db' => $this->getConnection(), + ]); + $this->assertCount(3, $dataProvider->getModels()); + } + + public function testTotalCount() + { + $dataProvider = new SqlDataProvider([ + 'sql' => 'select * from `customer`', + 'db' => $this->getConnection(), + ]); + $this->assertEquals(3, $dataProvider->getTotalCount()); + } +} From 78a003570370ca2bb30cf44f0c216823121734dc Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 1 Apr 2017 16:37:48 +0300 Subject: [PATCH 080/184] Removed unnecessary else --- framework/data/SqlDataProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php index e3f93c4..e0062ba 100644 --- a/framework/data/SqlDataProvider.php +++ b/framework/data/SqlDataProvider.php @@ -153,9 +153,9 @@ class SqlDataProvider extends BaseDataProvider } return $keys; - } else { - return array_keys($models); } + + return array_keys($models); } /** From 4e9497c4fadb052119545587438c15b7864d3e53 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Sat, 1 Apr 2017 16:57:17 +0300 Subject: [PATCH 081/184] Fixes #13770: Added support for `yii\widgets\Menu` item classes definition in the form of an array --- framework/CHANGELOG.md | 1 + framework/widgets/Menu.php | 8 +-- tests/framework/widgets/MenuTest.php | 101 +++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2d8c2a5..433575b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -53,6 +53,7 @@ Yii Framework 2 Change Log - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) - Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) +- Enh #13770: Added support for `yii\widgets\Menu` item classes definition in the form of an array (Kolyunya) - Enh #13823: Refactored migrations template (Kolyunya) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) - Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) diff --git a/framework/widgets/Menu.php b/framework/widgets/Menu.php index e6506f6..db678d4 100644 --- a/framework/widgets/Menu.php +++ b/framework/widgets/Menu.php @@ -208,13 +208,7 @@ class Menu extends Widget if ($i === $n - 1 && $this->lastItemCssClass !== null) { $class[] = $this->lastItemCssClass; } - if (!empty($class)) { - if (empty($options['class'])) { - $options['class'] = implode(' ', $class); - } else { - $options['class'] .= ' ' . implode(' ', $class); - } - } + Html::addCssClass($options, $class); $menu = $this->renderItem($item); if (!empty($item['items'])) { diff --git a/tests/framework/widgets/MenuTest.php b/tests/framework/widgets/MenuTest.php index fe75711..6b9cc27 100644 --- a/tests/framework/widgets/MenuTest.php +++ b/tests/framework/widgets/MenuTest.php @@ -196,6 +196,107 @@ HTML; $this->assertEqualsWithoutLE($expected, $output); } + public function testItemClassAsArray() + { + $output = Menu::widget([ + 'route' => 'test/test', + 'params' => [], + 'encodeLabels' => true, + 'activeCssClass' => 'item-active', + 'items' => [ + [ + 'label' => 'item1', + 'url' => '#', + 'active' => true, + 'options' => [ + 'class' => [ + 'someclass', + ], + ], + ], + [ + 'label' => 'item2', + 'url' => '#', + 'options' => [ + 'class' => [ + 'another-class', + 'other--class', + 'two classes', + ], + ], + ], + [ + 'label' => 'item3', + 'url' => '#', + ], + [ + 'label' => 'item4', + 'url' => '#', + 'options' => [ + 'class' => [ + 'some-other-class', + 'foo_bar_baz_class', + ], + ], + ], + ], + ]); + + $expected = <<
  • item1
  • +
  • item2
  • +
  • item3
  • +
  • item4
  • +HTML; + $this->assertEqualsWithoutLE($expected, $output); + } + + public function testItemClassAsString() + { + $output = Menu::widget([ + 'route' => 'test/test', + 'params' => [], + 'encodeLabels' => true, + 'activeCssClass' => 'item-active', + 'items' => [ + [ + 'label' => 'item1', + 'url' => '#', + 'options' => [ + 'class' => 'someclass', + ], + ], + [ + 'label' => 'item2', + 'url' => '#', + ], + [ + 'label' => 'item3', + 'url' => '#', + 'options' => [ + 'class' => 'some classes', + ], + ], + [ + 'label' => 'item4', + 'url' => '#', + 'active' => true, + 'options' => [ + 'class' => 'another-class other--class two classes', + ], + ], + ], + ]); + + $expected = <<
  • item1
  • +
  • item2
  • +
  • item3
  • +
  • item4
  • +HTML; + $this->assertEqualsWithoutLE($expected, $output); + } + public function testIsItemActive() { // TODO: implement test of protected method isItemActive() From ae7378974e19a82f7716887d2429b3ac3c3dde98 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 29 Mar 2017 13:41:07 +0200 Subject: [PATCH 082/184] Fixes #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination --- framework/CHANGELOG.md | 1 + framework/data/BaseDataProvider.php | 23 +++++++++++- tests/framework/data/BaseDataProviderTest.php | 54 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/framework/data/BaseDataProviderTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 433575b..1e68bd4 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -57,6 +57,7 @@ Yii Framework 2 Change Log - Enh #13823: Refactored migrations template (Kolyunya) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) - Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) +- Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php index 5883c18..b63131d 100644 --- a/framework/data/BaseDataProvider.php +++ b/framework/data/BaseDataProvider.php @@ -33,9 +33,15 @@ use yii\base\InvalidParamException; abstract class BaseDataProvider extends Component implements DataProviderInterface { /** + * @var int Number of data providers on the current page. Used to generate unique IDs. + */ + private static $counter = 0; + /** * @var string an ID that uniquely identifies the data provider among all data providers. - * You should set this property if the same page contains two or more different data providers. - * Otherwise, the [[pagination]] and [[sort]] may not work properly. + * Generated automatically the following way in case it is not set: + * + * - First data provider ID is empty. + * - Second and all subsequent data provider IDs are: "dp-1", "dp-2", etc. */ public $id; @@ -45,6 +51,19 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa private $_models; private $_totalCount; + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->id === null) { + if (self::$counter > 0) { + $this->id = 'dp-' . self::$counter; + } + self::$counter++; + } + } /** * Prepares the data models that will be made available in the current page. diff --git a/tests/framework/data/BaseDataProviderTest.php b/tests/framework/data/BaseDataProviderTest.php new file mode 100644 index 0000000..2c1827e --- /dev/null +++ b/tests/framework/data/BaseDataProviderTest.php @@ -0,0 +1,54 @@ +getProperty('counter'); + $rp->setAccessible(true); + $rp->setValue(null); + + $this->assertNull((new ConcreteDataProvider())->id); + $this->assertNotNull((new ConcreteDataProvider())->id); + } + +} + +/** + * ConcreteDataProvider + */ +class ConcreteDataProvider extends BaseDataProvider +{ + /** + * @inheritdoc + */ + protected function prepareModels() + { + return []; + } + + /** + * @inheritdoc + */ + protected function prepareKeys($models) + { + return []; + } + + /** + * @inheritdoc + */ + protected function prepareTotalCount() + { + return 0; + } +} From 902c2b563d606693eafb80f0af2f7d22d9fb1cbd Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 2 Apr 2017 00:41:34 +0300 Subject: [PATCH 083/184] Fixes #13831: Added more info about transactions in migrations to phpdoc [skip ci] --- framework/db/Migration.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/framework/db/Migration.php b/framework/db/Migration.php index b642b66..882aab6 100644 --- a/framework/db/Migration.php +++ b/framework/db/Migration.php @@ -26,6 +26,10 @@ use yii\di\Instance; * [[safeDown()]] so that if anything wrong happens during the upgrading or downgrading, * the whole migration can be reverted in a whole. * + * Note that some DB queries in some DBMS cannot be put into a transaction. For some examples, + * please refer to [implicit commit](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). If this is the case, + * you should still implement `up()` and `down()`, instead. + * * Migration provides a set of convenient methods for manipulating database data and schema. * For example, the [[insert()]] method can be used to easily insert a row of data into * a database table; the [[createTable()]] method can be used to create a database table. @@ -155,6 +159,11 @@ class Migration extends Component implements MigrationInterface * be enclosed within a DB transaction. * Child classes may implement this method instead of [[up()]] if the DB logic * needs to be within a transaction. + * + * Note: Not all DBMS support transactions. And some DB queries cannot be put into a transaction. For some examples, + * please refer to [implicit commit](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). If this is the case, + * you should still implement `up()` and `down()`, instead. + * * @return bool return a false value to indicate the migration fails * and should not proceed further. All other return values mean the migration succeeds. */ @@ -168,6 +177,11 @@ class Migration extends Component implements MigrationInterface * be enclosed within a DB transaction. * Child classes may implement this method instead of [[down()]] if the DB logic * needs to be within a transaction. + * + * Note: Not all DBMS support transactions. And some DB queries cannot be put into a transaction. For some examples, + * please refer to [implicit commit](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). If this is the case, + * you should still implement `up()` and `down()`, instead. + * * @return bool return a false value to indicate the migration fails * and should not proceed further. All other return values mean the migration succeeds. */ From 514b29d55d6705fd2ff26186fd1e9a9f2727901d Mon Sep 17 00:00:00 2001 From: aquy Date: Mon, 16 Jan 2017 11:48:33 +0300 Subject: [PATCH 084/184] Fixes #13369: Added ability to render current `yii\widgets\LinkPager` page disabled --- framework/CHANGELOG.md | 1 + framework/widgets/LinkPager.php | 7 ++++++- tests/framework/widgets/LinkPagerTest.php | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 1e68bd4..a38c7e4 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -58,6 +58,7 @@ Yii Framework 2 Change Log - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) - Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) - Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) +- Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/widgets/LinkPager.php b/framework/widgets/LinkPager.php index 16d6f11..52270d1 100644 --- a/framework/widgets/LinkPager.php +++ b/framework/widgets/LinkPager.php @@ -122,6 +122,11 @@ class LinkPager extends Widget * @var bool Hide widget when only one page exist. */ public $hideOnSinglePage = true; + /** + * @var bool whether to render current page button as disabled. + * @since 2.0.12 + */ + public $disableCurrentPageButton = false; /** @@ -190,7 +195,7 @@ class LinkPager extends Widget // internal pages list($beginPage, $endPage) = $this->getPageRange(); for ($i = $beginPage; $i <= $endPage; ++$i) { - $buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage); + $buttons[] = $this->renderPageButton($i + 1, $i, null, $this->disableCurrentPageButton && $i == $currentPage, $i == $currentPage); } // next page diff --git a/tests/framework/widgets/LinkPagerTest.php b/tests/framework/widgets/LinkPagerTest.php index 81ad3d3..90acf13 100644 --- a/tests/framework/widgets/LinkPagerTest.php +++ b/tests/framework/widgets/LinkPagerTest.php @@ -86,4 +86,26 @@ class LinkPagerTest extends \yiiunit\TestCase static::assertContains('
    «
    ', $output); } + + public function testDisableCurrentPageButton() + { + $pagination = new Pagination(); + $pagination->setPage(5); + $pagination->totalCount = 500; + $pagination->route = 'test'; + + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'disableCurrentPageButton' => false, + ]); + + static::assertContains('
  • 6
  • ', $output); + + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'disableCurrentPageButton' => true, + ]); + + static::assertContains('
  • 6
  • ', $output); + } } From 8ae207c3a186d7b4845a0e022d784ce0002dba8c Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 29 Mar 2017 14:26:15 +0200 Subject: [PATCH 085/184] Fixes #13837: Refactored masking of CSRF tokens --- framework/CHANGELOG.md | 1 + framework/base/Security.php | 32 ++++++++++++++++++ framework/web/Request.php | 64 +++++++++-------------------------- tests/framework/base/SecurityTest.php | 19 +++++++++++ tests/framework/web/RequestTest.php | 6 ++-- 5 files changed, 70 insertions(+), 52 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index a38c7e4..6f24f9f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -59,6 +59,7 @@ Yii Framework 2 Change Log - Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) - Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) - Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) +- Enh #13837: Refactored masking of CSRF tokens (sammousa) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/base/Security.php b/framework/base/Security.php index b946de7..a76103b 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -706,4 +706,36 @@ class Security extends Component } return $diff === 0; } + + /** + * Masks a token to make it uncompressible. + * Applies a random mask to the token and prepends the mask used to the result making the string always unique. + * Used to mitigate BREACH attack by randomizing how token is outputted on each request. + * @param string $token An unmasked token. + * @return string A masked token. + * @since 2.0.12 + */ + public function maskToken($token) + { + // The number of bytes in a mask is always equal to the number of bytes in a token. + $mask = $this->generateRandomKey(StringHelper::byteLength($token)); + return StringHelper::base64UrlEncode($mask . ($mask ^ $token)); + } + + /** + * Unmasks a token previously masked by `maskToken`. + * @param string $maskedToken A masked token. + * @return string An unmasked token, or an empty string in case of token format is invalid. + * @since 2.0.12 + */ + public function unmaskToken($maskedToken) + { + $decoded = StringHelper::base64UrlDecode($maskedToken); + $length = StringHelper::byteLength($decoded) / 2; + // Check if the masked token has an even length. + if (!is_int($length)) { + return ''; + } + return StringHelper::byteSubstr($decoded, $length, $length) ^ StringHelper::byteSubstr($decoded, 0, $length); + } } diff --git a/framework/web/Request.php b/framework/web/Request.php index d914bec..ade6dae 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -9,7 +9,6 @@ namespace yii\web; use Yii; use yii\base\InvalidConfigException; -use yii\helpers\StringHelper; /** * The web Request class represents an HTTP request @@ -91,6 +90,7 @@ class Request extends \yii\base\Request const CSRF_HEADER = 'X-CSRF-Token'; /** * The length of the CSRF token mask. + * @deprecated 2.0.12 The mask length is now equal to the token length. */ const CSRF_MASK_LENGTH = 8; @@ -1325,11 +1325,7 @@ class Request extends \yii\base\Request if ($regenerate || ($token = $this->loadCsrfToken()) === null) { $token = $this->generateCsrfToken(); } - // the mask doesn't need to be very random - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; - $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); - // The + sign may be decoded as blank space later, which will fail the validation - $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); + $this->_csrfToken = Yii::$app->security->maskToken($token); } return $this->_csrfToken; @@ -1350,12 +1346,12 @@ class Request extends \yii\base\Request } /** - * Generates an unmasked random token used to perform CSRF validation. + * Generates an unmasked random token used to perform CSRF validation. * @return string the random token for CSRF validation. */ protected function generateCsrfToken() { - $token = Yii::$app->getSecurity()->generateRandomString(); + $token = Yii::$app->getSecurity()->generateRandomKey(); if ($this->enableCsrfCookie) { $cookie = $this->createCsrfCookie($token); Yii::$app->getResponse()->getCookies()->add($cookie); @@ -1366,32 +1362,11 @@ class Request extends \yii\base\Request } /** - * Returns the XOR result of two strings. - * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one. - * @param string $token1 - * @param string $token2 - * @return string the XOR result - */ - private function xorTokens($token1, $token2) - { - $n1 = StringHelper::byteLength($token1); - $n2 = StringHelper::byteLength($token2); - if ($n1 > $n2) { - $token2 = str_pad($token2, $n1, $token2); - } elseif ($n1 < $n2) { - $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1); - } - - return $token1 ^ $token2; - } - - /** * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent. */ public function getCsrfTokenFromHeader() { - $key = 'HTTP_' . str_replace('-', '_', strtoupper(static::CSRF_HEADER)); - return isset($_SERVER[$key]) ? $_SERVER[$key] : null; + return $this->headers->get(static::CSRF_HEADER); } /** @@ -1418,12 +1393,12 @@ class Request extends \yii\base\Request * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method * is among GET, HEAD or OPTIONS. * - * @param string $token the user-provided CSRF token to be validated. If null, the token will be retrieved from + * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from * the [[csrfParam]] POST field or HTTP header. * This parameter is available since version 2.0.4. * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. */ - public function validateCsrfToken($token = null) + public function validateCsrfToken($clientSuppliedToken = null) { $method = $this->getMethod(); // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 @@ -1431,10 +1406,10 @@ class Request extends \yii\base\Request return true; } - $trueToken = $this->loadCsrfToken(); + $trueToken = $this->getCsrfToken(); - if ($token !== null) { - return $this->validateCsrfTokenInternal($token, $trueToken); + if ($clientSuppliedToken !== null) { + return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken); } else { return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); @@ -1444,25 +1419,18 @@ class Request extends \yii\base\Request /** * Validates CSRF token * - * @param string $token - * @param string $trueToken + * @param string $clientSuppliedToken The masked client-supplied token. + * @param string $trueToken The masked true token. * @return bool */ - private function validateCsrfTokenInternal($token, $trueToken) + private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken) { - if (!is_string($token)) { + if (!is_string($clientSuppliedToken)) { return false; } - $token = base64_decode(str_replace('.', '+', $token)); - $n = StringHelper::byteLength($token); - if ($n <= static::CSRF_MASK_LENGTH) { - return false; - } - $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); - $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); - $token = $this->xorTokens($mask, $token); + $security = Yii::$app->security; - return $token === $trueToken; + return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken); } } diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php index e9026d2..9d087d4 100644 --- a/tests/framework/base/SecurityTest.php +++ b/tests/framework/base/SecurityTest.php @@ -1251,6 +1251,25 @@ TEXT; { $this->assertEquals(strcmp($expected, $actual) === 0, $this->security->compareString($expected, $actual)); } + + /** + * @dataProvider maskProvider + */ + public function testMasking($unmaskedToken) + { + $this->assertEquals($unmaskedToken, $this->security->unmaskToken($this->security->maskToken($unmaskedToken))); + } + + /** + * @return array + */ + public function maskProvider() { + return [ + ['SimpleToken'], + ['Token with special characters: %d1 5"'], + ['Token with UTF8 character: †'] + ]; + } } } // closing namespace yiiunit\framework\base; diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index c225c03..667f7d7 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -174,11 +174,9 @@ class RequestTest extends TestCase foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $request->setBodyParams([]); - //$request->headers->remove(Request::CSRF_HEADER); - unset($_SERVER['HTTP_' . str_replace('-', '_', strtoupper(Request::CSRF_HEADER))]); + $request->headers->remove(Request::CSRF_HEADER); $this->assertFalse($request->validateCsrfToken()); - //$request->headers->add(Request::CSRF_HEADER, $token); - $_SERVER['HTTP_' . str_replace('-', '_', strtoupper(Request::CSRF_HEADER))] = $token; + $request->headers->add(Request::CSRF_HEADER, $token); $this->assertTrue($request->validateCsrfToken()); } From 43edf24123269c3858978eb122dbf748cca59b4f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 2 Apr 2017 02:15:39 +0300 Subject: [PATCH 086/184] Eliminated else branches in yii\web\Request --- framework/web/Request.php | 69 +++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/framework/web/Request.php b/framework/web/Request.php index ade6dae..60f1347 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -189,9 +189,9 @@ class Request extends \yii\base\Request $this->_queryParams = $params + $this->_queryParams; } return [$route, $this->getQueryParams()]; - } else { - throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } + + throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } /** @@ -459,9 +459,9 @@ class Request extends \yii\base\Request { if ($name === null) { return $this->getBodyParams(); - } else { - return $this->getBodyParam($name, $defaultValue); } + + return $this->getBodyParam($name, $defaultValue); } private $_queryParams; @@ -504,9 +504,9 @@ class Request extends \yii\base\Request { if ($name === null) { return $this->getQueryParams(); - } else { - return $this->getQueryParam($name, $defaultValue); } + + return $this->getQueryParam($name, $defaultValue); } /** @@ -689,11 +689,13 @@ class Request extends \yii\base\Request { if (isset($this->_scriptFile)) { return $this->_scriptFile; - } elseif (isset($_SERVER['SCRIPT_FILENAME'])) { + } + + if (isset($_SERVER['SCRIPT_FILENAME'])) { return $_SERVER['SCRIPT_FILENAME']; - } else { - throw new InvalidConfigException('Unable to determine the entry script file path.'); } + + throw new InvalidConfigException('Unable to determine the entry script file path.'); } /** @@ -1073,7 +1075,9 @@ class Request extends \yii\base\Request { if (isset($_SERVER['CONTENT_TYPE'])) { return $_SERVER['CONTENT_TYPE']; - } elseif (isset($_SERVER['HTTP_CONTENT_TYPE'])) { + } + + if (isset($_SERVER['HTTP_CONTENT_TYPE'])) { //fix bug https://bugs.php.net/bug.php?id=66606 return $_SERVER['HTTP_CONTENT_TYPE']; } @@ -1166,23 +1170,31 @@ class Request extends \yii\base\Request $b = $b['q']; if ($a[2] > $b[2]) { return -1; - } elseif ($a[2] < $b[2]) { + } + + if ($a[2] < $b[2]) { return 1; - } elseif ($a[1] === $b[1]) { + } + + if ($a[1] === $b[1]) { return $a[0] > $b[0] ? 1 : -1; - } elseif ($a[1] === '*/*') { + } + + if ($a[1] === '*/*') { return 1; - } elseif ($b[1] === '*/*') { + } + + if ($b[1] === '*/*') { return -1; - } else { - $wa = $a[1][strlen($a[1]) - 1] === '*'; - $wb = $b[1][strlen($b[1]) - 1] === '*'; - if ($wa xor $wb) { - return $wa ? 1 : -1; - } else { - return $a[0] > $b[0] ? 1 : -1; - } } + + $wa = $a[1][strlen($a[1]) - 1] === '*'; + $wb = $b[1][strlen($b[1]) - 1] === '*'; + if ($wa xor $wb) { + return $wa ? 1 : -1; + } + + return $a[0] > $b[0] ? 1 : -1; }); $result = []; @@ -1234,9 +1246,9 @@ class Request extends \yii\base\Request { if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { return preg_split('/[\s,]+/', str_replace('-gzip', '', $_SERVER['HTTP_IF_NONE_MATCH']), -1, PREG_SPLIT_NO_EMPTY); - } else { - return []; } + + return []; } /** @@ -1340,9 +1352,8 @@ class Request extends \yii\base\Request { if ($this->enableCsrfCookie) { return $this->getCookies()->getValue($this->csrfParam); - } else { - return Yii::$app->getSession()->get($this->csrfParam); } + return Yii::$app->getSession()->get($this->csrfParam); } /** @@ -1410,10 +1421,10 @@ class Request extends \yii\base\Request if ($clientSuppliedToken !== null) { return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken); - } else { - return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) - || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } + + return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) + || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } /** From 892512feba4a98363ee1dde6bf5cb71e08d849cc Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Sun, 12 Feb 2017 14:29:37 +0300 Subject: [PATCH 087/184] Fixes #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests --- framework/CHANGELOG.md | 1 + framework/widgets/FragmentCache.php | 46 +++++++----- tests/framework/widgets/FragmentCacheTest.php | 104 +++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 20 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6f24f9f..6e944b6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -60,6 +60,7 @@ Yii Framework 2 Change Log - Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) - Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) - Enh #13837: Refactored masking of CSRF tokens (sammousa) +- Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php index a542ffb..29b2238 100644 --- a/framework/widgets/FragmentCache.php +++ b/framework/widgets/FragmentCache.php @@ -134,25 +134,33 @@ class FragmentCache extends Widget */ public function getCachedContent() { - if ($this->_content === null) { - $this->_content = false; - if ($this->cache instanceof Cache) { - $key = $this->calculateKey(); - $data = $this->cache->get($key); - if (is_array($data) && count($data) === 2) { - list ($content, $placeholders) = $data; - if (is_array($placeholders) && count($placeholders) > 0) { - if (empty($this->getView()->cacheStack)) { - // outermost cache: replace placeholder with dynamic content - $content = $this->updateDynamicContent($content, $placeholders); - } - foreach ($placeholders as $name => $statements) { - $this->getView()->addDynamicPlaceholder($name, $statements); - } - } - $this->_content = $content; - } - } + if ($this->_content !== null) { + return $this->_content; + } + + $this->_content = false; + + if (!($this->cache instanceof Cache)) { + return $this->_content; + } + + $key = $this->calculateKey(); + $data = $this->cache->get($key); + if (!is_array($data) || count($data) !== 2) { + return $this->_content; + } + + list ($this->_content, $placeholders) = $data; + if (!is_array($placeholders) || count($placeholders) === 0) { + return $this->_content; + } + + if (empty($this->getView()->cacheStack)) { + // outermost cache: replace placeholder with dynamic content + $this->_content = $this->updateDynamicContent($this->_content, $placeholders); + } + foreach ($placeholders as $name => $statements) { + $this->getView()->addDynamicPlaceholder($name, $statements); } return $this->_content; diff --git a/tests/framework/widgets/FragmentCacheTest.php b/tests/framework/widgets/FragmentCacheTest.php index fdaa7a2..0800c6b 100644 --- a/tests/framework/widgets/FragmentCacheTest.php +++ b/tests/framework/widgets/FragmentCacheTest.php @@ -5,7 +5,6 @@ namespace yiiunit\framework\widgets; use Yii; use yii\caching\ArrayCache; use yii\base\View; -use yii\widgets\Breadcrumbs; /** * @group widgets @@ -86,6 +85,109 @@ class FragmentCacheTest extends \yiiunit\TestCase $this->assertEquals($expectedLevel, ob_get_level(), 'Output buffer not closed correctly.'); } + public function testSingleDynamicFragment() + { + Yii::$app->params['counter'] = 0; + + $view = new View(); + + for ($counter = 0; $counter < 42; $counter++) { + ob_start(); + ob_implicit_flush(false); + + $cacheUnavailable = $view->beginCache('test'); + + if ($counter === 0) { + $this->assertTrue($cacheUnavailable); + } else { + $this->assertFalse($cacheUnavailable); + } + + if ($cacheUnavailable) { + echo 'single dynamic cached fragment: '; + echo $view->renderDynamic('return \Yii::$app->params["counter"]++;'); + $view->endCache(); + } + + $expectedContent = vsprintf('single dynamic cached fragment: %d', [ + $counter, + ]); + $this->assertEquals($expectedContent, ob_get_clean()); + } + } + + public function testMultipleDynamicFragments() + { + Yii::$app->params['counter'] = 0; + + $view = new View(); + + for ($counter = 0; $counter < 42; $counter++) { + ob_start(); + ob_implicit_flush(false); + + $cacheUnavailable = $view->beginCache('test'); + + if ($counter === 0) { + $this->assertTrue($cacheUnavailable); + } else { + $this->assertFalse($cacheUnavailable); + } + + if ($cacheUnavailable) { + echo 'multiple dynamic cached fragments: '; + echo $view->renderDynamic('return md5(\Yii::$app->params["counter"]);'); + echo $view->renderDynamic('return \Yii::$app->params["counter"]++;'); + $view->endCache(); + } + + $expectedContent = vsprintf('multiple dynamic cached fragments: %s%d', [ + md5($counter), + $counter, + ]); + $this->assertEquals($expectedContent, ob_get_clean()); + } + } + + public function testNestedDynamicFragments() + { + Yii::$app->params['counter'] = 0; + + $view = new View(); + + for ($counter = 0; $counter < 42; $counter++) { + ob_start(); + ob_implicit_flush(false); + + $cacheUnavailable = $view->beginCache('test'); + + if ($counter === 0) { + $this->assertTrue($cacheUnavailable); + } else { + $this->assertFalse($cacheUnavailable); + } + + if ($cacheUnavailable) { + echo 'nested dynamic cached fragments: '; + echo $view->renderDynamic('return md5(\Yii::$app->params["counter"]);'); + + if ($view->beginCache('test-nested')) { + echo $view->renderDynamic('return sha1(\Yii::$app->params["counter"]);'); + $view->endCache(); + } + + echo $view->renderDynamic('return \Yii::$app->params["counter"]++;'); + $view->endCache(); + } + + $expectedContent = vsprintf('nested dynamic cached fragments: %s%s%d', [ + md5($counter), + sha1($counter), + $counter, + ]); + $this->assertEquals($expectedContent, ob_get_clean()); + } + } // TODO test dynamic replacements } From 6cd3eaf8285163f326bdd1a24d38aa5943729d5d Mon Sep 17 00:00:00 2001 From: "S.M.A. Javadi" Date: Sun, 2 Apr 2017 23:17:21 +0430 Subject: [PATCH 088/184] Fixed persian translation (#13906) [skip ci] --- framework/messages/fa/yii.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/framework/messages/fa/yii.php b/framework/messages/fa/yii.php index d220242..99f7f21 100644 --- a/framework/messages/fa/yii.php +++ b/framework/messages/fa/yii.php @@ -1,8 +1,5 @@ - * * Message translations. * * This file is automatically generated by 'yii message/extract' command. @@ -20,8 +17,7 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'The combination {values} of {attributes} has already been taken.' => 'مقدار {values} از {attributes} قبلاً گرفته شده است.', - 'Unknown alias: -{name}' => 'نام مستعار ناشناخته: -{name}', + ' and ' => ' و ', '(not set)' => '(تنظیم نشده)', 'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.', 'Are you sure you want to delete this item?' => 'آیا اطمینان به حذف این مورد دارید؟', @@ -41,6 +37,7 @@ return [ 'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:', 'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', + 'The combination {values} of {attributes} has already been taken.' => 'مقدار {values} از {attributes} قبلاً گرفته شده است.', 'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.', 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'حجم فایل "{file}" بسیار بیشتر می باشد. حجم آن نمی تواند از {formattedLimit} بیشتر باشد.', 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'حجم فایل "{file}" بسیار کم می باشد. حجم آن نمی تواند از {formattedLimit} کمتر باشد.', @@ -53,6 +50,7 @@ return [ 'The verification code is incorrect.' => 'کد تأیید اشتباه است.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} مورد.', 'Unable to verify your data submission.' => 'قادر به تأیید اطلاعات ارسالی شما نمی‌باشد.', + 'Unknown alias: -{name}' => 'نام مستعار ناشناخته: -{name}', 'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}', 'Update' => 'بروزرسانی', 'View' => 'نما', From 3e2086ed276d80dafe669f1be21a22838d7bf6e2 Mon Sep 17 00:00:00 2001 From: Bizley Date: Sun, 2 Apr 2017 20:48:13 +0200 Subject: [PATCH 089/184] Internals PL updates (#13907) [skip ci] --- docs/internals-pl/automation.md | 2 ++ docs/internals-pl/design-decisions.md | 4 ++++ docs/internals-pl/git-workflow.md | 29 ++++++++++++++++++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/internals-pl/automation.md b/docs/internals-pl/automation.md index 98b3a95..46fb896 100644 --- a/docs/internals-pl/automation.md +++ b/docs/internals-pl/automation.md @@ -16,6 +16,8 @@ Istnieją zadania wykonywane automatycznie podczas pracy nad Yii: - Uaktualnianie magicznego pliku z typami Mime (`framework/helpers/mimeTypes.php`) z repozytorium Apache HTTPd. Uruchom `./build/build mime-type`, aby uaktualnić plik. + +- Korekta porządku wpisów w pliku CHANGELOG może być przeprowadzona poprzez uruchomienie `./build/build release/sort-changelog framework`. Wszystkie powyższe komendy są uruchamiane w [procesie wydawania nowej wersji](release.md). Mogą być też uruchomione pomiędzy wydaniami, ale nie jest to konieczne. diff --git a/docs/internals-pl/design-decisions.md b/docs/internals-pl/design-decisions.md index 83ec6b4..ffeb3a1 100644 --- a/docs/internals-pl/design-decisions.md +++ b/docs/internals-pl/design-decisions.md @@ -25,3 +25,7 @@ powinna być najpierw zatwierdzona przez głównych deweloperów. Dodatkowo, pomimo tego, że unsigned int podwaja jego zakres, jeśli tabela wymaga tak dużych liczb, bezpieczniej jest używać typu bigint lub mediumint, niż polegać na unsigned. +6. [Klasy pomocnicze vs oddzielne niestatyczne klasy](https://github.com/yiisoft/yii2/pull/12661#issuecomment-251599463) +7. **Łańcuchowanie metod setterów** powinno być unikane, jeśli w klasie znajdują się metody zwracające ważne wartości. + Łańcuchowanie może być wspierane, jeśli klasa jest typu budującego, gdzie wszystkie settery modyfikują jedynie wewnętrzne stany: https://github.com/yiisoft/yii2/issues/13026 + \ No newline at end of file diff --git a/docs/internals-pl/git-workflow.md b/docs/internals-pl/git-workflow.md index 17d6514..f13649a 100644 --- a/docs/internals-pl/git-workflow.md +++ b/docs/internals-pl/git-workflow.md @@ -22,6 +22,8 @@ Jeśli napotkasz na problemy związane z Gitem i GitHubem na systemie operacyjny "Permission Denied (publickey)" ("Odmowa dostępu (klucz publiczny)"), musisz odpowiednio [skonfigurować instalację Gita do pracy z GitHubem](http://help.github.com/linux-set-up-git/). +> Tip: jeśli nie jesteś biegły w używaniu Gita, polecamy doskonałą darmową książkę [Pro Git](https://git-scm.com/book/en/v2) (z polskim tłumaczeniem dla [poprzedniej edycji](https://git-scm.com/book/pl/v1). + ### 2. Dodaj główne repozytorium Yii jako dodatkowy zdalny git nazwany "upstream" Przejdź do folderu, do którego sklonowałeś Yii (zwykle "yii2") i uruchom następującą komendę: @@ -34,15 +36,24 @@ git remote add upstream git://github.com/yiisoft/yii2.git Poniższe kroki nie są wymagane, jeśli chcesz pracować tylko nad tłumaczeniami lub dokumentacją. -- uruchom `composer update`, aby zainstalować wymagane zależności (zakładając, że masz [composera zainstalowanego globalnie](https://getcomposer.org/doc/00-intro.md#globally)). +- uruchom `composer install`, aby zainstalować wymagane zależności (zakładając, że masz [composera zainstalowanego globalnie](https://getcomposer.org/doc/00-intro.md#globally)). > Note: Jeśli otrzymujesz błędu typu `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` (`Problem 1 Wymagany pakiet bower-asset/jquery nie mógł być znaleziony w jakiejkolwiek wersji, być może w nazwie pakietu jest literówka.`), musisz uruchomić komendę `composer global require "fxp/composer-asset-plugin:^1.2.0"` -- uruchom komendę `php build/build dev/app basic`, aby sklonować podstawowy szablon projektu aplikacji i zainstaluj dla niego zależności composera. +Jeśli zamierzasz pracować z JavaScript: + +- uruchom `npm install`, aby zainstalować narzędzia testerskie JavaScript i ich zależności (zakładając, że masz [zainstalowane Node.js i NPM] +(https://nodejs.org/en/download/package-manager/)). + +> Note: testy JavaScript są zależne od biblioteki [jsdom](https://github.com/tmpvar/jsdom), która wymaga Node.js 4 lub nowszego. +Zalecane jest używanie Node.js w wersji 6 lub 7. + +- uruchom komendę `php build/build dev/app basic `, aby sklonować podstawowy szablon projektu aplikacji i zainstaluj dla niego zależności composera. + `` jest URL Twojego forka repozytorium, np. `git@github.com:my_nickname/yii2-app-basic.git`. Jeśli jesteś kontrybutorem głównego kodu frameworka, możesz pominąć wskazywanie forka. Komenda ta zainstaluje normalnie pakiety composera, ale jednocześnie podlinkuje folder yii2 do pobranego wcześniej repozytorium, dzięki czemu otrzymasz instalację jednej instacji całego kodu na raz. - Powtórz ten krok dla zaawansowanego szablonu projektu aplikacji, jeśli chcesz: `php build/build dev/app advanced`. + Powtórz ten krok dla zaawansowanego szablonu projektu aplikacji, jeśli chcesz: `php build/build dev/app advanced `. Ta komenda służy również do aktualizacji zależności; uruchamia wewnętrznie `composer update`. @@ -56,7 +67,8 @@ Poniższe kroki są opcjonalne. ### Testy jednostkowe Możesz uruchomić testy jednostkowe za pomocą komendy `phpunit` w głównym folderze repozytorium. -Jeśli nie posiadasz phpunit zainstalowanego globalnie, użyj zamiast tego komendy `php vendor/bin/phpunit`. +Jeśli nie posiadasz phpunit zainstalowanego globalnie, użyj zamiast tego komendy `php vendor/bin/phpunit` lub +`vendor/bin/phpunit.bat` w przypadku korzystania z systemu Windows. Niektóre testy wymagają przygotowania i skonfigurowania dodatkowych baz danych. Możesz utworzyć plik `tests/data/config.local.php`, aby nadpisać konfigurację ustawioną w `tests/data/config.php`. @@ -64,19 +76,22 @@ aby nadpisać konfigurację ustawioną w `tests/data/config.php`. Możesz ograniczyć testy do grupy tych, nad którymi akurat pracujesz, np. aby uruchomić tylko testy walidatorów i redisa użyj `phpunit --group=validators,redis`. Możesz zobaczyć listę dostępnych grup po wpisaniu `phpunit --list-groups`. +Możesz rozpocząć testy jednostkowe JavaScript, uruchamiając `npm test` w głównym folderze repozytorium. + ### Rozszerzenia Aby pracować nad rozszerzeniami, musisz sklonować ich repozytoria. Stworzyliśmy komendę, która pozwoli Ci to zrobić w prosty sposób: ``` -php build/build dev/ext +php build/build dev/ext ``` -Oczywiście zamiast `` wpisz konkretną jego nazwę, np. `redis`. +gdzie `` jest poprawną nazwą, np. `redis`, a `` jest URL forka rozszerzenia np. `git@github.com:my_nickname/yii2-redis.git`. +Jeśli jesteś kontrybutorem głównego kodu frameworka, możesz pominąć wskazywanie forka. Jeśli chcesz przetestować rozszerzenie w jednym z szablonów projektów, po prostu dodaj je do pliku `composer.json` aplikacji w zwyczajowy sposób, np. dodaj `"yiisoft/yii2-redis": "~2.0.0"` do sekcji `require` w podstawowym szablonie aplikacji. -Uruchomienie `php build/build dev/app basic` zainstaluje rozszerzenie i jego zależności i utworzy symlink do folderu +Uruchomienie `php build/build dev/app basic ` zainstaluje rozszerzenie i jego zależności i utworzy symlink do folderu `extensions/redis`, dzięki czemu możesz pracować bezpośrednio w repozytorium yii2, a nie folderze vendorowym composera. > Note: Również w tym przypadku pamiętaj o fladze `--useHttp`, jak to opisano powyżej. From 950e895fe03f433cd4fa5444dae88bd6e02e240a Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Sun, 2 Apr 2017 21:49:09 +0300 Subject: [PATCH 090/184] Fix phpDocumentor annotations (#13905) [skip ci] --- framework/base/Behavior.php | 2 +- framework/base/Component.php | 2 +- framework/base/Controller.php | 2 +- framework/base/ErrorHandler.php | 2 +- framework/base/Model.php | 2 +- framework/base/Module.php | 6 +++--- framework/base/Security.php | 8 ++++---- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php index df7e244..47d7058 100644 --- a/framework/base/Behavior.php +++ b/framework/base/Behavior.php @@ -23,7 +23,7 @@ namespace yii\base; class Behavior extends Object { /** - * @var Component the owner of this behavior + * @var Component|null the owner of this behavior */ public $owner; diff --git a/framework/base/Component.php b/framework/base/Component.php index 2188084..ee17b7e 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -566,7 +566,7 @@ class Component extends Object /** * Returns all behaviors attached to this component. - * @return Behavior[] list of behaviors attached to this component + * @return Behavior[]|null list of behaviors attached to this component */ public function getBehaviors() { diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 1a8ebd8..84b5141 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -212,7 +212,7 @@ class Controller extends Component implements ViewContextInterface * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that * method will be created and returned. * @param string $id the action ID. - * @return Action the newly created action instance. Null if the ID doesn't resolve into any action. + * @return Action|null the newly created action instance. Null if the ID doesn't resolve into any action. */ public function createAction($id) { diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index 1176343..69e196a 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -38,7 +38,7 @@ abstract class ErrorHandler extends Component */ public $memoryReserveSize = 262144; /** - * @var \Exception the exception that is being handled currently. + * @var \Exception|null the exception that is being handled currently. */ public $exception; diff --git a/framework/base/Model.php b/framework/base/Model.php index 79078d2..621fddc 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -849,7 +849,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab public static function loadMultiple($models, $data, $formName = null) { if ($formName === null) { - /* @var $first Model */ + /* @var $first Model|false */ $first = reset($models); if ($first === false) { return false; diff --git a/framework/base/Module.php b/framework/base/Module.php index 5c86374..e310eec 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -305,7 +305,7 @@ class Module extends ServiceLocator /** * Returns current module version. * If version is not explicitly set, [[defaultVersion()]] method will be used to determine its value. - * @return string the version of this module. + * @return string|callable the version of this module. * @since 2.0.11 */ public function getVersion() @@ -342,7 +342,7 @@ class Module extends ServiceLocator /** * Returns default module version. * Child class may override this method to provide more specific version detection. - * @return string the version of this module. + * @return string|callable the version of this module. * @since 2.0.11 */ protected function defaultVersion() @@ -606,7 +606,7 @@ class Module extends ServiceLocator * Note that this method does not check [[modules]] or [[controllerMap]]. * * @param string $id the controller ID. - * @return Controller the newly created controller instance, or `null` if the controller ID is invalid. + * @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid. * @throws InvalidConfigException if the controller class and its file name do not match. * This exception is only thrown when in debug mode. */ diff --git a/framework/base/Security.php b/framework/base/Security.php index a76103b..6f05681 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -163,7 +163,7 @@ class Security extends Component * @param string $data data to be encrypted * @param bool $passwordBased set true to use password-based key derivation * @param string $secret the encryption password or key - * @param string $info context/application specific information, e.g. a user ID + * @param string|null $info context/application specific information, e.g. a user ID * See [RFC 5869 Section 3.2](https://tools.ietf.org/html/rfc5869#section-3.2) for more details. * * @return string the encrypted data @@ -214,7 +214,7 @@ class Security extends Component * @param string $data encrypted data to be decrypted. * @param bool $passwordBased set true to use password-based key derivation * @param string $secret the decryption password or key - * @param string $info context/application specific information, @see encrypt() + * @param string|null $info context/application specific information, @see encrypt() * * @return bool|string the decrypted data or false on authentication failure * @throws InvalidConfigException on OpenSSL not loaded @@ -280,7 +280,7 @@ class Security extends Component } return $outputKey; } - + $test = @hash_hmac($algo, '', '', true); if (!$test) { throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo); @@ -409,7 +409,7 @@ class Security extends Component * It indicates whether the hash value in the data is in binary format. If false, it means the hash value consists * of lowercase hex digits only. * hex digits will be generated. - * @return string the real data with the hash stripped off. False if the data is tampered. + * @return string|false the real data with the hash stripped off. False if the data is tampered. * @throws InvalidConfigException when HMAC generation fails. * @see hashData() */ From 2a71e251a7dedff18c50075cdc20b9230bb9f031 Mon Sep 17 00:00:00 2001 From: Bizley Date: Sun, 2 Apr 2017 20:49:49 +0200 Subject: [PATCH 091/184] PL guide updated (#13909) [skip ci] --- docs/guide-pl/README.md | 20 +-- docs/guide-pl/caching-overview.md | 3 +- docs/guide-pl/caching-page.md | 6 +- docs/guide-pl/concept-components.md | 2 +- docs/guide-pl/db-active-record.md | 170 +++++++++++++++++++---- docs/guide-pl/images/start-gii-crud-preview.png | Bin 57090 -> 60647 bytes docs/guide-pl/images/start-gii-crud.png | Bin 58150 -> 56045 bytes docs/guide-pl/images/start-gii-model-preview.png | Bin 104451 -> 65510 bytes docs/guide-pl/images/start-gii-model.png | Bin 93741 -> 51340 bytes 9 files changed, 158 insertions(+), 43 deletions(-) diff --git a/docs/guide-pl/README.md b/docs/guide-pl/README.md index b89ce96..8f71531 100644 --- a/docs/guide-pl/README.md +++ b/docs/guide-pl/README.md @@ -147,7 +147,7 @@ Narzędzia wspomagające tworzenie aplikacji * [Pasek debugowania i debuger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) * [Generowanie kodu przy użyciu Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) -* **TBD** [Generowanie dokumentacji API](https://github.com/yiisoft/yii2-apidoc) +* [Generowanie dokumentacji API](https://github.com/yiisoft/yii2-apidoc) Testowanie @@ -179,16 +179,16 @@ Tematy specjalne Widżety ------- -* GridView: **TBD** link to demo page -* ListView: **TBD** link to demo page -* DetailView: **TBD** link to demo page -* ActiveForm: **TBD** link to demo page -* Pjax: **TBD** link to demo page -* Menu: **TBD** link to demo page -* LinkPager: **TBD** link to demo page -* LinkSorter: **TBD** link to demo page +* [GridView](http://www.yiiframework.com/doc-2.0/yii-grid-gridview.html) +* [ListView](http://www.yiiframework.com/doc-2.0/yii-widgets-listview.html) +* [DetailView](http://www.yiiframework.com/doc-2.0/yii-widgets-detailview.html) +* [ActiveForm](http://www.yiiframework.com/doc-2.0/guide-input-forms.html#activerecord-based-forms-activeform) +* [Pjax](http://www.yiiframework.com/doc-2.0/yii-widgets-pjax.html) +* [Menu](http://www.yiiframework.com/doc-2.0/yii-widgets-menu.html) +* [LinkPager](http://www.yiiframework.com/doc-2.0/yii-widgets-linkpager.html) +* [LinkSorter](http://www.yiiframework.com/doc-2.0/yii-widgets-linksorter.html) * [Widżety Bootstrapowe](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/README.md) -* [Widżety Jquery UI](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md) +* [Widżety jQuery UI](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md) Klasy pomocnicze diff --git a/docs/guide-pl/caching-overview.md b/docs/guide-pl/caching-overview.md index d2a05f6..70fd702 100644 --- a/docs/guide-pl/caching-overview.md +++ b/docs/guide-pl/caching-overview.md @@ -2,8 +2,7 @@ Pamięć podręczna ================ Mechanizmy wykorzystujące pamięć podręczną pozwalają na poprawienie wydajności aplikacji sieciowej w tani i efektywny sposób. -Zapisanie mniej lub bardziej statycznych danych w pamięci podręcznej i serwowanie ich stamtąd, zamiast generować je od podstaw przy każdym -wywołaniu, pozwala na znaczne zaoszczędzenie czasu odpowiedzi aplikacji. +Zapisanie statycznych danych w pamięci podręcznej, zamiast generowania ich od podstaw przy każdym wywołaniu, pozwala na znaczne zaoszczędzenie czasu odpowiedzi aplikacji. Zapis pamięci podręcznej może odbywać się na wielu poziomach i w wielu miejscach aplikacji. Po stronie serwera, na niskim poziomie, można wykorzystać pamięć podręczną do zapisania podstawowych danych, takich jak zbiór informacji o najnowszych artykułach pobieranych z bazy danych. diff --git a/docs/guide-pl/caching-page.md b/docs/guide-pl/caching-page.md index 7de78b4..940274b 100644 --- a/docs/guide-pl/caching-page.md +++ b/docs/guide-pl/caching-page.md @@ -5,7 +5,7 @@ Pamięć podręczna stron odnosi się do zapisu zawartości całej strony po str zawartość zostanie wyświetlona od razu z pamięci podręcznej zamiast generować ją ponownie od podstaw. Pamięć podręczna stron jest obsługiwana przez [filtr akcji](structure-filters.md) [[yii\filters\PageCache|PageCache]]. -Poniżej znajdziesz przykładowy sposób użycia w klasie kontrolera: +Poniżej znajdziesz przykładowy sposób użycia go w klasie kontrolera: ```php public function behaviors() @@ -27,13 +27,13 @@ public function behaviors() } ``` -W powyższym przykładzie kod zakłada użycie pamięci tylko dla akcji `index` - zawartość strony powinna zostać zapisana na maksymalnie +W powyższym przykładzie zakładamy użycie pamięci podręcznej tylko dla akcji `index` - zawartość strony powinna zostać zapisana na maksymalnie 60 sekund i powinna różnić się w zależności od wybranego w aplikacji języka. Dodatkowo, jeśli całkowita liczba postów w bazie danych ulegnie zmianie, zawartość pamięci powinna natychmiast stracić ważność i zostać pobrana ponownie. Jak widać, pamięć podręczna stron jest bardzo podobna do [pamięci podręcznej fragmentów](caching-fragment.md). W obu przypadkach można użyć opcji takich jak `duration` (czas ważności), `dependencies` (zależności), `variations` (warianty) oraz `enabled` (flaga aktywowania). -Główną różnicą tych dwóch przypadków jest to, że pamięć podręczna stron jest implemetowana jako [filtr akcji](structure-filters.md), a +Główną różnicą w tych dwóch przypadkach jest to, że pamięć podręczna stron jest implemetowana jako [filtr akcji](structure-filters.md), a pamięć podręczna fragmentów jako [widżet](structure-widgets.md). Oczywiście nic nie stoi na przeszkodzie, aby używać [pamięci podręcznej fragmentów](caching-fragment.md) jak diff --git a/docs/guide-pl/concept-components.md b/docs/guide-pl/concept-components.md index 2feaa22..5db0591 100644 --- a/docs/guide-pl/concept-components.md +++ b/docs/guide-pl/concept-components.md @@ -9,7 +9,7 @@ Trzy główne funkcjonalności, które zapewniają komponenty innym klasom to: * [Behaviory (zachowania)](concept-behaviors.md) Wszystkie razem i każda z tych funkcjonalności osobno zapewnia klasom Yii o wiele większą elastyczność i łatwość użycia. Dla przykładu, -dołączony [[yii\jui\DatePicker|widżet wybierania daty]], komponent interfejsu użytkownika, może być użyty w [widoku](structure-view.md), +dołączony [[yii\jui\DatePicker|widżet wybierania daty]], komponent interfejsu użytkownika, może być użyty w [widoku](structure-views.md), aby wygenerować interaktywny kalendarz: ```php diff --git a/docs/guide-pl/db-active-record.md b/docs/guide-pl/db-active-record.md index 7286662..ef70f84 100644 --- a/docs/guide-pl/db-active-record.md +++ b/docs/guide-pl/db-active-record.md @@ -47,9 +47,19 @@ W tej sekcji przewodnika opiszemy sposób użycia Active Record dla baz relacyjn ## Deklarowanie klas Active Record -Na początek zadeklaruj klasę typu Active Record rozszerzając [[yii\db\ActiveRecord|ActiveRecord]]. Ponieważ każda klasa Active Record -jest powiązana z tabelą bazy danych, należy nadpisać metodę [[yii\db\ActiveRecord::tableName()|tableName()]], aby wskazać -odpowiednią tabelę. +Na początek zadeklaruj klasę typu Active Record rozszerzając [[yii\db\ActiveRecord|ActiveRecord]]. + +### Deklarowanie nazwy tabeli + +Domyślnie każda klasa Active Record jest powiązana ze swoją tabelą w bazie danych. +Metoda [[yii\db\ActiveRecord::tableName()|tableName()]] zwraca nazwę tabeli konwertując nazwę klasy za pomocą [[yii\helpers\Inflector::camel2id()]]. +Możesz przeciążyć tę metodę, jeśli tabela nie jest nazwana zgodnie z tą konwencją. + +Identycznie zastosowany może być domyślny prefiks tabeli [[yii\db\Connection::$tablePrefix|tablePrefix]]. Przykładowo, jeśli +[[yii\db\Connection::$tablePrefix|tablePrefix]] to `tbl_`, tabelą klasy `Customer` staje się `tbl_customer`, a dla `OrderItem` jest to `tbl_order_item`. + +Jeśli nazwa tabeli zostanie podana jako `{{%NazwaTabeli}}`, znak procent `%` zostanie zamieniony automatycznie na prefiks tabeli. +Dla przykładu, `{{%post}}` staje się `{{tbl_post}}`. Nawiasy wokół nazwy tabeli są używane dla odpowiedniego [podawania nazw w kwerendach SQL](db-dao.md#quoting-table-and-column-names). W poniższym przykładzie deklarujemy klasę Active Record nazwaną `Customer` dla tabeli `customer` w bazie danych. @@ -68,11 +78,12 @@ class Customer extends ActiveRecord */ public static function tableName() { - return 'customer'; + return '{{customer}}'; } } ``` +### Aktywne rekordy nazywane są "modelami" Instancje Active Record są traktowane jak [modele](structure-models.md). Z tego powodu zwykle dodajemy klasy Active Record do przestrzeni nazw `app\models` (lub innej, przeznaczonej dla klas modeli). @@ -444,6 +455,26 @@ $customer->loadDefaultValues(); ``` +### Rzutowanie typów atrybutów + +Po wypełnieniu rezultatem kwerendy, [[yii\db\ActiveRecord]] przeprowadza automatyczne rzutowanie typów na wartościach swoich atrybutów, +używając do tego celu informacji zawartych w [schemacie tabeli bazy danych](db-dao.md#database-schema). Pozwala to na prawidłowe przedstawienie +danych pobranych z kolumny tabeli zadeklarowanej jako liczba całkowita, w postaci wartości typu PHP integer w instancji klasy ActiveRecord (typu boolean jako boolean itp.). +Mechanizm rzutowania ma jednak kilka ograniczeń: + +* Wartości typu zmiennoprzecinkowego nie są konwertowane na float, a zamiast tego są przedstawiane jako łańcuch znaków, aby zachować dokładność ich liczbowej prezentacji. +* Konwersja typu integer zależy od zakresu liczb całkowitych używanego systemu operacyjnego. + Wartości kolumn zadeklarowanych jako 'unsigned integer' lub 'big integer' będą przekonwertowane do PHP integer tylko na systemach 64-bitowych, + a na 32-bitowych będą przedstawione jako łańcuchy znaków. + +Zwróć uwagę na to, że rzutowanie typów jest wykonywane tylko podczas wypełniania instancji ActiveRecord rezultatem kwerendy. Automatyczna konwersja nie jest przeprowadzana +dla wartości załadowanych poprzez żądanie HTTP lub ustawionych bezpośrednio dla właściwości klasy. +Schemat tabeli będzie również użyty do przygotowania instrukcji SQL przy zapisywaniu danych ActiveRecord, aby upewnić się, że wartości są przypisane w kwerendzie z prawidłowymi typami. +Atrybuty instancji ActiveRecord nie będą jednak przekonwertowane w procesie zapisywania. + +> Tip: możesz użyć [[yii\behaviors\AttributeTypecastBehavior]], aby skonfigurować proces rzutowania typów dla wartości atrybutów w momencie ich walidacji lub zapisu. + + ### Aktualizowanie wielu wierszy jednocześnie Metody przedstawione powyżej działają na pojedynczych instancjach Active Record, dodając lub aktualizując indywidualne wiersze tabeli. @@ -575,9 +606,16 @@ try { } catch(\Exception $e) { $transaction->rollBack(); throw $e; +} catch(\Throwable $e) { + $transaction->rollBack(); + throw $e; } ``` +> Note: w powyższym kodzie znajdują się dwa bloki catch dla kompatybilności +> z PHP 5.x i PHP 7.x. `\Exception` implementuje [interfejs `\Throwable`](http://php.net/manual/en/class.throwable.php) +> od PHP 7.0, zatem można pominąć część z `\Exception`, jeśli Twoja aplikacja używa tylko PHP 7.0 lub wyższego. + Drugi sposób polega na utworzeniu listy operacji bazodanowych, które wymagają transakcji za pomocą metody [[yii\db\ActiveRecord::transactions()|transactions()]]. Dla przykładu: @@ -961,7 +999,7 @@ W powyższym przykładzie modyfikujemy relacyjną kwerendę dodając warunek ze > > ```php > $orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); -> // $orders[0]->customer ma zawsze wartość null. Aby rozwiązać ten problem, należy użyć: +> // $orders[0]->customer ma zawsze wartość `null`. Aby rozwiązać ten problem, należy użyć: > $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); > ``` @@ -1065,6 +1103,16 @@ Od wersji 2.0.7, Yii udostępnia do tego celu skróconą metodę. Możliwe jest $query->joinWith(['orders o'])->orderBy('o.id'); ``` +Powyższy kod działa dla prostych relacji. Jeśli jednak potrzebujesz aliasu dla tabeli dołączonej w zagnieżdżonej relacji, +np. `$query->joinWith(['orders.product'])`, musisz rozwinąć wywołanie `joinWith` jak w poniższym przykładzie: + +```php +$query->joinWith(['orders o' => function($q) { + $q->joinWith('product p'); + }]) + ->where('o.amount > 100'); +``` + ### Odwrócone relacje Deklaracje relacji są zazwyczaj obustronne dla dwóch klas Active Record. Przykładowo `Customer` jest powiązany z `Order` poprzez relację `orders`, @@ -1253,10 +1301,10 @@ Domyślnie wszystkie kwerendy Active Record używają klasy [[yii\db\ActiveQuery należy nadpisać metodę [[yii\db\ActiveRecord::find()|find()]], aby zwracała instancję żądanej klasy kwerend. Przykład: ```php +// plik Comment.php namespace app\models; use yii\db\ActiveRecord; -use yii\db\ActiveQuery; class Comment extends ActiveRecord { @@ -1265,33 +1313,39 @@ class Comment extends ActiveRecord return new CommentQuery(get_called_class()); } } - -class CommentQuery extends ActiveQuery -{ - // ... -} ``` Od tego momentu, za każdym razem, gdy wykonywana będzie kwerenda (np. `find()`, `findOne()`) lub pobierana relacja (np. `hasOne()`) klasy `Comment`, praca będzie odbywać się na instancji `CommentQuery` zamiast `ActiveQuery`. -> Tip: Dla dużych projektów rekomendowane jest, aby używać własnych, odpowiednio dopasowanych do potrzeb, klas kwerend, dzięki czemu klasy Active Record -> pozostają przejrzyste. - -Możesz dopasować klasę kwerend do własnych potrzeb na wiele kreatywnych sposobów. Przykładowo, możesz zdefiniować nowe metody konstruujące zapytanie: +Teraz należy zdefiniować klasę `CommentQuery`, którą można dopasować do własnych kreatywnych potrzeb, dzięki czemu budowanie zapytań bazodanowych będzie o wiele bardziej ułatwione. Dla przykładu, ```php +// plik CommentQuery.php +namespace app\models; + +use yii\db\ActiveQuery; + class CommentQuery extends ActiveQuery { + // dodatkowe warunki relacyjnej kwerendy dołączone jako domyślne (ten krok można pominąć) + public function init() + { + $this->andOnCondition(['deleted' => false]); + parent::init(); + } + + // ... dodaj zmodyfikowane metody kwerend w tym miejscu ... + public function active($state = true) { - return $this->andWhere(['active' => $state]); + return $this->andOnCondition(['active' => $state]); } } ``` -> Note: Zwykle, zamiast wywoływać metodę [[yii\db\ActiveQuery::where()|where()]], powinno się używać metody -> [[yii\db\ActiveQuery::andWhere()|andWhere()]] lub [[yii\db\ActiveQuery::orWhere()|orWhere()]], aby dołączać kolejne warunki zapytania w +> Note: Zwykle, zamiast wywoływać metodę [[yii\db\ActiveQuery::onCondition()|onCondition()]], powinno się używać metody +> [[yii\db\ActiveQuery::andOnCondition()|andOnCondition()]] lub [[yii\db\ActiveQuery::orOnCondition()|orOnCondition()]], aby dołączać kolejne warunki zapytania w > konstruktorze kwerend, dzięki czemu istniejące warunki nie zostaną nadpisane. Powyższy przykład pozwala na użycie następującego kodu: @@ -1301,6 +1355,9 @@ $comments = Comment::find()->active()->all(); $inactiveComments = Comment::find()->active(false)->all(); ``` +> Tip: Dla dużych projektów rekomendowane jest, aby używać własnych, odpowiednio dopasowanych do potrzeb, klas kwerend, dzięki czemu klasy Active Record +> pozostają przejrzyste. + Możesz także użyć nowych metod budowania kwerend przy definiowaniu relacji z `Comment` lub wykonywaniu relacyjnych kwerend: ```php @@ -1312,11 +1369,18 @@ class Customer extends \yii\db\ActiveRecord } } -$customers = Customer::find()->with('activeComments')->all(); +$customers = Customer::find()->joinWith('activeComments')->all(); // lub alternatywnie - -$customers = Customer::find()->with([ +class Customer extends \yii\db\ActiveRecord +{ + public function getComments() + { + return $this->hasMany(Comment::className(), ['customer_id' => 'id']); + } +} + +$customers = Customer::find()->joinWith([ 'comments' => function($q) { $q->active(); } @@ -1394,8 +1458,9 @@ $customers = Customer::find() ->all(); ``` -Wadą tej metody jest to, że jeśli informacja nie może zostać pobrana za pomocą kwerendy SQL, musi ona być obliczona oddzielnie, -co oznacza rónież, że świeżo zapisane rekordy nie będą zawierały informacji z dodatkowych pól: +Wadą tej metody jest to, że jeśli informacja nie może zostać pobrana za pomocą kwerendy SQL, musi ona być obliczona oddzielnie. +Zatem po pobraniu konkretnego wiersza tabeli za pomocą regularnej kwerendy bez dodatkowej instrukcji select, niemożliwym będzie +zwrócenie wartości dla dodatkowych pól. Tak samo stanie się w przypadku świeżo zapisanych rekordów. ```php $room = new Room(); @@ -1403,7 +1468,7 @@ $room->length = 100; $room->width = 50; $room->height = 2; -$room->volume; // ta wartość będzie wynosić null ponieważ nie została jeszcze zadeklarowana +$room->volume; // ta wartość będzie wynosić `null` ponieważ nie została jeszcze zadeklarowana ``` Używając magicznych metod [[yii\db\BaseActiveRecord::__get()|__get()]] i [[yii\db\BaseActiveRecord::__set()|__set()]], możemy emulować @@ -1438,9 +1503,9 @@ class Room extends \yii\db\ActiveRecord } ``` -Kiedy kwerenda nie zapewnii wartości kubatury, model będzie w stanie automatycznie ją obliczyć, używając swoich atrybutów. +Kiedy kwerenda nie zapewni wartości kubatury, model będzie w stanie automatycznie ją obliczyć, używając swoich atrybutów. -Podobnego sposobu można użyć na dodatkowych polach zależnych od danych tabel relacji: +Możesz obliczyć sumaryczne pola rónież korzystając ze zdefiniowanych relacji: ```php class Customer extends \yii\db\ActiveRecord @@ -1459,7 +1524,7 @@ class Customer extends \yii\db\ActiveRecord } if ($this->_ordersCount === null) { - $this->setOrdersCount(count($this->orders)); + $this->setOrdersCount($this->getOrders()->count()); // oblicz sumę na żądanie z relacji } return $this->_ordersCount; @@ -1473,3 +1538,54 @@ class Customer extends \yii\db\ActiveRecord } } ``` + +Dla powyższego kodu, kiedy 'ordersCount' występuje w instrukcji 'select' - `Customer::ordersCount` zostanie wypełnione +rezultatem kwerendy, w pozostałych przypadkach zostanie obliczone na żądanie używając relacji `Customer::orders`. + +Takie podejście może być równie dobrze użyte do stworzenia skrótów dla niektórych danych relacji, zwłąszcza tych służących do obliczania sumarycznego. +Przykładowo: + +```php +class Customer extends \yii\db\ActiveRecord +{ + /** + * Deklaracja wirtualnej właściwości tylko do odczytu dla danych sumarycznych. + */ + public function getOrdersCount() + { + if ($this->isNewRecord) { + return null; // to pozwala na uniknięcie uruchamiania wyszukującej kwerendy dla pustych kluczy głównych + } + + return empty($this->ordersAggregation) ? 0 : $this->ordersAggregation[0]['counted']; + } + + /** + * Deklaracja zwykłej relacji 'orders'. + */ + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } + + /** + * Deklaracja nowej relacji bazującej na 'orders', ale zapewniającej pobranie danych sumarycznych. + */ + public function getOrdersAggregation() + { + return $this->getOrders() + ->select(['customer_id', 'counted' => 'count(*)']) + ->groupBy('customer_id') + ->asArray(true); + } + + // ... +} + +foreach (Customer::find()->with('ordersAggregation')->all() as $customer) { + echo $customer->ordersCount; // wyświetla dane sumaryczne z relacji bez dodatkowej kwerendy dzięki gorliwemu pobieraniu +} + +$customer = Customer::findOne($pk); +$customer->ordersCount; // wyświetla dane sumaryczne z relacji pobranej leniwie +``` diff --git a/docs/guide-pl/images/start-gii-crud-preview.png b/docs/guide-pl/images/start-gii-crud-preview.png index 85c2355f2eb8e006b38d82dfea854f007744083b..a8912e28293c086546ae730d210a46273e00ad6e 100644 GIT binary patch literal 60647 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
  • q2{ySK}V3YHmqu_sHE{?J#;#z(7nz?J& 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�~ZEND@N&8f+qOpG-+iuAnKu#pI0gjllS;o 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 literal 57090 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Ⓢ` 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�?Qt$bDQa7W}PjjpOmDKO<|NBWD@%N;T{zFnXkSN5r<1cP?&Wxw- zk+^j6<#~GZNv{?eV9s6Ya}DjvfB`P_A^&`|#lq?zMq85q*=S4PKOJrP7X9CfwzTH@ zywGaTZD0Qpm=?I|txzwhTu9H;8+riJS&VnexH$lSKAtYx;TOiZF9=^tz?B>4;U7_o zshK|tfNs>8J^%7S%&%nT(T$|Z2v&MF?hisQB=?T+Wrs#W!q9KDIVJk*<$xP4nLJci z;>7$}{qYpH;Boy&IGf&caX}_|sCEWN<^INczRf@eyE;fu+iN*g$tK=Y4}NCfOe{Mo z@ALv=kfH(l1LDCVq;~M%YRMW8HqnlRUVPFzrHAdh&5vd`fQf# zH}wm+wC4CqGFQ=ob0Ns1dFv3l=vzS1MKIi+M&U#Oy9$1R&nCMVA#PRxBhyX*mF@pb zkLxC?@e|Z8q!oH1BqTN4K5q$%p6%p-bZe(rPuUB-mm6f2StP|&F&3dxhNUVMAyLDb z>Bj?$N<><5pvwSei+?TAudiKAwmdTvt$vX%@;+9+#jpxmr!# zrHB|F82Dkt6ZT>Nh(3*t#YIL+pJY{4jjHSdPbR2-{pz4_E`yrtXue`FLTf>GWo~dR zLTPDU>>pC}7i+!0)!z#_zRxs9nJ4c?x_V})K!rxO;3V7!gW%YtG9A4nN;0t@`1bsk zZ~DdokX~qCqeI7{O%DYKfc|IAUPOhiwTn^T02+=Gl;CsdnhPX0_yo?mt_~_p6UA0j z_b4K;JBNe0V7)5)&u*Wuy8-!NB+vPN+ihN^FnKL!{&jhFgvm{uAeQZI3hIq_rt6pf zbmKV8@)`6j)Z6+j%lp16iiS{{Db$QHOyOwkuT^*c?^O4;XZlz&0PGGngI<#3bK)=Q zCZqXxLNkUwXf&TRCf-D5803{$C!9x8DtSDC8c^{fo@>M3M^dmXe~F|Zt?~r^n>eft z`871&%RW^6-^O8uqqu^J@stGhS}R=xKW@y5%D^qy$lY-9&{1`*c8lnb;)q+x=2|4C zRo;%JXvZ%G9?q+W$xOh&0?ZQ_5fLh$^ZxEsK@R!xOP1r&2pf&6!nd*9w3qr#(qgof z%GpcBi@U;sA-N|FEx?0Tu(-a?sT8m!!ViPC^6PfbTY|GAxCq4BusUmcr&V@a6k_6f z|Bzown*mBcT~ZuiL-BQ8)p{p>t*uloQ@8W(6;RNo3)12TcjA1 zY?rzUqqT~2Z@j*y4P=wntF*1%yE;0u^YhF8oZHD=MD%ZZww2ON97;)l2^>`g1v99~ zz11D6l?p?PEB(n@TiR!c1J-&~BtvWp@>k-YtjQ&+gdiz3iTT~pLAAo2n?o3m^nOfntD7zFR|p$BF?B4_1JMb z7**_TrNi~m^XAbFk|`qNXpbnGbpstx`z8UudD$3z?z}hZVuE8&5p7rSVjlQ4SJEIa zmIw25yEl!}y-@pkIP>_jSmVOht<{_u)AV`|i#mbHm|mgZf1y`ArT_P3yUznA zc$J^d2Vw%r4n{x#oWHmM3w!?4mEat)fuQ+$$oc%G3-Ipw&yBn15pV#2qtA2bz^nG( z_$$)SXg=ocj4a~ea{|Aa#8LW<<1;76cQ*ET%5twBs=l*ve8|i7go|gI@I?-=0qyw) zlJ>^>j%GI257o@9jPZCLJ?7(l#KZYW=;31_;m16}kN96HWp17CsGPKtRKfGNKK}=1 CwRDL9 diff --git a/docs/guide-pl/images/start-gii-crud.png b/docs/guide-pl/images/start-gii-crud.png index 77c1ada18a91c00a21b3f5c344e797de635f54b7..948e6cd131d1e4bc048db5014bcf139cdfa34fc3 100644 GIT binary patch literal 56045 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 literal 58150 zcmd42Wl)=8(>6?%wm@l%dnv`exTY-y3KS^rQYdc4ofKN2xVyW%YoNG8aCZq#NRU91 zH*N3dx!)h(uW#m?`K}pet|1%FJ$Ls!_BeN!pw9}@cu&ZmU}0h5$$b2vjD>X%h5504 zbQklRmx+?|SXdhwG9TWlx-A^0;k{K=L!eJu&-uSY88W|fezz+O(l(^84{FP7Gqf=* zYfPz5F(jVhsJtO?xz|8A`*T6^fYUZ8&Zs=|vAQ39Yyf8#Lm^k<_g74UFNd*s(4M!UAafzsJSK|>Df1mU{; z8PffaTtam9IZc-L_bkmBx7gxknnpY61fd^zb!Xpoqx@GK}r!HN0iXBS?OlYpCC{DOjq4I@Y8GB!Q^Hi%e}%@Rvw0- zdByPb%T$W2XGjPCy#kbcfAiO^U+>lRr&OCS!<)n&|5dP~(sV5k6xMpsHj$>!Pf46H zh0x`X7}-z(XCMNHzSwKQxW2DdrU(b|gn?6s%19{iBo9L~Oi1c`LwZbAJThLIFISLa z^%D}H9R|cKQ1J`-+CP#1%-GT2AqTW_pB3S70O;_`Wq!WZjJ6&sA_~2=}9CVM8LT?*F({E0qC-x0oge$TRzfEGz z{9dt1Z9(6|%a#k9Wl?4H=b%1D)!5Cd@~i4m#?Jg(+HjoI4KhZ%1$2d^ei<`46S9^) zn^Zwjx-`Jd&gbsCroS4a z86fm5(sl4FNS}}fS*gBavVge5@Y7a5WX9%2xQi9RDm!9q)mC3fI=z1_kT>^UWWO4qBr+ZKaqD#0?`<@G0XKtvz!Y*l@+3{bKo;!|ClV#22x z8F{p8ovYJU?yqD^;fLKm(qloLC975SH0%~uLU4$u04M5WdaWb;seawNy~BBC8#JVb zozE;dpfc&79($Y^Js3~7PZYKmv{;RN_O5->5#P0UN|NCP*jSXrAri=xwaVNy=ugh# z2(&}OaNl46L5%REdnlhfo66x)&XX&pUp|(EwuG$}a(*DAFr-;aI~sB^hxuGx@H``HtXX13_QqwtdX*Bjlg$oYZ(_Yi`$U(qZVSxT-MF=84C*#XUCj1@@CnkLtzK z1lNWg%f&Q`Et&ATwDazyEv6qM+pA@53p1Kl$MyIA>ezArp&VkYmVPDW%gB!7V~>pt z{hguNl`|FArHrz~8#4my{ng!yy7C1Neei(xZMN&29b>=N)K37_yduU|jX*}eZBCVL zglkYwv|=j>wv7n4p@@nPB-Wa5Zbqy2AxRkHtnpUvgwl;s1LpqionsT5^keUE!BjReM z*x0lGUIGMP(Gw*v{BF8oetPwU4nLIvKzh270g0KW$1V;>%`Vws|JCyQW87?5!|tHK zXg%DuK0Dz$+8B#CV`!)9(YwofKyaA&z`gg+ue)|Cwxt+%({<=?7ViLSed$1C7!xL8-#YNa3yboA^vJnE6a z@*6U8oTCiC<@n;dwzcfqKs1(gLQX-$l(TWOomLCeC`5;smn#U>bu`q-#iyzJY#-{i z@qe@aiQ;@c*XR+`$L^fKdE(t5=b#8L>-st0zJ37NOy1x;Q21OW79R71fjuZsQ6GSs z52L|XW@a3@@K#8wz6ktLx9ZLB=2P>cI>Xc03(mQ*(FXsa5j=mE_;va--SkcA!@s-0 z75u@9l%aI*w&L;6YJXo(RN&b~Q|{rYxNVAtr30pPd>%vTQbx81dmI*Zs4lDs&aEhV zERt{zr62FjUb?VcTqxZ~xH>aE!~*FPo!s_uQYj;!M8~2CGCUU-rUON7%ZeI3*2J1r z+sHNdcpTq1WP;ARd{gL!wtRn}e~2sgj58jzuj&AUqfr-|W)Z*(Jd;5hM>}F5x0^x@ z#YmXP?B$@MYsA+XT+4&Rs1C-oyTsvxE1w@j@G=jSb zOG|^5f=cCBVyNb{+>6&3R3@xhvo3k;<{RCj`pN{#^+g)B7;IF$tq#N{JqIXYa^DXW zsau^L0PZhgX}j=ltDmoC^2@)j9)U%n9izKirB>=Hb6Hv58RH;=K_mI6pKgAU!j1~fh2y

    FAd+grAg?VjLUO=;i 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 R`u(|2o$&m<{#a=Ge*wTXaY6t9 literal 104451 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 diff --git a/docs/guide-pl/images/start-gii-model.png b/docs/guide-pl/images/start-gii-model.png index 59c16a477e9cb789847e622dc9e1e9a70e3aa20b..9c506dee00ffd810fea8a8ac7cb2fd6374713121 100644 GIT binary patch literal 51340 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
    `. Jak w przypadku każdego widżetu, możesz określić kilka opcji z jakimi widżet powinien być skonfigurowany przez przekazanie tablicy do metody `begin`. @@ -110,25 +117,75 @@ tak jak było to zrobione w przykładzie wyżej z [[yii\helpers\Html::submitButt > } > ``` -Tworzenie listy rozwijanej ---------------------- +Tworzenie list +-------------- -Możemy użyć metody klasy ActiveForm [[yii\widgets\ActiveForm::dropDownList()|dropDownList()]] do utworzenia rozwijanej listy: +Wyróżniamy trzy typy list: +* Listy rozwijane +* Listy opcji typu radio +* Listy opcji typu checkbox + +Aby stworzyć listę, musisz najpierw przygotować jej elementy. Można to zrobić ręcznie: ```php -use app\models\ProductCategory; +$items = [ + 1 => 'item 1', + 2 => 'item 2' +] +``` + +lub też pobierając elementy z bazy danych: -/* @var $this yii\web\View */ +```php +$items = Category::find() + ->select(['label']) + ->indexBy('id') + ->column(); +``` + +Elementy `$items` muszą być następnie przetworzone przez odpowiednie widżety list. +Wartość pola formularza (i aktualnie aktywny element) będzie automatycznie ustawiony przez aktualną wartość atrybutu `$model`. + +#### Tworzenie listy rozwijanej + +Możemy użyć metody klasy ActiveForm [[yii\widgets\ActiveForm::dropDownList()|dropDownList()]] do utworzenia rozwijanej listy: + +```php /* @var $form yii\widgets\ActiveForm */ -/* @var $model app\models\Product */ -echo $form->field($model, 'product_category')->dropdownList( - ProductCategory::find()->select(['category_name', 'id'])->indexBy('id')->column(), - ['prompt'=>'Select Category'] +echo $form->field($model, 'category')->dropdownList([ + 1 => 'item 1', + 2 => 'item 2' + ], + ['prompt'=>'Wybierz kategorię'] ); ``` -Wartość z Twojego modelu będzie automatycznie wybrana po wyświetleniu formularza. +#### Tworzenie radio listy + +Do stworzenia takiej listy możemy użyć metody ActiveField [[\yii\widgets\ActiveField::radioList()]]: + +```php +/* @var $form yii\widgets\ActiveForm */ + +echo $form->field($model, 'category')->radioList([ + 1 => 'radio 1', + 2 => 'radio 2' +]); +``` + +#### Tworzenie checkbox listy + +Do stworzenia takiej listy możemy użyć metody ActiveField [[\yii\widgets\ActiveField::checkboxList()]]: + +```php +/* @var $form yii\widgets\ActiveForm */ + +echo $form->field($model, 'category')->checkboxList([ + 1 => 'checkbox 1', + 2 => 'checkbox 2' +]); +``` Praca z Pjaxem ----------------------- diff --git a/docs/guide-pl/input-validation.md b/docs/guide-pl/input-validation.md index f1514d7..e705be5 100644 --- a/docs/guide-pl/input-validation.md +++ b/docs/guide-pl/input-validation.md @@ -317,8 +317,10 @@ Wbudowany walidator jest zdefiniowaną w modelu metodą lub funkcją anonimową. /** * @param string $attribute atrybut podlegający walidacji * @param mixed $params wartość parametru podanego w zasadzie walidacji + * @param \yii\validators\InlineValidator $validator powiązana instancja InlineValidator + * Ten parametr jest dostępny od wersji 2.0.11. */ -function ($attribute, $params) +function ($attribute, $params, $validator) ``` Jeśli atrybut nie przejdzie walidacji, metoda/funkcja powinna wywołać metodę [[yii\base\Model::addError()|addError()]] do zapisania wiadomości o błędzie w modelu, @@ -341,7 +343,7 @@ class MyForm extends Model ['country', 'validateCountry'], // Wbudowany walidator zdefiniowany jako funkcja anonimowa - ['token', function ($attribute, $params) { + ['token', function ($attribute, $params, $validator) { if (!ctype_alnum($this->$attribute)) { $this->addError($attribute, 'Token musi zawierać litery lub cyfry.'); } @@ -349,7 +351,7 @@ class MyForm extends Model ]; } - public function validateCountry($attribute, $params) + public function validateCountry($attribute, $params, $validator) { if (!in_array($this->$attribute, ['USA', 'Web'])) { $this->addError($attribute, 'Wybrany kraj musi być jednym z: "USA", "Web".'); @@ -358,6 +360,14 @@ class MyForm extends Model } ``` +> Note: Począwszy od wersji 2.0.11 możesz użyć [[yii\validators\InlineValidator::addError()]], aby dodać błędy bezpośrednio. W tym sposobie treść błędu +> może być sformatowana bezpośrednio za pomocą [[yii\i18n\I18N::format()]]. Użyj `{attribute}` i `{value}` w treści błędu, aby odwołać się odpowiednio +> do etykiety atrybutu (bez konieczności pobierania jej ręcznie) i wartości atrybutu: +> +> ```php +> $validator->addError($this, $attribute, 'Wartość "{value}" nie jest poprawna dla {attribute}.'); +> ``` + > Note: Domyślnie wbudowane walidatory nie zostaną zastosowane, jeśli ich powiązane atrybuty otrzymają puste wartości lub wcześniej nie przeszły którejś z zasad walidacji. > Jeśli chcesz się upewnić, że zasada zawsze zostanie zastosowana, możesz skonfigurować właściwość [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] i/lub > [[yii\validators\Validator::skipOnError|skipOnError]], przypisując jej wartość `false` w deklaracji zasady walidacji. Dla przykładu: @@ -425,6 +435,118 @@ class EntryForm extends Model } ``` +## Walidacja wielu atrybutów na raz + +Zdarza się, że walidatory sprawdzają wiele atrybutów jednocześnie. Rozważmy następujący formularz: + +```php +class MigrationForm extends \yii\base\Model +{ + /** + * Kwota minimalnych funduszy dla jednej dorosłej osoby + */ + const MIN_ADULT_FUNDS = 3000; + /** + * Kwota minimalnych funduszy dla jednego dziecka + */ + const MIN_CHILD_FUNDS = 1500; + + public $personalSalary; + public $spouseSalary; + public $childrenCount; + public $description; + + public function rules() + { + return [ + [['personalSalary', 'description'], 'required'], + [['personalSalary', 'spouseSalary'], 'integer', 'min' => self::MIN_ADULT_FUNDS], + ['childrenCount', 'integer', 'min' => 0, 'max' => 5], + [['spouseSalary', 'childrenCount'], 'default', 'value' => 0], + ['description', 'string'], + ]; + } +} +``` + +### Tworzenie walidatora + +Powiedzmy, że chcemy sprawdzić, czy dochód rodziny jest wystarczający do utrzymania dzieci. W tym celu możemy utworzyć wbudowany walidator +`validateChildrenFunds`, który będzie uruchamiany tylko jeśli `childrenCount` będzie większe niż 0. + +Zwróć uwagę na to, że nie możemy użyć wszystkich walidowanych atrybutów (`['personalSalary', 'spouseSalary', 'childrenCount']`) przy dołączaniu walidatora. +Wynika to z tego, że ten sam walidator będzie uruchomiony dla każdego z atrybutów oddzielnie (łącznie 3 razy), a musimy użyć go tylko raz dla całego zestawu atrybutów. + +Możesz użyć dowolnego z tych atrybutów zamiast podanego poniżej (lub też tego, który uważasz za najbardziej tu odpowiedni): + +```php +['childrenCount', 'validateChildrenFunds', 'when' => function ($model) { + return $model->childrenCount > 0; +}], +``` + +Implementacja `validateChildrenFunds` może wyglądać następująco: + +```php +public function validateChildrenFunds($attribute, $params) +{ + $totalSalary = $this->personalSalary + $this->spouseSalary; + // Podwój minimalny fundusz dorosłych, jeśli ustalono zarobki współmałżonka + $minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS; + $childFunds = $totalSalary - $minAdultFunds; + if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) { + $this->addError('childrenCount', 'Twoje zarobki nie są wystarczające, aby utrzymać dzieci.'); + } +} +``` + +Możesz zignorować parametr `$attribute`, ponieważ walidacja nie jest powiązana bezpośrednio tylko z jednym atrybutem. + + +### Dodawanie informacji o błędach + +Dodawanie błędów walidacji w przypadku wielu atrybutów może różnić się w zależności od ustalonej metodyki pracy z formularzami: + +- Można wybrać najbardziej w naszej opinii pole i dodać błąd do jego atrybutu: + +```php +$this->addError('childrenCount', 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.'); +``` + +- Można wybrać wiele ważnych odpowiednich atrybutów lub też wszystkie i dodać ten sam błąd do każdego z nich. Możemy przechować +treść w oddzielnej zmiennej przed przekazaniem jej do `addError`, aby nie powtarzać się w kodzie (zasada DRY - Don't Repeat Yourself). + +```php +$message = 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.'; +$this->addError('personalSalary', $message); +$this->addError('wifeSalary', $message); +$this->addError('childrenCount', $message); +``` + +Lub też użyć pętli: + +```php +$attributes = ['personalSalary, 'wifeSalary', 'childrenCount']; +foreach ($attributes as $attribute) { + $this->addError($attribute, 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.'); +} +``` + +- Można też dodać ogólny błąd (niepowiązany z żadnym szczególnym atrybutem). Do tego celu możemy wykorzystać nazwę nieistniejącego atrybutu, +na przykład `*`, ponieważ to, czy atrybut istnieje, nie jest sprawdzane w tym kroku. + +```php +$this->addError('*', 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.'); +``` + +W rezultacie takiej operacji nie zobaczymy błędu zaraz obok pól formularza. Aby go wyświetlić, możemy dodać do widoku podsumowanie błędów formularza: + +```php +errorSummary($model) ?> +``` + +> Note: Tworzenie walidatora operującego na wielu atrybutach jednocześnie jest dobrze opisane w [książce kucharskiej społeczności Yii](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md). + ## Walidacja po stronie klienta @@ -496,6 +618,22 @@ Możesz również wyłączyć ten rodzaj walidacji dla konkretnego pola, przez u [[yii\widgets\ActiveField::enableClientValidation|enableClientValidation]] na `false`. Jeśli właściwość `enableClientValidation` zostanie skonfigurowana na poziomie pola formularza i w samym formularzu jednocześnie, pierwszeństwo będzie miała opcja określona w formularzu. +> Info: Od wersji 2.0.11 wszystkie walidatory rozszerzające klasę [[yii\validators\Validator]] używają opcji klienta przekazywanych +> z oddzielnej metody - [[yii\validators\Validator::getClientOptions()]]. Możesz jej użyć: +> +> - jeśli chcesz zaimplementować swoją własną walidację po stronie klienta, ale pozostawić synchronizację z opcjami walidatora po stronie serwera; +> - do rozszerzenia lub zmodyfikowania dla uzyskania specjalnych korzyści: +> +> ```php +> public function getClientOptions($model, $attribute) +> { +> $options = parent::getClientOptions($model, $attribute); +> // Zmodyfikuj $options w tym miejscu +> +> return $options; +> } +> ``` + ### Implementacja walidacji po stronie klienta diff --git a/docs/guide-pl/intro-upgrade-from-v1.md b/docs/guide-pl/intro-upgrade-from-v1.md index 680c163..2db75e4 100644 --- a/docs/guide-pl/intro-upgrade-from-v1.md +++ b/docs/guide-pl/intro-upgrade-from-v1.md @@ -513,4 +513,5 @@ Zapoznaj się z sekcją dotyczącą [ID kontrolerów](structure-controllers.md#c Korzystanie z Yii 1.1 i 2.x jednocześnie ---------------------------------------- -Jeśli chciałbyś skorzystać z kodu napisanego dla Yii 1.1 w aplikacji Yii 2.0, prosimy o zapoznanie się z sekcją [Praca z kodem zewnętrznym](tutorial-yii-integration.md). +Jeśli chciałbyś skorzystać z kodu napisanego dla Yii 1.1 w aplikacji Yii 2.0, +prosimy o zapoznanie się z sekcją [Używanie Yii 1.1 i 2.0 razem](tutorial-yii-integration.md#using-both-yii2-yii1). diff --git a/docs/guide-pl/intro-yii.md b/docs/guide-pl/intro-yii.md index 9aedf66..fe80a41 100644 --- a/docs/guide-pl/intro-yii.md +++ b/docs/guide-pl/intro-yii.md @@ -30,12 +30,10 @@ oraz ActiveRecord dla baz danych relacyjnych i NoSQL, wsparcia dla tworzenia RES Dodatkowo Yii wykorzystuje architekturę rozszerzeń, dzięki czemu możesz w prosty sposób stworzyć i opublikować swoje własne moduły i widżety. * Podstawowym celem, do którego Yii zawsze dąży, jest wysoka wydajność. -Yii nie jest efektem pracy pojedynczego programisty - projekt wspiera zarówno [grupa doświadczonych deweloperów][about_yii], jak i ogromna społeczność programistyczna, nieustannie +Yii nie jest efektem pracy pojedynczego programisty - projekt wspiera zarówno [grupa doświadczonych deweloperów][http://www.yiiframework.com/team/], jak i ogromna społeczność programistyczna, nieustannie przyczyniając się do jego rozwoju. Deweloperzy trzymają rękę na pulsie najnowszych trendów Internetu, za pomocą prostych i eleganckich interfejsów wzbogacając Yii w najlepsze sprawdzone rozwiązania i funkcjonalności, dostępne w innych frameworkach i projektach. -[about_yii]: http://www.yiiframework.com/about/ - Wersje Yii ---------- @@ -48,8 +46,8 @@ Ten przewodnik opisuje wersję 2.0. Wymagania i zależności ---------------------- -Yii 2.0 wymaga PHP w wersji 5.4.0 lub nowszej. Aby otrzymać więcej informacji na temat wymagań i indywidualnych funkcjonalności, -uruchom specjalny skrypt testujący system `requirements.php`, dołączony w każdym wydaniu Yii. +Yii 2.0 wymaga PHP w wersji 5.4.0 lub nowszej i pracuje najwydajniej na najnowszej wersji PHP 7. Aby otrzymać więcej +informacji na temat wymagań i indywidualnych funkcjonalności, uruchom specjalny skrypt testujący system dołączony w każdym wydaniu Yii. Używanie Yii wymaga podstawowej wiedzy o programowaniu obiektowym w PHP (OOP), ponieważ Yii jest frameworkiem czysto obiektowym. Yii 2.0 wykorzystuje ostatnie udoskonalenia w PHP, jak From 14f629c7d44494a4f13c9f2979c258fe089d45dc Mon Sep 17 00:00:00 2001 From: Sergey Gonimar Date: Tue, 4 Apr 2017 13:12:18 +0500 Subject: [PATCH 098/184] Updated Tajik message translations [skip ci] (#13917) --- framework/messages/tg/yii.php | 103 ++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/framework/messages/tg/yii.php b/framework/messages/tg/yii.php index 5721a07..d20676e 100644 --- a/framework/messages/tg/yii.php +++ b/framework/messages/tg/yii.php @@ -23,9 +23,10 @@ return [ '(not set)' => '(супориш дода нашуд)', ' and ' => ' ва ', 'An internal server error occurred.' => 'Хатои дохилии сервер рух дод.', - 'Are you sure you want to delete this item?' => 'Оё шумо дар ҳақиқат мехоҳед, ки ин элементро нест кунед?', + 'Are you sure you want to delete this item?' => 'Шумо боварманд ҳастед, ки ҳамин элементро нест кардан мехоҳед?', + 'Delete' => 'Нест кардан', 'Error' => 'Иштибоҳ', - 'File upload failed.' => 'Фарокашии файл. имконнопазир гашт.', + 'File upload failed.' => 'Фарокашии файл муяссар гашт.', 'Home' => 'Саҳифаи асосӣ', 'Invalid data received for parameter "{param}".' => 'Маънои нодурусти параметри "{param}".', 'Login Required' => 'Вуруд талаб карда мешавад.', @@ -53,77 +54,79 @@ return [ 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ҳамаги {count, number} {count, plural, one{қайд} few{қайд} many{қайдҳо} other{қайд}}.', 'Unable to verify your data submission.' => 'Санҷидани маълумоти фиристодаи Шумо муяссар нагардид.', 'Unknown option: --{name}' => 'Гузинаи номаълум: --{name}', + 'Update' => 'Таҳрир намудан', + 'View' => 'Аз назар гузарондан', 'Yes' => 'Ҳа', 'You are not allowed to perform this action.' => 'Шумо барои анҷом додани амали мазкур иҷозат надоред.', - 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ҳамаги {limit, number} аплод карда метавонед.', - 'in {delta, plural, =1{a day} other{# days}}' => '{delta} рӯзи дигар', - 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} дақиқаи дигар', - 'in {delta, plural, =1{a month} other{# months}}' => '{delta} моҳи дигар', - 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} сонияи дигар', - 'in {delta, plural, =1{a year} other{# years}}' => '{delta} соли дигар', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Шумо наметавонед зиёда аз {limit, number} {limit, plural,one{файлро} few{файлҳоро} many{файлро} other{файлро}} фаро бикашед.', + 'in {delta, plural, =1{a day} other{# days}}' => 'баъд аз {delta, plural, =1{рӯз} one{# рӯз} few{# рӯз} many{# рӯз} other{# рӯз}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'баъд аз {delta, plural, =1{дақиқа} one{# дақиқа} few{# дақиқа} many{# дақиқа} other{# дақиқа}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'баъд аз {delta, plural, =1{моҳ} one{# моҳ} few{# моҳ} many{# моҳ} other{# моҳ}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'баъд аз {delta, plural, =1{сония} one{# сония} few{# сония} many{# сония} other{# сония}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'баъд аз {delta, plural, =1{сол} one{# сол} few{# сол} many{# сол} other{# сол}}', 'in {delta, plural, =1{an hour} other{# hours}}' => 'баъд аз {delta, plural, =1{соат} one{# соат} few{# соат} many{# соат} other{# соат}}', 'just now' => 'ҳоло', 'the input value' => 'ҷадвали воридшуда', - '{attribute} "{value}" has already been taken.' => 'Ҷадвали «{value}» барои {attribute} аллакай банд аст.', + '{attribute} "{value}" has already been taken.' => 'Ҷадвали «{value}» барои «{attribute}» аллакай банд аст.', '{attribute} cannot be blank.' => 'Ҳошияи «{attribute}» набояд холӣ бошад.', '{attribute} contains wrong subnet mask.' => 'Маънои "{attribute}" дорои нодурусти ниқоби зершабака мебошад.', - '{attribute} is invalid.' => 'Ҷадвали {attribute} ғалат аст.', + '{attribute} is invalid.' => 'Ҷадвали «{attribute}» ғалат аст.', '{attribute} is not a valid URL.' => 'Ҷадвали «{attribute}» URL-и нодуруст мебошад.', - '{attribute} is not a valid email address.' => 'Ҷадвали {attribute} сӯроғаи дурусти E-mail нест.', - '{attribute} is not in the allowed range.' => 'Ҷадвали «{attribute}» ба рӯйхати сӯроғаҳои диапазонҳои иҷозат додашуда дохил намешавад.', + '{attribute} is not a valid email address.' => 'Ҷадвали {attribute} суроғаи дурусти паёмнигор нест.', + '{attribute} is not in the allowed range.' => 'Ҷадвали «{attribute}» ба рӯйхати суроғаҳои диапазонҳои иҷозат додашуда дохил намешавад.', '{attribute} must be "{requiredValue}".' => 'Ҷадвали «{attribute}» бояд ба «{requiredValue}» баробар бошад.', - '{attribute} must be a number.' => 'Ҷадвали {attribute} бояд адад бошад.', - '{attribute} must be a string.' => 'Ҷадвали {attribute} бояд сатр бошад.', - '{attribute} must be a valid IP address.' => 'Ҷадвали «{attribute}» бояд сӯроғаи дурусти IP бошад.', - '{attribute} must be an IP address with specified subnet.' => 'Ҷадвали «{attribute}» бояд сӯроғаи IP бо зершабака бошад.', - '{attribute} must be an integer.' => 'Ҷадвали {attribute} бояд адади бутун бошад.', + '{attribute} must be a number.' => 'Ҷадвали «{attribute}» бояд адад бошад.', + '{attribute} must be a string.' => 'Ҷадвали «{attribute}» бояд сатр бошад.', + '{attribute} must be a valid IP address.' => 'Ҷадвали «{attribute}» бояд суроғаи дурусти IP бошад.', + '{attribute} must be an IP address with specified subnet.' => 'Ҷадвали «{attribute}» бояд суроғаи IP бо зершабака бошад.', + '{attribute} must be an integer.' => 'Ҷадвали «{attribute}» бояд адади бутун бошад.', '{attribute} must be either "{true}" or "{false}".' => 'Маънои «{attribute}» бояд ба «{true}» ё «{false}» баробар бошад.', '{attribute} must be equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд ба «{compareValueOrAttribute}» баробар бошад.', '{attribute} must be greater than "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» бузургтар бошад.', '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» бузургтар ё ба он баробар бошад.', '{attribute} must be less than "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» хурдтар бошад.', '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» хурдтар ё ба он баробар бошад.', - '{attribute} must be no greater than {max}.' => '{attribute} бояд аз {max} зиёд набошад.', - '{attribute} must be no less than {min}.' => '{attribute} бояд аз {min} кам набошад.', + '{attribute} must be no greater than {max}.' => '«{attribute}» бояд аз {max} зиёд набошад.', + '{attribute} must be no less than {min}.' => '«{attribute}» бояд аз {min} кам набошад.', '{attribute} must not be a subnet.' => 'Маънии «{attribute}» набояд зершабака бошад.', - '{attribute} must not be an IPv4 address.' => 'Маънии «{attribute}» набояд сӯроғаи IPv4 бошад.', - '{attribute} must not be an IPv6 address.' => 'Маънии «{attribute}» набояд сӯроғаи IPv6 бошад.', - '{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» набояд ба «{compareValueOrAttribute}» баробар бошад.', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} хади ақал {min, number} рамз дошта бошад.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} хамаги {max, number} рамз дошта бошад.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} бояд {length, number} рамз дошта бошад.', + '{attribute} must not be an IPv4 address.' => 'Ҷадвали «{attribute}» набояд суроғаи IPv4 бошад.', + '{attribute} must not be an IPv6 address.' => 'Ҷадвали «{attribute}» набояд суроғаи IPv6 бошад.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Ҷадвали «{attribute}» набояд ба «{compareValueOrAttribute}» баробар бошад.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Ҷадвали «{attribute}» бояд хади ақал {min, number} {min, plural, one{аломат} few{аломат} many{аломат} other{аломат}} дошта бошад.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Ҷадвали «{attribute}» бояд ҳади аксар {min, number} {min, plural, one{аломат} few{аломат} many{аломат} other{аломат}} дошта бошад.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Ҷадвали «{attribute}» бояд {length, number} {length, plural, one{аломат} few{аломат} many{аломат} other{аломат}} дошта бошад.', '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# рӯз} few{# рӯз} many{# рӯз} other{# рӯз}}', '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# соат} few{# соат} many{# соат} other{# соат}}', '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# дақиқа} few{# дақиқа} many{# дақиқа} other{# дақиқа}}', '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# моҳ} few{# моҳ} many{# моҳ} other{# моҳ}}', '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# сония} few{# сония} many{# сония} other{# сония}}', '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# сол} few{# сол} many{# сол} other{# сол}}', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta} рӯзи қабл', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} дақиқаи қабл', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta} моҳи қабл', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} сонияи қабл', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta} сол пеш', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} соати қабл', - '{nFormatted} B' => '{nFormatted} B', - '{nFormatted} GB' => '{nFormatted} GB', - '{nFormatted} GiB' => '{nFormatted} GiB', - '{nFormatted} KB' => '{nFormatted} KB', - '{nFormatted} KiB' => '{nFormatted} KiB', - '{nFormatted} MB' => '{nFormatted} MB', - '{nFormatted} MiB' => '{nFormatted} MiB', - '{nFormatted} PB' => '{nFormatted} PB', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{рӯз} one{# рӯз} few{# рӯз} many{# рӯз} other{# рӯз}} пеш', + '{delta, plural, =1{a minute} other{# minutes}} ago' => ' {delta, plural, =1{дақиқа} one{# дақиқа} few{# дақиқа} many{# дақиқа} other{# дақиқа}} пеш', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{моҳ} one{# моҳ} few{# моҳ} many{# моҳ} other{# моҳ}} пеш', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{сония} one{# сония} few{# сония} many{# сония} other{# сония}} пеш', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{сол} one{# сол} few{# сол} many{# сол} other{# сол}} пеш', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{соат} one{# соат} few{# соат} many{# соат} other{# соат}} пеш', + '{nFormatted} B' => '{nFormatted} Б', + '{nFormatted} GB' => '{nFormatted} ГБ', + '{nFormatted} GiB' => '{nFormatted} ГиБ', + '{nFormatted} KB' => '{nFormatted} КБ', + '{nFormatted} KiB' => '{nFormatted} КиБ', + '{nFormatted} MB' => '{nFormatted} МБ', + '{nFormatted} MiB' => '{nFormatted} МиБ', + '{nFormatted} PB' => '{nFormatted} ПБ', '{nFormatted} PiB' => '{nFormatted} PiB', '{nFormatted} TB' => '{nFormatted} TB', '{nFormatted} TiB' => '{nFormatted} TiB', - '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} байт', - '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} гибибайт', - '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} гигабайт', - '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} кибибайт', - '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} килобайт', - '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} мебибайт', - '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} мегабайт', - '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} пебибайт', - '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} петабайт', - '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} тебибайт', - '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} терабайт', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{байт} few{байт} many{байт} other{байт}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{гибибайт} few{гибибайт} many{гибибайт} other{гибибайт}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{гигабайт} few{гигабайт} many{гигабайт} other{гигабайт}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{кибибайт} few{кибибайт} many{кибибайт} other{кибибайт}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{килобайт} few{килобайт} many{килобайт} other{килобайт}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, one{мебибайт} few{мебибайт} many{мебибайт} other{мебибайт}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, one{мегабайт} few{мегабайт} many{мегабайт} other{мегабайт}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, one{пебибайт} few{пебибайт} many{пебибайт} other{пебибайт}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{петабайт} few{мегабайт} many{петабайт} other{петабайт}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{тебибайт} few{тебибайт} many{тебибайт} other{тебибайт}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{терабайт} few{терабайт} many{терабайт} other{терабайт}}', ]; From 5bbf372f857ac131de43f0f4fbbfb5fa7e008f1b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 4 Apr 2017 10:33:17 +0200 Subject: [PATCH 099/184] cleanup some phpdoc --- framework/validators/DateValidator.php | 2 +- framework/web/ErrorHandler.php | 1 + framework/web/Response.php | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php index a6cdeea..53e605f 100644 --- a/framework/validators/DateValidator.php +++ b/framework/validators/DateValidator.php @@ -139,7 +139,7 @@ class DateValidator extends Validator * * If not set, [[timestampAttribute]] will receive a UNIX timestamp. * If [[timestampAttribute]] is not set, this property will be ignored. - * @see formatMessage + * @see format * @see timestampAttribute * @since 2.0.4 */ diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index f051b6e..bf9e662 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -308,6 +308,7 @@ class ErrorHandler extends \yii\base\ErrorHandler * Renders call stack. * @param \Exception $exception exception to get call stack from * @return string HTML content of the rendered call stack. + * @since 2.0.12 */ public function renderCallStack(\Exception $exception) { diff --git a/framework/web/Response.php b/framework/web/Response.php index f846f61..40a6d24 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -293,10 +293,9 @@ class Response extends \yii\base\Response /** * Sets the response status code based on the exception. - * @param \Exception|\Error $e + * @param \Exception|\Error $e the exception object. * @throws InvalidParamException if the status code is invalid. * @return $this the response object itself - * * @since 2.0.12 */ public function setStatusCodeByException($e) From 5faa0032cb33dd9e9a546a652fc51e248baf9d2e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 4 Apr 2017 10:47:48 +0200 Subject: [PATCH 100/184] added more assertations for security masking tests --- tests/framework/base/SecurityTest.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php index 9d087d4..debce77 100644 --- a/tests/framework/base/SecurityTest.php +++ b/tests/framework/base/SecurityTest.php @@ -1257,7 +1257,23 @@ TEXT; */ public function testMasking($unmaskedToken) { - $this->assertEquals($unmaskedToken, $this->security->unmaskToken($this->security->maskToken($unmaskedToken))); + $maskedToken = $this->security->maskToken($unmaskedToken); + $this->assertGreaterThan(mb_strlen($unmaskedToken, '8bit') * 2, mb_strlen($maskedToken, '8bit')); + $this->assertEquals($unmaskedToken, $this->security->unmaskToken($maskedToken)); + } + + public function testUnMaskingInvalidStrings() + { + $this->assertEquals('', $this->security->unmaskToken('')); + $this->assertEquals('', $this->security->unmaskToken('1')); + } + + /** + * @expectedException \yii\base\InvalidParamException + */ + public function testMaskingInvalidStrings() + { + $this->security->maskToken(''); } /** @@ -1265,6 +1281,7 @@ TEXT; */ public function maskProvider() { return [ + ['1'], ['SimpleToken'], ['Token with special characters: %d1 5"'], ['Token with UTF8 character: †'] From d9f592712b8a7faa3df427072c56e3be97ca15f1 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 4 Apr 2017 10:50:15 +0200 Subject: [PATCH 101/184] remove upgrade note for #13822 / #13829 --- framework/UPGRADE.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index c99cade..9f51979 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -59,9 +59,6 @@ Upgrade from Yii 2.0.11 * `yii\grid\DataColumn` filter is now automatically generated as dropdown list with localized `Yes` and `No` strings in case of `format` being set to `boolean`. -* `yii\web\User::loginRequired()` now throws an `UnauthorizedHttpException` instead of a `ForbiddenHttpException`. - Check and adjust your try-catch blocks. - Upgrade from Yii 2.0.10 ----------------------- From b04ff959cec2cc46beb4e725047eb61cad58f305 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 7 Apr 2017 15:59:59 +0300 Subject: [PATCH 102/184] Fixed misleading docs about encoded URIs [skip ci] --- framework/web/Request.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/web/Request.php b/framework/web/Request.php index 60f1347..af2edac 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -74,7 +74,7 @@ use yii\base\InvalidConfigException; * @property int $securePort Port number for secure requests. * @property string $serverName Server name, null if not available. This property is read-only. * @property int|null $serverPort Server port number, null if not available. This property is read-only. - * @property string $url The currently requested relative URL. Note that the URI returned is URL-encoded. + * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client. * @property string|null $userAgent User agent, null if not available. This property is read-only. * @property string|null $userHost User host name, null if not available. This property is read-only. * @property string|null $userIP User IP address, null if not available. This property is read-only. @@ -808,7 +808,7 @@ class Request extends \yii\base\Request * Returns the currently requested relative URL. * This refers to the portion of the URL that is after the [[hostInfo]] part. * It includes the [[queryString]] part if any. - * @return string the currently requested relative URL. Note that the URI returned is URL-encoded. + * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client. * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration */ public function getUrl() @@ -836,7 +836,7 @@ class Request extends \yii\base\Request * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any. * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. * @return string|bool the request URI portion for the currently requested URL. - * Note that the URI returned is URL-encoded. + * Note that the URI returned may be URL-encoded depending on the client. * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration */ protected function resolveRequestUri() From a9be295b9429b5b7ad3949514e6fd37db448db29 Mon Sep 17 00:00:00 2001 From: GirMate Date: Fri, 7 Apr 2017 23:54:55 +0300 Subject: [PATCH 103/184] Update input-validation.md (#13942) [skip ci] --- docs/guide-ru/input-validation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guide-ru/input-validation.md b/docs/guide-ru/input-validation.md index 675b451..303a99b 100644 --- a/docs/guide-ru/input-validation.md +++ b/docs/guide-ru/input-validation.md @@ -499,8 +499,7 @@ HTML-форма построена с помощью следующего код - `messages`: массив, используемый для хранения сообщений об ошибках, проверки значения атрибута. - `deferred`: массив, который содержит отложенные объекты (описано в следующем подразделе). - В следующем примере мы создаем `StatusValidator` который проверяет, if an input is a valid - status input against the existing status data. + В следующем примере мы создаем `StatusValidator` который проверяет значение поля на соответствие допустимым статусам. Валидатор поддерживает оба способа проверки и на стороне сервера и на стороне клиента. ```php From 3a6a590eae4acc9bddb8eb665a84ff81c231d994 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 9 Apr 2017 22:51:02 +0300 Subject: [PATCH 104/184] Fixes #13945: Removed Courier New from error page fonts list since it looks bad on Linux (#13947) --- framework/CHANGELOG.md | 1 + framework/views/errorHandler/exception.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 64c699b..68cf8da 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -62,6 +62,7 @@ Yii Framework 2 Change Log - Enh #13837: Refactored masking of CSRF tokens (sammousa) - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) +- Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php index db3e88b..3eae788 100644 --- a/framework/views/errorHandler/exception.php +++ b/framework/views/errorHandler/exception.php @@ -239,7 +239,7 @@ h1,h2,h3,p,img,ul li{ line-height: 20px; font-size: 12px; margin-top: 1px; - font-family: Consolas, Courier New, monospace; + font-family: Consolas, monospace; } .call-stack ul li .code pre{ position: relative; @@ -247,7 +247,7 @@ h1,h2,h3,p,img,ul li{ left: 50px; line-height: 20px; font-size: 12px; - font-family: Consolas, Courier New, monospace; + font-family: Consolas, monospace; display: inline; } @-moz-document url-prefix() { @@ -272,7 +272,7 @@ h1,h2,h3,p,img,ul li{ .request .code pre{ font-size: 14px; line-height: 18px; - font-family: Consolas, Courier New, monospace; + font-family: Consolas, monospace; display: inline; word-wrap: break-word; } From e62ea0136cd619e4815b521e94fc655a68a496a2 Mon Sep 17 00:00:00 2001 From: Skiptir Engu Date: Mon, 10 Apr 2017 18:43:38 -0300 Subject: [PATCH 105/184] Fixed phpdoc broken links to PHP manual pages [skip ci] --- framework/caching/MemCacheServer.php | 2 +- framework/db/Command.php | 2 +- framework/db/Connection.php | 8 ++++---- framework/helpers/BaseArrayHelper.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/framework/caching/MemCacheServer.php b/framework/caching/MemCacheServer.php index 131ce22..3e6753d 100644 --- a/framework/caching/MemCacheServer.php +++ b/framework/caching/MemCacheServer.php @@ -10,7 +10,7 @@ namespace yii\caching; /** * MemCacheServer represents the configuration data for a single memcache or memcached server. * - * See [PHP manual](http://www.php.net/manual/en/function.Memcache-addServer.php) for detailed explanation + * See [PHP manual](http://php.net/manual/en/memcache.addserver.php) for detailed explanation * of each configuration property. * * For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview). diff --git a/framework/db/Command.php b/framework/db/Command.php index 86538b4..3713bf5 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -365,7 +365,7 @@ class Command extends Component /** * Executes the SQL statement and returns the first row of the result. * This method is best used when only the first row of result is needed for a query. - * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](http://php.net/manual/en/pdostatement.setfetchmode.php) * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. * @return array|false the first row (in terms of an array) of the query result. False is returned if the query * results in nothing. diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 877049c..c39e82f 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -153,7 +153,7 @@ class Connection extends Component /** * @var string the Data Source Name, or DSN, contains the information required to connect to the database. - * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on + * Please refer to the [PHP manual](http://php.net/manual/en/pdo.construct.php) on * the format of the DSN string. * * For [SQLite](http://php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a path alias @@ -173,7 +173,7 @@ class Connection extends Component /** * @var array PDO attributes (name => value) that should be set when calling [[open()]] * to establish a DB connection. Please refer to the - * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for + * [PHP manual](http://php.net/manual/en/pdo.setattribute.php) for * details about available attributes. */ public $attributes; @@ -789,7 +789,7 @@ class Connection extends Component * Returns the ID of the last inserted row or sequence value. * @param string $sequenceName name of the sequence object (required by some DBMS) * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object - * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php + * @see http://php.net/manual/en/pdo.lastinsertid.php */ public function getLastInsertID($sequenceName = '') { @@ -801,7 +801,7 @@ class Connection extends Component * Note that if the parameter is not a string, it will be returned without change. * @param string $value string to be quoted * @return string the properly quoted string - * @see http://www.php.net/manual/en/function.PDO-quote.php + * @see http://php.net/manual/en/pdo.quote.php */ public function quoteValue($value) { diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index a395f6f..6b5186b 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -761,7 +761,7 @@ class BaseArrayHelper * but additionally works on objects that implement the [[\Traversable]] interface. * @param mixed $var The variable being evaluated. * @return bool whether $var is array-like - * @see http://php.net/manual/en/function.is_array.php + * @see http://php.net/manual/en/function.is-array.php * @since 2.0.8 */ public static function isTraversable($var) From 9445e5508b54a155f1c567fca33f947dd0911fbb Mon Sep 17 00:00:00 2001 From: Alexey Rogachev Date: Tue, 11 Apr 2017 05:05:36 +0600 Subject: [PATCH 106/184] Fixes #10675: Added docs for disabling CSRF validation in standalone actions --- docs/guide/security-best-practices.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index baeba07..7ecb273 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -209,11 +209,36 @@ class SiteController extends Controller } ``` +Disabling CSRF validation in [standalone actions](structure-controllers.md#standalone-actions) must be done in `init()` +method. Do not place this code into `beforeRun()` method because it won't have effect. + +```php +request; + if ($request->referrer === 'yiipowered.com' + && $model->load($request->post()) + && $model->validate() + ) { + $model->sendEmail(); + } + } +} +``` + Further reading on the topic: - - Avoiding file exposure ---------------------- From ed9912591eabea5a28a2acabf011d0ca2717d0dd Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 11 Apr 2017 09:24:46 +0200 Subject: [PATCH 107/184] updated docs for base64UrlEncode() as discussed in https://github.com/yiisoft/yii2/pull/13411#issuecomment-285695112 --- framework/helpers/BaseStringHelper.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index e63a729..6e5eae3 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -315,10 +315,13 @@ class BaseStringHelper /** * Encodes string into "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648) - * @see https://tools.ietf.org/html/rfc4648#page-7 * - * @param string $input - * @return string + * > Note: Base 64 padding `=` may be at the end of the returned string. + * > `=` is not transparent to URL encoding. + * + * @see https://tools.ietf.org/html/rfc4648#page-7 + * @param string $input the string to encode. + * @return string encoded string. * @since 2.0.12 */ public static function base64UrlEncode($input) @@ -328,10 +331,10 @@ class BaseStringHelper /** * Decodes "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648) - * @see https://tools.ietf.org/html/rfc4648#page-7 * - * @param string $input - * @return string + * @see https://tools.ietf.org/html/rfc4648#page-7 + * @param string $input encoded string. + * @return string decoded string. * @since 2.0.12 */ public static function base64UrlDecode($input) From 11761ac42a2e11fbc65a731675b270f16d37fef5 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Tue, 11 Apr 2017 15:51:32 +0300 Subject: [PATCH 108/184] Fix `\yii\db\QueryTrait` type hints (#13956) [skip ci] --- framework/db/QueryTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 76651c0..932d28c 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -376,7 +376,7 @@ trait QueryTrait /** * Sets the LIMIT part of the query. - * @param int|Expression $limit the limit. Use null or negative value to disable limit. + * @param int|Expression|null $limit the limit. Use null or negative value to disable limit. * @return $this the query object itself */ public function limit($limit) @@ -387,7 +387,7 @@ trait QueryTrait /** * Sets the OFFSET part of the query. - * @param int|Expression $offset the offset. Use null or negative value to disable offset. + * @param int|Expression|null $offset the offset. Use null or negative value to disable offset. * @return $this the query object itself */ public function offset($offset) From e1b1622244d08151df18aaf2415b7145ee3d2d36 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Tue, 11 Apr 2017 18:20:41 +0300 Subject: [PATCH 109/184] Fix `\yii\db\QueryInterface` type hints (#13959) [skip ci] --- framework/db/QueryInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php index 6fa5194..2f52eac 100644 --- a/framework/db/QueryInterface.php +++ b/framework/db/QueryInterface.php @@ -241,14 +241,14 @@ interface QueryInterface /** * Sets the LIMIT part of the query. - * @param int $limit the limit. Use null or negative value to disable limit. + * @param int|null $limit the limit. Use null or negative value to disable limit. * @return $this the query object itself */ public function limit($limit); /** * Sets the OFFSET part of the query. - * @param int $offset the offset. Use null or negative value to disable offset. + * @param int|null $offset the offset. Use null or negative value to disable offset. * @return $this the query object itself */ public function offset($offset); From 519753d868cc0d82202dedd362741a77fb9eb221 Mon Sep 17 00:00:00 2001 From: Angel Guevara Date: Tue, 11 Apr 2017 17:55:13 -0500 Subject: [PATCH 110/184] Added missing parts of disabling CSRF validation doc [skip ci] (#13966) --- docs/guide/security-best-practices.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index 7ecb273..d646c35 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -221,6 +221,12 @@ use yii\base\Action; class ContactAction extends Action { + public function init() + { + parent::init(); + $this->controller->enableCsrfValidation = false; + } + public function run() { $model = new ContactForm(); @@ -235,6 +241,8 @@ class ContactAction extends Action } ``` +> Warning: Disabling CSRF will allow any site to send POST requests to your site. It is important to implement extra validation such as checking an IP address or a secret token in this case. + Further reading on the topic: - From 510cd3541d32331653913a149c908c9e579a7cc2 Mon Sep 17 00:00:00 2001 From: Evgeniy Moiseenko Date: Wed, 12 Apr 2017 11:29:56 +0300 Subject: [PATCH 111/184] Fixed wording in Russian translation (#13970) [skip ci] --- docs/guide-ru/output-data-widgets.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/guide-ru/output-data-widgets.md b/docs/guide-ru/output-data-widgets.md index f89ac40..11d7b66 100644 --- a/docs/guide-ru/output-data-widgets.md +++ b/docs/guide-ru/output-data-widgets.md @@ -607,7 +607,7 @@ CREATE OR REPLACE VIEW vw_user_info AS WHERE user.id = user_profile.user_id ``` -Теперь необходимо создать ActiveRecord, которая будет отображение данных из этого вида: +Теперь вам необходимо создать ActiveRecord, через который будут доступны данные из вида выше: ```php @@ -650,8 +650,6 @@ class UserView extends ActiveRecord // здесь определяйте ваши метки атрибутов ]; } - - } ``` From d2fb17f44458f5da91be0b5113959eb3212490dd Mon Sep 17 00:00:00 2001 From: Vladimir Guts Date: Wed, 12 Apr 2017 21:48:16 +0300 Subject: [PATCH 112/184] Fixes #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed --- framework/CHANGELOG.md | 1 + framework/rbac/DbManager.php | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 68cf8da..5bb436e 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -63,6 +63,7 @@ Yii Framework 2 Change Log - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) +- Bug #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed (vsguts) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index fca49ee..3f7becb 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -987,7 +987,11 @@ class DbManager extends BaseManager $query = (new Query)->from($this->ruleTable); $this->rules = []; foreach ($query->all($this->db) as $row) { - $this->rules[$row['name']] = unserialize($row['data']); + $data = $row['data']; + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $this->rules[$row['name']] = unserialize($data); } $query = (new Query)->from($this->itemChildTable); From 5d4a7a588d67b1415813101ea0298882cc66db49 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 11:44:25 +0300 Subject: [PATCH 113/184] compatibility for PHPUnit adjusted --- tests/TestCase.php | 2 +- tests/compatibility.php | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 0551fd2..45310f4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,7 +7,7 @@ use yii\helpers\ArrayHelper; /** * This is the base class for all yii framework unit tests. */ -abstract class TestCase extends \PHPUnit_Framework_TestCase +abstract class TestCase extends \PHPUnit\Framework\TestCase { public static $params; diff --git a/tests/compatibility.php b/tests/compatibility.php index 9141b4f..ee617ab 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -1,12 +1,16 @@ Date: Thu, 13 Apr 2017 14:49:43 +0300 Subject: [PATCH 114/184] compatibility with PHPUnit 6.x added --- tests/compatibility.php | 18 +++++++++- tests/framework/BaseYiiTest.php | 4 ++- tests/framework/base/BehaviorTest.php | 2 +- tests/framework/base/ComponentTest.php | 22 +++++-------- tests/framework/base/DynamicModelTest.php | 2 +- tests/framework/base/ModelTest.php | 4 +-- tests/framework/base/ObjectTest.php | 16 ++++----- tests/framework/base/SecurityTest.php | 9 +++-- tests/framework/console/ControllerTest.php | 3 +- tests/framework/console/RequestTest.php | 3 +- .../console/controllers/AssetControllerTest.php | 8 +++-- .../controllers/BaseMessageControllerTest.php | 7 ++-- .../console/controllers/HelpControllerTest.php | 5 ++- .../controllers/MigrateControllerTestTrait.php | 5 ++- tests/framework/db/ActiveRecordTest.php | 2 +- tests/framework/db/CommandTest.php | 6 ++-- tests/framework/db/ConnectionTest.php | 2 +- tests/framework/di/InstanceTest.php | 15 +++++---- tests/framework/filters/AccessRuleTest.php | 4 ++- tests/framework/filters/HostControlTest.php | 3 +- tests/framework/filters/auth/AuthMethodTest.php | 6 ++-- tests/framework/helpers/FileHelperTest.php | 4 +-- tests/framework/helpers/HtmlTest.php | 4 +-- tests/framework/helpers/JsonTest.php | 2 +- tests/framework/helpers/UrlTest.php | 4 +-- .../i18n/FallbackMessageFormatterTest.php | 4 +-- tests/framework/i18n/FormatterDateTest.php | 2 +- tests/framework/i18n/FormatterTest.php | 2 +- tests/framework/log/EmailTargetTest.php | 36 +++++++++++--------- tests/framework/log/LoggerTest.php | 38 ++++++++++++++++------ tests/framework/log/SyslogTargetTest.php | 9 +++-- tests/framework/log/TargetTest.php | 6 ++-- tests/framework/mail/BaseMailerTest.php | 4 ++- .../framework/validators/CompareValidatorTest.php | 2 +- tests/framework/validators/FilterValidatorTest.php | 2 +- tests/framework/validators/IpValidatorTest.php | 4 +-- tests/framework/validators/RangeValidatorTest.php | 3 +- .../validators/RegularExpressionValidatorTest.php | 2 +- tests/framework/validators/UniqueValidatorTest.php | 2 +- tests/framework/validators/ValidatorTest.php | 6 ++-- tests/framework/web/AssetBundleTest.php | 4 +-- tests/framework/web/CacheSessionTest.php | 2 +- tests/framework/web/ResponseTest.php | 2 +- tests/framework/web/UserTest.php | 4 +-- tests/framework/widgets/BreadcrumbsTest.php | 2 +- 45 files changed, 181 insertions(+), 115 deletions(-) diff --git a/tests/compatibility.php b/tests/compatibility.php index ee617ab..f7fd3f2 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -11,6 +11,22 @@ namespace PHPUnit\Framework\Constraint { namespace PHPUnit\Framework { if (!class_exists('PHPUnit\Framework\TestCase') && class_exists('PHPUnit_Framework_TestCase')) { - abstract class TestCase extends \PHPUnit_Framework_TestCase {} + abstract class TestCase extends \PHPUnit_Framework_TestCase { + /** + * @param string $exception + */ + public function expectException($exception) + { + $this->setExpectedException($exception); + } + + /** + * @param string $message + */ + public function expectExceptionMessage($message) + { + $this->setExpectedException($this->getExpectedException(), $message); + } + } } } diff --git a/tests/framework/BaseYiiTest.php b/tests/framework/BaseYiiTest.php index d045dff..38df4fb 100644 --- a/tests/framework/BaseYiiTest.php +++ b/tests/framework/BaseYiiTest.php @@ -113,7 +113,9 @@ class BaseYiiTest extends TestCase */ public function testLog() { - $logger = $this->getMock('yii\\log\\Logger', ['log']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['log']) + ->getMock(); BaseYii::setLogger($logger); $logger->expects($this->exactly(6)) diff --git a/tests/framework/base/BehaviorTest.php b/tests/framework/base/BehaviorTest.php index c5ec181..a54f713 100644 --- a/tests/framework/base/BehaviorTest.php +++ b/tests/framework/base/BehaviorTest.php @@ -141,7 +141,7 @@ class BehaviorTest extends TestCase { $bar = new BarClass(); $behavior = new BarBehavior(); - $this->setExpectedException('yii\base\UnknownMethodException'); + $this->expectException('yii\base\UnknownMethodException'); $this->assertFalse($bar->hasMethod('nomagicBehaviorMethod')); $bar->attachBehavior('bar', $behavior); diff --git a/tests/framework/base/ComponentTest.php b/tests/framework/base/ComponentTest.php index da04122..dc01e91 100644 --- a/tests/framework/base/ComponentTest.php +++ b/tests/framework/base/ComponentTest.php @@ -96,7 +96,7 @@ class ComponentTest extends TestCase public function testGetProperty() { $this->assertSame('default', $this->component->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); + $this->expectException('yii\base\UnknownPropertyException'); $value2 = $this->component->Caption; } @@ -105,7 +105,7 @@ class ComponentTest extends TestCase $value = 'new value'; $this->component->Text = $value; $this->assertEquals($value, $this->component->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); + $this->expectException('yii\base\UnknownPropertyException'); $this->component->NewMember = $value; } @@ -130,7 +130,7 @@ class ComponentTest extends TestCase public function testCallUnknownMethod() { - $this->setExpectedException('yii\base\UnknownMethodException'); + $this->expectException('yii\base\UnknownMethodException'); $this->component->unknownMethod(); } @@ -150,7 +150,7 @@ class ComponentTest extends TestCase public function testUnsetReadonly() { - $this->setExpectedException('yii\base\InvalidCallException'); + $this->expectException('yii\base\InvalidCallException'); unset($this->component->object); } @@ -244,7 +244,7 @@ class ComponentTest extends TestCase $this->assertSame($behavior, $component->detachBehavior('a')); $this->assertFalse($component->hasProperty('p')); - $this->setExpectedException('yii\base\UnknownMethodException'); + $this->expectException('yii\base\UnknownMethodException'); $component->test(); $p = 'as b'; @@ -305,10 +305,8 @@ class ComponentTest extends TestCase public function testSetReadOnlyProperty() { - $this->setExpectedException( - '\yii\base\InvalidCallException', - 'Setting read-only property: yiiunit\framework\base\NewComponent::object' - ); + $this->expectException('\yii\base\InvalidCallException'); + $this->expectExceptionMessage('Setting read-only property: yiiunit\framework\base\NewComponent::object'); $this->component->object = 'z'; } @@ -336,10 +334,8 @@ class ComponentTest extends TestCase public function testWriteOnlyProperty() { - $this->setExpectedException( - '\yii\base\InvalidCallException', - 'Getting write-only property: yiiunit\framework\base\NewComponent::writeOnly' - ); + $this->expectException('\yii\base\InvalidCallException'); + $this->expectExceptionMessage('Getting write-only property: yiiunit\framework\base\NewComponent::writeOnly'); $this->component->writeOnly; } diff --git a/tests/framework/base/DynamicModelTest.php b/tests/framework/base/DynamicModelTest.php index 8388a7f..d4ca339 100644 --- a/tests/framework/base/DynamicModelTest.php +++ b/tests/framework/base/DynamicModelTest.php @@ -72,7 +72,7 @@ class DynamicModelTest extends TestCase $model = new DynamicModel(compact('name', 'email')); $this->assertEquals($email, $model->email); $this->assertEquals($name, $model->name); - $this->setExpectedException('yii\base\UnknownPropertyException'); + $this->expectException('yii\base\UnknownPropertyException'); $age = $model->age; } diff --git a/tests/framework/base/ModelTest.php b/tests/framework/base/ModelTest.php index 351e07d..0617c01 100644 --- a/tests/framework/base/ModelTest.php +++ b/tests/framework/base/ModelTest.php @@ -427,8 +427,8 @@ class ModelTest extends TestCase public function testCreateValidators() { - $this->setExpectedException('yii\base\InvalidConfigException', - 'Invalid validation rule: a rule must specify both attribute names and validator type.'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('Invalid validation rule: a rule must specify both attribute names and validator type.'); $invalid = new InvalidRulesModel(); $invalid->createValidators(); diff --git a/tests/framework/base/ObjectTest.php b/tests/framework/base/ObjectTest.php index 401d39d..10320f9 100644 --- a/tests/framework/base/ObjectTest.php +++ b/tests/framework/base/ObjectTest.php @@ -61,7 +61,7 @@ class ObjectTest extends TestCase public function testGetProperty() { $this->assertSame('default', $this->object->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); + $this->expectException('yii\base\UnknownPropertyException'); $value2 = $this->object->Caption; } @@ -70,13 +70,13 @@ class ObjectTest extends TestCase $value = 'new value'; $this->object->Text = $value; $this->assertEquals($value, $this->object->Text); - $this->setExpectedException('yii\base\UnknownPropertyException'); + $this->expectException('yii\base\UnknownPropertyException'); $this->object->NewMember = $value; } public function testSetReadOnlyProperty() { - $this->setExpectedException('yii\base\InvalidCallException'); + $this->expectException('yii\base\InvalidCallException'); $this->object->object = 'test'; } @@ -106,13 +106,13 @@ class ObjectTest extends TestCase public function testUnsetReadOnlyProperty() { - $this->setExpectedException('yii\base\InvalidCallException'); + $this->expectException('yii\base\InvalidCallException'); unset($this->object->object); } public function testCallUnknownMethod() { - $this->setExpectedException('yii\base\UnknownMethodException'); + $this->expectException('yii\base\UnknownMethodException'); $this->object->unknownMethod(); } @@ -148,10 +148,8 @@ class ObjectTest extends TestCase public function testReadingWriteOnlyProperty() { - $this->setExpectedException( - 'yii\base\InvalidCallException', - 'Getting write-only property: yiiunit\framework\base\NewObject::writeOnly' - ); + $this->expectException('yii\base\InvalidCallException'); + $this->expectExceptionMessage('Getting write-only property: yiiunit\framework\base\NewObject::writeOnly'); $this->object->writeOnly; } } diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php index debce77..c872f43 100644 --- a/tests/framework/base/SecurityTest.php +++ b/tests/framework/base/SecurityTest.php @@ -887,7 +887,8 @@ TEXT; { static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ]; static::$fopen = false; - $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + $this->expectException('yii\base\Exception'); + $this->expectExceptionMessage('Unable to generate a random key'); $this->security->generateRandomKey(42); } @@ -899,7 +900,8 @@ TEXT; { static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ]; static::$fread = false; - $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + $this->expectException('yii\base\Exception'); + $this->expectExceptionMessage('Unable to generate a random key'); $this->security->generateRandomKey(42); } @@ -933,7 +935,8 @@ TEXT; } // there is no /dev/urandom on windows so we expect this to fail if (DIRECTORY_SEPARATOR === '\\' && $functions['random_bytes'] === false && $functions['openssl_random_pseudo_bytes'] === false && $functions['mcrypt_create_iv'] === false ) { - $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + $this->expectException('yii\base\Exception'); + $this->expectExceptionMessage('Unable to generate a random key'); } // Function mcrypt_create_iv() is deprecated since PHP 7.1 if (version_compare(PHP_VERSION, '7.1.0alpha', '>=') && $functions['random_bytes'] === false && $functions['mcrypt_create_iv'] === true) { diff --git a/tests/framework/console/ControllerTest.php b/tests/framework/console/ControllerTest.php index 041bc84..fda6929 100644 --- a/tests/framework/console/ControllerTest.php +++ b/tests/framework/console/ControllerTest.php @@ -61,7 +61,8 @@ class ControllerTest extends TestCase $params = ['avaliable']; $message = Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', ['missing'])]); - $this->setExpectedException('yii\console\Exception', $message); + $this->expectException('yii\console\Exception'); + $this->expectExceptionMessage($message); $result = $controller->runAction('aksi3', $params); } diff --git a/tests/framework/console/RequestTest.php b/tests/framework/console/RequestTest.php index c4c1bee..b9ec163 100644 --- a/tests/framework/console/RequestTest.php +++ b/tests/framework/console/RequestTest.php @@ -140,7 +140,8 @@ class RequestTest extends TestCase public function testResolve($params, $expected, $expectedException = null) { if (isset($expectedException)) { - $this->setExpectedException($expectedException[0], $expectedException[1]); + $this->expectException($expectedException[0]); + $this->expectExceptionMessage($expectedException[1]); } $request = new Request(); diff --git a/tests/framework/console/controllers/AssetControllerTest.php b/tests/framework/console/controllers/AssetControllerTest.php index 7e5f2eb..9e3026b 100644 --- a/tests/framework/console/controllers/AssetControllerTest.php +++ b/tests/framework/console/controllers/AssetControllerTest.php @@ -67,7 +67,10 @@ class AssetControllerTest extends TestCase */ protected function createAssetController() { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); + $module = $this->getMockBuilder('yii\\base\\Module') + ->setMethods(['fake']) + ->setConstructorArgs(['console']) + ->getMock(); $assetController = new AssetControllerMock('asset', $module); $assetController->interactive = false; $assetController->jsCompressor = 'cp {from} {to}'; @@ -436,7 +439,8 @@ EOL; // Assert : $expectedExceptionMessage = ": {$namespace}\AssetA -> {$namespace}\AssetB -> {$namespace}\AssetC -> {$namespace}\AssetA"; - $this->setExpectedException('yii\console\Exception', $expectedExceptionMessage); + $this->expectException('yii\console\Exception'); + $this->expectExceptionMessage($expectedExceptionMessage); // When : $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); diff --git a/tests/framework/console/controllers/BaseMessageControllerTest.php b/tests/framework/console/controllers/BaseMessageControllerTest.php index b22af5b..1b7251d 100644 --- a/tests/framework/console/controllers/BaseMessageControllerTest.php +++ b/tests/framework/console/controllers/BaseMessageControllerTest.php @@ -55,7 +55,10 @@ abstract class BaseMessageControllerTest extends TestCase */ protected function createMessageController() { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); + $module = $this->getMockBuilder('yii\\base\\Module') + ->setMethods(['fake']) + ->setConstructorArgs(['console']) + ->getMock(); $messageController = new MessageControllerMock('message', $module); $messageController->interactive = false; @@ -146,7 +149,7 @@ abstract class BaseMessageControllerTest extends TestCase public function testConfigFileNotExist() { - $this->setExpectedException('yii\\console\\Exception'); + $this->expectException('yii\\console\\Exception'); $this->runMessageControllerAction('extract', ['not_existing_file.php']); } diff --git a/tests/framework/console/controllers/HelpControllerTest.php b/tests/framework/console/controllers/HelpControllerTest.php index e508b16..0364e26 100644 --- a/tests/framework/console/controllers/HelpControllerTest.php +++ b/tests/framework/console/controllers/HelpControllerTest.php @@ -27,7 +27,10 @@ class HelpControllerTest extends TestCase */ protected function createController() { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); + $module = $this->getMockBuilder('yii\\base\\Module') + ->setMethods(['fake']) + ->setConstructorArgs(['console']) + ->getMock(); return new BufferedHelpController('help', $module); } diff --git a/tests/framework/console/controllers/MigrateControllerTestTrait.php b/tests/framework/console/controllers/MigrateControllerTestTrait.php index 1aae086..acbb904 100644 --- a/tests/framework/console/controllers/MigrateControllerTestTrait.php +++ b/tests/framework/console/controllers/MigrateControllerTestTrait.php @@ -60,7 +60,10 @@ trait MigrateControllerTestTrait */ protected function createMigrateController(array $config = []) { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); + $module = $this->getMockBuilder('yii\\base\\Module') + ->setMethods(['fake']) + ->setConstructorArgs(['console']) + ->getMock(); $class = $this->migrateControllerClass; $migrateController = new $class('migrate', $module); $migrateController->interactive = false; diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index 1e9a6b5..f31d76a 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -1172,7 +1172,7 @@ abstract class ActiveRecordTest extends DatabaseTestCase $record = Document::findOne(1); $record->content = 'Rewrite attempt content'; $record->version = 0; - $this->setExpectedException('yii\db\StaleObjectException'); + $this->expectException('yii\db\StaleObjectException'); $record->save(false); } diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index 80804bd..1f29c39 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -71,7 +71,7 @@ abstract class CommandTest extends DatabaseTestCase $this->assertEquals(1, $command->queryScalar()); $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); + $this->expectException('\yii\db\Exception'); $command->execute(); } @@ -132,7 +132,7 @@ abstract class CommandTest extends DatabaseTestCase $this->assertFalse($command->queryScalar()); $command = $db->createCommand('bad SQL'); - $this->setExpectedException('\yii\db\Exception'); + $this->expectException('\yii\db\Exception'); $command->query(); } @@ -649,7 +649,7 @@ SQL; public function testIntegrityViolation() { - $this->setExpectedException('\yii\db\IntegrityException'); + $this->expectException('\yii\db\IntegrityException'); $db = $this->getConnection(); diff --git a/tests/framework/db/ConnectionTest.php b/tests/framework/db/ConnectionTest.php index c31d6e3..761be2a 100644 --- a/tests/framework/db/ConnectionTest.php +++ b/tests/framework/db/ConnectionTest.php @@ -35,7 +35,7 @@ abstract class ConnectionTest extends DatabaseTestCase $connection = new Connection; $connection->dsn = 'unknown::memory:'; - $this->setExpectedException('yii\db\Exception'); + $this->expectException('yii\db\Exception'); $connection->open(); } diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 3f91f48..6b8c059 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -100,7 +100,8 @@ class InstanceTest extends TestCase 'dsn' => 'test', ]); - $this->setExpectedException('yii\base\InvalidConfigException', '"db" refers to a yii\db\Connection component. yii\base\Widget is expected.'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('"db" refers to a yii\db\Connection component. yii\base\Widget is expected.'); Instance::ensure('db', 'yii\base\Widget', $container); Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], 'yii\base\Widget', $container); @@ -108,13 +109,15 @@ class InstanceTest extends TestCase public function testExceptionInvalidDataType() { - $this->setExpectedException('yii\base\InvalidConfigException', 'Invalid data type: yii\db\Connection. yii\base\Widget is expected.'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('Invalid data type: yii\db\Connection. yii\base\Widget is expected.'); Instance::ensure(new Connection, 'yii\base\Widget'); } public function testExceptionComponentIsNotSpecified() { - $this->setExpectedException('yii\base\InvalidConfigException', 'The required component is not specified.'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('The required component is not specified.'); Instance::ensure(''); } @@ -175,10 +178,8 @@ PHP public function testRestoreAfterVarExportRequiresId() { - $this->setExpectedException( - 'yii\base\InvalidConfigException', - 'Failed to instantiate class "Instance". Required parameter "id" is missing' - ); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('Failed to instantiate class "Instance". Required parameter "id" is missing'); Instance::__set_state([]); } diff --git a/tests/framework/filters/AccessRuleTest.php b/tests/framework/filters/AccessRuleTest.php index 3a8394e..5e5cef0 100644 --- a/tests/framework/filters/AccessRuleTest.php +++ b/tests/framework/filters/AccessRuleTest.php @@ -34,7 +34,9 @@ class AccessRuleTest extends \yiiunit\TestCase protected function mockRequest($method = 'GET') { /** @var Request $request */ - $request = $this->getMockBuilder('\yii\web\Request')->setMethods(['getMethod'])->getMock(); + $request = $this->getMockBuilder('\yii\web\Request') + ->setMethods(['getMethod']) + ->getMock(); $request->method('getMethod')->willReturn($method); return $request; } diff --git a/tests/framework/filters/HostControlTest.php b/tests/framework/filters/HostControlTest.php index ed53d4d..9ec462a 100644 --- a/tests/framework/filters/HostControlTest.php +++ b/tests/framework/filters/HostControlTest.php @@ -148,7 +148,8 @@ class HostControlTest extends TestCase public function testErrorHandlerWithDefaultHost() { - $this->setExpectedException('yii\web\NotFoundHttpException', 'Page not found.'); + $this->expectException('yii\web\NotFoundHttpException'); + $this->expectExceptionMessage('Page not found.'); $filter = new HostControl(); $filter->allowedHosts = ['example.com']; diff --git a/tests/framework/filters/auth/AuthMethodTest.php b/tests/framework/filters/auth/AuthMethodTest.php index c02bd44..f79e274 100644 --- a/tests/framework/filters/auth/AuthMethodTest.php +++ b/tests/framework/filters/auth/AuthMethodTest.php @@ -31,7 +31,9 @@ class AuthMethodTest extends TestCase */ protected function createFilter($authenticateCallback) { - $filter = $this->getMock(AuthMethod::className(), ['authenticate']); + $filter = $this->getMockBuilder(AuthMethod::className()) + ->setMethods(['authenticate']) + ->getMock(); $filter->method('authenticate')->willReturnCallback($authenticateCallback); return $filter; @@ -58,7 +60,7 @@ class AuthMethodTest extends TestCase $this->assertTrue($filter->beforeAction($action)); $filter = $this->createFilter(function () {return null;}); - $this->setExpectedException('yii\web\UnauthorizedHttpException'); + $this->expectException('yii\web\UnauthorizedHttpException'); $this->assertTrue($filter->beforeAction($action)); } diff --git a/tests/framework/helpers/FileHelperTest.php b/tests/framework/helpers/FileHelperTest.php index 082d473..2b97f15 100644 --- a/tests/framework/helpers/FileHelperTest.php +++ b/tests/framework/helpers/FileHelperTest.php @@ -293,7 +293,7 @@ class FileHelperTest extends TestCase $dirName => [], ]); - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); $dirName = $this->testFilePath . DIRECTORY_SEPARATOR . 'test_dir'; FileHelper::copyDirectory($dirName, $dirName); @@ -309,7 +309,7 @@ class FileHelperTest extends TestCase 'backup' => ['data' => []] ]); - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); FileHelper::copyDirectory( $this->testFilePath . DIRECTORY_SEPARATOR . 'backup', diff --git a/tests/framework/helpers/HtmlTest.php b/tests/framework/helpers/HtmlTest.php index 443f416..af84a69 100644 --- a/tests/framework/helpers/HtmlTest.php +++ b/tests/framework/helpers/HtmlTest.php @@ -1309,7 +1309,7 @@ EOD; public function testAttributeNameValidation($name, $expected) { if (!isset($expected)) { - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); Html::getAttributeName($name); } else { $this->assertEquals($expected, Html::getAttributeName($name)); @@ -1323,7 +1323,7 @@ EOD; */ public function testAttributeNameException($name) { - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); Html::getAttributeName($name); } } diff --git a/tests/framework/helpers/JsonTest.php b/tests/framework/helpers/JsonTest.php index 7d59e36..a88ef94 100644 --- a/tests/framework/helpers/JsonTest.php +++ b/tests/framework/helpers/JsonTest.php @@ -147,7 +147,7 @@ class JsonTest extends TestCase // exception $json = '{"a":1,"b":2'; - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); Json::decode($json); } diff --git a/tests/framework/helpers/UrlTest.php b/tests/framework/helpers/UrlTest.php index 543d3d3..505e70c 100644 --- a/tests/framework/helpers/UrlTest.php +++ b/tests/framework/helpers/UrlTest.php @@ -108,7 +108,7 @@ class UrlTest extends TestCase // In case there is no controller, an exception should be thrown for relative route $this->removeMockedAction(); - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); Url::toRoute('site/view'); } @@ -217,7 +217,7 @@ class UrlTest extends TestCase //In case there is no controller, throw an exception $this->removeMockedAction(); - $this->setExpectedException('yii\base\InvalidParamException'); + $this->expectException('yii\base\InvalidParamException'); Url::to(['site/view']); } diff --git a/tests/framework/i18n/FallbackMessageFormatterTest.php b/tests/framework/i18n/FallbackMessageFormatterTest.php index 97765d5..4b2de7c 100644 --- a/tests/framework/i18n/FallbackMessageFormatterTest.php +++ b/tests/framework/i18n/FallbackMessageFormatterTest.php @@ -219,7 +219,7 @@ _MSG_ { $pattern = 'Number {'.self::N.', number, percent}'; $formatter = new FallbackMessageFormatter(); - $this->setExpectedException('yii\base\NotSupportedException'); + $this->expectException('yii\base\NotSupportedException'); $formatter->fallbackFormat($pattern, [self::N => self::N_VALUE], 'en-US'); } @@ -227,7 +227,7 @@ _MSG_ { $pattern = 'Number {'.self::N.', number, currency}'; $formatter = new FallbackMessageFormatter(); - $this->setExpectedException('yii\base\NotSupportedException'); + $this->expectException('yii\base\NotSupportedException'); $formatter->fallbackFormat($pattern, [self::N => self::N_VALUE], 'en-US'); } } diff --git a/tests/framework/i18n/FormatterDateTest.php b/tests/framework/i18n/FormatterDateTest.php index 354f854..70758d8 100644 --- a/tests/framework/i18n/FormatterDateTest.php +++ b/tests/framework/i18n/FormatterDateTest.php @@ -44,7 +44,7 @@ class FormatterDateTest extends TestCase $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'date')); $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'DATE')); $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'php:Y/m/d'])); - $this->setExpectedException('\yii\base\InvalidParamException'); + $this->expectException('\yii\base\InvalidParamException'); $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data')); } diff --git a/tests/framework/i18n/FormatterTest.php b/tests/framework/i18n/FormatterTest.php index 5fd885a..fb41b7c 100644 --- a/tests/framework/i18n/FormatterTest.php +++ b/tests/framework/i18n/FormatterTest.php @@ -47,7 +47,7 @@ class FormatterTest extends TestCase $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'date')); $this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'DATE')); $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'php:Y/m/d'])); - $this->setExpectedException('\yii\base\InvalidParamException'); + $this->expectException('\yii\base\InvalidParamException'); $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data')); } diff --git a/tests/framework/log/EmailTargetTest.php b/tests/framework/log/EmailTargetTest.php index 26897aa..ac2eb48 100644 --- a/tests/framework/log/EmailTargetTest.php +++ b/tests/framework/log/EmailTargetTest.php @@ -70,15 +70,18 @@ class EmailTargetTest extends TestCase $message->expects($this->once())->method('send')->with($this->equalTo($this->mailer)); $message->expects($this->once())->method('setSubject')->with($this->equalTo('Hello world')); - $mailTarget = $this->getMock('yii\\log\\EmailTarget', ['formatMessage'], [ - [ - 'mailer' => $this->mailer, - 'message'=> [ - 'to' => 'developer@example.com', - 'subject' => 'Hello world' + $mailTarget = $this->getMockBuilder('yii\\log\\EmailTarget') + ->setMethods(['formatMessage']) + ->setConstructorArgs([ + [ + 'mailer' => $this->mailer, + 'message'=> [ + 'to' => 'developer@example.com', + 'subject' => 'Hello world' + ] ] - ] - ]); + ]) + ->getMock(); $mailTarget->messages = $messages; $mailTarget->expects($this->exactly(2))->method('formatMessage')->willReturnMap( @@ -111,14 +114,17 @@ class EmailTargetTest extends TestCase $message->expects($this->once())->method('send')->with($this->equalTo($this->mailer)); $message->expects($this->once())->method('setSubject')->with($this->equalTo('Application Log')); - $mailTarget = $this->getMock('yii\\log\\EmailTarget', ['formatMessage'], [ - [ - 'mailer' => $this->mailer, - 'message'=> [ - 'to' => 'developer@example.com', + $mailTarget = $this->getMockBuilder('yii\\log\\EmailTarget') + ->setMethods(['formatMessage']) + ->setConstructorArgs([ + [ + 'mailer' => $this->mailer, + 'message'=> [ + 'to' => 'developer@example.com', + ] ] - ] - ]); + ]) + ->getMock(); $mailTarget->messages = $messages; $mailTarget->expects($this->exactly(2))->method('formatMessage')->willReturnMap( diff --git a/tests/framework/log/LoggerTest.php b/tests/framework/log/LoggerTest.php index b9441e7..267aaa0 100644 --- a/tests/framework/log/LoggerTest.php +++ b/tests/framework/log/LoggerTest.php @@ -26,7 +26,9 @@ class LoggerTest extends TestCase protected function setUp() { $this->logger = new Logger(); - $this->dispatcher = $this->getMock('yii\\log\\Dispatcher', ['dispatch']); + $this->dispatcher = $this->getMockBuilder('yii\\log\\Dispatcher') + ->setMethods(['dispatch']) + ->getMock(); } /** @@ -80,7 +82,9 @@ class LoggerTest extends TestCase */ public function testLogWithFlush() { - $logger = $this->getMock('yii\\log\\Logger', ['flush']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['flush']) + ->getMock(); $logger->flushInterval = 1; $logger->expects($this->exactly(1))->method('flush'); $logger->log('test1', Logger::LEVEL_INFO); @@ -91,7 +95,7 @@ class LoggerTest extends TestCase */ public function testFlushWithoutDispatcher() { - $dispatcher = $this->getMock('\stdClass'); + $dispatcher = $this->getMockBuilder('\stdClass')->getMock(); $dispatcher->expects($this->never())->method($this->anything()); $this->logger->messages = ['anything']; @@ -141,7 +145,9 @@ class LoggerTest extends TestCase ['duration' => 30], ]; - $logger = $this->getMock('yii\\log\\Logger', ['getProfiling']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['getProfiling']) + ->getMock(); $logger->method('getProfiling')->willReturn($timings); $logger->expects($this->once()) ->method('getProfiling') @@ -271,7 +277,9 @@ class LoggerTest extends TestCase { $messages = ['anyData']; $returnValue = 'return value'; - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); @@ -295,7 +303,9 @@ class LoggerTest extends TestCase 'duration' => 5, ] ]; - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); @@ -332,7 +342,9 @@ class LoggerTest extends TestCase /** * Matched by category name */ - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); @@ -342,7 +354,9 @@ class LoggerTest extends TestCase /** * Matched by prefix */ - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); @@ -388,7 +402,9 @@ class LoggerTest extends TestCase /** * Exclude by category name */ - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); @@ -398,7 +414,9 @@ class LoggerTest extends TestCase /** * Exclude by category prefix */ - $logger = $this->getMock('yii\\log\\Logger', ['calculateTimings']); + $logger = $this->getMockBuilder('yii\\log\\Logger') + ->setMethods(['calculateTimings']) + ->getMock(); $logger->messages = $messages; $logger->method('calculateTimings')->willReturn($returnValue); diff --git a/tests/framework/log/SyslogTargetTest.php b/tests/framework/log/SyslogTargetTest.php index 4a61b82..29c83f0 100644 --- a/tests/framework/log/SyslogTargetTest.php +++ b/tests/framework/log/SyslogTargetTest.php @@ -50,7 +50,9 @@ namespace yiiunit\framework\log { */ protected function setUp() { - $this->syslogTarget = $this->getMock('yii\\log\\SyslogTarget', ['getMessagePrefix']); + $this->syslogTarget = $this->getMockBuilder('yii\\log\\SyslogTarget') + ->setMethods(['getMessagePrefix']) + ->getMock(); } /** @@ -70,8 +72,9 @@ namespace yiiunit\framework\log { ['profile begin message', Logger::LEVEL_PROFILE_BEGIN], ['profile end message', Logger::LEVEL_PROFILE_END], ]; - $syslogTarget = $this - ->getMock('yii\\log\\SyslogTarget', ['openlog', 'syslog', 'formatMessage', 'closelog']); + $syslogTarget = $this->getMockBuilder('yii\\log\\SyslogTarget') + ->setMethods(['openlog', 'syslog', 'formatMessage', 'closelog']) + ->getMock(); $syslogTarget->identity = $identity; $syslogTarget->options = $options; diff --git a/tests/framework/log/TargetTest.php b/tests/framework/log/TargetTest.php index dcc1ab3..67316da 100644 --- a/tests/framework/log/TargetTest.php +++ b/tests/framework/log/TargetTest.php @@ -138,7 +138,8 @@ class TargetTest extends TestCase $target->setLevels(['trace']); $this->assertEquals(Logger::LEVEL_TRACE, $target->getLevels()); - $this->setExpectedException('yii\\base\\InvalidConfigException', 'Unrecognized level: unknown level'); + $this->expectException('yii\\base\\InvalidConfigException'); + $this->expectExceptionMessage('Unrecognized level: unknown level'); $target->setLevels(['info', 'unknown level']); } @@ -156,7 +157,8 @@ class TargetTest extends TestCase $target->setLevels(Logger::LEVEL_TRACE); $this->assertEquals(Logger::LEVEL_TRACE, $target->getLevels()); - $this->setExpectedException('yii\\base\\InvalidConfigException', 'Incorrect 128 value'); + $this->expectException('yii\\base\\InvalidConfigException'); + $this->expectExceptionMessage('Incorrect 128 value'); $target->setLevels(128); } } diff --git a/tests/framework/mail/BaseMailerTest.php b/tests/framework/mail/BaseMailerTest.php index 2e457bb..11d6bea 100644 --- a/tests/framework/mail/BaseMailerTest.php +++ b/tests/framework/mail/BaseMailerTest.php @@ -292,7 +292,9 @@ TEXT { $message = new Message(); - $mailerMock = $this->getMockBuilder('yiiunit\framework\mail\Mailer')->setMethods(['beforeSend', 'afterSend'])->getMock(); + $mailerMock = $this->getMockBuilder('yiiunit\framework\mail\Mailer') + ->setMethods(['beforeSend', 'afterSend']) + ->getMock(); $mailerMock->expects($this->once())->method('beforeSend')->with($message)->will($this->returnValue(true)); $mailerMock->expects($this->once())->method('afterSend')->with($message, true); $mailerMock->send($message); diff --git a/tests/framework/validators/CompareValidatorTest.php b/tests/framework/validators/CompareValidatorTest.php index 5e3d888..181e98a 100644 --- a/tests/framework/validators/CompareValidatorTest.php +++ b/tests/framework/validators/CompareValidatorTest.php @@ -19,7 +19,7 @@ class CompareValidatorTest extends TestCase public function testValidateValueException() { - $this->setExpectedException('yii\base\InvalidConfigException'); + $this->expectException('yii\base\InvalidConfigException'); $val = new CompareValidator; $val->validate('val'); } diff --git a/tests/framework/validators/FilterValidatorTest.php b/tests/framework/validators/FilterValidatorTest.php index 1b77047..b554e48 100644 --- a/tests/framework/validators/FilterValidatorTest.php +++ b/tests/framework/validators/FilterValidatorTest.php @@ -19,7 +19,7 @@ class FilterValidatorTest extends TestCase public function testAssureExceptionOnInit() { - $this->setExpectedException('yii\base\InvalidConfigException'); + $this->expectException('yii\base\InvalidConfigException'); new FilterValidator(); } diff --git a/tests/framework/validators/IpValidatorTest.php b/tests/framework/validators/IpValidatorTest.php index 04cd90e..99ebb81 100644 --- a/tests/framework/validators/IpValidatorTest.php +++ b/tests/framework/validators/IpValidatorTest.php @@ -23,8 +23,8 @@ class IpValidatorTest extends TestCase public function testInitException() { - $this->setExpectedException('yii\base\InvalidConfigException', - 'Both IPv4 and IPv6 checks can not be disabled at the same time'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('Both IPv4 and IPv6 checks can not be disabled at the same time'); new IpValidator(['ipv4' => false, 'ipv6' => false]); } diff --git a/tests/framework/validators/RangeValidatorTest.php b/tests/framework/validators/RangeValidatorTest.php index 15b9eff..f882eb1 100644 --- a/tests/framework/validators/RangeValidatorTest.php +++ b/tests/framework/validators/RangeValidatorTest.php @@ -19,7 +19,8 @@ class RangeValidatorTest extends TestCase public function testInitException() { - $this->setExpectedException('yii\base\InvalidConfigException', 'The "range" property must be set.'); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessage('The "range" property must be set.'); new RangeValidator(['range' => 'not an array']); } diff --git a/tests/framework/validators/RegularExpressionValidatorTest.php b/tests/framework/validators/RegularExpressionValidatorTest.php index e9c3ce7..adbf7be 100644 --- a/tests/framework/validators/RegularExpressionValidatorTest.php +++ b/tests/framework/validators/RegularExpressionValidatorTest.php @@ -48,7 +48,7 @@ class RegularExpressionValidatorTest extends TestCase public function testInitException() { - $this->setExpectedException('yii\base\InvalidConfigException'); + $this->expectException('yii\base\InvalidConfigException'); $val = new RegularExpressionValidator(); $val->validate('abc'); } diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index c77369d..afdb0c4 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -139,7 +139,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase public function testValidateAttributeAttributeNotInTableException() { - $this->setExpectedException('yii\db\Exception'); + $this->expectException('yii\db\Exception'); $val = new UniqueValidator(); $m = new ValidatorTestMainModel(); $val->validateAttribute($m, 'testMainVal'); diff --git a/tests/framework/validators/ValidatorTest.php b/tests/framework/validators/ValidatorTest.php index 4f64887..eb3ab05 100644 --- a/tests/framework/validators/ValidatorTest.php +++ b/tests/framework/validators/ValidatorTest.php @@ -168,10 +168,8 @@ class ValidatorTest extends TestCase public function testValidateValue() { - $this->setExpectedException( - 'yii\base\NotSupportedException', - TestValidator::className() . ' does not support validateValue().' - ); + $this->expectException('yii\base\NotSupportedException'); + $this->expectExceptionMessage(TestValidator::className() . ' does not support validateValue().'); $val = new TestValidator(); $val->validate('abc'); } diff --git a/tests/framework/web/AssetBundleTest.php b/tests/framework/web/AssetBundleTest.php index 430da08..d2e9274 100644 --- a/tests/framework/web/AssetBundleTest.php +++ b/tests/framework/web/AssetBundleTest.php @@ -299,13 +299,13 @@ EOF; if ($jqAlreadyRegistered) { TestJqueryAsset::register($view); } - $this->setExpectedException('yii\\base\\InvalidConfigException'); + $this->expectException('yii\\base\\InvalidConfigException'); TestAssetBundle::register($view); } public function testCircularDependency() { - $this->setExpectedException('yii\\base\\InvalidConfigException'); + $this->expectException('yii\\base\\InvalidConfigException'); TestAssetCircleA::register($this->getView()); } diff --git a/tests/framework/web/CacheSessionTest.php b/tests/framework/web/CacheSessionTest.php index e61ced9..3cb9383 100644 --- a/tests/framework/web/CacheSessionTest.php +++ b/tests/framework/web/CacheSessionTest.php @@ -30,7 +30,7 @@ class CacheSessionTest extends \yiiunit\TestCase public function testInvalidCache() { - $this->setExpectedException('\Exception'); + $this->expectException('\Exception'); new CacheSession(['cache' => 'invalid']); } diff --git a/tests/framework/web/ResponseTest.php b/tests/framework/web/ResponseTest.php index 56967df..866a43a 100644 --- a/tests/framework/web/ResponseTest.php +++ b/tests/framework/web/ResponseTest.php @@ -75,7 +75,7 @@ class ResponseTest extends \yiiunit\TestCase */ public function testSendFileWrongRanges($rangeHeader) { - $this->setExpectedException('yii\web\RangeNotSatisfiableHttpException'); + $this->expectException('yii\web\RangeNotSatisfiableHttpException'); $dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt'); $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index f9dcc35..dd457a3 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -268,7 +268,7 @@ class UserTest extends TestCase $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; - $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + $this->expectException('yii\\web\\ForbiddenHttpException'); $user->loginRequired(); } @@ -291,7 +291,7 @@ class UserTest extends TestCase $this->mockWebApplication($appConfig); $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json,q=0.1'; - $this->setExpectedException('yii\\web\\ForbiddenHttpException'); + $this->expectException('yii\\web\\ForbiddenHttpException'); Yii::$app->user->loginRequired(); } diff --git a/tests/framework/widgets/BreadcrumbsTest.php b/tests/framework/widgets/BreadcrumbsTest.php index 75124c1..17a816e 100644 --- a/tests/framework/widgets/BreadcrumbsTest.php +++ b/tests/framework/widgets/BreadcrumbsTest.php @@ -88,7 +88,7 @@ class BreadcrumbsTest extends \yiiunit\TestCase { $link = ['url' => 'http://localhost/yii2']; $method = $this->reflectMethod(); - $this->setExpectedException('yii\base\InvalidConfigException'); + $this->expectException('yii\base\InvalidConfigException'); $method->invoke($this->breadcrumbs, $link, $this->breadcrumbs->itemTemplate); } From 15561b7a2c16e812a1ef359b9c34b0ec010c2492 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 14:56:03 +0300 Subject: [PATCH 115/184] unit test fix --- tests/framework/log/LoggerTest.php | 2 +- tests/framework/web/ErrorHandlerTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/framework/log/LoggerTest.php b/tests/framework/log/LoggerTest.php index 267aaa0..d40e776 100644 --- a/tests/framework/log/LoggerTest.php +++ b/tests/framework/log/LoggerTest.php @@ -68,7 +68,7 @@ class LoggerTest extends TestCase $this->assertEquals('application', $this->logger->messages[0][2]); $this->assertEquals([ 'file' => __FILE__, - 'line' => 62, + 'line' => 64, 'function' => 'log', 'class' => get_class($this->logger), 'type' => '->' diff --git a/tests/framework/web/ErrorHandlerTest.php b/tests/framework/web/ErrorHandlerTest.php index 6054c98..2cdd1de 100644 --- a/tests/framework/web/ErrorHandlerTest.php +++ b/tests/framework/web/ErrorHandlerTest.php @@ -25,7 +25,9 @@ class ErrorHandlerTest extends TestCase { /** @var ErrorHandler $handler */ $handler = Yii::$app->getErrorHandler(); + ob_start(); // suppress response output $this->invokeMethod($handler, 'renderException', [new NotFoundHttpException('This message is displayed to end user')]); + ob_get_clean(); $out = Yii::$app->response->data; $this->assertEquals('Code: 404 Message: This message is displayed to end user From b0b55472da12afad7901ddc7460c7c7bd9be7ede Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 13 Apr 2017 15:07:19 +0300 Subject: [PATCH 116/184] `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` Closes #13981 --- framework/CHANGELOG.md | 3 ++- framework/caching/Cache.php | 12 ++++++------ tests/framework/caching/CacheTestCase.php | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5bb436e..3e647f7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -63,7 +63,8 @@ Yii Framework 2 Change Log - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) -- Bug #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed (vsguts) +- Bug #13961: RBAC Rules: `PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given` was fixed (vsguts) +- Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index 4efc473..1384189 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -541,7 +541,7 @@ abstract class Cache extends Component implements \ArrayAccess /** * Method combines both [[set()]] and [[get()]] methods to retrieve value identified by a $key, - * or to store the result of $closure execution if there is no cache available for the $key. + * or to store the result of $callable execution if there is no cache available for the $key. * * Usage example: * @@ -556,23 +556,23 @@ abstract class Cache extends Component implements \ArrayAccess * * @param mixed $key a key identifying the value to be cached. This can be a simple string or * a complex data structure consisting of factors representing the key. - * @param \Closure $closure the closure that will be used to generate a value to be cached. - * In case $closure returns `false`, the value will not be cached. + * @param callable|\Closure $callable the callable or closure that will be used to generate a value to be cached. + * In case $callable returns `false`, the value will not be cached. * @param int $duration default duration in seconds before the cache will expire. If not set, * [[defaultDuration]] value will be used. * @param Dependency $dependency dependency of the cached item. If the dependency changes, * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. * This parameter is ignored if [[serializer]] is `false`. - * @return mixed result of $closure execution + * @return mixed result of $callable execution * @since 2.0.11 */ - public function getOrSet($key, \Closure $closure, $duration = null, $dependency = null) + public function getOrSet($key, $callable, $duration = null, $dependency = null) { if (($value = $this->get($key)) !== false) { return $value; } - $value = call_user_func($closure, $this); + $value = call_user_func($callable, $this); if (!$this->set($key, $value, $duration, $dependency)) { Yii::warning('Failed to set cache value for key ' . json_encode($key), __METHOD__); } diff --git a/tests/framework/caching/CacheTestCase.php b/tests/framework/caching/CacheTestCase.php index a9b3004..9d5c399 100644 --- a/tests/framework/caching/CacheTestCase.php +++ b/tests/framework/caching/CacheTestCase.php @@ -255,6 +255,23 @@ abstract class CacheTestCase extends TestCase public function testGetOrSet() { $cache = $this->prepare(); + + $expected = $this->getOrSetCallable($cache); + $callable = [$this, 'getOrSetCallable']; + + $this->assertEquals(null, $cache->get('something')); + $this->assertEquals($expected, $cache->getOrSet('something', $callable)); + $this->assertEquals($expected, $cache->get('something')); + } + + public function getOrSetCallable($cache) + { + return get_class($cache); + } + + public function testGetOrSetWithDependencies() + { + $cache = $this->prepare(); $dependency = new TagDependency(['tags' => 'test']); $expected = 'SilverFire'; From 04a14efd3f513d666b835f512d5526f1e13924b1 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 15:29:10 +0300 Subject: [PATCH 117/184] compatibility with PHPUnit 6.x fix --- tests/IsOneOfAssert.php | 2 +- tests/compatibility.php | 8 ++++++++ tests/framework/di/InstanceTest.php | 10 ++++++---- tests/framework/i18n/FormatterDateTest.php | 2 +- tests/framework/web/ErrorActionTest.php | 3 ++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/IsOneOfAssert.php b/tests/IsOneOfAssert.php index f813ab2..3ca85d2 100644 --- a/tests/IsOneOfAssert.php +++ b/tests/IsOneOfAssert.php @@ -5,7 +5,7 @@ namespace yiiunit; /** * IsOneOfAssert asserts that the value is one of the expected values. */ -class IsOneOfAssert extends \PHPUnit_Framework_Constraint +class IsOneOfAssert extends \PHPUnit\Framework\Constraint\Constraint { private $allowedValues; diff --git a/tests/compatibility.php b/tests/compatibility.php index f7fd3f2..9c89bcb 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -27,6 +27,14 @@ namespace PHPUnit\Framework { { $this->setExpectedException($this->getExpectedException(), $message); } + + /** + * @param string $messageRegExp + */ + public function expectExceptionMessageRegExp($messageRegExp) + { + $this->setExpectedExceptionRegExp($messageRegExp); + } } } } diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 6b8c059..13b383f 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -51,8 +51,9 @@ class InstanceTest extends TestCase */ public function testEnsure_NonExistingComponentException() { - $container = new Container; - $this->setExpectedExceptionRegExp('yii\base\InvalidConfigException', '/^Failed to instantiate component or class/i'); + $container = new Container(); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessageRegExp('/^Failed to instantiate component or class/i'); Instance::ensure('cache', 'yii\cache\Cache', $container); } @@ -61,8 +62,9 @@ class InstanceTest extends TestCase */ public function testEnsure_NonExistingClassException() { - $container = new Container; - $this->setExpectedExceptionRegExp('yii\base\InvalidConfigException', '/^Failed to instantiate component or class/i'); + $container = new Container(); + $this->expectException('yii\base\InvalidConfigException'); + $this->expectExceptionMessageRegExp('/^Failed to instantiate component or class/i'); Instance::ensure('yii\cache\DoesNotExist', 'yii\cache\Cache', $container); } diff --git a/tests/framework/i18n/FormatterDateTest.php b/tests/framework/i18n/FormatterDateTest.php index 70758d8..359f8de 100644 --- a/tests/framework/i18n/FormatterDateTest.php +++ b/tests/framework/i18n/FormatterDateTest.php @@ -520,7 +520,7 @@ class FormatterDateTest extends TestCase public function testDateInput($expected, $value, $expectedException = null) { if ($expectedException !== null) { - $this->setExpectedException($expectedException); + $this->expectException($expectedException); } $this->assertSame($expected, $this->formatter->asDate($value, 'yyyy-MM-dd HH:mm:ss')); $this->assertSame($expected, $this->formatter->asTime($value, 'yyyy-MM-dd HH:mm:ss')); diff --git a/tests/framework/web/ErrorActionTest.php b/tests/framework/web/ErrorActionTest.php index 10d7368..f6e57f2 100644 --- a/tests/framework/web/ErrorActionTest.php +++ b/tests/framework/web/ErrorActionTest.php @@ -99,7 +99,8 @@ Exception: yii\web\NotFoundHttpException', $this->getController()->runAction('er // Unset view name. Class should try to load view that matches action name by default $action->view = null; $ds = preg_quote(DIRECTORY_SEPARATOR, '\\'); - $this->setExpectedExceptionRegExp('yii\base\ViewNotFoundException', '#The view file does not exist: .*?views' . $ds . 'test' . $ds . 'error.php#'); + $this->expectException('yii\base\ViewNotFoundException'); + $this->expectExceptionMessageRegExp('#The view file does not exist: .*?views' . $ds . 'test' . $ds . 'error.php#'); $this->invokeMethod($action, 'renderHtmlResponse'); } From 80ca12872eba34ed3c2ff300b55c31d9a2f60553 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 15:40:17 +0300 Subject: [PATCH 118/184] garbage collection added to unit tests --- tests/framework/base/BehaviorTest.php | 7 +++++++ .../framework/behaviors/AttributeTypecastBehaviorTest.php | 2 ++ tests/framework/behaviors/BlameableBehaviorTest.php | 14 ++++++++------ tests/framework/behaviors/SluggableBehaviorTest.php | 2 ++ tests/framework/behaviors/TimestampBehaviorTest.php | 2 ++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/framework/base/BehaviorTest.php b/tests/framework/base/BehaviorTest.php index a54f713..d923184 100644 --- a/tests/framework/base/BehaviorTest.php +++ b/tests/framework/base/BehaviorTest.php @@ -74,6 +74,13 @@ class BehaviorTest extends TestCase $this->mockApplication(); } + protected function tearDown() + { + parent::tearDown(); + gc_enable(); + gc_collect_cycles(); + } + public function testAttachAndAccessingWithName() { BarBehavior::$attachCount = 0; diff --git a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php index b73d500..2de622f 100644 --- a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php +++ b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php @@ -49,6 +49,8 @@ class AttributeTypecastBehaviorTest extends TestCase { parent::tearDown(); AttributeTypecastBehavior::clearAutoDetectedAttributeTypes(); + gc_enable(); + gc_collect_cycles(); } // Tests : diff --git a/tests/framework/behaviors/BlameableBehaviorTest.php b/tests/framework/behaviors/BlameableBehaviorTest.php index 739dce6..b5cbad1 100644 --- a/tests/framework/behaviors/BlameableBehaviorTest.php +++ b/tests/framework/behaviors/BlameableBehaviorTest.php @@ -48,6 +48,14 @@ class BlameableBehaviorTest extends TestCase $this->getUser()->login(10); } + public function tearDown() + { + Yii::$app->getDb()->close(); + parent::tearDown(); + gc_enable(); + gc_collect_cycles(); + } + /** * @return UserMock */ @@ -56,12 +64,6 @@ class BlameableBehaviorTest extends TestCase return Yii::$app->get('user'); } - public function tearDown() - { - Yii::$app->getDb()->close(); - parent::tearDown(); - } - public function testInsertUserIsGuest() { $this->getUser()->logout(); diff --git a/tests/framework/behaviors/SluggableBehaviorTest.php b/tests/framework/behaviors/SluggableBehaviorTest.php index 484cae4..571e67c 100644 --- a/tests/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/framework/behaviors/SluggableBehaviorTest.php @@ -59,6 +59,8 @@ class SluggableBehaviorTest extends TestCase { Yii::$app->getDb()->close(); parent::tearDown(); + gc_enable(); + gc_collect_cycles(); } // Tests : diff --git a/tests/framework/behaviors/TimestampBehaviorTest.php b/tests/framework/behaviors/TimestampBehaviorTest.php index b37f4a5..486e8e0 100644 --- a/tests/framework/behaviors/TimestampBehaviorTest.php +++ b/tests/framework/behaviors/TimestampBehaviorTest.php @@ -59,6 +59,8 @@ class TimestampBehaviorTest extends TestCase { Yii::$app->getDb()->close(); parent::tearDown(); + gc_enable(); + gc_collect_cycles(); } // Tests : From 679bc09b54b9d5b3eadc6ef9ef114c8c06bb2e86 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 15:46:17 +0300 Subject: [PATCH 119/184] compatibility with PHPUnit 6.x fix --- tests/compatibility.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compatibility.php b/tests/compatibility.php index 9c89bcb..cd6c3d7 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -33,7 +33,7 @@ namespace PHPUnit\Framework { */ public function expectExceptionMessageRegExp($messageRegExp) { - $this->setExpectedExceptionRegExp($messageRegExp); + $this->setExpectedExceptionRegExp($this->getExpectedException(), $messageRegExp); } } } From d8e6a89af9070b97ae0bb54604528515793c1c8c Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 16:17:35 +0300 Subject: [PATCH 120/184] fixed tests without assettions --- tests/framework/db/ActiveRecordTest.php | 3 ++- tests/framework/db/ConnectionTest.php | 2 ++ tests/framework/db/QueryBuilderTest.php | 1 + tests/framework/db/QueryTest.php | 4 ++-- tests/framework/db/pgsql/ConnectionTest.php | 2 +- tests/framework/helpers/FileHelperTest.php | 1 + tests/framework/i18n/FormatterTest.php | 4 ++-- tests/framework/log/EmailTargetTest.php | 3 ++- tests/framework/widgets/MenuTest.php | 4 ++-- 9 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index f31d76a..a1a5cc4 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -577,11 +577,12 @@ abstract class ActiveRecordTest extends DatabaseTestCase public function testJoinWithVia() { Order::getDb()->getQueryBuilder()->separator = "\n"; - Order::find()->joinWith('itemsInOrder1')->joinWith([ + $rows = Order::find()->joinWith('itemsInOrder1')->joinWith([ 'items' => function ($q) { $q->orderBy('item.id'); }, ])->all(); + $this->assertNotEmpty($rows); } public function aliasMethodProvider() diff --git a/tests/framework/db/ConnectionTest.php b/tests/framework/db/ConnectionTest.php index 761be2a..9e95be5 100644 --- a/tests/framework/db/ConnectionTest.php +++ b/tests/framework/db/ConnectionTest.php @@ -151,6 +151,8 @@ abstract class ConnectionTest extends DatabaseTestCase $transaction = $connection->beginTransaction(Transaction::SERIALIZABLE); $transaction->commit(); + + $this->assertTrue(true); // should not be any exception so far } /** diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 3312bd7..34d6b06 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -1036,6 +1036,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase } } $this->getConnection(false)->createCommand($qb->createTable('column_type_table', $columns))->execute(); + $this->assertNotEmpty($qb->db->getTableSchema('column_type_table', true)); } public function conditionProvider() diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index f230c86..1275a3f 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -168,9 +168,9 @@ abstract class QueryTest extends DatabaseTestCase $this->assertEquals(['and', ['id' => 1]], $query->where); } - public function testJoin() + /*public function testJoin() { - } + }*/ public function testGroup() { diff --git a/tests/framework/db/pgsql/ConnectionTest.php b/tests/framework/db/pgsql/ConnectionTest.php index 429c545..bea53ff 100644 --- a/tests/framework/db/pgsql/ConnectionTest.php +++ b/tests/framework/db/pgsql/ConnectionTest.php @@ -14,7 +14,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest public function testConnection() { - $this->getConnection(true); + $this->assertTrue(is_object($this->getConnection(true))); } public function testQuoteValue() diff --git a/tests/framework/helpers/FileHelperTest.php b/tests/framework/helpers/FileHelperTest.php index 2b97f15..d70c378 100644 --- a/tests/framework/helpers/FileHelperTest.php +++ b/tests/framework/helpers/FileHelperTest.php @@ -331,6 +331,7 @@ class FileHelperTest extends TestCase $this->testFilePath . DIRECTORY_SEPARATOR . 'data', $this->testFilePath . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'data' ); + $this->assertTrue(file_exists($this->testFilePath . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'data')); } /** diff --git a/tests/framework/i18n/FormatterTest.php b/tests/framework/i18n/FormatterTest.php index fb41b7c..8d53f55 100644 --- a/tests/framework/i18n/FormatterTest.php +++ b/tests/framework/i18n/FormatterTest.php @@ -135,10 +135,10 @@ class FormatterTest extends TestCase $this->assertSame($this->formatter->nullDisplay, $this->formatter->asParagraphs(null)); } - public function testAsHtml() + /*public function testAsHtml() { // todo: dependency on HtmlPurifier - } + }*/ public function testAsEmail() { diff --git a/tests/framework/log/EmailTargetTest.php b/tests/framework/log/EmailTargetTest.php index ac2eb48..c1062a7 100644 --- a/tests/framework/log/EmailTargetTest.php +++ b/tests/framework/log/EmailTargetTest.php @@ -36,7 +36,8 @@ class EmailTargetTest extends TestCase */ public function testInitWithOptionTo() { - new EmailTarget(['mailer' => $this->mailer, 'message'=> ['to' => 'developer1@example.com']]); + $target = new EmailTarget(['mailer' => $this->mailer, 'message'=> ['to' => 'developer1@example.com']]); + $this->assertTrue(is_object($target)); // should be no exception during `init()` } /** diff --git a/tests/framework/widgets/MenuTest.php b/tests/framework/widgets/MenuTest.php index 6b9cc27..0691bb3 100644 --- a/tests/framework/widgets/MenuTest.php +++ b/tests/framework/widgets/MenuTest.php @@ -297,8 +297,8 @@ HTML; $this->assertEqualsWithoutLE($expected, $output); } - public function testIsItemActive() + /*public function testIsItemActive() { // TODO: implement test of protected method isItemActive() - } + }*/ } From 8269b73b45e21c06768e2fedb449f14cac43436e Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 16:22:43 +0300 Subject: [PATCH 121/184] compatibility with PHPUnit 6.x fix --- tests/compatibility.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/compatibility.php b/tests/compatibility.php index cd6c3d7..63e6238 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -25,6 +25,11 @@ namespace PHPUnit\Framework { */ public function expectExceptionMessage($message) { + $parentClassMethods = get_class_methods(get_parent_class($this)); + if (in_array('expectExceptionMessage', $parentClassMethods)) { + parent::expectExceptionMessage($message); + return; + } $this->setExpectedException($this->getExpectedException(), $message); } @@ -33,6 +38,11 @@ namespace PHPUnit\Framework { */ public function expectExceptionMessageRegExp($messageRegExp) { + $parentClassMethods = get_class_methods(get_parent_class($this)); + if (in_array('expectExceptionMessageRegExp', $parentClassMethods)) { + parent::expectExceptionMessageRegExp($messageRegExp); + return; + } $this->setExpectedExceptionRegExp($this->getExpectedException(), $messageRegExp); } } From 86bff27be54b5e58f6fc7418fe8eab7a84b661a6 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 16:30:11 +0300 Subject: [PATCH 122/184] compatibility with PHPUnit 6.x fix --- tests/compatibility.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compatibility.php b/tests/compatibility.php index 63e6238..95e4cd1 100644 --- a/tests/compatibility.php +++ b/tests/compatibility.php @@ -25,7 +25,7 @@ namespace PHPUnit\Framework { */ public function expectExceptionMessage($message) { - $parentClassMethods = get_class_methods(get_parent_class($this)); + $parentClassMethods = get_class_methods('PHPUnit_Framework_TestCase'); if (in_array('expectExceptionMessage', $parentClassMethods)) { parent::expectExceptionMessage($message); return; @@ -38,7 +38,7 @@ namespace PHPUnit\Framework { */ public function expectExceptionMessageRegExp($messageRegExp) { - $parentClassMethods = get_class_methods(get_parent_class($this)); + $parentClassMethods = get_class_methods('PHPUnit_Framework_TestCase'); if (in_array('expectExceptionMessageRegExp', $parentClassMethods)) { parent::expectExceptionMessageRegExp($messageRegExp); return; From 928a30588d942bc08534be9a7d1eb4b71eb30098 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 13 Apr 2017 17:00:59 +0300 Subject: [PATCH 123/184] memory usage at unit test reduced --- tests/TestCase.php | 11 +++++++++++ tests/framework/base/ComponentTest.php | 2 ++ tests/framework/di/ContainerTest.php | 6 ++++++ tests/framework/di/InstanceTest.php | 12 +++++++++--- tests/framework/di/ServiceLocatorTest.php | 8 ++++---- tests/framework/filters/AccessRuleTest.php | 7 +++---- tests/framework/filters/HttpCacheTest.php | 8 ++++---- 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 45310f4..829be9f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace yiiunit; +use Yii; use yii\helpers\ArrayHelper; /** @@ -12,6 +13,16 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase public static $params; /** + * Clean up after test case. + */ + public static function tearDownAfterClass() + { + parent::tearDownAfterClass(); + $logger = Yii::getLogger(); + $logger->flush(); + } + + /** * Returns a test configuration param from /data/config.php * @param string $name params name * @param mixed $default default value to use when param is not set. diff --git a/tests/framework/base/ComponentTest.php b/tests/framework/base/ComponentTest.php index dc01e91..b535891 100644 --- a/tests/framework/base/ComponentTest.php +++ b/tests/framework/base/ComponentTest.php @@ -38,6 +38,8 @@ class ComponentTest extends TestCase { parent::tearDown(); $this->component = null; + gc_enable(); + gc_collect_cycles(); } public function testClone() diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php index 211d336..9655ef2 100644 --- a/tests/framework/di/ContainerTest.php +++ b/tests/framework/di/ContainerTest.php @@ -28,6 +28,12 @@ use yii\validators\NumberValidator; */ class ContainerTest extends TestCase { + protected function tearDown() + { + parent::tearDown(); + Yii::$container = new Container(); + } + public function testDefault() { $namespace = __NAMESPACE__ . '\stubs'; diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 13b383f..3e673dd 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -21,9 +21,15 @@ use yiiunit\TestCase; */ class InstanceTest extends TestCase { + protected function tearDown() + { + parent::tearDown(); + Yii::$container = new Container(); + } + public function testOf() { - $container = new Container; + $container = new Container(); $className = Component::className(); $instance = Instance::of($className); @@ -35,7 +41,7 @@ class InstanceTest extends TestCase public function testEnsure() { - $container = new Container; + $container = new Container(); $container->set('db', [ 'class' => 'yii\db\Connection', 'dsn' => 'test', @@ -70,7 +76,7 @@ class InstanceTest extends TestCase public function testEnsure_WithoutType() { - $container = new Container; + $container = new Container(); $container->set('db', [ 'class' => 'yii\db\Connection', 'dsn' => 'test', diff --git a/tests/framework/di/ServiceLocatorTest.php b/tests/framework/di/ServiceLocatorTest.php index 0e8411b..36766b7 100644 --- a/tests/framework/di/ServiceLocatorTest.php +++ b/tests/framework/di/ServiceLocatorTest.php @@ -35,7 +35,7 @@ class ServiceLocatorTest extends TestCase public function testCallable() { // anonymous function - $container = new ServiceLocator; + $container = new ServiceLocator(); $className = TestClass::className(); $container->set($className, function () { return new TestClass([ @@ -49,7 +49,7 @@ class ServiceLocatorTest extends TestCase $this->assertEquals(200, $object->prop2); // static method - $container = new ServiceLocator; + $container = new ServiceLocator(); $className = TestClass::className(); $container->set($className, [__NAMESPACE__ . "\\Creator", 'create']); $object = $container->get($className); @@ -60,7 +60,7 @@ class ServiceLocatorTest extends TestCase public function testObject() { - $object = new TestClass; + $object = new TestClass(); $className = TestClass::className(); $container = new ServiceLocator; $container->set($className, $object); @@ -70,7 +70,7 @@ class ServiceLocatorTest extends TestCase public function testShared() { // with configuration: shared - $container = new ServiceLocator; + $container = new ServiceLocator(); $className = TestClass::className(); $container->set($className, [ 'class' => $className, diff --git a/tests/framework/filters/AccessRuleTest.php b/tests/framework/filters/AccessRuleTest.php index 5e5cef0..57e67ac 100644 --- a/tests/framework/filters/AccessRuleTest.php +++ b/tests/framework/filters/AccessRuleTest.php @@ -67,9 +67,10 @@ class AccessRuleTest extends \yiiunit\TestCase } /** - * @return BaseManager + * @return \yii\rbac\BaseManager */ - protected function mockAuthManager() { + protected function mockAuthManager() + { $auth = new MockAuthManager(); // add "createPost" permission $createPost = $auth->createPermission('createPost'); @@ -180,7 +181,6 @@ class AccessRuleTest extends \yiiunit\TestCase $this->assertEquals($expected, $rule->allows($action, $user, $request)); } - public function testMatchVerb() { $action = $this->mockAction(); @@ -334,5 +334,4 @@ class AccessRuleTest extends \yiiunit\TestCase $rule->allow = false; $this->assertNull($rule->allows($action, $user, $request)); } - } diff --git a/tests/framework/filters/HttpCacheTest.php b/tests/framework/filters/HttpCacheTest.php index deafe97..46a05df 100644 --- a/tests/framework/filters/HttpCacheTest.php +++ b/tests/framework/filters/HttpCacheTest.php @@ -22,7 +22,7 @@ class HttpCacheTest extends \yiiunit\TestCase public function testDisabled() { - $httpCache = new HttpCache; + $httpCache = new HttpCache(); $this->assertTrue($httpCache->beforeAction(null)); $httpCache->enabled=false; $this->assertTrue($httpCache->beforeAction(null)); @@ -30,7 +30,7 @@ class HttpCacheTest extends \yiiunit\TestCase public function testEmptyPragma() { - $httpCache = new HttpCache; + $httpCache = new HttpCache(); $httpCache->etagSeed = function($action, $params) { return ''; }; @@ -45,7 +45,7 @@ class HttpCacheTest extends \yiiunit\TestCase */ public function testValidateCache() { - $httpCache = new HttpCache; + $httpCache = new HttpCache(); $method = new \ReflectionMethod($httpCache, 'validateCache'); $method->setAccessible(true); @@ -75,7 +75,7 @@ class HttpCacheTest extends \yiiunit\TestCase */ public function testGenerateEtag() { - $httpCache = new HttpCache; + $httpCache = new HttpCache(); $httpCache->weakEtag = false; $httpCache->etagSeed = function($action, $params) { From 0f78008bc71af6aca45152452f9f5017e7361e6a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 13 Apr 2017 18:56:11 +0200 Subject: [PATCH 124/184] update phpdoc annotation in BaseActiveRecord https://github.com/yiisoft/yii2-gii/pull/267#issuecomment-293958830 --- framework/db/BaseActiveRecord.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index c2e6e61..f857666 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -371,7 +371,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function hasOne($class, $link) { /* @var $class ActiveRecordInterface */ - /* @var $query ActiveQuery */ + /* @var $query ActiveQueryInterface */ $query = $class::find(); $query->primaryModel = $this; $query->link = $link; @@ -412,7 +412,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function hasMany($class, $link) { /* @var $class ActiveRecordInterface */ - /* @var $query ActiveQuery */ + /* @var $query ActiveQueryInterface */ $query = $class::find(); $query->primaryModel = $this; $query->link = $link; From dbfe36521e78c856bb5ca9aefdf80336ea0038d3 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 13 Apr 2017 19:02:06 +0200 Subject: [PATCH 125/184] added upgrade note about yii\cache\Cache::getOrSet() --- framework/UPGRADE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 9f51979..08a976c 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -59,6 +59,9 @@ Upgrade from Yii 2.0.11 * `yii\grid\DataColumn` filter is now automatically generated as dropdown list with localized `Yes` and `No` strings in case of `format` being set to `boolean`. +* The signature of `yii\cache\Cache::getOrSet()` has been adjusted to also accept a callable and not only `Closure`. + If you extend this method, make sure to adjust your code. + Upgrade from Yii 2.0.10 ----------------------- From 0fdd5b81dded1fc388ba2a83a62712edacab7b70 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 13 Apr 2017 19:21:40 +0200 Subject: [PATCH 126/184] Revert "update phpdoc annotation in BaseActiveRecord" This reverts commit 0f78008bc71af6aca45152452f9f5017e7361e6a. https://github.com/yiisoft/yii2-gii/pull/267#issuecomment-293965278 --- framework/db/BaseActiveRecord.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index f857666..c2e6e61 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -371,7 +371,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function hasOne($class, $link) { /* @var $class ActiveRecordInterface */ - /* @var $query ActiveQueryInterface */ + /* @var $query ActiveQuery */ $query = $class::find(); $query->primaryModel = $this; $query->link = $link; @@ -412,7 +412,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function hasMany($class, $link) { /* @var $class ActiveRecordInterface */ - /* @var $query ActiveQueryInterface */ + /* @var $query ActiveQuery */ $query = $class::find(); $query->primaryModel = $this; $query->link = $link; From e81ebc0fba467577a42dd0dd597376f8fb638a3f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Apr 2017 13:41:07 -0600 Subject: [PATCH 127/184] Tweaked verbiage slightly [ci skip] --- docs/guide/helper-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/helper-overview.md b/docs/guide/helper-overview.md index ed6563c..f6f85c3 100644 --- a/docs/guide/helper-overview.md +++ b/docs/guide/helper-overview.md @@ -76,5 +76,5 @@ Yii::$classMap['yii\helpers\ArrayHelper'] = '@app/components/ArrayHelper.php'; ``` Note that customizing of helper classes is only useful if you want to change the behavior of an existing function -of the helpers. If you want to add additional functions to use in your application you may better create a separate +of the helpers. If you want to add additional functions to use in your application, you may be better off creating a separate helper for that. From 8eabff0698f5530723e7f570324b7f6d56b040d7 Mon Sep 17 00:00:00 2001 From: micro-maureen Date: Sat, 15 Apr 2017 22:55:35 +0100 Subject: [PATCH 128/184] #13975: make documentation of current behaviour more clear [skip ci] --- framework/web/Controller.php | 4 ++-- framework/web/Response.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/web/Controller.php b/framework/web/Controller.php index 5b91329..751e9c4 100644 --- a/framework/web/Controller.php +++ b/framework/web/Controller.php @@ -189,8 +189,8 @@ class Controller extends \yii\base\Controller * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`) * [[Url::to()]] will be used to convert the array into a URL. * - * Any relative URL will be converted into an absolute one by prepending it with the host info - * of the current request. + * Any relative URL that starts with a single forward slash "/" will be converted + * into an absolute one by prepending it with the host info of the current request. * * @param int $statusCode the HTTP status code. Defaults to 302. * See diff --git a/framework/web/Response.php b/framework/web/Response.php index 40a6d24..8348e4e 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -815,8 +815,8 @@ class Response extends \yii\base\Response * Note that the route is with respect to the whole application, instead of relative to a controller or module. * [[Url::to()]] will be used to convert the array into a URL. * - * Any relative URL will be converted into an absolute one by prepending it with the host info - * of the current request. + * Any relative URL that starts with a single forward slash "/" will be converted + * into an absolute one by prepending it with the host info of the current request. * * @param int $statusCode the HTTP status code. Defaults to 302. * See From 85cc75aaa2873601e769d3e06d3b881882fbd627 Mon Sep 17 00:00:00 2001 From: "Charles Q. Wu" Date: Sun, 16 Apr 2017 16:07:59 -0700 Subject: [PATCH 129/184] docs/guide/input-multiple-models: added a note on avoiding validating the same input data with the same rules more than once (#13995) [skip ci] --- docs/guide/input-multiple-models.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guide/input-multiple-models.md b/docs/guide/input-multiple-models.md index 509abac..f6b5f1e 100644 --- a/docs/guide/input-multiple-models.md +++ b/docs/guide/input-multiple-models.md @@ -59,8 +59,10 @@ class UserController extends Controller ``` In the `update` action, we first load the `$user` and `$profile` models to be updated from the database. We then call -[[yii\base\Model::load()]] to populate these two models with the user input. If successful we will validate -the two models and save them. Otherwise we will render the `update` view which has the following content: +[[yii\base\Model::load()]] to populate these two models with the user input. If loading is successful, we will validate +the two models and then save them — please note that we use `save(false)` to skip over validations inside the models +as the user input data have already been validated. If loading is not successful, we will render the `update` view which +has the following content: ```php Date: Mon, 17 Apr 2017 03:31:43 +0300 Subject: [PATCH 130/184] Fixes #13807: Fixed `yii\db\QueryBuilder` to inherit subquery params when building a `INSERT INTO ... SELECT` query --- framework/CHANGELOG.md | 1 + framework/UPGRADE.md | 3 +++ framework/db/QueryBuilder.php | 15 +++++++++------ framework/db/mysql/QueryBuilder.php | 2 +- framework/db/oci/QueryBuilder.php | 2 +- tests/framework/db/CommandTest.php | 36 ++++++++++++++++++++++++------------ 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3e647f7..ba6ff24 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -55,6 +55,7 @@ Yii Framework 2 Change Log - Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13770: Added support for `yii\widgets\Menu` item classes definition in the form of an array (Kolyunya) - Enh #13823: Refactored migrations template (Kolyunya) +- Bug #13807: Fixed `yii\db\QueryBuilder` to inherit subquery params when building a `INSERT INTO ... SELECT` query (sergeymakinen) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) - Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) - Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 08a976c..fb46bc5 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -58,6 +58,9 @@ Upgrade from Yii 2.0.11 * `yii\grid\DataColumn` filter is now automatically generated as dropdown list with localized `Yes` and `No` strings in case of `format` being set to `boolean`. + +* The signature of `yii\db\QueryBuilder::prepareInsertSelectSubQuery()` was changed. The method has got an extra optional parameter + `$params`. * The signature of `yii\cache\Cache::getOrSet()` has been adjusted to also accept a callable and not only `Closure`. If you extend this method, make sure to adjust your code. diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 579e568..ae4ec26 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -176,7 +176,7 @@ class QueryBuilder extends \yii\base\Object $placeholders = []; $values = ' DEFAULT VALUES'; if ($columns instanceof \yii\db\Query) { - list($names, $values) = $this->prepareInsertSelectSubQuery($columns, $schema); + list($names, $values, $params) = $this->prepareInsertSelectSubQuery($columns, $schema); } else { foreach ($columns as $name => $value) { $names[] = $schema->quoteColumnName($name); @@ -204,18 +204,21 @@ class QueryBuilder extends \yii\base\Object /** * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement. * - * @param \yii\db\Query $columns Object, which represents select query - * @param \yii\db\Schema $schema Schema object to qoute column name + * @param \yii\db\Query $columns Object, which represents select query. + * @param \yii\db\Schema $schema Schema object to quote column name. + * @param array $params the parameters to be bound to the generated SQL statement. These parameters will + * be included in the result with the additional parameters generated during the query building process. * @return array + * @throws InvalidParamException if query's select does not contain named parameters only. * @since 2.0.11 */ - protected function prepareInsertSelectSubQuery($columns, $schema) + protected function prepareInsertSelectSubQuery($columns, $schema, $params = []) { if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) { throw new InvalidParamException('Expected select query object with enumerated (named) parameters'); } - list ($values, ) = $this->build($columns); + list($values, $params) = $this->build($columns, $params); $names = []; $values = ' ' . $values; foreach ($columns->select as $title => $field) { @@ -228,7 +231,7 @@ class QueryBuilder extends \yii\base\Object } } - return [$names, $values]; + return [$names, $values, $params]; } /** diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index 18f4882..2f2bb04 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -216,7 +216,7 @@ class QueryBuilder extends \yii\db\QueryBuilder $placeholders = []; $values = ' DEFAULT VALUES'; if ($columns instanceof \yii\db\Query) { - list($names, $values) = $this->prepareInsertSelectSubQuery($columns, $schema); + list($names, $values, $params) = $this->prepareInsertSelectSubQuery($columns, $schema, $params); } else { foreach ($columns as $name => $value) { $names[] = $schema->quoteColumnName($name); diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index ca9e45f..de4480e 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -194,7 +194,7 @@ EOD; $placeholders = []; $values = ' DEFAULT VALUES'; if ($columns instanceof \yii\db\Query) { - list($names, $values) = $this->prepareInsertSelectSubQuery($columns, $schema); + list($names, $values, $params) = $this->prepareInsertSelectSubQuery($columns, $schema, $params); } else { foreach ($columns as $name => $value) { $names[] = $schema->quoteColumnName($name); diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index 1f29c39..5e7b3f3 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -336,7 +336,7 @@ SQL; public function testInsertSelect() { $db = $this->getConnection(); - $db->createCommand('DELETE FROM {{customer}};')->execute(); + $db->createCommand('DELETE FROM {{customer}}')->execute(); $command = $db->createCommand(); $command->insert( @@ -350,11 +350,17 @@ SQL; $query = new \yii\db\Query(); $query->select([ - '{{customer}}.email as name', - 'name as email', - 'address', + '{{customer}}.[[email]] as name', + '[[name]] as email', + '[[address]]', ] - )->from('{{customer}}'); + ) + ->from('{{customer}}') + ->where([ + 'and', + ['<>', 'name', 'foo'], + ['status' => [0, 1, 2, 3]], + ]); $command = $db->createCommand(); $command->insert( @@ -362,8 +368,8 @@ SQL; $query )->execute(); - $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}};')->queryScalar()); - $record = $db->createCommand('SELECT email, name, address FROM {{customer}};')->queryAll(); + $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar()); + $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll(); $this->assertEquals([ [ 'email' => 't1@example.com', @@ -384,7 +390,7 @@ SQL; public function testInsertSelectAlias() { $db = $this->getConnection(); - $db->createCommand('DELETE FROM {{customer}};')->execute(); + $db->createCommand('DELETE FROM {{customer}}')->execute(); $command = $db->createCommand(); $command->insert( @@ -398,11 +404,17 @@ SQL; $query = new \yii\db\Query(); $query->select([ - 'email' => '{{customer}}.email', + 'email' => '{{customer}}.[[email]]', 'address' => 'name', 'name' => 'address', ] - )->from('{{customer}}'); + ) + ->from('{{customer}}') + ->where([ + 'and', + ['<>', 'name', 'foo'], + ['status' => [0, 1, 2, 3]], + ]); $command = $db->createCommand(); $command->insert( @@ -410,8 +422,8 @@ SQL; $query )->execute(); - $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}};')->queryScalar()); - $record = $db->createCommand('SELECT email, name, address FROM {{customer}};')->queryAll(); + $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar()); + $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll(); $this->assertEquals([ [ 'email' => 't1@example.com', From 400c531d9539cecf364af600bf7f9c568eea16f0 Mon Sep 17 00:00:00 2001 From: Pavel Dovlatov Date: Mon, 17 Apr 2017 15:51:09 +0300 Subject: [PATCH 131/184] Tiny documentation fix [skip ci] (#13998) --- docs/guide-es/input-file-upload.md | 2 +- docs/guide-fr/input-file-upload.md | 2 +- docs/guide-ja/input-file-upload.md | 2 +- docs/guide-pl/input-file-upload.md | 2 +- docs/guide-ru/input-file-upload.md | 2 +- docs/guide/input-file-upload.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guide-es/input-file-upload.md b/docs/guide-es/input-file-upload.md index 54728b6..595cc6e 100644 --- a/docs/guide-es/input-file-upload.md +++ b/docs/guide-es/input-file-upload.md @@ -77,7 +77,7 @@ use yii\widgets\ActiveForm; Es importante recordad que agregues la opción `enctype` al formulario para que el archivo pueda ser subido apropiadamente. La llamada a `fileInput()` renderizará un tag `` que le permitirá al usuario seleccionar el archivo a subir. -> Tip: desde la versión 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] agrega la opción `enctype` al formulario +> Tip: desde la versión 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] agrega la opción `enctype` al formulario automáticamente cuando se utiliza una campo de subida de archivo. ## Uniendo Todo diff --git a/docs/guide-fr/input-file-upload.md b/docs/guide-fr/input-file-upload.md index f21a947..0c0213a 100644 --- a/docs/guide-fr/input-file-upload.md +++ b/docs/guide-fr/input-file-upload.md @@ -67,7 +67,7 @@ use yii\widgets\ActiveForm; 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é. +> Tip: depuis la version 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] ajoute l'option `enctype` au formulaire automatiquement lorsqu'un champ d'entrée de fichier est utilisé. ## Câblage diff --git a/docs/guide-ja/input-file-upload.md b/docs/guide-ja/input-file-upload.md index 63ede88..b433e76 100644 --- a/docs/guide-ja/input-file-upload.md +++ b/docs/guide-ja/input-file-upload.md @@ -79,7 +79,7 @@ use yii\widgets\ActiveForm; ファイルが正しくアップロードされるように、フォームに `enctype` オプションを追加することを憶えておくのは重要なことです。 `fileInput()` を呼ぶと `` のタグがレンダリングされて、ユーザがアップロードするファイルを選ぶことが出来るようになります。 -> Tip: バージョン 2.0.8 以降では、ファイルインプットのフィールドが使われているときは、[[yii\web\widgets\ActiveField::fileInput|fileInput]] がフォームに `enctype` オプションを自動的に追加します。 +> Tip: バージョン 2.0.8 以降では、ファイルインプットのフィールドが使われているときは、[[yii\widgets\ActiveField::fileInput|fileInput]] がフォームに `enctype` オプションを自動的に追加します。 ## 繋ぎ合せる diff --git a/docs/guide-pl/input-file-upload.md b/docs/guide-pl/input-file-upload.md index b50046f..00f7044 100644 --- a/docs/guide-pl/input-file-upload.md +++ b/docs/guide-pl/input-file-upload.md @@ -74,7 +74,7 @@ use yii\widgets\ActiveForm; Należy pamiętać, aby dodać opcję `enctype` do formularza, przez co plik będzie mógł być prawidłowo przesłany. Wywołanie `fileInput()` spowoduje wyrenderowanie tagu ``, który pozwala użytkownikowi na wybranie oraz przesłanie pliku. -> Tip: od wersji 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] dodaje automatycznie opcję `enctype` do formularza, kiedy pole typu 'file input' jest używane. +> Tip: od wersji 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] dodaje automatycznie opcję `enctype` do formularza, kiedy pole typu 'file input' jest używane. ## Implementacja kontrolera diff --git a/docs/guide-ru/input-file-upload.md b/docs/guide-ru/input-file-upload.md index 19c71a3..9b9228d 100644 --- a/docs/guide-ru/input-file-upload.md +++ b/docs/guide-ru/input-file-upload.md @@ -77,7 +77,7 @@ use yii\widgets\ActiveForm; Важно помнить, что для корректной загрузки файла, необходим параметр формы `enctype`. Метод `fileInput()` выведет тег ``, позволяющий пользователю выбрать файл для загрузки. -> Tip: начиная с версии 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] автоматически добавляет +> Tip: начиная с версии 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] автоматически добавляет к форме свойство `enctype`, если в ней есть поле для загрузки файла. ## Загрузка diff --git a/docs/guide/input-file-upload.md b/docs/guide/input-file-upload.md index 385ac41..e25f4ce 100644 --- a/docs/guide/input-file-upload.md +++ b/docs/guide/input-file-upload.md @@ -77,7 +77,7 @@ use yii\widgets\ActiveForm; It is important to remember that you add the `enctype` option to the form so that the file can be properly uploaded. The `fileInput()` call will render a `` tag which will allow users to select a file to upload. -> Tip: since version 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] adds `enctype` option to the form +> Tip: since version 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] adds `enctype` option to the form automatically when file input field is used. ## Wiring Up From cd8b98bb92c72eb9662c024a892b4075b5ddf4f2 Mon Sep 17 00:00:00 2001 From: Joel Small Date: Tue, 18 Apr 2017 21:09:55 +0930 Subject: [PATCH 132/184] Added note to findOne and findAll docs (#14002) [skip ci] Added note to findOne and findAll docs to clearly state that complex conditions are not supported. --- framework/db/ActiveRecordInterface.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/framework/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php index ae886bf..d539a44 100644 --- a/framework/db/ActiveRecordInterface.php +++ b/framework/db/ActiveRecordInterface.php @@ -162,6 +162,8 @@ interface ActiveRecordInterface * - an associative array of name-value pairs: query by a set of attribute values and return a single record * matching all of them (or `null` if not found). Note that `['id' => 1, 2]` is treated as a non-associative array. * + * NOTE: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. + * * That this method will automatically call the `one()` method and return an [[ActiveRecordInterface|ActiveRecord]] * instance. For example, * @@ -172,6 +174,12 @@ interface ActiveRecordInterface * // the above code is equivalent to: * $customer = Customer::find()->where(['id' => 10])->one(); * + * // find the customers whose primary key value is 10, 11 or 12. + * $customers = Customer::findOne([10, 11, 12]); + * + * // the above code is equivalent to: + * $customers = Customer::find()->where(['id' => [10, 11, 12]])->one(); + * * // find the first customer whose age is 30 and whose status is 1 * $customer = Customer::findOne(['age' => 30, 'status' => 1]); * @@ -199,6 +207,8 @@ interface ActiveRecordInterface * matching all of them (or an empty array if none was found). Note that `['id' => 1, 2]` is treated as * a non-associative array. * + * NOTE: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. + * * This method will automatically call the `all()` method and return an array of [[ActiveRecordInterface|ActiveRecord]] * instances. For example, * From 59d329239938c8b37ebdcc453cc74d5b8306f25c Mon Sep 17 00:00:00 2001 From: Mikk Tendermann Date: Tue, 18 Apr 2017 19:06:31 +0300 Subject: [PATCH 133/184] [IpValidator] disable ipv6 checks (#13984) * disable ipv6 checks * remove empty line * updated CHANGELOG.md --- framework/CHANGELOG.md | 3 ++- framework/validators/IpValidator.php | 5 ----- tests/framework/validators/IpValidatorTest.php | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ba6ff24..12af5cb 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -64,7 +64,8 @@ Yii Framework 2 Change Log - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) -- Bug #13961: RBAC Rules: `PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given` was fixed (vsguts) +- Bug #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed (vsguts) +- Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for inet_* methods to work (mikk150) - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) 2.0.11.2 February 08, 2017 diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php index 9b09cce..bfe84f7 100644 --- a/framework/validators/IpValidator.php +++ b/framework/validators/IpValidator.php @@ -221,11 +221,6 @@ class IpValidator extends Validator if (!$this->ipv4 && !$this->ipv6) { throw new InvalidConfigException('Both IPv4 and IPv6 checks can not be disabled at the same time'); } - - if (!defined('AF_INET6') && $this->ipv6) { - throw new InvalidConfigException('IPv6 validation can not be used. PHP is compiled without IPv6'); - } - if ($this->message === null) { $this->message = Yii::t('yii', '{attribute} must be a valid IP address.'); } diff --git a/tests/framework/validators/IpValidatorTest.php b/tests/framework/validators/IpValidatorTest.php index 99ebb81..1fc2a7e 100644 --- a/tests/framework/validators/IpValidatorTest.php +++ b/tests/framework/validators/IpValidatorTest.php @@ -13,10 +13,6 @@ class IpValidatorTest extends TestCase { protected function setUp() { - if (!defined('AF_INET6')) { - $this->markTestSkipped('The environment does not support IPv6.'); - } - parent::setUp(); $this->mockApplication(); } From 14a245a4135b90b12dd51f4c7f4ce25b1f0b413a Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Tue, 18 Apr 2017 19:12:28 +0300 Subject: [PATCH 134/184] Added AF_INET6 to requirements --- framework/requirements/requirements.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/requirements/requirements.php b/framework/requirements/requirements.php index 2e72825..dac5958 100644 --- a/framework/requirements/requirements.php +++ b/framework/requirements/requirements.php @@ -97,4 +97,12 @@ return array( 'by' => 'Document Object Model', 'memo' => 'Required for REST API to send XML responses via yii\web\XmlResponseFormatter.' ), + array( + 'name' => 'IPv6 support', + 'mandatory' => false, + 'condition' => defined('AF_INET6'), + 'by' => 'IPv6 expansion in IpValidator', + 'memo' => 'When IpValidator::expandIPv6 + property is set to true, PHP must support IPv6 protocol stack.' + ) ); From 620ce9af191d036efe13018f5626487c9875c1c1 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Tue, 18 Apr 2017 19:15:16 +0300 Subject: [PATCH 135/184] Enhanced requirements memo for IPv6 --- framework/requirements/requirements.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/requirements/requirements.php b/framework/requirements/requirements.php index dac5958..cb14ec3 100644 --- a/framework/requirements/requirements.php +++ b/framework/requirements/requirements.php @@ -103,6 +103,7 @@ return array( 'condition' => defined('AF_INET6'), 'by' => 'IPv6 expansion in IpValidator', 'memo' => 'When IpValidator::expandIPv6 - property is set to true, PHP must support IPv6 protocol stack.' + property is set to true, PHP must support IPv6 protocol stack. Currently PHP constant AF_INET6 is not defined + and IPv6 is probably unsupported.' ) ); From 02ffbb28ec6a641dc57e9d8eab9043cff80c6f31 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 18 Apr 2017 23:35:16 +0200 Subject: [PATCH 136/184] update AR docs --- framework/db/ActiveRecordInterface.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/framework/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php index d539a44..0400f6e 100644 --- a/framework/db/ActiveRecordInterface.php +++ b/framework/db/ActiveRecordInterface.php @@ -162,10 +162,13 @@ interface ActiveRecordInterface * - an associative array of name-value pairs: query by a set of attribute values and return a single record * matching all of them (or `null` if not found). Note that `['id' => 1, 2]` is treated as a non-associative array. * - * NOTE: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. - * * That this method will automatically call the `one()` method and return an [[ActiveRecordInterface|ActiveRecord]] - * instance. For example, + * instance. + * + * > Note: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. + * > If you need to specify more complex conditions, use [[find()]] in combination with [[ActiveQuery::where()|where()]] instead. + * + * See the following code for usage examples: * * ```php * // find a single customer whose primary key value is 10 @@ -207,10 +210,13 @@ interface ActiveRecordInterface * matching all of them (or an empty array if none was found). Note that `['id' => 1, 2]` is treated as * a non-associative array. * - * NOTE: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. - * * This method will automatically call the `all()` method and return an array of [[ActiveRecordInterface|ActiveRecord]] - * instances. For example, + * instances. + * + * > Note: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. + * > If you need to specify more complex conditions, use [[find()]] in combination with [[ActiveQuery::where()|where()]] instead. + * + * See the following code for usage examples: * * ```php * // find the customers whose primary key value is 10 From c35ddec09a384b6ef2e34801b45947a793073885 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 19 Apr 2017 02:55:16 +0300 Subject: [PATCH 137/184] Update composer fxp plugin version in docs and travis scripts --- .travis.yml | 4 ++-- docs/guide-es/start-installation.md | 2 +- docs/guide-es/tutorial-start-from-scratch.md | 2 +- docs/guide-es/tutorial-yii-integration.md | 2 +- docs/guide-id/start-installation.md | 2 +- docs/guide-it/start-installation.md | 2 +- docs/guide-ja/start-installation.md | 2 +- docs/guide-ja/tutorial-start-from-scratch.md | 2 +- docs/guide-ja/tutorial-yii-integration.md | 2 +- docs/guide-pl/start-installation.md | 2 +- docs/guide-pl/tutorial-start-from-scratch.md | 2 +- docs/guide-pt-BR/start-installation.md | 2 +- docs/guide-pt-BR/tutorial-yii-integration.md | 2 +- docs/guide-ru/start-installation.md | 2 +- docs/guide-ru/tutorial-start-from-scratch.md | 2 +- docs/guide-ru/tutorial-yii-integration.md | 2 +- docs/guide-uk/start-installation.md | 2 +- docs/guide-uk/tutorial-start-from-scratch.md | 2 +- docs/guide-uk/tutorial-yii-integration.md | 2 +- docs/guide-vi/start-installation.md | 2 +- docs/guide-zh-CN/start-installation.md | 2 +- docs/guide-zh-CN/tutorial-yii-integration.md | 2 +- docs/guide/start-installation.md | 2 +- docs/guide/tutorial-start-from-scratch.md | 2 +- docs/guide/tutorial-yii-integration.md | 2 +- docs/internals-ja/git-workflow.md | 2 +- docs/internals-pl/git-workflow.md | 2 +- docs/internals-ru/git-workflow.md | 2 +- docs/internals/git-workflow.md | 2 +- framework/README.md | 2 +- framework/UPGRADE.md | 4 ++-- 31 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index f458b99..acf5ddc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,7 @@ matrix: # 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 global require "fxp/composer-asset-plugin:^1.3.1" --no-plugins - travis_retry composer install --prefer-dist --no-interaction before_script: - node --version @@ -118,7 +118,7 @@ install: 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.2.0" --no-plugins + - travis_retry composer global require "fxp/composer-asset-plugin:^1.3.1" --no-plugins - export PATH="$HOME/.composer/vendor/bin:$PATH" # core framework: - travis_retry composer install --prefer-dist --no-interaction diff --git a/docs/guide-es/start-installation.md b/docs/guide-es/start-installation.md index 48b4741..3896c35 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.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-es/tutorial-start-from-scratch.md b/docs/guide-es/tutorial-start-from-scratch.md index 616d755..3a3619e 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.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide-es/tutorial-yii-integration.md b/docs/guide-es/tutorial-yii-integration.md index f7787ba..5eab4d7 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.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/guide-id/start-installation.md b/docs/guide-id/start-installation.md index dc3d770..08ebb61 100644 --- a/docs/guide-id/start-installation.md +++ b/docs/guide-id/start-installation.md @@ -40,7 +40,7 @@ dengan menjalankan `composer self-update`. Dengan Komposer diinstal, Anda dapat menginstal Yii dengan menjalankan perintah berikut di bawah folder yang terakses web: ```bash -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-it/start-installation.md b/docs/guide-it/start-installation.md index 755ac34..526f041 100644 --- a/docs/guide-it/start-installation.md +++ b/docs/guide-it/start-installation.md @@ -27,7 +27,7 @@ Se hai già Composer installato assicurati di avere una versione aggiornata. Puo Una volta installato Composer, puoi installare Yii eseguendo questo comando in una directory accessbile via web: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic Il primo comando installa il [plugin composer asset](https://github.com/francoispluchino/composer-asset-plugin/) diff --git a/docs/guide-ja/start-installation.md b/docs/guide-ja/start-installation.md index 0b62fc2..917ad5f 100644 --- a/docs/guide-ja/start-installation.md +++ b/docs/guide-ja/start-installation.md @@ -55,7 +55,7 @@ Composer は `composer self-update` コマンドを実行してアップデー Composer がインストールされたら、ウェブからアクセスできるフォルダで下記のコマンドを実行することによって Yii をインストールすることが出来ます。 ```bash -composer global require "fxp/composer-asset-plugin:~1.2.0" +composer global require "fxp/composer-asset-plugin:~1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-ja/tutorial-start-from-scratch.md b/docs/guide-ja/tutorial-start-from-scratch.md index 2dd342c..b2fc842 100644 --- a/docs/guide-ja/tutorial-start-from-scratch.md +++ b/docs/guide-ja/tutorial-start-from-scratch.md @@ -54,6 +54,6 @@ Yii の新しいプロジェクトテンプレートを作成するのに必要 これで、あなたのテンプレートを使ってプロジェクトを作成することが出来ます。 ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide-ja/tutorial-yii-integration.md b/docs/guide-ja/tutorial-yii-integration.md index cea1c1a..b4f9f43 100644 --- a/docs/guide-ja/tutorial-yii-integration.md +++ b/docs/guide-ja/tutorial-yii-integration.md @@ -77,7 +77,7 @@ Yii は数多くの優れた機能を提供していますので、サードパ サードパーティのシステムが Composer を使って依存を管理している場合は、単に下記のコマンドを実行すれば Yii をインストールすることが出来ます。 - composer global require "fxp/composer-asset-plugin:~1.2.0" + composer global require "fxp/composer-asset-plugin:~1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/guide-pl/start-installation.md b/docs/guide-pl/start-installation.md index 8db219e..b0c6add 100644 --- a/docs/guide-pl/start-installation.md +++ b/docs/guide-pl/start-installation.md @@ -56,7 +56,7 @@ Jeśli jesteś już posiadaczem Composera, upewnij się, że jest on zaktualizow Teraz możesz przejść już do instalacji samego Yii, wywołując poniższe komendy w katalogu dostępnym z poziomu sieci web: ```bash -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-pl/tutorial-start-from-scratch.md b/docs/guide-pl/tutorial-start-from-scratch.md index baab856..99a52db 100644 --- a/docs/guide-pl/tutorial-start-from-scratch.md +++ b/docs/guide-pl/tutorial-start-from-scratch.md @@ -52,6 +52,6 @@ Użycie szablonu Tylko tyle jest wymagane, aby stworzyć nowy szablon projektu Yii. Teraz już możesz rozpocząć pracę nad świeżym projektem, używając swojego szablonu, za pomocą komend: ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mojafirma/yii2-app-fajna nowy-projekt ``` diff --git a/docs/guide-pt-BR/start-installation.md b/docs/guide-pt-BR/start-installation.md index 8bd2307..3975b7b 100644 --- a/docs/guide-pt-BR/start-installation.md +++ b/docs/guide-pt-BR/start-installation.md @@ -46,7 +46,7 @@ Você pode atualizar o Composer executando o comando `composer self-update`. Com o Composer instalado, você pode instalar o Yii executando o seguinte comando em um diretório acessível pela Web: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic O primeiro comando instala o [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) diff --git a/docs/guide-pt-BR/tutorial-yii-integration.md b/docs/guide-pt-BR/tutorial-yii-integration.md index e9783ee..b6c88c8 100644 --- a/docs/guide-pt-BR/tutorial-yii-integration.md +++ b/docs/guide-pt-BR/tutorial-yii-integration.md @@ -66,7 +66,7 @@ Como o Yii fornece muitas características excelentes, algumas vezes você pode Se o sistema em questão utilizar o Composer para gerenciar suas dependências, você pode simplesmente executar o seguinte comando para instalar o Yii: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/guide-ru/start-installation.md b/docs/guide-ru/start-installation.md index 2361490..5d818ec 100644 --- a/docs/guide-ru/start-installation.md +++ b/docs/guide-ru/start-installation.md @@ -47,7 +47,7 @@ mv composer.phar /usr/local/bin/composer ### Установка Yii ```bash -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-ru/tutorial-start-from-scratch.md b/docs/guide-ru/tutorial-start-from-scratch.md index a12fd7c..0504f0c 100644 --- a/docs/guide-ru/tutorial-start-from-scratch.md +++ b/docs/guide-ru/tutorial-start-from-scratch.md @@ -46,6 +46,6 @@ git clone git@github.com:yiisoft/yii2-app-basic.git Это все, что требуется для создания нового шаблона проекта в Yii. Сейчас вы можете создавать проекты, использующие ваш шаблон: ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide-ru/tutorial-yii-integration.md b/docs/guide-ru/tutorial-yii-integration.md index 8cbdcca..3d1a1c7 100644 --- a/docs/guide-ru/tutorial-yii-integration.md +++ b/docs/guide-ru/tutorial-yii-integration.md @@ -63,7 +63,7 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; Если сторонняя система использует для управления зависимостями Composer, Yii можно просто установить с помощью следующих команд: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/guide-uk/start-installation.md b/docs/guide-uk/start-installation.md index 9e5c3fb..2e6c516 100644 --- a/docs/guide-uk/start-installation.md +++ b/docs/guide-uk/start-installation.md @@ -40,7 +40,7 @@ Після встановлення Composer, встановити Yii можна виконавши наступну команду з директорії, яка доступна через Web: ```bash - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide-uk/tutorial-start-from-scratch.md b/docs/guide-uk/tutorial-start-from-scratch.md index d63c2ee..4eda6d0 100644 --- a/docs/guide-uk/tutorial-start-from-scratch.md +++ b/docs/guide-uk/tutorial-start-from-scratch.md @@ -57,6 +57,6 @@ git clone git@github.com:yiisoft/yii2-app-basic.git Тепер ви можете створювати проекти, використовуючи свій шаблон: ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide-uk/tutorial-yii-integration.md b/docs/guide-uk/tutorial-yii-integration.md index 068cbf1..e61d3fb 100644 --- a/docs/guide-uk/tutorial-yii-integration.md +++ b/docs/guide-uk/tutorial-yii-integration.md @@ -84,7 +84,7 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; Якщо стороння система використовує управління залежностями Composer, ви можете встановити Yii за допомогою наступних команд: ```bash - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install ``` diff --git a/docs/guide-vi/start-installation.md b/docs/guide-vi/start-installation.md index 923080b..a241c4b 100644 --- a/docs/guide-vi/start-installation.md +++ b/docs/guide-vi/start-installation.md @@ -35,7 +35,7 @@ Nếu bạn đã cài Composer rồi, hãy chắc chắn rằng bạn đang sử Sau khi cài đặt Composer, bạn có thể cài đặt Yii bằng cách chạy lệnh sau ở thư mục Web mà ứng dụng cần chạy: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic Câu lệnh đầu tiên sẽ cài đặt [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) diff --git a/docs/guide-zh-CN/start-installation.md b/docs/guide-zh-CN/start-installation.md index 14b6eb8..ae88f65 100644 --- a/docs/guide-zh-CN/start-installation.md +++ b/docs/guide-zh-CN/start-installation.md @@ -22,7 +22,7 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下命令即可安装 Yii : - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic 第一条命令安装 [Composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/),它是通过 Composer 管理 bower 和 npm 包所必须的,此命令全局生效,一劳永逸。 diff --git a/docs/guide-zh-CN/tutorial-yii-integration.md b/docs/guide-zh-CN/tutorial-yii-integration.md index 44581c2..3d069ae 100644 --- a/docs/guide-zh-CN/tutorial-yii-integration.md +++ b/docs/guide-zh-CN/tutorial-yii-integration.md @@ -68,7 +68,7 @@ Yii::$classMap['Class2'] = 'path/to/Class2.php'; 若这个第三方系统支持 Composer 管理他的依赖文件,你可以直接运行一下命令来安装 Yii: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md index d4a4880..c36df4d 100644 --- a/docs/guide/start-installation.md +++ b/docs/guide/start-installation.md @@ -58,7 +58,7 @@ by running `composer self-update`. With Composer installed, you can install Yii by running the following commands under a Web-accessible folder: ```bash -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist yiisoft/yii2-app-basic basic ``` diff --git a/docs/guide/tutorial-start-from-scratch.md b/docs/guide/tutorial-start-from-scratch.md index 910eaa5..a563834 100644 --- a/docs/guide/tutorial-start-from-scratch.md +++ b/docs/guide/tutorial-start-from-scratch.md @@ -50,6 +50,6 @@ Use the Template That's all that's required to create a new Yii project template. Now you can create projects using your template: ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project ``` diff --git a/docs/guide/tutorial-yii-integration.md b/docs/guide/tutorial-yii-integration.md index ee25d83..4c418fd 100644 --- a/docs/guide/tutorial-yii-integration.md +++ b/docs/guide/tutorial-yii-integration.md @@ -82,7 +82,7 @@ take two steps: install Yii, and bootstrap Yii. If the third-party system uses Composer to manage its dependencies, you can simply run the following commands to install Yii: - composer global require "fxp/composer-asset-plugin:^1.2.0" + composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 composer install diff --git a/docs/internals-ja/git-workflow.md b/docs/internals-ja/git-workflow.md index fa4e20c..dfc1aad 100644 --- a/docs/internals-ja/git-workflow.md +++ b/docs/internals-ja/git-workflow.md @@ -35,7 +35,7 @@ git remote add upstream git://github.com/yiisoft/yii2.git - `composer install` を実行して、依存パッケージをインストールします ([composer をグローバルにインストール](https://getcomposer.org/doc/00-intro.md#globally) したものと仮定しています)。 -> Note: `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` というようなエラーが生ずる場合は、`composer global require "fxp/composer-asset-plugin:^1.2.0"` を実行する必要があります。 +> Note: `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` というようなエラーが生ずる場合は、`composer global require "fxp/composer-asset-plugin:^1.3.1"` を実行する必要があります。 JavaScript を扱おうとしている場合は、 - `npm install` を実行して JavaScript テストツール群とその依存ライブラリをインストールします diff --git a/docs/internals-pl/git-workflow.md b/docs/internals-pl/git-workflow.md index f13649a..e56d9db 100644 --- a/docs/internals-pl/git-workflow.md +++ b/docs/internals-pl/git-workflow.md @@ -38,7 +38,7 @@ Poniższe kroki nie są wymagane, jeśli chcesz pracować tylko nad tłumaczenia - uruchom `composer install`, aby zainstalować wymagane zależności (zakładając, że masz [composera zainstalowanego globalnie](https://getcomposer.org/doc/00-intro.md#globally)). -> Note: Jeśli otrzymujesz błędu typu `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` (`Problem 1 Wymagany pakiet bower-asset/jquery nie mógł być znaleziony w jakiejkolwiek wersji, być może w nazwie pakietu jest literówka.`), musisz uruchomić komendę `composer global require "fxp/composer-asset-plugin:^1.2.0"` +> Note: Jeśli otrzymujesz błędu typu `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.` (`Problem 1 Wymagany pakiet bower-asset/jquery nie mógł być znaleziony w jakiejkolwiek wersji, być może w nazwie pakietu jest literówka.`), musisz uruchomić komendę `composer global require "fxp/composer-asset-plugin:^1.3.1"` Jeśli zamierzasz pracować z JavaScript: diff --git a/docs/internals-ru/git-workflow.md b/docs/internals-ru/git-workflow.md index 518a686..05425f0 100644 --- a/docs/internals-ru/git-workflow.md +++ b/docs/internals-ru/git-workflow.md @@ -36,7 +36,7 @@ git remote add upstream git://github.com/yiisoft/yii2.git - выполните `composer update` для установки зависимостей (если [composer у вас установлен глобально](https://getcomposer.org/doc/00-intro.md#globally)). > Note: Если вы видите такие ошибки, как `Problem 1 The requested package bower-asset/jquery could not be found in -> any version, there may be a typo in the package name.`, необходимо запустить `composer global require "fxp/composer-asset-plugin:^1.2.0"` +> any version, there may be a typo in the package name.`, необходимо запустить `composer global require "fxp/composer-asset-plugin:^1.3.1"` - выполните `php build/build dev/app basic` для клонирования базового приложения и установки его зависимостей. Эта команда установит сторонние пакеты composer обычным образом, но создаст ссылку с репозитория yii2 на только diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index a1a7236..323655f 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -37,7 +37,7 @@ The following steps are not necessary if you want to work only on translations o - run `composer install` to install dependencies (assuming you have [composer installed globally](https://getcomposer.org/doc/00-intro.md#globally)). -> Note: If you see errors like `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.`, you will need to run `composer global require "fxp/composer-asset-plugin:^1.2.0"` +> Note: If you see errors like `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.`, you will need to run `composer global require "fxp/composer-asset-plugin:^1.3.1"` If you are going to work with JavaScript: diff --git a/framework/README.md b/framework/README.md index 9d5e849..522cdbe 100644 --- a/framework/README.md +++ b/framework/README.md @@ -15,7 +15,7 @@ The preferred way to install the Yii framework is through [composer](http://getc Either run ``` -composer global require "fxp/composer-asset-plugin:^1.2.0" +composer global require "fxp/composer-asset-plugin:^1.3.1" composer require yiisoft/yii2 ``` diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index fb46bc5..4a67062 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -22,7 +22,7 @@ Before upgrading, make sure you have a global installation of the latest version as well as a stable version of Composer: composer self-update - composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins + composer global require "fxp/composer-asset-plugin:^1.3.1" --no-plugins The simple way to upgrade Yii, for example to version 2.0.10 (replace this with the version you want) will be running `composer require`: @@ -275,7 +275,7 @@ Upgrade from Yii 2.0 Beta the composer-asset-plugin, *before* you update your project: ``` - php composer.phar global require "fxp/composer-asset-plugin:~1.0.0" + php composer.phar global require "fxp/composer-asset-plugin:~1.3.1" ``` You also need to add the following code to your project's `composer.json` file: From 0cf236ec66ed715fece7cb92f9e2dc7fa032094b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D0=B4=D0=B8=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20=D0=AE?= =?UTF-8?q?=D1=80=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 19 Apr 2017 14:38:47 +0500 Subject: [PATCH 138/184] Fixes #13963: Added tests for yii\behaviors\TimestampBehavior --- framework/CHANGELOG.md | 1 + .../framework/behaviors/TimestampBehaviorTest.php | 35 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 12af5cb..4c0ea3d 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) diff --git a/tests/framework/behaviors/TimestampBehaviorTest.php b/tests/framework/behaviors/TimestampBehaviorTest.php index 486e8e0..9927fbb 100644 --- a/tests/framework/behaviors/TimestampBehaviorTest.php +++ b/tests/framework/behaviors/TimestampBehaviorTest.php @@ -190,6 +190,41 @@ class TimestampBehaviorTest extends TestCase $this->assertEquals($enforcedTime, $model->created_at, 'Create time has been set on update!'); $this->assertEquals(date('Y'), $model->updated_at); } + + public function testTouchingNewRecordGeneratesException() + { + ActiveRecordTimestamp::$behaviors = [ + 'timestamp' => [ + 'class' => TimestampBehavior::className(), + 'value' => new Expression("strftime('%Y')"), + ], + ]; + $model = new ActiveRecordTimestamp(); + + $this->setExpectedException('yii\base\InvalidCallException'); + + $model->touch('created_at'); + } + + public function testTouchigNotNewRecord() + { + ActiveRecordTimestamp::$behaviors = [ + 'timestamp' => [ + 'class' => TimestampBehavior::className(), + 'value' => new Expression("strftime('%Y')"), + ], + ]; + $model = new ActiveRecordTimestamp(); + $enforcedTime = date('Y') - 1; + $model->created_at = $enforcedTime; + $model->updated_at = $enforcedTime; + $model->save(false); + $expectedCreatedAt = new Expression("strftime('%Y')"); + + $model->touch('created_at'); + + $this->assertEquals($expectedCreatedAt, $model->created_at); + } } /** From 224201950dd494c6de603c307aed9adf220633b0 Mon Sep 17 00:00:00 2001 From: PaulZi Date: Fri, 14 Apr 2017 15:52:53 +0300 Subject: [PATCH 139/184] Fixes #13911: Significantly enhanced MSSQL schema reading performance --- framework/CHANGELOG.md | 3 +- framework/db/mssql/Schema.php | 69 +++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4c0ea3d..2830882 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -67,7 +67,8 @@ Yii Framework 2 Change Log - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) - Bug #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed (vsguts) - Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for inet_* methods to work (mikk150) -- Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) +- Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) +- Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php index 045f746..82a8b53 100644 --- a/framework/db/mssql/Schema.php +++ b/framework/db/mssql/Schema.php @@ -253,16 +253,22 @@ class Schema extends \yii\db\Schema $sql = <<name; + if ($table->schemaName !== null) { + $object = $table->schemaName . '.' . $object; + } if ($table->catalogName !== null) { - $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName; - $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName; + $object = $table->catalogName . '.' . $object; } - $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName); - $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); // please refer to the following page for more details: // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx $sql = <<db->createCommand($sql, [ - ':tableName' => $table->name, - ':schemaName' => $table->schemaName, + ':object' => $object, ])->queryAll(); $table->foreignKeys = []; From 9bd7dfc89ed92ddbb156c7fa167c7f53e0188115 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 19 Apr 2017 18:03:39 +0300 Subject: [PATCH 140/184] Skipped segfaulting test when running on Travis and corresponding PHP versions --- tests/framework/db/mysql/connection/DeadLockTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/framework/db/mysql/connection/DeadLockTest.php b/tests/framework/db/mysql/connection/DeadLockTest.php index 9922b84..2caf747 100644 --- a/tests/framework/db/mysql/connection/DeadLockTest.php +++ b/tests/framework/db/mysql/connection/DeadLockTest.php @@ -26,6 +26,14 @@ class DeadLockTest extends \yiiunit\framework\db\mysql\ConnectionTest */ public function testDeadlockException() { + if ( + getenv('TRAVIS') + && version_compare(PHP_VERSION, '5.5.0', '>=') + && version_compare(PHP_VERSION, '7.0.0', '<') + ) { + $this->markTestSkipped('Skipping PHP 5.5 and 5.6 on Travis since it segfaults with pcntl'); + } + if (!function_exists('pcntl_fork')) { $this->markTestSkipped('pcntl_fork() is not available'); } From 5af3118120ed3399d2bb1b876d92c4cb5e7e54a1 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 20 Apr 2017 11:38:25 +0300 Subject: [PATCH 141/184] enforce `backupGlobals` enabled for PHPUnit --- phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index eb6f339..4dc2775 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,6 @@ Date: Thu, 20 Apr 2017 12:20:30 +0300 Subject: [PATCH 142/184] Fix `TimestampBehaviorTest` to be compatible with PHPUnit 6 --- tests/framework/behaviors/TimestampBehaviorTest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/framework/behaviors/TimestampBehaviorTest.php b/tests/framework/behaviors/TimestampBehaviorTest.php index 9927fbb..5732a1f 100644 --- a/tests/framework/behaviors/TimestampBehaviorTest.php +++ b/tests/framework/behaviors/TimestampBehaviorTest.php @@ -107,8 +107,6 @@ class TimestampBehaviorTest extends TestCase */ public function testUpdateCleanRecord() { - $currentTime = time(); - ActiveRecordTimestamp::$behaviors = [ TimestampBehavior::className(), ]; @@ -201,12 +199,12 @@ class TimestampBehaviorTest extends TestCase ]; $model = new ActiveRecordTimestamp(); - $this->setExpectedException('yii\base\InvalidCallException'); + $this->expectException('yii\base\InvalidCallException'); $model->touch('created_at'); } - public function testTouchigNotNewRecord() + public function testTouchingNotNewRecord() { ActiveRecordTimestamp::$behaviors = [ 'timestamp' => [ From d3f97f7e64f9ced72a418b70d4e2ad9f3f23d902 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 20 Apr 2017 14:46:30 +0300 Subject: [PATCH 143/184] Switched to asset-packagist --- .travis.yml | 2 -- composer.json | 6 ++++++ tests/TestCase.php | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index acf5ddc..dd21239 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,6 @@ matrix: # 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.3.1" --no-plugins - travis_retry composer install --prefer-dist --no-interaction before_script: - node --version @@ -118,7 +117,6 @@ install: 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.3.1" --no-plugins - export PATH="$HOME/.composer/vendor/bin:$PATH" # core framework: - travis_retry composer install --prefer-dist --no-interaction diff --git a/composer.json b/composer.json index f74a82e..3766521 100644 --- a/composer.json +++ b/composer.json @@ -84,6 +84,12 @@ "phpunit/phpunit": "~4.4", "cebe/indent": "~1.0.2" }, + "repositories": [ + { + "type": "composer", + "url": "https://asset-packagist.org" + } + ], "suggest": { "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii" }, diff --git a/tests/TestCase.php b/tests/TestCase.php index 829be9f..41cb70e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -68,6 +68,10 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase 'id' => 'testapp', 'basePath' => __DIR__, 'vendorPath' => $this->getVendorPath(), + 'aliases' => [ + '@bower' => '@vendor/bower-asset', + '@npm' => '@vendor/npm-asset', + ], 'components' => [ 'request' => [ 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq', From 9e1063e104e00c540e602348dab9cdd3a51d8d4a Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 20 Apr 2017 14:59:18 +0300 Subject: [PATCH 144/184] Enable xdebug for coverage on 7.1 insted of 5.6 as it should work faster --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dd21239..8cb0cd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -112,7 +112,7 @@ addons: install: - | - if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != hhv* ]]; then + if [[ $TRAVIS_PHP_VERSION != '7.1' && $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 From f62b53f475eae8241c33b4b259036322d74f513b Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 20 Apr 2017 15:14:23 +0300 Subject: [PATCH 145/184] Switched ocular to use PHP 7.1 execution results --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8cb0cd7..47df824 100644 --- a/.travis.yml +++ b/.travis.yml @@ -139,9 +139,9 @@ before_script: - 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 + # enable code coverage on PHP 7.1, only one PHP version needs to generate coverage data - | - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + if [ $TRAVIS_PHP_VERSION = '7.1' ]; then PHPUNIT_FLAGS="--coverage-clover=coverage.clover" fi @@ -157,7 +157,7 @@ script: after_script: - | - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then + if [ $TRAVIS_PHP_VERSION = '7.1' ]; then travis_retry wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover fi From 5e42b9e1426eb93686ed3e70c51f2a4be679285b Mon Sep 17 00:00:00 2001 From: Nurbek Nurjanov Date: Fri, 21 Apr 2017 00:19:19 +0600 Subject: [PATCH 146/184] Added note clarifying that load function clears fixture data [skip ci] --- docs/guide/test-fixtures.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/test-fixtures.md b/docs/guide/test-fixtures.md index 71de0d7..2987df9 100644 --- a/docs/guide/test-fixtures.md +++ b/docs/guide/test-fixtures.md @@ -295,6 +295,8 @@ change this behavior with config or command options. You can exclude some fixtur To load fixture, run the following command: +> Note: Prior to loading data unload sequence is executed. Usually that results in cleaning up all the existing data inserted by previous fixture executions. + ``` yii fixture/load ``` From d0ce165ba61720b297a3acc09386eeeece4f3fd8 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 20 Apr 2017 21:30:04 +0300 Subject: [PATCH 147/184] Fixes #13728: Fixed the bug when `yii\behaviors\SluggableBehavior` wasn't preserving immutable slug values --- framework/CHANGELOG.md | 1 + framework/behaviors/SluggableBehavior.php | 22 ++++++++----- .../framework/behaviors/SluggableBehaviorTest.php | 38 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2830882..feb0cce 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -46,6 +46,7 @@ Yii Framework 2 Change Log - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) +- Bug #13728: Fixed the bug when `yii\behaviors\SluggableBehavior` wasn't preserving immutable slug values (Kolyunya) - Bug #13707: Fixed `\yii\web\ErrorHandler` and `\yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) - Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 350be44..d1c2e04 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -138,17 +138,17 @@ class SluggableBehavior extends AttributeBehavior */ protected function getValue($event) { - if ($this->attribute !== null) { - if ($this->isNewSlugNeeded()) { - $slugParts = []; - foreach ((array) $this->attribute as $attribute) { - $slugParts[] = ArrayHelper::getValue($this->owner, $attribute); - } + if (!$this->isNewSlugNeeded()) { + return $this->owner->{$this->slugAttribute}; + } - $slug = $this->generateSlug($slugParts); - } else { - return $this->owner->{$this->slugAttribute}; + if ($this->attribute !== null) { + $slugParts = []; + foreach ((array) $this->attribute as $attribute) { + $slugParts[] = ArrayHelper::getValue($this->owner, $attribute); } + + $slug = $this->generateSlug($slugParts); } else { $slug = parent::getValue($event); } @@ -173,6 +173,10 @@ class SluggableBehavior extends AttributeBehavior return false; } + if ($this->attribute === null) { + return true; + } + foreach ((array)$this->attribute as $attribute) { if ($this->owner->isAttributeChanged($attribute)) { return true; diff --git a/tests/framework/behaviors/SluggableBehaviorTest.php b/tests/framework/behaviors/SluggableBehaviorTest.php index 571e67c..b119e37 100644 --- a/tests/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/framework/behaviors/SluggableBehaviorTest.php @@ -168,6 +168,44 @@ class SluggableBehaviorTest extends TestCase $model->save(); $this->assertEquals('test-name', $model->slug); } + + /** + * @depends testSlug + */ + public function testImmutableByAttribute() + { + $model = new ActiveRecordSluggable(); + $model->getSluggable()->immutable = true; + + $model->name = 'test name'; + $model->validate(); + $this->assertEquals('test-name', $model->slug); + + $model->name = 'another name'; + $model->validate(); + $this->assertEquals('test-name', $model->slug); + } + + /** + * @depends testSlug + */ + public function testImmutableByCallback() + { + $model = new ActiveRecordSluggable(); + $model->getSluggable()->immutable = true; + $model->getSluggable()->attribute = null; + $model->getSluggable()->value = function () use ($model) { + return $model->name; + }; + + $model->name = 'test name'; + $model->validate(); + $this->assertEquals('test name', $model->slug); + + $model->name = 'another name'; + $model->validate(); + $this->assertEquals('test name', $model->slug); + } } /** From d077839d135fa4e191f71ca79cc1814141e4b41a Mon Sep 17 00:00:00 2001 From: Masterklavi Date: Tue, 10 Jan 2017 23:41:04 +0500 Subject: [PATCH 148/184] Changed return value in MemCache::setValues() --- framework/caching/MemCache.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index a9cf8b5..1cb1bfe 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -304,7 +304,7 @@ class MemCache extends Cache * Stores multiple key-value pairs in cache. * @param array $data array where key corresponds to cache key while value is the value stored * @param int $duration the number of seconds in which the cached values will expire. 0 means never expire. - * @return array array of failed keys. Always empty in case of using memcached. + * @return array array of failed keys. */ protected function setValues($data, $duration) { @@ -313,9 +313,10 @@ class MemCache extends Cache // @see http://php.net/manual/en/memcache.set.php // @see http://php.net/manual/en/memcached.expiration.php $expire = $duration > 0 ? $duration + time() : 0; - $this->_cache->setMulti($data, $expire); - return []; + // Memcached::setMulti() returns boolean + // @see http://php.net/manual/ru/memcached.setmulti.php + return $this->_cache->setMulti($data, $expire) ? [] : array_keys($data); } else { return parent::setValues($data, $duration); } From 079c5a41f7542af418599dcf73683c322d5603e6 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 20 Apr 2017 21:46:11 +0300 Subject: [PATCH 149/184] Fixes #13362: Fixed return value of --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index feb0cce..f5e77c3 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) From 28d8fa66fccafc5fd646e08900f2f5b8cb007c8a Mon Sep 17 00:00:00 2001 From: Insolita Date: Fri, 21 Apr 2017 07:23:51 +0800 Subject: [PATCH 150/184] Fixes #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views --- framework/CHANGELOG.md | 2 +- framework/db/pgsql/Schema.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index f5e77c3..dcd77c2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -3,7 +3,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- - +- Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 0d0fa34..3eb4f72 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -221,7 +221,7 @@ SQL; SELECT c.relname AS table_name FROM pg_class c INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace -WHERE ns.nspname = :schemaName AND c.relkind = 'v' +WHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm') ORDER BY c.relname SQL; return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn(); From a3d245274141953690da6b3bf0cfab5ee24427cf Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 22 Apr 2017 16:25:27 +0300 Subject: [PATCH 151/184] Created issue template --- .github/ISSUE_TEMPLATE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 020f890..a355613 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ +Note that only PHP 7 compatibility issues are accepted. For security issues contact maintainers privately. + ### What steps will reproduce the problem? ### What is the expected result? @@ -9,6 +11,6 @@ | Q | A | ---------------- | --- -| Yii version | 2.0.? -| PHP version | +| Yii version | 1.1.? +| PHP version | 7.? | Operating system | From 3cb3b324d3396178272dd0848333d7a87db437dc Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 22 Apr 2017 16:26:51 +0300 Subject: [PATCH 152/184] Wrong repo [skip ci] --- .github/ISSUE_TEMPLATE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a355613..8fe8f24 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -Note that only PHP 7 compatibility issues are accepted. For security issues contact maintainers privately. + ### What steps will reproduce the problem? @@ -11,6 +11,6 @@ Note that only PHP 7 compatibility issues are accepted. For security issues cont | Q | A | ---------------- | --- -| Yii version | 1.1.? -| PHP version | 7.? +| Yii version | 2.0.? +| PHP version | | Operating system | From 38488ca55ba6e875e4e0f2a8231c55d2b9ec5ad3 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 24 Apr 2017 11:24:52 +0300 Subject: [PATCH 153/184] Fixes #13694: yii\widgets\Pjax now sends X-Pjax-Url header with response to fix redirect --- framework/CHANGELOG.md | 2 ++ framework/widgets/Pjax.php | 1 + 2 files changed, 3 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index dcd77c2..8624f3c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -3,6 +3,8 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- + +- Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) diff --git a/framework/widgets/Pjax.php b/framework/widgets/Pjax.php index ee109c5..4d85673 100644 --- a/framework/widgets/Pjax.php +++ b/framework/widgets/Pjax.php @@ -171,6 +171,7 @@ class Pjax extends Widget $response->setStatusCode(200); $response->format = Response::FORMAT_HTML; $response->content = $content; + $response->headers->setDefault('X-Pjax-Url', Yii::$app->request->url); $response->send(); Yii::$app->end(); From eed00dc33d20d6c90598b42298bfde4dd8d81cf7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 24 Apr 2017 11:26:32 +0300 Subject: [PATCH 154/184] Fixed PHP manual link language [skip ci] --- framework/caching/MemCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index 1cb1bfe..6e69b40 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -315,7 +315,7 @@ class MemCache extends Cache $expire = $duration > 0 ? $duration + time() : 0; // Memcached::setMulti() returns boolean - // @see http://php.net/manual/ru/memcached.setmulti.php + // @see http://php.net/manual/en/memcached.setmulti.php return $this->_cache->setMulti($data, $expire) ? [] : array_keys($data); } else { return parent::setValues($data, $duration); From d4d80ed3120a28e7808e35f1cfa583ace5cea0bb Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 24 Apr 2017 21:50:37 +0300 Subject: [PATCH 155/184] Add setup for Apache Describes the settings for Apache. --- docs/guide-ru/test-environment-setup.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/guide-ru/test-environment-setup.md b/docs/guide-ru/test-environment-setup.md index 8431196..09ac6cd 100644 --- a/docs/guide-ru/test-environment-setup.md +++ b/docs/guide-ru/test-environment-setup.md @@ -47,3 +47,25 @@ Changed current directory to путём запуска команды `codecept` без указания пути. Тем не менее, данный подход может не подойти. К примеру, в двух разных проектах может потребоваться установить разные версии Codeception. Для простоты все команды в разделах про тестирование используются так, будто Codeception установлен глобально. + +### Настройка веб сервера Apache + +Если вы использутете Apache и настроили его как описано в разделе «[Установк Yii](start-installation.md)», то для тестов вам необходимо создать отдельнвй виртуальный хост который будет работать с той же папкой, но использовать входной скрипт index-test.php: +``` + + DocumentRoot "path/to/basic/webb" + ServerName mysite-test + + Order Allow,Deny + Allow from all + AddDefaultCharset utf-8 + DirectoryIndex index-test.php + RewriteEngine on + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . index-test.php + + +``` +Так мы укажем веб серверу перенаправлять все запросы на скрипт index-test.php. +> Note: Обратите внимание, что здесь мы указываем параметр DirectoryIndex, помимо тех параметров, которые были указаны для первого хоста. Это сделано с той целью, чтобы при обращении к главной странице по адресу mysite-test также использовался бы скрипт index-test.php. From bda091bbb24de594adaae0554421cb81598b00f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D0=B4=D0=B8=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20=D0=AE?= =?UTF-8?q?=D1=80=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Tue, 25 Apr 2017 00:20:46 +0500 Subject: [PATCH 156/184] yii\filters\RateLimiter refactored (#13994) Refactored yii\filters\RateLimiter and added tests --- framework/CHANGELOG.md | 1 + framework/filters/RateLimiter.php | 30 ++++-- tests/TestCase.php | 2 +- tests/framework/filters/RateLimiterTest.php | 155 ++++++++++++++++++++++++++++ tests/framework/filters/stubs/RateLimit.php | 44 ++++++++ 5 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 tests/framework/filters/RateLimiterTest.php create mode 100644 tests/framework/filters/stubs/RateLimit.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 8624f3c..20eb6f5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -8,6 +8,7 @@ Yii Framework 2 Change Log - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) +- Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) diff --git a/framework/filters/RateLimiter.php b/framework/filters/RateLimiter.php index 7987061..4feaff2 100644 --- a/framework/filters/RateLimiter.php +++ b/framework/filters/RateLimiter.php @@ -65,22 +65,34 @@ class RateLimiter extends ActionFilter /** * @inheritdoc */ + public function init() + { + if ($this->request === null) { + $this->request = Yii::$app->getRequest(); + } + if ($this->response === null) { + $this->response = Yii::$app->getResponse(); + } + } + + /** + * @inheritdoc + */ public function beforeAction($action) { - $user = $this->user ? : (Yii::$app->getUser() ? Yii::$app->getUser()->getIdentity(false) : null); - if ($user instanceof RateLimitInterface) { + if ($this->user === null && Yii::$app->getUser()) { + $this->user = Yii::$app->getUser()->getIdentity(false); + } + + if ($this->user instanceof RateLimitInterface) { Yii::trace('Check rate limit', __METHOD__); - $this->checkRateLimit( - $user, - $this->request ? : Yii::$app->getRequest(), - $this->response ? : Yii::$app->getResponse(), - $action - ); - } elseif ($user) { + $this->checkRateLimit($this->user, $this->request, $this->response, $action); + } elseif ($this->user) { Yii::info('Rate limit skipped: "user" does not implement RateLimitInterface.', __METHOD__); } else { Yii::info('Rate limit skipped: user not logged in.', __METHOD__); } + return true; } diff --git a/tests/TestCase.php b/tests/TestCase.php index 41cb70e..fedfdd7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -152,7 +152,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase } $property = $class->getProperty($propertyName); $property->setAccessible(true); - $property->setValue($value); + $property->setValue($object, $value); if ($revoke) { $property->setAccessible(false); } diff --git a/tests/framework/filters/RateLimiterTest.php b/tests/framework/filters/RateLimiterTest.php new file mode 100644 index 0000000..e4ca515 --- /dev/null +++ b/tests/framework/filters/RateLimiterTest.php @@ -0,0 +1,155 @@ +prophesize(Logger::className()); + $logger + ->log(Argument::any(), Argument::any(), Argument::any()) + ->will(function ($parameters, $logger) { + $logger->messages = $parameters; + }); + + Yii::setLogger($logger->reveal()); + + $this->mockWebApplication(); + } + protected function tearDown() + { + parent::tearDown(); + Yii::setLogger(null); + } + + public function testInitFilledRequest() + { + $rateLimiter = new RateLimiter(['request' => 'Request']); + + $this->assertEquals('Request', $rateLimiter->request); + } + + public function testInitNotFilledRequest() + { + $rateLimiter = new RateLimiter(); + + $this->assertInstanceOf(Request::className(), $rateLimiter->request); + } + + public function testInitFilledResponse() + { + $rateLimiter = new RateLimiter(['response' => 'Response']); + + $this->assertEquals('Response', $rateLimiter->response); + } + + public function testInitNotFilledResponse() + { + $rateLimiter = new RateLimiter(); + + $this->assertInstanceOf(Response::className(), $rateLimiter->response); + } + + public function testBeforeActionUserInstanceOfRateLimitInterface() + { + $rateLimiter = new RateLimiter(); + $rateLimit = new RateLimit(); + $rateLimit->setAllowance([1, time()]) + ->setRateLimit([1, 1]); + $rateLimiter->user = $rateLimit; + + $result = $rateLimiter->beforeAction('test'); + + $this->assertContains('Check rate limit', Yii::getLogger()->messages); + $this->assertTrue($result); + } + + public function testBeforeActionUserNotInstanceOfRateLimitInterface() + { + $rateLimiter = new RateLimiter(['user' => 'User']); + + $result = $rateLimiter->beforeAction('test'); + + $this->assertContains('Rate limit skipped: "user" does not implement RateLimitInterface.', Yii::getLogger()->messages); + $this->assertTrue($result); + } + + public function testBeforeActionEmptyUser() + { + $user = new User(['identityClass' => RateLimit::className()]); + Yii::$app->set('user', $user); + $rateLimiter = new RateLimiter(); + + $result = $rateLimiter->beforeAction('test'); + + $this->assertContains('Rate limit skipped: user not logged in.', Yii::getLogger()->messages); + $this->assertTrue($result); + } + + public function testCheckRateLimitTooManyRequests() + { + /* @var $rateLimit UserIdentity|\Prophecy\ObjectProphecy */ + $rateLimit = new RateLimit; + $rateLimit + ->setRateLimit([1, 1]) + ->setAllowance([1, time() + 2]); + $rateLimiter = new RateLimiter(); + + $this->setExpectedException('yii\web\TooManyRequestsHttpException'); + $rateLimiter->checkRateLimit($rateLimit, Yii::$app->request, Yii::$app->response, 'testAction'); + } + + public function testCheckRateaddRateLimitHeaders() + { + /* @var $user UserIdentity|\Prophecy\ObjectProphecy */ + $rateLimit = new RateLimit; + $rateLimit + ->setRateLimit([1, 1]) + ->setAllowance([1, time()]); + $rateLimiter = $this->getMockBuilder(RateLimiter::className()) + ->setMethods(['addRateLimitHeaders']) + ->getMock(); + $rateLimiter->expects(self::at(0)) + ->method('addRateLimitHeaders') + ->willReturn(null); + + $rateLimiter->checkRateLimit($rateLimit, Yii::$app->request, Yii::$app->response, 'testAction'); + } + + public function testAddRateLimitHeadersDisabledRateLimitHeaders() + { + $rateLimiter = new RateLimiter(); + $rateLimiter->enableRateLimitHeaders = false; + $response = Yii::$app->response; + + $rateLimiter->addRateLimitHeaders($response, 1, 0, 0); + $this->assertCount(0, $response->getHeaders()); + } + + public function testAddRateLimitHeadersEnabledRateLimitHeaders() + { + $rateLimiter = new RateLimiter(); + $rateLimiter->enableRateLimitHeaders = true; + $response = Yii::$app->response; + + $rateLimiter->addRateLimitHeaders($response, 1, 0, 0); + $this->assertCount(3, $response->getHeaders()); + } +} diff --git a/tests/framework/filters/stubs/RateLimit.php b/tests/framework/filters/stubs/RateLimit.php new file mode 100644 index 0000000..040fb83 --- /dev/null +++ b/tests/framework/filters/stubs/RateLimit.php @@ -0,0 +1,44 @@ +_rateLimit; + } + + public function setRateLimit($rateLimit) + { + $this->_rateLimit = $rateLimit; + + return $this; + } + + public function loadAllowance($request, $action) + { + return $this->_allowance; + } + + public function setAllowance($allowance) + { + $this->_allowance = $allowance; + + return $this; + } + + + public function saveAllowance($request, $action, $allowance, $timestamp) + { + return [$action, $allowance, $timestamp]; + } + +} From 918aee784ef30be88dbfbef0bc90fca544e31fec Mon Sep 17 00:00:00 2001 From: Dmitry Naumenko Date: Tue, 25 Apr 2017 07:54:55 +0300 Subject: [PATCH 157/184] Fixed spelling and typos --- docs/guide-ru/test-environment-setup.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guide-ru/test-environment-setup.md b/docs/guide-ru/test-environment-setup.md index 09ac6cd..bc53747 100644 --- a/docs/guide-ru/test-environment-setup.md +++ b/docs/guide-ru/test-environment-setup.md @@ -48,9 +48,9 @@ Changed current directory to разных проектах может потребоваться установить разные версии Codeception. Для простоты все команды в разделах про тестирование используются так, будто Codeception установлен глобально. -### Настройка веб сервера Apache +### Настройка веб-сервера Apache -Если вы использутете Apache и настроили его как описано в разделе «[Установк Yii](start-installation.md)», то для тестов вам необходимо создать отдельнвй виртуальный хост который будет работать с той же папкой, но использовать входной скрипт index-test.php: +Если вы используете Apache и настроили его как описано в разделе «[Установка Yii](start-installation.md)», то для тестов вам необходимо создать отдельный виртуальный хост который будет работать с той же папкой, но использовать входной скрипт `index-test.php`: ``` DocumentRoot "path/to/basic/webb" @@ -67,5 +67,5 @@ Changed current directory to ``` -Так мы укажем веб серверу перенаправлять все запросы на скрипт index-test.php. -> Note: Обратите внимание, что здесь мы указываем параметр DirectoryIndex, помимо тех параметров, которые были указаны для первого хоста. Это сделано с той целью, чтобы при обращении к главной странице по адресу mysite-test также использовался бы скрипт index-test.php. +Так мы укажем веб серверу перенаправлять все запросы на скрипт `index-test.php`. +> Note: Обратите внимание, что здесь мы указываем параметр `DirectoryIndex`, помимо тех параметров, которые были указаны для первого хоста. Это сделано с той целью, чтобы при обращении к главной странице по адресу `mysite-test` также использовался бы скрипт `index-test.php`. From 6ce6385094f4830b2d66a7b731584db3e7edcc73 Mon Sep 17 00:00:00 2001 From: depesr Date: Tue, 25 Apr 2017 09:45:29 +0200 Subject: [PATCH 158/184] Updated slovak localization Fixed missing translation for '{attribute} must be an integer.' string --- framework/messages/sk/yii.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/messages/sk/yii.php b/framework/messages/sk/yii.php index ac2269b..ef89a0f 100644 --- a/framework/messages/sk/yii.php +++ b/framework/messages/sk/yii.php @@ -79,7 +79,7 @@ return [ '{attribute} must be a string.' => '{attribute} musí byť reťazec.', '{attribute} must be a valid IP address.' => '{attribute} musí byť platná IP adresa.', '{attribute} must be an IP address with specified subnet.' => '{attribute} musí byť IP adresa so špecifikovanou podsieťou.', - '{attribute} must be an integer.' => '{attribute} musí byť integer.', + '{attribute} must be an integer.' => '{attribute} musí byť celé číslo.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} musí byť "{true}" alebo "{false}".', '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} musí byť "{compareValueOrAttribute}".', '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} musí byť väčšie ako "{compareValueOrAttribute}".', From 98871123c9765896e181788c2ea653681e92782d Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Tue, 25 Apr 2017 10:54:18 +0200 Subject: [PATCH 159/184] Fixed ignored params when using count in `SqlDataProvider` --- framework/data/SqlDataProvider.php | 5 ++++- tests/framework/data/SqlDataProviderTest.php | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php index e0062ba..0b894d3 100644 --- a/framework/data/SqlDataProvider.php +++ b/framework/data/SqlDataProvider.php @@ -163,6 +163,9 @@ class SqlDataProvider extends BaseDataProvider */ protected function prepareTotalCount() { - return (new Query())->from(['sub' => "({$this->sql})"])->count('*', $this->db); + return (new Query([ + 'from' => ['sub' => "({$this->sql})"], + 'params' => $this->params + ]))->count('*', $this->db); } } diff --git a/tests/framework/data/SqlDataProviderTest.php b/tests/framework/data/SqlDataProviderTest.php index 0be1882..d5edc47 100644 --- a/tests/framework/data/SqlDataProviderTest.php +++ b/tests/framework/data/SqlDataProviderTest.php @@ -32,4 +32,16 @@ class SqlDataProviderTest extends DatabaseTestCase ]); $this->assertEquals(3, $dataProvider->getTotalCount()); } + + public function testTotalCountWithParams() + { + $dataProvider = new SqlDataProvider([ + 'sql' => 'select * from `customer` where id > :minimum', + 'params' => [ + ':minimum' => -1 + ], + 'db' => $this->getConnection(), + ]); + $this->assertEquals(3, $dataProvider->getTotalCount()); + } } From 4af62a6bd76e30c6771c3201d64c942b1f0b5e97 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 25 Apr 2017 23:02:19 +0200 Subject: [PATCH 160/184] improve session PHPdoc --- framework/web/Session.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/framework/web/Session.php b/framework/web/Session.php index d658064..588c29f 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -190,6 +190,11 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co /** * Frees all session variables and destroys all data registered to a session. + * + * This method has no effect when session is not [[getIsActive()|active]]. + * Make sure to call [[open()]] before calling it. + * @see open() + * @see isActive */ public function destroy() { @@ -270,9 +275,16 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } /** - * Updates the current session ID with a newly generated one . + * Updates the current session ID with a newly generated one. + * * Please refer to for more details. + * + * This method has no effect when session is not [[getIsActive()|active]]. + * Make sure to call [[open()]] before calling it. + * * @param bool $deleteOldSession Whether to delete the old associated session file or not. + * @see open() + * @see isActive */ public function regenerateID($deleteOldSession = false) { From 339c7663eb921982df35a65403eeac737cdb8b56 Mon Sep 17 00:00:00 2001 From: sasha-ch Date: Thu, 26 Jan 2017 17:16:38 +0300 Subject: [PATCH 161/184] Fixes #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` --- framework/CHANGELOG.md | 1 + framework/web/XmlResponseFormatter.php | 40 ++++++++++++++++++++++-- tests/framework/web/XmlResponseFormatterTest.php | 10 ++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 20eb6f5..961099f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) diff --git a/framework/web/XmlResponseFormatter.php b/framework/web/XmlResponseFormatter.php index 441a522..fadf5bb 100644 --- a/framework/web/XmlResponseFormatter.php +++ b/framework/web/XmlResponseFormatter.php @@ -10,6 +10,7 @@ namespace yii\web; use DOMDocument; use DOMElement; use DOMText; +use DOMException; use yii\base\Arrayable; use yii\base\Component; use yii\helpers\StringHelper; @@ -94,11 +95,11 @@ class XmlResponseFormatter extends Component implements ResponseFormatterInterfa if (is_int($name) && is_object($value)) { $this->buildXml($element, $value); } elseif (is_array($value) || is_object($value)) { - $child = new DOMElement(is_int($name) ? $this->itemTag : $name); + $child = new DOMElement($this->getValidXmlElementName($name)); $element->appendChild($child); $this->buildXml($child, $value); } else { - $child = new DOMElement(is_int($name) ? $this->itemTag : $name); + $child = new DOMElement($this->getValidXmlElementName($name)); $element->appendChild($child); $child->appendChild(new DOMText($this->formatScalarValue($value))); } @@ -143,4 +144,39 @@ class XmlResponseFormatter extends Component implements ResponseFormatterInterfa return (string) $value; } + + /** + * Returns element name ready to be used in DOMElement if + * name is not empty, is not int and is valid. + * + * Falls back to [[itemTag]] otherwise. + * + * @param mixed $name + * @return string + * @since 2.0.12 + */ + protected function getValidXmlElementName($name) + { + if (empty($name) || is_int($name) || !$this->isValidXmlName($name)) { + return $this->itemTag; + } + return $name; + } + + /** + * Checks if name is valid to be used in XML + * + * @param mixed $name + * @return bool + * @see http://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 + */ + protected function isValidXmlName($name) + { + try { + new DOMElement($name); + return true; + } catch (DOMException $e) { + return false; + } + } } diff --git a/tests/framework/web/XmlResponseFormatterTest.php b/tests/framework/web/XmlResponseFormatterTest.php index 519d96c..046f2bb 100644 --- a/tests/framework/web/XmlResponseFormatterTest.php +++ b/tests/framework/web/XmlResponseFormatterTest.php @@ -69,6 +69,16 @@ class XmlResponseFormatterTest extends FormatterTest 'c' => [2, '<>'], false, ], "1abc2<>false\n"], + + // Checks if empty keys and keys not valid in XML are processed. + // See https://github.com/yiisoft/yii2/pull/10346/ + [[ + '' => 1, + '2015-06-18' => '2015-06-18', + 'b:c' => 'b:c', + 'a b c' => 'a b c', + 'äøñ' => 'äøñ' + ], "12015-06-18b:ca b c<äøñ>äøñ\n"], ]); } From 3598f8180ce72e9e8048634e2105068081ac8b47 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Wed, 26 Apr 2017 02:29:10 +0300 Subject: [PATCH 162/184] Fixes #13087: Fixed getting active validators for safe attribute --- framework/CHANGELOG.md | 2 +- framework/validators/Validator.php | 24 +++++------------------- tests/framework/validators/ValidatorTest.php | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 961099f..6c51715 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -14,7 +14,7 @@ Yii Framework 2 Change Log - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) - Bug #7946: Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) -- Bug #13087: Fixed getting active validators for safe attribute (developeruz) +- Bug #13087: Fixed getting active validators for safe attribute (developeruz, klimov-paul) - Bug #13571: Fix `yii\db\mssql\QueryBuilder::checkIntegrity` for all tables (boboldehampsink) - Bug #11230: Include `defaultRoles` in `yii\rbac\DbManager->getRolesByUser()` results (developeruz) - Enh #13243: Added support for unicode attribute names in `yii\widgets\DetailView` (arogachev) diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index f654380..2915d31 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -47,6 +47,8 @@ use yii\base\NotSupportedException; * - `ip`: [[IpValidator]] * * For more details and usage information on Validator, see the [guide article on validators](guide:input-validation). + * + * @property array $attributeNames cleaned attribute names without the `!` character at the beginning. This property is read-only. * * @author Qiang Xue * @since 2.0 @@ -102,11 +104,6 @@ class Validator extends Component */ public $attributes = []; /** - * @var array cleaned attribute names. Contains attribute names without `!` character at the beginning - * @since 2.0.12 - */ - private $_attributeNames = []; - /** * @var string the user-defined error message. It may contain the following placeholders which * will be replaced accordingly by the validator: * @@ -238,7 +235,6 @@ class Validator extends Component $this->attributes = (array) $this->attributes; $this->on = (array) $this->on; $this->except = (array) $this->except; - $this->setAttributeNames((array)$this->attributes); } /** @@ -459,23 +455,13 @@ class Validator extends Component /** * Returns cleaned attribute names without the `!` character at the beginning - * @return array + * @return array attribute names. * @since 2.0.12 */ public function getAttributeNames() { - return $this->_attributeNames; - } - - /** - * Saves attribute names without `!` character at the beginning - * @param array $attributeNames - * @since 2.0.12 - */ - private function setAttributeNames($attributeNames) - { - $this->_attributeNames = array_map(function($attribute) { + return array_map(function($attribute) { return ltrim($attribute, '!'); - }, $attributeNames); + }, $this->attributes); } } diff --git a/tests/framework/validators/ValidatorTest.php b/tests/framework/validators/ValidatorTest.php index eb3ab05..0921076 100644 --- a/tests/framework/validators/ValidatorTest.php +++ b/tests/framework/validators/ValidatorTest.php @@ -240,17 +240,27 @@ class ValidatorTest extends TestCase $this->assertEquals('attr_msg_val::abc::param_value', $errors[0]); } + public function testGetAttributeNames() + { + $validator = new TestValidator(); + $validator->attributes = ['id', 'name', '!email']; + $this->assertEquals(['id', 'name', 'email'], $validator->getAttributeNames()); + } + + /** + * @depends testGetAttributeNames + */ public function testGetActiveValidatorsForSafeAttributes() { $model = $this->getTestModel(); $validators = $model->getActiveValidators('safe_attr'); - $is_found = false; + $isFound = false; foreach ($validators as $v) { if ($v instanceof NumberValidator) { - $is_found = true; + $isFound = true; break; } } - $this->assertTrue($is_found); + $this->assertTrue($isFound); } } From 385eb8804878404bb023bd44c4e75d5cf28f7805 Mon Sep 17 00:00:00 2001 From: Leandro Guindani Gehlen Date: Wed, 26 Apr 2017 01:28:52 +0200 Subject: [PATCH 163/184] Added `yii\data\Sort::parseSortParams` allowing customize other params request formats in descendant class. close #13170 --- framework/CHANGELOG.md | 1 + framework/data/Sort.php | 31 +++++++++++++++++++++++++++++-- tests/framework/data/SortTest.php | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6c51715..1101a59 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -34,6 +34,7 @@ Yii Framework 2 Change Log - Bug #8120: Fixes LIKE special characters escaping for Cubrid/MSSQL/Oracle/SQLite in `yii\db\QueryBuilder` (sergeymakinen) - Bug #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception (Vovan-VE) - Enh #8641: Enhanced `yii\console\Request::resolve()` to prevent passing parameters, that begin from digits (silverfire) +- Enh #13179: Added `yii\data\Sort::parseSortParam` allowing to customize sort param in descendant class (leandrogehlen) - Enh #13278: `yii\caching\DbQueryDependency` created allowing specification of the cache dependency via `yii\db\QueryInterface` (klimov-paul) - Enh #13467: `yii\data\ActiveDataProvider` no longer queries models if models count is zero (kLkA, Kolyunya) - Enh #13523: Plural rule for pasta (developeruz) diff --git a/framework/data/Sort.php b/framework/data/Sort.php index 6049af4..30185db 100644 --- a/framework/data/Sort.php +++ b/framework/data/Sort.php @@ -243,8 +243,8 @@ class Sort extends Object $request = Yii::$app->getRequest(); $params = $request instanceof Request ? $request->getQueryParams() : []; } - if (isset($params[$this->sortParam]) && is_scalar($params[$this->sortParam])) { - $attributes = explode($this->separator, $params[$this->sortParam]); + if (isset($params[$this->sortParam])) { + $attributes = $this->parseSortParam($params[$this->sortParam]); foreach ($attributes as $attribute) { $descending = false; if (strncmp($attribute, '-', 1) === 0) { @@ -269,6 +269,33 @@ class Sort extends Object } /** + * Parses the value of [[sortParam]] into an array of sort attributes. + * + * The format must be the attribute name only for ascending + * or the attribute name prefixed with `-` for descending. + * + * For example the following return value will result in ascending sort by + * `category` and descending sort by `created_at`: + * + * ```php + * [ + * 'category', + * '-created_at' + * ] + * ``` + * + * @param string $param the value of the [[sortParam]]. + * @return array the valid sort attributes. + * @since 2.0.12 + * @see $separator for the attribute name separator. + * @see $sortParam + */ + protected function parseSortParam($param) + { + return is_scalar($param) ? explode($this->separator, $param) : []; + } + + /** * Sets up the currently sort information. * @param array|null $attributeOrders sort directions indexed by attribute names. * Sort direction can be either `SORT_ASC` for ascending order or diff --git a/tests/framework/data/SortTest.php b/tests/framework/data/SortTest.php index d0fd505..31fe325 100644 --- a/tests/framework/data/SortTest.php +++ b/tests/framework/data/SortTest.php @@ -225,4 +225,36 @@ class SortTest extends TestCase $this->assertEquals('Age', $sort->link('age')); } + + public function testParseSortParam() + { + $sort = new CustomSort([ + 'attributes' => [ + 'age', + 'name' + ], + 'params' => [ + 'sort' => [ + ['field' => 'age', 'dir' => 'asc'], + ['field' => 'name', 'dir' => 'desc'] + ] + ], + 'enableMultiSort' => true + ]); + + $this->assertEquals(SORT_ASC, $sort->getAttributeOrder('age')); + $this->assertEquals(SORT_DESC, $sort->getAttributeOrder('name')); + } +} + +class CustomSort extends Sort +{ + protected function parseSortParam($params) + { + $attributes = []; + foreach ($params as $item) { + $attributes[] = ($item['dir'] == 'desc') ? '-' . $item['field'] : $item['field']; + } + return $attributes; + } } From 70dc2fa33b33fafcd30cc9ac9747645b5d5b91f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Fri, 3 Feb 2017 11:29:44 +0100 Subject: [PATCH 164/184] Fixes #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` --- framework/CHANGELOG.md | 1 + framework/caching/DbCache.php | 37 ++++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 1101a59..50b23ae 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) - Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index cecc8d6..a57bd48 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -187,13 +187,16 @@ class DbCache extends Cache */ protected function setValue($key, $value, $duration) { - $command = $this->db->createCommand() - ->update($this->cacheTable, [ - 'expire' => $duration > 0 ? $duration + time() : 0, - 'data' => [$value, \PDO::PARAM_LOB], - ], ['id' => $key]); + $result = $this->db->noCache(function (Connection $db) use ($key, $value, $duration) { + $command = $db->createCommand() + ->update($this->cacheTable, [ + 'expire' => $duration > 0 ? $duration + time() : 0, + 'data' => [$value, \PDO::PARAM_LOB], + ], ['id' => $key]); + return $command->execute(); + }); - if ($command->execute()) { + if ($result) { $this->gc(); return true; @@ -216,12 +219,14 @@ class DbCache extends Cache $this->gc(); try { - $this->db->createCommand() - ->insert($this->cacheTable, [ - 'id' => $key, - 'expire' => $duration > 0 ? $duration + time() : 0, - 'data' => [$value, \PDO::PARAM_LOB], - ])->execute(); + $this->db->noCache(function (Connection $db) use ($key, $value, $duration) { + $db->createCommand() + ->insert($this->cacheTable, [ + 'id' => $key, + 'expire' => $duration > 0 ? $duration + time() : 0, + 'data' => [$value, \PDO::PARAM_LOB], + ])->execute(); + }); return true; } catch (\Exception $e) { @@ -237,9 +242,11 @@ class DbCache extends Cache */ protected function deleteValue($key) { - $this->db->createCommand() - ->delete($this->cacheTable, ['id' => $key]) - ->execute(); + $this->db->noCache(function (Connection $db) use ($key) { + $db->createCommand() + ->delete($this->cacheTable, ['id' => $key]) + ->execute(); + }); return true; } From 3a8f702759924f3d13200b5f98295a2917f7f083 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 03:16:12 +0300 Subject: [PATCH 165/184] Adjusted code style for less conditions branching --- framework/caching/DbCache.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index a57bd48..0bae1ab 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -136,9 +136,8 @@ class DbCache extends Cache $this->db->enableQueryCache = true; return $result; - } else { - return $query->createCommand($this->db)->queryScalar(); } + return $query->createCommand($this->db)->queryScalar(); } /** @@ -200,9 +199,8 @@ class DbCache extends Cache $this->gc(); return true; - } else { - return $this->addValue($key, $value, $duration); } + return $this->addValue($key, $value, $duration); } /** From 70cd75c84e2d993a9f6c8cad0219d46cdc80686f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 03:19:49 +0300 Subject: [PATCH 166/184] Fixes #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console --- framework/CHANGELOG.md | 1 + framework/console/controllers/CacheController.php | 25 ++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 50b23ae..62fa5a0 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) - Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) - Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) diff --git a/framework/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php index 1f39310..9d65f8d 100644 --- a/framework/console/controllers/CacheController.php +++ b/framework/console/controllers/CacheController.php @@ -8,6 +8,7 @@ namespace yii\console\controllers; use Yii; +use yii\caching\ApcCache; use yii\console\Controller; use yii\caching\Cache; use yii\helpers\Console; @@ -32,7 +33,7 @@ use yii\console\Exception; * configured are different from web application, web application cache won't be cleared. In order to fix it please * duplicate web application cache components in console config. You can use any component names. * - * Both APC and OpCache aren't shared between PHP processes so flushing cache from command line has no effect on web. + * APC is not shared between PHP processes so flushing cache from command line has no effect on web. * Flushing web cache could be either done by: * * - Putting a php file under web root and calling it via HTTP @@ -99,7 +100,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false, ]; } @@ -123,7 +124,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false, ]; } @@ -178,7 +179,11 @@ class CacheController extends Controller $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW); foreach ($caches as $name => $class) { - $this->stdout("\t* $name ($class)\n", Console::FG_GREEN); + if ($this->canBeFlushed($class)) { + $this->stdout("\t* $name ($class)\n", Console::FG_GREEN); + } else { + $this->stdout("\t* $name ($class) - can not be flushed via console\n", Console::FG_YELLOW); + } } $this->stdout("\n"); @@ -256,7 +261,7 @@ class CacheController extends Controller $findAll = ($cachesNames === []); foreach ($components as $name => $component) { - if (!$findAll && !in_array($name, $cachesNames)) { + if (!$findAll && !in_array($name, $cachesNames, true)) { continue; } @@ -281,4 +286,14 @@ class CacheController extends Controller { return is_subclass_of($className, Cache::className()); } + + /** + * Checks if cache of a certain class can be flushed + * @param string $className class name. + * @return bool + */ + private function canBeFlushed($className) + { + return !is_a($className, ApcCache::className(), true) || php_sapi_name() !== "cli"; + } } From db0e7a6c8f31351df5c38a5f2ad638c57ca8770b Mon Sep 17 00:00:00 2001 From: Cyrillos Jonua Date: Wed, 26 Apr 2017 03:28:05 +0300 Subject: [PATCH 167/184] Fixes #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` --- framework/CHANGELOG.md | 1 + framework/di/Instance.php | 7 ++++++- tests/framework/di/InstanceTest.php | 9 ++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 62fa5a0..5786abc 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) - Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) - Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) - Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) diff --git a/framework/di/Instance.php b/framework/di/Instance.php index b4d6dea..2300cc4 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -116,7 +116,12 @@ class Instance $container = Yii::$container; } unset($reference['class']); - return $container->get($class, [], $reference); + $component = $container->get($class, [], $reference); + if ($type === null || $component instanceof $type) { + return $component; + } else { + throw new InvalidConfigException('Invalid data type: ' . $class .'. ' . $type . ' is expected.'); + } } elseif (empty($reference)) { throw new InvalidConfigException('The required component is not specified.'); } diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index 3e673dd..a668919 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -112,7 +112,6 @@ class InstanceTest extends TestCase $this->expectExceptionMessage('"db" refers to a yii\db\Connection component. yii\base\Widget is expected.'); Instance::ensure('db', 'yii\base\Widget', $container); - Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], 'yii\base\Widget', $container); } public function testExceptionInvalidDataType() @@ -191,4 +190,12 @@ PHP Instance::__set_state([]); } + + public function testExceptionInvalidDataTypeInArray() + { + $this->setExpectedException('yii\base\InvalidConfigException', 'Invalid data type: yii\db\Connection. yii\base\Widget is expected.'); + Instance::ensure([ + 'class' => Connection::className(), + ], 'yii\base\Widget'); + } } From 6792099bcc0b682cc9fcfa9e91157a29e8bfe460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20H=C3=A4rtl?= Date: Tue, 21 Mar 2017 18:23:27 +0100 Subject: [PATCH 168/184] Fixes #13689: Fixed handling of errors in closures --- framework/CHANGELOG.md | 1 + framework/web/ErrorHandler.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5786abc..2d35d49 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -6,6 +6,7 @@ Yii Framework 2 Change Log - Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) - Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) +- Bug #13689: Fixed handling of errors in closures (mikehaertl) - Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) - Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index bf9e662..a6c4e01 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -191,7 +191,7 @@ class ErrorHandler extends \yii\base\ErrorHandler $url = null; $shouldGenerateLink = true; - if ($method !== null) { + if ($method !== null && substr_compare($method, '{closure}', -9) !== 0) { $reflection = new \ReflectionMethod($class, $method); $shouldGenerateLink = $reflection->isPublic() || $reflection->isProtected(); } From d786b78f2553613f7b31efcceb56a044c1b33cb6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 26 Apr 2017 09:49:44 +0200 Subject: [PATCH 169/184] Helper independent (#14050) * Made sure most helpers are independent of Yii::$app * Made sure most validators are independent of Yii::$app --- tests/framework/helpers/ArrayHelperTest.php | 6 +++++- tests/framework/helpers/ConsoleTest.php | 8 ++++++++ tests/framework/helpers/FileHelperTest.php | 6 ++++++ tests/framework/helpers/InflectorTest.php | 8 ++++++++ tests/framework/helpers/JsonTest.php | 8 ++++++++ tests/framework/helpers/MarkdownTest.php | 8 ++++++++ tests/framework/helpers/VarDumperTest.php | 8 ++++++++ tests/framework/validators/BooleanValidatorTest.php | 4 +++- tests/framework/validators/CompareValidatorTest.php | 4 +++- tests/framework/validators/DefaultValueValidatorTest.php | 4 +++- tests/framework/validators/EachValidatorTest.php | 4 +++- tests/framework/validators/EmailValidatorTest.php | 4 +++- tests/framework/validators/ExistValidatorTest.php | 6 ++++-- tests/framework/validators/FileValidatorTest.php | 3 ++- tests/framework/validators/FilterValidatorTest.php | 3 ++- tests/framework/validators/IpValidatorTest.php | 3 ++- tests/framework/validators/NumberValidatorTest.php | 5 ++++- tests/framework/validators/RangeValidatorTest.php | 4 +++- tests/framework/validators/RegularExpressionValidatorTest.php | 4 +++- tests/framework/validators/RequiredValidatorTest.php | 4 +++- tests/framework/validators/UniqueValidatorTest.php | 6 ++++-- tests/framework/validators/UrlValidatorTest.php | 4 +++- tests/framework/validators/ValidatorTest.php | 4 +++- 23 files changed, 99 insertions(+), 19 deletions(-) diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index 224ef06..282ae7f 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -44,7 +44,9 @@ class ArrayHelperTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); } public function testToArray() @@ -267,6 +269,7 @@ class ArrayHelperTest extends TestCase $sort = new Sort([ 'attributes' => ['name', 'age'], 'defaultOrder' => ['name' => SORT_ASC], + 'params' => [], ]); $orders = $sort->getOrders(); @@ -284,6 +287,7 @@ class ArrayHelperTest extends TestCase $sort = new Sort([ 'attributes' => ['name', 'age'], 'defaultOrder' => ['name' => SORT_ASC, 'age' => SORT_DESC], + 'params' => [], ]); $orders = $sort->getOrders(); diff --git a/tests/framework/helpers/ConsoleTest.php b/tests/framework/helpers/ConsoleTest.php index 61f777d..8952106 100644 --- a/tests/framework/helpers/ConsoleTest.php +++ b/tests/framework/helpers/ConsoleTest.php @@ -12,6 +12,14 @@ use yiiunit\TestCase; */ class ConsoleTest extends TestCase { + protected function setUp() + { + parent::setUp(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); + } + public function testStripAnsiFormat() { ob_start(); diff --git a/tests/framework/helpers/FileHelperTest.php b/tests/framework/helpers/FileHelperTest.php index d70c378..ca976db 100644 --- a/tests/framework/helpers/FileHelperTest.php +++ b/tests/framework/helpers/FileHelperTest.php @@ -33,8 +33,14 @@ class FileHelperTest extends TestCase */ $this->markTestInComplete('Unit tests runtime directory should be local!'); } + + parent::setUp(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); } + public function tearDown() { $this->removeDir($this->testFilePath); diff --git a/tests/framework/helpers/InflectorTest.php b/tests/framework/helpers/InflectorTest.php index 0d8875c..2408ba1 100644 --- a/tests/framework/helpers/InflectorTest.php +++ b/tests/framework/helpers/InflectorTest.php @@ -10,6 +10,14 @@ use yiiunit\TestCase; */ class InflectorTest extends TestCase { + protected function setUp() + { + parent::setUp(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); + } + public function testPluralize() { $testData = [ diff --git a/tests/framework/helpers/JsonTest.php b/tests/framework/helpers/JsonTest.php index a88ef94..9788858 100644 --- a/tests/framework/helpers/JsonTest.php +++ b/tests/framework/helpers/JsonTest.php @@ -14,6 +14,14 @@ use yiiunit\framework\web\Post; */ class JsonTest extends TestCase { + protected function setUp() + { + parent::setUp(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); + } + public function testEncode() { // basic data encoding diff --git a/tests/framework/helpers/MarkdownTest.php b/tests/framework/helpers/MarkdownTest.php index af83c62..3e99360 100644 --- a/tests/framework/helpers/MarkdownTest.php +++ b/tests/framework/helpers/MarkdownTest.php @@ -12,6 +12,14 @@ use yii\helpers\Markdown; */ class MarkdownTest extends TestCase { + protected function setUp() + { + parent::setUp(); + + // destroy application, Helper must work without Yii::$app + $this->destroyApplication(); + } + public function testOriginalFlavor() { $text = <<destroyApplication(); + } + public function testDumpIncompleteObject() { $serializedObj = 'O:16:"nonExistingClass":0:{}'; diff --git a/tests/framework/validators/BooleanValidatorTest.php b/tests/framework/validators/BooleanValidatorTest.php index c3f0130..29b008f 100644 --- a/tests/framework/validators/BooleanValidatorTest.php +++ b/tests/framework/validators/BooleanValidatorTest.php @@ -14,7 +14,9 @@ class BooleanValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValue() diff --git a/tests/framework/validators/CompareValidatorTest.php b/tests/framework/validators/CompareValidatorTest.php index 181e98a..e831cb1 100644 --- a/tests/framework/validators/CompareValidatorTest.php +++ b/tests/framework/validators/CompareValidatorTest.php @@ -14,7 +14,9 @@ class CompareValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValueException() diff --git a/tests/framework/validators/DefaultValueValidatorTest.php b/tests/framework/validators/DefaultValueValidatorTest.php index 80a0fdc..5699415 100644 --- a/tests/framework/validators/DefaultValueValidatorTest.php +++ b/tests/framework/validators/DefaultValueValidatorTest.php @@ -13,7 +13,9 @@ class DefaultValueValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateAttribute() diff --git a/tests/framework/validators/EachValidatorTest.php b/tests/framework/validators/EachValidatorTest.php index 7430527..a2757d8 100644 --- a/tests/framework/validators/EachValidatorTest.php +++ b/tests/framework/validators/EachValidatorTest.php @@ -14,7 +14,9 @@ class EachValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testArrayFormat() diff --git a/tests/framework/validators/EmailValidatorTest.php b/tests/framework/validators/EmailValidatorTest.php index 6e59dee..5fa88ae 100644 --- a/tests/framework/validators/EmailValidatorTest.php +++ b/tests/framework/validators/EmailValidatorTest.php @@ -13,7 +13,9 @@ class EmailValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValue() diff --git a/tests/framework/validators/ExistValidatorTest.php b/tests/framework/validators/ExistValidatorTest.php index 306f98d..d3aacc4 100644 --- a/tests/framework/validators/ExistValidatorTest.php +++ b/tests/framework/validators/ExistValidatorTest.php @@ -14,10 +14,12 @@ use yiiunit\framework\db\DatabaseTestCase; abstract class ExistValidatorTest extends DatabaseTestCase { - public function setUp() + protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); ActiveRecord::$db = $this->getConnection(); } diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php index 8775bd5..bfa88e4 100644 --- a/tests/framework/validators/FileValidatorTest.php +++ b/tests/framework/validators/FileValidatorTest.php @@ -14,8 +14,9 @@ use yiiunit\TestCase; */ class FileValidatorTest extends TestCase { - public function setUp() + protected function setUp() { + parent::setUp(); $this->mockApplication(); } diff --git a/tests/framework/validators/FilterValidatorTest.php b/tests/framework/validators/FilterValidatorTest.php index b554e48..9512c2f 100644 --- a/tests/framework/validators/FilterValidatorTest.php +++ b/tests/framework/validators/FilterValidatorTest.php @@ -14,7 +14,8 @@ class FilterValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testAssureExceptionOnInit() diff --git a/tests/framework/validators/IpValidatorTest.php b/tests/framework/validators/IpValidatorTest.php index 1fc2a7e..56f5d96 100644 --- a/tests/framework/validators/IpValidatorTest.php +++ b/tests/framework/validators/IpValidatorTest.php @@ -14,7 +14,8 @@ class IpValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testInitException() diff --git a/tests/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php index 7959d1a..b4456a5 100644 --- a/tests/framework/validators/NumberValidatorTest.php +++ b/tests/framework/validators/NumberValidatorTest.php @@ -46,8 +46,11 @@ class NumberValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + $this->oldLocale = setlocale(LC_NUMERIC, 0); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testEnsureMessageOnInit() diff --git a/tests/framework/validators/RangeValidatorTest.php b/tests/framework/validators/RangeValidatorTest.php index f882eb1..f995740 100644 --- a/tests/framework/validators/RangeValidatorTest.php +++ b/tests/framework/validators/RangeValidatorTest.php @@ -14,7 +14,9 @@ class RangeValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testInitException() diff --git a/tests/framework/validators/RegularExpressionValidatorTest.php b/tests/framework/validators/RegularExpressionValidatorTest.php index adbf7be..bafb1bc 100644 --- a/tests/framework/validators/RegularExpressionValidatorTest.php +++ b/tests/framework/validators/RegularExpressionValidatorTest.php @@ -14,7 +14,9 @@ class RegularExpressionValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValue() diff --git a/tests/framework/validators/RequiredValidatorTest.php b/tests/framework/validators/RequiredValidatorTest.php index 0c206e4..6f43bff 100644 --- a/tests/framework/validators/RequiredValidatorTest.php +++ b/tests/framework/validators/RequiredValidatorTest.php @@ -13,7 +13,9 @@ class RequiredValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValueWithDefaults() diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index afdb0c4..c6248d5 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -16,11 +16,13 @@ use yiiunit\framework\db\DatabaseTestCase; abstract class UniqueValidatorTest extends DatabaseTestCase { - public function setUp() + protected function setUp() { parent::setUp(); - $this->mockApplication(); ActiveRecord::$db = $this->getConnection(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testAssureMessageSetOnInit() diff --git a/tests/framework/validators/UrlValidatorTest.php b/tests/framework/validators/UrlValidatorTest.php index 063d172..1448866 100644 --- a/tests/framework/validators/UrlValidatorTest.php +++ b/tests/framework/validators/UrlValidatorTest.php @@ -14,7 +14,9 @@ class UrlValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } public function testValidateValue() diff --git a/tests/framework/validators/ValidatorTest.php b/tests/framework/validators/ValidatorTest.php index 0921076..8e9dcbe 100644 --- a/tests/framework/validators/ValidatorTest.php +++ b/tests/framework/validators/ValidatorTest.php @@ -17,7 +17,9 @@ class ValidatorTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); + + // destroy application, Validator must work without Yii::$app + $this->destroyApplication(); } protected function getTestModel($additionalAttributes = []) From 1e65fc29edf0d4f10effb88bd77f79e718dfcb6d Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Sat, 22 Apr 2017 19:42:52 +0300 Subject: [PATCH 170/184] Fixes #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM --- framework/CHANGELOG.md | 1 + framework/filters/AccessRule.php | 11 +++++++++-- tests/framework/filters/AccessRuleTest.php | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2d35d49..03d6b0c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya) - Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) - Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) - Bug #13689: Fixed handling of errors in closures (mikehaertl) diff --git a/framework/filters/AccessRule.php b/framework/filters/AccessRule.php index 4eacf57..d827e63 100644 --- a/framework/filters/AccessRule.php +++ b/framework/filters/AccessRule.php @@ -165,7 +165,7 @@ class AccessRule extends Component } /** - * @param string $ip the IP address + * @param string|null $ip the IP address * @return bool whether the rule applies to the IP address */ protected function matchIP($ip) @@ -174,7 +174,14 @@ class AccessRule extends Component return true; } foreach ($this->ips as $rule) { - if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) { + if ($rule === '*' || + $rule === $ip || + ( + $ip !== null && + ($pos = strpos($rule, '*')) !== false && + strncmp($ip, $rule, $pos) === 0 + ) + ) { return true; } } diff --git a/tests/framework/filters/AccessRuleTest.php b/tests/framework/filters/AccessRuleTest.php index 57e67ac..b9a5680 100644 --- a/tests/framework/filters/AccessRuleTest.php +++ b/tests/framework/filters/AccessRuleTest.php @@ -292,6 +292,14 @@ class AccessRuleTest extends \yiiunit\TestCase $this->assertNull($rule->allows($action, $user, $request)); $rule->allow = false; $this->assertNull($rule->allows($action, $user, $request)); + + // undefined IP + $_SERVER['REMOTE_ADDR'] = null; + $rule->ips = ['192.168.*']; + $rule->allow = true; + $this->assertNull($rule->allows($action, $user, $request)); + $rule->allow = false; + $this->assertNull($rule->allows($action, $user, $request)); } public function testMatchIPWildcard() From a9bf1a3a7fa377457a4f09ab24562c9c1d8017b4 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 12:02:09 +0300 Subject: [PATCH 171/184] Removed redundant use --- tests/framework/filters/AccessRuleTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/framework/filters/AccessRuleTest.php b/tests/framework/filters/AccessRuleTest.php index b9a5680..8bcb21d 100644 --- a/tests/framework/filters/AccessRuleTest.php +++ b/tests/framework/filters/AccessRuleTest.php @@ -5,7 +5,6 @@ namespace yiiunit\framework\filters; use Yii; use yii\base\Action; use yii\filters\AccessRule; -use yii\filters\HttpCache; use yii\web\Controller; use yii\web\Request; use yii\web\User; @@ -21,8 +20,8 @@ class AccessRuleTest extends \yiiunit\TestCase { parent::setUp(); - $_SERVER['SCRIPT_FILENAME'] = "/index.php"; - $_SERVER['SCRIPT_NAME'] = "/index.php"; + $_SERVER['SCRIPT_FILENAME'] = '/index.php'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $this->mockWebApplication(); } @@ -42,7 +41,7 @@ class AccessRuleTest extends \yiiunit\TestCase } /** - * @param string optional user id + * @param string $userid optional user id * @return User */ protected function mockUser($userid = null) From 100213d9b2e2e028a08504d4c719c45c7a4e478b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 12:02:17 +0300 Subject: [PATCH 172/184] Removed redundant else --- framework/filters/AccessRule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/filters/AccessRule.php b/framework/filters/AccessRule.php index d827e63..0d859e9 100644 --- a/framework/filters/AccessRule.php +++ b/framework/filters/AccessRule.php @@ -115,9 +115,9 @@ class AccessRule extends Component && $this->matchCustom($action) ) { return $this->allow ? true : false; - } else { - return null; } + + return null; } /** From a3a123077f4feb3d8031a063fe7dba20d5434464 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 12:52:49 +0300 Subject: [PATCH 173/184] Fixes #13790: Fixed error in `\yii\widgets\MaskedInput` JavaScript by raising version required --- framework/CHANGELOG.md | 1 + framework/composer.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 03d6b0c..049ebf1 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #13790: Fixed error in `\yii\widgets\MaskedInput` JavaScript by raising version required (samdark) - Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya) - Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) - Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) diff --git a/framework/composer.json b/framework/composer.json index 2d00889..8bbb8c7 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -71,7 +71,7 @@ "ezyang/htmlpurifier": "~4.6", "cebe/markdown": "~1.0.0 | ~1.1.0", "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/jquery.inputmask": "~3.2.2 | ~3.3.5", "bower-asset/punycode": "1.3.*", "bower-asset/yii2-pjax": "~2.0.1" }, From 705dae996468985c8213cc6fe1f6b159d61fded5 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 12:56:54 +0300 Subject: [PATCH 174/184] Fixes #14052: Fixed processing parse errors on PHP 7 since these are instances of `\ParseError` --- framework/CHANGELOG.md | 1 + framework/web/ErrorHandler.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 049ebf1..4ff9044 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- +- Bug #14052: Fixed processing parse errors on PHP 7 since these are instances of `\ParseError` (samdark) - Bug #13790: Fixed error in `\yii\widgets\MaskedInput` JavaScript by raising version required (samdark) - Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya) - Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index a6c4e01..f01d6d0 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -306,11 +306,11 @@ class ErrorHandler extends \yii\base\ErrorHandler /** * Renders call stack. - * @param \Exception $exception exception to get call stack from + * @param \Exception|\ParseError $exception exception to get call stack from * @return string HTML content of the rendered call stack. * @since 2.0.12 */ - public function renderCallStack(\Exception $exception) + public function renderCallStack($exception) { $out = '
      '; $out .= $this->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1); From 375ea7a847271e7c61fdbc1d4991f0d13b551c42 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Apr 2017 13:32:58 +0300 Subject: [PATCH 175/184] Fixes #13951: renamed get-method which was conflicting with existing property (#14055) --- framework/rbac/BaseManager.php | 2 +- framework/rbac/DbManager.php | 2 +- framework/rbac/PhpManager.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/rbac/BaseManager.php b/framework/rbac/BaseManager.php index 879215f..27e9c87 100644 --- a/framework/rbac/BaseManager.php +++ b/framework/rbac/BaseManager.php @@ -194,7 +194,7 @@ abstract class BaseManager extends Component implements ManagerInterface * @since 2.0.12 * @return Role[] default roles. The array is indexed by the role names */ - public function getDefaultRoles() + public function getDefaultRoleInstances() { $result = []; foreach ($this->defaultRoles as $roleName) { diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 3f7becb..473cbb1 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -466,7 +466,7 @@ class DbManager extends BaseManager ->andWhere(['a.user_id' => (string) $userId]) ->andWhere(['b.type' => Item::TYPE_ROLE]); - $roles = $this->getDefaultRoles(); + $roles = $this->getDefaultRoleInstances(); foreach ($query->all($this->db) as $row) { $roles[$row['name']] = $this->populateItem($row); } diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index d489a75..26eccaa 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -392,7 +392,7 @@ class PhpManager extends BaseManager */ public function getRolesByUser($userId) { - $roles = $this->getDefaultRoles(); + $roles = $this->getDefaultRoleInstances(); foreach ($this->getAssignments($userId) as $name => $assignment) { $role = $this->items[$assignment->roleName]; if ($role->type === Item::TYPE_ROLE) { From d7fad8a5552fb1556795c70920c692e6a6ae3bcc Mon Sep 17 00:00:00 2001 From: Dmitry Eliseev Date: Wed, 26 Apr 2017 16:09:55 +0300 Subject: [PATCH 176/184] Fixes #14059: Removed unused AR instantiating for calling of static methods --- framework/CHANGELOG.md | 1 + framework/db/mssql/QueryBuilder.php | 5 ++--- framework/test/BaseActiveFixture.php | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4ff9044..38d992d 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -83,6 +83,7 @@ Yii Framework 2 Change Log - Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for inet_* methods to work (mikk150) - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) - Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) +- Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 6cccd40..f8cfa33 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -276,9 +276,8 @@ class QueryBuilder extends \yii\db\QueryBuilder if (!$modelClass) { return null; } - /* @var $model \yii\db\ActiveRecord */ - $model = new $modelClass; - $schema = $model->getTableSchema(); + /* @var $modelClass \yii\db\ActiveRecord */ + $schema = $modelClass::getTableSchema(); return array_keys($schema->columns); } diff --git a/framework/test/BaseActiveFixture.php b/framework/test/BaseActiveFixture.php index 7213876..e3b6092 100644 --- a/framework/test/BaseActiveFixture.php +++ b/framework/test/BaseActiveFixture.php @@ -65,10 +65,8 @@ abstract class BaseActiveFixture extends DbFixture implements \IteratorAggregate $row = $this->data[$name]; /* @var $modelClass \yii\db\ActiveRecord */ $modelClass = $this->modelClass; - /* @var $model \yii\db\ActiveRecord */ - $model = new $modelClass; $keys = []; - foreach ($model->primaryKey() as $key) { + foreach ($modelClass::primaryKey() as $key) { $keys[$key] = isset($row[$key]) ? $row[$key] : null; } From da6eada8c88addc3cb4723a1557cb76336eb3d64 Mon Sep 17 00:00:00 2001 From: Nikolay Oleynikov Date: Thu, 27 Apr 2017 17:46:46 +0300 Subject: [PATCH 177/184] Fix an invalid phpDocumentor annotation Fixed an invalid phpDocumentor annotation of `yii\data\DataProviderInterface::getPagination()`. --- framework/data/DataProviderInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/data/DataProviderInterface.php b/framework/data/DataProviderInterface.php index 1eed882..71ed1fe 100644 --- a/framework/data/DataProviderInterface.php +++ b/framework/data/DataProviderInterface.php @@ -66,7 +66,7 @@ interface DataProviderInterface public function getSort(); /** - * @return Pagination the pagination object. If this is false, it means the pagination is disabled. + * @return Pagination|false the pagination object. If this is false, it means the pagination is disabled. */ public function getPagination(); } From 96dae1cebc1835a712be572fff47505085d035ea Mon Sep 17 00:00:00 2001 From: lynicidn Date: Thu, 27 Apr 2017 20:51:56 +0300 Subject: [PATCH 178/184] file will not loaded https://github.com/yiisoft/yii2/tree/master/framework/test --- framework/console/controllers/FixtureController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php index f87c3d8..8934a2c 100644 --- a/framework/console/controllers/FixtureController.php +++ b/framework/console/controllers/FixtureController.php @@ -58,7 +58,7 @@ class FixtureController extends Controller * that disables and enables integrity check, so your data can be safely loaded. */ public $globalFixtures = [ - 'yii\test\InitDb', + 'yii\test\InitDbFixture', ]; From 4a170327176685b9f969572eff3a80be4fa3908b Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Thu, 27 Apr 2017 21:50:51 +0300 Subject: [PATCH 179/184] Fix validators documentation --- framework/validators/StringValidator.php | 3 +++ framework/validators/Validator.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php index 5a0fc3a..4d1b92c 100644 --- a/framework/validators/StringValidator.php +++ b/framework/validators/StringValidator.php @@ -159,6 +159,9 @@ class StringValidator extends Validator return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; } + /** + * @inheritdoc + */ public function getClientOptions($model, $attribute) { $label = $model->getAttributeLabel($attribute); diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index 2915d31..3fcc966 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -352,7 +352,7 @@ class Validator extends Component * @param string $attribute the name of the attribute to be validated. * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. - * @return string the client-side validation script. Null if the validator does not support + * @return string|null the client-side validation script. Null if the validator does not support * client-side validation. * @see getClientOptions() * @see \yii\widgets\ActiveForm::enableClientValidation From a480d7087546c085866e3076fdda59f91349b964 Mon Sep 17 00:00:00 2001 From: Oleg Poludnenko Date: Sun, 18 Dec 2016 18:26:31 +0200 Subject: [PATCH 180/184] More control over ActiveForm widget If part of a form is rendered separately (e.g. in ajax request), it's not easy to apply JS validation to it. By moving JS registration to a separate method this problem could be solved much more easier. --- framework/widgets/ActiveForm.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 47a6d6b..c71dc3d 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -191,7 +191,7 @@ class ActiveForm extends Widget /** * Runs the widget. - * This registers the necessary JavaScript code and renders the form close tag. + * This registers the necessary JavaScript code and renders the form open and close tags. * @throws InvalidCallException if `beginField()` and `endField()` calls are not matching. */ public function run() @@ -205,16 +205,24 @@ class ActiveForm extends Widget echo $content; if ($this->enableClientScript) { - $id = $this->options['id']; - $options = Json::htmlEncode($this->getClientOptions()); - $attributes = Json::htmlEncode($this->attributes); - $view = $this->getView(); - ActiveFormAsset::register($view); - $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + $this->registerJs(); } echo Html::endForm(); } + + /** + * This registers the necessary JavaScript code. + */ + public function registerJs() + { + $id = $this->options['id']; + $options = Json::htmlEncode($this->getClientOptions()); + $attributes = Json::htmlEncode($this->attributes); + $view = $this->getView(); + ActiveFormAsset::register($view); + $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + } /** * Returns the options for the form JS widget. From fce20a30204188282ec46d2b132928aacf52ed3b Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 27 Apr 2017 22:26:43 +0300 Subject: [PATCH 181/184] Renamed `ActiveForm::registerJs()` to `registerClientScript()`, added tests --- framework/CHANGELOG.md | 1 + framework/widgets/ActiveForm.php | 7 ++++--- tests/framework/widgets/ActiveFormTest.php | 24 +++++++++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 38d992d..b20d5e6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -84,6 +84,7 @@ Yii Framework 2 Change Log - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) - Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) - Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN) +- Enh #13240: Client scripts registration in `yii\widgets\ActiverForm` was moved to the separate `registerClientScript()` method (uaoleg, silverfire) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index c71dc3d..aeb937b 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -111,6 +111,7 @@ class ActiveForm extends Widget * This property must be set `true` if you want to support client validation and/or AJAX validation, or if you * want to take advantage of the `yii.activeForm` plugin. When this is `false`, the form will not generate * any JavaScript. + * @see registerClientScript */ public $enableClientScript = true; /** @@ -205,16 +206,16 @@ class ActiveForm extends Widget echo $content; if ($this->enableClientScript) { - $this->registerJs(); + $this->registerClientScript(); } echo Html::endForm(); } - + /** * This registers the necessary JavaScript code. */ - public function registerJs() + public function registerClientScript() { $id = $this->options['id']; $options = Json::htmlEncode($this->getClientOptions()); diff --git a/tests/framework/widgets/ActiveFormTest.php b/tests/framework/widgets/ActiveFormTest.php index 26e0ad5..ae4cc86 100644 --- a/tests/framework/widgets/ActiveFormTest.php +++ b/tests/framework/widgets/ActiveFormTest.php @@ -6,6 +6,7 @@ namespace yiiunit\framework\widgets; use yii\base\DynamicModel; +use yii\web\View; use yii\widgets\ActiveForm; /** @@ -89,7 +90,6 @@ EOF ActiveForm::end(); $content = ob_get_clean(); - //ob_end_clean(); $this->assertEquals($obLevel, ob_get_level(), 'Output buffers not closed correctly.'); @@ -106,4 +106,26 @@ HTML , $content); } + + public function testRegisterClientScript() + { + $this->mockWebApplication(); + $_SERVER['REQUEST_URI'] = 'http://example.com/'; + + $model = new DynamicModel(['name']); + $model->addRule(['name'], 'required'); + + $view = $this->getMock(View::className()); + $view->method('registerJs')->with($this->equalTo("jQuery('#w0').yiiActiveForm([], {\"validateOnSubmit\":false});")); + $view->method('registerAssetBundle')->willReturn(true); + + $form = ActiveForm::begin(['view' => $view, 'validateOnSubmit' => false]); + $form->field($model, 'name'); + $form::end(); + + // Disable clientScript will not call `View->registerJs()` + $form = ActiveForm::begin(['view' => $view, 'enableClientScript' => false]); + $form->field($model, 'name'); + $form::end(); + } } From 8349362271203b952c36c3b9d4991f75672b9471 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 27 Apr 2017 22:43:37 +0300 Subject: [PATCH 182/184] Fixed tests --- tests/framework/widgets/ActiveFormTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/framework/widgets/ActiveFormTest.php b/tests/framework/widgets/ActiveFormTest.php index ae4cc86..9496bca 100644 --- a/tests/framework/widgets/ActiveFormTest.php +++ b/tests/framework/widgets/ActiveFormTest.php @@ -6,6 +6,7 @@ namespace yiiunit\framework\widgets; use yii\base\DynamicModel; +use yii\base\Widget; use yii\web\View; use yii\widgets\ActiveForm; @@ -116,9 +117,13 @@ HTML $model->addRule(['name'], 'required'); $view = $this->getMock(View::className()); - $view->method('registerJs')->with($this->equalTo("jQuery('#w0').yiiActiveForm([], {\"validateOnSubmit\":false});")); + $view->method('registerJs')->with($this->matches("jQuery('#w0').yiiActiveForm([], {\"validateOnSubmit\":false});")); $view->method('registerAssetBundle')->willReturn(true); + Widget::$counter = 0; + ob_start(); + ob_implicit_flush(false); + $form = ActiveForm::begin(['view' => $view, 'validateOnSubmit' => false]); $form->field($model, 'name'); $form::end(); @@ -127,5 +132,6 @@ HTML $form = ActiveForm::begin(['view' => $view, 'enableClientScript' => false]); $form->field($model, 'name'); $form::end(); + ob_get_clean(); } } From e8e48e4820c548b18070be1e15b8109a4de66a3a Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 27 Apr 2017 23:02:34 +0300 Subject: [PATCH 183/184] Fixed CHANGELOG --- framework/CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b20d5e6..4650455 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -15,7 +15,7 @@ Yii Framework 2 Change Log - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) -- Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) +- Enh #13963: Added tests for `yii\behaviors\TimestampBehavior` (vladis84) - Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) @@ -27,16 +27,16 @@ Yii Framework 2 Change Log - Enh #13243: Added support for unicode attribute names in `yii\widgets\DetailView` (arogachev) - Bug #11404: `yii\base\Model::loadMultiple()` returns true even if `yii\base\Model::load()` returns false (zvook) - Bug #13306: Wildcard in `reloadableScripts` in `yii.js` allows 0 characters (arogachev) -- Bug #13340: Fixed `yii\db\Connection::useMaster()` - Exception within callback completely disables slaves (Vovan-VE) +- Bug #13340: Fixed `yii\db\Connection::useMaster()` - exception within callback completely disables slaves (Vovan-VE) - Bug #13343: Fixed `yii\i18n\Formatter::asTime()` to process time-only values without time zone conversion (bizley) -- Bug #13418: Fixed `QueryBuilder::batchInsert()` if $rows is `\Generator` (lav45) +- Bug #13418: Fixed `QueryBuilder::batchInsert()` if `$rows` is `\Generator` (lav45) - Bug #13494: Fixed `yii\console\controllers\MessageConstroller::saveMessagesToDb()` to work on different DBMS correctly (silverfire) - Bug #13513: Fixed RBAC migration to work correctly on Oracle DBMS (silverfire) - Bug #13537: Fixed `yii\web\CacheSession::destroySession()` to work correctly when session is not written yet (silverfire, papalapa) - Bug #13538: Fixed `yii\db\BaseActiveRecord::deleteAll()` changes method signature declared by `yii\db\ActiveRecordInterface::deleteAll()` (klimov-paul) - Bug #13577: `yii\db\QueryBuilder::truncateTable` should work consistent over all databases (boboldehampsink) - Bug #13582: PK column in `yii\db\pgsql\QueryBuilder::resetSequence()` was not quoted properly (boboldehampsink) -- Bug #13592: Fixes Oracle’s `yii\db\oci\Schema::setTransactionIsolationLevel()` (sergeymakinen) +- Bug #13592: Fixes `yii\db\oci\Schema::setTransactionIsolationLevel()` in Oracle (sergeymakinen) - Bug #13594: Fixes insufficient quoting in `yii\db\QueryBuilder::prepareInsertSelectSubQuery()` (sergeymakinen) - Bug #8120: Fixes LIKE special characters escaping for Cubrid/MSSQL/Oracle/SQLite in `yii\db\QueryBuilder` (sergeymakinen) - Bug #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception (Vovan-VE) @@ -44,25 +44,25 @@ Yii Framework 2 Change Log - Enh #13179: Added `yii\data\Sort::parseSortParam` allowing to customize sort param in descendant class (leandrogehlen) - Enh #13278: `yii\caching\DbQueryDependency` created allowing specification of the cache dependency via `yii\db\QueryInterface` (klimov-paul) - Enh #13467: `yii\data\ActiveDataProvider` no longer queries models if models count is zero (kLkA, Kolyunya) -- Enh #13523: Plural rule for pasta (developeruz) -- Enh #13550: Refactored unset call order in `yii\di\ServiceLocator::set()` (Lanrik) +- Enh #13523: Fixed pluralization and singularization for words `pasta`, `currency` (developeruz, silverfire) +- Enh #13550: Refactored `unset()` call order in `yii\di\ServiceLocator::set()` (Lanrik) - Enh #13576: Added support of `srcset` to `yii\helpers\Html::img()` (Kolyunya) - Enh #13577: Implemented `yii\db\mssql\QueryBuilder::resetSequence()` (boboldehampsink) -- Enh #13582: Added tests for all `yii\db\QueryBuilder::resetSequence` implementations, fixed SQLite implementation (boboldehampsink) +- Enh #13582: Added tests for all `yii\db\QueryBuilder::resetSequence()` implementations, fixed SQLite implementation (boboldehampsink) - Enh #13407: Added URL-safe base64 encode/decode methods to `StringHelper` (andrewnester) - Bug #13649: Fixes issue where `['uncheck' => false]` and `['label' => false]` options for `ActiveRadio` and `ActiveCheckbox` were ignored (Alex-Code) - Enh #13221: Make `\yii\db\QueryTrait::limit()` and `\yii\db\QueryTrait::offset()` methods work with `\yii\db\Expression` (Ni-san) - Enh #13144: Refactored `yii\db\Query::queryScalar()` (Alex-Code) - Enh #13360: Added Dockerized test setup for the framework tests (schmunk42) -- Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev) +- Bug #13379: Fixed `applyFilter()` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev) - Enh #13650: Improved `yii\base\Security::hkdf()` to take advantage of native `hash_hkdf()` implementation in PHP >= 7.1.2 (charlesportwoodii) - Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) -- Enh #13695: `\yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) +- Enh #13695: `yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) - Bug #13728: Fixed the bug when `yii\behaviors\SluggableBehavior` wasn't preserving immutable slug values (Kolyunya) -- Bug #13707: Fixed `\yii\web\ErrorHandler` and `\yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) +- Bug #13707: Fixed `yii\web\ErrorHandler` and `yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) -- Enh #13254: Core validators no longer require Yii::$app to be set (sammousa) +- Enh #13254: Core validators no longer require `Yii::$app` to be set (sammousa) - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) @@ -72,15 +72,15 @@ Yii Framework 2 Change Log - Enh #13823: Refactored migrations template (Kolyunya) - Bug #13807: Fixed `yii\db\QueryBuilder` to inherit subquery params when building a `INSERT INTO ... SELECT` query (sergeymakinen) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) -- Enh #13883: `\yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) +- Enh #13883: `yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) - Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) - Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) - Enh #13837: Refactored masking of CSRF tokens (sammousa) - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) -- Bug #13961: RBAC Rules: PostgreSQL: PHP Warning "unserialize() expects parameter 1 to be string, resource given" was fixed (vsguts) -- Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for inet_* methods to work (mikk150) +- Bug #13961: Fixed `unserialize()` error during RBAC rule retrieving from PostgreSQL DBMS (vsguts) +- Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for `inet_*` methods to work (mikk150) - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) - Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) - Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN) From 6c03216aab8c6e30ddbcf46b96d8dd62c385665c Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 27 Apr 2017 23:02:53 +0300 Subject: [PATCH 184/184] Reordered CHANGELOG --- framework/CHANGELOG.md | 93 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4650455..9a4830b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,87 +4,88 @@ Yii Framework 2 Change Log 2.0.12 under development -------------------------- -- Bug #14052: Fixed processing parse errors on PHP 7 since these are instances of `\ParseError` (samdark) -- Bug #13790: Fixed error in `\yii\widgets\MaskedInput` JavaScript by raising version required (samdark) -- Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya) -- Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) -- Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) -- Bug #13689: Fixed handling of errors in closures (mikehaertl) -- Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) -- Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) -- Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) -- Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) -- Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) -- Enh #13963: Added tests for `yii\behaviors\TimestampBehavior` (vladis84) -- Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) -- Enh #13820: Add new HTTP status code 451 (yyxx9988) -- Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) -- Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) +- Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #7946: Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya) -- Bug #13087: Fixed getting active validators for safe attribute (developeruz, klimov-paul) -- Bug #13571: Fix `yii\db\mssql\QueryBuilder::checkIntegrity` for all tables (boboldehampsink) +- Bug #8120: Fixes LIKE special characters escaping for Cubrid/MSSQL/Oracle/SQLite in `yii\db\QueryBuilder` (sergeymakinen) +- Bug #10346: Fixed "DOMException: Invalid Character Error" in `yii\web\XmlResponseFormatter::buildXml()` (sasha-ch) +- Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) - Bug #11230: Include `defaultRoles` in `yii\rbac\DbManager->getRolesByUser()` results (developeruz) -- Enh #13243: Added support for unicode attribute names in `yii\widgets\DetailView` (arogachev) - Bug #11404: `yii\base\Model::loadMultiple()` returns true even if `yii\base\Model::load()` returns false (zvook) +- Bug #11719: Fixed `yii\db\Connection::$enableQueryCache` caused infinite loop when the same connection was used for `yii\caching\DbCache` (michaelarnauts) +- Bug #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception (Vovan-VE) +- Bug #13087: Fixed getting active validators for safe attribute (developeruz, klimov-paul) - Bug #13306: Wildcard in `reloadableScripts` in `yii.js` allows 0 characters (arogachev) - Bug #13340: Fixed `yii\db\Connection::useMaster()` - exception within callback completely disables slaves (Vovan-VE) - Bug #13343: Fixed `yii\i18n\Formatter::asTime()` to process time-only values without time zone conversion (bizley) +- Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) +- Bug #13379: Fixed `applyFilter()` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev) - Bug #13418: Fixed `QueryBuilder::batchInsert()` if `$rows` is `\Generator` (lav45) - Bug #13494: Fixed `yii\console\controllers\MessageConstroller::saveMessagesToDb()` to work on different DBMS correctly (silverfire) - Bug #13513: Fixed RBAC migration to work correctly on Oracle DBMS (silverfire) - Bug #13537: Fixed `yii\web\CacheSession::destroySession()` to work correctly when session is not written yet (silverfire, papalapa) - Bug #13538: Fixed `yii\db\BaseActiveRecord::deleteAll()` changes method signature declared by `yii\db\ActiveRecordInterface::deleteAll()` (klimov-paul) +- Bug #13571: Fix `yii\db\mssql\QueryBuilder::checkIntegrity` for all tables (boboldehampsink) - Bug #13577: `yii\db\QueryBuilder::truncateTable` should work consistent over all databases (boboldehampsink) - Bug #13582: PK column in `yii\db\pgsql\QueryBuilder::resetSequence()` was not quoted properly (boboldehampsink) - Bug #13592: Fixes `yii\db\oci\Schema::setTransactionIsolationLevel()` in Oracle (sergeymakinen) - Bug #13594: Fixes insufficient quoting in `yii\db\QueryBuilder::prepareInsertSelectSubQuery()` (sergeymakinen) -- Bug #8120: Fixes LIKE special characters escaping for Cubrid/MSSQL/Oracle/SQLite in `yii\db\QueryBuilder` (sergeymakinen) -- Bug #12715: Exception `SAVEPOINT LEVEL1 does not exist` instead of deadlock exception (Vovan-VE) +- Bug #13649: Fixes issue where `['uncheck' => false]` and `['label' => false]` options for `ActiveRadio` and `ActiveCheckbox` were ignored (Alex-Code) +- Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) +- Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) +- Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) +- Bug #13689: Fixed handling of errors in closures (mikehaertl) +- Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) +- Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) +- Bug #13707: Fixed `yii\web\ErrorHandler` and `yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) +- Bug #13728: Fixed the bug when `yii\behaviors\SluggableBehavior` wasn't preserving immutable slug values (Kolyunya) +- Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) +- Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) +- Bug #13790: Fixed error in `\yii\widgets\MaskedInput` JavaScript by raising version required (samdark) +- Bug #13807: Fixed `yii\db\QueryBuilder` to inherit subquery params when building a `INSERT INTO ... SELECT` query (sergeymakinen) +- Bug #13848: `yii\di\Instance::ensure()` wasn't throwing an exception when `$type` is specified and `$reference` object isn't instance of `$type` (c-jonua) +- Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) +- Bug #13961: Fixed `unserialize()` error during RBAC rule retrieving from PostgreSQL DBMS (vsguts) +- Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) +- Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya) +- Bug #14052: Fixed processing parse errors on PHP 7 since these are instances of `\ParseError` (samdark) - Enh #8641: Enhanced `yii\console\Request::resolve()` to prevent passing parameters, that begin from digits (silverfire) +- Enh #13144: Refactored `yii\db\Query::queryScalar()` (Alex-Code) - Enh #13179: Added `yii\data\Sort::parseSortParam` allowing to customize sort param in descendant class (leandrogehlen) +- Enh #13221: Make `\yii\db\QueryTrait::limit()` and `\yii\db\QueryTrait::offset()` methods work with `\yii\db\Expression` (Ni-san) +- Enh #13226: `yii cache` command now warns about the fact that it's not able to flush APC cache from console (samdark) +- Enh #13240: Client scripts registration in `yii\widgets\ActiverForm` was moved to the separate `registerClientScript()` method (uaoleg, silverfire) +- Enh #13243: Added support for unicode attribute names in `yii\widgets\DetailView` (arogachev) +- Enh #13254: Core validators no longer require `Yii::$app` to be set (sammousa) - Enh #13278: `yii\caching\DbQueryDependency` created allowing specification of the cache dependency via `yii\db\QueryInterface` (klimov-paul) +- Enh #13360: Added Dockerized test setup for the framework tests (schmunk42) +- Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) +- Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) +- Enh #13407: Added URL-safe base64 encode/decode methods to `StringHelper` (andrewnester) - Enh #13467: `yii\data\ActiveDataProvider` no longer queries models if models count is zero (kLkA, Kolyunya) - Enh #13523: Fixed pluralization and singularization for words `pasta`, `currency` (developeruz, silverfire) - Enh #13550: Refactored `unset()` call order in `yii\di\ServiceLocator::set()` (Lanrik) +- Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) - Enh #13576: Added support of `srcset` to `yii\helpers\Html::img()` (Kolyunya) - Enh #13577: Implemented `yii\db\mssql\QueryBuilder::resetSequence()` (boboldehampsink) - Enh #13582: Added tests for all `yii\db\QueryBuilder::resetSequence()` implementations, fixed SQLite implementation (boboldehampsink) -- Enh #13407: Added URL-safe base64 encode/decode methods to `StringHelper` (andrewnester) -- Bug #13649: Fixes issue where `['uncheck' => false]` and `['label' => false]` options for `ActiveRadio` and `ActiveCheckbox` were ignored (Alex-Code) -- Enh #13221: Make `\yii\db\QueryTrait::limit()` and `\yii\db\QueryTrait::offset()` methods work with `\yii\db\Expression` (Ni-san) -- Enh #13144: Refactored `yii\db\Query::queryScalar()` (Alex-Code) -- Enh #13360: Added Dockerized test setup for the framework tests (schmunk42) -- Bug #13379: Fixed `applyFilter()` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev) - Enh #13650: Improved `yii\base\Security::hkdf()` to take advantage of native `hash_hkdf()` implementation in PHP >= 7.1.2 (charlesportwoodii) -- Bug #13670: Fixed alias option from console when it includes `-` or `_` in option name (pana1990) -- Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Enh #13695: `yii\web\Response::setStatusCode()` method now returns the Response object itself (kyle-mccarthy) -- Bug #13728: Fixed the bug when `yii\behaviors\SluggableBehavior` wasn't preserving immutable slug values (Kolyunya) -- Bug #13707: Fixed `yii\web\ErrorHandler` and `yii\web\ErrorAction` not setting correct response code to response object before rendering error view (samdark) - Enh #13698: `yii\grid\DataColumn` filter is automatically generated as dropdown list in case of `format` set to `boolean` (bizley) -- Enh #13254: Core validators no longer require `Yii::$app` to be set (sammousa) -- Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) -- Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) -- Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) -- Bug #13776: Fixed setting precision and scale for decimal columns in MSSQL (arturf) -- Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) - Enh #13770: Added support for `yii\widgets\Menu` item classes definition in the form of an array (Kolyunya) +- Enh #13820: Add new HTTP status code 451 (yyxx9988) - Enh #13823: Refactored migrations template (Kolyunya) -- Bug #13807: Fixed `yii\db\QueryBuilder` to inherit subquery params when building a `INSERT INTO ... SELECT` query (sergeymakinen) +- Enh #13837: Refactored masking of CSRF tokens (sammousa) - Enh #13845: `mt_rand()` is used instead of `rand()` in `yii\captcha\CaptchaAction` (kalessil) - Enh #13883: `yii\data\SqlDataProvider` now provides automatic fallback for the case when `totalCount` is not specified (SamMousa) -- Enh #13376: Data provider now automatically sets an ID so there is no need to set it manually in case multiple data providers are used with pagination (SamMousa) -- Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) -- Enh #13837: Refactored masking of CSRF tokens (sammousa) -- Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) -- Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) +- Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) - Enh #13945: Removed Courier New from error page fonts list since it looks bad on Linux (samdark) -- Bug #13961: Fixed `unserialize()` error during RBAC rule retrieving from PostgreSQL DBMS (vsguts) +- Enh #13963: Added tests for `yii\behaviors\TimestampBehavior` (vladis84) - Enh #13976: Disabled IPv6 check on `\yii\validators\IpValidator` as it turns out it is not needed for `inet_*` methods to work (mikk150) - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) -- Enh #13911: Significantly enhanced MSSQL schema reading performance (paulzi, WebdevMerlion) +- Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) - Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN) -- Enh #13240: Client scripts registration in `yii\widgets\ActiverForm` was moved to the separate `registerClientScript()` method (uaoleg, silverfire) +- Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) + 2.0.11.2 February 08, 2017 --------------------------

    !cSFfqK)*8Voq4_Ac32l+5z?eVQ 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 literal 93741 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Μ43NT`J&jg!DG&uwMSYbZJS*ZuF91m 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 YK(3FvTLYf{Mf#I}uJQ~f{qoI!1B3wLKmY&$ From 4eed7fb3da029aaba1dc4082f5cc9e5b92dde14f Mon Sep 17 00:00:00 2001 From: Kolyunya Date: Sun, 2 Apr 2017 09:17:50 +0300 Subject: [PATCH 092/184] Fixes #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` --- framework/CHANGELOG.md | 1 + framework/validators/IpValidator.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6e944b6..64c699b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -61,6 +61,7 @@ Yii Framework 2 Change Log - Enh #13369: Added ability to render current `yii\widgets\LinkPager` page disabled (aquy) - Enh #13837: Refactored masking of CSRF tokens (sammousa) - Enh #13560: Refactored `\yii\widgets\FragmentCache::getCachedContent()`, added tests (Kolyunya) +- Bug #13901: Fixed passing unused parameter to `formatMessage()` call in `\yii\validators\IpValidator` (Kolyunya) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php index b257695..9b09cce 100644 --- a/framework/validators/IpValidator.php +++ b/framework/validators/IpValidator.php @@ -608,7 +608,7 @@ class IpValidator extends Validator foreach ($messages as &$message) { $message = $this->formatMessage($message, [ 'attribute' => $model->getAttributeLabel($attribute), - ], Yii::$app->language); + ]); } $options = [ From 9c8b2891165b4ca1eb383de8276b6707731d8681 Mon Sep 17 00:00:00 2001 From: Roman Grinyov Date: Sun, 2 Apr 2017 22:22:10 +0300 Subject: [PATCH 093/184] Incorrect use case of Datepicker widget (#13903) [skip ci] --- docs/guide/structure-widgets.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/guide/structure-widgets.md b/docs/guide/structure-widgets.md index 55ddb1f..9ba20bb 100644 --- a/docs/guide/structure-widgets.md +++ b/docs/guide/structure-widgets.md @@ -34,9 +34,7 @@ use yii\jui\DatePicker; 'model' => $model, 'attribute' => 'from_date', 'language' => 'ru', - 'clientOptions' => [ - 'dateFormat' => 'yy-mm-dd', - ], + 'dateFormat' => 'php:Y-m-d', ]) ?> ``` From 726d4d1b6b1fe6eed6b272d665a2a217670acdfd Mon Sep 17 00:00:00 2001 From: Bizley Date: Sun, 2 Apr 2017 21:54:57 +0200 Subject: [PATCH 094/184] Code style headers fix (#13908) [skip ci] --- docs/internals-ja/core-code-style.md | 12 ++++-------- docs/internals-pl/core-code-style.md | 12 ++++-------- docs/internals-ru/core-code-style.md | 12 ++++-------- docs/internals-uk/core-code-style.md | 12 ++++-------- docs/internals/core-code-style.md | 12 ++++-------- 5 files changed, 20 insertions(+), 40 deletions(-) diff --git a/docs/internals-ja/core-code-style.md b/docs/internals-ja/core-code-style.md index dae1952..54414dc 100644 --- a/docs/internals-ja/core-code-style.md +++ b/docs/internals-ja/core-code-style.md @@ -12,8 +12,7 @@ Yii 2 コアフレームワークコードスタイル しかし、コアコードや公式エクステンションに対して実際に寄稿する場合には、それらを英語で書く必要があります。 -1. 概要 -------- +## 1. 概要 全体として、私たちは [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 互換のスタイルを使っていますので、 [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) に適用されることは、すべて私たちのコードスタイルにも適用されます。 @@ -29,8 +28,7 @@ Yii 2 コアフレームワークコードスタイル - プロパティ名は private である場合はアンダースコアで始まらなければならない。 - `else if` ではなく常に `elseif` を使用すること。 -2. ファイル ------------ +## 2. ファイル ### 2.1. PHP タグ @@ -43,13 +41,11 @@ Yii 2 コアフレームワークコードスタイル PHP コードは BOM 無しの UTF-8 のみを使わなければなりません。 -3. クラス名 ------------ +## 3. クラス名 クラス名は `StudlyCaps` で宣言されなければなりません。例えば、`Controller`、`Model`。 -4. クラス ---------- +## 4. クラス ここで "クラス" という用語はあらゆるクラスとインタフェイスを指すものとします。 diff --git a/docs/internals-pl/core-code-style.md b/docs/internals-pl/core-code-style.md index a4d6040..fee890d 100644 --- a/docs/internals-pl/core-code-style.md +++ b/docs/internals-pl/core-code-style.md @@ -7,8 +7,7 @@ stosowania go we własnych aplikacjach. Wybierz styl, który najbardziej odpowia Możesz pobrać gotową konfigurację dla CodeSniffera pod adresem: https://github.com/yiisoft/yii2-coding-standards -1. Omówienie ------------- +## 1. Omówienie Używamy przede wszystkim standardu kodowania [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md), zatem wszystko, co dotyczy @@ -26,8 +25,7 @@ kodowania. - Nazwy właściwości klasy MUSZĄ zaczynać się podkreślnikiem, jeśli są prywatne. - Należy używać `elseif` zamiast `else if`. -2. Pliki --------- +## 2. Pliki ### 2.1. Tagi PHP @@ -40,13 +38,11 @@ kodowania. Kod PHP MUSI używać wyłącznie UTF-8 bez znacznika BOM. -3. Nazwy klas -------------- +## 3. Nazwy klas Nazwy klas MUSZĄ być zadeklarowane w formacie `StudlyCaps`. Przykładowo `Controller`, `Model`. -4. Klasy --------- +## 4. Klasy Termin "klasa" odnosi się tutaj do wszystkich klas i interfejsów. diff --git a/docs/internals-ru/core-code-style.md b/docs/internals-ru/core-code-style.md index d405287..fb4728e 100644 --- a/docs/internals-ru/core-code-style.md +++ b/docs/internals-ru/core-code-style.md @@ -5,8 +5,7 @@ Пример конфигурационного файла для CodeSniffer вы можете найти здесь: https://github.com/yiisoft/yii2-coding-standards -1. Обзор ------------ +## 1. Обзор В общем, мы используем совместимый со стандартом [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) стиль, так что все положения [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) вполне применимы к нашему стилю кодирования. @@ -21,8 +20,7 @@ - Имена свойств ДОЛЖНЫ начинаться с подчеркивания если они объявлены с использованием модификатора `private`; - Всегда используйте `elseif` вместо `else if`. -2. Файлы --------- +## 2. Файлы ### 2.1. Теги PHP @@ -35,13 +33,11 @@ PHP код должен содержать только символы в кодировке UTF-8 без BOM. -3. Имена Классов --------------- +## 3. Имена Классов Имена классов ДОЛЖНЫ быть определены используя `StudlyCaps`. Например, `Controller`, `Model`. -4. Классы ----------- +## 4. Классы В данном случае, под классом подразумеваются все классы и интерфейсы. diff --git a/docs/internals-uk/core-code-style.md b/docs/internals-uk/core-code-style.md index 6899339..61d2f77 100644 --- a/docs/internals-uk/core-code-style.md +++ b/docs/internals-uk/core-code-style.md @@ -7,8 +7,7 @@ Ви можете отримати конфігурацію для CodeSniffer за посиланням: https://github.com/yiisoft/yii2-coding-standards -1. Загальні положення ---------------------- +## 1. Загальні положення В основному командою розробників використовується стиль сумісний з [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md), тому все, що стосується @@ -25,8 +24,7 @@ - Імена приватних властивостей ПОВИННІ починатись з підкреслення. - Завжди використовуйте `elseif` замість `else if`. -2. Файли --------- +## 2. Файли ### 2.1. Теги PHP @@ -39,13 +37,11 @@ PHP код ПОВИНЕН використовувати лише UTF-8 без BOM. -3. Імена класів ---------------- +## 3. Імена класів Імена класів ПОВИННІ оголошуватись як `StudlyCaps`. Наприклад: `Controller`, `Model`. -4. Класи --------- +## 4. Класи Тут термін "клас" відноситься до всіх класів та інтерфейсів. diff --git a/docs/internals/core-code-style.md b/docs/internals/core-code-style.md index 3bd647e..019dc0d 100644 --- a/docs/internals/core-code-style.md +++ b/docs/internals/core-code-style.md @@ -7,8 +7,7 @@ what suits you better. You can get a config for CodeSniffer here: https://github.com/yiisoft/yii2-coding-standards -1. Overview ------------ +## 1. Overview Overall we're using [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) compatible style so everything that applies to @@ -26,8 +25,7 @@ style as well. - Property names MUST start with an initial underscore if they are private. - Always use `elseif` instead of `else if`. -2. Files --------- +## 2. Files ### 2.1. PHP Tags @@ -40,13 +38,11 @@ style as well. PHP code MUST use only UTF-8 without BOM. -3. Class Names --------------- +## 3. Class Names Class names MUST be declared in `StudlyCaps`. For example, `Controller`, `Model`. -4. Classes ----------- +## 4. Classes The term "class" refers to all classes and interfaces here. From 843428561109820cd6ecdadeb4eea6dfccc78224 Mon Sep 17 00:00:00 2001 From: Evgeniy Moiseenko Date: Mon, 3 Apr 2017 13:15:48 +0300 Subject: [PATCH 095/184] Fixed russian translate --- docs/guide-ru/rest-rate-limiting.md | 2 +- docs/guide-ru/rest-resources.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide-ru/rest-rate-limiting.md b/docs/guide-ru/rest-rate-limiting.md index f09c902..3a77c27 100644 --- a/docs/guide-ru/rest-rate-limiting.md +++ b/docs/guide-ru/rest-rate-limiting.md @@ -2,7 +2,7 @@ =============================== Чтобы избежать злоупотреблений, вам следует подумать о добавлении ограничения частоты запросов к вашим API. Например, -вы можете ограничить использование API 100 вызовов API в течение 10 минут для каждого пользователя. Если от пользователя +вы можете ограничить использование API до 100 вызовов в течение 10 минут для каждого пользователя. Если от пользователя в течение этого периода времени приходит большее количество запросов, будет возвращаться ответ с кодом состояния 429 («слишком много запросов»). diff --git a/docs/guide-ru/rest-resources.md b/docs/guide-ru/rest-resources.md index 99a1c20..ee148e4 100644 --- a/docs/guide-ru/rest-resources.md +++ b/docs/guide-ru/rest-resources.md @@ -5,7 +5,7 @@ RESTful API строятся вокруг доступа к *ресурсам* о [моделях](structure-models.md) из [MVC](http://ru.wikipedia.org/wiki/Model-View-Controller). Хотя не существует никаких ограничений на то, как представить ресурс, в Yii ресурсы обычно представляются -как объекты [[yii\base\Model]] или дочерних классов (например [[yii\db\ActiveRecord]]), потому как: +как объекты [[yii\base\Model]] или дочерние классы (например [[yii\db\ActiveRecord]]), потому как: * [[yii\base\Model]] реализует интерфейс [[yii\base\Arrayable]], который позволяет задать способ отдачи данных ресурса через RESTful API. From 6301daecf787dce70219a7491cda88fa76fecad1 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Apr 2017 14:26:41 +0200 Subject: [PATCH 096/184] reverted wrong phpdoc change from #13905 --- framework/base/Component.php | 2 +- framework/base/Module.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/base/Component.php b/framework/base/Component.php index ee17b7e..2188084 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -566,7 +566,7 @@ class Component extends Object /** * Returns all behaviors attached to this component. - * @return Behavior[]|null list of behaviors attached to this component + * @return Behavior[] list of behaviors attached to this component */ public function getBehaviors() { diff --git a/framework/base/Module.php b/framework/base/Module.php index e310eec..e631d87 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -305,7 +305,7 @@ class Module extends ServiceLocator /** * Returns current module version. * If version is not explicitly set, [[defaultVersion()]] method will be used to determine its value. - * @return string|callable the version of this module. + * @return string the version of this module. * @since 2.0.11 */ public function getVersion() @@ -342,7 +342,7 @@ class Module extends ServiceLocator /** * Returns default module version. * Child class may override this method to provide more specific version detection. - * @return string|callable the version of this module. + * @return string the version of this module. * @since 2.0.11 */ protected function defaultVersion() From d370b10cb99b5b6104ff4520ebeb5eca04d8a1fd Mon Sep 17 00:00:00 2001 From: Bizley Date: Tue, 4 Apr 2017 00:06:44 +0200 Subject: [PATCH 097/184] Guide PL updates (#13914) [skip ci] --- docs/guide-pl/input-forms.md | 81 ++++++++++++++++--- docs/guide-pl/input-validation.md | 144 ++++++++++++++++++++++++++++++++- docs/guide-pl/intro-upgrade-from-v1.md | 3 +- docs/guide-pl/intro-yii.md | 8 +- 4 files changed, 215 insertions(+), 21 deletions(-) diff --git a/docs/guide-pl/input-forms.md b/docs/guide-pl/input-forms.md index 7ba7bc3..4120f61 100644 --- a/docs/guide-pl/input-forms.md +++ b/docs/guide-pl/input-forms.md @@ -1,6 +1,8 @@ Tworzenie formularzy -============== +==================== +Formularze oparte na ActiveRecord: ActiveForm +--------------------------------------------- Podstawowym sposobem korzystania z formularzy w Yii jest użycie [[yii\widgets\ActiveForm|ActiveForm]]. Ten sposób powinien być używany, jeśli formularz jest bazowany na modelu. Dodatkowo, klasa [[yii\helpers\Html|Html]] zawiera sporo użytecznych metod, które zazwyczaj używane są do dodawania przycisków i tekstów pomocniczych do każdego formularza. @@ -9,7 +11,11 @@ stronie serwera (sprawdź sekcję [Walidacja danych wejściowych](input-validati Podczas tworzenia formularza na podstawie modelu, pierwszym krokiem jest zdefiniowanie samego modelu. Model może być bazowany na klasie [Active Record](db-active-record.md), reprezentując dane z bazy danych, lub może być też bazowany na klasie generycznej [[yii\base\Model|Model]], aby przechwytywać dowolne dane wejściowe, np. formularz logowania. -W poniższym przykładzie pokażemy, jak model generyczny może być użyty do formularza logowania: + +> Tip: Jeśli pola formularza są różne od kolumn tabeli w bazie danych lub też występuje tu formatowanie i logika specyficzna tylko dla tego formularza, +> zaleca się stworzenie oddzielnego modelu rozszerzającego [[yii\base\Model]]. + +W poniższym przykładzie pokażemy, jak model generyczny może być użyty do stworzenia formularza logowania: ```php ``` +### Otaczanie kodu przez `begin()` i `end()` W powyższym kodzie, [[yii\widgets\ActiveForm::begin()|begin()]] nie tylko tworzy instancję formularza, ale zaznacza też jego początek. Cała zawartość położona pomiędzy [[yii\widgets\ActiveForm::begin()|begin()]] i [[yii\widgets\ActiveForm::end()|end()]] zostanie otoczona tagiem HTML'owym `