mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 14:26:54 +08:00
Merge branch 'master' into feature/pcntl-req-check
This commit is contained in:
147
.github/workflows/build.yml
vendored
147
.github/workflows/build.yml
vendored
@ -4,95 +4,104 @@ on: [push, pull_request]
|
||||
|
||||
env:
|
||||
DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi"
|
||||
PHPUNIT_EXCLUDE_GROUP: mssql,oci,wincache,xcache,zenddata,cubrid
|
||||
PHPUNIT_EXCLUDE_GROUP: db,wincache,xcache,zenddata
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
phpunit:
|
||||
name: PHP ${{ matrix.php }} on ${{ matrix.os }}
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: yiitest
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
postgres:
|
||||
image: postgres:9.6
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: yiitest
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --name=postgres --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [5.4, 5.5, 5.6, 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1]
|
||||
include:
|
||||
- php: 5.4
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 5.5
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 5.6
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 7.0
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 7.1
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 7.2
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 7.3
|
||||
coverage: none
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 7.4
|
||||
coverage: xdebug
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 8.0
|
||||
coverage: none
|
||||
extensions: apcu, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 8.1
|
||||
coverage: none
|
||||
extensions: apcu, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
os: ubuntu-latest
|
||||
- php: 8.2
|
||||
extensions: apcu, curl, dom, imagick, intl, mbstring, mcrypt, memcached
|
||||
coverage: none
|
||||
os: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Generate french locale
|
||||
- name: Generate french locale.
|
||||
run: sudo locale-gen fr_FR.UTF-8
|
||||
- name: Checkout
|
||||
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
- name: Install PHP
|
||||
|
||||
- name: Install PHP.
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: ${{ matrix.coverage }}
|
||||
extensions: ${{ matrix.extensions }}
|
||||
ini-values: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC', session.save_path="${{ runner.temp }}"
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: pecl
|
||||
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached, mysql, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, pgsql, sqlite
|
||||
ini-values: date.timezone='UTC', session.save_path="${{ runner.temp }}"
|
||||
- name: Install Memcached
|
||||
|
||||
- name: Install Memcached.
|
||||
uses: niden/actions-memcached@v7
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
|
||||
- name: Install dependencies.
|
||||
run: composer update $DEFAULT_COMPOSER_FLAGS
|
||||
- name: PHP Unit tests
|
||||
|
||||
- name: Run tests with PHPUnit.
|
||||
if: matrix.php < '7.4' || matrix.php >= '8.1'
|
||||
run: vendor/bin/phpunit --verbose --exclude-group $PHPUNIT_EXCLUDE_GROUP --colors=always
|
||||
|
||||
npm:
|
||||
name: NPM 6 on ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
- name: Run tests with PHPUnit.
|
||||
if: matrix.php == '8.0'
|
||||
run: vendor/bin/phpunit --verbose --exclude-group $PHPUNIT_EXCLUDE_GROUP --colors=always
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- name: Run tests with PHPUnit and generate coverage.
|
||||
if: matrix.php == '7.4'
|
||||
run: vendor/bin/phpunit --verbose --exclude-group $PHPUNIT_EXCLUDE_GROUP --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: Upload coverage to Codecov.
|
||||
if: matrix.php == '7.4'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
php-version: 7.2
|
||||
ini-values: session.save_path=${{ runner.temp }}
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
run: composer update $DEFAULT_COMPOSER_FLAGS
|
||||
- name: Install node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 6
|
||||
- name: Tests
|
||||
run: |
|
||||
npm install
|
||||
npm test
|
||||
# env:
|
||||
# CI: true
|
||||
file: ./coverage.xml
|
||||
|
71
.github/workflows/ci-mssql.yml
vendored
71
.github/workflows/ci-mssql.yml
vendored
@ -4,42 +4,31 @@ on:
|
||||
|
||||
name: ci-mssql
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHP ${{ matrix.php }}-mssql-${{ matrix.mssql }}
|
||||
|
||||
env:
|
||||
key: cache
|
||||
EXTENSIONS: pdo, pdo_sqlsrv
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.0
|
||||
extensions: pdo, pdo_sqlsrv-5.8.1
|
||||
mssql: server:2017-latest
|
||||
- php: 7.1
|
||||
extensions: pdo, pdo_sqlsrv-5.8.1
|
||||
mssql: server:2017-latest
|
||||
- php: 7.2
|
||||
extensions: pdo, pdo_sqlsrv-5.8.1
|
||||
mssql: server:2017-latest
|
||||
- php: 7.3
|
||||
extensions: pdo, pdo_sqlsrv-5.8.1
|
||||
mssql: server:2017-latest
|
||||
- php: 7.4
|
||||
extensions: pdo, pdo_sqlsrv
|
||||
mssql: server:2017-latest
|
||||
- php: 7.4
|
||||
extensions: pdo, pdo_sqlsrv
|
||||
mssql: server:2019-latest
|
||||
- php: 8.0
|
||||
extensions: pdo, pdo_sqlsrv
|
||||
mssql: server:2017-latest
|
||||
- php: 8.0
|
||||
extensions: pdo, pdo_sqlsrv
|
||||
mssql: server:2019-latest
|
||||
- php: 8.1
|
||||
mssql: server:2019-latest
|
||||
- php: 8.2
|
||||
mssql: server:2022-latest
|
||||
|
||||
services:
|
||||
mssql:
|
||||
@ -62,42 +51,28 @@ jobs:
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: ${{ matrix.extensions }}
|
||||
coverage: xdebug
|
||||
extensions: ${{ env.EXTENSIONS }}
|
||||
ini-values: date.timezone='UTC'
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2, pecl
|
||||
|
||||
- name: Determine composer cache directory on Linux
|
||||
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache dependencies installed with composer
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_DIR }}
|
||||
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
php${{ matrix.php }}-composer-
|
||||
|
||||
- name: Update composer
|
||||
run: composer self-update
|
||||
|
||||
- name: Install dependencies with composer
|
||||
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
|
||||
- name: Install dependencies with composer php 8.0
|
||||
if: matrix.php == '8.0'
|
||||
run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
- name: Run MSSQL tests with PHPUnit and generate coverage.
|
||||
if: matrix.php == '7.4'
|
||||
run: vendor/bin/phpunit --group mssql --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: PHP Unit tests for PHP 7.1
|
||||
run: vendor/bin/phpunit --coverage-clover=coverage.clover --group mssql --colors=always
|
||||
if: matrix.php == '7.1'
|
||||
|
||||
- name: Run tests with phpunit without coverage
|
||||
- name: Run MSSQL tests with PHPUnit.
|
||||
if: matrix.php > '7.4'
|
||||
run: vendor/bin/phpunit --group mssql --colors=always
|
||||
|
||||
- name: Code coverage
|
||||
run: |
|
||||
wget https://scrutinizer-ci.com/ocular.phar
|
||||
php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
if: matrix.php == '7.1'
|
||||
continue-on-error: true # if is fork
|
||||
- name: Upload coverage to Codecov.
|
||||
if: matrix.php == '7.4'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
|
71
.github/workflows/ci-mysql.yml
vendored
71
.github/workflows/ci-mysql.yml
vendored
@ -4,12 +4,16 @@ on:
|
||||
|
||||
name: ci-mysql
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHP ${{ matrix.php-version }}-mysql-${{ matrix.mysql-version }}
|
||||
name: PHP ${{ matrix.php }}-mysql-${{ matrix.mysql }}
|
||||
env:
|
||||
extensions: curl, intl, pdo, pdo_mysql
|
||||
key: cache-v1
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -18,15 +22,19 @@ jobs:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
php-version:
|
||||
php:
|
||||
- 7.4
|
||||
- 8.0
|
||||
- 8.1
|
||||
- 8.2
|
||||
|
||||
mysql-version:
|
||||
mysql:
|
||||
- 5.7
|
||||
- latest
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:${{ matrix.mysql-version }}
|
||||
image: mysql:${{ matrix.mysql }}
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: yiitest
|
||||
@ -35,46 +43,31 @@ jobs:
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup cache environment
|
||||
id: cache-env
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: ${{ env.extensions }}
|
||||
key: ${{ env.key }}
|
||||
|
||||
- name: Cache extensions
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.cache-env.outputs.dir }}
|
||||
key: ${{ steps.cache-env.outputs.key }}
|
||||
restore-keys: ${{ steps.cache-env.outputs.key }}
|
||||
|
||||
- name: Install PHP with extensions
|
||||
- name: Install PHP with extensions.
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: ${{ env.extensions }}
|
||||
coverage: xdebug
|
||||
extensions: ${{ env.EXTENSIONS }}
|
||||
ini-values: date.timezone='UTC'
|
||||
coverage: pcov
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2, pecl
|
||||
|
||||
- name: Determine composer cache directory
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache dependencies installed with composer
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_DIR }}
|
||||
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
|
||||
|
||||
- name: Install dependencies with composer
|
||||
- name: Install dependencies with composer.
|
||||
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
|
||||
- name: Run mysql tests with phpunit
|
||||
- name: Run MySQL tests with PHPUnit and generate coverage.
|
||||
if: matrix.php == '7.4'
|
||||
run: vendor/bin/phpunit --group mysql --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: Run MySQL tests with PHPUnit.
|
||||
if: matrix.php > '7.4'
|
||||
run: vendor/bin/phpunit --group mysql --colors=always
|
||||
|
||||
- name: Upload coverage to Codecov.
|
||||
if: matrix.php == '7.4'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
|
36
.github/workflows/ci-node.yml
vendored
Normal file
36
.github/workflows/ci-node.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: build-node
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: NPM 6 on ubuntu-latest
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies.
|
||||
run: composer update $DEFAULT_COMPOSER_FLAGS
|
||||
|
||||
- name: Install JQuery `3.6.*@stable` for tests.
|
||||
run: composer require "bower-asset/jquery:3.6.*@stable"
|
||||
|
||||
- name: Install node.js.
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 6
|
||||
|
||||
- name: Tests.
|
||||
run: |
|
||||
npm install
|
||||
npm test
|
58
.github/workflows/ci-oracle.yml
vendored
58
.github/workflows/ci-oracle.yml
vendored
@ -4,13 +4,17 @@ on:
|
||||
|
||||
name: ci-oracle
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHP ${{ matrix.php }}-${{ matrix.os }}
|
||||
|
||||
env:
|
||||
extensions: oci8, pdo, pdo_oci
|
||||
key: cache-v1
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -30,52 +34,28 @@ jobs:
|
||||
options: --name=oci
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup cache environment
|
||||
id: cache-env
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: ${{ env.extensions }}
|
||||
key: ${{ env.key }}
|
||||
|
||||
- name: Cache extensions
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.cache-env.outputs.dir }}
|
||||
key: ${{ steps.cache-env.outputs.key }}
|
||||
restore-keys: ${{ steps.cache-env.outputs.key }}
|
||||
|
||||
- name: Install PHP with extensions
|
||||
- name: Install PHP with extensions.
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: ${{ env.extensions }}
|
||||
coverage: xdebug
|
||||
extensions: ${{ env.EXTENSIONS }}
|
||||
ini-values: date.timezone='UTC'
|
||||
coverage: pcov
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2, pecl
|
||||
|
||||
- name: Determine composer cache directory
|
||||
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
|
||||
- name: Update composer.
|
||||
run: composer self-update
|
||||
|
||||
- name: Cache dependencies installed with composer
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_DIR }}
|
||||
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
php${{ matrix.php }}-composer-
|
||||
|
||||
- name: Install dependencies with composer
|
||||
- name: Install dependencies with composer.
|
||||
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
|
||||
- name: PHP Unit tests
|
||||
run: vendor/bin/phpunit --coverage-clover=coverage.clover --group oci --colors=always
|
||||
- name: Run Oracle tests with PHPUnit and generate coverage.
|
||||
run: vendor/bin/phpunit --group oci --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: Code coverage
|
||||
run: |
|
||||
wget https://scrutinizer-ci.com/ocular.phar
|
||||
php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
continue-on-error: true # if is fork
|
||||
- name: Upload coverage to Codecov.
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
|
69
.github/workflows/ci-pgsql.yml
vendored
69
.github/workflows/ci-pgsql.yml
vendored
@ -4,12 +4,16 @@ on:
|
||||
|
||||
name: ci-pgsql
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHP ${{ matrix.php-version }}-pgsql-${{ matrix.pgsql-version }}
|
||||
name: PHP ${{ matrix.php }}-pgsql-${{ matrix.pgsql }}
|
||||
env:
|
||||
extensions: curl, intl, pdo, pdo_pgsql
|
||||
key: cache-v1
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -18,19 +22,22 @@ jobs:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
php-version:
|
||||
php:
|
||||
- 7.4
|
||||
- 8.0
|
||||
- 8.1
|
||||
|
||||
pgsql-version:
|
||||
pgsql:
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:${{ matrix.pgsql-version }}
|
||||
image: postgres:${{ matrix.pgsql }}
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@ -40,46 +47,34 @@ jobs:
|
||||
options: --name=postgres --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup cache environment
|
||||
id: cache-env
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: ${{ env.extensions }}
|
||||
key: ${{ env.key }}
|
||||
|
||||
- name: Cache extensions
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.cache-env.outputs.dir }}
|
||||
key: ${{ steps.cache-env.outputs.key }}
|
||||
restore-keys: ${{ steps.cache-env.outputs.key }}
|
||||
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: ${{ env.extensions }}
|
||||
coverage: xdebug
|
||||
extensions: ${{ env.EXTENSIONS }}
|
||||
ini-values: date.timezone='UTC'
|
||||
coverage: pcov
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2, pecl
|
||||
|
||||
- name: Determine composer cache directory
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
|
||||
- name: Update composer.
|
||||
run: composer self-update
|
||||
|
||||
- name: Cache dependencies installed with composer
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_DIR }}
|
||||
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
|
||||
|
||||
- name: Install dependencies with composer
|
||||
- name: Install dependencies with composer.
|
||||
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
|
||||
- name: Run pgsql tests with phpunit
|
||||
- name: Run Pgsql tests with PHPUnit and generate coverage.
|
||||
if: matrix.php == '7.4'
|
||||
run: vendor/bin/phpunit --group pgsql --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: Run Pgsql tests with PHPUnit.
|
||||
if: matrix.php > '7.4'
|
||||
run: vendor/bin/phpunit --group pgsql --colors=always
|
||||
|
||||
- name: Upload coverage to Codecov.
|
||||
if: matrix.php == '7.4'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
|
63
.github/workflows/ci-sqlite.yml
vendored
Normal file
63
.github/workflows/ci-sqlite.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
on:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
name: ci-sqlite
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHP ${{ matrix.php }}-sqlite
|
||||
|
||||
env:
|
||||
EXTENSIONS: pdo, pdo_sqlite, sqlite3
|
||||
XDEBUG_MODE: coverage, develop
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
php:
|
||||
- 7.4
|
||||
- 8.0
|
||||
- 8.1
|
||||
- 8.2
|
||||
|
||||
steps:
|
||||
- name: Checkout.
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install PHP with extensions.
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: xdebug
|
||||
extensions: ${{ env.EXTENSIONS }}
|
||||
ini-values: date.timezone='UTC'
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2, pecl
|
||||
|
||||
- name: Update composer.
|
||||
run: composer self-update
|
||||
|
||||
- name: Install dependencies with composer.
|
||||
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
|
||||
|
||||
- name: Run SQLite tests with PHPUnit and generate coverage.
|
||||
if: matrix.php == '7.4'
|
||||
run: vendor/bin/phpunit --group sqlite --coverage-clover=coverage.xml --colors=always
|
||||
|
||||
- name: Run SQLite tests with PHPUnit.
|
||||
if: matrix.php > '7.4'
|
||||
run: vendor/bin/phpunit --group sqlite --colors=always
|
||||
|
||||
- name: Upload coverage to Codecov.
|
||||
if: matrix.php == '7.4'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
@ -4,10 +4,7 @@ FROM ${DOCKER_YII2_PHP_IMAGE}
|
||||
# Project source-code
|
||||
WORKDIR /project
|
||||
ADD composer.* /project/
|
||||
# Apply testing patches
|
||||
ADD tests/phpunit_mock_objects.patch /project/tests/phpunit_mock_objects.patch
|
||||
ADD tests/phpunit_getopt.patch /project/tests/phpunit_getopt.patch
|
||||
# Install packgaes
|
||||
# Install packages
|
||||
RUN /usr/local/bin/composer install --prefer-dist
|
||||
ADD ./ /project
|
||||
ENV PATH /project/vendor/bin:${PATH}
|
||||
|
@ -49,14 +49,19 @@ class MimeTypeController extends Controller
|
||||
* @var array MIME types to add to the ones parsed from Apache files
|
||||
*/
|
||||
private $additionalMimeTypes = [
|
||||
'apng' => 'image/apng',
|
||||
'avif' => 'image/avif',
|
||||
'jfif' => 'image/jpeg',
|
||||
'mjs' => 'text/javascript',
|
||||
'pjp' => 'image/jpeg',
|
||||
'pjpeg' => 'image/jpeg',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $outFile the mime file to update. Defaults to @yii/helpers/mimeTypes.php
|
||||
* @param string $aliasesOutFile the aliases file to update. Defaults to @yii/helpers/mimeAliases.php
|
||||
*/
|
||||
public function actionIndex($outFile = null, $aliasesOutFile = null)
|
||||
public function actionIndex($outFile = null, $aliasesOutFile = null, $extensionsOutFile = null)
|
||||
{
|
||||
if ($outFile === null) {
|
||||
$outFile = Yii::getAlias('@yii/helpers/mimeTypes.php');
|
||||
@ -66,11 +71,16 @@ class MimeTypeController extends Controller
|
||||
$aliasesOutFile = Yii::getAlias('@yii/helpers/mimeAliases.php');
|
||||
}
|
||||
|
||||
if ($extensionsOutFile === null) {
|
||||
$extensionsOutFile = Yii::getAlias('@yii/helpers/mimeExtensions.php');
|
||||
}
|
||||
|
||||
$this->stdout('Downloading mime-type file from apache httpd repository...');
|
||||
if ($apacheMimeTypesFileContent = file_get_contents('https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=co')) {
|
||||
$this->stdout("Done.\n", Console::FG_GREEN);
|
||||
$this->generateMimeTypesFile($outFile, $apacheMimeTypesFileContent);
|
||||
$this->generateMimeAliasesFile($aliasesOutFile);
|
||||
$this->generateMimeExtensionsFile($extensionsOutFile, $apacheMimeTypesFileContent);
|
||||
} else {
|
||||
$this->stderr("Failed to download mime.types file from apache SVN.\n");
|
||||
}
|
||||
@ -97,8 +107,8 @@ class MimeTypeController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
$mimeMap = array_merge($mimeMap, $this->additionalMimeTypes);
|
||||
ksort($mimeMap);
|
||||
$mimeMap = array_replace($mimeMap, $this->additionalMimeTypes);
|
||||
ksort($mimeMap, SORT_STRING);
|
||||
$array = VarDumper::export($mimeMap);
|
||||
|
||||
$content = <<<EOD
|
||||
@ -119,6 +129,7 @@ if (PHP_VERSION_ID >= 80100) {
|
||||
}
|
||||
|
||||
return \$mimeTypes;
|
||||
|
||||
EOD;
|
||||
file_put_contents($outFile, $content);
|
||||
$this->stdout("done.\n", Console::FG_GREEN);
|
||||
@ -140,6 +151,67 @@ EOD;
|
||||
*/
|
||||
return $array;
|
||||
|
||||
EOD;
|
||||
file_put_contents($outFile, $content);
|
||||
$this->stdout("done.\n", Console::FG_GREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $outFile
|
||||
* @param string $content
|
||||
*/
|
||||
private function generateMimeExtensionsFile($outFile, $content)
|
||||
{
|
||||
$this->stdout("Generating file $outFile...");
|
||||
|
||||
$extensionMap = [];
|
||||
foreach (explode("\n", $content) as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line) || strpos($line, '#') === 0) { // skip comments and empty lines
|
||||
continue;
|
||||
}
|
||||
$parts = preg_split('/\s+/', $line);
|
||||
$mime = array_shift($parts);
|
||||
if (!empty($parts)) {
|
||||
$extensionMap[$mime] = [];
|
||||
foreach ($parts as $ext) {
|
||||
if (!empty($ext)) {
|
||||
$extensionMap[$mime][] = $ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->additionalMimeTypes as $ext => $mime) {
|
||||
if (!array_key_exists($mime, $extensionMap)) {
|
||||
$extensionMap[$mime] = [];
|
||||
}
|
||||
$extensionMap[$mime][] = $ext;
|
||||
}
|
||||
|
||||
foreach ($extensionMap as $mime => $extensions) {
|
||||
if (count($extensions) === 1) {
|
||||
$extensionMap[$mime] = $extensions[0];
|
||||
}
|
||||
}
|
||||
|
||||
ksort($extensionMap, SORT_STRING);
|
||||
$array = VarDumper::export($extensionMap);
|
||||
|
||||
$content = <<<EOD
|
||||
<?php
|
||||
/**
|
||||
* MIME type extensions.
|
||||
*
|
||||
* This file contains most commonly used extensions for MIME types.
|
||||
* If there are multiple extensions for a singe MIME type
|
||||
* they are ordered from most to least common.
|
||||
* Its content is generated from the apache http mime.types file.
|
||||
* https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=markup
|
||||
* This file has been placed in the public domain for unlimited redistribution.
|
||||
*/
|
||||
return $array;
|
||||
|
||||
EOD;
|
||||
file_put_contents($outFile, $content);
|
||||
$this->stdout("done.\n", Console::FG_GREEN);
|
||||
|
@ -218,10 +218,10 @@ class ReleaseController extends Controller
|
||||
$gitDir = reset($what) === 'framework' ? 'framework/' : '';
|
||||
$gitVersion = $versions[reset($what)];
|
||||
if (strncmp('app-', reset($what), 4) !== 0) {
|
||||
$this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n\n git diff $gitVersion.. ${gitDir}CHANGELOG.md\n\n");
|
||||
$this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n\n git diff $gitVersion.. {$gitDir}CHANGELOG.md\n\n");
|
||||
$this->stdout("- are all new `@since` tags for this release version?\n");
|
||||
}
|
||||
$this->stdout("- other issues with code changes?\n\n git diff -w $gitVersion.. ${gitDir}\n\n");
|
||||
$this->stdout("- other issues with code changes?\n\n git diff -w $gitVersion.. {$gitDir}\n\n");
|
||||
$travisUrl = reset($what) === 'framework' ? '' : '-' . reset($what);
|
||||
$this->stdout("- are unit tests passing on travis? https://travis-ci.com/yiisoft/yii2$travisUrl/builds\n");
|
||||
$this->stdout("- also make sure the milestone on github is complete and no issues or PRs are left open.\n\n");
|
||||
|
@ -75,7 +75,7 @@
|
||||
"yiisoft/yii2-composer": "~2.0.4",
|
||||
"ezyang/htmlpurifier": "^4.6",
|
||||
"cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0",
|
||||
"bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
|
||||
"bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
|
||||
"bower-asset/inputmask": "~3.2.2 | ~3.3.5",
|
||||
"bower-asset/punycode": "1.3.*",
|
||||
"bower-asset/yii2-pjax": "~2.0.1",
|
||||
|
1935
composer.lock
generated
1935
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -117,7 +117,7 @@ Seguridad
|
||||
* **TBD** [Autenticación](security-authentication.md)
|
||||
* **TBD** [Autorización](security-authorization.md)
|
||||
* **TBD** [Trabajar con contraseñas](security-passwords.md)
|
||||
* **TBD** [Autenticar Clientes](security-auth-clients.md)
|
||||
* [Autenticar Clientes](https://www.yiiframework.com/extension/yiisoft/yii2-authclient/doc/guide)
|
||||
* **TBD** [Buenas prácticas](security-best-practices.md)
|
||||
|
||||
|
||||
|
@ -7,5 +7,6 @@
|
||||
"Lucas Barros",
|
||||
"Raphael de Almeida",
|
||||
"Sidney da Silva Lins",
|
||||
"Wanderson Bragança"
|
||||
"Wanderson Bragança",
|
||||
"Anthony Tesche"
|
||||
]
|
||||
|
103
docs/guide-pt-BR/tutorial-docker.md
Normal file
103
docs/guide-pt-BR/tutorial-docker.md
Normal file
@ -0,0 +1,103 @@
|
||||
Yii e Docker
|
||||
==============
|
||||
|
||||
Para o desenvolvimento e implantação de aplicativos Yii, eles podem ser executados como contêineres Docker. Um contêiner é como uma máquina virtual isolada e leve que mapeia seus serviços para as portas do host, ou seja, um servidor da web em um contêiner na porta 80 está disponível na porta 8888 do seu (local) host.
|
||||
|
||||
Os contêineres podem resolver muitos problemas, como ter versões idênticas de software no computador do desenvolvedor e no servidor, implantações rápidas ou simulação de arquitetura multi-servidor durante o desenvolvimento.
|
||||
|
||||
Você pode ler mais sobre contêineres Docker em [docker.com](https://www.docker.com/why-docker).
|
||||
|
||||
## Requisitos
|
||||
|
||||
- `docker`
|
||||
- `docker-compose`
|
||||
|
||||
Visite a [página de download](https://www.docker.com/products/container-runtime) para obter as ferramentas do Docker.
|
||||
|
||||
## Instalação
|
||||
|
||||
Após a instalação, você deve ser capaz de executar o comando docker ps e ver uma saída semelhante a esta:
|
||||
|
||||
```
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
|
||||
```
|
||||
|
||||
Isso significa que o seu daemon Docker está em execução.
|
||||
|
||||
Além disso, execute o comando docker-compose version, a saída deve ser semelhante a esta:
|
||||
|
||||
```
|
||||
docker-compose version 1.20.0, build unknown
|
||||
docker-py version: 3.1.3
|
||||
CPython version: 3.6.4
|
||||
OpenSSL version: OpenSSL 1.1.0g 2 Nov 2017
|
||||
```
|
||||
|
||||
Com o Compose, você pode configurar e gerenciar todos os serviços necessários para a sua aplicação, como bancos de dados e cache.
|
||||
|
||||
## Recursos
|
||||
|
||||
- As imagens base do PHP para Yii podem ser encontradas em [yii2-docker](https://github.com/yiisoft/yii2-docker)
|
||||
- Suporte do Docker para [yii2-app-basic](https://github.com/yiisoft/yii2-app-basic#install-with-docker)
|
||||
- Suporte do Docker para [yii2-app-advanced](https://github.com/yiisoft/yii2-app-advanced/pull/347) está em desenvolvimento
|
||||
|
||||
## Uso
|
||||
|
||||
Os comandos básicos do Docker são
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
para iniciar todos os serviços em sua pilha, em segundo plano
|
||||
|
||||
docker-compose ps
|
||||
|
||||
para listar os serviços em execução
|
||||
|
||||
docker-compose logs -f
|
||||
|
||||
para visualizar os logs de todos os serviços continuamente
|
||||
|
||||
docker-compose stop
|
||||
|
||||
para interromper todos os serviços em sua pilha de forma elegante
|
||||
|
||||
docker-compose kill
|
||||
|
||||
para interromper todos os serviços em sua pilha imediatamente
|
||||
|
||||
docker-compose down -v
|
||||
|
||||
para parar e remover todos os serviços, **atenção à perda de dados ao não usar volumes do host**
|
||||
|
||||
Para executar comandos em um contêiner:
|
||||
|
||||
docker-compose run --rm php composer install
|
||||
|
||||
executa a instalação do Composer em um novo contêiner
|
||||
|
||||
docker-compose exec php bash
|
||||
|
||||
executa um shell bash em um serviço php que está em *execução*.
|
||||
|
||||
|
||||
## Tópicos avançados
|
||||
|
||||
### Testes do framework Yii
|
||||
|
||||
Você pode executar os testes do framework Yii em um contêiner Docker, conforme descrito [aqui](https://github.com/yiisoft/yii2/blob/master/tests/README.md#dockerized-testing).
|
||||
|
||||
### Database administration tools
|
||||
|
||||
Ao executar o MySQL como (`mysql`), você pode adicionar um contêiner do phpMyAdmin à sua pilha, como mostrado abaixo:
|
||||
|
||||
```
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
ports:
|
||||
- '8888:80'
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
||||
- PMA_HOST=mysql
|
||||
depends_on:
|
||||
- mysql
|
||||
```
|
@ -373,7 +373,7 @@ $query->orderBy([
|
||||
```
|
||||
|
||||
В данном коде, ключи массива - это имена столбцов, а значения массива - это соответствующее направление сортировки.
|
||||
PHP константа `SORT_ASC` определяет сортировку по возрастанию и `SORT_DESC` сортировка по умолчанию.
|
||||
PHP константа `SORT_ASC` определяет сортировку по возрастанию и `SORT_DESC` сортировку по убыванию.
|
||||
|
||||
Если `ORDER BY` содержит только простые имена столбцов, вы можете определить их с помощью столбцов, также
|
||||
как и при написании обычного SQL. Например,
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
В конструкторе приложения происходит следующий процесс предзагрузки:
|
||||
|
||||
1. Вызывается метод [[yii\base\Application::preInit()|preInit()]], которые конфигурирует свойства приложения, имеющие
|
||||
1. Вызывается метод [[yii\base\Application::preInit()|preInit()]], который конфигурирует свойства приложения, имеющие
|
||||
наивысший приоритет, такие как [[yii\base\Application::basePath|basePath]];
|
||||
2. Регистрируется [[yii\base\Application::errorHandler|обработчик ошибок]];
|
||||
3. Происходит инициализация свойств приложения согласно заданной конфигурации;
|
||||
|
@ -82,6 +82,8 @@ and accessible. The following code shows how to configure the `cache` applicatio
|
||||
|
||||
You can then access the above cache component using the expression `Yii::$app->cache`.
|
||||
|
||||
If no cache component is specified, then Yii will use [yii\caching\FileCache](https://www.yiiframework.com/doc/api/2.0/yii-caching-filecache) as default.
|
||||
|
||||
Because all cache components support the same set of APIs, you can swap the underlying cache component
|
||||
with a different one by reconfiguring it in the application configuration without modifying the code that uses the cache.
|
||||
For example, you can modify the above configuration to use [[yii\caching\ApcCache|APC cache]]:
|
||||
|
@ -263,6 +263,12 @@ Further reading on the topic:
|
||||
- <https://owasp.org/www-community/SameSite>
|
||||
|
||||
|
||||
Avoiding arbitrary object instantiations
|
||||
----------------------------------------
|
||||
|
||||
Yii [configurations](concept-configurations.md) are associative arrays used by the framework to instantiate new objects through `Yii::createObject($config)`. These arrays specify the class name for instantiation, and it is important to ensure that this class name does not originate from untrusted sources. Otherwise, it can lead to Unsafe Reflection, a vulnerability that allows the execution of malicious code by exploiting the loading of specific classes. Additionally, when you need to dynamically add keys to an object derived from a framework class, such as the base `Component` class, it's essential to validate these dynamic properties using a whitelist approach. This precaution is necessary because the framework might employ `Yii::createObject($config)` within the `__set()` magic method.
|
||||
|
||||
|
||||
Avoiding file exposure
|
||||
----------------------
|
||||
|
||||
|
@ -293,6 +293,8 @@ public function rules()
|
||||
|
||||
// username and password are required in "login" scenario
|
||||
[['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN],
|
||||
|
||||
[['username'], 'string'], // username must always be a string, this rule applies to all scenarios
|
||||
];
|
||||
}
|
||||
```
|
||||
|
@ -68,7 +68,7 @@ class BaseYii
|
||||
*/
|
||||
public static $classMap = [];
|
||||
/**
|
||||
* @var \yii\console\Application|\yii\web\Application|\yii\base\Application the application instance
|
||||
* @var \yii\console\Application|\yii\web\Application the application instance
|
||||
*/
|
||||
public static $app;
|
||||
/**
|
||||
@ -93,7 +93,7 @@ class BaseYii
|
||||
*/
|
||||
public static function getVersion()
|
||||
{
|
||||
return '2.0.48-dev';
|
||||
return '2.0.50-dev';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,27 +1,77 @@
|
||||
Yii Framework 2 Change Log
|
||||
==========================
|
||||
|
||||
2.0.48 under development
|
||||
2.0.50 under development
|
||||
------------------------
|
||||
|
||||
- Enh #19766: Add support for PHP generators to JSON helper (vladis84)
|
||||
- Bug #19683: Updated `framework\mimeType.php` to the actual value. Fix typo in `build/controllers/MimeTypeController.php` (DeryabinSergey)
|
||||
- Bug #19705: Add binary and other data type to `$typeMap` list for MySQL (sohelahmed7)
|
||||
- Enh #19741: Added option to use a closure for `$variations` definition in `yii\filters\PageCache` (nadar)
|
||||
- Bug #19925: Improved PHP version check when handling MIME types (schmunk42)
|
||||
- Bug #19940: File Log writer without newline (terabytesoftw)
|
||||
- Bug #19951: Removed unneeded MIME file tests (schmunk42)
|
||||
- Bug #19950: Fix `Query::groupBy(null)` causes error for PHP 8.1: `trim(): Passing null to parameter #1 ($string) of type string is deprecated` (uaoleg)
|
||||
|
||||
|
||||
2.0.49 August 29, 2023
|
||||
----------------------
|
||||
|
||||
- Bug #9899: Fix caching a MSSQL query with BLOB data type (terabytesoftw)
|
||||
- Bug #16208: Fix `yii\log\FileTarget` to not export empty messages (terabytesoftw)
|
||||
- Bug #18859: Fix `yii\web\Controller::bindInjectedParams()` to not throw error when argument of `ReflectionUnionType` type is passed (bizley)
|
||||
- Bug #19857: Fix AttributeTypecastBehavior::resetOldAttributes() causes "class has no attribute named" InvalidArgumentException (uaoleg)
|
||||
- Bug #19868: Added whitespace sanitation for tests, due to updates in ICU 72 (schmunk42)
|
||||
- Bug #19872: Fixed the definition of dirty attributes in AR properties for a non-associative array in case of changing the order of elements (eegusakov)
|
||||
- Bug #19899: Fixed `GridView` in some cases calling `Model::generateAttributeLabel()` to generate label values that are never used (PowerGamer1)
|
||||
- Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh)
|
||||
- Bug #19908: Fix associative array cell content rendering in Table widget (rhertogh)
|
||||
- Bug #19911: Resolved inconsistency in `ActiveRecord::getAttributeLabel()` with regard of overriding in primary model labels for attributes of related model in favor of allowing such overriding for all levels of relation nesting (PowerGamer1)
|
||||
- Bug #19914: Fixed `ArrayHelper::keyExists()` and `::remove()` functions when the key is a float and the value is `null` (rhertogh)
|
||||
- Bug #19924: Fix `yii\i18n\Formatter` to not throw error `Unknown named parameter` under PHP 8 (arollmann)
|
||||
- Enh #19841: Allow jQuery 3.7 to be installed (wouter90)
|
||||
- Enh #19853: Added support for default value for `\yii\helpers\Console::select()` (rhertogh)
|
||||
- Enh #19884: Added support Enums in Query Builder (sk1t0n)
|
||||
- Enh #19920: Broadened the accepted type of `Cookie::$expire` from `int` to `int|string|\DateTimeInterface|null` (rhertogh)
|
||||
|
||||
|
||||
2.0.48.1 May 24, 2023
|
||||
---------------------
|
||||
|
||||
- Bug #19847: Fix regression introduced in #15376 that caused `DbManager::getRolesByUser()` to return stale data (michaelarnauts)
|
||||
|
||||
|
||||
2.0.48 May 22, 2023
|
||||
-------------------
|
||||
|
||||
- Bug #15376: Added $userId for RBAC roles cache (manchenkoff)
|
||||
- Bug #17194: Fix unnecessary SQL updates in the database on attributes typecast via `yii\behaviors\AttributeTypecastBehavior` (aivchen)
|
||||
- Bug #18867: Fixed multiple issues with `yii\grid\CheckboxColumn`: "check all" checkbox not being checked on page load when all data row checkboxes are initially checked; clicking checkboxes triggered "change" event for other checkboxes that do not change their state; "check all" checkbox not being checked when disabled checkboxes are present and clicking last non-checked data row checkbox (PowerGamer1)
|
||||
- Bug #19635: PHP 8.2 compatibility fix for `yii\validators\DateValidator` (PowerGamer1)
|
||||
- Bug #17194: Fix unnecessary SQL updates in the database on attributes typecast via `yii\behaviors\AttributeTypecastBehavior` (aivchen)
|
||||
- Bug #19683: Updated `framework\mimeType.php` to the actual value. Fix typo in `build/controllers/MimeTypeController.php` (DeryabinSergey)
|
||||
- Bug #19693: Fix db/Command not caching `NULL` result with scalar fetchMode (Arkeins)
|
||||
- Enh #15376: Added cache usage for `yii\rbac\DbManager::getRolesByUser()` (manchenkoff)
|
||||
- Enh #9740: Usage of DI instead of new keyword in Schemas (manchenkoff)
|
||||
- Enh #19689: Remove empty elements from the `class` array in `yii\helpers\BaseHtml::renderTagAttributes()` to prevent unwanted spaces (MoritzLost)
|
||||
- Chg #19696: Change visibility of `yii\web\View::isPageEnded` to `protected` (lubosdz, samdark)
|
||||
- Bug #19705: Add binary and other data types to `$typeMap` list for MySQL (sohelahmed7)
|
||||
- Bug #19712: Cast shell_exec() output to string for jsCompressor (impayru)
|
||||
- Bug #19720: Fix "zh-HK" locale causing [error][yii\i18n\PhpMessageSource::loadFallbackMessages] The message file for category 'yii' doesn't exist (uaoleg)
|
||||
- Bug #19731: Fix `yii\data\Sort` to generate a proper link when multisort is on and attribute has a default sort order set (bizley)
|
||||
- Bug #19734: PHP 8.1 compatibility fix for `$query->orderBy(null)` (uaoleg)
|
||||
- Bug #19731: Fix `yii\data\Sort` to generate proper link when multisort is on and attribute has a default sort order set (bizley)
|
||||
- Bug #19735: Fix `yii\validators\NumberValidator` to use programmable message for the value validation (bizley)
|
||||
- Bug #19735: Fix `yii\validators\NumberValidator` to use a programmable message for the value validation (bizley)
|
||||
- Bug #19736: Fix `StringHelper::truncate(null, 10)` causes error `Deprecated: mb_strlen(): Passing null to parameter #1 ($string) of type string is deprecated` (uaoleg)
|
||||
- Bug #19743: Non-associative array values in AR weren't considered dirty when reordered (samdark)
|
||||
- Bug #19749: Add PHP 8.2 support (samdark, schmunk42, aldok10, DanaLuther)
|
||||
- Bug #19770: Fix `yii\mutex\MysqlMutex` `keyPrefix` expression param binding (kamarton)
|
||||
- Bug #19795: Fix `yii\web\Response::redirect()` to prevent setting headers with URL containing new line character (bizley)
|
||||
- Bug #19807: Fix REST serializer not using `serializeModel()` when working with an array of models (zucha)
|
||||
- Bug #19813: Fix `yii\base\DynamicModel` validation with validators that reference missing attributes (michaelarnauts)
|
||||
- Bug #19828: Fix "strtr(): Passing null to parameter #1 ($string) of type string is deprecated" (uaoleg)
|
||||
- Bug #19837: Fixed processing of numeric file extensions in `yii\build\controllers\MimeTypeController::generateMimeTypesFile()` (rhertogh)
|
||||
- Enh #9740: Usage of DI instead of new keyword in Schemas (manchenkoff)
|
||||
- Enh #15376: Added cache usage for `yii\rbac\DbManager::getRolesByUser()` (manchenkoff)
|
||||
- Enh #19689: Remove empty elements from the `class` array in `yii\helpers\BaseHtml::renderTagAttributes()` to prevent unwanted spaces (MoritzLost)
|
||||
- Enh #19741: Added option to use a closure for `$variations` definition in `yii\filters\PageCache` (nadar)
|
||||
- Enh #19766: Add support for PHP generators to JSON helper (vladis84)
|
||||
- Enh #19794: Add caching in `yii\web\Request` for `getUserIP()` and `getSecureForwardedHeaderTrustedParts()` (rhertogh)
|
||||
- Enh #19804: Remove the unnecessary call to `$this->oldAttributes` in `BaseActiveRecord::getDirtyAttributes()` (thiagotalma)
|
||||
- Enh #19816: Explicitly pass `$fallbackToMaster` as `true` to `getSlavePdo()` to ensure it isn't affected by child class with changed defaults (developedsoftware)
|
||||
- Enh #19838: Added `yii\helpers\BaseFileHelper::getExtensionByMimeType()` to get the most common extension for a given MIME type (rhertogh)
|
||||
- Chg #19696: Change visibility of `yii\web\View::isPageEnded` to `protected` (lubosdz, samdark)
|
||||
|
||||
|
||||
2.0.47 November 18, 2022
|
||||
------------------------
|
||||
|
@ -52,6 +52,17 @@ version B between A and C, you need to follow the instructions
|
||||
for both A and B.
|
||||
|
||||
|
||||
Upgrade from Yii 2.0.48
|
||||
-----------------------
|
||||
|
||||
* Since Yii 2.0.49 the `yii\console\Controller::select()` function supports a default value and respects
|
||||
the `yii\console\Controller::$interactive` setting. Before the user was always prompted to select an option
|
||||
regardless of the `$interactive` setting. Now the `$default` value is automatically returned when `$interactive` is
|
||||
`false`.
|
||||
* The function signature for `yii\console\Controller::select()` and `yii\helpers\BaseConsole::select()` have changed.
|
||||
They now have an additional `$default = null` parameter. In case those methods are overwritten you will need to
|
||||
update your child classes accordingly.
|
||||
|
||||
Upgrade from Yii 2.0.46
|
||||
-----------------------
|
||||
|
||||
@ -89,6 +100,11 @@ Upgrade from Yii 2.0.45
|
||||
2.0.45 behavior, [introduce your own method](https://github.com/yiisoft/yii2/pull/19495/files).
|
||||
* `yii\log\FileTarget::$rotateByCopy` is now deprecated and setting it to `false` has no effect since rotating of
|
||||
the files is done only by copy.
|
||||
* `yii\validators\UniqueValidator` and `yii\validators\ExistValidator`, when used on multiple attributes, now only
|
||||
generate an error on a single attribute. Previously, they would report a separate error on each attribute.
|
||||
Old behavior can be achieved by setting `'skipOnError' => false`, but this might have undesired side effects with
|
||||
additional validators on one of the target attributes.
|
||||
See [issue #19407](https://github.com/yiisoft/yii2/issues/19407)
|
||||
|
||||
Upgrade from Yii 2.0.44
|
||||
-----------------------
|
||||
|
@ -15,7 +15,7 @@ namespace yii\base;
|
||||
* like the following:
|
||||
*
|
||||
* ```php
|
||||
* public function __constructor($param1, $param2, ..., $config = [])
|
||||
* public function __construct($param1, $param2, ..., $config = [])
|
||||
* ```
|
||||
*
|
||||
* That is, the last parameter of the constructor must accept a configuration array.
|
||||
|
@ -201,6 +201,7 @@ class DynamicModel extends Model
|
||||
}
|
||||
|
||||
$validators->append($validator);
|
||||
$this->defineAttributesByValidator($validator);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -223,9 +224,11 @@ class DynamicModel extends Model
|
||||
foreach ($rules as $rule) {
|
||||
if ($rule instanceof Validator) {
|
||||
$validators->append($rule);
|
||||
$model->defineAttributesByValidator($rule);
|
||||
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
|
||||
$validator = Validator::createValidator($rule[1], $model, (array)$rule[0], array_slice($rule, 2));
|
||||
$validators->append($validator);
|
||||
$model->defineAttributesByValidator($validator);
|
||||
} else {
|
||||
throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
|
||||
}
|
||||
@ -237,6 +240,19 @@ class DynamicModel extends Model
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the attributes that applies to the specified Validator.
|
||||
* @param Validator $validator the validator whose attributes are to be defined.
|
||||
*/
|
||||
private function defineAttributesByValidator($validator)
|
||||
{
|
||||
foreach ($validator->getAttributeNames() as $attribute) {
|
||||
if (!$this->hasAttribute($attribute)) {
|
||||
$this->defineAttribute($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -366,7 +366,10 @@ class AttributeTypecastBehavior extends Behavior
|
||||
$this->resetOldAttributes();
|
||||
}
|
||||
|
||||
private function resetOldAttributes()
|
||||
/**
|
||||
* Resets the old values of the named attributes.
|
||||
*/
|
||||
protected function resetOldAttributes()
|
||||
{
|
||||
if ($this->attributeTypes === null) {
|
||||
return;
|
||||
@ -375,7 +378,9 @@ class AttributeTypecastBehavior extends Behavior
|
||||
$attributes = array_keys($this->attributeTypes);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->owner->setOldAttribute($attribute, $this->owner->{$attribute});
|
||||
if ($this->owner->canSetOldAttribute($attribute)) {
|
||||
$this->owner->setOldAttribute($attribute, $this->owner->{$attribute});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ class DbCache extends Cache
|
||||
*/
|
||||
protected function getDataFieldName()
|
||||
{
|
||||
return $this->isVarbinaryDataField() ? 'convert(nvarchar(max),[data]) data' : 'data';
|
||||
return $this->isVarbinaryDataField() ? 'CONVERT(VARCHAR(MAX), [[data]]) data' : 'data';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,9 +345,9 @@ return [
|
||||
'yii\validators\RequiredValidator' => YII2_PATH . '/validators/RequiredValidator.php',
|
||||
'yii\validators\SafeValidator' => YII2_PATH . '/validators/SafeValidator.php',
|
||||
'yii\validators\StringValidator' => YII2_PATH . '/validators/StringValidator.php',
|
||||
'yii\validators\TrimValidator' => YII2_PATH . '/validators/TrimValidator.php',
|
||||
'yii\validators\UniqueValidator' => YII2_PATH . '/validators/UniqueValidator.php',
|
||||
'yii\validators\UrlValidator' => YII2_PATH . '/validators/UrlValidator.php',
|
||||
'yii\validators\TrimValidator' => YII2_PATH . '/validators/TrimValidator.php',
|
||||
'yii\validators\ValidationAsset' => YII2_PATH . '/validators/ValidationAsset.php',
|
||||
'yii\validators\Validator' => YII2_PATH . '/validators/Validator.php',
|
||||
'yii\web\Application' => YII2_PATH . '/web/Application.php',
|
||||
|
@ -68,9 +68,9 @@
|
||||
"ext-ctype": "*",
|
||||
"lib-pcre": "*",
|
||||
"yiisoft/yii2-composer": "~2.0.4",
|
||||
"ezyang/htmlpurifier": "~4.6",
|
||||
"ezyang/htmlpurifier": "^4.6",
|
||||
"cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0",
|
||||
"bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
|
||||
"bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
|
||||
"bower-asset/inputmask": "~3.2.2 | ~3.3.5",
|
||||
"bower-asset/punycode": "1.3.*",
|
||||
"bower-asset/yii2-pjax": "~2.0.1",
|
||||
|
@ -28,8 +28,8 @@ use yii\helpers\Inflector;
|
||||
* where `<route>` is a route to a controller action and the params will be populated as properties of a command.
|
||||
* See [[options()]] for details.
|
||||
*
|
||||
* @property-read string $help The help information for this controller.
|
||||
* @property-read string $helpSummary The one-line short summary describing this controller.
|
||||
* @property-read string $help
|
||||
* @property-read string $helpSummary
|
||||
* @property-read array $passedOptionValues The properties corresponding to the passed options.
|
||||
* @property-read array $passedOptions The names of the options passed during execution.
|
||||
*
|
||||
@ -400,12 +400,19 @@ class Controller extends \yii\base\Controller
|
||||
*
|
||||
* @param string $prompt the prompt message
|
||||
* @param array $options Key-value array of options to choose from
|
||||
* @param string|null $default value to use when the user doesn't provide an option.
|
||||
* If the default is `null`, the user is required to select an option.
|
||||
*
|
||||
* @return string An option character the user chose
|
||||
* @since 2.0.49 Added the $default argument
|
||||
*/
|
||||
public function select($prompt, $options = [])
|
||||
public function select($prompt, $options = [], $default = null)
|
||||
{
|
||||
return Console::select($prompt, $options);
|
||||
if ($this->interactive) {
|
||||
return Console::select($prompt, $options, $default);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +136,11 @@ class Table extends Widget
|
||||
{
|
||||
$this->rows = array_map(function($row) {
|
||||
return array_map(function($value) {
|
||||
return empty($value) && !is_numeric($value) ? ' ' : $value;
|
||||
return empty($value) && !is_numeric($value)
|
||||
? ' '
|
||||
: (is_array($value)
|
||||
? array_values($value)
|
||||
: $value);
|
||||
}, array_values($row));
|
||||
}, $rows);
|
||||
return $this;
|
||||
@ -252,18 +256,32 @@ class Table extends Widget
|
||||
if ($index !== 0) {
|
||||
$buffer .= $spanMiddle . ' ';
|
||||
}
|
||||
|
||||
$arrayFromMultilineString = false;
|
||||
if (is_string($cell)) {
|
||||
$cellLines = explode(PHP_EOL, $cell);
|
||||
if (count($cellLines) > 1) {
|
||||
$cell = $cellLines;
|
||||
$arrayFromMultilineString = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($cell)) {
|
||||
if (empty($renderedChunkTexts[$index])) {
|
||||
$renderedChunkTexts[$index] = '';
|
||||
$start = 0;
|
||||
$prefix = $this->listPrefix;
|
||||
$prefix = $arrayFromMultilineString ? '' : $this->listPrefix;
|
||||
if (!isset($arrayPointer[$index])) {
|
||||
$arrayPointer[$index] = 0;
|
||||
}
|
||||
} else {
|
||||
$start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset);
|
||||
}
|
||||
$chunk = Console::ansiColorizedSubstr($cell[$arrayPointer[$index]], $start, $cellSize - 4);
|
||||
$chunk = Console::ansiColorizedSubstr(
|
||||
$cell[$arrayPointer[$index]],
|
||||
$start,
|
||||
$cellSize - 2 - Console::ansiStrwidth($prefix)
|
||||
);
|
||||
$renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk);
|
||||
$fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]);
|
||||
if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) {
|
||||
@ -339,6 +357,9 @@ class Table extends Widget
|
||||
if (is_array($val)) {
|
||||
return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix);
|
||||
}
|
||||
if (is_string($val)) {
|
||||
return max(array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val)));
|
||||
}
|
||||
return Console::ansiStrwidth($val);
|
||||
}, $column)) + 2;
|
||||
$this->columnWidths[] = $columnWidth;
|
||||
@ -388,6 +409,9 @@ class Table extends Widget
|
||||
if (is_array($val)) {
|
||||
return array_map('yii\helpers\Console::ansiStrwidth', $val);
|
||||
}
|
||||
if (is_string($val)) {
|
||||
return array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val));
|
||||
}
|
||||
return Console::ansiStrwidth($val);
|
||||
}, $row));
|
||||
return max($rowsPerCell);
|
||||
|
@ -183,15 +183,11 @@ class ActiveDataProvider extends BaseDataProvider
|
||||
$sort->attributes[$attribute] = [
|
||||
'asc' => [$attribute => SORT_ASC],
|
||||
'desc' => [$attribute => SORT_DESC],
|
||||
'label' => $model->getAttributeLabel($attribute),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
foreach ($sort->attributes as $attribute => $config) {
|
||||
if (!isset($config['label'])) {
|
||||
$sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($sort->modelClass === null) {
|
||||
$sort->modelClass = $modelClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +191,12 @@ class Sort extends BaseObject
|
||||
* @since 2.0.33
|
||||
*/
|
||||
public $sortFlags = SORT_REGULAR;
|
||||
/**
|
||||
* @var string|null the name of the [[\yii\base\Model]]-based class used by the [[link()]] method to retrieve
|
||||
* attributes' labels. See [[link]] method for details.
|
||||
* @since 2.0.49
|
||||
*/
|
||||
public $modelClass;
|
||||
|
||||
|
||||
/**
|
||||
@ -363,7 +369,8 @@ class Sort extends BaseObject
|
||||
* @param array $options additional HTML attributes for the hyperlink tag.
|
||||
* There is one special attribute `label` which will be used as the label of the hyperlink.
|
||||
* If this is not set, the label defined in [[attributes]] will be used.
|
||||
* If no label is defined, [[\yii\helpers\Inflector::camel2words()]] will be called to get a label.
|
||||
* If no label is defined, it will be retrieved from the instance of [[modelClass]] (if [[modelClass]] is not null)
|
||||
* or generated from attribute name using [[\yii\helpers\Inflector::camel2words()]].
|
||||
* Note that it will not be HTML-encoded.
|
||||
* @return string the generated hyperlink
|
||||
* @throws InvalidConfigException if the attribute is unknown
|
||||
@ -388,6 +395,11 @@ class Sort extends BaseObject
|
||||
} else {
|
||||
if (isset($this->attributes[$attribute]['label'])) {
|
||||
$label = $this->attributes[$attribute]['label'];
|
||||
} elseif ($this->modelClass !== null) {
|
||||
$modelClass = $this->modelClass;
|
||||
/** @var \yii\base\Model $model */
|
||||
$model = $modelClass::instance();
|
||||
$label = $model->getAttributeLabel($attribute);
|
||||
} else {
|
||||
$label = Inflector::camel2words($attribute);
|
||||
}
|
||||
|
@ -144,6 +144,7 @@ interface ActiveRecordInterface extends StaticInstanceInterface
|
||||
* // Use where() to ignore the default condition
|
||||
* // SELECT FROM customer WHERE age>30
|
||||
* $customers = Customer::find()->where('age>30')->all();
|
||||
* ```
|
||||
*
|
||||
* @return ActiveQueryInterface the newly created [[ActiveQueryInterface]] instance.
|
||||
*/
|
||||
|
@ -282,7 +282,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
|
||||
if (array_key_exists($name, $this->_attributes)) {
|
||||
return $this->_attributes[$name];
|
||||
}
|
||||
|
||||
@ -290,7 +290,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
|
||||
if (array_key_exists($name, $this->_related)) {
|
||||
return $this->_related[$name];
|
||||
}
|
||||
$value = parent::__get($name);
|
||||
@ -576,13 +576,24 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
*/
|
||||
public function setOldAttribute($name, $value)
|
||||
{
|
||||
if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
|
||||
if ($this->canSetOldAttribute($name)) {
|
||||
$this->_oldAttributes[$name] = $value;
|
||||
} else {
|
||||
throw new InvalidArgumentException(get_class($this) . ' has no attribute named "' . $name . '".');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the old named attribute can be set.
|
||||
* @param string $name the attribute name
|
||||
* @return bool whether the old attribute can be set
|
||||
* @see setOldAttribute()
|
||||
*/
|
||||
public function canSetOldAttribute($name)
|
||||
{
|
||||
return (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an attribute dirty.
|
||||
* This method may be called to force updating a record when calling [[update()]],
|
||||
@ -639,7 +650,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
}
|
||||
} else {
|
||||
foreach ($this->_attributes as $name => $value) {
|
||||
if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $this->isAttributeDirty($name, $value))) {
|
||||
if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $this->isValueDifferent($value, $this->_oldAttributes[$name]))) {
|
||||
$attributes[$name] = $value;
|
||||
}
|
||||
}
|
||||
@ -1599,40 +1610,46 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
|
||||
/**
|
||||
* Returns the text label for the specified attribute.
|
||||
* If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
|
||||
* The attribute may be specified in a dot format to retrieve the label from related model or allow this model to override the label defined in related model.
|
||||
* For example, if the attribute is specified as 'relatedModel1.relatedModel2.attr' the function will return the first label definition it can find
|
||||
* in the following order:
|
||||
* - the label for 'relatedModel1.relatedModel2.attr' defined in [[attributeLabels()]] of this model;
|
||||
* - the label for 'relatedModel2.attr' defined in related model represented by relation 'relatedModel1' of this model;
|
||||
* - the label for 'attr' defined in related model represented by relation 'relatedModel2' of relation 'relatedModel1'.
|
||||
* If no label definition was found then the value of $this->generateAttributeLabel('relatedModel1.relatedModel2.attr') will be returned.
|
||||
* @param string $attribute the attribute name
|
||||
* @return string the attribute label
|
||||
* @see generateAttributeLabel()
|
||||
* @see attributeLabels()
|
||||
* @see generateAttributeLabel()
|
||||
*/
|
||||
public function getAttributeLabel($attribute)
|
||||
{
|
||||
$labels = $this->attributeLabels();
|
||||
if (isset($labels[$attribute])) {
|
||||
return $labels[$attribute];
|
||||
} elseif (strpos($attribute, '.')) {
|
||||
$attributeParts = explode('.', $attribute);
|
||||
$neededAttribute = array_pop($attributeParts);
|
||||
|
||||
$relatedModel = $this;
|
||||
foreach ($attributeParts as $relationName) {
|
||||
if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
|
||||
$relatedModel = $relatedModel->$relationName;
|
||||
} else {
|
||||
try {
|
||||
$relation = $relatedModel->getRelation($relationName);
|
||||
} catch (InvalidParamException $e) {
|
||||
return $this->generateAttributeLabel($attribute);
|
||||
}
|
||||
/* @var $modelClass ActiveRecordInterface */
|
||||
$modelClass = $relation->modelClass;
|
||||
$relatedModel = $modelClass::instance();
|
||||
}
|
||||
$model = $this;
|
||||
$modelAttribute = $attribute;
|
||||
for (;;) {
|
||||
$labels = $model->attributeLabels();
|
||||
if (isset($labels[$modelAttribute])) {
|
||||
return $labels[$modelAttribute];
|
||||
}
|
||||
|
||||
$labels = $relatedModel->attributeLabels();
|
||||
if (isset($labels[$neededAttribute])) {
|
||||
return $labels[$neededAttribute];
|
||||
$parts = explode('.', $modelAttribute, 2);
|
||||
if (count($parts) < 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
list ($relationName, $modelAttribute) = $parts;
|
||||
|
||||
if ($model->isRelationPopulated($relationName) && $model->$relationName instanceof self) {
|
||||
$model = $model->$relationName;
|
||||
} else {
|
||||
try {
|
||||
$relation = $model->getRelation($relationName);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
break;
|
||||
}
|
||||
/* @var $modelClass ActiveRecordInterface */
|
||||
$modelClass = $relation->modelClass;
|
||||
$model = $modelClass::instance();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1756,18 +1773,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param mixed $newValue
|
||||
* @param mixed $oldValue
|
||||
* @return bool
|
||||
* @since 2.0.48
|
||||
*/
|
||||
private function isAttributeDirty($attribute, $value)
|
||||
private function isValueDifferent($newValue, $oldValue)
|
||||
{
|
||||
$old_attribute = $this->oldAttributes[$attribute];
|
||||
if (is_array($value) && is_array($this->oldAttributes[$attribute])) {
|
||||
$value = ArrayHelper::recursiveSort($value);
|
||||
$old_attribute = ArrayHelper::recursiveSort($old_attribute);
|
||||
if (is_array($newValue) && is_array($oldValue) && ArrayHelper::isAssociative($oldValue)) {
|
||||
$newValue = ArrayHelper::recursiveSort($newValue);
|
||||
$oldValue = ArrayHelper::recursiveSort($oldValue);
|
||||
}
|
||||
|
||||
return $value !== $old_attribute;
|
||||
return $newValue !== $oldValue;
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ class Command extends Component
|
||||
$forRead = false;
|
||||
}
|
||||
if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
|
||||
$pdo = $this->db->getSlavePdo();
|
||||
$pdo = $this->db->getSlavePdo(true);
|
||||
} else {
|
||||
$pdo = $this->db->getMasterPdo();
|
||||
}
|
||||
@ -377,6 +377,13 @@ class Command extends Component
|
||||
$this->pendingParams[$name] = [$value->getValue(), $value->getType()];
|
||||
$this->params[$name] = $value->getValue();
|
||||
} else {
|
||||
if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
|
||||
if ($value instanceof \BackedEnum) {
|
||||
$value = $value->value;
|
||||
} elseif ($value instanceof \UnitEnum) {
|
||||
$value = $value->name;
|
||||
}
|
||||
}
|
||||
$type = $schema->getPdoType($value);
|
||||
$this->pendingParams[$name] = [$value, $type];
|
||||
$this->params[$name] = $value;
|
||||
@ -631,7 +638,8 @@ class Command extends Component
|
||||
*
|
||||
* The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
|
||||
* where name stands for a column name which will be properly quoted by the method, and definition
|
||||
* stands for the column type which can contain an abstract DB type.
|
||||
* stands for the column type which must contain an abstract DB type.
|
||||
*
|
||||
* The method [[QueryBuilder::getColumnType()]] will be called
|
||||
* to convert the abstract column types to physical ones. For example, `string` will be converted
|
||||
* as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
|
||||
@ -639,6 +647,16 @@ class Command extends Component
|
||||
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
|
||||
* inserted into the generated SQL.
|
||||
*
|
||||
* Example usage:
|
||||
* ```php
|
||||
* Yii::$app->db->createCommand()->createTable('post', [
|
||||
* 'id' => 'pk',
|
||||
* 'title' => 'string',
|
||||
* 'text' => 'text',
|
||||
* 'column_name double precision null default null',
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param string $table the name of the table to be created. The name will be properly quoted by the method.
|
||||
* @param array $columns the columns (name => definition) in the new table.
|
||||
* @param string|null $options additional SQL fragment that will be appended to the generated SQL.
|
||||
|
@ -1013,7 +1013,7 @@ class Connection extends Component
|
||||
if (($pos = strpos((string)$this->dsn, ':')) !== false) {
|
||||
$this->_driverName = strtolower(substr($this->dsn, 0, $pos));
|
||||
} else {
|
||||
$this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
|
||||
$this->_driverName = strtolower($this->getSlavePdo(true)->getAttribute(PDO::ATTR_DRIVER_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,13 +305,25 @@ class Migration extends Component implements MigrationInterface
|
||||
*
|
||||
* The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
|
||||
* where name stands for a column name which will be properly quoted by the method, and definition
|
||||
* stands for the column type which can contain an abstract DB type.
|
||||
* stands for the column type which must contain an abstract DB type.
|
||||
*
|
||||
* The [[QueryBuilder::getColumnType()]] method will be invoked to convert any abstract type into a physical one.
|
||||
*
|
||||
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
|
||||
* put into the generated SQL.
|
||||
*
|
||||
* Example usage:
|
||||
* ```php
|
||||
* class m200000_000000_create_table_fruits extends \yii\db\Migration
|
||||
* {
|
||||
* public function safeUp()
|
||||
* {
|
||||
* $this->createTable('{{%fruits}}', [
|
||||
* // ...
|
||||
* 'column_name double precision null default null',
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @param string $table the name of the table to be created. The name will be properly quoted by the method.
|
||||
* @param array $columns the columns (name => definition) in the new table.
|
||||
* @param string|null $options additional SQL fragment that will be appended to the generated SQL.
|
||||
|
@ -1049,7 +1049,7 @@ PATTERN;
|
||||
|
||||
/**
|
||||
* Sets the GROUP BY part of the query.
|
||||
* @param string|array|ExpressionInterface $columns the columns to be grouped by.
|
||||
* @param string|array|ExpressionInterface|null $columns the columns to be grouped by.
|
||||
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
|
||||
* The method will automatically quote the column names unless a column contains some parenthesis
|
||||
* (which means the column contains a DB expression).
|
||||
@ -1067,7 +1067,7 @@ PATTERN;
|
||||
{
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
$columns = [$columns];
|
||||
} elseif (!is_array($columns)) {
|
||||
} elseif (!is_array($columns) && !is_null($columns)) {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
$this->groupBy = $columns;
|
||||
|
@ -692,7 +692,7 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
*
|
||||
* The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
|
||||
* where name stands for a column name which will be properly quoted by the method, and definition
|
||||
* stands for the column type which can contain an abstract DB type.
|
||||
* stands for the column type which must contain an abstract DB type.
|
||||
* The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
|
||||
*
|
||||
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
|
||||
@ -705,6 +705,7 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* 'id' => 'pk',
|
||||
* 'name' => 'string',
|
||||
* 'age' => 'integer',
|
||||
* 'column_name double precision null default null', # definition only example
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
|
@ -458,7 +458,7 @@ abstract class Schema extends BaseObject
|
||||
return $str;
|
||||
}
|
||||
|
||||
if (mb_stripos($this->db->dsn, 'odbc:') === false && ($value = $this->db->getSlavePdo()->quote($str)) !== false) {
|
||||
if (mb_stripos((string)$this->db->dsn, 'odbc:') === false && ($value = $this->db->getSlavePdo(true)->quote($str)) !== false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
@ -695,7 +695,7 @@ abstract class Schema extends BaseObject
|
||||
public function getServerVersion()
|
||||
{
|
||||
if ($this->_serverVersion === null) {
|
||||
$this->_serverVersion = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$this->_serverVersion = $this->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
return $this->_serverVersion;
|
||||
}
|
||||
@ -809,7 +809,7 @@ abstract class Schema extends BaseObject
|
||||
*/
|
||||
protected function normalizePdoRowKeyCase(array $row, $multiple)
|
||||
{
|
||||
if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
|
||||
if ($this->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,8 @@ use yii\base\InvalidArgumentException;
|
||||
class LikeCondition extends SimpleCondition
|
||||
{
|
||||
/**
|
||||
* @var array|null|false map of chars to their replacements, false if characters should not be escaped
|
||||
* or either null or empty array if escaping is condition builder responsibility.
|
||||
* @var array|null|false map of chars to their replacements, `false` if characters should not be escaped
|
||||
* or either `null` or empty array if escaping is condition builder responsibility.
|
||||
* By default it's set to `null`.
|
||||
*/
|
||||
protected $escapingReplacements;
|
||||
@ -40,9 +40,10 @@ class LikeCondition extends SimpleCondition
|
||||
/**
|
||||
* This method allows to specify how to escape special characters in the value(s).
|
||||
*
|
||||
* @param array an array of mappings from the special characters to their escaped counterparts.
|
||||
* You may use `false` or an empty array to indicate the values are already escaped and no escape
|
||||
* should be applied. Note that when using an escape mapping (or the third operand is not provided),
|
||||
* @param array|null|false an array of mappings from the special characters to their escaped counterparts.
|
||||
* You may use `false` to indicate the values are already escaped and no escape should be applied,
|
||||
* or either `null` or empty array if escaping is condition builder responsibility.
|
||||
* Note that when using an escape mapping (or the third operand is not provided),
|
||||
* the values will be automatically enclosed within a pair of percentage characters.
|
||||
*/
|
||||
public function setEscapingReplacements($escapingReplacements)
|
||||
@ -51,7 +52,7 @@ class LikeCondition extends SimpleCondition
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|false
|
||||
* @return array|null|false
|
||||
*/
|
||||
public function getEscapingReplacements()
|
||||
{
|
||||
|
@ -78,7 +78,7 @@ class LikeConditionBuilder implements ExpressionBuilderInterface
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$phName = $this->queryBuilder->buildExpression($value, $params);
|
||||
} else {
|
||||
$phName = $this->queryBuilder->bindParam(empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'), $params);
|
||||
$phName = $this->queryBuilder->bindParam(empty($escape) ? $value : ('%' . strtr((string)$value, $escape) . '%'), $params);
|
||||
}
|
||||
$parts[] = "{$column} {$operator} {$phName}{$escapeSql}";
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
|
||||
*/
|
||||
protected function findTableNames($schema = '')
|
||||
{
|
||||
$pdo = $this->db->getSlavePdo();
|
||||
$pdo = $this->db->getSlavePdo(true);
|
||||
$tables = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
|
||||
$tableNames = [];
|
||||
foreach ($tables as $table) {
|
||||
@ -110,7 +110,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
|
||||
*/
|
||||
protected function loadTableSchema($name)
|
||||
{
|
||||
$pdo = $this->db->getSlavePdo();
|
||||
$pdo = $this->db->getSlavePdo(true);
|
||||
|
||||
$tableInfo = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name);
|
||||
|
||||
@ -159,7 +159,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
|
||||
*/
|
||||
protected function loadTablePrimaryKey($tableName)
|
||||
{
|
||||
$primaryKey = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $tableName);
|
||||
$primaryKey = $this->db->getSlavePdo(true)->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $tableName);
|
||||
if (empty($primaryKey)) {
|
||||
return null;
|
||||
}
|
||||
@ -183,7 +183,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
|
||||
3 => 'SET NULL',
|
||||
];
|
||||
|
||||
$foreignKeys = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $tableName);
|
||||
$foreignKeys = $this->db->getSlavePdo(true)->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $tableName);
|
||||
$foreignKeys = ArrayHelper::index($foreignKeys, null, 'FK_NAME');
|
||||
ArrayHelper::multisort($foreignKeys, 'KEY_SEQ', SORT_ASC, SORT_NUMERIC);
|
||||
$result = [];
|
||||
@ -386,7 +386,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
|
||||
*/
|
||||
private function loadTableConstraints($tableName, $returnType)
|
||||
{
|
||||
$constraints = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_CONSTRAINT, $tableName);
|
||||
$constraints = $this->db->getSlavePdo(true)->cubrid_schema(\PDO::CUBRID_SCH_CONSTRAINT, $tableName);
|
||||
$constraints = ArrayHelper::index($constraints, null, ['TYPE', 'NAME']);
|
||||
ArrayHelper::multisort($constraints, 'KEY_ORDER', SORT_ASC, SORT_NUMERIC);
|
||||
$result = [
|
||||
|
@ -460,10 +460,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
$columnSchemas = $tableSchema->columns;
|
||||
foreach ($columns as $name => $value) {
|
||||
// @see https://github.com/yiisoft/yii2/issues/12599
|
||||
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && (is_string($value) || $value === null)) {
|
||||
$phName = $this->bindParam($value, $params);
|
||||
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && (is_string($value))) {
|
||||
// @see https://github.com/yiisoft/yii2/issues/12599
|
||||
$columns[$name] = new Expression("CONVERT(VARBINARY(MAX), $phName)", $params);
|
||||
$columns[$name] = new Expression('CONVERT(VARBINARY(MAX), ' . ('0x' . bin2hex($value)) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
}
|
||||
$version = $cache ? $cache->get($key) : null;
|
||||
if (!$version) {
|
||||
$version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$version = $this->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
if ($cache) {
|
||||
$cache->set($key, $version, $this->db->schemaCacheDuration);
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ SQL;
|
||||
protected function isOldMysql()
|
||||
{
|
||||
if ($this->_oldMysql === null) {
|
||||
$version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$version = $this->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$this->_oldMysql = version_compare($version, '5.1', '<=');
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,12 @@ class BaseArrayHelper
|
||||
*/
|
||||
public static function remove(&$array, $key, $default = null)
|
||||
{
|
||||
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
||||
// ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2)
|
||||
if (is_float($key)) {
|
||||
$key = (int)$key;
|
||||
}
|
||||
|
||||
if (is_array($array) && array_key_exists($key, $array)) {
|
||||
$value = $array[$key];
|
||||
unset($array[$key]);
|
||||
|
||||
@ -608,17 +613,20 @@ class BaseArrayHelper
|
||||
* Checks if the given array contains the specified key.
|
||||
* This method enhances the `array_key_exists()` function by supporting case-insensitive
|
||||
* key comparison.
|
||||
* @param string $key the key to check
|
||||
* @param string|int $key the key to check
|
||||
* @param array|ArrayAccess $array the array with keys to check
|
||||
* @param bool $caseSensitive whether the key comparison should be case-sensitive
|
||||
* @return bool whether the array contains the specified key
|
||||
*/
|
||||
public static function keyExists($key, $array, $caseSensitive = true)
|
||||
{
|
||||
// ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2)
|
||||
if (is_float($key)) {
|
||||
$key = (int)$key;
|
||||
}
|
||||
|
||||
if ($caseSensitive) {
|
||||
// Function `isset` checks key faster but skips `null`, `array_key_exists` handles this case
|
||||
// https://www.php.net/manual/en/function.array-key-exists.php#107786
|
||||
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
||||
if (is_array($array) && array_key_exists($key, $array)) {
|
||||
return true;
|
||||
}
|
||||
// Cannot use `array_has_key` on Objects for PHP 7.4+, therefore we need to check using [[ArrayAccess::offsetExists()]]
|
||||
|
@ -948,13 +948,17 @@ class BaseConsole
|
||||
* @param string $prompt the prompt message
|
||||
* @param array $options Key-value array of options to choose from. Key is what is inputed and used, value is
|
||||
* what's displayed to end user by help command.
|
||||
* @param string|null $default value to use when the user doesn't provide an option.
|
||||
* If the default is `null`, the user is required to select an option.
|
||||
*
|
||||
* @return string An option character the user chose
|
||||
* @since 2.0.49 Added the $default argument
|
||||
*/
|
||||
public static function select($prompt, $options = [])
|
||||
public static function select($prompt, $options = [], $default = null)
|
||||
{
|
||||
top:
|
||||
static::stdout("$prompt [" . implode(',', array_keys($options)) . ',?]: ');
|
||||
static::stdout("$prompt (" . implode(',', array_keys($options)) . ',?)'
|
||||
. ($default !== null ? '[' . $default . ']' : '') . ': ');
|
||||
$input = static::stdin();
|
||||
if ($input === '?') {
|
||||
foreach ($options as $key => $value) {
|
||||
@ -962,6 +966,8 @@ class BaseConsole
|
||||
}
|
||||
static::output(' ? - Show help');
|
||||
goto top;
|
||||
} elseif ($default !== null && $input === '') {
|
||||
return $default;
|
||||
} elseif (!array_key_exists($input, $options)) {
|
||||
goto top;
|
||||
}
|
||||
|
@ -39,6 +39,11 @@ class BaseFileHelper
|
||||
* @since 2.0.14
|
||||
*/
|
||||
public static $mimeAliasesFile = '@yii/helpers/mimeAliases.php';
|
||||
/**
|
||||
* @var string the path (or alias) of a PHP file containing extensions per MIME type.
|
||||
* @since 2.0.48
|
||||
*/
|
||||
public static $mimeExtensionsFile = '@yii/helpers/mimeExtensions.php';
|
||||
|
||||
|
||||
/**
|
||||
@ -213,10 +218,49 @@ class BaseFileHelper
|
||||
$mimeType = $aliases[$mimeType];
|
||||
}
|
||||
|
||||
// Note: For backwards compatibility the "MimeTypes" file is used.
|
||||
$mimeTypes = static::loadMimeTypes($magicFile);
|
||||
return array_keys($mimeTypes, mb_strtolower($mimeType, 'UTF-8'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the most common extension by given MIME type.
|
||||
* This method will use a local map between MIME types and extension names.
|
||||
* @param string $mimeType file MIME type.
|
||||
* @param bool $preferShort return an extension with a maximum of 3 characters.
|
||||
* @param string|null $magicFile the path (or alias) of the file that contains all available MIME type information.
|
||||
* If this is not set, the file specified by [[mimeMagicFile]] will be used.
|
||||
* @return string|null the extensions corresponding to the specified MIME type
|
||||
* @since 2.0.48
|
||||
*/
|
||||
public static function getExtensionByMimeType($mimeType, $preferShort = false, $magicFile = null)
|
||||
{
|
||||
$aliases = static::loadMimeAliases(static::$mimeAliasesFile);
|
||||
if (isset($aliases[$mimeType])) {
|
||||
$mimeType = $aliases[$mimeType];
|
||||
}
|
||||
|
||||
$mimeExtensions = static::loadMimeExtensions($magicFile);
|
||||
|
||||
if (!array_key_exists($mimeType, $mimeExtensions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$extensions = $mimeExtensions[$mimeType];
|
||||
if (is_array($extensions)) {
|
||||
if ($preferShort) {
|
||||
foreach ($extensions as $extension) {
|
||||
if (mb_strlen($extension, 'UTF-8') <= 3) {
|
||||
return $extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $extensions[0];
|
||||
} else {
|
||||
return $extensions;
|
||||
}
|
||||
}
|
||||
|
||||
private static $_mimeTypes = [];
|
||||
|
||||
/**
|
||||
@ -260,6 +304,28 @@ class BaseFileHelper
|
||||
return self::$_mimeAliases[$aliasesFile];
|
||||
}
|
||||
|
||||
private static $_mimeExtensions = [];
|
||||
|
||||
/**
|
||||
* Loads MIME extensions from the specified file.
|
||||
* @param string|null $extensionsFile the path (or alias) of the file that contains MIME type aliases.
|
||||
* If this is not set, the file specified by [[mimeAliasesFile]] will be used.
|
||||
* @return array the mapping from file extensions to MIME types
|
||||
* @since 2.0.48
|
||||
*/
|
||||
protected static function loadMimeExtensions($extensionsFile)
|
||||
{
|
||||
if ($extensionsFile === null) {
|
||||
$extensionsFile = static::$mimeExtensionsFile;
|
||||
}
|
||||
$extensionsFile = Yii::getAlias($extensionsFile);
|
||||
if (!isset(self::$_mimeExtensions[$extensionsFile])) {
|
||||
self::$_mimeExtensions[$extensionsFile] = require $extensionsFile;
|
||||
}
|
||||
|
||||
return self::$_mimeExtensions[$extensionsFile];
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a whole directory as another one.
|
||||
* The files and sub-directories will also be copied over.
|
||||
|
@ -49,7 +49,7 @@ class BaseStringHelper
|
||||
$length = static::byteLength($string);
|
||||
}
|
||||
|
||||
return mb_substr($string, $start, $length, '8bit');
|
||||
return mb_substr((string)$string, $start, $length, '8bit');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,6 +67,8 @@ class BaseStringHelper
|
||||
*/
|
||||
public static function basename($path, $suffix = '')
|
||||
{
|
||||
$path = (string)$path;
|
||||
|
||||
$len = mb_strlen($suffix);
|
||||
if ($len > 0 && mb_substr($path, -$len) === $suffix) {
|
||||
$path = mb_substr($path, 0, -$len);
|
||||
@ -93,7 +95,7 @@ class BaseStringHelper
|
||||
public static function dirname($path)
|
||||
{
|
||||
$normalizedPath = rtrim(
|
||||
str_replace('\\', '/', $path),
|
||||
str_replace('\\', '/', (string)$path),
|
||||
'/'
|
||||
);
|
||||
$separatorPosition = mb_strrpos($normalizedPath, '/');
|
||||
@ -122,6 +124,8 @@ class BaseStringHelper
|
||||
*/
|
||||
public static function truncate($string, $length, $suffix = '...', $encoding = null, $asHtml = false)
|
||||
{
|
||||
$string = (string)$string;
|
||||
|
||||
if ($encoding === null) {
|
||||
$encoding = Yii::$app ? Yii::$app->charset : 'UTF-8';
|
||||
}
|
||||
@ -233,6 +237,9 @@ class BaseStringHelper
|
||||
*/
|
||||
public static function startsWith($string, $with, $caseSensitive = true)
|
||||
{
|
||||
$string = (string)$string;
|
||||
$with = (string)$with;
|
||||
|
||||
if (!$bytes = static::byteLength($with)) {
|
||||
return true;
|
||||
}
|
||||
@ -257,6 +264,9 @@ class BaseStringHelper
|
||||
*/
|
||||
public static function endsWith($string, $with, $caseSensitive = true)
|
||||
{
|
||||
$string = (string)$string;
|
||||
$with = (string)$with;
|
||||
|
||||
if (!$bytes = static::byteLength($with)) {
|
||||
return true;
|
||||
}
|
||||
|
1238
framework/helpers/mimeExtensions.php
Normal file
1238
framework/helpers/mimeExtensions.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
||||
* This file has been placed in the public domain for unlimited redistribution.
|
||||
*/
|
||||
$mimeTypes = [
|
||||
123 => 'application/vnd.lotus-1-2-3',
|
||||
'3dml' => 'text/vnd.in3d.3dml',
|
||||
'3ds' => 'image/x-3ds',
|
||||
'3g2' => 'video/3gpp2',
|
||||
@ -37,6 +38,7 @@ $mimeTypes = [
|
||||
'ait' => 'application/vnd.dvb.ait',
|
||||
'ami' => 'application/vnd.amiga.ami',
|
||||
'apk' => 'application/vnd.android.package-archive',
|
||||
'apng' => 'image/apng',
|
||||
'appcache' => 'text/cache-manifest',
|
||||
'application' => 'application/x-ms-application',
|
||||
'apr' => 'application/vnd.lotus-approach',
|
||||
@ -53,6 +55,7 @@ $mimeTypes = [
|
||||
'atx' => 'application/vnd.antix.game-component',
|
||||
'au' => 'audio/basic',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'avif' => 'image/avif',
|
||||
'aw' => 'application/applixware',
|
||||
'azf' => 'application/vnd.airzip.filesecure.azf',
|
||||
'azs' => 'application/vnd.airzip.filesecure.azs',
|
||||
@ -279,6 +282,7 @@ $mimeTypes = [
|
||||
'geo' => 'application/vnd.dynageo',
|
||||
'gex' => 'application/vnd.geometry-explorer',
|
||||
'ggb' => 'application/vnd.geogebra.file',
|
||||
'ggs' => 'application/vnd.geogebra.slides',
|
||||
'ggt' => 'application/vnd.geogebra.tool',
|
||||
'ghf' => 'application/vnd.groove-help',
|
||||
'gif' => 'image/gif',
|
||||
@ -318,12 +322,11 @@ $mimeTypes = [
|
||||
'htke' => 'application/vnd.kenameaapp',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'hvd' => 'application/vnd.yamaha.hv-dic',
|
||||
'hvp' => 'application/vnd.yamaha.hv-voice',
|
||||
'hvs' => 'application/vnd.yamaha.hv-script',
|
||||
'i2g' => 'application/vnd.intergeo',
|
||||
'icc' => 'application/vnd.iccprofile',
|
||||
0 => 'application/vnd.lotus-1-2-3',
|
||||
'hvd' => 'application/vnd.yamaha.hv-dic',
|
||||
'ice' => 'x-conference/x-cooltalk',
|
||||
'icm' => 'application/vnd.iccprofile',
|
||||
'ico' => 'image/x-icon',
|
||||
@ -356,6 +359,7 @@ $mimeTypes = [
|
||||
'jam' => 'application/vnd.jam',
|
||||
'jar' => 'application/java-archive',
|
||||
'java' => 'text/x-java-source',
|
||||
'jfif' => 'image/jpeg',
|
||||
'jisp' => 'application/vnd.jisp',
|
||||
'jlt' => 'application/vnd.hp-jlyt',
|
||||
'jnlp' => 'application/x-java-jnlp-file',
|
||||
@ -598,6 +602,8 @@ $mimeTypes = [
|
||||
'pgn' => 'application/x-chess-pgn',
|
||||
'pgp' => 'application/pgp-encrypted',
|
||||
'pic' => 'image/x-pict',
|
||||
'pjp' => 'image/jpeg',
|
||||
'pjpeg' => 'image/jpeg',
|
||||
'pkg' => 'application/octet-stream',
|
||||
'pki' => 'application/pkixcmp',
|
||||
'pkipath' => 'application/pkix-pkipath',
|
||||
@ -882,6 +888,7 @@ $mimeTypes = [
|
||||
'vxml' => 'application/voicexml+xml',
|
||||
'w3d' => 'application/x-director',
|
||||
'wad' => 'application/x-doom',
|
||||
'wasm' => 'application/wasm',
|
||||
'wav' => 'audio/x-wav',
|
||||
'wax' => 'audio/x-ms-wax',
|
||||
'wbmp' => 'image/vnd.wap.wbmp',
|
||||
@ -996,8 +1003,9 @@ $mimeTypes = [
|
||||
'zmm' => 'application/vnd.handheld-entertainment+xml',
|
||||
];
|
||||
|
||||
if (PHP_VERSION_ID >= 80100) {
|
||||
# fix for bundled libmagic bug, see also https://github.com/yiisoft/yii2/issues/19925
|
||||
if ((PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80122) || (PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80209)) {
|
||||
$mimeTypes = array_replace($mimeTypes, array('xz' => 'application/octet-stream'));
|
||||
}
|
||||
|
||||
return $mimeTypes;
|
||||
return $mimeTypes;
|
||||
|
@ -460,7 +460,7 @@ class Formatter extends Component
|
||||
}
|
||||
$method = 'as' . $format;
|
||||
if ($this->hasMethod($method)) {
|
||||
return call_user_func_array([$this, $method], $params);
|
||||
return call_user_func_array([$this, $method], array_values($params));
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Unknown format type: $format");
|
||||
|
@ -106,12 +106,18 @@ class FileTarget extends Target
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
|
||||
$trimmedText = trim($text);
|
||||
|
||||
if (empty($trimmedText)) {
|
||||
return; // No messages to export, so we exit the function early
|
||||
}
|
||||
|
||||
if (strpos($this->logFile, '://') === false || strncmp($this->logFile, 'file://', 7) === 0) {
|
||||
$logPath = dirname($this->logFile);
|
||||
FileHelper::createDirectory($logPath, $this->dirMode, true);
|
||||
}
|
||||
|
||||
$text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
|
||||
if (($fp = @fopen($this->logFile, 'a')) === false) {
|
||||
throw new InvalidConfigException("Unable to append to log file: {$this->logFile}");
|
||||
}
|
||||
|
@ -882,6 +882,9 @@ class DbManager extends BaseManager
|
||||
])->execute();
|
||||
|
||||
unset($this->checkAccessAssignments[(string) $userId]);
|
||||
|
||||
$this->invalidateCache();
|
||||
|
||||
return $assignment;
|
||||
}
|
||||
|
||||
@ -895,9 +898,13 @@ class DbManager extends BaseManager
|
||||
}
|
||||
|
||||
unset($this->checkAccessAssignments[(string) $userId]);
|
||||
return $this->db->createCommand()
|
||||
$result = $this->db->createCommand()
|
||||
->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
|
||||
->execute() > 0;
|
||||
|
||||
$this->invalidateCache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -910,9 +917,13 @@ class DbManager extends BaseManager
|
||||
}
|
||||
|
||||
unset($this->checkAccessAssignments[(string) $userId]);
|
||||
return $this->db->createCommand()
|
||||
$result = $this->db->createCommand()
|
||||
->delete($this->assignmentTable, ['user_id' => (string) $userId])
|
||||
->execute() > 0;
|
||||
|
||||
$this->invalidateCache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,10 +294,9 @@ class Serializer extends Component
|
||||
*/
|
||||
protected function serializeModels(array $models)
|
||||
{
|
||||
list($fields, $expand) = $this->getRequestedFields();
|
||||
foreach ($models as $i => $model) {
|
||||
if ($model instanceof Arrayable) {
|
||||
$models[$i] = $model->toArray($fields, $expand);
|
||||
$models[$i] = $this->serializeModel($model);
|
||||
} elseif (is_array($model)) {
|
||||
$models[$i] = ArrayHelper::toArray($model);
|
||||
}
|
||||
|
@ -17,10 +17,6 @@ use yii\helpers\Url;
|
||||
*
|
||||
* For more details and usage information on Controller, see the [guide article on controllers](guide:structure-controllers).
|
||||
*
|
||||
* @property Request $request The request object.
|
||||
* @property Response $response The response object.
|
||||
* @property View $view The view object that can be used to render views or view files.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
@ -145,6 +141,7 @@ class Controller extends \yii\base\Controller
|
||||
} elseif (
|
||||
PHP_VERSION_ID >= 70000
|
||||
&& ($type = $param->getType()) !== null
|
||||
&& method_exists($type, 'isBuiltin')
|
||||
&& $type->isBuiltin()
|
||||
&& ($params[$name] !== null || !$type->allowsNull())
|
||||
) {
|
||||
|
@ -57,8 +57,8 @@ class Cookie extends \yii\base\BaseObject
|
||||
*/
|
||||
public $domain = '';
|
||||
/**
|
||||
* @var int the timestamp at which the cookie expires. This is the server timestamp.
|
||||
* Defaults to 0, meaning "until the browser is closed".
|
||||
* @var int|string|\DateTimeInterface|null the timestamp or date at which the cookie expires. This is the server timestamp.
|
||||
* Defaults to 0, meaning "until the browser is closed" (the same applies to `null`).
|
||||
*/
|
||||
public $expire = 0;
|
||||
/**
|
||||
|
@ -18,7 +18,6 @@ use yii\base\InvalidCallException;
|
||||
* For more details and usage information on CookieCollection, see the [guide article on handling cookies](guide:runtime-sessions-cookies).
|
||||
*
|
||||
* @property-read int $count The number of cookies in the collection.
|
||||
* @property-read ArrayIterator $iterator An iterator for traversing the cookies in the collection.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
@ -52,7 +51,7 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
||||
* Returns an iterator for traversing the cookies in the collection.
|
||||
* This method is required by the SPL interface [[\IteratorAggregate]].
|
||||
* It will be implicitly called when you use `foreach` to traverse the collection.
|
||||
* @return ArrayIterator an iterator for traversing the cookies in the collection.
|
||||
* @return ArrayIterator<string, Cookie> an iterator for traversing the cookies in the collection.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
@ -114,7 +113,18 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->_cookies[$name]) && $this->_cookies[$name]->value !== ''
|
||||
&& ($this->_cookies[$name]->expire === null || $this->_cookies[$name]->expire === 0 || $this->_cookies[$name]->expire >= time());
|
||||
&& ($this->_cookies[$name]->expire === null
|
||||
|| $this->_cookies[$name]->expire === 0
|
||||
|| (
|
||||
(is_string($this->_cookies[$name]->expire) && strtotime($this->_cookies[$name]->expire) >= time())
|
||||
|| (
|
||||
interface_exists('\\DateTimeInterface')
|
||||
&& $this->_cookies[$name]->expire instanceof \DateTimeInterface
|
||||
&& $this->_cookies[$name]->expire->getTimestamp() >= time()
|
||||
)
|
||||
|| $this->_cookies[$name]->expire >= time()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,7 +185,7 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
||||
|
||||
/**
|
||||
* Returns the collection as a PHP array.
|
||||
* @return array the array representation of the collection.
|
||||
* @return Cookie[] the array representation of the collection.
|
||||
* The array keys are cookie names, and the array values are the corresponding cookie objects.
|
||||
*/
|
||||
public function toArray()
|
||||
|
@ -13,9 +13,6 @@ use yii\base\BaseObject;
|
||||
/**
|
||||
* HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers.
|
||||
*
|
||||
* @property-read int $count The number of headers in the collection.
|
||||
* @property-read \ArrayIterator $iterator An iterator for traversing the headers in the collection.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
|
@ -1220,6 +1220,8 @@ class Request extends \yii\base\Request
|
||||
return null;
|
||||
}
|
||||
|
||||
private $_ip = null;
|
||||
|
||||
/**
|
||||
* Returns the user IP address.
|
||||
* The IP is determined using headers and / or `$_SERVER` variables.
|
||||
@ -1227,8 +1229,14 @@ class Request extends \yii\base\Request
|
||||
*/
|
||||
public function getUserIP()
|
||||
{
|
||||
$ip = $this->getUserIpFromIpHeaders();
|
||||
return $ip === null ? $this->getRemoteIP() : $ip;
|
||||
if ($this->_ip === null) {
|
||||
$this->_ip = $this->getUserIpFromIpHeaders();
|
||||
if ($this->_ip === null) {
|
||||
$this->_ip = $this->getRemoteIP();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_ip;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1902,6 +1910,8 @@ class Request extends \yii\base\Request
|
||||
return null;
|
||||
}
|
||||
|
||||
private $_secureForwardedHeaderTrustedParts;
|
||||
|
||||
/**
|
||||
* Gets only trusted `Forwarded` header parts
|
||||
*
|
||||
@ -1911,6 +1921,10 @@ class Request extends \yii\base\Request
|
||||
*/
|
||||
protected function getSecureForwardedHeaderTrustedParts()
|
||||
{
|
||||
if ($this->_secureForwardedHeaderTrustedParts !== null) {
|
||||
return $this->_secureForwardedHeaderTrustedParts;
|
||||
}
|
||||
|
||||
$validator = $this->getIpValidator();
|
||||
$trustedHosts = [];
|
||||
foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
|
||||
@ -1921,9 +1935,14 @@ class Request extends \yii\base\Request
|
||||
}
|
||||
$validator->setRanges($trustedHosts);
|
||||
|
||||
return array_filter($this->getSecureForwardedHeaderParts(), function ($headerPart) use ($validator) {
|
||||
return isset($headerPart['for']) ? !$validator->validate($headerPart['for']) : true;
|
||||
});
|
||||
$this->_secureForwardedHeaderTrustedParts = array_filter(
|
||||
$this->getSecureForwardedHeaderParts(),
|
||||
function ($headerPart) use ($validator) {
|
||||
return isset($headerPart['for']) ? !$validator->validate($headerPart['for']) : true;
|
||||
}
|
||||
);
|
||||
|
||||
return $this->_secureForwardedHeaderTrustedParts;
|
||||
}
|
||||
|
||||
private $_secureForwardedHeaderParts;
|
||||
|
@ -10,6 +10,7 @@ namespace yii\web;
|
||||
use Yii;
|
||||
use yii\base\InvalidArgumentException;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\base\InvalidRouteException;
|
||||
use yii\helpers\FileHelper;
|
||||
use yii\helpers\Inflector;
|
||||
use yii\helpers\StringHelper;
|
||||
@ -400,12 +401,21 @@ class Response extends \yii\base\Response
|
||||
}
|
||||
foreach ($this->getCookies() as $cookie) {
|
||||
$value = $cookie->value;
|
||||
if ($cookie->expire != 1 && isset($validationKey)) {
|
||||
$expire = $cookie->expire;
|
||||
if (is_string($expire)) {
|
||||
$expire = strtotime($expire);
|
||||
} elseif (interface_exists('\\DateTimeInterface') && $expire instanceof \DateTimeInterface) {
|
||||
$expire = $expire->getTimestamp();
|
||||
}
|
||||
if ($expire === null || $expire === false) {
|
||||
$expire = 0;
|
||||
}
|
||||
if ($expire != 1 && isset($validationKey)) {
|
||||
$value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
|
||||
}
|
||||
if (PHP_VERSION_ID >= 70300) {
|
||||
setcookie($cookie->name, $value, [
|
||||
'expires' => $cookie->expire,
|
||||
'expires' => $expire,
|
||||
'path' => $cookie->path,
|
||||
'domain' => $cookie->domain,
|
||||
'secure' => $cookie->secure,
|
||||
@ -419,7 +429,7 @@ class Response extends \yii\base\Response
|
||||
if (!is_null($cookie->sameSite)) {
|
||||
$cookiePath .= '; samesite=' . $cookie->sameSite;
|
||||
}
|
||||
setcookie($cookie->name, $value, $cookie->expire, $cookiePath, $cookie->domain, $cookie->secure, $cookie->httpOnly);
|
||||
setcookie($cookie->name, $value, $expire, $cookiePath, $cookie->domain, $cookie->secure, $cookie->httpOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -886,12 +896,13 @@ class Response extends \yii\base\Response
|
||||
}
|
||||
$request = Yii::$app->getRequest();
|
||||
$normalizedUrl = Url::to($url);
|
||||
if (
|
||||
$normalizedUrl !== null
|
||||
&& strncmp($normalizedUrl, '/', 1) === 0
|
||||
&& strncmp($normalizedUrl, '//', 2) !== 0
|
||||
) {
|
||||
$normalizedUrl = $request->getHostInfo() . $normalizedUrl;
|
||||
if ($normalizedUrl !== null) {
|
||||
if (preg_match('/\n/', $normalizedUrl)) {
|
||||
throw new InvalidRouteException('Route with new line character detected "' . $normalizedUrl . '".');
|
||||
}
|
||||
if (strncmp($normalizedUrl, '/', 1) === 0 && strncmp($normalizedUrl, '//', 2) !== 0) {
|
||||
$normalizedUrl = $request->getHostInfo() . $normalizedUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($checkAjax && $request->getIsAjax()) {
|
||||
|
@ -57,7 +57,6 @@ use yii\base\InvalidConfigException;
|
||||
* @property bool $hasSessionId Whether the current request has sent the session ID.
|
||||
* @property string $id The current session ID.
|
||||
* @property-read bool $isActive Whether the session has started.
|
||||
* @property-read SessionIterator $iterator An iterator for traversing the session variables.
|
||||
* @property string $name The current session name.
|
||||
* @property string $savePath The current session save path, defaults to '/tmp'.
|
||||
* @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
|
||||
@ -406,7 +405,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
|
||||
* 'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
|
||||
* ]
|
||||
* ```
|
||||
* See https://www.owasp.org/index.php/SameSite for more information about `sameSite`.
|
||||
* See https://owasp.org/www-community/SameSite for more information about `sameSite`.
|
||||
*
|
||||
* @throws InvalidArgumentException if the parameters are incomplete.
|
||||
* @see https://www.php.net/manual/en/function.session-set-cookie-params.php
|
||||
|
@ -133,6 +133,7 @@ class View extends \yii\base\View
|
||||
|
||||
private $_assetManager;
|
||||
|
||||
|
||||
/**
|
||||
* Whether [[endPage()]] has been called and all files have been registered
|
||||
* @var bool
|
||||
@ -140,7 +141,6 @@ class View extends \yii\base\View
|
||||
*/
|
||||
protected $isPageEnded = false;
|
||||
|
||||
|
||||
/**
|
||||
* Marks the position of an HTML head section.
|
||||
*/
|
||||
|
@ -21,7 +21,6 @@
|
||||
<file>framework/web/ResponseFormatterInterface.php</file>
|
||||
<file>framework/.phpstorm.meta.php</file>
|
||||
<directory suffix="Exception.php">framework/base</directory>
|
||||
<directory suffix=".php">framework/db/mssql</directory>
|
||||
<directory suffix=".php">framework/bootstrap</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
|
@ -123,6 +123,39 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserting two strings equality ignoring unicode whitespaces.
|
||||
* @param string $expected
|
||||
* @param string $actual
|
||||
* @param string $message
|
||||
*/
|
||||
protected function assertEqualsAnyWhitespace($expected, $actual, $message = ''){
|
||||
$expected = $this->sanitizeWhitespaces($expected);
|
||||
$actual = $this->sanitizeWhitespaces($actual);
|
||||
|
||||
$this->assertEquals($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that two variables have the same type and value and sanitizes value if it is a string.
|
||||
* Used on objects, it asserts that two variables reference
|
||||
* the same object.
|
||||
*
|
||||
* @param mixed $expected
|
||||
* @param mixed $actual
|
||||
* @param string $message
|
||||
*/
|
||||
protected function assertSameAnyWhitespace($expected, $actual, $message = ''){
|
||||
if (is_string($expected)) {
|
||||
$expected = $this->sanitizeWhitespaces($expected);
|
||||
}
|
||||
if (is_string($actual)) {
|
||||
$actual = $this->sanitizeWhitespaces($actual);
|
||||
}
|
||||
|
||||
$this->assertSame($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a haystack contains a needle ignoring line endings.
|
||||
*
|
||||
@ -138,6 +171,17 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||
$this->assertContains($needle, $haystack, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces unicode whitespaces with standard whitespace
|
||||
*
|
||||
* @see https://github.com/yiisoft/yii2/issues/19868 (ICU 72 changes)
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitizeWhitespaces($string){
|
||||
return preg_replace("/[\pZ\pC]/u", " ", $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a inaccessible method.
|
||||
* @param $object
|
||||
|
27
tests/data/ar/NoAutoLabels.php
Normal file
27
tests/data/ar/NoAutoLabels.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yiiunit\data\ar;
|
||||
|
||||
/**
|
||||
* @property int $attr1
|
||||
* @property int $attr2
|
||||
*/
|
||||
class NoAutoLabels extends ActiveRecord
|
||||
{
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'attr1' => 'Label for attr1',
|
||||
];
|
||||
}
|
||||
|
||||
public function generateAttributeLabel($name)
|
||||
{
|
||||
throw new \yii\base\InvalidArgumentException('Label not defined!');
|
||||
}
|
||||
}
|
@ -37,6 +37,20 @@ class DynamicModelTest extends TestCase
|
||||
$this->assertTrue($model->hasErrors('age'));
|
||||
}
|
||||
|
||||
public function testValidateDataWithPostData()
|
||||
{
|
||||
$post = [
|
||||
'name' => 'long name',
|
||||
];
|
||||
$model = DynamicModel::validateData($post, [
|
||||
[['email', 'name'], 'required'],
|
||||
['age', 'default', 'value' => 18],
|
||||
]);
|
||||
$this->assertTrue($model->hasErrors());
|
||||
$this->assertTrue($model->hasErrors('email'));
|
||||
$this->assertEquals(18, $model->age);
|
||||
}
|
||||
|
||||
public function testAddRule()
|
||||
{
|
||||
$model = new DynamicModel();
|
||||
|
@ -23,7 +23,7 @@ class ApcCacheTest extends CacheTestCase
|
||||
*/
|
||||
protected function getCacheInstance()
|
||||
{
|
||||
if (!extension_loaded('apc')) {
|
||||
if (!extension_loaded('apc') && !extension_loaded('apcu')) {
|
||||
$this->markTestSkipped('APC not installed. Skipping.');
|
||||
} elseif ('cli' === PHP_SAPI && !ini_get('apc.enable_cli')) {
|
||||
$this->markTestSkipped('APC cli is not enabled. Skipping.');
|
||||
@ -33,7 +33,9 @@ class ApcCacheTest extends CacheTestCase
|
||||
$this->markTestSkipped('APC is installed but not enabled. Skipping.');
|
||||
}
|
||||
|
||||
if ($this->_cacheInstance === null) {
|
||||
if ($this->_cacheInstance === null && PHP_VERSION_ID >= 70400) {
|
||||
$this->_cacheInstance = new ApcCache(['useApcu' => true]);
|
||||
} elseif ($this->_cacheInstance === null) {
|
||||
$this->_cacheInstance = new ApcCache();
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,9 @@ class MemCachedTest extends CacheTestCase
|
||||
$this->markTestSkipped('memcached not installed. Skipping.');
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID >= 80100 && version_compare(phpversion('memcached'), '3.1.5', '<=')) {
|
||||
if (
|
||||
PHP_VERSION_ID >= 80100 && version_compare(phpversion('memcached'), '3.1.5', '<=')
|
||||
) {
|
||||
$php_version = phpversion();
|
||||
$memcached_version = phpversion('memcached');
|
||||
$this->markTestSkipped("memcached version $memcached_version is not ready for PHP $php_version. Skipping.");
|
||||
|
@ -58,6 +58,121 @@ class TableTest extends TestCase
|
||||
║ testcontent21 │ testcontent22 │ testcontent23 ║
|
||||
╚═══════════════╧═══════════════╧═══════════════╝
|
||||
|
||||
EXPECTED;
|
||||
|
||||
$tableContent = $table
|
||||
->setHeaders($headers)
|
||||
->setRows($rows)
|
||||
->setScreenWidth(200)
|
||||
->run();
|
||||
$this->assertEqualsWithoutLE($expected, $tableContent);
|
||||
}
|
||||
|
||||
public function getMultiLineTableData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
['test1', 'test2', 'test3' . PHP_EOL . 'multiline'],
|
||||
[
|
||||
['test' . PHP_EOL . 'content1', 'testcontent2', 'test' . PHP_EOL . 'content3'],
|
||||
[
|
||||
'testcontent21',
|
||||
'testcontent22' . PHP_EOL
|
||||
. 'loooooooooooooooooooooooooooooooooooong' . PHP_EOL
|
||||
. 'content',
|
||||
'testcontent23' . PHP_EOL
|
||||
. 'loooooooooooooooooooooooooooooooooooong content'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
['key1' => 'test1', 'key2' => 'test2', 'key3' => 'test3' . PHP_EOL . 'multiline'],
|
||||
[
|
||||
[
|
||||
'key1' => 'test' . PHP_EOL . 'content1',
|
||||
'key2' => 'testcontent2',
|
||||
'key3' => 'test' . PHP_EOL . 'content3'
|
||||
],
|
||||
[
|
||||
'key1' => 'testcontent21',
|
||||
'key2' => 'testcontent22' . PHP_EOL
|
||||
. 'loooooooooooooooooooooooooooooooooooong' . PHP_EOL
|
||||
. 'content',
|
||||
'key3' => 'testcontent23' . PHP_EOL
|
||||
. 'loooooooooooooooooooooooooooooooooooong content'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getMultiLineTableData
|
||||
*/
|
||||
public function testMultiLineTable($headers, $rows)
|
||||
{
|
||||
$table = new Table();
|
||||
|
||||
$expected = <<<'EXPECTED'
|
||||
╔═════════════╤═════════════════════════════════════╤═════════════════════════════════════════════╗
|
||||
║ test1 │ test2 │ test3 ║
|
||||
║ │ │ multiline ║
|
||||
╟─────────────┼─────────────────────────────────────┼─────────────────────────────────────────────╢
|
||||
║ test │ testcontent2 │ test ║
|
||||
║ content1 │ │ content3 ║
|
||||
╟─────────────┼─────────────────────────────────────┼─────────────────────────────────────────────╢
|
||||
║ testcontent │ testcontent22 │ testcontent23 ║
|
||||
║ 21 │ loooooooooooooooooooooooooooooooooo │ loooooooooooooooooooooooooooooooooooong con ║
|
||||
║ │ oong │ tent ║
|
||||
║ │ content │ ║
|
||||
╚═════════════╧═════════════════════════════════════╧═════════════════════════════════════════════╝
|
||||
|
||||
EXPECTED;
|
||||
|
||||
$tableContent = $table
|
||||
->setHeaders($headers)
|
||||
->setRows($rows)
|
||||
->setScreenWidth(100)
|
||||
->run();
|
||||
$this->assertEqualsWithoutLE($expected, $tableContent);
|
||||
}
|
||||
|
||||
public function getNumericTableData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[
|
||||
[1, 1.2, -1.3],
|
||||
[-2, 2.2, 2.3],
|
||||
]
|
||||
],
|
||||
[
|
||||
['key1' => 1, 'key2' => 2, 'key3' => 3],
|
||||
[
|
||||
['key1' => 1, 'key2' => 1.2, 'key3' => -1.3],
|
||||
['key1' => -2, 'key2' => 2.2, 'key3' => 2.3],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getNumericTableData
|
||||
*/
|
||||
public function testNumericTable($headers, $rows)
|
||||
{
|
||||
$table = new Table();
|
||||
|
||||
$expected = <<<'EXPECTED'
|
||||
╔════╤═════╤══════╗
|
||||
║ 1 │ 2 │ 3 ║
|
||||
╟────┼─────┼──────╢
|
||||
║ 1 │ 1.2 │ -1.3 ║
|
||||
╟────┼─────┼──────╢
|
||||
║ -2 │ 2.2 │ 2.3 ║
|
||||
╚════╧═════╧══════╝
|
||||
|
||||
EXPECTED;
|
||||
|
||||
$tableContent = $table
|
||||
@ -101,7 +216,8 @@ EXPECTED;
|
||||
╔═══════════════╤═══════════════╤══════════════╗
|
||||
║ test1 │ test2 │ test3 ║
|
||||
╟───────────────┼───────────────┼──────────────╢
|
||||
║ testcontent1 │ testcontent2 │ testcontent3 ║
|
||||
║ • col1 │ testcontent2 │ testcontent3 ║
|
||||
║ • col2 │ │ ║
|
||||
╟───────────────┼───────────────┼──────────────╢
|
||||
║ testcontent21 │ testcontent22 │ • col1 ║
|
||||
║ │ │ • col2 ║
|
||||
@ -111,7 +227,7 @@ EXPECTED;
|
||||
|
||||
$this->assertEqualsWithoutLE($expected, $table->setHeaders(['test1', 'test2', 'test3'])
|
||||
->setRows([
|
||||
['testcontent1', 'testcontent2', 'testcontent3'],
|
||||
[['key1' => 'col1', 'key2' => 'col2'], 'testcontent2', 'testcontent3'],
|
||||
['testcontent21', 'testcontent22', ['col1', 'col2']],
|
||||
])->setScreenWidth(200)->run()
|
||||
);
|
||||
@ -141,6 +257,35 @@ EXPECTED;
|
||||
);
|
||||
}
|
||||
|
||||
public function testLongerListPrefix()
|
||||
{
|
||||
$table = new Table();
|
||||
|
||||
$expected = <<<'EXPECTED'
|
||||
╔═════════════════════════════════╤═════════════════════════════════╤═════════════════════════════╗
|
||||
║ test1 │ test2 │ test3 ║
|
||||
╟─────────────────────────────────┼─────────────────────────────────┼─────────────────────────────╢
|
||||
║ testcontent1 │ testcontent2 │ testcontent3 ║
|
||||
╟─────────────────────────────────┼─────────────────────────────────┼─────────────────────────────╢
|
||||
║ testcontent21 with looooooooooo │ testcontent22 with looooooooooo │ -- col1 with looooooooooooo ║
|
||||
║ ooooooooooooong content │ ooooooooooooong content │ ooooooooooong content ║
|
||||
║ │ │ -- col2 with long content ║
|
||||
╚═════════════════════════════════╧═════════════════════════════════╧═════════════════════════════╝
|
||||
|
||||
EXPECTED;
|
||||
|
||||
$this->assertEqualsWithoutLE($expected, $table->setHeaders(['test1', 'test2', 'test3'])
|
||||
->setRows([
|
||||
['testcontent1', 'testcontent2', 'testcontent3'],
|
||||
[
|
||||
'testcontent21 with loooooooooooooooooooooooong content',
|
||||
'testcontent22 with loooooooooooooooooooooooong content',
|
||||
['col1 with loooooooooooooooooooooooong content', 'col2 with long content']
|
||||
],
|
||||
])->setScreenWidth(100)->setListPrefix('-- ')->run()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCustomChars()
|
||||
{
|
||||
$table = new Table();
|
||||
|
@ -2192,4 +2192,87 @@ abstract class ActiveRecordTest extends DatabaseTestCase
|
||||
$this->assertNotNull($order->virtualCustomer);
|
||||
}
|
||||
|
||||
public function labelTestModelProvider()
|
||||
{
|
||||
$data = [];
|
||||
|
||||
// Model 2 and 3 are represented by objects.
|
||||
$model1 = new LabelTestModel1();
|
||||
$model2 = new LabelTestModel2();
|
||||
$model3 = new LabelTestModel3();
|
||||
$model2->populateRelation('model3', $model3);
|
||||
$model1->populateRelation('model2', $model2);
|
||||
$data[] = [$model1];
|
||||
|
||||
// Model 2 and 3 are represented by arrays instead of objects.
|
||||
$model1 = new LabelTestModel1();
|
||||
$model2 = ['model3' => []];
|
||||
$model1->populateRelation('model2', $model2);
|
||||
$data[] = [$model1];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider labelTestModelProvider
|
||||
* @param \yii\db\ActiveRecord $model
|
||||
*/
|
||||
public function testGetAttributeLabel($model)
|
||||
{
|
||||
$this->assertEquals('model3.attr1 from model2', $model->getAttributeLabel('model2.model3.attr1'));
|
||||
$this->assertEquals('attr2 from model3', $model->getAttributeLabel('model2.model3.attr2'));
|
||||
$this->assertEquals('model3.attr3 from model2', $model->getAttributeLabel('model2.model3.attr3'));
|
||||
$attr = 'model2.doesNotExist.attr1';
|
||||
$this->assertEquals($model->generateAttributeLabel($attr), $model->getAttributeLabel($attr));
|
||||
}
|
||||
}
|
||||
|
||||
class LabelTestModel1 extends \yii\db\ActiveRecord
|
||||
{
|
||||
public function attributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getModel2()
|
||||
{
|
||||
return $this->hasOne(LabelTestModel2::className(), []);
|
||||
}
|
||||
}
|
||||
|
||||
class LabelTestModel2 extends \yii\db\ActiveRecord
|
||||
{
|
||||
public function attributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getModel3()
|
||||
{
|
||||
return $this->hasOne(LabelTestModel3::className(), []);
|
||||
}
|
||||
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'model3.attr1' => 'model3.attr1 from model2', // Override label defined in model3.
|
||||
'model3.attr3' => 'model3.attr3 from model2', // Define label not defined in model3.
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class LabelTestModel3 extends \yii\db\ActiveRecord
|
||||
{
|
||||
public function attributes()
|
||||
{
|
||||
return ['attr1', 'attr2', 'attr3'];
|
||||
}
|
||||
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'attr1' => 'attr1 from model3',
|
||||
'attr2' => 'attr2 from model3',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
42
tests/framework/db/BaseActiveRecordTest.php
Normal file
42
tests/framework/db/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db;
|
||||
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
|
||||
abstract class BaseActiveRecordTest extends DatabaseTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
ActiveRecord::$db = $this->getConnection();
|
||||
}
|
||||
|
||||
public function provideArrayValueWithChange()
|
||||
{
|
||||
return [
|
||||
'not an associative array with data change' => [
|
||||
[1, 2, 3],
|
||||
[1, 3, 2],
|
||||
],
|
||||
|
||||
'associative array with data change case 1' => [
|
||||
['pineapple' => 2, 'apple' => 5, 'banana' => 1],
|
||||
['apple' => 5, 'pineapple' => 1, 'banana' => 3],
|
||||
],
|
||||
'associative array with data change case 2' => [
|
||||
['pineapple' => 2, 'apple' => 5, 'banana' => 1],
|
||||
['pineapple' => 2, 'apple' => 3, 'banana' => 1],
|
||||
],
|
||||
|
||||
'filling an empty array' => [
|
||||
[],
|
||||
['pineapple' => 3, 'apple' => 1, 'banana' => 1],
|
||||
],
|
||||
'zeroing the array' => [
|
||||
['pineapple' => 3, 'apple' => 1, 'banana' => 17],
|
||||
[],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -1525,4 +1525,23 @@ SQL;
|
||||
$db->createCommand()->setSql("SELECT :p1")->bindValues([':p1' => [2, \PDO::PARAM_STR]]);
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testBindValuesSupportsEnums()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
|
||||
$db = $this->getConnection();
|
||||
$command = $db->createCommand();
|
||||
|
||||
$command->setSql('SELECT :p1')->bindValues([':p1' => enums\Status::ACTIVE]);
|
||||
$this->assertSame('ACTIVE', $command->params[':p1']);
|
||||
|
||||
$command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeString::ACTIVE]);
|
||||
$this->assertSame('active', $command->params[':p1']);
|
||||
|
||||
$command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeInt::ACTIVE]);
|
||||
$this->assertSame(1, $command->params[':p1']);
|
||||
} else {
|
||||
$this->markTestSkipped('Enums are not supported in PHP < 8.1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,7 +469,7 @@ abstract class ConnectionTest extends DatabaseTestCase
|
||||
|
||||
|
||||
/**
|
||||
* Test whether slave connection is recovered when call getSlavePdo() after close().
|
||||
* Test whether slave connection is recovered when call getSlavePdo(true) after close().
|
||||
*
|
||||
* @see https://github.com/yiisoft/yii2/issues/14165
|
||||
*/
|
||||
|
@ -757,7 +757,7 @@ abstract class SchemaTest extends DatabaseTestCase
|
||||
}
|
||||
|
||||
$connection = $this->getConnection(false);
|
||||
$connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
|
||||
$connection->getSlavePdo(true)->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
|
||||
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
|
||||
$this->assertMetadataEquals($expected, $constraints);
|
||||
}
|
||||
@ -775,7 +775,7 @@ abstract class SchemaTest extends DatabaseTestCase
|
||||
}
|
||||
|
||||
$connection = $this->getConnection(false);
|
||||
$connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
|
||||
$connection->getSlavePdo(true)->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
|
||||
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
|
||||
$this->assertMetadataEquals($expected, $constraints);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
|
||||
|
||||
public function testCommentColumn()
|
||||
{
|
||||
$version = $this->getQueryBuilder(false)->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$version = $this->getQueryBuilder(false)->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
if (version_compare($version, '10.0', '<')) {
|
||||
$this->markTestSkipped('Comments on columns are supported starting with CUBRID 10.0.');
|
||||
return;
|
||||
|
9
tests/framework/db/enums/Status.php
Normal file
9
tests/framework/db/enums/Status.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\enums;
|
||||
|
||||
enum Status
|
||||
{
|
||||
case ACTIVE;
|
||||
case INACTIVE;
|
||||
}
|
9
tests/framework/db/enums/StatusTypeInt.php
Normal file
9
tests/framework/db/enums/StatusTypeInt.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\enums;
|
||||
|
||||
enum StatusTypeInt: int
|
||||
{
|
||||
case ACTIVE = 1;
|
||||
case INACTIVE = 0;
|
||||
}
|
9
tests/framework/db/enums/StatusTypeString.php
Normal file
9
tests/framework/db/enums/StatusTypeString.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\enums;
|
||||
|
||||
enum StatusTypeString: string
|
||||
{
|
||||
case ACTIVE = 'active';
|
||||
case INACTIVE = 'inactive';
|
||||
}
|
@ -134,22 +134,19 @@ class CommandTest extends \yiiunit\framework\db\CommandTest
|
||||
{
|
||||
$db = $this->getConnection();
|
||||
|
||||
$testData = json_encode(['test' => 'string', 'test2' => 'integer']);
|
||||
$qb = $db->getQueryBuilder();
|
||||
$testData = json_encode(['test' => 'string', 'test2' => 'integer'], JSON_THROW_ON_ERROR);
|
||||
|
||||
$params = [];
|
||||
|
||||
$qb = $db->getQueryBuilder();
|
||||
$sql = $qb->upsert('T_upsert_varbinary', ['id' => 1, 'blob_col' => $testData] , ['blob_col' => $testData], $params);
|
||||
|
||||
$sql = $qb->upsert('T_upsert_varbinary', ['id' => 1, 'blob_col' => $testData], ['blob_col' => $testData], $params);
|
||||
$result = $db->createCommand($sql, $params)->execute();
|
||||
|
||||
$this->assertEquals(1, $result);
|
||||
|
||||
$query = (new Query())
|
||||
->select(['convert(nvarchar(max),blob_col) as blob_col'])
|
||||
->from('T_upsert_varbinary')
|
||||
->where(['id' => 1]);
|
||||
$this->assertSame(1, $result);
|
||||
|
||||
$query = (new Query())->select(['blob_col'])->from('T_upsert_varbinary')->where(['id' => 1]);
|
||||
$resultData = $query->createCommand($db)->queryOne();
|
||||
$this->assertEquals($testData, $resultData['blob_col']);
|
||||
|
||||
$this->assertSame($testData, $resultData['blob_col']);
|
||||
}
|
||||
}
|
||||
|
55
tests/framework/db/mssql/QueryCacheTest.php
Normal file
55
tests/framework/db/mssql/QueryCacheTest.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yiiunit\framework\db\mssql;
|
||||
|
||||
use yii\caching\FileCache;
|
||||
use yii\db\Query;
|
||||
use yiiunit\framework\db\DatabaseTestCase;
|
||||
|
||||
/**
|
||||
* @group db
|
||||
* @group mssql
|
||||
*/
|
||||
class QueryCacheTest extends DatabaseTestCase
|
||||
{
|
||||
protected $driverName = 'sqlsrv';
|
||||
|
||||
public function testQueryCacheFileCache()
|
||||
{
|
||||
$db = $this->getConnection();
|
||||
$db->enableQueryCache = true;
|
||||
$db->queryCache = new FileCache(['cachePath' => '@yiiunit/runtime/cache']);
|
||||
|
||||
$db->createCommand()->delete('type')->execute();
|
||||
$db->createCommand()->insert('type', [
|
||||
'int_col' => $key = 1,
|
||||
'char_col' => '',
|
||||
'char_col2' => '6a3ce1a0bffe8eeb6fa986caf443e24c',
|
||||
'float_col' => 0.0,
|
||||
'blob_col' => 'a:1:{s:13:"template";s:1:"1";}',
|
||||
'bool_col' => true,
|
||||
])->execute();
|
||||
|
||||
$function = function($db) use ($key){
|
||||
return (new Query())
|
||||
->select(['blob_col'])
|
||||
->from('type')
|
||||
->where(['int_col' => $key])
|
||||
->createCommand($db)
|
||||
->queryScalar();
|
||||
};
|
||||
|
||||
// First run return
|
||||
$result = $db->cache($function);
|
||||
$this->assertSame('a:1:{s:13:"template";s:1:"1";}', $result);
|
||||
|
||||
// After the request has been cached return
|
||||
$result = $db->cache($function);
|
||||
$this->assertSame('a:1:{s:13:"template";s:1:"1";}', $result);
|
||||
}
|
||||
}
|
44
tests/framework/db/mssql/type/VarbinaryTest.php
Normal file
44
tests/framework/db/mssql/type/VarbinaryTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yiiunit\framework\db\mssql\Type;
|
||||
|
||||
use yii\db\Query;
|
||||
use yiiunit\framework\db\DatabaseTestCase;
|
||||
|
||||
/**
|
||||
* @group db
|
||||
* @group mssql
|
||||
*/
|
||||
class VarbinaryTest extends DatabaseTestCase
|
||||
{
|
||||
protected $driverName = 'sqlsrv';
|
||||
|
||||
public function testVarbinary()
|
||||
{
|
||||
$db = $this->getConnection();
|
||||
|
||||
$db->createCommand()->delete('type')->execute();
|
||||
$db->createCommand()->insert('type', [
|
||||
'int_col' => $key = 1,
|
||||
'char_col' => '',
|
||||
'char_col2' => '6a3ce1a0bffe8eeb6fa986caf443e24c',
|
||||
'float_col' => 0.0,
|
||||
'blob_col' => 'a:1:{s:13:"template";s:1:"1";}',
|
||||
'bool_col' => true,
|
||||
])->execute();
|
||||
|
||||
$result = (new Query())
|
||||
->select(['blob_col'])
|
||||
->from('type')
|
||||
->where(['int_col' => $key])
|
||||
->createCommand($db)
|
||||
->queryScalar();
|
||||
|
||||
$this->assertSame('a:1:{s:13:"template";s:1:"1";}', $result);
|
||||
}
|
||||
}
|
37
tests/framework/db/mysql/BaseActiveRecordTest.php
Normal file
37
tests/framework/db/mysql/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\mysql;
|
||||
|
||||
use yiiunit\data\ar\Storage;
|
||||
|
||||
class BaseActiveRecordTest extends \yiiunit\framework\db\BaseActiveRecordTest
|
||||
{
|
||||
public $driverName = 'mysql';
|
||||
|
||||
/**
|
||||
* @see https://github.com/yiisoft/yii2/issues/19872
|
||||
*
|
||||
* @dataProvider provideArrayValueWithChange
|
||||
*/
|
||||
public function testJsonDirtyAttributesWithDataChange($actual, $modified)
|
||||
{
|
||||
if (version_compare($this->getConnection()->getSchema()->getServerVersion(), '5.7', '<')) {
|
||||
$this->markTestSkipped('JSON columns are not supported in MySQL < 5.7');
|
||||
}
|
||||
if (version_compare(PHP_VERSION, '5.6', '<')) {
|
||||
$this->markTestSkipped('JSON columns are not supported in PDO for PHP < 5.6');
|
||||
}
|
||||
|
||||
$createdStorage = new Storage(['data' => $actual]);
|
||||
|
||||
$createdStorage->save();
|
||||
|
||||
$foundStorage = Storage::find()->limit(1)->one();
|
||||
|
||||
$this->assertNotNull($foundStorage);
|
||||
|
||||
$foundStorage->data = $modified;
|
||||
|
||||
$this->assertSame(['data' => $modified], $foundStorage->getDirtyAttributes());
|
||||
}
|
||||
}
|
@ -125,7 +125,7 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
|
||||
/**
|
||||
* @link https://github.com/yiisoft/yii2/issues/14367
|
||||
*/
|
||||
$mysqlVersion = $this->getDb()->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$mysqlVersion = $this->getDb()->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
$supportsFractionalSeconds = version_compare($mysqlVersion,'5.6.4', '>=');
|
||||
if ($supportsFractionalSeconds) {
|
||||
$expectedValues = [
|
||||
|
@ -119,7 +119,12 @@ class DeadLockTest extends \yiiunit\framework\db\mysql\ConnectionTest
|
||||
. ($logContent ? ". Shared children log:\n$logContent" : '')
|
||||
);
|
||||
}
|
||||
$this->assertEquals(1, $deadlockHitCount, "exactly one child must hit deadlock; shared children log:\n" . $logContent);
|
||||
|
||||
if (version_compare($this->getConnection()->getSchema()->getServerVersion(), '8.0', '<')) {
|
||||
$this->assertEquals(1, $deadlockHitCount, "exactly one child must hit deadlock; shared children log:\n" . $logContent);
|
||||
} else {
|
||||
$this->assertEquals(0, $deadlockHitCount, "exactly zero children must hit deadlock; shared children log:\n" . $logContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
46
tests/framework/db/pgsql/BaseActiveRecordTest.php
Normal file
46
tests/framework/db/pgsql/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\pgsql;
|
||||
|
||||
use yii\db\JsonExpression;
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
|
||||
class BaseActiveRecordTest extends \yiiunit\framework\db\BaseActiveRecordTest
|
||||
{
|
||||
public $driverName = 'pgsql';
|
||||
|
||||
/**
|
||||
* @see https://github.com/yiisoft/yii2/issues/19872
|
||||
*
|
||||
* @dataProvider provideArrayValueWithChange
|
||||
*/
|
||||
public function testJsonDirtyAttributesWithDataChange($actual, $modified)
|
||||
{
|
||||
$createdStorage = new ArrayAndJsonType([
|
||||
'json_col' => new JsonExpression($actual),
|
||||
]);
|
||||
|
||||
$createdStorage->save();
|
||||
|
||||
$foundStorage = ArrayAndJsonType::find()->limit(1)->one();
|
||||
|
||||
$this->assertNotNull($foundStorage);
|
||||
|
||||
$foundStorage->json_col = $modified;
|
||||
|
||||
$this->assertSame(['json_col' => $modified], $foundStorage->getDirtyAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @property array id
|
||||
* @property array json_col
|
||||
*/
|
||||
class ArrayAndJsonType extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return '{{%array_and_json_types}}';
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
|
||||
namespace yiiunit\framework\db\sqlite;
|
||||
|
||||
use yii\db\sqlite\Schema;
|
||||
|
||||
/**
|
||||
* @group db
|
||||
* @group sqlite
|
||||
@ -109,4 +111,60 @@ SQL;
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
public function testResetSequence()
|
||||
{
|
||||
$db = $this->getConnection();
|
||||
|
||||
if ($db->getTableSchema('reset_sequence', true) !== null) {
|
||||
$db->createCommand()->dropTable('reset_sequence')->execute();
|
||||
}
|
||||
|
||||
// create table reset_sequence
|
||||
$db->createCommand()->createTable(
|
||||
'reset_sequence',
|
||||
[
|
||||
'id' => Schema::TYPE_PK,
|
||||
'description' => Schema::TYPE_TEXT,
|
||||
]
|
||||
)->execute();
|
||||
|
||||
// ensure auto increment is working
|
||||
$db->createCommand()->insert('reset_sequence', ['description' => 'test'])->execute();
|
||||
$this->assertEquals(1, $db->createCommand('SELECT MAX([[id]]) FROM {{reset_sequence}}')->queryScalar());
|
||||
|
||||
// remove all records
|
||||
$db->createCommand()->delete('reset_sequence')->execute();
|
||||
$this->assertEquals(0, $db->createCommand('SELECT COUNT(*) FROM {{reset_sequence}}')->queryScalar());
|
||||
|
||||
// counter should be reset to 1
|
||||
$db->createCommand()->resetSequence('reset_sequence')->execute();
|
||||
$db->createCommand()->insert('reset_sequence', ['description' => 'test'])->execute();
|
||||
$this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{reset_sequence}}')->queryScalar());
|
||||
$this->assertEquals(1, $db->createCommand('SELECT MAX([[id]]) FROM {{reset_sequence}}')->queryScalar());
|
||||
|
||||
// counter should be reset to 5, so next record gets ID 5
|
||||
$db->createCommand()->resetSequence('reset_sequence', 5)->execute();
|
||||
$db->createCommand()->insert('reset_sequence', ['description' => 'test'])->execute();
|
||||
$this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{reset_sequence}}')->queryScalar());
|
||||
$this->assertEquals(5, $db->createCommand('SELECT MAX([[id]]) FROM {{reset_sequence}}')->queryScalar());
|
||||
}
|
||||
|
||||
public function testResetSequenceExceptionTableNoExist()
|
||||
{
|
||||
$this->expectException('yii\base\InvalidArgumentException');
|
||||
$this->expectExceptionMessage('Table not found: no_exist_table');
|
||||
|
||||
$db = $this->getConnection();
|
||||
$db->createCommand()->resetSequence('no_exist_table', 5)->execute();
|
||||
}
|
||||
|
||||
public function testResetSequenceExceptionSquenceNoExist()
|
||||
{
|
||||
$this->expectException('yii\base\InvalidArgumentException');
|
||||
$this->expectExceptionMessage("There is not sequence associated with table 'type'.");
|
||||
|
||||
$db = $this->getConnection();
|
||||
$db->createCommand()->resetSequence('type', 5)->execute();
|
||||
}
|
||||
}
|
||||
|
@ -422,17 +422,18 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
{
|
||||
$action = $this->mockAction();
|
||||
$user = false;
|
||||
$request = $this->mockRequest();
|
||||
|
||||
$rule = new AccessRule();
|
||||
|
||||
// by default match all IPs
|
||||
$request = $this->mockRequest();
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
$rule->allow = false;
|
||||
$this->assertFalse($rule->allows($action, $user, $request));
|
||||
|
||||
// empty IPs = match all IPs
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = [];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -441,6 +442,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match, one IP
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.0.1'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -449,6 +451,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no match, one IP
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['192.168.0.1'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -457,12 +460,14 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no partial match, one IP
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.0.10'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
$rule->allow = false;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.10';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.0.1'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -471,6 +476,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match, one IP IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['::1'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -479,6 +485,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no match, one IP IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['dead::beaf::1'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -487,12 +494,14 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no partial match, one IP IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['::123'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
$rule->allow = false;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
$_SERVER['REMOTE_ADDR'] = '::123';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['::1'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -501,6 +510,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// undefined IP
|
||||
$_SERVER['REMOTE_ADDR'] = null;
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['192.168.*'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -512,12 +522,12 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
{
|
||||
$action = $this->mockAction();
|
||||
$user = false;
|
||||
$request = $this->mockRequest();
|
||||
|
||||
$rule = new AccessRule();
|
||||
|
||||
// no match
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['192.168.*'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -526,6 +536,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.*'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -534,6 +545,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match, IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '2a01:4f8:120:7202::2';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['2a01:4f8:120:*'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -542,6 +554,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no match, IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['2a01:4f8:120:*'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -553,12 +566,12 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
{
|
||||
$action = $this->mockAction();
|
||||
$user = false;
|
||||
$request = $this->mockRequest();
|
||||
|
||||
$rule = new AccessRule();
|
||||
|
||||
// no match
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.0.32/27'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
@ -567,6 +580,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['127.0.0.1/27'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -575,6 +589,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// match, IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '2a01:4f8:120:7202::2';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['2a01:4f8:120:7202::2/127'];
|
||||
$rule->allow = true;
|
||||
$this->assertTrue($rule->allows($action, $user, $request));
|
||||
@ -583,6 +598,7 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
|
||||
// no match, IPv6
|
||||
$_SERVER['REMOTE_ADDR'] = '2a01:4f8:120:7202::ffff';
|
||||
$request = $this->mockRequest();
|
||||
$rule->ips = ['2a01:4f8:120:7202::2/123'];
|
||||
$rule->allow = true;
|
||||
$this->assertNull($rule->allows($action, $user, $request));
|
||||
|
@ -7,10 +7,12 @@
|
||||
|
||||
namespace yiiunit\framework\grid;
|
||||
|
||||
use Yii;
|
||||
use yii\data\ArrayDataProvider;
|
||||
use yii\grid\DataColumn;
|
||||
use yii\grid\GridView;
|
||||
use yii\web\View;
|
||||
use yiiunit\data\ar\NoAutoLabels;
|
||||
|
||||
/**
|
||||
* @author Evgeniy Tkachenko <et.coder@gmail.com>
|
||||
@ -150,4 +152,53 @@ class GridViewTest extends \yiiunit\TestCase
|
||||
|
||||
$this->assertTrue(preg_match("/<\/tbody><tfoot>/", $html) === 1);
|
||||
}
|
||||
|
||||
public function testHeaderLabels()
|
||||
{
|
||||
// Ensure GridView does not call Model::generateAttributeLabel() to generate labels unless the labels are explicitly used.
|
||||
|
||||
$this->mockApplication([
|
||||
'components' => [
|
||||
'db' => [
|
||||
'class' => \yii\db\Connection::className(),
|
||||
'dsn' => 'sqlite::memory:',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
NoAutoLabels::$db = Yii::$app->getDb();
|
||||
Yii::$app->getDb()->createCommand()->createTable(NoAutoLabels::tableName(), ['attr1' => 'int', 'attr2' => 'int'])->execute();
|
||||
|
||||
$urlManager = new \yii\web\UrlManager([
|
||||
'baseUrl' => '/',
|
||||
'scriptUrl' => '/index.php',
|
||||
]);
|
||||
|
||||
$grid = new GridView([
|
||||
'dataProvider' => new \yii\data\ActiveDataProvider([
|
||||
'query' => NoAutoLabels::find(),
|
||||
]),
|
||||
'columns' => [
|
||||
'attr1',
|
||||
'attr2:text:Label for attr2',
|
||||
],
|
||||
]);
|
||||
|
||||
// NoAutoLabels::generateAttributeLabel() should not be called.
|
||||
$grid->dataProvider->setSort([
|
||||
'route' => '/',
|
||||
'urlManager' => $urlManager,
|
||||
]);
|
||||
$grid->renderTableHeader();
|
||||
|
||||
// NoAutoLabels::generateAttributeLabel() should not be called.
|
||||
$grid->dataProvider->setSort([
|
||||
'route' => '/',
|
||||
'urlManager' => $urlManager,
|
||||
'attributes' => ['attr1', 'attr2'],
|
||||
]);
|
||||
$grid->renderTableHeader();
|
||||
|
||||
// If NoAutoLabels::generateAttributeLabel() has not been called no exception will be thrown meaning this test passed successfully.
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,29 @@ class ArrayHelperTest extends TestCase
|
||||
$this->assertEquals('defaultValue', $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testRemoveWithFloat()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
|
||||
$this->markTestSkipped('Using floats as array key is deprecated.');
|
||||
}
|
||||
|
||||
$array = ['name' => 'b', 'age' => 3, 1.1 => null];
|
||||
|
||||
$name = ArrayHelper::remove($array, 'name');
|
||||
$this->assertEquals($name, 'b');
|
||||
$this->assertEquals($array, ['age' => 3, 1.1 => null]);
|
||||
|
||||
$floatVal = ArrayHelper::remove($array, 1.1);
|
||||
$this->assertNull($floatVal);
|
||||
$this->assertEquals($array, ['age' => 3]);
|
||||
|
||||
$default = ArrayHelper::remove($array, 'nonExisting', 'defaultValue');
|
||||
$this->assertEquals('defaultValue', $default);
|
||||
}
|
||||
|
||||
public function testRemoveValueMultiple()
|
||||
{
|
||||
$array = [
|
||||
@ -506,14 +529,21 @@ class ArrayHelperTest extends TestCase
|
||||
/**
|
||||
* @see https://github.com/yiisoft/yii2/pull/11549
|
||||
*/
|
||||
public function test()
|
||||
public function testGetValueWithFloatKeys()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
|
||||
$this->markTestSkipped('Using floats as array key is deprecated.');
|
||||
}
|
||||
|
||||
$array = [];
|
||||
$array[1.0] = 'some value';
|
||||
|
||||
$result = ArrayHelper::getValue($array, 1.0);
|
||||
$array[1.1] = 'some value';
|
||||
$array[2.1] = null;
|
||||
|
||||
$result = ArrayHelper::getValue($array, 1.2);
|
||||
$this->assertEquals('some value', $result);
|
||||
|
||||
$result = ArrayHelper::getValue($array, 2.2);
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testIndex()
|
||||
@ -712,6 +742,7 @@ class ArrayHelperTest extends TestCase
|
||||
'a' => 1,
|
||||
'B' => 2,
|
||||
];
|
||||
|
||||
$this->assertTrue(ArrayHelper::keyExists('a', $array));
|
||||
$this->assertFalse(ArrayHelper::keyExists('b', $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists('B', $array));
|
||||
@ -723,6 +754,27 @@ class ArrayHelperTest extends TestCase
|
||||
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
|
||||
}
|
||||
|
||||
public function testKeyExistsWithFloat()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
|
||||
$this->markTestSkipped('Using floats as array key is deprecated.');
|
||||
}
|
||||
|
||||
$array = [
|
||||
1 => 3,
|
||||
2.2 => 4, // Note: Floats are cast to ints, which means that the fractional part will be truncated.
|
||||
3.3 => null,
|
||||
];
|
||||
|
||||
$this->assertTrue(ArrayHelper::keyExists(1, $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists(1.1, $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists(2, $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists('2', $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists(2.2, $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists(3, $array));
|
||||
$this->assertTrue(ArrayHelper::keyExists(3.3, $array));
|
||||
}
|
||||
|
||||
public function testKeyExistsArrayAccess()
|
||||
{
|
||||
$array = new TraversableArrayAccessibleObject([
|
||||
|
@ -216,27 +216,6 @@ class ConsoleTest extends TestCase
|
||||
$expectedHtml = "Error message. Here are some chars: < >\nError message. Here are even more chars: \"\"";
|
||||
$this->assertEqualsWithoutLE($expectedHtml, Console::errorSummary($model, $options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property string name
|
||||
* @property array types
|
||||
* @property string description
|
||||
*/
|
||||
class TestConsoleModel extends DynamicModel
|
||||
{
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['name', 'required'],
|
||||
['name', 'string', 'max' => 100]
|
||||
];
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->defineAttribute('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \yii\helpers\BaseConsole::input()
|
||||
@ -390,16 +369,16 @@ class TestConsoleModel extends DynamicModel
|
||||
$this->truncateStreams();
|
||||
|
||||
foreach ([
|
||||
'y' => true,
|
||||
'Y' => true,
|
||||
'yes' => true,
|
||||
'YeS' => true,
|
||||
'n' => false,
|
||||
'N' => false,
|
||||
'no' => false,
|
||||
'NO' => false,
|
||||
'WHAT?!' . PHP_EOL . 'yes' => true,
|
||||
] as $currInput => $currAssertion) {
|
||||
'y' => true,
|
||||
'Y' => true,
|
||||
'yes' => true,
|
||||
'YeS' => true,
|
||||
'n' => false,
|
||||
'N' => false,
|
||||
'no' => false,
|
||||
'NO' => false,
|
||||
'WHAT?!' . PHP_EOL . 'yes' => true,
|
||||
] as $currInput => $currAssertion) {
|
||||
$this->sendInput($currInput);
|
||||
$result = ConsoleStub::confirm('Are you sure?');
|
||||
$this->assertEquals($currAssertion, $result, $currInput);
|
||||
@ -420,31 +399,63 @@ class TestConsoleModel extends DynamicModel
|
||||
|
||||
$this->sendInput('c');
|
||||
$result = ConsoleStub::select('Usual behavior', $options);
|
||||
$this->assertEquals('Usual behavior [c,d,m,?]: ', $this->readOutput());
|
||||
$this->assertEquals('Usual behavior (c,d,m,?): ', $this->readOutput());
|
||||
$this->assertEquals('c', $result);
|
||||
$this->truncateStreams();
|
||||
|
||||
$this->sendInput('x', 'd');
|
||||
$result = ConsoleStub::select('Wrong character', $options);
|
||||
$this->assertEquals('Wrong character [c,d,m,?]: Wrong character [c,d,m,?]: ', $this->readOutput());
|
||||
$this->assertEquals('Wrong character (c,d,m,?): Wrong character (c,d,m,?): ', $this->readOutput());
|
||||
$this->assertEquals('d', $result);
|
||||
$this->truncateStreams();
|
||||
|
||||
$this->sendInput('?', 'm');
|
||||
$result = ConsoleStub::select('Using help', $options);
|
||||
$this->assertEquals(
|
||||
'Using help [c,d,m,?]: '
|
||||
. ' c - cat'
|
||||
. PHP_EOL
|
||||
. ' d - dog'
|
||||
. PHP_EOL
|
||||
. ' m - mouse'
|
||||
. PHP_EOL
|
||||
. ' ? - Show help'
|
||||
. PHP_EOL
|
||||
. 'Using help [c,d,m,?]: ',
|
||||
'Using help (c,d,m,?): '
|
||||
. ' c - cat'
|
||||
. PHP_EOL
|
||||
. ' d - dog'
|
||||
. PHP_EOL
|
||||
. ' m - mouse'
|
||||
. PHP_EOL
|
||||
. ' ? - Show help'
|
||||
. PHP_EOL
|
||||
. 'Using help (c,d,m,?): ',
|
||||
$this->readOutput()
|
||||
);
|
||||
$this->truncateStreams();
|
||||
|
||||
$this->sendInput('');
|
||||
$result = ConsoleStub::select('Use Default', $options, 'm');
|
||||
$this->assertEquals('m', $result);
|
||||
$this->truncateStreams();
|
||||
|
||||
$this->sendInput('', 'd');
|
||||
$result = ConsoleStub::select('Empty without Default', $options);
|
||||
$this->assertEquals('Empty without Default (c,d,m,?): Empty without Default (c,d,m,?): ', $this->readOutput());
|
||||
$this->assertEquals('d', $result);
|
||||
$this->truncateStreams();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property string name
|
||||
* @property array types
|
||||
* @property string description
|
||||
*/
|
||||
class TestConsoleModel extends DynamicModel
|
||||
{
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['name', 'required'],
|
||||
['name', 'string', 'max' => 100]
|
||||
];
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->defineAttribute('name');
|
||||
}
|
||||
}
|
||||
|
@ -1270,4 +1270,60 @@ class FileHelperTest extends TestCase
|
||||
[true, null, 'test'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getExtensionsByMimeTypeProvider
|
||||
* @param string $mimeType
|
||||
* @param array $extensions
|
||||
* @return void
|
||||
*/
|
||||
public function testGetExtensionsByMimeType($mimeType, $extensions)
|
||||
{
|
||||
$this->assertEquals($extensions, FileHelper::getExtensionsByMimeType($mimeType));
|
||||
}
|
||||
|
||||
public function getExtensionsByMimeTypeProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'application/json',
|
||||
[
|
||||
'json',
|
||||
],
|
||||
],
|
||||
[
|
||||
'image/jpeg',
|
||||
[ // Note: For backwards compatibility the (alphabetic) order of `framework/helpers/mimeTypes.php` is expected.
|
||||
'jfif',
|
||||
'jpe',
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'pjp',
|
||||
'pjpeg',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getExtensionByMimeTypeProvider
|
||||
* @param string $mimeType
|
||||
* @param bool $preferShort
|
||||
* @param array $extension
|
||||
* @return void
|
||||
*/
|
||||
public function testGetExtensionByMimeType($mimeType, $preferShort, $extension)
|
||||
{
|
||||
$this->assertEquals($extension, FileHelper::getExtensionByMimeType($mimeType, $preferShort));
|
||||
}
|
||||
|
||||
public function getExtensionByMimeTypeProvider()
|
||||
{
|
||||
return [
|
||||
['application/json', true, 'json'],
|
||||
['application/json', false, 'json'],
|
||||
['image/jpeg', true, 'jpg'],
|
||||
['image/jpeg', false, 'jpeg'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ class FormatConverterTest extends TestCase
|
||||
|
||||
public function testIntlIcuToPhpShortForm()
|
||||
{
|
||||
$this->assertEquals('n/j/y', FormatConverter::convertDateIcuToPhp('short', 'date', 'en-US'));
|
||||
$this->assertEquals('d.m.y', FormatConverter::convertDateIcuToPhp('short', 'date', 'de-DE'));
|
||||
$this->assertEqualsAnyWhitespace('n/j/y', FormatConverter::convertDateIcuToPhp('short', 'date', 'en-US'));
|
||||
$this->assertEqualsAnyWhitespace('d.m.y', FormatConverter::convertDateIcuToPhp('short', 'date', 'de-DE'));
|
||||
}
|
||||
|
||||
public function testIntlIcuToPhpShortFormDefaultLang()
|
||||
@ -53,13 +53,13 @@ class FormatConverterTest extends TestCase
|
||||
|
||||
public function testIntlIcuToPhpShortFormTime()
|
||||
{
|
||||
$this->assertEquals('g:i A', FormatConverter::convertDateIcuToPhp('short', 'time', 'en-US'));
|
||||
$this->assertEquals('H:i', FormatConverter::convertDateIcuToPhp('short', 'time', 'de-DE'));
|
||||
$this->assertEqualsAnyWhitespace('g:i A', FormatConverter::convertDateIcuToPhp('short', 'time', 'en-US'));
|
||||
$this->assertEqualsAnyWhitespace('H:i', FormatConverter::convertDateIcuToPhp('short', 'time', 'de-DE'));
|
||||
}
|
||||
|
||||
public function testIntlIcuToPhpShortFormDateTime()
|
||||
{
|
||||
$this->assertEquals('n/j/y, g:i A', FormatConverter::convertDateIcuToPhp('short', 'datetime', 'en-US'));
|
||||
$this->assertEqualsAnyWhitespace('n/j/y, g:i A', FormatConverter::convertDateIcuToPhp('short', 'datetime', 'en-US'));
|
||||
$this->assertEquals(
|
||||
PHP_VERSION_ID < 50600 ? 'd.m.y H:i' : 'd.m.y, H:i',
|
||||
FormatConverter::convertDateIcuToPhp('short', 'datetime', 'de-DE')
|
||||
@ -208,13 +208,13 @@ class FormatConverterTest extends TestCase
|
||||
|
||||
public function testIntlIcuToJuiShortFormTime()
|
||||
{
|
||||
$this->assertEquals(': ', FormatConverter::convertDateIcuToJui('short', 'time', 'en-US'));
|
||||
$this->assertEquals(':', FormatConverter::convertDateIcuToJui('short', 'time', 'de-DE'));
|
||||
$this->assertEqualsAnyWhitespace(': ', FormatConverter::convertDateIcuToJui('short', 'time', 'en-US'));
|
||||
$this->assertEqualsAnyWhitespace(':', FormatConverter::convertDateIcuToJui('short', 'time', 'de-DE'));
|
||||
}
|
||||
|
||||
public function testIntlIcuToJuiShortFormDateTime()
|
||||
{
|
||||
$this->assertEquals('m/d/y, : ', FormatConverter::convertDateIcuToJui('short', 'datetime', 'en-US'));
|
||||
$this->assertEqualsAnyWhitespace('m/d/y, : ', FormatConverter::convertDateIcuToJui('short', 'datetime', 'en-US'));
|
||||
$this->assertEquals(
|
||||
PHP_VERSION_ID < 50600 ? 'dd.mm.y :' : 'dd.mm.y, :',
|
||||
FormatConverter::convertDateIcuToJui('short', 'datetime', 'de-DE')
|
||||
|
@ -96,14 +96,18 @@ class JsonTest extends TestCase
|
||||
$data->data = (object) null;
|
||||
$this->assertSame('{}', Json::encode($data));
|
||||
|
||||
// Generator
|
||||
$data = function () {
|
||||
foreach (['a' => 1, 'b' => 2] as $name => $value) {
|
||||
yield $name => $value;
|
||||
}
|
||||
};
|
||||
|
||||
$this->assertSame('{"a":1,"b":2}', Json::encode($data()));
|
||||
// Generator (Only supported since PHP 5.5)
|
||||
if (PHP_VERSION_ID >= 50500) {
|
||||
$data = eval(<<<'PHP'
|
||||
return function () {
|
||||
foreach (['a' => 1, 'b' => 2] as $name => $value) {
|
||||
yield $name => $value;
|
||||
}
|
||||
};
|
||||
PHP
|
||||
);
|
||||
$this->assertSame('{"a":1,"b":2}', Json::encode($data()));
|
||||
}
|
||||
}
|
||||
|
||||
public function testHtmlEncode()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -143,23 +143,23 @@ class FormatterDateTest extends TestCase
|
||||
public function testAsTime()
|
||||
{
|
||||
$value = time();
|
||||
$this->assertSame(date('g:i:s A', $value), $this->formatter->asTime($value));
|
||||
$this->assertSameAnyWhitespace(date('g:i:s A', $value), $this->formatter->asTime($value));
|
||||
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value, 'php:h:i:s A'));
|
||||
|
||||
$value = new DateTime();
|
||||
$this->assertSame(date('g:i:s A', $value->getTimestamp()), $this->formatter->asTime($value));
|
||||
$this->assertSameAnyWhitespace(date('g:i:s A', $value->getTimestamp()), $this->formatter->asTime($value));
|
||||
$this->assertSame(date('h:i:s A', $value->getTimestamp()), $this->formatter->asTime($value, 'php:h:i:s A'));
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
|
||||
$value = new \DateTimeImmutable();
|
||||
$this->assertSame(date('g:i:s A', $value->getTimestamp()), $this->formatter->asTime($value));
|
||||
$this->assertSameAnyWhitespace(date('g:i:s A', $value->getTimestamp()), $this->formatter->asTime($value));
|
||||
$this->assertSame(date('h:i:s A', $value->getTimestamp()), $this->formatter->asTime($value, 'php:h:i:s A'));
|
||||
}
|
||||
|
||||
// empty input
|
||||
$this->assertSame('12:00:00 AM', $this->formatter->asTime(''));
|
||||
$this->assertSame('12:00:00 AM', $this->formatter->asTime(0));
|
||||
$this->assertSame('12:00:00 AM', $this->formatter->asTime(false));
|
||||
$this->assertSameAnyWhitespace('12:00:00 AM', $this->formatter->asTime(''));
|
||||
$this->assertSameAnyWhitespace('12:00:00 AM', $this->formatter->asTime(0));
|
||||
$this->assertSameAnyWhitespace('12:00:00 AM', $this->formatter->asTime(false));
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
|
||||
}
|
||||
@ -178,23 +178,35 @@ class FormatterDateTest extends TestCase
|
||||
public function testAsDatetime()
|
||||
{
|
||||
$value = time();
|
||||
$this->assertRegExp(date('~M j, Y,? g:i:s A~', $value), $this->formatter->asDatetime($value));
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces(date('~M j, Y,? g:i:s A~', $value)),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime($value))
|
||||
);
|
||||
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A'));
|
||||
|
||||
$value = new DateTime();
|
||||
$this->assertRegExp(date('~M j, Y,? g:i:s A~', $value->getTimestamp()), $this->formatter->asDatetime($value));
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces(date('~M j, Y,? g:i:s A~', $value->getTimestamp())),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime($value))
|
||||
);
|
||||
$this->assertSame(date('Y/m/d h:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A'));
|
||||
|
||||
// empty time
|
||||
$value = new DateTime();
|
||||
$date = $value->format('Y-m-d');
|
||||
$value = new DateTime($date);
|
||||
$this->assertRegExp(date('~M j, Y,? g:i:s A~', $value->getTimestamp()), $this->formatter->asDatetime($date));
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces(date('~M j, Y,? g:i:s A~', $value->getTimestamp())),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime($date))
|
||||
);
|
||||
$this->assertSame(date('Y/m/d h:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($date, 'php:Y/m/d h:i:s A'));
|
||||
|
||||
if (PHP_VERSION_ID >= 50500) {
|
||||
$value = new \DateTimeImmutable();
|
||||
$this->assertRegExp(date('~M j, Y,? g:i:s A~', $value->getTimestamp()), $this->formatter->asDatetime($value));
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces(date('~M j, Y,? g:i:s A~', $value->getTimestamp())),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime($value))
|
||||
);
|
||||
$this->assertSame(date('Y/m/d h:i:s A', $value->getTimestamp()), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A'));
|
||||
}
|
||||
|
||||
@ -205,9 +217,18 @@ class FormatterDateTest extends TestCase
|
||||
}
|
||||
|
||||
// empty input
|
||||
$this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime(''));
|
||||
$this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime(0));
|
||||
$this->assertRegExp('~Jan 1, 1970,? 12:00:00 AM~', $this->formatter->asDatetime(false));
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces('~Jan 1, 1970,? 12:00:00 AM~'),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime(''))
|
||||
);
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces('~Jan 1, 1970,? 12:00:00 AM~'),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime(0))
|
||||
);
|
||||
$this->assertRegExp(
|
||||
$this->sanitizeWhitespaces('~Jan 1, 1970,? 12:00:00 AM~'),
|
||||
$this->sanitizeWhitespaces($this->formatter->asDatetime(false))
|
||||
);
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use yii\helpers\FileHelper;
|
||||
use yii\log\Dispatcher;
|
||||
use yii\log\FileTarget;
|
||||
use yii\log\Logger;
|
||||
use yiiunit\framework\log\mocks\CustomLogger;
|
||||
use yiiunit\TestCase;
|
||||
|
||||
/**
|
||||
@ -110,4 +111,53 @@ class FileTargetTest extends TestCase
|
||||
$this->assertFileNotExists($logFile . '.3');
|
||||
$this->assertFileNotExists($logFile . '.4');
|
||||
}
|
||||
|
||||
public function testLogEmptyStrings()
|
||||
{
|
||||
$logFile = Yii::getAlias('@yiiunit/runtime/log/filetargettest.log');
|
||||
$this->clearLogFile($logFile);
|
||||
|
||||
$logger = new CustomLogger();
|
||||
$logger->logFile = $logFile;
|
||||
$logger->messages = array_fill(0, 1, 'xxx');
|
||||
$logger->export();
|
||||
|
||||
$test = file($logFile);
|
||||
$this->assertEquals("xxx\n", $test[0]);
|
||||
|
||||
$this->clearLogFile($logFile);
|
||||
|
||||
$logger = new CustomLogger();
|
||||
$logger->logFile = $logFile;
|
||||
$logger->messages = array_fill(0, 3, 'xxx');
|
||||
$logger->export();
|
||||
|
||||
$test = file($logFile);
|
||||
$this->assertEquals("xxx\n", $test[0]);
|
||||
$this->assertEquals("xxx\n", $test[1]);
|
||||
$this->assertEquals("xxx\n", $test[2]);
|
||||
|
||||
$this->clearLogFile($logFile);
|
||||
|
||||
$logger->messages = array_fill(0, 1, 'yyy');
|
||||
$logger->export();
|
||||
|
||||
$this->assertFileNotExists($logFile);
|
||||
|
||||
$logger->messages = array_fill(0, 10, '');
|
||||
$logger->export();
|
||||
|
||||
$this->assertFileNotExists($logFile);
|
||||
|
||||
$logger->messages = array_fill(0, 10, null);
|
||||
$logger->export();
|
||||
|
||||
$this->assertFileNotExists($logFile);
|
||||
}
|
||||
|
||||
private function clearLogFile($logFile)
|
||||
{
|
||||
FileHelper::removeDirectory(dirname($logFile));
|
||||
mkdir(dirname($logFile), 0777, true);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user