diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000000..e8c068d6bc5 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,41 @@ +build: false +version: dev-{build} +clone_folder: C:\projects\yii2 + +environment: + matrix: + - php_ver: 7.4.0 + +cache: + - '%APPDATA%\Composer' + - '%LOCALAPPDATA%\Composer' + - C:\tools\php -> .appveyor.yml + - C:\tools\composer.phar -> .appveyor.yml + +init: + - SET PATH=C:\tools\php;%PATH% + +install: + - ps: Set-Service wuauserv -StartupType Manual + - IF NOT EXIST C:\tools\php (choco install --yes --allow-empty-checksums php --version %php_ver% --params '/InstallDir:C:\tools\php') + - cd C:\tools\php + - copy php.ini-production php.ini + - echo date.timezone="UTC" >> php.ini + - echo memory_limit=512M >> php.ini + - echo extension_dir=ext >> php.ini + - echo extension=php_curl.dll >> php.ini + - echo extension=php_fileinfo.dll >> php.ini + - echo extension=php_gd2.dll >> php.ini + - echo extension=php_intl.dll >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo extension=php_pdo_sqlite.dll >> php.ini + - IF NOT EXIST C:\tools\composer.phar (cd C:\tools && appveyor DownloadFile https://getcomposer.org/download/2.6.3/composer.phar) + +before_test: + - cd C:\projects\yii2 + - php C:\tools\composer.phar update --no-interaction --no-progress --prefer-stable --no-ansi + +test_script: + - cd C:\projects\yii2 + - vendor\bin\phpunit --exclude-group mssql,mysql,pgsql,sqlite,db,oci,wincache,cubrid diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..5c156024dfa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +vendor +docs \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..5e9a93ea50d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..5b50d8e96bc --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,24 @@ +# Reformat code to be PSR-2 compatible +b5f8a4dc22d5f8188405a2099d85fc154226c9b2 +# Added php-cs-fixer coding standards validation to Travis CI +ba0ab403b52124c941dbeb46fbd9efdc12252a5d +# Coding style fixes +9d327baa8b2c80b53d4d405678f03e6b89ff6e38 +# Add visibility for all class elements +c82da8dc829d557e82b12edbed9e37003dfcc9a3 +# Fix codestyle in build and tests +909396074eef92d62dd34b4709bb7351e722bec3 +# Add void return to method in tests +d71f7309aeec1ac0fef223840cb65bbbf96f1f99 +# Use `::class` instead of `::className()` in tests +c960f93dfeefe760eb44bcdfa4463dcc7b29cc43 +# Replace deprecated PHPUnit mock builder `setMethods()` usage with `createPartialMock()`, `onlyMethods()` and `addMethods()` methods +80545100b3f40423118b5cd413fcdb9c7dd7fa5e +# Make test data providers static and declare array return types +da20adc82aef3eb5ae0bad3889fe6695e6424f06 +# Fix codestyle in `tests` +2f8e62d6b64324099b44cbac69395fef0e53b13e +# Short array syntax +1f6a8230732d829cdf2f3ca6755e4ac32f2c6f4f +# CS fixes. +7a7d2a9c06c099de8064729ca3fc95fb24241b75 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..87563215ea3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Autodetect text files +* text=auto eol=lf + +# ...Unless the name matches the following overriding patterns + +# Definitively text files +*.php text +*.css text +*.js text +*.txt text +*.md text +*.xml text +*.json text +*.bat text +*.sql text +*.yml text + +# Ensure those won't be messed up with +*.png binary +*.jpg binary +*.gif binary +*.ttf binary + +# Ignore some meta files when creating an archive of this repository +# We do not ignore any content, because this repo represents the +# `yiisoft/yii2-dev` package, which is expected to ship all tests and docs. +/.appveyor.yml export-ignore +/.github export-ignore +/.editorconfig export-ignore +/.git-blame-ignore-revs export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore + +# Avoid merge conflicts in CHANGELOG +# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/ +/framework/CHANGELOG.md merge=union + diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000000..bdf7df026de --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,7 @@ +Contributing to Yii 2 +===================== + +- [Report an issue](../docs/internals/report-an-issue.md) +- [Translate documentation or messages](../docs/internals/translation-workflow.md) +- [Give us feedback or start a design discussion](https://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) +- [Contribute to the core code or fix bugs](../docs/internals/git-workflow.md) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..d6f1e3b4a1b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +open_collective: yiisoft +github: [yiisoft] +tidelift: "packagist/yiisoft/yii2" diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..9dfde405a63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ + + +### What steps will reproduce the problem? + +### What is the expected result? + +### What do you get instead? + + +### Additional info + +| Q | A +| ---------------- | --- +| Yii version | 2.0.? +| PHP version | +| Operating system | diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..cecccf6dcf1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +| Q | A +| ------------- | --- +| Is bugfix? | ✔️/❌ +| New feature? | ✔️/❌ +| Breaks BC? | ✔️/❌ +| Fixed issues | diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000000..405acca4e76 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +Please use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you find in Yii. +DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help. + +Please note that as a non-commercial OpenSource project we are not able to pay bounties at the moment. diff --git a/.github/actions/php-setup/action.yml b/.github/actions/php-setup/action.yml new file mode 100644 index 00000000000..c31c7646d03 --- /dev/null +++ b/.github/actions/php-setup/action.yml @@ -0,0 +1,83 @@ +--- +name: PHP setup with composer. +description: Setup PHP environment with composer and install dependencies. + +inputs: + composer-command: + description: Composer command (install or update) to run. + default: update + required: false + type: string + composer-flags: + description: Additional composer flags + default: >- + --prefer-dist + --no-interaction + --no-progress + --optimize-autoloader + --ansi + required: false + type: string + composer-version: + description: Composer version to use. + default: + required: false + type: string + coverage-driver: + description: Code coverage driver to use (pcov, xdebug). + default: none + required: false + type: string + extensions: + description: List of extensions to PHP. + default: + required: false + type: string + ignore-platform-reqs: + description: Whether to add --ignore-platform-reqs to composer command. + default: false + required: false + type: boolean + ini-values: + description: Initial values for PHP configuration. + default: date.timezone='UTC' + required: false + type: string + php-version: + description: PHP versions as a JSON array string '["8.4"]'. + default: '["7.4","8.0","8.1","8.2","8.3","8.4"]' + required: false + type: string + tools: + description: Tools to test, separated by comma. + default: pie + required: false + type: string + +runs: + using: composite + steps: + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + coverage: ${{ inputs.coverage-driver }} + extensions: ${{ inputs.extensions }} + ini-values: ${{ inputs.ini-values }} + php-version: ${{ inputs.php-version }} + tools: ${{ inputs.tools }} + + - name: Update composer. + shell: bash + run: composer self-update ${{ inputs.composer-version }} + + - name: Install dependencies with composer. + shell: bash + run: >- + composer + ${{ inputs.composer-command }} + ${{ inputs.composer-flags }} + ${{ + inputs.ignore-platform-reqs == 'true' + && '--ignore-platform-reqs' + || '' + }} diff --git a/.github/actions/phpunit/action.yml b/.github/actions/phpunit/action.yml new file mode 100644 index 00000000000..6d4b24e7823 --- /dev/null +++ b/.github/actions/phpunit/action.yml @@ -0,0 +1,129 @@ +--- +name: PHPUnit Test Runner. +description: Run PHPUnit tests with coverage and configurable options. + +inputs: + additional-args: + description: Additional PHPUnit arguments. + default: "--log-junit junit.xml --verbose" + required: false + type: string + configuration: + description: PHPUnit configuration file. + default: "" + required: false + type: string + coverage-driver: + description: Code coverage driver to use (pcov, xdebug, none). + default: none + required: false + type: string + coverage-file: + description: Coverage output file name. + default: coverage.xml + required: false + type: string + coverage-format: + description: Coverage report format (clover, html, xml). + default: clover + required: false + type: string + coverage-token: + description: Codecov token for uploading coverage. + default: "" + required: false + type: string + debug: + description: Display warnings in phpunit. + default: "" + required: false + type: string + exclude-group: + description: Exclude group from phpunit. + default: "" + required: false + type: string + group: + description: Include specific group in phpunit. + default: "" + required: false + type: string + path: + description: Path to PHPUnit executable. + default: vendor/bin/phpunit + required: false + type: string + test-suite: + description: Specific test suite to run. + default: "" + required: false + type: string + +runs: + using: composite + steps: + - name: Build PHPUnit command. + id: build-cmd + shell: bash + run: | + PATH_INPUT="${{ inputs.path }}" + CONFIG_INPUT="${{ inputs.configuration }}" + SUITE_INPUT="${{ inputs.test-suite }}" + GROUP_INPUT="${{ inputs.group }}" + EXCLUDE_GROUP_INPUT="${{ inputs.exclude-group }}" + DEBUG_INPUT="${{ inputs.debug }}" + ADDITIONAL_ARGS="${{ inputs.additional-args }}" + COVERAGE_DRIVER="${{ inputs.coverage-driver }}" + COVERAGE_FORMAT="${{ inputs.coverage-format }}" + COVERAGE_FILE="${{ inputs.coverage-file }}" + + PHPUNIT_CMD="$PATH_INPUT --colors=always" + + add_param() { + if [ -n "$2" ]; then + PHPUNIT_CMD="$PHPUNIT_CMD $1 $2" + fi + } + + if [ -n "$COVERAGE_DRIVER" ] && [ "$COVERAGE_DRIVER" != "none" ]; then + PHPUNIT_CMD="$PHPUNIT_CMD --coverage-$COVERAGE_FORMAT=$COVERAGE_FILE" + fi + + add_param "--configuration" "$CONFIG_INPUT" + add_param "--testsuite" "$SUITE_INPUT" + add_param "--group" "$GROUP_INPUT" + add_param "--exclude-group" "$EXCLUDE_GROUP_INPUT" + + if [ -n "$DEBUG_INPUT" ]; then + PHPUNIT_CMD="$PHPUNIT_CMD $DEBUG_INPUT" + fi + + if [ -n "$ADDITIONAL_ARGS" ]; then + PHPUNIT_CMD="$PHPUNIT_CMD $ADDITIONAL_ARGS" + fi + + echo "command=$PHPUNIT_CMD" >> $GITHUB_OUTPUT + echo "PHPUnit command: $PHPUNIT_CMD" + + - name: Run PHPUnit tests on Linux. + shell: bash + if: runner.os != 'Windows' + run: ${{ steps.build-cmd.outputs.command }} + + - name: Run PHPUnit tests on Windows. + shell: pwsh + if: runner.os == 'Windows' + run: Invoke-Expression "${{ steps.build-cmd.outputs.command }}" + + - name: Upload test results to Codecov. + if: ${{ !cancelled() && inputs.coverage-driver != 'none' }} + uses: codecov/test-results-action@v1 + with: + token: ${{ inputs.coverage-token }} + + - name: Upload coverage to Codecov. + if: ${{ !cancelled() && inputs.coverage-driver != 'none' }} + uses: codecov/codecov-action@v5 + with: + files: ./${{ inputs.coverage-file }} + token: ${{ inputs.coverage-token }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..bca6aa771a8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,113 @@ +--- +name: build + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, dom, imagick, intl, mbstring, mcrypt, memcached + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_EXCLUDE_GROUP: db,wincache + XDEBUG_MODE: coverage + +jobs: + phpunit: + name: PHP ${{ matrix.php }} + + env: + COVERAGE_DRIVER: ${{ matrix.php < 8.1 && 'xdebug' || 'pcov' }} + IGNORE_PLATFORM_REQS: false + + runs-on: ubuntu-latest + + services: &memcached-service + memcached: + image: memcached:latest + ports: + - 11211:11211 + options: >- + --health-cmd "timeout 5 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/11211'" + --health-interval 10s + --health-retries 5 + --health-timeout 5s + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5] + + steps: &build-steps + - name: Checkout. + uses: actions/checkout@v5 + + - name: Generate french locale. + run: sudo locale-gen fr_FR.UTF-8 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ignore-platform-reqs: ${{ env.IGNORE_PLATFORM_REQS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + exclude-group: ${{ env.PHPUNIT_EXCLUDE_GROUP }} + + phpunit-dev: + name: PHP ${{ matrix.php }} + + env: + COVERAGE_DRIVER: none + IGNORE_PLATFORM_REQS: true + + runs-on: ubuntu-latest + + services: *memcached-service + + strategy: + fail-fast: false + matrix: + php: [8.6] + + steps: *build-steps diff --git a/.github/workflows/ci-mariadb.yml b/.github/workflows/ci-mariadb.yml new file mode 100644 index 00000000000..eb3f6cf2ba2 --- /dev/null +++ b/.github/workflows/ci-mariadb.yml @@ -0,0 +1,121 @@ +--- +name: ci-mariadb + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, intl, pdo, pdo_mysql + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: mysql + XDEBUG_MODE: coverage + +jobs: + tests: + name: PHP ${{ matrix.php }}-${{ matrix.mariadb }} + + env: + COVERAGE_DRIVER: xdebug + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.5] + mariadb: + - mariadb:10.4 + - mariadb:latest + + services: &mariadb-service + mysql: + image: ${{ matrix.mariadb }} + env: + MARIADB_ROOT_PASSWORD: root + MARIADB_DATABASE: yiitest + ports: + - 3306:3306 + options: >- + --name=mariadb + --health-cmd="mariadb-admin ping" + --health-interval=10s + --health-retries=3 + --health-timeout=5s + --mount type=tmpfs,destination=/var/lib/mysql + + steps: &mariadb-steps + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} + + tests-dev: + name: PHP ${{ matrix.php }}-${{ matrix.mariadb }} + + env: + COVERAGE_DRIVER: none + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [8.0, 8.1, 8.2, 8.3, 8.4] + mariadb: + - mariadb:latest + + services: *mariadb-service + + steps: *mariadb-steps diff --git a/.github/workflows/ci-mssql.yml b/.github/workflows/ci-mssql.yml new file mode 100644 index 00000000000..3cc99944df0 --- /dev/null +++ b/.github/workflows/ci-mssql.yml @@ -0,0 +1,127 @@ +--- +name: ci-mssql + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, intl, pdo, pdo_sqlsrv + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: mssql + XDEBUG_MODE: coverage + +jobs: + tests: + name: PHP ${{ matrix.php }}-mssql-${{ matrix.mssql }} + + env: + COVERAGE_DRIVER: xdebug + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.5] + mssql: + - server:2019-latest + - server:2022-latest + + services: &mssql-service + mssql: + image: mcr.microsoft.com/mssql/${{ matrix.mssql }} + env: + SA_PASSWORD: YourStrong!Passw0rd + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --name=mssql + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" + --health-interval=10s + --health-retries=3 + --health-timeout=5s + + steps: &mssql-steps + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Install ODBC driver. + run: sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 + + - name: Create MS SQL Database. + run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE yiitest' + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} + + tests-dev: + name: PHP ${{ matrix.php }}-mssql-${{ matrix.mssql }} + + env: + COVERAGE_DRIVER: none + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [8.0, 8.1, 8.2, 8.3, 8.4] + mssql: + - server:2022-latest + + services: *mssql-service + + steps: *mssql-steps diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml new file mode 100644 index 00000000000..ac595b54a2a --- /dev/null +++ b/.github/workflows/ci-mysql.yml @@ -0,0 +1,119 @@ +--- +name: ci-mysql + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, intl, pdo, pdo_mysql + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: mysql + XDEBUG_MODE: coverage + +jobs: + tests: + name: PHP ${{ matrix.php }}-mysql-${{ matrix.mysql }} + + env: + COVERAGE_DRIVER: xdebug + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.5] + mysql: [5.7, latest] + + services: &mysql-service + mysql: + image: mysql:${{ matrix.mysql }} + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: yiitest + ports: + - 3306:3306 + options: >- + --name=mysql + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-retries=3 + --health-timeout=5s + --mount type=tmpfs,destination=/var/lib/mysql + + steps: &mysql-steps + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} + + tests-dev: + name: PHP ${{ matrix.php }}-mysql-${{ matrix.mysql }} + + env: + COVERAGE_DRIVER: none + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [8.0, 8.1, 8.2, 8.3, 8.4] + mysql: + - latest + + services: *mysql-service + + steps: *mysql-steps diff --git a/.github/workflows/ci-node.yml b/.github/workflows/ci-node.yml new file mode 100644 index 00000000000..911d9ae5a4d --- /dev/null +++ b/.github/workflows/ci-node.yml @@ -0,0 +1,68 @@ +name: build-node + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +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 10 on ubuntu-latest + + runs-on: ubuntu-latest + + steps: + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - 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@v4 + with: + node-version: 20 + + - name: Tests. + run: | + npm install + npm run lint + npm test diff --git a/.github/workflows/ci-oracle.yml b/.github/workflows/ci-oracle.yml new file mode 100644 index 00000000000..c321fe4a712 --- /dev/null +++ b/.github/workflows/ci-oracle.yml @@ -0,0 +1,88 @@ +--- +name: ci-oracle + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: PHP ${{ matrix.php }}-oci + + env: + COVERAGE_DRIVER: ${{ matrix.php == 7.4 && 'xdebug' || 'none' }} + PHP_EXTENSIONS: curl, intl, oci8, pdo, pdo_oci + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: oci + XDEBUG_MODE: coverage + + runs-on: ubuntu-latest + + strategy: + matrix: + php: [7.4] + + services: + oci: + image: wnameless/oracle-xe-11g-r2:latest + ports: + - 1521:1521 + options: >- + --name=oci + + steps: + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} diff --git a/.github/workflows/ci-pgsql.yml b/.github/workflows/ci-pgsql.yml new file mode 100644 index 00000000000..5ff7d6dfc9b --- /dev/null +++ b/.github/workflows/ci-pgsql.yml @@ -0,0 +1,120 @@ +--- +name: ci-pgsql + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, intl, pdo, pdo_pgsql + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: pgsql + XDEBUG_MODE: coverage + +jobs: + tests: + name: PHP ${{ matrix.php }}-pgsql-${{ matrix.pgsql }} + + env: + COVERAGE_DRIVER: xdebug + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.5] + pgsql: [10, 11, 12, 13, 14, 15, 16, 17] + + services: &pgsql-service + postgres: + image: postgres:${{ matrix.pgsql }} + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: yiitest + ports: + - 5432:5432 + options: >- + --name=postgres + --health-cmd="pg_isready" + --health-interval=10s + --health-retries=3 + --health-timeout=5s + --mount type=tmpfs,destination=/var/lib/postgresql/data + + steps: &pgsql-steps + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} + + tests-dev: + name: PHP ${{ matrix.php }}-pgsql-${{ matrix.pgsql }} + + env: + COVERAGE_DRIVER: none + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [8.0, 8.1, 8.2, 8.3, 8.4] + pgsql: + - 17 + + services: *pgsql-service + + steps: *pgsql-steps diff --git a/.github/workflows/ci-sqlite.yml b/.github/workflows/ci-sqlite.yml new file mode 100644 index 00000000000..086a2ff0779 --- /dev/null +++ b/.github/workflows/ci-sqlite.yml @@ -0,0 +1,98 @@ +--- +name: ci-sqlite + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: curl, intl, pdo, pdo_sqlite + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + PHPUNIT_GROUP: sqlite + XDEBUG_MODE: coverage + +jobs: + tests: + name: PHP ${{ matrix.php }}-sqlite + + env: + COVERAGE_DRIVER: xdebug + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.5] + + steps: &sqlite-steps + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + + - name: Run PHPUnit tests. + uses: ./.github/actions/phpunit + with: + coverage-driver: ${{ env.COVERAGE_DRIVER }} + coverage-token: ${{ secrets.CODECOV_TOKEN }} + group: ${{ env.PHPUNIT_GROUP }} + + tests-dev: + name: PHP ${{ matrix.php }}-sqlite + + env: + COVERAGE_DRIVER: none + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [8.0, 8.1, 8.2, 8.3, 8.4] + + steps: *sqlite-steps diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000000..dd1861d3d3a --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,69 @@ +--- +name: lint + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + phpcs: + runs-on: ubuntu-latest + + name: PHP ${{ matrix.php }}-PHP_CodeSniffer + + strategy: + fail-fast: false + matrix: + php: [8.4] + + steps: + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + composer-command: install + php-version: ${{ matrix.php }} + tools: cs2pr + + - name: Run PHP_CodeSniffer. + run: vendor/bin/phpcs -q --report=checkstyle framework/ tests/ build/ | cs2pr diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000000..e9b83acdc9f --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,79 @@ +--- +name: static analysis + +permissions: + contents: read + pull-requests: write + +on: + pull_request: &ignore-paths + paths-ignore: + - ".appveyor.yml" + - ".dockerignore" + - ".editorconfig" + - ".git-blame-ignore-revs" + - ".gitattributes" + - ".github/CONTRIBUTING.md" + - ".github/FUNDING.yml" + - ".github/ISSUE_TEMPLATE.md" + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/SECURITY.md" + - ".gitignore" + - ".gitlab-ci.yml" + - "code-of-conduct.md" + - "docs/**" + - "framework/.gitignore" + - "framework/.phpstorm.meta.php" + - "framework/CHANGELOG.md" + - "framework/LICENSE.md" + - "framework/README.md" + - "framework/UPGRADE.md" + - "eslint.config.js" + - "LICENSE.md" + - "README.md" + - "ROADMAP.md" + + push: *ignore-paths + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + phpstan: + name: PHP ${{ matrix.php }}-PHPStan + + env: + PHP_EXTENSIONS: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, curl, dom, imagick, intl, mbstring, mcrypt, memcached + PHP_INI_VALUES: apc.enabled=1,apc.shm_size=32M,apc.enable_cli=1, date.timezone='UTC' + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5] + + steps: + - name: Monitor action permissions. + if: runner.os != 'Windows' + uses: GitHubSecurityLab/actions-permissions/monitor@v1 + + - name: Checkout. + uses: actions/checkout@v5 + + - name: Setup PHP with Composer. + uses: ./.github/actions/php-setup + with: + extensions: ${{ matrix.php < 8.0 && 'apc' || 'apcu' }}, ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }}, session.save_path="${{ runner.temp }}" + php-version: ${{ matrix.php }} + tools: cs2pr, pie + + - name: Static analysis PHP 7.x. + if: matrix.php == '7.3' || matrix.php == '7.4' + run: vendor/bin/phpstan analyse --configuration=phpstan-7x.dist.neon --error-format=checkstyle | cs2pr + + - name: Static analysis PHP 8.x. + if: matrix.php != '7.3' && matrix.php != '7.4' + run: vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr diff --git a/.gitignore b/.gitignore index 13fcf4a1839..ed9306c2527 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # phpstorm project files .idea +*.iml # netbeans project files nbproject @@ -9,11 +10,45 @@ nbproject .project .settings +# sublime text project / workspace files +*.sublime-project +*.sublime-workspace + +# visual studio code project files +.vscode + # windows thumbnail cache Thumbs.db # composer vendor dir -/yii/vendor +/vendor +# cubrid install dir +/cubrid # composer itself is not needed composer.phar + +# composer.lock in applications is ignored since it's automatically created by composer when application is installed +/apps/*/composer.lock + +# Mac DS_Store Files +.DS_Store + +# phpunit itself is not needed +phpunit.phar +# local phpunit config +/phpunit.xml +.phpunit.result.cache + +# ignore dev installed apps and extensions +/apps +/extensions +/packages + +# NPM packages +/node_modules +.env +package-lock.json + +# local phpstan config +phpstan.neon diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000000..d7797e87128 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,113 @@ +image: docker:latest + +services: + - docker:dind + +variables: + DOCKER_YII2_PHP_IMAGE: yiisoftware/yii2-php:7.4-apache + DOCKER_MYSQL_IMAGE: percona:5.7 + DOCKER_POSTGRES_IMAGE: postgres:9.3 + +before_script: + - apk add --no-cache git curl docker-compose + - docker info + - cd tests + +stages: + - travis + - test + - cleanup + +test: + stage: test + script: + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --exclude caching,db,data --log-junit tests/_junit/test.xml + +caching: + stage: test + only: + - tests/caching + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --group caching --exclude db + +db: + stage: test + only: + - tests/mysql + - tests/full + script: + - docker-compose up --build -d + - docker-compose run --rm php vendor/bin/phpunit -v --group db --exclude caching,mysql,pgsql,mssql,cubrid,oci + + +mysql: + stage: test + only: + - tests/mysql + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c "while ! curl mysql:3306; do ((c++)) && ((c==30)) && break; sleep 2; done" + - docker-compose run --rm php vendor/bin/phpunit -v --group mysql + + +pgsql: + stage: test + only: + - tests/pgsql + - tests/full + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.${CI_BUILD_NAME}.yml + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl postgres:5432; if [ $? == 52 ]; then break; fi; ((c++)) && ((c==25)) && break; sleep 2; done' + - docker-compose run --rm php vendor/bin/phpunit -v --group pgsql + + +cubrid: + stage: test + only: + - tests/cubrid + - tests/extra + script: + - cd cubrid + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl cubrid:1523; if [ $? == 56 ]; then break; fi; ((c++)) && ((c==20)) && break; sleep 3; done' + - sleep 5 + - docker-compose run --rm php /project/vendor/bin/phpunit -v --group cubrid + + +mssql: + stage: test + only: + - tests/mssql + - tests/extra + script: + - cd mssql + - docker-compose up --build -d + # wait for db (retry X times) + - docker-compose run --rm php bash -c 'while [ true ]; do curl mssql:1433; if [ $? == 52 ]; then break; fi; ((c++)) && ((c==15)) && break; sleep 5; done' + - sleep 3 + # Note: Password has to be the last parameter + - docker-compose run --rm sqlcmd sh -c 'sqlcmd -S mssql -U sa -Q "CREATE DATABASE yii2test" -P Microsoft-12345' + - docker-compose run --rm php vendor/bin/phpunit -v --group mssql + + +travis: + stage: travis + only: + - travis + script: + - export COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml:docker-compose.pgsql.yml + - docker-compose up --build -d + # wait for dbs ... + - sleep 10 + - docker-compose run --rm php vendor/bin/phpunit -v --exclude mssql,cubrid,oci,wincache,cubrid + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e4b82780384..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - -env: - - DB=mysql - -before_script: - - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - -script: phpunit \ No newline at end of file diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 00000000000..c323cd41260 --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://www.yiiframework.com/funding.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..4e779bdeba4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +ARG DOCKER_YII2_PHP_IMAGE +FROM ${DOCKER_YII2_PHP_IMAGE} + +# Project source-code +WORKDIR /project +ADD composer.* /project/ +# Install packages +RUN /usr/local/bin/composer install --prefer-dist +ADD ./ /project +ENV PATH /project/vendor/bin:${PATH} diff --git a/LICENSE.md b/LICENSE.md index 6edcc4f571a..bc5674fe470 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,4 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software (https://www.yiiframework.com/) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -14,7 +11,7 @@ are met: notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Yii Software LLC nor the names of its + * Neither the name of Yii Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -29,4 +26,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 9bd64803a4f..2348867cc7c 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,115 @@ -Yii 2.0 Public Preview -====================== +

+ + + + + Yii Framework + + +

+ +Yii 2 is a modern framework designed to be a solid foundation for your PHP application. + +It is fast, secure and efficient and works right out of the box pre-configured with reasonable defaults. +The framework is easy to adjust to meet your needs, because Yii has been designed to be flexible. + +[![Latest Stable Version](https://img.shields.io/packagist/v/yiisoft/yii2.svg?logo=packagist&style=for-the-badge&label=Stable)](https://packagist.org/packages/yiisoft/yii2) +[![Total Downloads](https://img.shields.io/packagist/dt/yiisoft/yii2.svg?style=for-the-badge)](https://packagist.org/packages/yiisoft/yii2) +[![Build Status](https://img.shields.io/github/actions/workflow/status/yiisoft/yii2/build.yml?style=for-the-badge&logo=github&label=Build)](https://github.com/yiisoft/yii2/actions/workflows/build.yml) +[![Static Analysis](https://img.shields.io/github/actions/workflow/status/yiisoft/yii2/static.yml?style=for-the-badge&label=Static&logo=github)](https://github.com/yiisoft/yii2/actions/workflows/static.yml) +[![codecov](https://img.shields.io/codecov/c/github/yiisoft/yii2.svg?style=for-the-badge&logo=codecov&logoColor=white&label=Codecov)](https://codecov.io/gh/yiisoft/yii2) + +Installation +------------ -Thank you for choosing Yii - a high-performance component-based PHP framework. +> [!IMPORTANT] +> - The minimum required [PHP](https://www.php.net/) version of Yii is PHP `7.4`. +> - It works best with PHP `8`. -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). +- [Follow the Definitive Guide](https://www.yiiframework.com/doc-2.0/guide-start-installation.html) +in order to get step by step instructions. -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** +Documentation +------------- -[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) +- A [Definitive Guide](https://www.yiiframework.com/doc/guide/2.0) and +a [Class Reference](https://www.yiiframework.com/doc/api/2.0) cover every detail +of the framework. +- There is a [PDF version](https://www.yiiframework.com/doc/download/yii-guide-2.0-en.pdf) of the Definitive Guide +and a [Definitive Guide Mirror](http://stuff.cebe.cc/yii2docs/) which is updated every 15 minutes. +- For Yii 1.1 users, there is [Upgrading from Yii 1.1](https://www.yiiframework.com/doc/guide/2.0/en/intro-upgrade-from-v1) +to get an idea of what has changed in 2.0. +Versions & PHP compatibility +---------------------------- -DIRECTORY STRUCTURE -------------------- +> [!NOTE] +> See ["Release Cycle" at the website](https://www.yiiframework.com/release-cycle) for detailed information about supported versions. - apps/ ready-to-use Web apps built on Yii 2 - basic/ a simple app supporting user login and contact page - build/ internally used build tools - docs/ documentation - yii/ framework source files - tests/ tests of the core framework code +Community +--------- +- Participate in [discussions at forums](https://www.yiiframework.com/forum/). +- [Community Slack](https://join.slack.com/t/yii/shared_invite/MjIxMjMxMTk5MTU1LTE1MDE3MDAwMzMtM2VkMTMyMjY1Ng) and [Chat in IRC](https://www.yiiframework.com/chat/). +- Follow us on [Facebook](https://www.facebook.com/groups/yiitalk/), [Twitter](https://twitter.com/yiiframework) +and [GitHub](https://github.com/yiisoft/yii2). +- Check [other communities](https://github.com/yiisoft/yii2/wiki/communities). -REQUIREMENTS +Contributing ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The framework is [Open Source](LICENSE.md) powered by [an excellent community](https://github.com/yiisoft/yii2/graphs/contributors). +You may join us and: -DOCUMENTATION -------------- +- [Report an issue](docs/internals/report-an-issue.md) +- [Translate documentation or messages](docs/internals/translation-workflow.md) +- [Give us feedback or start a design discussion](https://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) +- [Contribute to the core code or fix bugs](docs/internals/git-workflow.md) +- [Become a sponsor](#sponsoring) + +### Reporting Security issues + +> [!WARNING] +> Please do not report security vulnerabilities through public GitHub issues. + +Please refer to a [special page at the website](https://www.yiiframework.com/security/) +describing proper workflow for security issue reports. + +### Directory Structure + +``` +build/ internally used build tools +docs/ documentation +framework/ core framework code +tests/ tests of the core framework code +``` + +### Spreading the Word + +Acknowledging or citing Yii 2 is as important as direct contributions. + +**In presentations** + +If you are giving a presentation or talk featuring work that makes use of Yii 2 and would like to acknowledge it, +we suggest using [our logo](https://www.yiiframework.com/logo/) on your title slide. -For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md) -to have a general idea of what has changed in 2.0. +**In projects** -We are writing more documentation to get you started and learn more in depth. +If you are using Yii 2 as part of an OpenSource project, a way to acknowledge it is to +[use a special badge](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=for-the-badge&logo=yii) in your README: +[![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=for-the-badge&logo=yii)](https://www.yiiframework.com/) -HOW TO PARTICIPATE ------------------- +If your code is hosted at GitHub, you can place the following in your README.md file to get the badge: -**Your participation to Yii 2 development is very welcome!** +``` +[![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=for-the-badge&logo=yii)](https://www.yiiframework.com/) +``` -You may participate in the following ways: +### Sponsoring -* [Report issues](https://github.com/yiisoft/yii2/issues) -* [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-design-discussions-for-yii-20/) -* Fix issues, develop features, write/polish documentation - - Before you start, please adopt an existing issue (labelled with "ready for adoption") or start a new one to avoid duplicated efforts. - - Please submit a merge request after you finish development. +Support this project by becoming a sponsor or a backer. +[![Open Collective sponsors](https://img.shields.io/opencollective/sponsors/yiisoft?style=for-the-badge&logo=opencollective)](https://opencollective.com/yiisoft) +[![Open Collective backers](https://img.shields.io/opencollective/backers/yiisoft?style=for-the-badge&logo=opencollective)](https://opencollective.com/yiisoft) diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000000..b21267051a4 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,27 @@ +> Roadmap for Yii 3.0 and further was moved to [yiisoft/docs](https://github.com/yiisoft/docs/blob/master/003-roadmap.md). + +- Enhancements are not accepted for framework version 2.0. +- Enhancements are accepted for 2.0 extensions. +- Bug and security fixes are expected. +- Pull requests and maintainers are very welcome. + +Above would stand as it is [for two years after Yii 3.0 release](https://www.yiiframework.com/release-cycle). + +## Additional releases + +While we focus on 3.0, we tag 2.0 releases and extension releases [about once in a week](https://www.yiiframework.com/release-cycle). + + +## 2.0.16+ (since 2019 till 3.0 release + 2 years) + +- Bugfixes. + +## 2.0.15 (2nd quarter of 2018) + +- Since this release main focus is bug fixing. +- No full-branch merges into 3.0. +- No enhancements are accepted. + +## 2.0.14 (1st quarter of 2018) + +Will be last release with features and enhancements the last one that will be merged into 3.0 directly. diff --git a/apps/advanced/.gitignore b/apps/advanced/.gitignore deleted file mode 100644 index b1cf7192cc4..00000000000 --- a/apps/advanced/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/yii \ No newline at end of file diff --git a/apps/advanced/LICENSE.md b/apps/advanced/LICENSE.md deleted file mode 100644 index 6edcc4f571a..00000000000 --- a/apps/advanced/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/apps/advanced/README.md b/apps/advanced/README.md deleted file mode 100644 index a2bcdd45a75..00000000000 --- a/apps/advanced/README.md +++ /dev/null @@ -1,98 +0,0 @@ -Yii 2 Advanced Application Template -=================================== - -**NOTE** Yii 2 and the relevant applications and extensions are still under heavy -development. We may make significant changes without prior notices. Please do not -use them for production. Please consider using [Yii v1.1](https://github.com/yiisoft/yii) -if you have a project to be deployed for production soon. - - -Thank you for using Yii 2 Advanced Application Template - an application template -that works out-of-box and can be easily customized to fit for your needs. - -Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backstage separation, -deployment in different environments, configuration nesting etc. - - -DIRECTORY STRUCTURE -------------------- - -``` -common - config/ contains shared configurations - models/ contains model classes used in both backstage and frontend -console - config/ contains console configurations - controllers/ contains console controllers (commands) - migrations/ contains database migrations - models/ contains console-specific model classes - runtime/ contains files generated during runtime -backstage - assets/ contains application assets such as JavaScript and CSS - config/ contains backstage configurations - controllers/ contains Web controller classes - models/ contains backstage-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - www/ contains the entry script and Web resources -frontend - assets/ contains application assets such as JavaScript and CSS - config/ contains frontend configurations - controllers/ contains Web controller classes - models/ contains frontend-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - www/ contains the entry script and Web resources -vendor/ contains dependent 3rd-party packages -environments/ contains environment-based overrides -``` - - - -REQUIREMENTS ------------- - -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. - - -INSTALLATION ------------- - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), you may download it from -[http://getcomposer.org/](http://getcomposer.org/) or run the following command on Linux/Unix/MacOS: - -~~~ -curl -s http://getcomposer.org/installer | php -~~~ - -You can then install the Bootstrap Application using the following command: - -~~~ -php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced -~~~ - -Now you should be able to access: - -- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` -- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` - -assuming `yii-advanced` is directly under the document root of your Web server. - - -### Install from an Archive File - -This is not currently available. We will provide it when Yii 2 is formally released. - -GETTING STARTED ---------------- - -After template application and its dependencies are downloaded you need to initialize it and set some config values to -match your application requirements. - -1. Execute `install` command selecting `dev` as environment. -2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`. -3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. -4. In `common/config/params.php` set your database details in `components.db` values. - diff --git a/apps/advanced/backstage/assets/.gitkeep b/apps/advanced/backstage/assets/.gitkeep deleted file mode 100644 index 72e8ffc0db8..00000000000 --- a/apps/advanced/backstage/assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backstage/config/.gitignore b/apps/advanced/backstage/config/.gitignore deleted file mode 100644 index 20da318cb2c..00000000000 --- a/apps/advanced/backstage/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/backstage/config/assets.php b/apps/advanced/backstage/config/assets.php deleted file mode 100644 index ee0d6101bbd..00000000000 --- a/apps/advanced/backstage/config/assets.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - 'basePath' => '@wwwroot', - 'baseUrl' => '@www', - 'css' => array( - 'css/site.css', - ), - 'js' => array( - - ), - 'depends' => array( - 'yii', - 'yii/bootstrap/responsive', - ), - ), -); diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backstage/config/main.php deleted file mode 100644 index 4898bfd5fbe..00000000000 --- a/apps/advanced/backstage/config/main.php +++ /dev/null @@ -1,40 +0,0 @@ - 'change-me', - 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'preload' => array('log'), - 'controllerNamespace' => 'backstage\controllers', - 'modules' => array( - ), - 'components' => array( - 'db' => $params['components.db'], - 'cache' => $params['components.cache'], - 'user' => array( - 'class' => 'yii\web\User', - 'identityClass' => 'common\models\User', - ), - 'assetManager' => array( - 'bundles' => require(__DIR__ . '/assets.php'), - ), - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), - 'params' => $params, -); diff --git a/apps/advanced/backstage/config/params.php b/apps/advanced/backstage/config/params.php deleted file mode 100644 index 1e197d0632e..00000000000 --- a/apps/advanced/backstage/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -); \ No newline at end of file diff --git a/apps/advanced/backstage/controllers/SiteController.php b/apps/advanced/backstage/controllers/SiteController.php deleted file mode 100644 index d40738af45d..00000000000 --- a/apps/advanced/backstage/controllers/SiteController.php +++ /dev/null @@ -1,33 +0,0 @@ -render('index'); - } - - public function actionLogin() - { - $model = new LoginForm(); - if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); - } else { - echo $this->render('login', array( - 'model' => $model, - )); - } - } - - public function actionLogout() - { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); - } -} diff --git a/apps/advanced/backstage/models/.gitkeep b/apps/advanced/backstage/models/.gitkeep deleted file mode 100644 index 72e8ffc0db8..00000000000 --- a/apps/advanced/backstage/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backstage/views/layouts/main.php b/apps/advanced/backstage/views/layouts/main.php deleted file mode 100644 index 44117f41f31..00000000000 --- a/apps/advanced/backstage/views/layouts/main.php +++ /dev/null @@ -1,64 +0,0 @@ -registerAssetBundle('app'); -?> -beginPage(); ?> - - - - - <?php echo Html::encode($this->title); ?> - head(); ?> - - -
- beginBody(); ?> -
-

My Company

- - - -
- - isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - - -
- - - endBody(); ?> -
- - - -endPage(); ?> diff --git a/apps/advanced/backstage/views/site/index.php b/apps/advanced/backstage/views/site/index.php deleted file mode 100644 index 158b61cdcc9..00000000000 --- a/apps/advanced/backstage/views/site/index.php +++ /dev/null @@ -1,47 +0,0 @@ -title = 'Welcome'; -?> -
-

Welcome!

- -

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus - commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

- Get started with Yii -
- -
- - -
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta - felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum - massa.

- -

View details »

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

title); ?>

- -

Please fill out the following fields to login:

- - array('class' => 'form-horizontal'))); ?> - field($model, 'username')->textInput(); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/advanced/backstage/www/.gitignore b/apps/advanced/backstage/www/.gitignore deleted file mode 100644 index 148f2b0a52d..00000000000 --- a/apps/advanced/backstage/www/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/index.php \ No newline at end of file diff --git a/apps/advanced/backstage/www/assets/.gitignore b/apps/advanced/backstage/www/assets/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/backstage/www/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/backstage/www/css/site.css b/apps/advanced/backstage/www/css/site.css deleted file mode 100644 index 890a953eb31..00000000000 --- a/apps/advanced/backstage/www/css/site.css +++ /dev/null @@ -1,78 +0,0 @@ -body { - padding-top: 20px; - padding-bottom: 60px; -} - -/* Custom container */ -.container { - margin: 0 auto; - max-width: 1000px; -} - -.container > hr { - margin: 60px 0; -} - -/* Main marketing message and sign up button */ -.jumbotron { - margin: 80px 0; - text-align: center; -} - -.jumbotron h1 { - font-size: 100px; - line-height: 1; -} - -.jumbotron .lead { - font-size: 24px; - line-height: 1.25; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -/* Supporting marketing content */ -.marketing { - margin: 60px 0; -} - -.marketing p + h4 { - margin-top: 28px; -} - -/* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { - padding: 0; -} - -.navbar .nav { - margin: 0; - display: table; - width: 100%; -} - -.navbar .nav li { - display: table-cell; - width: 1%; - float: none; -} - -.navbar .nav li a { - font-weight: bold; - text-align: center; - border-left: 1px solid rgba(255, 255, 255, .75); - border-right: 1px solid rgba(0, 0, 0, .1); -} - -.navbar .nav li:first-child a { - border-left: 0; - border-radius: 3px 0 0 3px; -} - -.navbar .nav li:last-child a { - border-right: 0; - border-radius: 0 3px 3px 0; -} diff --git a/apps/advanced/common/config/.gitignore b/apps/advanced/common/config/.gitignore deleted file mode 100644 index 46f6eb40a88..00000000000 --- a/apps/advanced/common/config/.gitignore +++ /dev/null @@ -1 +0,0 @@ -params-local.php \ No newline at end of file diff --git a/apps/advanced/common/config/params.php b/apps/advanced/common/config/params.php deleted file mode 100644 index b9409f90192..00000000000 --- a/apps/advanced/common/config/params.php +++ /dev/null @@ -1,16 +0,0 @@ - 'admin@example.com', - - 'components.cache' => array( - 'class' => 'yii\caching\FileCache', - ), - - 'components.db' => array( - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', - 'username' => 'root', - 'password' => '', - ), -); \ No newline at end of file diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php deleted file mode 100644 index 4631dbd9d00..00000000000 --- a/apps/advanced/common/models/LoginForm.php +++ /dev/null @@ -1,58 +0,0 @@ -username); - if (!$user || !$user->validatePassword($this->password)) { - $this->addError('password', 'Incorrect username or password.'); - } - } - - /** - * Logs in a user using the provided username and password. - * @return boolean whether the user is logged in successfully - */ - public function login() - { - if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; - } else { - return false; - } - } -} diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php deleted file mode 100644 index 78307187720..00000000000 --- a/apps/advanced/common/models/User.php +++ /dev/null @@ -1,114 +0,0 @@ - array( - 'class' => 'yii\behaviors\AutoTimestamp', - 'attributes' => array( - ActiveRecord::EVENT_BEFORE_INSERT => array('create_time', 'update_time'), - ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', - ), - ), - ); - } - - public static function findIdentity($id) - { - return static::find($id); - } - - public static function findByUsername($username) - { - return static::find(array('username' => $username, 'status' => static::STATUS_ACTIVE)); - } - - public function getId() - { - return $this->id; - } - - public function getAuthKey() - { - return $this->auth_key; - } - - public function validateAuthKey($authKey) - { - return $this->auth_key === $authKey; - } - - public function validatePassword($password) - { - return SecurityHelper::validatePassword($password, $this->password_hash); - } - - public function rules() - { - return array( - array('username', 'filter', 'filter' => 'trim'), - array('username', 'required'), - array('username', 'length', 'min' => 2, 'max' => 255), - - array('email', 'filter', 'filter' => 'trim'), - array('email', 'required'), - array('email', 'email'), - array('email', 'unique', 'message' => 'This email address has already been taken.'), - - array('password', 'required'), - array('password', 'length', 'min' => 6), - ); - } - - public function scenarios() - { - return array( - 'signup' => array('username', 'email', 'password'), - 'login' => array('username', 'password'), - ); - } - - public function beforeSave($insert) - { - if(parent::beforeSave($insert)) { - if($this->isNewRecord) { - if(!empty($this->password)) { - $this->password_hash = SecurityHelper::generatePasswordHash($this->password); - } - } - return true; - } - return false; - } -} diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json deleted file mode 100644 index db97efd2b1a..00000000000 --- a/apps/advanced/composer.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "yiisoft/yii2-app-advanced", - "description": "Yii 2 Advanced Application Template", - "keywords": ["yii", "framework", "advanced", "application template"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.3.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" - }, - "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ] - }, - "extra": { - "yii-install-writable": [ - "backstage/runtime", - "backstage/www/assets", - - "console/runtime", - "console/migrations", - - "frontend/runtime", - "frontend/www/assets" - ], - "yii-install-executable": [ - "yii" - ] - } -} diff --git a/apps/advanced/composer.lock b/apps/advanced/composer.lock deleted file mode 100644 index 761ae2f2609..00000000000 --- a/apps/advanced/composer.lock +++ /dev/null @@ -1,164 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" - ], - "hash": "0b96a35ac23eae4e84ffd588653e88d2", - "packages": [ - { - "name": "yiisoft/yii2", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "lib-pcre": "*", - "php": ">=5.3.7" - }, - "suggest": { - "ezyang/htmlpurifier": "Required by HtmlPurifier.", - "michelf/php-markdown": "Required by Markdown.", - "smarty/smarty": "Required by SmartyViewRenderer.", - "twig/twig": "Required by TwigViewRenderer." - }, - "type": "library", - "autoload": { - "psr-0": { - "yii\\": "/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Wei Zhuo", - "email": "weizhuo@gmail.com", - "role": "Project site maintenance and development" - }, - { - "name": "Sebastián Thierer", - "email": "sebas@artfos.com", - "role": "Component development" - }, - { - "name": "Jeffrey Winesett", - "email": "jefftulsa@gmail.com", - "role": "Documentation and marketing" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - } - ], - "description": "Yii2 Web Programming Framework", - "homepage": "http://www.yiiframework.com/", - "keywords": [ - "framework", - "yii" - ], - "time": "2013-05-25 20:59:05" - }, - { - "name": "yiisoft/yii2-composer", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "7ce4060faca940b836ab88de207638940a0a0568" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7ce4060faca940b836ab88de207638940a0a0568", - "reference": "7ce4060faca940b836ab88de207638940a0a0568", - "shasum": "" - }, - "require": { - "yiisoft/yii2": "*" - }, - "type": "library", - "autoload": { - "psr-0": { - "yii\\composer": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - } - ], - "description": "The composer integration for the Yii framework", - "keywords": [ - "composer", - "install", - "update", - "yii" - ], - "time": "2013-05-23 19:12:45" - } - ], - "packages-dev": [ - - ], - "aliases": [ - - ], - "minimum-stability": "dev", - "stability-flags": { - "yiisoft/yii2": 20, - "yiisoft/yii2-composer": 20 - }, - "platform": { - "php": ">=5.3.0" - }, - "platform-dev": [ - - ] -} diff --git a/apps/advanced/console/config/.gitignore b/apps/advanced/console/config/.gitignore deleted file mode 100644 index 20da318cb2c..00000000000 --- a/apps/advanced/console/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php deleted file mode 100644 index cceb3116d6e..00000000000 --- a/apps/advanced/console/config/main.php +++ /dev/null @@ -1,33 +0,0 @@ - 'change-me', - 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'preload' => array('log'), - 'controllerNamespace' => 'console\controllers', - 'modules' => array( - ), - 'components' => array( - 'db' => $params['components.db'], - 'cache' => $params['components.cache'], - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), - 'params' => $params, -); diff --git a/apps/advanced/console/config/params.php b/apps/advanced/console/config/params.php deleted file mode 100644 index 1e197d0632e..00000000000 --- a/apps/advanced/console/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -); \ No newline at end of file diff --git a/apps/advanced/console/migrations/m130524_201442_init.php b/apps/advanced/console/migrations/m130524_201442_init.php deleted file mode 100644 index a5e9d305090..00000000000 --- a/apps/advanced/console/migrations/m130524_201442_init.php +++ /dev/null @@ -1,29 +0,0 @@ -createTable('tbl_user', array( - 'id' => Schema::TYPE_PK, - 'username' => Schema::TYPE_STRING.' NOT NULL', - 'password_hash' => Schema::TYPE_STRING.' NOT NULL', - 'email' => Schema::TYPE_STRING.' NOT NULL', - 'role' => 'tinyint NOT NULL DEFAULT 10', - - 'status' => 'tinyint NOT NULL DEFAULT 10', - 'create_time' => Schema::TYPE_INTEGER.' NOT NULL', - 'update_time' => Schema::TYPE_INTEGER.' NOT NULL', - ), $tableOptions); - } - - public function down() - { - $this->dropTable('tbl_user'); - } -} diff --git a/apps/advanced/console/models/.gitkeep b/apps/advanced/console/models/.gitkeep deleted file mode 100644 index 72e8ffc0db8..00000000000 --- a/apps/advanced/console/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/console/runtime/.gitignore b/apps/advanced/console/runtime/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/console/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/environments/dev/backstage/config/main-local.php b/apps/advanced/environments/dev/backstage/config/main-local.php deleted file mode 100644 index f74bfa3b3b5..00000000000 --- a/apps/advanced/environments/dev/backstage/config/main-local.php +++ /dev/null @@ -1,17 +0,0 @@ - array( -// 'debug' => array( -// 'class' => 'yii\debug\Module', -// ), - ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\logging\DebugTarget', -// ) - ), - ), - ), -); diff --git a/apps/advanced/environments/dev/backstage/config/params-local.php b/apps/advanced/environments/dev/backstage/config/params-local.php deleted file mode 100644 index 26701437b87..00000000000 --- a/apps/advanced/environments/dev/backstage/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/common/config/params-local.php b/apps/advanced/environments/dev/common/config/params-local.php deleted file mode 100644 index 26701437b87..00000000000 --- a/apps/advanced/environments/dev/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - array( -// 'debug' => array( -// 'class' => 'yii\debug\Module', -// ), - ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\logging\DebugTarget', -// ) - ), - ), - ), -); diff --git a/apps/advanced/environments/dev/frontend/config/params-local.php b/apps/advanced/environments/dev/frontend/config/params-local.php deleted file mode 100644 index 26701437b87..00000000000 --- a/apps/advanced/environments/dev/frontend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii deleted file mode 100644 index d7632172776..00000000000 --- a/apps/advanced/environments/dev/yii +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env php -run(); diff --git a/apps/advanced/environments/index.php b/apps/advanced/environments/index.php deleted file mode 100644 index ff907d25bcc..00000000000 --- a/apps/advanced/environments/index.php +++ /dev/null @@ -1,38 +0,0 @@ - array( - * 'path' => 'directory storing the local files', - * 'writable' => array( - * // list of directories that should be set writable - * ), - * ), - * ); - * ``` - */ -return array( - 'Development' => array( - 'path' => 'dev', - 'writable' => array( - // handled by composer.json already - ), - 'executable' => array( - 'yiic', - ), - ), - 'Production' => array( - 'path' => 'prod', - 'writable' => array( - // handled by composer.json already - ), - 'executable' => array( - 'yiic', - ), - ), -); diff --git a/apps/advanced/environments/prod/backstage/config/main-local.php b/apps/advanced/environments/prod/backstage/config/main-local.php deleted file mode 100644 index 5b61b0e4ddb..00000000000 --- a/apps/advanced/environments/prod/backstage/config/main-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/prod/common/config/params-local.php b/apps/advanced/environments/prod/common/config/params-local.php deleted file mode 100644 index 26701437b87..00000000000 --- a/apps/advanced/environments/prod/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii deleted file mode 100644 index 395aedebef0..00000000000 --- a/apps/advanced/environments/prod/yii +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env php -run(); diff --git a/apps/advanced/frontend/assets/.gitkeep b/apps/advanced/frontend/assets/.gitkeep deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/frontend/assets/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/frontend/config/.gitignore b/apps/advanced/frontend/config/.gitignore deleted file mode 100644 index 20da318cb2c..00000000000 --- a/apps/advanced/frontend/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/frontend/config/assets.php b/apps/advanced/frontend/config/assets.php deleted file mode 100644 index ee0d6101bbd..00000000000 --- a/apps/advanced/frontend/config/assets.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - 'basePath' => '@wwwroot', - 'baseUrl' => '@www', - 'css' => array( - 'css/site.css', - ), - 'js' => array( - - ), - 'depends' => array( - 'yii', - 'yii/bootstrap/responsive', - ), - ), -); diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php deleted file mode 100644 index 02a66c99dc4..00000000000 --- a/apps/advanced/frontend/config/main.php +++ /dev/null @@ -1,40 +0,0 @@ - 'change-me', - 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'preload' => array('log'), - 'controllerNamespace' => 'frontend\controllers', - 'modules' => array( - ), - 'components' => array( - 'db' => $params['components.db'], - 'cache' => $params['components.cache'], - 'user' => array( - 'class' => 'yii\web\User', - 'identityClass' => 'common\models\User', - ), - 'assetManager' => array( - 'bundles' => require(__DIR__ . '/assets.php'), - ), - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), - 'params' => $params, -); diff --git a/apps/advanced/frontend/config/params.php b/apps/advanced/frontend/config/params.php deleted file mode 100644 index 1e197d0632e..00000000000 --- a/apps/advanced/frontend/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -); \ No newline at end of file diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php deleted file mode 100644 index cd3339cbd43..00000000000 --- a/apps/advanced/frontend/controllers/SiteController.php +++ /dev/null @@ -1,61 +0,0 @@ - array( - 'class' => 'yii\web\CaptchaAction', - ), - ); - } - - public function actionIndex() - { - echo $this->render('index'); - } - - public function actionLogin() - { - $model = new LoginForm(); - if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); - } else { - echo $this->render('login', array( - 'model' => $model, - )); - } - } - - public function actionLogout() - { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); - } - - public function actionContact() - { - $model = new ContactForm; - if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { - Yii::$app->session->setFlash('contactFormSubmitted'); - Yii::$app->response->refresh(); - } else { - echo $this->render('contact', array( - 'model' => $model, - )); - } - } - - public function actionAbout() - { - echo $this->render('about'); - } -} diff --git a/apps/advanced/frontend/models/ContactForm.php b/apps/advanced/frontend/models/ContactForm.php deleted file mode 100644 index b3d8682a7d1..00000000000 --- a/apps/advanced/frontend/models/ContactForm.php +++ /dev/null @@ -1,63 +0,0 @@ - 'Verification Code', - ); - } - - /** - * Sends an email to the specified email address using the information collected by this model. - * @param string $email the target email address - * @return boolean whether the model passes validation - */ - public function contact($email) - { - if ($this->validate()) { - $name = '=?UTF-8?B?' . base64_encode($this->name) . '?='; - $subject = '=?UTF-8?B?' . base64_encode($this->subject) . '?='; - $headers = "From: $name <{$this->email}>\r\n" . - "Reply-To: {$this->email}\r\n" . - "MIME-Version: 1.0\r\n" . - "Content-type: text/plain; charset=UTF-8"; - mail($email, $subject, $this->body, $headers); - return true; - } else { - return false; - } - } -} diff --git a/apps/advanced/frontend/runtime/.gitignore b/apps/advanced/frontend/runtime/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/frontend/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php deleted file mode 100644 index 635e1181aff..00000000000 --- a/apps/advanced/frontend/views/layouts/main.php +++ /dev/null @@ -1,66 +0,0 @@ -registerAssetBundle('app'); -?> -beginPage(); ?> - - - - - <?php echo Html::encode($this->title); ?> - head(); ?> - - -
- beginBody(); ?> -
-

My Company

- - - -
- - isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - - -
- - - endBody(); ?> -
- - - -endPage(); ?> diff --git a/apps/advanced/frontend/views/site/about.php b/apps/advanced/frontend/views/site/about.php deleted file mode 100644 index 86e19e11c00..00000000000 --- a/apps/advanced/frontend/views/site/about.php +++ /dev/null @@ -1,16 +0,0 @@ -title = 'About'; -$this->params['breadcrumbs'][] = $this->title; -?> -

title); ?>

- -

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

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

title); ?>

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

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

- - array('class' => 'form-horizontal'), - 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), -)); ?> - field($model, 'name')->textInput(); ?> - field($model, 'email')->textInput(); ?> - field($model, 'subject')->textInput(); ?> - field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode'); - echo $field->begin() - . $field->label() - . Captcha::widget() - . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) - . $field->error() - . $field->end(); - ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/advanced/frontend/views/site/index.php b/apps/advanced/frontend/views/site/index.php deleted file mode 100644 index 158b61cdcc9..00000000000 --- a/apps/advanced/frontend/views/site/index.php +++ /dev/null @@ -1,47 +0,0 @@ -title = 'Welcome'; -?> -
-

Welcome!

- -

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus - commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

- Get started with Yii -
- -
- - -
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta - felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum - massa.

- -

View details »

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

title); ?>

- -

Please fill out the following fields to login:

- - array('class' => 'form-horizontal'))); ?> - field($model, 'username')->textInput(); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/advanced/frontend/www/.gitignore b/apps/advanced/frontend/www/.gitignore deleted file mode 100644 index 148f2b0a52d..00000000000 --- a/apps/advanced/frontend/www/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/index.php \ No newline at end of file diff --git a/apps/advanced/frontend/www/assets/.gitignore b/apps/advanced/frontend/www/assets/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/frontend/www/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/frontend/www/css/site.css b/apps/advanced/frontend/www/css/site.css deleted file mode 100644 index 890a953eb31..00000000000 --- a/apps/advanced/frontend/www/css/site.css +++ /dev/null @@ -1,78 +0,0 @@ -body { - padding-top: 20px; - padding-bottom: 60px; -} - -/* Custom container */ -.container { - margin: 0 auto; - max-width: 1000px; -} - -.container > hr { - margin: 60px 0; -} - -/* Main marketing message and sign up button */ -.jumbotron { - margin: 80px 0; - text-align: center; -} - -.jumbotron h1 { - font-size: 100px; - line-height: 1; -} - -.jumbotron .lead { - font-size: 24px; - line-height: 1.25; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -/* Supporting marketing content */ -.marketing { - margin: 60px 0; -} - -.marketing p + h4 { - margin-top: 28px; -} - -/* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { - padding: 0; -} - -.navbar .nav { - margin: 0; - display: table; - width: 100%; -} - -.navbar .nav li { - display: table-cell; - width: 1%; - float: none; -} - -.navbar .nav li a { - font-weight: bold; - text-align: center; - border-left: 1px solid rgba(255, 255, 255, .75); - border-right: 1px solid rgba(0, 0, 0, .1); -} - -.navbar .nav li:first-child a { - border-left: 0; - border-radius: 3px 0 0 3px; -} - -.navbar .nav li:last-child a { - border-right: 0; - border-radius: 0 3px 3px 0; -} diff --git a/apps/advanced/install b/apps/advanced/install deleted file mode 100755 index 6864440c313..00000000000 --- a/apps/advanced/install +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env php - $name) { - echo " [$i] $name\n"; -} -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit installation.\n"; - return; -} - -$env = $envs[$envNames[$answer]]; -echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit installation.\n"; - return; -} - -echo "\n Start installation ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { - break; - } -} - -if (isset($env['writable'])) { - foreach ($env['writable'] as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -if (isset($env['executable'])) { - foreach ($env['executable'] as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -echo "\n ... installation completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = array(); - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} diff --git a/apps/advanced/install.bat b/apps/advanced/install.bat deleted file mode 100644 index dc2cd83aece..00000000000 --- a/apps/advanced/install.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line install script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%install" %* - -@endlocal diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php deleted file mode 100644 index c9e64931ea9..00000000000 --- a/apps/advanced/requirements.php +++ /dev/null @@ -1,103 +0,0 @@ -Error'; - echo '

The path to yii framework seems to be incorrect.

'; - echo '

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

'; - echo '

Please refer to the README on how to install Yii.

'; -} - -require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); -$requirementsChecker = new YiiRequirementChecker(); - -/** - * Adjust requirements according to your application specifics. - */ -$requirements = array( - // Database : - array( - 'name' => 'PDO extension', - 'mandatory' => true, - 'condition' => extension_loaded('pdo'), - 'by' => 'All DB-related classes', - ), - array( - 'name' => 'PDO SQLite extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_sqlite'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for SQLite database.', - ), - array( - 'name' => 'PDO MySQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_mysql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for MySQL database.', - ), - // Cache : - array( - 'name' => 'Memcache extension', - 'mandatory' => false, - 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), - 'by' => 'CMemCache', - 'memo' => extension_loaded('memcached') ? 'To use memcached set CMemCache::useMemcached to true.' : '' - ), - array( - 'name' => 'APC extension', - 'mandatory' => false, - 'condition' => extension_loaded('apc') || extension_loaded('apc'), - 'by' => 'CApcCache', - ), - // Additional PHP extensions : - array( - 'name' => 'Mcrypt extension', - 'mandatory' => false, - 'condition' => extension_loaded('mcrypt'), - 'by' => 'CSecurityManager', - 'memo' => 'Required by encrypt and decrypt methods.' - ), - // PHP ini : - 'phpSafeMode' => array( - 'name' => 'PHP safe mode', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), - 'by' => 'File uploading and console command execution', - 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( - 'name' => 'Expose PHP', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), - 'by' => 'Security reasons', - 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( - 'name' => 'PHP allow url include', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), - 'by' => 'Security reasons', - 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( - 'name' => 'PHP mail SMTP', - 'mandatory' => false, - 'condition' => strlen(ini_get('SMTP'))>0, - 'by' => 'Email sending', - 'memo' => 'PHP mail SMTP server required', - ), -); -$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/advanced/vendor/.gitignore b/apps/advanced/vendor/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/advanced/vendor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/yii.bat b/apps/advanced/yii.bat deleted file mode 100644 index 5e21e2e9908..00000000000 --- a/apps/advanced/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/basic/LICENSE.md b/apps/basic/LICENSE.md deleted file mode 100644 index 6edcc4f571a..00000000000 --- a/apps/basic/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/apps/basic/README.md b/apps/basic/README.md deleted file mode 100644 index 5300448d900..00000000000 --- a/apps/basic/README.md +++ /dev/null @@ -1,61 +0,0 @@ -Yii 2 Basic Application Template -================================ - -**NOTE** Yii 2 and the relevant applications and extensions are still under heavy -development. We may make significant changes without prior notices. Please do not -use them for production. Please consider using [Yii v1.1](https://github.com/yiisoft/yii) -if you have a project to be deployed for production soon. - - -Thank you for using Yii 2 Basic Application Template - an application template -that works out-of-box and can be easily customized to fit for your needs. - -Yii 2 Basic Application Template is best suitable for small Websites which mainly contain -a few informational pages. - - -DIRECTORY STRUCTURE -------------------- - - commands/ contains console commands (controllers) - config/ contains application configurations - controllers/ contains Web controller classes - models/ contains model classes - runtime/ contains files generated during runtime - vendor/ contains dependent 3rd-party packages - views/ contains view files for the Web application - www/ contains the entry script and Web resources - - - -REQUIREMENTS ------------- - -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. - - -INSTALLATION ------------- - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), you may download it from -[http://getcomposer.org/](http://getcomposer.org/) or run the following command on Linux/Unix/MacOS: - -~~~ -curl -s http://getcomposer.org/installer | php -~~~ - -You can then install the Bootstrap Application using the following command: - -~~~ -php composer.phar create-project --stability=dev yiisoft/yii2-app-basic yii-basic -~~~ - -Now you should be able to access the application using the URL `http://localhost/yii-basic/www/`, -assuming `yii-basic` is directly under the document root of your Web server. - - -### Install from an Archive File - -This is not currently available. We will provide it when Yii 2 is formally released. diff --git a/apps/basic/assets/.gitkeep b/apps/basic/assets/.gitkeep deleted file mode 100644 index 72e8ffc0db8..00000000000 --- a/apps/basic/assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/basic/commands/HelloController.php b/apps/basic/commands/HelloController.php deleted file mode 100644 index b5ecac26d81..00000000000 --- a/apps/basic/commands/HelloController.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class HelloController extends Controller -{ - /** - * This command echos what you have entered as the message. - * @param string $message the message to be echoed. - */ - public function actionIndex($message = 'hello world') - { - echo $message."\n"; - } -} \ No newline at end of file diff --git a/apps/basic/composer.json b/apps/basic/composer.json deleted file mode 100644 index 29b05d1108b..00000000000 --- a/apps/basic/composer.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "yiisoft/yii2-app-basic", - "description": "Yii 2 Basic Application Template", - "keywords": ["yii", "framework", "basic", "application template"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.3.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" - }, - "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ] - }, - "extra": { - "yii-install-writable": [ - "runtime", - "www/assets" - ], - "yii-install-executable": [ - "yii" - ] - } -} diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock deleted file mode 100644 index a66bbeab8e8..00000000000 --- a/apps/basic/composer.lock +++ /dev/null @@ -1,164 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" - ], - "hash": "0411dbbd774aa1c89256c77c68023940", - "packages": [ - { - "name": "yiisoft/yii2", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "lib-pcre": "*", - "php": ">=5.3.7" - }, - "suggest": { - "ezyang/htmlpurifier": "Required by HtmlPurifier.", - "michelf/php-markdown": "Required by Markdown.", - "smarty/smarty": "Required by SmartyViewRenderer.", - "twig/twig": "Required by TwigViewRenderer." - }, - "type": "library", - "autoload": { - "psr-0": { - "yii\\": "/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Wei Zhuo", - "email": "weizhuo@gmail.com", - "role": "Project site maintenance and development" - }, - { - "name": "Sebastián Thierer", - "email": "sebas@artfos.com", - "role": "Component development" - }, - { - "name": "Jeffrey Winesett", - "email": "jefftulsa@gmail.com", - "role": "Documentation and marketing" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - } - ], - "description": "Yii2 Web Programming Framework", - "homepage": "http://www.yiiframework.com/", - "keywords": [ - "framework", - "yii" - ], - "time": "2013-05-25 20:59:05" - }, - { - "name": "yiisoft/yii2-composer", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "7ce4060faca940b836ab88de207638940a0a0568" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7ce4060faca940b836ab88de207638940a0a0568", - "reference": "7ce4060faca940b836ab88de207638940a0a0568", - "shasum": "" - }, - "require": { - "yiisoft/yii2": "*" - }, - "type": "library", - "autoload": { - "psr-0": { - "yii\\composer": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - } - ], - "description": "The composer integration for the Yii framework", - "keywords": [ - "composer", - "install", - "update", - "yii" - ], - "time": "2013-05-23 19:12:45" - } - ], - "packages-dev": [ - - ], - "aliases": [ - - ], - "minimum-stability": "dev", - "stability-flags": { - "yiisoft/yii2": 20, - "yiisoft/yii2-composer": 20 - }, - "platform": { - "php": ">=5.3.0" - }, - "platform-dev": [ - - ] -} diff --git a/apps/basic/config/assets.php b/apps/basic/config/assets.php deleted file mode 100644 index ee0d6101bbd..00000000000 --- a/apps/basic/config/assets.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - 'basePath' => '@wwwroot', - 'baseUrl' => '@www', - 'css' => array( - 'css/site.css', - ), - 'js' => array( - - ), - 'depends' => array( - 'yii', - 'yii/bootstrap/responsive', - ), - ), -); diff --git a/apps/basic/config/console.php b/apps/basic/config/console.php deleted file mode 100644 index bfb3ed7127d..00000000000 --- a/apps/basic/config/console.php +++ /dev/null @@ -1,26 +0,0 @@ - 'bootstrap-console', - 'basePath' => dirname(__DIR__), - 'preload' => array('log'), - 'controllerPath' => dirname(__DIR__) . '/commands', - 'controllerNamespace' => 'app\commands', - 'modules' => array( - ), - 'components' => array( - 'cache' => array( - 'class' => 'yii\caching\FileCache', - ), - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), - 'params' => $params, -); diff --git a/apps/basic/config/main.php b/apps/basic/config/main.php deleted file mode 100644 index 9adfba6d52c..00000000000 --- a/apps/basic/config/main.php +++ /dev/null @@ -1,38 +0,0 @@ - 'bootstrap', - 'basePath' => dirname(__DIR__), - 'preload' => array('log'), - 'controllerNamespace' => 'app\controllers', - 'modules' => array( -// 'debug' => array( -// 'class' => 'yii\debug\Module', -// ) - ), - 'components' => array( - 'cache' => array( - 'class' => 'yii\caching\FileCache', - ), - 'user' => array( - 'class' => 'yii\web\User', - 'identityClass' => 'app\models\User', - ), - 'assetManager' => array( - 'bundles' => require(__DIR__ . '/assets.php'), - ), - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), -// array( -// 'class' => 'yii\logging\DebugTarget', -// ) - ), - ), - ), - 'params' => $params, -); diff --git a/apps/basic/config/params.php b/apps/basic/config/params.php deleted file mode 100644 index 1e197d0632e..00000000000 --- a/apps/basic/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -); \ No newline at end of file diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php deleted file mode 100644 index ff3b8b47109..00000000000 --- a/apps/basic/controllers/SiteController.php +++ /dev/null @@ -1,61 +0,0 @@ - array( - 'class' => 'yii\web\CaptchaAction', - ), - ); - } - - public function actionIndex() - { - echo $this->render('index'); - } - - public function actionLogin() - { - $model = new LoginForm(); - if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); - } else { - echo $this->render('login', array( - 'model' => $model, - )); - } - } - - public function actionLogout() - { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); - } - - public function actionContact() - { - $model = new ContactForm; - if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { - Yii::$app->session->setFlash('contactFormSubmitted'); - Yii::$app->response->refresh(); - } else { - echo $this->render('contact', array( - 'model' => $model, - )); - } - } - - public function actionAbout() - { - echo $this->render('about'); - } -} diff --git a/apps/basic/models/ContactForm.php b/apps/basic/models/ContactForm.php deleted file mode 100644 index 7b713a1b246..00000000000 --- a/apps/basic/models/ContactForm.php +++ /dev/null @@ -1,63 +0,0 @@ - 'Verification Code', - ); - } - - /** - * Sends an email to the specified email address using the information collected by this model. - * @param string $email the target email address - * @return boolean whether the model passes validation - */ - public function contact($email) - { - if ($this->validate()) { - $name = '=?UTF-8?B?' . base64_encode($this->name) . '?='; - $subject = '=?UTF-8?B?' . base64_encode($this->subject) . '?='; - $headers = "From: $name <{$this->email}>\r\n" . - "Reply-To: {$this->email}\r\n" . - "MIME-Version: 1.0\r\n" . - "Content-type: text/plain; charset=UTF-8"; - mail($email, $subject, $this->body, $headers); - return true; - } else { - return false; - } - } -} diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php deleted file mode 100644 index 5ba1dc6719b..00000000000 --- a/apps/basic/models/LoginForm.php +++ /dev/null @@ -1,58 +0,0 @@ -username); - if (!$user || !$user->validatePassword($this->password)) { - $this->addError('password', 'Incorrect username or password.'); - } - } - - /** - * Logs in a user using the provided username and password. - * @return boolean whether the user is logged in successfully - */ - public function login() - { - if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; - } else { - return false; - } - } -} diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php deleted file mode 100644 index afbf9f8b257..00000000000 --- a/apps/basic/models/User.php +++ /dev/null @@ -1,61 +0,0 @@ - array( - 'id' => '100', - 'username' => 'admin', - 'password' => 'admin', - 'authKey' => 'test100key', - ), - '101' => array( - 'id' => '101', - 'username' => 'demo', - 'password' => 'demo', - 'authKey' => 'test101key', - ), - ); - - public static function findIdentity($id) - { - return isset(self::$users[$id]) ? new self(self::$users[$id]) : null; - } - - public static function findByUsername($username) - { - foreach (self::$users as $user) { - if (strcasecmp($user['username'], $username) === 0) { - return new self($user); - } - } - return null; - } - - public function getId() - { - return $this->id; - } - - public function getAuthKey() - { - return $this->authKey; - } - - public function validateAuthKey($authKey) - { - return $this->authKey === $authKey; - } - - public function validatePassword($password) - { - return $this->password === $password; - } -} diff --git a/apps/basic/requirements.php b/apps/basic/requirements.php deleted file mode 100644 index c9e64931ea9..00000000000 --- a/apps/basic/requirements.php +++ /dev/null @@ -1,103 +0,0 @@ -Error'; - echo '

The path to yii framework seems to be incorrect.

'; - echo '

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

'; - echo '

Please refer to the README on how to install Yii.

'; -} - -require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); -$requirementsChecker = new YiiRequirementChecker(); - -/** - * Adjust requirements according to your application specifics. - */ -$requirements = array( - // Database : - array( - 'name' => 'PDO extension', - 'mandatory' => true, - 'condition' => extension_loaded('pdo'), - 'by' => 'All DB-related classes', - ), - array( - 'name' => 'PDO SQLite extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_sqlite'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for SQLite database.', - ), - array( - 'name' => 'PDO MySQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_mysql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for MySQL database.', - ), - // Cache : - array( - 'name' => 'Memcache extension', - 'mandatory' => false, - 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), - 'by' => 'CMemCache', - 'memo' => extension_loaded('memcached') ? 'To use memcached set CMemCache::useMemcached to true.' : '' - ), - array( - 'name' => 'APC extension', - 'mandatory' => false, - 'condition' => extension_loaded('apc') || extension_loaded('apc'), - 'by' => 'CApcCache', - ), - // Additional PHP extensions : - array( - 'name' => 'Mcrypt extension', - 'mandatory' => false, - 'condition' => extension_loaded('mcrypt'), - 'by' => 'CSecurityManager', - 'memo' => 'Required by encrypt and decrypt methods.' - ), - // PHP ini : - 'phpSafeMode' => array( - 'name' => 'PHP safe mode', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), - 'by' => 'File uploading and console command execution', - 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( - 'name' => 'Expose PHP', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), - 'by' => 'Security reasons', - 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( - 'name' => 'PHP allow url include', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), - 'by' => 'Security reasons', - 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( - 'name' => 'PHP mail SMTP', - 'mandatory' => false, - 'condition' => strlen(ini_get('SMTP'))>0, - 'by' => 'Email sending', - 'memo' => 'PHP mail SMTP server required', - ), -); -$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/runtime/.gitignore b/apps/basic/runtime/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/basic/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/basic/vendor/.gitignore b/apps/basic/vendor/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/basic/vendor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php deleted file mode 100644 index 635e1181aff..00000000000 --- a/apps/basic/views/layouts/main.php +++ /dev/null @@ -1,66 +0,0 @@ -registerAssetBundle('app'); -?> -beginPage(); ?> - - - - - <?php echo Html::encode($this->title); ?> - head(); ?> - - -
- beginBody(); ?> -
-

My Company

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

title); ?>

- -

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

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

title); ?>

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

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

- - array('class' => 'form-horizontal'), - 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), -)); ?> - field($model, 'name')->textInput(); ?> - field($model, 'email')->textInput(); ?> - field($model, 'subject')->textInput(); ?> - field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode'); - echo $field->begin() - . $field->label() - . Captcha::widget() - . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) - . $field->error() - . $field->end(); - ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/basic/views/site/index.php b/apps/basic/views/site/index.php deleted file mode 100644 index 158b61cdcc9..00000000000 --- a/apps/basic/views/site/index.php +++ /dev/null @@ -1,47 +0,0 @@ -title = 'Welcome'; -?> -
-

Welcome!

- -

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus - commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

- Get started with Yii -
- -
- - -
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta - felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum - massa.

- -

View details »

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

title); ?>

- -

Please fill out the following fields to login:

- - array('class' => 'form-horizontal'))); ?> - field($model, 'username')->textInput(); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/basic/www/assets/.gitignore b/apps/basic/www/assets/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/apps/basic/www/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/basic/www/css/site.css b/apps/basic/www/css/site.css deleted file mode 100644 index 890a953eb31..00000000000 --- a/apps/basic/www/css/site.css +++ /dev/null @@ -1,78 +0,0 @@ -body { - padding-top: 20px; - padding-bottom: 60px; -} - -/* Custom container */ -.container { - margin: 0 auto; - max-width: 1000px; -} - -.container > hr { - margin: 60px 0; -} - -/* Main marketing message and sign up button */ -.jumbotron { - margin: 80px 0; - text-align: center; -} - -.jumbotron h1 { - font-size: 100px; - line-height: 1; -} - -.jumbotron .lead { - font-size: 24px; - line-height: 1.25; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -/* Supporting marketing content */ -.marketing { - margin: 60px 0; -} - -.marketing p + h4 { - margin-top: 28px; -} - -/* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { - padding: 0; -} - -.navbar .nav { - margin: 0; - display: table; - width: 100%; -} - -.navbar .nav li { - display: table-cell; - width: 1%; - float: none; -} - -.navbar .nav li a { - font-weight: bold; - text-align: center; - border-left: 1px solid rgba(255, 255, 255, .75); - border-right: 1px solid rgba(0, 0, 0, .1); -} - -.navbar .nav li:first-child a { - border-left: 0; - border-radius: 3px 0 0 3px; -} - -.navbar .nav li:last-child a { - border-right: 0; - border-radius: 0 3px 3px 0; -} diff --git a/apps/basic/www/index.php b/apps/basic/www/index.php deleted file mode 100644 index 3b7b2fcd077..00000000000 --- a/apps/basic/www/index.php +++ /dev/null @@ -1,12 +0,0 @@ -run(); diff --git a/apps/basic/yii b/apps/basic/yii deleted file mode 100755 index 07935231e7d..00000000000 --- a/apps/basic/yii +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env php -run(); diff --git a/apps/basic/yii.bat b/apps/basic/yii.bat deleted file mode 100644 index 5e21e2e9908..00000000000 --- a/apps/basic/yii.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line bootstrap script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%yii" %* - -@endlocal diff --git a/apps/benchmark/LICENSE.md b/apps/benchmark/LICENSE.md deleted file mode 100644 index 6edcc4f571a..00000000000 --- a/apps/benchmark/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md deleted file mode 100644 index 2aeb0ae5d83..00000000000 --- a/apps/benchmark/README.md +++ /dev/null @@ -1,56 +0,0 @@ -Yii 2 Benchmark Application -=========================== - -**NOTE** Yii 2 and the relevant applications and extensions are still under heavy -development. We may make significant changes without prior notices. Please do not -use them for production. Please consider using [Yii v1.1](https://github.com/yiisoft/yii) -if you have a project to be deployed for production soon. - - -Yii 2 Benchmark Application is an application built to demonstrate the minimal overhead -introduced by the Yii framework. The application contains a single page which only renders -the "hello world" string. - -The application attempts to simulate the scenario in which you can achieve the best performance -when using Yii. It does so by assuming that both of the main application configuration and the page -content are cached in memory, and the application enables pretty URLs. - - -DIRECTORY STRUCTURE -------------------- - - protected/ contains application source code - controllers/ contains Web controller classes - index.php the entry script - - -REQUIREMENTS ------------- - -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. - - -INSTALLATION ------------- - -If you do not have [Composer](http://getcomposer.org/), you may download it from -[http://getcomposer.org/](http://getcomposer.org/) or run the following command on Linux/Unix/MacOS: - -~~~ -curl -s http://getcomposer.org/installer | php -~~~ - -You can then install the Bootstrap Application using the following command: - -~~~ -php composer.phar create-project --stability=dev yiisoft/yii2-app-benchmark yii-benchmark -~~~ - -Now you should be able to access the benchmark page using the URL - -~~~ -http://localhost/yii-benchmark/index.php/site/hello -~~~ - -In the above, we assume `yii-benchmark` is directly under the document root of your Web server. - diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json deleted file mode 100644 index 2b077e0cf15..00000000000 --- a/apps/benchmark/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "yiisoft/yii2-app-benchmark", - "description": "Yii 2 Benchmark Application", - "keywords": ["yii", "framework", "benchmark", "application"], - "homepage": "http://www.yiiframework.com/", - "type": "project", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "config": { - "vendor-dir": "protected/vendor" - }, - "minimum-stability": "dev", - "require": { - "php": ">=5.3.0", - "yiisoft/yii2": "dev-master" - } -} diff --git a/apps/benchmark/composer.lock b/apps/benchmark/composer.lock deleted file mode 100644 index c7a03243281..00000000000 --- a/apps/benchmark/composer.lock +++ /dev/null @@ -1,119 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" - ], - "hash": "5ce5f1ad2aa7d7e31c3e216b8ce23387", - "packages": [ - { - "name": "yiisoft/yii2", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f3c3d9d764de25fc46711bce2259274bcceade1c", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "lib-pcre": "*", - "php": ">=5.3.7" - }, - "suggest": { - "ezyang/htmlpurifier": "Required by HtmlPurifier.", - "michelf/php-markdown": "Required by Markdown.", - "smarty/smarty": "Required by SmartyViewRenderer.", - "twig/twig": "Required by TwigViewRenderer." - }, - "type": "library", - "autoload": { - "psr-0": { - "yii\\": "/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Wei Zhuo", - "email": "weizhuo@gmail.com", - "role": "Project site maintenance and development" - }, - { - "name": "Sebastián Thierer", - "email": "sebas@artfos.com", - "role": "Component development" - }, - { - "name": "Jeffrey Winesett", - "email": "jefftulsa@gmail.com", - "role": "Documentation and marketing" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - } - ], - "description": "Yii2 Web Programming Framework", - "homepage": "http://www.yiiframework.com/", - "keywords": [ - "framework", - "yii" - ], - "time": "2013-05-26 21:57:00" - } - ], - "packages-dev": [ - - ], - "aliases": [ - - ], - "minimum-stability": "dev", - "stability-flags": { - "yiisoft/yii2": 20 - }, - "platform": { - "php": ">=5.3.0" - }, - "platform-dev": [ - - ] -} diff --git a/apps/benchmark/index.php b/apps/benchmark/index.php deleted file mode 100644 index ddf6081a244..00000000000 --- a/apps/benchmark/index.php +++ /dev/null @@ -1,18 +0,0 @@ - 'benchmark', - 'basePath' => __DIR__ . '/protected', - 'components' => array( - 'urlManager' => array( - 'enablePrettyUrl' => true, - ), - ) -); - -$application = new yii\web\Application($config); -$application->run(); diff --git a/apps/benchmark/protected/.htaccess b/apps/benchmark/protected/.htaccess deleted file mode 100644 index e01983226fe..00000000000 --- a/apps/benchmark/protected/.htaccess +++ /dev/null @@ -1 +0,0 @@ -deny from all diff --git a/apps/benchmark/protected/controllers/SiteController.php b/apps/benchmark/protected/controllers/SiteController.php deleted file mode 100644 index 16089d0619a..00000000000 --- a/apps/benchmark/protected/controllers/SiteController.php +++ /dev/null @@ -1,13 +0,0 @@ - $id, 'basePath' => $basePath)); +if (!isset($vendorPath)) { + echo "composer autoloader could not be found.\nYou should run `composer install` in repo root directory.\n"; + exit(1); +} +require __DIR__ . '/../framework/Yii.php'; + +Yii::setAlias('@yii/build', __DIR__); + +$application = new yii\console\Application([ + 'id' => 'yii-build', + 'basePath' => __DIR__, + 'controllerNamespace' => 'yii\build\controllers', + 'enableCoreCommands' => false, +]); $application->run(); diff --git a/build/build.bat b/build/build.bat index a1ae41f2ba8..67fc5fdbf91 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,23 +1,23 @@ -@echo off - -rem ------------------------------------------------------------- -rem build script for Windows. -rem -rem This is the bootstrap script for running build on Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem @version $Id$ -rem ------------------------------------------------------------- - -@setlocal - -set BUILD_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -%PHP_COMMAND% "%BUILD_PATH%build" %* - -@endlocal \ No newline at end of file +@echo off + +rem ------------------------------------------------------------- +rem build script for Windows. +rem +rem This is the bootstrap script for running build on Windows. +rem +rem @author Qiang Xue +rem @link https://www.yiiframework.com/ +rem @copyright 2008 Yii Software LLC +rem @license https://www.yiiframework.com/license/ +rem @version $Id$ +rem ------------------------------------------------------------- + +@setlocal + +set BUILD_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +%PHP_COMMAND% "%BUILD_PATH%build" %* + +@endlocal diff --git a/build/build.xml b/build/build.xml index 9c18af0cae1..253d5632dcd 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,12 +1,12 @@ @@ -21,7 +21,7 @@ - + @@ -241,7 +241,7 @@ Please update yiisite/common/data/versions.php file with the following code: package="${phing.project.name}" summary="Yii PHP Framework" pkgdescription="Yii PHP Framework: Best for Web 2.0 Development" - notes="http://www.yiiframework.com/files/CHANGELOG-$%7Byii.release%7D.txt" + notes="https://www.yiiframework.com/files/CHANGELOG-$%7Byii.release%7D.txt" license="BSD" /> @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and YiiBase.php + - sync : synchronize yiilite.php and BaseYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php new file mode 100644 index 00000000000..4777a1de267 --- /dev/null +++ b/build/controllers/ClassmapController.php @@ -0,0 +1,96 @@ + + * @since 2.0 + * + * @extends Controller + */ +class ClassmapController extends Controller +{ + public $defaultAction = 'create'; + + /** + * Creates a class map for the core Yii classes. + * @param string $root the root path of Yii framework. Defaults to YII2_PATH. + * @param string $mapFile the file to contain the class map. Defaults to YII2_PATH . '/classes.php'. + */ + public function actionCreate($root = null, $mapFile = null) + { + if ($root === null) { + $root = YII2_PATH; + } + $root = FileHelper::normalizePath($root); + if ($mapFile === null) { + $mapFile = YII2_PATH . '/classes.php'; + } + $options = [ + 'filter' => function ($path) { + if (is_file($path)) { + $file = basename($path); + if ($file[0] < 'A' || $file[0] > 'Z') { + return false; + } + } + + return null; + }, + 'only' => ['*.php'], + 'except' => [ + '/Yii.php', + '/BaseYii.php', + '/console/', + '/requirements/', + ], + ]; + $files = FileHelper::findFiles($root, $options); + $map = []; + foreach ($files as $file) { + if (strpos($file, $root) !== 0) { + throw new Exception("Something wrong: $file\n"); + } + $path = str_replace('\\', '/', substr($file, \strlen($root))); + $map[$path] = " 'yii" . substr(str_replace('/', '\\', $path), 0, -4) . "' => YII2_PATH . '$path',"; + } + ksort($map); + $map = implode("\n", $map); + $output = << + * @since 2.0 + * + * @extends Controller + */ +class DevController extends Controller +{ + /** + * {@inheritdoc} + */ + public $defaultAction = 'all'; + /** + * @var bool whether to use HTTP when cloning GitHub repositories + */ + public $useHttp = false; + /** + * @var bool whether to use --no-progress option when running composer + */ + public $composerNoProgress = false; + /** + * @var array + */ + public $apps = [ + 'basic' => 'git@github.com:yiisoft/yii2-app-basic.git', + 'advanced' => 'git@github.com:yiisoft/yii2-app-advanced.git', + 'benchmark' => 'git@github.com:yiisoft/yii2-app-benchmark.git', + ]; + /** + * @var array + */ + public $extensions = [ + 'apidoc' => 'git@github.com:yiisoft/yii2-apidoc.git', + 'authclient' => 'git@github.com:yiisoft/yii2-authclient.git', + 'bootstrap' => 'git@github.com:yiisoft/yii2-bootstrap.git', + 'bootstrap4' => 'git@github.com:yiisoft/yii2-bootstrap4.git', + 'bootstrap5' => 'git@github.com:yiisoft/yii2-bootstrap5.git', + 'composer' => 'git@github.com:yiisoft/yii2-composer.git', + 'debug' => 'git@github.com:yiisoft/yii2-debug.git', + 'elasticsearch' => 'git@github.com:yiisoft/yii2-elasticsearch.git', + 'faker' => 'git@github.com:yiisoft/yii2-faker.git', + 'gii' => 'git@github.com:yiisoft/yii2-gii.git', + 'httpclient' => 'git@github.com:yiisoft/yii2-httpclient.git', + 'imagine' => 'git@github.com:yiisoft/yii2-imagine.git', + 'jui' => 'git@github.com:yiisoft/yii2-jui.git', + 'mongodb' => 'git@github.com:yiisoft/yii2-mongodb.git', + 'queue' => 'git@github.com:yiisoft/yii2-queue.git', + 'redis' => 'git@github.com:yiisoft/yii2-redis.git', + 'shell' => 'git@github.com:yiisoft/yii2-shell.git', + 'smarty' => 'git@github.com:yiisoft/yii2-smarty.git', + 'sphinx' => 'git@github.com:yiisoft/yii2-sphinx.git', + 'swiftmailer' => 'git@github.com:yiisoft/yii2-swiftmailer.git', + 'symfonymailer' => 'git@github.com:yiisoft/yii2-symfonymailer.git', + 'twig' => 'git@github.com:yiisoft/yii2-twig.git', + ]; + + + /** + * Install all extensions and advanced + basic app. + */ + public function actionAll() + { + if (!$this->confirm('Install all applications and all extensions now?')) { + return 1; + } + + foreach ($this->extensions as $ext => $repo) { + $ret = $this->actionExt($ext); + if ($ret !== 0) { + return $ret; + } + } + + foreach ($this->apps as $app => $repo) { + $ret = $this->actionApp($app); + if ($ret !== 0) { + return $ret; + } + } + + return 0; + } + + /** + * Runs a command in all extension and application directories. + * + * Can be used to run e.g. `git pull`. + * + * ./build/build dev/run git pull + * + * @param string $command the command to run + */ + public function actionRun($command) + { + $command = implode(' ', \func_get_args()); + + // root of the dev repo + $base = \dirname(\dirname(__DIR__)); + $dirs = $this->listSubDirs("$base/extensions"); + $dirs = array_merge($dirs, $this->listSubDirs("$base/apps")); + asort($dirs); + + $oldcwd = getcwd(); + foreach ($dirs as $dir) { + $displayDir = substr($dir, \strlen($base)); + $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD); + chdir($dir); + passthru($command); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + } + chdir($oldcwd); + } + + /** + * This command installs a project template in the `apps` directory and links the framework and extensions. + * + * It basically runs the following commands in the dev repo root: + * + * - Run `composer update` + * - `rm -rf apps/basic/vendor/yiisoft/yii2` + * - `rm -rf apps/basic/vendor/yiisoft/yii2-*` + * + * And replaces them with symbolic links to the extensions and framework path in the dev repo. + * + * Extensions required by the application are automatically installed using the `ext` action. + * + * @param string $app the application name e.g. `basic` or `advanced`. + * @param string $repo url of the git repo to clone if it does not already exist. + * @return int return code + */ + public function actionApp($app, $repo = null) + { + // root of the dev repo + $base = \dirname(\dirname(__DIR__)); + $appDir = "$base/apps/$app"; + + if (!file_exists($appDir)) { + if (empty($repo)) { + if (isset($this->apps[$app])) { + $repo = $this->apps[$app]; + if ($this->useHttp) { + $repo = str_replace('git@github.com:', 'https://github.com/', $repo); + } + } else { + $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED); + return 1; + } + } + + $this->stdout("cloning application repo '$app' from '$repo'...\n", Console::BOLD); + passthru('git clone ' . escapeshellarg($repo) . ' ' . $appDir, $returnVar); + if ($returnVar !== 0) { + $this->stdout("Error occurred while cloning repository.\n", Console::BOLD, Console::FG_RED); + return 1; + } + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + } + + // cleanup + $this->stdout("cleaning up application '$app' vendor directory...\n", Console::BOLD); + $this->cleanupVendorDir($appDir); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + // composer update + $this->stdout("updating composer for app '$app'...\n", Console::BOLD); + chdir($appDir); + $command = 'composer update --prefer-dist'; + if ($this->composerNoProgress) { + $command .= ' --no-progress'; + } + passthru($command); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + // link directories + $this->stdout("linking framework and extensions to '$app' app vendor dir...\n", Console::BOLD); + $this->linkFrameworkAndExtensions($appDir, $base); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + return 0; + } + + /** + * This command installs an extension in the `extensions` directory and links the framework and other extensions. + * + * @param string $extension the application name e.g. `basic` or `advanced`. + * @param string $repo url of the git repo to clone if it does not already exist. + * + * @return int + */ + public function actionExt($extension, $repo = null) + { + // root of the dev repo + $base = \dirname(\dirname(__DIR__)); + $extensionDir = "$base/extensions/$extension"; + + if (!file_exists($extensionDir)) { + if (empty($repo)) { + if (isset($this->extensions[$extension])) { + $repo = $this->extensions[$extension]; + if ($this->useHttp) { + $repo = str_replace('git@github.com:', 'https://github.com/', $repo); + } + } else { + $this->stderr("Repo argument is required for extension '$extension'.\n", Console::FG_RED); + return 1; + } + } + + $this->stdout("cloning extension repo '$extension' from '$repo'...\n", Console::BOLD); + passthru('git clone ' . escapeshellarg($repo) . ' ' . $extensionDir); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + } + + // cleanup + $this->stdout("cleaning up extension '$extension' vendor directory...\n", Console::BOLD); + $this->cleanupVendorDir($extensionDir); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + // composer update + $this->stdout("updating composer for extension '$extension'...\n", Console::BOLD); + chdir($extensionDir); + $command = 'composer update --prefer-dist'; + if ($this->composerNoProgress) { + $command .= ' --no-progress'; + } + passthru($command); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + // link directories + $this->stdout("linking framework and extensions to '$extension' vendor dir...\n", Console::BOLD); + $this->linkFrameworkAndExtensions($extensionDir, $base); + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + + return 0; + } + + /** + * {@inheritdoc} + */ + public function options($actionID) + { + $options = parent::options($actionID); + if (\in_array($actionID, ['ext', 'app', 'all'], true)) { + $options[] = 'useHttp'; + $options[] = 'composerNoProgress'; + } + + return $options; + } + + + /** + * Remove all symlinks in the vendor subdirectory of the directory specified. + * @param string $dir base directory + */ + protected function cleanupVendorDir($dir) + { + if (is_link($link = "$dir/vendor/yiisoft/yii2")) { + $this->stdout("Removing symlink $link.\n"); + FileHelper::unlink($link); + } + $extensions = $this->findDirs("$dir/vendor/yiisoft"); + foreach ($extensions as $ext) { + if (is_link($link = "$dir/vendor/yiisoft/yii2-$ext")) { + $this->stdout("Removing symlink $link.\n"); + FileHelper::unlink($link); + } + } + } + + /** + * Creates symlinks to framework and extension sources for the application. + * @param string $dir application directory + * @param string $base Yii sources base directory + * + * @return int + */ + protected function linkFrameworkAndExtensions($dir, $base) + { + if (is_dir($link = "$dir/vendor/yiisoft/yii2")) { + $this->stdout("Removing dir $link.\n"); + FileHelper::removeDirectory($link); + $this->stdout("Creating symlink for $link.\n"); + symlink("$base/framework", $link); + } + $extensions = $this->findDirs("$dir/vendor/yiisoft"); + foreach ($extensions as $ext) { + if (is_dir($link = "$dir/vendor/yiisoft/yii2-$ext")) { + $this->stdout("Removing dir $link.\n"); + FileHelper::removeDirectory($link); + $this->stdout("Creating symlink for $link.\n"); + if (!file_exists("$base/extensions/$ext")) { + $ret = $this->actionExt($ext); + if ($ret !== 0) { + return $ret; + } + } + symlink("$base/extensions/$ext", $link); + } + } + + return ExitCode::OK; + } + + /** + * Get a list of subdirectories for directory specified. + * @param string $dir directory to read + * + * @return array list of subdirectories + */ + protected function listSubDirs($dir) + { + $list = []; + $handle = opendir($dir); + if ($handle === false) { + throw new InvalidParamException("Unable to open directory: $dir"); + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + // ignore hidden directories + if (strpos($file, '.') === 0) { + continue; + } + if (is_dir("$dir/$file")) { + $list[] = "$dir/$file"; + } + } + closedir($handle); + return $list; + } + + /** + * Finds linkable applications. + * + * @param string $dir directory to search in + * @return array list of applications command can link + */ + protected function findDirs($dir) + { + $list = []; + $handle = @opendir($dir); + if ($handle === false) { + return []; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_dir($path) && preg_match('/^yii2-(.*)$/', $file, $matches)) { + $list[] = $matches[1]; + } + } + closedir($handle); + + foreach ($list as $i => $e) { + if ($e === 'composer') { // skip composer to not break composer update + unset($list[$i]); + } + } + + return $list; + } +} diff --git a/build/controllers/LocaleController.php b/build/controllers/LocaleController.php deleted file mode 100644 index 1f9827e1f9c..00000000000 --- a/build/controllers/LocaleController.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -class LocaleController extends Controller -{ - public $defaultAction = 'plural'; - - /** - * Generates the plural rules data. - * - * This command will parse the plural rule XML file from CLDR and convert them - * into appropriate PHP representation to support Yii message translation feature. - * @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in - * http://www.unicode.org/Public/cldr/latest/core.zip - * Extract the zip file and locate the file "common/supplemental/plurals.xml". - * @throws Exception - */ - public function actionPlural($xmlFile) - { - if (!is_file($xmlFile)) { - throw new Exception("The source plural rule file does not exist: $xmlFile"); - } - - $xml = simplexml_load_file($xmlFile); - - $allRules = array(); - - $patterns = array( - '/n in 0..1/' => '(n==0||n==1)', - '/\s+is\s+not\s+/i' => '!=', //is not - '/\s+is\s+/i' => '==', //is - '/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%") - '/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in - '/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in - '/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within - '/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within - ); - foreach ($xml->plurals->pluralRules as $node) { - $attributes = $node->attributes(); - $locales = explode(' ', $attributes['locales']); - $rules = array(); - - if (!empty($node->pluralRule)) { - foreach ($node->pluralRule as $rule) { - $expr_or = preg_split('/\s+or\s+/i', $rule); - foreach ($expr_or as $key_or => $val_or) { - $expr_and = preg_split('/\s+and\s+/i', $val_or); - $expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and); - $expr_or[$key_or] = implode('&&', $expr_and); - } - $expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or)); - $rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) { - if ($matches[2] - $matches[1] <= 5) { - return 'array(' . implode(',', range($matches[1], $matches[2])) . ')'; - } else { - return $matches[0]; - } - }, $expr); - - } - foreach ($locales as $locale) { - $allRules[$locale] = $rules; - } - } - } - // hard fix for "br": the rule is too complex - $allRules['br'] = array( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', - 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', - 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', - 3 => 'fmod($n,1000000)==0&&$n!=0', - ); - if (preg_match('/\d+/', $xml->version['number'], $matches)) { - $revision = $matches[0]; - } else { - $revision = -1; - } - - echo " + * @since 2.0 + * + * @extends Controller + */ +class MimeTypeController extends Controller +{ + /** + * @var array MIME type aliases + */ + private $_aliases = [ + 'text/rtf' => 'application/rtf', + 'text/xml' => 'application/xml', + 'image/svg' => 'image/svg+xml', + 'image/x-bmp' => 'image/bmp', + 'image/x-bitmap' => 'image/bmp', + 'image/x-xbitmap' => 'image/bmp', + 'image/x-win-bitmap' => 'image/bmp', + 'image/x-windows-bmp' => 'image/bmp', + 'image/ms-bmp' => 'image/bmp', + 'image/x-ms-bmp' => 'image/bmp', + 'application/bmp' => 'image/bmp', + 'application/x-bmp' => 'image/bmp', + 'application/x-win-bitmap' => 'image/bmp', + ]; + + /** + * @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, $extensionsOutFile = null) + { + if ($outFile === null) { + $outFile = Yii::getAlias('@yii/helpers/mimeTypes.php'); + } + + if ($aliasesOutFile === null) { + $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://raw.githubusercontent.com/apache/httpd/refs/heads/trunk/docs/conf/mime.types')) { + $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 Git.\n"); + } + } + + /** + * @param string $outFile + * @param string $content + */ + private function generateMimeTypesFile($outFile, $content) + { + $this->stdout("Generating file $outFile..."); + $mimeMap = []; + 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); + foreach ($parts as $ext) { + if (!empty($ext)) { + $mimeMap[$ext] = $mime; + } + } + } + $mimeMap = array_replace($mimeMap, $this->_additionalMimeTypes); + ksort($mimeMap, SORT_STRING); + $array = VarDumper::export($mimeMap); + + $content = <<= 80100 && PHP_VERSION_ID < 80122) || (PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80209)) { + \$mimeTypes = array_replace(\$mimeTypes, array('xz' => 'application/octet-stream')); +} + +return \$mimeTypes; + +EOD; + file_put_contents($outFile, $content); + $this->stdout("done.\n", Console::FG_GREEN); + } + + /** + * @param string $outFile + */ + private function generateMimeAliasesFile($outFile) + { + $this->stdout("generating file $outFile..."); + $array = VarDumper::export($this->_aliases); + $content = <<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 = <<stdout("done.\n", Console::FG_GREEN); + } +} diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php new file mode 100644 index 00000000000..c38130b9c7e --- /dev/null +++ b/build/controllers/PhpDocController.php @@ -0,0 +1,989 @@ + + * @author Alexander Makarov + * @since 2.0 + * + * @extends ConsoleController + */ +class PhpDocController extends ConsoleController +{ + /** + * Manually added PHPDoc properties that do not need to be removed or changed. + * + * @phpstan-var array + */ + private const MANUALLY_ADDED_PROPERTIES = [ + WebController::class => [ + 'request', + 'response', + 'view', + ], + ConsoleController::class => [ + 'request', + 'response', + ], + Model::class => [ + 'errors', + ], + Module::class => [ + 'aliases', + ], + Dispatcher::class => [ + 'flushInterval', + 'logger', + ], + Target::class => [ + 'enabled', + ], + WebRequest::class => [ + 'hostInfo', + ], + QueryBuilder::class => [ + 'conditionClasses', + ], + ]; + + private const PROPERTIES_ENCLOSURE = " *\n"; + + /** + * {@inheritdoc} + */ + public $defaultAction = 'property'; + /** + * @var bool whether to update class docs directly. Setting this to false will just output docs + * for copy and paste. + */ + public $updateFiles = true; + /** + * @var bool whether to add copyright header to php files. This should be skipped in application code. + */ + public $skipFrameworkRequirements = false; + + + /** + * Generates `@property` annotations in class files from getters and setters. + * + * Property description will be taken from getter or setter or from an `@property` annotation + * in the getters docblock if there is one defined. + * + * See https://github.com/yiisoft/yii2/wiki/Core-framework-code-style#documentation for details. + * + * @param string $root the directory to parse files from. Defaults to YII2_PATH. + */ + public function actionProperty($root = null) + { + $files = $this->findFiles($root); + + $nFilesTotal = 0; + $nFilesUpdated = 0; + foreach ($files as $file) { + $result = $this->generateClassPropertyDocs($file); + if ($result !== false) { + list($className, $phpdoc) = $result; + if ($this->updateFiles) { + if ($this->updateClassPropertyDocs($file, $className, $phpdoc)) { + $nFilesUpdated++; + } + } elseif (!empty($phpdoc)) { + $this->stdout("\n[ " . $file . " ]\n\n", Console::BOLD); + $this->stdout($phpdoc); + } + } + $nFilesTotal++; + } + + $this->stdout("\nParsed $nFilesTotal files.\n"); + $this->stdout("Updated $nFilesUpdated files.\n"); + } + + /** + * Fix some issues with PHPDoc in files. + * + * @param string $root the directory to parse files from. Defaults to YII2_PATH. + */ + public function actionFix($root = null) + { + $files = $this->findFiles($root, false); + + $nFilesTotal = 0; + $nFilesUpdated = 0; + foreach ($files as $file) { + $contents = file_get_contents($file); + $hash = $this->hash($contents); + + // fix line endings + $lines = preg_split('/(\r\n|\n|\r)/', $contents); + + if (!$this->skipFrameworkRequirements) { + $this->fixFileDoc($lines); + } + $this->fixDocBlockIndentation($lines); + $lines = array_values($this->fixLineSpacing($lines)); + + $newContent = implode("\n", $lines); + if ($hash !== $this->hash($newContent)) { + file_put_contents($file, $newContent); + $nFilesUpdated++; + } + $nFilesTotal++; + } + + $this->stdout("\nParsed $nFilesTotal files.\n"); + $this->stdout("Updated $nFilesUpdated files.\n"); + } + + /** + * {@inheritdoc} + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), ['updateFiles', 'skipFrameworkRequirements']); + } + + /** + * @param string $root + * @param bool $needsInclude + * @return array list of files. + */ + protected function findFiles($root, $needsInclude = true) + { + $except = []; + if ($needsInclude) { + $extensionExcept = [ + 'apidoc' => [ + '/helpers/PrettyPrinter.php', + '/extensions/apidoc/helpers/ApiIndexer.php', + '/extensions/apidoc/helpers/ApiMarkdownLaTeX.php', + ], + 'codeception' => [ + '/TestCase.php', + '/DbTestCase.php', + ], + 'gii' => [ + '/components/DiffRendererHtmlInline.php', + '/generators/extension/default/AutoloadExample.php', + ], + 'swiftmailer' => [ + 'src/Logger.php', + ], + 'twig' => [ + '/Extension.php', + '/Optimizer.php', + '/Template.php', + '/TwigSimpleFileLoader.php', + '/ViewRendererStaticClassProxy.php', + ], + ]; + } else { + $extensionExcept = []; + } + + if ($root === null) { + $root = \dirname(YII2_PATH); + $extensionPath = "$root/extensions"; + $this->setUpExtensionAliases($extensionPath); + + $except = [ + '/apps/', + '/build/', + '/docs/', + '/extensions/composer/', + '/framework/BaseYii.php', + '/framework/Yii.php', + 'assets/', + 'tests/', + 'vendor/', + ]; + foreach ($extensionExcept as $ext => $paths) { + foreach ($paths as $path) { + $except[] = "/extensions/$ext$path"; + } + } + } elseif (preg_match('~extensions/([\w-]+)[\\\\/]?$~', $root, $matches)) { + $extensionPath = \dirname(rtrim($root, '\\/')); + $this->setUpExtensionAliases($extensionPath); + + list(, $extension) = $matches; + Yii::setAlias("@yii/$extension", (string)$root); + if (is_file($autoloadFile = Yii::getAlias("@yii/$extension/vendor/autoload.php"))) { + include $autoloadFile; + } + + if (isset($extensionExcept[$extension])) { + foreach ($extensionExcept[$extension] as $path) { + $except[] = $path; + } + } + $except[] = '/vendor/'; + $except[] = '/tests/'; + $except[] = '/docs/'; + + // // composer extension does not contain yii code + // if ($extension === 'composer') { + // return []; + // } + } elseif (preg_match('~apps/([\w-]+)[\\\\/]?$~', $root, $matches)) { + $extensionPath = \dirname(\dirname(rtrim($root, '\\/'))) . '/extensions'; + $this->setUpExtensionAliases($extensionPath); + + list(, $appName) = $matches; + Yii::setAlias("@app-$appName", (string)$root); + if (is_file($autoloadFile = Yii::getAlias("@app-$appName/vendor/autoload.php"))) { + include $autoloadFile; + } + + $except[] = '/runtime/'; + $except[] = '/vendor/'; + $except[] = '/tests/'; + $except[] = '/docs/'; + } + $root = FileHelper::normalizePath($root); + $options = [ + 'filter' => function ($path) { + if (is_file($path)) { + $file = basename($path); + if ($file[0] < 'A' || $file[0] > 'Z') { + return false; + } + } + + return null; + }, + 'only' => ['*.php'], + 'except' => array_merge($except, [ + '.git/', + 'views/', + 'requirements/', + 'gii/generators/', + 'vendor/', + '_support/', + ]), + ]; + + return FileHelper::findFiles($root, $options); + } + + /** + * @param string $extensionPath root path containing extension repositories. + */ + private function setUpExtensionAliases($extensionPath) + { + foreach (scandir($extensionPath) as $extension) { + if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { + Yii::setAlias("@yii/$extension", "$extensionPath/$extension"); + + $composerConfigFile = $extensionPath . '/' . $extension . '/composer.json'; + if (file_exists($composerConfigFile)) { + $composerConfig = Json::decode(file_get_contents($composerConfigFile)); + if (isset($composerConfig['autoload']['psr-4'])) { + foreach ($composerConfig['autoload']['psr-4'] as $namespace => $subPath) { + $alias = '@' . str_replace('\\', '/', $namespace); + $path = rtrim("$extensionPath/$extension/$subPath", '/'); + Yii::setAlias($alias, $path); + } + } + } + } + } + } + + /** + * Fix file PHPDoc. + */ + protected function fixFileDoc(&$lines) + { + // find namespace + $namespace = false; + $namespaceLine = ''; + $contentAfterNamespace = false; + foreach ($lines as $i => $line) { + $line = trim($line); + if (!empty($line)) { + if (strncmp($line, 'namespace', 9) === 0) { + $namespace = $i; + $namespaceLine = $line; + } elseif ($namespace !== false) { + $contentAfterNamespace = $i; + break; + } + } + } + + if ($namespace !== false && $contentAfterNamespace !== false) { + while ($contentAfterNamespace > 0) { + array_shift($lines); + $contentAfterNamespace--; + } + $lines = array_merge([ + ' $line) { + if (preg_match('~^(\s*)/\*\*$~', $line, $matches)) { + $docBlock = true; + $indent = $matches[1]; + } elseif (preg_match('~^(\s*)\*+/~', $line)) { + if ($docBlock) { // could be the end of normal comment + $lines[$i] = $indent . ' */'; + } + $docBlock = false; + $codeBlock = false; + $listIndent = ''; + $tag = false; + } elseif ($docBlock) { + $line = ltrim($line); + if (strpos($line, '*') === 0) { + $line = substr($line, 1); + } + if (strpos($line, ' ') === 0) { + $line = substr($line, 1); + } + $docLine = str_replace("\t", ' ', rtrim($line)); + if (empty($docLine)) { + $listIndent = ''; + } elseif (strpos($docLine, '@') === 0) { + $listIndent = ''; + $codeBlock = false; + $tag = true; + $docLine = preg_replace('/\s+/', ' ', $docLine); + $docLine = $this->fixParamTypes($docLine); + } elseif (preg_match('/^(~~~|```)/', $docLine)) { + $codeBlock = !$codeBlock; + $listIndent = ''; + } elseif (preg_match('/^(\s*)([0-9]+\.|-|\*|\+) /', $docLine, $matches)) { + $listIndent = str_repeat(' ', \strlen($matches[0])); + $tag = false; + $lines[$i] = $indent . ' * ' . $docLine; + continue; + } + if ($codeBlock) { + $lines[$i] = rtrim($indent . ' * ' . $docLine); + } else { + $lines[$i] = rtrim($indent . ' * ' . (empty($listIndent) && !$tag ? $docLine : ($listIndent . ltrim($docLine)))); + } + } + } + } + + /** + * @param string $line + * @return string + */ + protected function fixParamTypes($line) + { + return preg_replace_callback('~@(param|return) ([\w\\|]+)~i', function ($matches) { + $types = explode('|', $matches[2]); + foreach ($types as $i => $type) { + switch ($type) { + case 'integer': + $types[$i] = 'int'; + break; + case 'boolean': + $types[$i] = 'bool'; + break; + } + } + + return '@' . $matches[1] . ' ' . implode('|', $types); + }, $line); + } + + /** + * Fixes line spacing code style for properties and constants. + * @param string[] $lines + * @return string[] + */ + protected function fixLineSpacing($lines) + { + $propertiesOnly = false; + // remove blank lines between properties + $skip = true; + $level = 0; + foreach ($lines as $i => $line) { + if (strpos($line, 'class ') !== false) { + $skip = false; + } + if ($skip) { + continue; + } + + // keep spaces in multi line arrays + if (strpos($line, '*') === false && strncmp(trim($line), "'SQLSTATE[", 10) !== 0) { + $level += substr_count($line, '[') - substr_count($line, ']'); + } + + if (trim($line) === '') { + if ($level == 0) { + unset($lines[$i]); + } + } elseif (ltrim($line)[0] !== '*' && strpos($line, 'function ') !== false) { + break; + } elseif (trim($line) === '}') { + $propertiesOnly = true; + break; + } + } + $lines = array_values($lines); + + // add back some + $endofUse = false; + $endofConst = false; + $endofPublic = false; + $endofProtected = false; + $endofPrivate = false; + $skip = true; + $level = 0; // track array properties + $property = ''; + foreach ($lines as $i => $line) { + if (strpos($line, 'class ') !== false) { + $skip = false; + } + if ($skip) { + continue; + } + + // check for multi line array + if ($level > 0) { + ${'endof' . $property} = $i; + } + + $line = trim($line); + if (strncmp($line, 'public $', 8) === 0 || strncmp($line, 'public static $', 15) === 0) { + $endofPublic = $i; + $property = 'Public'; + $level = 0; + } elseif (strncmp($line, 'protected $', 11) === 0 || strncmp($line, 'protected static $', 18) === 0) { + $endofProtected = $i; + $property = 'Protected'; + $level = 0; + } elseif (strncmp($line, 'private $', 9) === 0 || strncmp($line, 'private static $', 16) === 0) { + $endofPrivate = $i; + $property = 'Private'; + $level = 0; + } elseif (strpos($line, 'const ') === 0) { + $endofConst = $i; + $property = false; + } elseif (strpos($line, 'use ') === 0) { + $endofUse = $i; + $property = false; + } elseif (strpos($line, '*') === 0) { + $property = false; + } elseif (strpos($line, '*') !== 0 && strpos($line, 'function ') !== false || $line === '}') { + break; + } + + // check for multi line array + if ($property !== false && strncmp($line, "'SQLSTATE[", 10) !== 0) { + $level += substr_count($line, '[') - substr_count($line, ']'); + } + } + + $endofAll = false; + foreach (['Private', 'Protected', 'Public', 'Const', 'Use'] as $var) { + if (${'endof' . $var} !== false) { + $endofAll = ${'endof' . $var}; + break; + } + } + + // $this->checkPropertyOrder($lineInfo); + $result = []; + foreach ($lines as $i => $line) { + $result[] = $line; + if (!($propertiesOnly && $i === $endofAll)) { + if ( + $i === $endofUse || $i === $endofConst || $i === $endofPublic || + $i === $endofProtected || $i === $endofPrivate + ) { + $result[] = ''; + } + if ($i === $endofAll) { + $result[] = ''; + } + } + } + + return $result; + } + + protected function checkPropertyOrder($lineInfo) + { + // TODO + } + + protected function updateClassPropertyDocs($file, $className, $propertyDoc) + { + if ($this->shouldSkipClass($className)) { + $this->stderr("[INFO] Skipping class $className.\n", Console::FG_BLUE, Console::BOLD); + return false; + } + + try { + $ref = new \ReflectionClass($className); + } catch (\Exception $e) { + $this->stderr("[ERR] Unable to create ReflectionClass for class '$className': " . $e->getMessage() . "\n", Console::FG_RED); + return false; + } catch (\Error $e) { + $this->stderr("[ERR] Unable to create ReflectionClass for class '$className': " . $e->getMessage() . "\n", Console::FG_RED); + return false; + } + if ($ref->getFileName() != $file) { + $this->stderr("[ERR] Unable to create ReflectionClass for class: $className loaded class is not from file: $file\n", Console::FG_RED); + return false; + } + + if ($this->isBaseObject($className, $ref)) { + $this->stderr("[INFO] Skipping class $className as it is not a subclass of yii\\base\\BaseObject.\n", Console::FG_BLUE, Console::BOLD); + return false; + } + + if ($ref->isSubclassOf('yii\db\BaseActiveRecord')) { + $this->stderr("[INFO] Skipping class $className as it is an ActiveRecord class, property handling is not supported yet.\n", Console::FG_BLUE, Console::BOLD); + return false; + } + + $oldDoc = $ref->getDocComment(); + $newDoc = $this->cleanDocComment($this->updateDocComment($oldDoc, $propertyDoc, $className)); + + $seenSince = false; + $seenAuthor = false; + + // TODO move these checks to different action + $lines = explode("\n", $newDoc); + $firstLine = trim($lines[1]); + if ($firstLine === '*' || strncmp($firstLine, '* @', 3) === 0) { + $this->stderr("[WARN] Class $className has no short description.\n", Console::FG_YELLOW, Console::BOLD); + } + foreach ($lines as $line) { + $line = trim($line); + if (strncmp($line, '* @since ', 9) === 0) { + $seenSince = true; + } elseif (strncmp($line, '* @author ', 10) === 0) { + $seenAuthor = true; + } + } + + if (!$this->skipFrameworkRequirements) { + if (!$seenSince) { + $this->stderr("[ERR] No @since found in class doc in file: $file\n", Console::FG_RED); + } + if (!$seenAuthor) { + $this->stderr("[ERR] No @author found in class doc in file: $file\n", Console::FG_RED); + } + } + + if (trim($oldDoc) != trim($newDoc)) { + $fileContent = explode("\n", file_get_contents($file)); + $start = $ref->getStartLine() - 2; + $docStart = $start - \count(explode("\n", $oldDoc)) + 1; + + $newFileContent = []; + $n = \count($fileContent); + for ($i = 0; $i < $n; $i++) { + if ($i > $start || $i < $docStart) { + $newFileContent[] = $fileContent[$i]; + } else { + $newFileContent[] = trim($newDoc); + $i = $start; + } + } + + file_put_contents($file, implode("\n", $newFileContent)); + + return true; + } + + return false; + } + + /** + * remove multi empty lines and trim trailing whitespace. + * + * @param $doc + * @return string + */ + protected function cleanDocComment($doc) + { + $lines = explode("\n", $doc); + $n = \count($lines); + for ($i = 0; $i < $n; $i++) { + $lines[$i] = rtrim($lines[$i]); + if (trim($lines[$i]) == '*' && trim($lines[$i + 1]) == '*') { + unset($lines[$i]); + } + } + + return implode("\n", $lines); + } + + /** + * Replace property annotations in doc comment. + * @param $doc + * @param $properties + * @return string + */ + protected function updateDocComment($doc, $properties, $className) + { + $manuallyAddedProperties = self::MANUALLY_ADDED_PROPERTIES[$className] ?? []; + $lines = explode("\n", $doc); + $propertyPart = false; + $propertyPosition = false; + $lastPropertyName = null; + $hasManuallyAddedProperties = false; + + foreach ($lines as $i => $line) { + $line = trim($line); + if (strncmp($line, '* @property', 11) === 0) { + $propertyPart = true; + } elseif ($propertyPart && $line === '*') { + $propertyPosition = $i; + $propertyPart = false; + } + if (strncmp($line, '* @author ', 10) === 0 && $propertyPosition === false) { + $propertyPosition = $i - 1; + $propertyPart = false; + } + if ($propertyPart) { + preg_match( + '/@property(?:-read|-write)?\s+([\\\\\w\|\[\]]+)\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', + $line, + $matches + ); + + if (isset($matches[2])) { + $lastPropertyName = $matches[2]; + } + + if (in_array($lastPropertyName, $manuallyAddedProperties)) { + $hasManuallyAddedProperties = true; + } else { + unset($lines[$i]); + } + } + } + + if ($properties === '') { + return implode("\n", $lines); + } + + // if no properties or other tags were present add properties at the end + if ($propertyPosition === false) { + $propertyPosition = \count($lines) - 2; + } + + // if there are properties that were added manually, remove start enclosure + if ($hasManuallyAddedProperties) { + $properties = substr($properties, strlen(self::PROPERTIES_ENCLOSURE)); + } + + $finalDoc = ''; + foreach ($lines as $i => $line) { + if (!$hasManuallyAddedProperties || $i !== $propertyPosition) { + $finalDoc .= $line . "\n"; + } + + if ($i == $propertyPosition) { + $finalDoc .= $properties; + } + } + + return $finalDoc; + } + + protected function generateClassPropertyDocs($fileName) + { + $phpdoc = ''; + $file = str_replace("\r", '', str_replace("\t", ' ', file_get_contents($fileName, true))); + $ns = $this->match('#\nnamespace (?[\w\\\\]+);\n#', $file); + $namespace = reset($ns); + if ($namespace === false) { + $namespace = '\\'; + } else { + $namespace = $namespace['name']; + } + $classes = $this->match('#\n(?:abstract )?(?:final )?class (?\w+)( extends .+)?( implements .+)?\n\{(?.*)\n\}(\n|$)#', $file); + + if (\count($classes) > 1) { + $this->stderr("[ERR] There should be only one class in a file: $fileName\n", Console::FG_RED); + + return false; + } + if (\count($classes) < 1) { + $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.*)\n\}(\n|$)#', $file); + if (\count($interfaces) == 1) { + return false; + } + + if (\count($interfaces) > 1) { + $this->stderr("[ERR] There should be only one interface in a file: $fileName\n", Console::FG_RED); + } else { + $traits = $this->match('#\ntrait (?\w+)\n\{(?.*)\n\}(\n|$)#', $file); + if (\count($traits) == 1) { + return false; + } + + if (\count($traits) > 1) { + $this->stderr("[ERR] There should be only one class/trait/interface in a file: $fileName\n", Console::FG_RED); + } else { + $this->stderr("[ERR] No class in file: $fileName\n", Console::FG_RED); + } + } + + return false; + } + + $className = null; + foreach ($classes as &$class) { + $className = $namespace . '\\' . $class['name']; + + $gets = $this->match( + '#\* @return (?[\w\\|\\\\\\[\\]]+)' + . '(?: (?(?:(?!\*/|\* @).)+?)(?:(?!\*/).)+|[\s\n]*)((\*\n)|(\*\s.+))*\*/' + . '[\s\n]{2,}(\#\[\\\\*.+\])*[\s\n]{2,}' + . 'public function (?get)(?\w+)\((?:,? ?\$\w+ ?= ?[^,]+)*\)(\:\s*[\w\\|\\\\\\[\\]]+)?#', + $class['content'], + true + ); + + $sets = $this->match( + '#\* @param (?[\w\\|\\\\\\[\\]]+) \$\w+' + . '(?: (?(?:(?!\*/|\* @).)+?)(?:(?!\*/).)+|[\s\n]*)((\*\n)|(\*\s.+))*\*/' + . '[\s\n]{2,}(\#\[\\\\*.+\])*[\s\n]{2,}' + . 'public function (?set)(?\w+)\(([\w\\|\\\\\\[\\]]+\s*)?\$\w+(?:, ?\$\w+ ?= ?[^,]+)*\)(\:\s*[\w\\|\\\\\\[\\]]+)?#', + $class['content'], + true + ); + + $acrs = array_merge($gets, $sets); + $manuallyAddedProperties = self::MANUALLY_ADDED_PROPERTIES[$className] ?? []; + $props = []; + + foreach ($acrs as &$acr) { + $acr['name'] = lcfirst($acr['name']); + if (in_array($acr['name'], $manuallyAddedProperties)) { + continue; + } + + $acr['comment'] = trim(preg_replace('#(^|\n)\s+\*\s?#', '$1 * ', $acr['comment'])); + $props[$acr['name']][$acr['kind']] = [ + 'type' => $acr['type'], + 'comment' => $this->fixSentence($acr['comment']), + ]; + } + + if (\count($props) === 0) { + continue; + } + + ksort($props); + + foreach ($props as $propName => &$prop) { + $docLine = ' * @property'; + $note = ''; + if (isset($prop['get'], $prop['set'])) { + if ($prop['get']['type'] !== $prop['set']['type']) { + $note = ' Note that the type of this property differs in getter and setter.' + . ' See [[get' . ucfirst($propName) . '()]]' + . ' and [[set' . ucfirst($propName) . '()]] for details.'; + } + } elseif (isset($prop['get'])) { + if (!$this->hasSetterInParents($className, $propName)) { + $docLine .= '-read'; + } + } elseif (isset($prop['set'])) { + if (!$this->hasGetterInParents($className, $propName)) { + $docLine .= '-write'; + } + } else { + continue; + } + $docLine .= ' ' . $this->getPropParam($prop, 'type') . " $$propName "; + $comment = explode("\n", $this->getPropParam($prop, 'comment') . $note); + foreach ($comment as &$cline) { + $cline = ltrim(rtrim($cline), '* '); + } + $docLine = wordwrap($docLine . implode(' ', $comment), 110, "\n * ") . "\n"; + + $phpdoc .= $docLine; + } + } + + if ($phpdoc !== '') { + $phpdoc = self::PROPERTIES_ENCLOSURE . $phpdoc . self::PROPERTIES_ENCLOSURE; + } + + return [$className, $phpdoc]; + } + + protected function match($pattern, $subject, $split = false) + { + $sets = []; + // split subject by double newlines because regex sometimes has problems with matching + // in the complete set of methods + // example: yii\di\ServiceLocator setComponents() is not recognized in the whole but in + // a part of the class. + $parts = $split ? explode("\n\n", $subject) : [$subject]; + foreach ($parts as $part) { + preg_match_all($pattern . 'suU', $part, $matches, PREG_SET_ORDER); + foreach ($matches as &$set) { + foreach ($set as $i => $match) { + if (is_numeric($i) /*&& $i != 0*/) { + unset($set[$i]); + } + } + + $sets[] = $set; + } + } + + return $sets; + } + + protected function fixSentence($str) + { + $str = rtrim($str, '*'); + $str = rtrim($str); + + // TODO fix word wrap + if ($str == '') { + return ''; + } + + return strtoupper(substr($str, 0, 1)) . substr($str, 1) . ($str[\strlen($str) - 1] !== '.' ? '.' : ''); + } + + protected function getPropParam($prop, $param) + { + return isset($prop['property']) ? $prop['property'][$param] : (isset($prop['get']) ? $prop['get'][$param] : $prop['set'][$param]); + } + + /** + * Generate a hash value (message digest) + * @param string $string message to be hashed. + * @return string calculated message digest. + */ + private function hash($string) + { + if (!function_exists('hash')) { + return sha1($string); + } + return hash('sha256', $string); + } + + /** + * @param string $className + * @param string $propName + * @return bool + */ + protected function hasGetterInParents($className, $propName) + { + $class = $className; + + try { + while ($parent = get_parent_class($class)) { + if (method_exists($parent, 'get' . ucfirst($propName))) { + return true; + } + $class = $parent; + } + } catch (\Throwable $t) { + $this->stderr("[ERR] Error when getting parents for $className\n", Console::FG_RED); + return false; + } + return false; + } + + /** + * @param string $className + * @param string $propName + * @return bool + */ + protected function hasSetterInParents($className, $propName) + { + $class = $className; + + try { + while ($parent = get_parent_class($class)) { + if (method_exists($parent, 'set' . ucfirst($propName))) { + return true; + } + $class = $parent; + } + } catch (\Throwable $t) { + $this->stderr("[ERR] Error when getting parents for $className\n", Console::FG_RED); + return false; + } + return false; + } + + /** + * @param string $className + * @param \ReflectionClass $ref + * @return bool + * + * @phpstan-param \ReflectionClass $ref + */ + protected function isBaseObject($className, \ReflectionClass $ref) + { + $isDeprecatedObject = false; + if (PHP_VERSION_ID <= 70100) { + $isDeprecatedObject = $ref->isSubclassOf('yii\base\Object') || $className === 'yii\base\Object'; + } + return !$isDeprecatedObject && !$ref->isSubclassOf('yii\base\BaseObject') && $className !== 'yii\base\BaseObject'; + } + + private function shouldSkipClass($className) + { + if (PHP_VERSION_ID > 70100) { + return $className === 'yii\base\Object'; + } + return false; + } +} diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php new file mode 100644 index 00000000000..4ffa7142f05 --- /dev/null +++ b/build/controllers/ReleaseController.php @@ -0,0 +1,1077 @@ + + * @since 2.0 + * + * @extends Controller + */ +class ReleaseController extends Controller +{ + public $defaultAction = 'release'; + + /** + * @var string base path to use for releases. + */ + public $basePath; + /** + * @var bool whether to make actual changes. If true, it will run without changing or pushing anything. + */ + public $dryRun = false; + /** + * @var bool whether to fetch the latest tags. + */ + public $update = false; + /** + * @var string override the default version. e.g. for major or patch releases. + */ + public $version; + + + public function options($actionID) + { + $options = ['basePath']; + if ($actionID === 'release') { + $options[] = 'dryRun'; + $options[] = 'version'; + } elseif ($actionID === 'sort-changelog') { + $options[] = 'version'; + } elseif ($actionID === 'info') { + $options[] = 'update'; + } + + return array_merge(parent::options($actionID), $options); + } + + + public function beforeAction($action) + { + if (!$this->interactive) { + throw new Exception('Sorry, but releases should be run interactively to ensure you actually verify what you are doing ;)'); + } + if ($this->basePath === null) { + $this->basePath = \dirname(\dirname(__DIR__)); + } + $this->basePath = rtrim($this->basePath, '\\/'); + return parent::beforeAction($action); + } + + /** + * Shows information about current framework and extension versions. + */ + public function actionInfo() + { + $items = [ + 'framework', + 'app-basic', + 'app-advanced', + ]; + $extensionPath = "{$this->basePath}/extensions"; + foreach (scandir($extensionPath) as $extension) { + if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { + $items[] = $extension; + } + } + + if ($this->update) { + foreach ($items as $item) { + $this->stdout("fetching tags for $item..."); + if ($item === 'framework') { + $this->gitFetchTags((string)$this->basePath); + } elseif (strncmp('app-', $item, 4) === 0) { + $this->gitFetchTags("{$this->basePath}/apps/" . substr($item, 4)); + } else { + $this->gitFetchTags("{$this->basePath}/extensions/$item"); + } + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + } + } else { + $this->stdout("\nInformation may be outdated, re-run with `--update` to fetch latest tags.\n\n"); + } + + $versions = $this->getCurrentVersions($items); + $nextVersions = $this->getNextVersions($versions, self::PATCH); + + // print version table + $w = $this->minWidth(array_keys($versions)); + $this->stdout(str_repeat(' ', $w + 2) . "Current Version Next Version\n", Console::BOLD); + foreach ($versions as $ext => $version) { + $this->stdout($ext . str_repeat(' ', $w + 3 - mb_strlen($ext)) . $version . ''); + $this->stdout(str_repeat(' ', 17 - mb_strlen($version)) . $nextVersions[$ext] . "\n"); + } + } + + private function minWidth($a) + { + $w = 1; + foreach ($a as $s) { + if (($l = mb_strlen($s)) > $w) { + $w = $l; + } + } + + return $w; + } + + /** + * Automation tool for making Yii framework and official extension releases. + * + * Usage: + * + * To make a release, make sure your git is clean (no uncommitted changes) and run the following command in + * the yii dev repo root: + * + * ``` + * ./build/build release framework + * ``` + * + * or + * + * ``` + * ./build/build release redis,bootstrap,apidoc + * ``` + * + * You may use the `--dryRun` switch to test the command without changing or pushing anything: + * + * ``` + * ./build/build release redis --dryRun + * ``` + * + * The command will guide you through the complete release process including changing of files, + * committing and pushing them. Each git command must be confirmed and can be skipped individually. + * You may adjust changes in a separate shell or your IDE while the command is waiting for confirmation. + * + * @param array $what what do you want to release? this can either be: + * + * - an extension name such as `redis` or `bootstrap`, + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * - or `framework` if you want to release a new version of the framework itself. + * + * @return int + */ + public function actionRelease(array $what) + { + if (\count($what) > 1) { + $this->stdout("Currently only one simultaneous release is supported.\n"); + return 1; + } + + $this->stdout("This is the Yii release manager\n\n", Console::BOLD); + + if ($this->dryRun) { + $this->stdout("Running in \"dry-run\" mode, nothing will actually be changed.\n\n", Console::BOLD, Console::FG_GREEN); + } + + $this->validateWhat($what); + $versions = $this->getCurrentVersions($what); + + if ($this->version !== null) { + // if a version is explicitly given + $newVersions = []; + foreach ($versions as $k => $v) { + $newVersions[$k] = $this->version; + } + } else { + // otherwise, get next patch or minor + $newVersions = $this->getNextVersions($versions, self::PATCH); + } + + $this->stdout("You are about to prepare a new release for the following things:\n\n"); + $this->printWhat($what, $newVersions, $versions); + $this->stdout("\n"); + + $this->stdout("Before you make a release briefly go over the changes and check if you spot obvious mistakes:\n\n", Console::BOLD); + $gitDir = reset($what) === 'framework' ? 'framework/' : ''; + $gitVersion = $versions[reset($what)]; + if (strncmp('app-', reset($what), 4) !== 0) { + $this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n\n git diff $gitVersion.. {$gitDir}CHANGELOG.md\n\n"); + $this->stdout("- are all new `@since` tags for this release version?\n"); + } + $this->stdout("- other issues with code changes?\n\n git diff -w $gitVersion.. {$gitDir}\n\n"); + $travisUrl = reset($what) === 'framework' ? '' : '-' . reset($what); + $this->stdout("- are unit tests passing on travis? https://travis-ci.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"); + $this->printWhatUrls($what, $versions); + $this->stdout("\n"); + + if (!$this->confirm('When you continue, this tool will run cleanup jobs and update the changelog as well as other files (locally). Continue?', false)) { + $this->stdout("Canceled.\n"); + return 1; + } + + foreach ($what as $ext) { + if ($ext === 'framework') { + $this->releaseFramework("{$this->basePath}/framework", $newVersions['framework']); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->releaseApplication(substr($ext, 4), "{$this->basePath}/apps/" . substr($ext, 4), $newVersions[$ext]); + } else { + $this->releaseExtension($ext, "{$this->basePath}/extensions/$ext", $newVersions[$ext]); + } + } + + return 0; + } + + /** + * This will generate application packages for download page. + * + * Usage: + * + * ``` + * ./build/build release/package app-basic + * ``` + * + * @param array $what what do you want to package? this can either be: + * + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * + * @return int + */ + public function actionPackage(array $what) + { + $this->validateWhat($what, ['app']); + $versions = $this->getCurrentVersions($what); + + $this->stdout("You are about to generate packages for the following things:\n\n"); + foreach ($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(' - '); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(' application version '); + } elseif ($ext === 'framework') { + $this->stdout(' - Yii Framework version '); + } else { + $this->stdout(' - '); + $this->stdout($ext, Console::FG_RED); + $this->stdout(' extension version '); + } + $this->stdout($versions[$ext], Console::BOLD); + $this->stdout("\n"); + } + $this->stdout("\n"); + + $packagePath = "{$this->basePath}/packages"; + $this->stdout("Packages will be stored in $packagePath\n\n"); + + if (!$this->confirm('Continue?', false)) { + $this->stdout("Canceled.\n"); + return 1; + } + + foreach ($what as $ext) { + if ($ext === 'framework') { + throw new Exception('Can not package framework.'); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->packageApplication(substr($ext, 4), $versions[$ext], $packagePath); + } else { + throw new Exception('Can not package extension.'); + } + } + + $this->stdout("\ndone. verify the versions composer installed above and push it to github!\n\n"); + + return 0; + } + + /** + * Sorts CHANGELOG for framework or extension. + * + * @param array $what what do you want to resort changelog for? this can either be: + * + * - an extension name such as `redis` or `bootstrap`, + * - or `framework` if you want to release a new version of the framework itself. + */ + public function actionSortChangelog(array $what) + { + if (\count($what) > 1) { + $this->stdout("Currently only one simultaneous release is supported.\n"); + return 1; + } + $this->validateWhat($what, ['framework', 'ext'], false); + + $version = $this->version ?: array_values($this->getNextVersions($this->getCurrentVersions($what), self::PATCH))[0]; + $this->stdout('sorting CHANGELOG of '); + $this->stdout(reset($what), Console::BOLD); + $this->stdout(' for version '); + $this->stdout($version, Console::BOLD); + $this->stdout('...'); + + $this->resortChangelogs($what, $version); + + $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); + } + + protected function printWhat(array $what, $newVersions, $versions) + { + foreach ($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(' - '); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(' application version '); + } elseif ($ext === 'framework') { + $this->stdout(' - Yii Framework version '); + } else { + $this->stdout(' - '); + $this->stdout($ext, Console::FG_RED); + $this->stdout(' extension version '); + } + $this->stdout($newVersions[$ext], Console::BOLD); + $this->stdout(", last release was {$versions[$ext]}\n"); + } + } + + protected function printWhatUrls(array $what, $oldVersions) + { + foreach ($what as $ext) { + if ($ext === 'framework') { + $this->stdout("framework: https://github.com/yiisoft/yii2-framework/compare/{$oldVersions[$ext]}...master\n"); + $this->stdout("app-basic: https://github.com/yiisoft/yii2-app-basic/compare/{$oldVersions[$ext]}...master\n"); + $this->stdout("app-advanced: https://github.com/yiisoft/yii2-app-advanced/compare/{$oldVersions[$ext]}...master\n"); + } else { + $this->stdout($ext, Console::FG_RED); + $this->stdout(": https://github.com/yiisoft/yii2-$ext/compare/{$oldVersions[$ext]}...master\n"); + } + } + } + + /** + * @param array $what list of items + * @param array $limit list of things to allow, or empty to allow any, can be `app`, `framework`, `extension` + * @param bool $ensureGitClean + * @throws \yii\base\Exception + */ + protected function validateWhat(array $what, $limit = [], $ensureGitClean = true) + { + foreach ($what as $w) { + if (strncmp('app-', $w, 4) === 0) { + if (!empty($limit) && !\in_array('app', $limit)) { + throw new Exception('Only the following types are allowed: ' . implode(', ', $limit) . "\n"); + } + if (!is_dir($appPath = "{$this->basePath}/apps/" . substr($w, 4))) { + throw new Exception("Application path does not exist: \"{$appPath}\"\n"); + } + if ($ensureGitClean) { + $this->ensureGitClean($appPath); + } + } elseif ($w === 'framework') { + if (!empty($limit) && !\in_array('framework', $limit)) { + throw new Exception('Only the following types are allowed: ' . implode(', ', $limit) . "\n"); + } + if (!is_dir($fwPath = "{$this->basePath}/framework")) { + throw new Exception("Framework path does not exist: \"{$this->basePath}/framework\"\n"); + } + if ($ensureGitClean) { + $this->ensureGitClean($fwPath); + } + } else { + if (!empty($limit) && !\in_array('ext', $limit)) { + throw new Exception('Only the following types are allowed: ' . implode(', ', $limit) . "\n"); + } + if (!is_dir($extPath = "{$this->basePath}/extensions/$w")) { + throw new Exception("Extension path for \"$w\" does not exist: \"{$this->basePath}/extensions/$w\"\n"); + } + if ($ensureGitClean) { + $this->ensureGitClean($extPath); + } + } + } + } + + + protected function releaseFramework($frameworkPath, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing framework release version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', \strlen($h)) . "\n\n", Console::BOLD); + + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $frameworkPath); + + // checks + + $this->stdout('check if framework composer.json matches yii2-dev composer.json...'); + $this->checkComposer($frameworkPath); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + // adjustments + + $this->stdout('prepare classmap...', Console::BOLD); + $this->dryRun || Yii::$app->runAction('classmap', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating mimetype magic file and mime aliases...', Console::BOLD); + $this->dryRun || Yii::$app->runAction('mime-type', ["$frameworkPath/helpers/mimeTypes.php"]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("fixing various PHPDoc style issues...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPDoc @property annotations...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$frameworkPath]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('sorting changelogs...', Console::BOLD); + $this->dryRun || $this->resortChangelogs(['framework'], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('closing changelogs...', Console::BOLD); + $this->dryRun || $this->closeChangelogs(['framework'], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating Yii version...'); + $this->dryRun || $this->updateYiiVersion($frameworkPath, $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit('git diff --color', $frameworkPath); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while (!$this->confirm('Type `yes` to continue, `no` to view git diff again. Continue?')); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $frameworkPath); + $this->runGit("git tag -s $version -m \"version $version\"", $frameworkPath); + $this->runGit('git push', $frameworkPath); + $this->runGit('git push --tags', $frameworkPath); + + $this->stdout("\n\n"); + $this->stdout('CONGRATULATIONS! You have just released ', Console::FG_YELLOW, Console::BOLD); + $this->stdout('framework', Console::FG_RED, Console::BOLD); + $this->stdout(' version ', Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // TODO release applications + // $this->composerSetStability($what, $version); + + + // $this->resortChangelogs($what, $version); + // $this->closeChangelogs($what, $version); + // $this->composerSetStability($what, $version); + // if (in_array('framework', $what)) { + // $this->updateYiiVersion($version); + // } + + + // if done: + // * ./build/build release/done framework 2.0.0-dev 2.0.0-rc + // * ./build/build release/done redis 2.0.0-dev 2.0.0-rc + // $this->openChangelogs($what, $nextVersion); + // $this->composerSetStability($what, 'dev'); + // if (in_array('framework', $what)) { + // $this->updateYiiVersion($devVersion); + // } + + + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout('opening changelogs...', Console::BOLD); + $nextVersion = $this->getNextVersions(['framework' => $version], self::PATCH); // TODO support other versions + $this->dryRun || $this->openChangelogs(['framework'], $nextVersion['framework']); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating Yii version...'); + $this->dryRun || $this->updateYiiVersion($frameworkPath, $nextVersion['framework'] . '-dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + + $this->stdout("\n"); + $this->runGit('git diff --color', $frameworkPath); + $this->stdout("\n\n"); + $this->runGit('git commit -a -m "prepare for next release"', $frameworkPath); + $this->runGit('git push', $frameworkPath); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- wait for your changes to be propagated to the repo and create a tag $version on https://github.com/yiisoft/yii2-framework\n\n"); + $this->stdout(" git clone git@github.com:yiisoft/yii2-framework.git\n"); + $this->stdout(" cd yii2-framework/\n"); + + $grepVersion = preg_quote($version, '~'); + $this->stdout(" export RELEASECOMMIT=$(git log --oneline |grep \"$grepVersion\" | grep -Po \"^[0-9a-f]+\")\n"); + $this->stdout(" git tag -s $version -m \"version $version\" \$RELEASECOMMIT\n"); + $this->stdout(" git tag --verify $version\n"); + $this->stdout(" git push --tags\n\n"); + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion['framework']} and {$nextVersion2['framework']}: https://github.com/yiisoft/yii2/milestones\n"); + $this->stdout("- create a release on github.\n"); + $this->stdout("- release news and announcement.\n"); + $this->stdout("- update the website (will be automated soon and is only relevant for the new website).\n"); + $this->stdout(" https://github.com/yiisoft-contrib/yiiframework.com/blob/master/config/versions.php#L69\n"); + $this->stdout("\n"); + $this->stdout("- release applications: ./build/build release app-basic\n"); + $this->stdout("- release applications: ./build/build release app-advanced\n"); + + $this->stdout("\n"); + } + + protected function releaseApplication($name, $path, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing release for application $name version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', \strlen($h)) . "\n\n", Console::BOLD); + + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); + + // adjustments + + $this->stdout("fixing various PHPDoc style issues...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPDoc @property annotations...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit('git diff --color', $path); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while (!$this->confirm('Type `yes` to continue, `no` to view git diff again. Continue?')); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $path); + $this->runGit("git tag -s $version -m \"version $version\"", $path); + $this->runGit('git push', $path); + $this->runGit('git push --tags', $path); + + $this->stdout("\n\n"); + $this->stdout('CONGRATULATIONS! You have just released application ', Console::FG_YELLOW, Console::BOLD); + $this->stdout($name, Console::FG_RED, Console::BOLD); + $this->stdout(' version ', Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], 'dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $nextVersion = $this->getNextVersions(["app-$name" => $version], self::PATCH); // TODO support other versions + + $this->stdout("\n"); + $this->runGit('git diff --color', $path); + $this->stdout("\n\n"); + $this->runGit('git commit -a -m "prepare for next release"', $path); + $this->runGit('git push', $path); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion["app-$name"]} and {$nextVersion2["app-$name"]}: https://github.com/yiisoft/yii2-app-$name/milestones\n"); + $this->stdout("- Create Application packages and upload them to framework release at github: ./build/build release/package app-$name\n"); + + $this->stdout("\n"); + } + + private $_oldAlias; + + protected function setAppAliases($app, $path) + { + $this->_oldAlias = Yii::getAlias('@app'); + switch ($app) { + case 'basic': + Yii::setAlias('@app', $path); + break; + case 'advanced': + // setup @frontend, @backend etc... + require "$path/common/config/bootstrap.php"; + break; + } + } + + protected function resetAppAliases() + { + Yii::setAlias('@app', $this->_oldAlias); + } + + protected function packageApplication($name, $version, $packagePath) + { + FileHelper::createDirectory($packagePath); + + $this->runCommand("composer create-project yiisoft/yii2-app-$name $name $version", $packagePath); + // clear cookie validation key in basic app + if (is_file($configFile = "$packagePath/$name/config/web.php")) { + $this->sed( + "/'cookieValidationKey' => '.*?',/", + "'cookieValidationKey' => '',", + $configFile + ); + } + $this->runCommand("tar zcf yii-$name-app-$version.tgz $name", $packagePath); + } + + protected function releaseExtension($name, $path, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing release for extension $name version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', \strlen($h)) . "\n\n", Console::BOLD); + + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); + + // adjustments + + $this->stdout("fixing various PHPDoc style issues...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$path]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPDoc @property annotations...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$path]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('sorting changelogs...', Console::BOLD); + $this->dryRun || $this->resortChangelogs([$name], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('closing changelogs...', Console::BOLD); + $this->dryRun || $this->closeChangelogs([$name], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit('git diff --color', $path); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while (!$this->confirm('Type `yes` to continue, `no` to view git diff again. Continue?')); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->stdout("Make sure to have your git set up for GPG signing. The following tag and commit should be signed.\n\n"); + + $this->runGit("git commit -S -a -m \"release version $version\"", $path); + $this->runGit("git tag -s $version -m \"version $version\"", $path); + $this->runGit('git push', $path); + $this->runGit('git push --tags', $path); + + $this->stdout("\n\n"); + $this->stdout('CONGRATULATIONS! You have just released extension ', Console::FG_YELLOW, Console::BOLD); + $this->stdout($name, Console::FG_RED, Console::BOLD); + $this->stdout(' version ', Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout('opening changelogs...', Console::BOLD); + $nextVersion = $this->getNextVersions([$name => $version], self::PATCH); // TODO support other versions + $this->dryRun || $this->openChangelogs([$name], $nextVersion[$name]); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\n"); + $this->runGit('git diff --color', $path); + $this->stdout("\n\n"); + $this->runGit('git commit -a -m "prepare for next release"', $path); + $this->runGit('git push', $path); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion[$name]} and {$nextVersion2[$name]}: https://github.com/yiisoft/yii2-$name/milestones\n"); + $this->stdout("- release news and announcement.\n"); + $this->stdout("- update the website (will be automated soon and is only relevant for the new website).\n"); + + $this->stdout("\n"); + } + + + protected function runCommand($cmd, $path) + { + $this->stdout("running $cmd ...", Console::BOLD); + if ($this->dryRun) { + $this->stdout("dry run, command `$cmd` not executed.\n"); + return; + } + chdir($path); + exec($cmd, $output, $ret); + if ($ret != 0) { + echo implode("\n", $output); + throw new Exception("Command \"$cmd\" failed with code " . $ret); + } + $this->stdout("\ndone.\n", Console::BOLD, Console::FG_GREEN); + } + + protected function runGit($cmd, $path) + { + if ($this->confirm("Run `$cmd`?", true)) { + if ($this->dryRun) { + $this->stdout("dry run, command `$cmd` not executed.\n"); + return; + } + chdir($path); + exec($cmd, $output, $ret); + echo implode("\n", $output); + if ($ret != 0) { + throw new Exception("Command \"$cmd\" failed with code " . $ret); + } + echo "\n"; + } + } + + protected function ensureGitClean($path) + { + chdir($path); + exec('git status --porcelain -uno', $changes, $ret); + if ($ret != 0) { + throw new Exception('Command "git status --porcelain -uno" failed with code ' . $ret); + } + if (!empty($changes)) { + throw new Exception("You have uncommitted changes in $path: " . print_r($changes, true)); + } + } + + protected function gitFetchTags($path) + { + try { + chdir($path); + } catch (\yii\base\ErrorException $e) { + throw new Exception('Failed to fetch git tags in ' . $path . ': ' . $e->getMessage()); + } + exec('git fetch --tags', $output, $ret); + if ($ret != 0) { + throw new Exception('Command "git fetch --tags" failed with code ' . $ret); + } + } + + + protected function checkComposer($fwPath) + { + if (!$this->confirm("\nNot yet automated: Please check if composer.json dependencies in framework dir match the one in repo root. Continue?", false)) { + exit; + } + } + + + protected function closeChangelogs($what, $version) + { + $v = str_replace('\\-', '[\\- ]', preg_quote($version, '/')); + $headline = $version . ' ' . date('F d, Y'); + $this->sed( + '/' . $v . ' under development\R(-+?)\R/', + $headline . "\n" . str_repeat('-', \strlen($headline)) . "\n", + $this->getChangelogs($what) + ); + } + + protected function openChangelogs($what, $version) + { + $headline = "\n$version under development\n"; + $headline .= str_repeat('-', \strlen($headline) - 2) . "\n\n- no changes in this release.\n"; + foreach ($this->getChangelogs($what) as $file) { + $lines = explode("\n", file_get_contents($file)); + $hl = [ + array_shift($lines), + array_shift($lines), + ]; + array_unshift($lines, $headline); + + file_put_contents($file, implode("\n", array_merge($hl, $lines))); + } + } + + protected function resortChangelogs($what, $version) + { + foreach ($this->getChangelogs($what) as $file) { + // split the file into relevant parts + list($start, $changelog, $end) = $this->splitChangelog($file, $version); + $changelog = $this->resortChangelog($changelog); + file_put_contents($file, implode("\n", array_merge($start, $changelog, $end))); + } + } + + /** + * Extract changelog content for a specific version. + * @param string $file + * @param string $version + * @return array + */ + protected function splitChangelog($file, $version) + { + $lines = explode("\n", file_get_contents($file)); + + // split the file into relevant parts + $start = []; + $changelog = []; + $end = []; + + $state = 'start'; + foreach ($lines as $l => $line) { + // starting from the changelogs headline + if ( + isset($lines[$l - 2]) && strpos($lines[$l - 2], $version) !== false && + isset($lines[$l - 1]) && strncmp($lines[$l - 1], '---', 3) === 0 + ) { + $state = 'changelog'; + } + if ($state === 'changelog' && isset($lines[$l + 1]) && strncmp($lines[$l + 1], '---', 3) === 0) { + $state = 'end'; + } + // add continued lines to the last item to keep them together + if (!empty(${$state}) && trim($line) !== '' && strncmp($line, '- ', 2) !== 0) { + end(${$state}); + + if (($k = key(${$state})) !== null) { + ${$state}[$k] .= "\n" . $line; + } + } else { + ${$state}[] = $line; + } + } + + return [$start, $changelog, $end]; + } + + /** + * Ensure sorting of the changelog lines. + * @param string[] $changelog + * @return string[] + */ + protected function resortChangelog($changelog) + { + // cleanup whitespace + foreach ($changelog as $i => $line) { + $changelog[$i] = rtrim($line); + } + $changelog = array_filter($changelog); + + $i = 0; + ArrayHelper::multisort($changelog, function ($line) use (&$i) { + if (preg_match('/^- (Chg|Enh|Bug|New)( #\d+(, #\d+)*)?: .+/', $line, $m)) { + $o = ['Bug' => 'C', 'Enh' => 'D', 'Chg' => 'E', 'New' => 'F']; + return $o[$m[1]] . ' ' . (!empty($m[2]) ? $m[2] : 'AAAA' . $i++); + } + + return 'B' . $i++; + }, SORT_ASC, SORT_NATURAL); + + // re-add leading and trailing lines + array_unshift($changelog, ''); + $changelog[] = ''; + $changelog[] = ''; + + return $changelog; + } + + protected function getChangelogs($what) + { + $changelogs = []; + if (\in_array('framework', $what)) { + $changelogs[] = $this->getFrameworkChangelog(); + } + + return array_merge($changelogs, $this->getExtensionChangelogs($what)); + } + + protected function getFrameworkChangelog() + { + return $this->basePath . '/framework/CHANGELOG.md'; + } + + protected function getExtensionChangelogs($what) + { + return array_filter(glob($this->basePath . '/extensions/*/CHANGELOG.md'), function ($elem) use ($what) { + foreach ($what as $ext) { + if (strpos($elem, "extensions/$ext/CHANGELOG.md") !== false) { + return true; + } + } + + return false; + }); + } + + protected function composerSetStability($what, $version) + { + $apps = []; + if (\in_array('app-advanced', $what)) { + $apps[] = $this->basePath . '/apps/advanced/composer.json'; + } + if (\in_array('app-basic', $what)) { + $apps[] = $this->basePath . '/apps/basic/composer.json'; + } + if (\in_array('app-benchmark', $what)) { + $apps[] = $this->basePath . '/apps/benchmark/composer.json'; + } + if (empty($apps)) { + return; + } + + $stability = 'stable'; + if (strpos($version, 'alpha') !== false) { + $stability = 'alpha'; + } elseif (strpos($version, 'beta') !== false) { + $stability = 'beta'; + } elseif (strpos($version, 'rc') !== false) { + $stability = 'RC'; + } elseif (strpos($version, 'dev') !== false) { + $stability = 'dev'; + } + + $this->sed( + '/"minimum-stability": "(.+?)",/', + '"minimum-stability": "' . $stability . '",', + $apps + ); + } + + protected function updateYiiVersion($frameworkPath, $version) + { + $this->sed( + '/function getVersion\(\)\R {4}\{\R {8}return \'(.+?)\';/', + "function getVersion()\n {\n return '$version';", + $frameworkPath . '/BaseYii.php' + ); + } + + protected function sed($pattern, $replace, $files) + { + foreach ((array) $files as $file) { + file_put_contents($file, preg_replace($pattern, $replace, file_get_contents($file))); + } + } + + protected function getCurrentVersions(array $what) + { + $versions = []; + foreach ($what as $ext) { + if ($ext === 'framework') { + chdir("{$this->basePath}/framework"); + } elseif (strncmp('app-', $ext, 4) === 0) { + chdir("{$this->basePath}/apps/" . substr($ext, 4)); + } else { + chdir("{$this->basePath}/extensions/$ext"); + } + $tags = []; + exec('git tag', $tags, $ret); + if ($ret != 0) { + throw new Exception('Command "git tag" failed with code ' . $ret); + } + rsort($tags, SORT_NATURAL); // TODO this can not deal with alpha/beta/rc... + + // exclude 3.0.0-alpha1 tag + if (($key = array_search('3.0.0-alpha1', $tags, true)) !== false) { + unset($tags[$key]); + } + + $versions[$ext] = reset($tags); + } + + return $versions; + } + + public const MINOR = 'minor'; + public const PATCH = 'patch'; + + protected function getNextVersions(array $versions, $type) + { + foreach ($versions as $k => $v) { + if (empty($v)) { + $versions[$k] = '2.0.0'; + continue; + } + $parts = explode('.', $v); + switch ($type) { + case self::MINOR: + $parts[1] = (int) $parts[1] + 1; + $parts[2] = 0; + if (isset($parts[3])) { + unset($parts[3]); + } + break; + case self::PATCH: + $parts[2] = (int) $parts[2] + 1; + if (isset($parts[3])) { + unset($parts[3]); + } + break; + default: + throw new Exception('Unknown version type.'); + } + $versions[$k] = implode('.', $parts); + } + + return $versions; + } +} diff --git a/build/controllers/TranslationController.php b/build/controllers/TranslationController.php new file mode 100644 index 00000000000..3bbedb1ebba --- /dev/null +++ b/build/controllers/TranslationController.php @@ -0,0 +1,143 @@ + report_guide_ru.html + * + * @author Alexander Makarov + * + * @extends Controller + */ +class TranslationController extends Controller +{ + public $defaultAction = 'report'; + + /** + * Creates a report about documentation updates since last update of same named translations. + * + * @param string $sourcePath the directory where the original documentation files are + * @param string $translationPath the directory where the translated documentation files are + * @param string $title custom title to use for report + */ + public function actionReport($sourcePath, $translationPath, $title = 'Translation report') + { + $sourcePath = trim($sourcePath, '/\\'); + $translationPath = trim($translationPath, '/\\'); + + $results = []; + + $dir = new DirectoryIterator($sourcePath); + foreach ($dir as $fileinfo) { + /** @var DirectoryIterator $fileinfo */ + if (!$fileinfo->isDot() && !$fileinfo->isDir()) { + $translatedFilePath = $translationPath . '/' . $fileinfo->getFilename(); + $sourceFilePath = $sourcePath . '/' . $fileinfo->getFilename(); + + $errors = $this->checkFiles($translatedFilePath); + $diff = empty($errors) ? $this->getDiff($translatedFilePath, $sourceFilePath) : ''; + if (!empty($diff)) { + $errors[] = 'Translation outdated.'; + } + + $result = [ + 'errors' => $errors, + 'diff' => $diff, + ]; + + $results[$fileinfo->getFilename()] = $result; + } + } + + // checking if there are obsolete translation files + $dir = new DirectoryIterator($translationPath); + foreach ($dir as $fileinfo) { + /** @var DirectoryIterator $fileinfo */ + if (!$fileinfo->isDot() && !$fileinfo->isDir()) { + $translatedFilePath = $translationPath . '/' . $fileinfo->getFilename(); + + $errors = $this->checkFiles(null, $translatedFilePath); + if (!empty($errors)) { + $results[$fileinfo->getFilename()]['errors'] = $errors; + } + } + } + + echo $this->renderFile(__DIR__ . '/views/translation/report_html.php', [ + 'results' => $results, + 'sourcePath' => $sourcePath, + 'translationPath' => $translationPath, + 'title' => $title, + ]); + } + + /** + * Checks for files existence. + * + * @param string $translatedFilePath + * @param string $sourceFilePath + * @return array errors + */ + protected function checkFiles($translatedFilePath = null, $sourceFilePath = null) + { + $errors = []; + if ($translatedFilePath !== null && !file_exists($translatedFilePath)) { + $errors[] = 'Translation does not exist.'; + } + + if ($sourceFilePath !== null && !file_exists($sourceFilePath)) { + $errors[] = 'Source does not exist.'; + } + + return $errors; + } + + /** + * Getting DIFF from git. + * + * @param string $translatedFilePath path pointing to translated file + * @param string $sourceFilePath path pointing to original file + * @return string DIFF + */ + protected function getDiff($translatedFilePath, $sourceFilePath) + { + $lastTranslationHash = shell_exec('git log -1 --format=format:"%H" -- ' . $translatedFilePath); + return shell_exec('git diff ' . $lastTranslationHash . '..HEAD -- ' . $sourceFilePath); + } + + /** + * Adds all necessary HTML tags and classes to diff output. + * + * @param string $diff DIFF + * @return string highlighted DIFF + */ + public function highlightDiff($diff) + { + $lines = explode("\n", $diff); + foreach ($lines as $key => $val) { + if (strpos($val, '@') === 0) { + $lines[$key] = '' . Html::encode($val) . ''; + } elseif (strpos($val, '+') === 0) { + $lines[$key] = '' . Html::encode($val) . ''; + } elseif (strpos($val, '-') === 0) { + $lines[$key] = '' . Html::encode($val) . ''; + } else { + $lines[$key] = Html::encode($val); + } + } + + return implode("\n", $lines); + } +} diff --git a/build/controllers/Utf8Controller.php b/build/controllers/Utf8Controller.php new file mode 100644 index 00000000000..9545ed5865c --- /dev/null +++ b/build/controllers/Utf8Controller.php @@ -0,0 +1,133 @@ + + * + * @extends Controller + */ +class Utf8Controller extends Controller +{ + public $defaultAction = 'check-guide'; + + /** + * Check guide for non-printable characters that may break docs generation. + * + * @param string $directory the directory to check. If not specified, the default + * guide directory will be checked. + */ + public function actionCheckGuide($directory = null) + { + if ($directory === null) { + $directory = \dirname(\dirname(__DIR__)) . '/docs'; + } + if (is_file($directory)) { + $files = [$directory]; + } else { + $files = FileHelper::findFiles($directory, [ + 'only' => ['*.md'], + ]); + } + + foreach ($files as $file) { + $content = file_get_contents($file); + $chars = preg_split('//u', $content, null, PREG_SPLIT_NO_EMPTY); + + $line = 1; + $pos = 0; + foreach ($chars as $c) { + $ord = $this->unicodeOrd($c); + + $pos++; + if ($ord == 0x000A) { + $line++; + $pos = 0; + } + + if ($ord === false) { + $this->found('BROKEN UTF8', $c, $line, $pos, $file); + continue; + } + + // https://unicode-table.com/en/blocks/general-punctuation/ + if ( + 0x2000 <= $ord && $ord <= 0x200F + || 0x2028 <= $ord && $ord <= 0x202E + || 0x205f <= $ord && $ord <= 0x206F + ) { + $this->found('UNSUPPORTED SPACE CHARACTER', $c, $line, $pos, $file); + continue; + } + if ( + $ord < 0x0020 && $ord != 0x000A && $ord != 0x0009 || + 0x0080 <= $ord && $ord < 0x009F + ) { + $this->found('CONTROL CHARACTER', $c, $line, $pos, $file); + continue; + } + // if ($ord > 0x009F) { + // $this->found("NON ASCII CHARACTER", $c, $line, $pos, $file); + // continue; + // } + } + } + } + + private $_foundFiles = []; + + private function found($what, $char, $line, $pos, $file) + { + if (!isset($this->_foundFiles[$file])) { + $this->stdout("$file: \n", Console::BOLD); + $this->_foundFiles[$file] = $file; + } + + $hexcode = dechex($this->unicodeOrd($char)); + $hexcode = str_repeat('0', max(4 - \strlen($hexcode), 0)) . $hexcode; + + $this->stdout(" at $line:$pos FOUND $what: 0x$hexcode '$char' https://unicode-table.com/en/$hexcode/\n"); + } + + /** + * Equivalent for ord() just for unicode. + * + * https://stackoverflow.com/questions/10333098/utf-8-safe-equivalent-of-ord-or-charcodeat-in-php/10333324#10333324 + * + * @param $c + * @return bool|int + */ + private function unicodeOrd($c) + { + $h = \ord($c[0]); + if ($h <= 0x7F) { + return $h; + } elseif ($h < 0xC2) { + return false; + } elseif ($h <= 0xDF) { + return ($h & 0x1F) << 6 | (\ord($c[1]) & 0x3F); + } elseif ($h <= 0xEF) { + return ($h & 0x0F) << 12 | (\ord($c[1]) & 0x3F) << 6 + | (\ord($c[2]) & 0x3F); + } elseif ($h <= 0xF4) { + return ($h & 0x0F) << 18 | (\ord($c[1]) & 0x3F) << 12 + | (\ord($c[2]) & 0x3F) << 6 + | (\ord($c[3]) & 0x3F); + } + + return false; + } +} diff --git a/build/controllers/views/translation/report_html.php b/build/controllers/views/translation/report_html.php new file mode 100644 index 00000000000..17d0a89cda3 --- /dev/null +++ b/build/controllers/views/translation/report_html.php @@ -0,0 +1,61 @@ + + + + + Translation report + + + + +

+ +
    +
  • Source:
  • +
  • Translation:
  • +
+ + $result): ?> +

+ +

+ + +
context->highlightDiff($result['diff']) ?>
+ + + + diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 00000000000..4428c7b15e4 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,68 @@ +Yii Contributor Code of Conduct +======================= + +## Our Pledge + +As contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to respect all community members. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Personal attacks +* Trolling or insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other conduct which could reasonably be considered inappropriate in + a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in response +to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when +an individual is representing the project or its community. Examples of representing +a project or community include posting via an official social media account, +within project GitHub, official forum or acting as an appointed representative at +an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported +by contacting core team members. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to the reporter of +an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith +may face temporary or permanent repercussions as determined by other members of +the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4.0, available at +[https://contributor-covenant.org/version/1/4/][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/composer.json b/composer.json new file mode 100644 index 00000000000..b37b13cbbe6 --- /dev/null +++ b/composer.json @@ -0,0 +1,126 @@ +{ + "name": "yiisoft/yii2-dev", + "description": "Yii PHP Framework Version 2 - Development Package", + "keywords": [ + "yii2", + "framework" + ], + "homepage": "https://www.yiiframework.com/", + "type": "yii2-extension", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "https://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "https://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "https://www.cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu/", + "role": "Core framework development" + } + ], + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "https://forum.yiiframework.com/", + "wiki": "https://www.yiiframework.com/wiki", + "irc": "ircs://irc.libera.chat:6697/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "replace": { + "yiisoft/yii2": "self.version" + }, + "require": { + "php": ">=7.4.0", + "ext-mbstring": "*", + "ext-ctype": "*", + "lib-pcre": "*", + "yiisoft/yii2-composer": "~2.0.4", + "ezyang/htmlpurifier": "^4.17", + "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", + "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": "^5.0.8 ", + "bower-asset/punycode": "^2.2", + "bower-asset/yii2-pjax": "~2.0.1" + }, + "require-dev": { + "cebe/indent": "~1.0.2", + "dealerdirect/phpcodesniffer-composer-installer": "*", + "dms/phpunit-arraysubset-asserts": "^0.5", + "phpunit/phpunit": "^9.6", + "yiisoft/yii2-coding-standards": "^3.0", + "phpstan/phpstan": "^2.1" + }, + "repositories": [ + { + "type": "composer", + "url": "https://asset-packagist.org/" + } + ], + "suggest": { + "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii" + }, + "autoload": { + "psr-4": { + "yii\\": "framework/" + } + }, + "autoload-dev": { + "psr-4": { + "yii\\build\\": "build/", + "yiiunit\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "cweagans/composer-patches": true, + "yiisoft/yii2-composer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "bin": [ + "framework/yii" + ], + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000000..e9658077f64 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2500 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0a2fa42581d3a5d78f1afefe5173bc2a", + "packages": [ + { + "name": "bower-asset/inputmask", + "version": "5.0.9", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/Inputmask.git", + "reference": "310a33557e2944daf86d5946a5e8c82b9118f8f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/310a33557e2944daf86d5946a5e8c82b9118f8f7", + "reference": "310a33557e2944daf86d5946a5e8c82b9118f8f7" + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset", + "license": [ + "http://opensource.org/licenses/mit-license.php" + ] + }, + { + "name": "bower-asset/jquery", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "fde1f76e2799dd877c176abde0ec836553246991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/fde1f76e2799dd877c176abde0ec836553246991", + "reference": "fde1f76e2799dd877c176abde0ec836553246991" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/punycode", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/mathiasbynens/punycode.js.git", + "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mathiasbynens/punycode.js/zipball/9e1b2cda98d215d3a73fcbfe93c62e021f4ba768", + "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/yii2-pjax", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/jquery-pjax.git", + "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/a9298d57da63d14a950f1b94366a864bc62264fb", + "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "cebe/markdown", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86", + "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "4.1.*" + }, + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ], + "support": { + "issues": "https://github.com/cebe/markdown/issues", + "source": "https://github.com/cebe/markdown" + }, + "time": "2018-03-26T11:24:36+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com/" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + }, + "time": "2024-11-01T03:51:45+00:00" + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.11", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "b684b01ecb119c8287721def726a0e24fec2fef2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/b684b01ecb119c8287721def726a0e24fec2fef2", + "reference": "b684b01ecb119c8287721def726a0e24fec2fef2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 | ^2.0" + }, + "require-dev": { + "composer/composer": "^1.0 | ^2.0@dev", + "phpunit/phpunit": "<7" + }, + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ], + "support": { + "forum": "https://www.yiiframework.com/forum/", + "irc": "ircs://irc.libera.chat:6697/yii", + "issues": "https://github.com/yiisoft/yii2-composer/issues", + "source": "https://github.com/yiisoft/yii2-composer", + "wiki": "https://www.yiiframework.com/wiki/" + }, + "funding": [ + { + "url": "https://github.com/yiisoft", + "type": "github" + }, + { + "url": "https://opencollective.com/yiisoft", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer", + "type": "tidelift" + } + ], + "time": "2025-02-13T20:59:36+00:00" + } + ], + "packages-dev": [ + { + "name": "cebe/indent", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/cebe/indent.git", + "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/indent/zipball/c500ed74d30ed2d7e085f9cf07f8092d32d70776", + "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776", + "shasum": "" + }, + "bin": [ + "indent" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + } + ], + "description": "a small tool to convert text file indentation", + "support": { + "issues": "https://github.com/cebe/indent/issues", + "source": "https://github.com/cebe/indent/tree/master" + }, + "time": "2014-05-23T14:40:08+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev/", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-07-17T20:45:56+00:00" + }, + { + "name": "dms/phpunit-arraysubset-asserts", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/rdohms/phpunit-arraysubset-asserts.git", + "reference": "aa6b9e858414e91cca361cac3b2035ee57d212e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rdohms/phpunit-arraysubset-asserts/zipball/aa6b9e858414e91cca361cac3b2035ee57d212e0", + "reference": "aa6b9e858414e91cca361cac3b2035ee57d212e0", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + }, + "require-dev": { + "dms/coding-standard": "^9" + }, + "type": "library", + "autoload": { + "files": [ + "assertarraysubset-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rafael Dohms", + "email": "rdohms@gmail.com" + } + ], + "description": "This package provides ArraySubset and related asserts once deprecated in PHPUnit 8", + "support": { + "issues": "https://github.com/rdohms/phpunit-arraysubset-asserts/issues", + "source": "https://github.com/rdohms/phpunit-arraysubset-asserts/tree/v0.5.0" + }, + "time": "2023-06-02T17:33:53+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + }, + "time": "2025-08-13T20:13:15+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.32", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-11-11T15:18:17+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.29", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.9", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:29:11+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:51:50+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:03:27+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:57:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-17T22:17:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "yiisoft/yii2-coding-standards", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-coding-standards.git", + "reference": "842ffdf6c31f46bb6f4b3f3c7dda4f570321ace7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-coding-standards/zipball/842ffdf6c31f46bb6f4b3f3c7dda4f570321ace7", + "reference": "842ffdf6c31f46bb6f4b3f3c7dda4f570321ace7", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": ">=3.2" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "yii\\console\\controllers\\": "src/console/controllers/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "https://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "https://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "https://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "https://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "https://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2 - Coding standard tools", + "homepage": "https://www.yiiframework.com/", + "keywords": [ + "codesniffer", + "framework", + "yii" + ], + "support": { + "forum": "https://www.yiiframework.com/forum/", + "irc": "ircs://irc.libera.chat:6697/yii", + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "source": "https://github.com/yiisoft/yii2", + "wiki": "https://www.yiiframework.com/wiki/" + }, + "funding": [ + { + "url": "https://opencollective.com/yiisoft", + "type": "open_collective" + } + ], + "time": "2024-06-12T13:50:40+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "bower-asset/jquery": 0 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.4.0", + "ext-mbstring": "*", + "ext-ctype": "*", + "lib-pcre": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/contrib/completion/bash/yii b/contrib/completion/bash/yii new file mode 100644 index 00000000000..12e50f759f0 --- /dev/null +++ b/contrib/completion/bash/yii @@ -0,0 +1,60 @@ +# This file implements bash completion for the ./yii command file. +# It completes the commands available by the ./yii command. +# See also: +# - https://debian-administration.org/article/317/An_introduction_to_bash_completion_part_2 on how this works. +# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html +# - https://www.yiiframework.com/doc-2.0/guide-tutorial-console.html#bash-completion +# +# Usage: +# Temporarily you can source this file in you bash by typing: source yii +# For permanent availability, copy or link this file to /etc/bash_completion.d/ +# + +_yii() +{ + local cur opts yii command + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + yii="${COMP_WORDS[0]}" + + # exit if ./yii does not exist + test -f $yii || return 0 + + # lookup for command + for word in ${COMP_WORDS[@]:1}; do + if [[ $word != -* ]]; then + command=$word + break + fi + done + + [[ $cur == $command ]] && state="command" + [[ $cur != $command ]] && state="option" + [[ $cur = *=* ]] && state="value" + [[ $prev == "help" ]] && state="help" + + case $state in + command|help) + # complete command/route if not given + # fetch available commands from ./yii help/list command + opts=$($yii help/list 2> /dev/null) + ;; + option) + # fetch available options from ./yii help/list-action-options command + opts=$($yii help/list-action-options $command 2> /dev/null | grep -o '^--[a-zA-Z0-9\-]*') + ;; + value) + # TODO allow normal file completion after an option, e.g. --migrationPath=... + ;; + esac + + # generate completion suggestions + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + +} + +# register completion for the ./yii command +# you may adjust this line if your command file is named differently +complete -o default -F _yii ./yii yii diff --git a/contrib/completion/zsh/_yii b/contrib/completion/zsh/_yii new file mode 100644 index 00000000000..01f4765a2dc --- /dev/null +++ b/contrib/completion/zsh/_yii @@ -0,0 +1,40 @@ +#compdef yii + +_yii() { + local state command lastArgument commands options executive + lastArgument=${words[${#words[@]}]} + prevArgument=${words[${#words[@]}-1]} + executive=$words[1] + + # lookup for command + for word in ${words[@]:1}; do + if [[ $word != -* ]]; then + command=$word + break + fi + done + + + [[ $lastArgument == $command ]] && state="command" + [[ $lastArgument != $command ]] && state="option" + [[ $prevArgument == "help" ]] && state="help" + + case $state in + command|help) + commands=("${(@f)$(${executive} help/list 2>/dev/null)}") + _describe 'command' commands + ;; + option) + options=("${(@f)$(${executive} help/usage ${command} 2>/dev/null)}") + _message -r "$options" + + suboptions=("${(@f)$(${executive} help/list-action-options ${command} 2>/dev/null)}") + _describe -V -o -t suboption 'action options' suboptions + ;; + *) + esac + +} + +compdef _yii yii + diff --git a/docs/api/base/Component.md b/docs/api/base/Component.md deleted file mode 100644 index 01ef462ce59..00000000000 --- a/docs/api/base/Component.md +++ /dev/null @@ -1,80 +0,0 @@ -Component is the base class that implements the *property*, *event* and *behavior* features. - -Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in -its parent class [[Object]]. - -Event is a way to "inject" custom code into existing code at certain places. For example, a comment object can trigger -an "add" event when the user adds a comment. We can write custom code and attach it to this event so that when the event -is triggered (i.e. comment will be added), our custom code will be executed. - -An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*. - -One or multiple PHP callbacks, called *event handlers*, can be attached to an event. You can call [[trigger()]] to -raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were -attached. - -To attach an event handler to an event, call [[on()]]: - -~~~ -$post->on('update', function($event) { - // send email notification -}); -~~~ - -In the above, an anonymous function is attached to the "update" event of the post. You may attach -the following types of event handlers: - -- anonymous function: `function($event) { ... }` -- object method: `array($object, 'handleAdd')` -- static class method: `array('Page', 'handleAdd')` -- global function: `'handleAdd'` - -The signature of an event handler should be like the following: - -~~~ -function foo($event) -~~~ - -where `$event` is an [[Event]] object which includes parameters associated with the event. - -You can also attach a handler to an event when configuring a component with a configuration array. -The syntax is like the following: - -~~~ -array( - 'on add' => function($event) { ... } -) -~~~ - -where `on add` stands for attaching an event to the `add` event. - -Sometimes, you may want to associate extra data with an event handler when you attach it to an event -and then access it when the handler is invoked. You may do so by - -~~~ -$post->on('update', function($event) { - // the data can be accessed via $event->data -}, $data); -~~~ - - -A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple -behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the -component directly, as if the component owns those properties and methods. - -To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]]. Behaviors -declared in [[behaviors()]] are automatically attached to the corresponding component. - -One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the -following: - -~~~ -array( - 'as tree' => array( - 'class' => 'Tree', - ), -) -~~~ - -where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]] -to create the behavior object. \ No newline at end of file diff --git a/docs/api/base/Object.md b/docs/api/base/Object.md deleted file mode 100644 index 1b9fca095eb..00000000000 --- a/docs/api/base/Object.md +++ /dev/null @@ -1,61 +0,0 @@ -Object is the base class that implements the *property* feature. - -A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example, -the following getter and setter methods define a property named `label`: - -~~~ -private $_label; - -public function getLabel() -{ - return $this->_label; -} - -public function setLabel($value) -{ - $this->_label = $value; -} -~~~ - -Property names are *case-insensitive*. - -A property can be accessed like a member variable of an object. Reading or writing a property will cause the invocation -of the corresponding getter or setter method. For example, - -~~~ -// equivalent to $label = $object->getLabel(); -$label = $object->label; -// equivalent to $object->setLabel('abc'); -$object->label = 'abc'; -~~~ - -If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying -to modify the property value will cause an exception. - -One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property. - - -Besides the property feature, Object also introduces an important object initialization life cycle. In particular, -creating an new instance of Object or its derived class will involve the following life cycles sequentially: - -1. the class constructor is invoked; -2. object properties are initialized according to the given configuration; -3. the `init()` method is invoked. - -In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that -you perform object initialization in the `init()` method because at that stage, the object configuration -is already applied. - -In order to ensure the above life cycles, if a child class of Object needs to override the constructor, -it should be done like the following: - -~~~ -public function __construct($param1, $param2, ..., $config = array()) -{ - ... - parent::__construct($config); -} -~~~ - -That is, a `$config` parameter (defaults to `array()`) should be declared as the last parameter -of the constructor, and the parent implementation should be called at the end of the constructor. diff --git a/docs/api/db/ActiveRecord-find.md b/docs/api/db/ActiveRecord-find.md deleted file mode 100644 index 42940b62f3b..00000000000 --- a/docs/api/db/ActiveRecord-find.md +++ /dev/null @@ -1,26 +0,0 @@ -The returned [[ActiveQuery]] instance can be further customized by calling -methods defined in [[ActiveQuery]] before `one()`, `all()` or `value()` is -called to return the populated active records: - -~~~ -// find all customers -$customers = Customer::find()->all(); - -// find all active customers and order them by their age: -$customers = Customer::find() - ->where(array('status' => 1)) - ->orderBy('age') - ->all(); - -// find a single customer whose primary key value is 10 -$customer = Customer::find(10); - -// the above is equivalent to: -$customer = Customer::find()->where(array('id' => 10))->one(); - -// find a single customer whose age is 30 and whose status is 1 -$customer = Customer::find(array('age' => 30, 'status' => 1)); - -// the above is equivalent to: -$customer = Customer::find()->where(array('age' => 30, 'status' => 1))->one(); -~~~ \ No newline at end of file diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md deleted file mode 100644 index d8bedb45bec..00000000000 --- a/docs/api/db/ActiveRecord.md +++ /dev/null @@ -1,452 +0,0 @@ -ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). -The idea is that an ActiveRecord object is associated with a row in a database table -so object properties are mapped to colums of the corresponding database row. -For example, a `Customer` object is associated with a row in the `tbl_customer` -table. Instead of writing raw SQL statements to access the data in the table, -you can call intuitive methods available in the corresponding ActiveRecord class -to achieve the same goals. For example, calling [[save()]] would insert or update a row -in the underlying table: - -~~~ -$customer = new Customer(); -$customer->name = 'Qiang'; -$customer->save(); -~~~ - - -### Declaring ActiveRecord Classes - -To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and -implement `tableName` method like the following: - -~~~ -class Customer extends \yii\db\ActiveRecord -{ - /** - * @return string the name of the table associated with this ActiveRecord class. - */ - public static function tableName() - { - return 'tbl_customer'; - } -} -~~~ - -### Connecting to Database - -ActiveRecord relies on a [[Connection|DB connection]]. By default, it assumes that -there is an application component named `db` that gives the needed [[Connection]] -instance which serves as the DB connection. Usually this component is configured -via application configuration like the following: - -~~~ -return array( - 'components' => array( - 'db' => array( - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=testdb', - 'username' => 'demo', - 'password' => 'demo', - // turn on schema caching to improve performance - // 'schemaCacheDuration' => 3600, - ), - ), -); -~~~ - - -### Getting Data from Database - -There are two ActiveRecord methods for getting data: - -- [[find()]] -- [[findBySql()]] - -They both return an [[ActiveQuery]] instance. Coupled with the various customization and query methods -provided by [[ActiveQuery]], ActiveRecord supports very flexible and powerful data retrieval approaches. - -The followings are some examples, - -~~~ -// to retrieve all *active* customers and order them by their ID: -$customers = Customer::find() - ->where(array('status' => $active)) - ->orderBy('id') - ->all(); - -// to return a single customer whose ID is 1: -$customer = Customer::find() - ->where(array('id' => 1)) - ->one(); - -// or use the following shortcut approach: -$customer = Customer::find(1); - -// to retrieve customers using a raw SQL statement: -$sql = 'SELECT * FROM tbl_customer'; -$customers = Customer::findBySql($sql)->all(); - -// to return the number of *active* customers: -$count = Customer::find() - ->where(array('status' => $active)) - ->count(); - -// to return customers in terms of arrays rather than `Customer` objects: -$customers = Customer::find()->asArray()->all(); -// each $customers element is an array of name-value pairs - -// to index the result by customer IDs: -$customers = Customer::find()->indexBy('id')->all(); -// $customers array is indexed by customer IDs -~~~ - - -### Accessing Column Data - -ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord -object. An attribute is like a regular object property whose name is the same as the corresponding column -name and is case sensitive. - -To read the value of a column, we can use the following expression: - -~~~ -// "id" is the name of a column in the table associated with $customer ActiveRecord object -$id = $customer->id; -// or alternatively, -$id = $customer->getAttribute('id'); -~~~ - -We can get all column values through the [[attributes]] property: - -~~~ -$values = $customer->attributes; -~~~ - - -### Persisting Data to Database - -ActiveRecord provides the following methods to insert, update and delete data: - -- [[save()]] -- [[insert()]] -- [[update()]] -- [[delete()]] -- [[updateCounters()]] -- [[updateAll()]] -- [[updateAllCounters()]] -- [[deleteAll()]] - -Note that [[updateAll()]], [[updateAllCounters()]] and [[deleteAll()]] apply to the whole database -table, while the rest of the methods only apply to the row associated with the ActiveRecord object. - -The followings are some examples: - -~~~ -// to insert a new customer record -$customer = new Customer; -$customer->name = 'James'; -$customer->email = 'james@example.com'; -$customer->save(); // equivalent to $customer->insert(); - -// to update an existing customer record -$customer = Customer::find($id); -$customer->email = 'james@example.com'; -$customer->save(); // equivalent to $customer->update(); - -// to delete an existing customer record -$customer = Customer::find($id); -$customer->delete(); - -// to increment the age of all customers by 1 -Customer::updateAllCounters(array('age' => 1)); -~~~ - - -### Getting Relational Data - -Using ActiveRecord you can expose relationships as properties. For example, -with an appropriate declaration, `$customer->orders` can return an array of `Order` objects -which represent the orders placed by the specified customer. - -To declare a relationship, define a getter method which returns an [[ActiveRelation]] object. For example, - -~~~ -class Customer extends \yii\db\ActiveRecord -{ - public function getOrders() - { - return $this->hasMany('Order', array('customer_id' => 'id')); - } -} - -class Order extends \yii\db\ActiveRecord -{ - public function getCustomer() - { - return $this->hasOne('Customer', array('id' => 'customer_id')); - } -} -~~~ - -Within the getter methods above, we call [[hasMany()]] or [[hasOne()]] methods to -create a new [[ActiveRelation]] object. The [[hasMany()]] method declares -a one-many relationship. For example, a customer has many orders. And the [[hasOne()]] -method declares a many-one or one-one relationship. For example, an order has one customer. -Both methods take two parameters: - -- `$class`: the name of the class related models should use. If specified without - a namespace, the namespace will be taken from the declaring class. -- `$link`: the association between columns from two tables. This should be given as an array. - The keys of the array are the names of the columns from the table associated with `$class`, - while the values of the array are the names of the columns from the declaring class. - It is a good practice to define relationships based on table foreign keys. - -After declaring relationships getting relational data is as easy as accessing -a component property that is defined by the getter method: - -~~~ -// the orders of a customer -$customer = Customer::find($id); -$orders = $customer->orders; // $orders is an array of Order objects - -// the customer of the first order -$customer2 = $orders[0]->customer; // $customer == $customer2 -~~~ - -Because [[ActiveRelation]] extends from [[ActiveQuery]], it has the same query building methods, -which allows us to customize the query for retrieving the related objects. -For example, we may declare a `bigOrders` relationship which returns orders whose -subtotal exceeds certain amount: - -~~~ -class Customer extends \yii\db\ActiveRecord -{ - public function getBigOrders($threshold = 100) - { - return $this->hasMany('Order', array('customer_id' => 'id')) - ->where('subtotal > :threshold', array(':threshold' => $threshold)) - ->orderBy('id'); - } -} -~~~ - - -Sometimes, two tables are related together via an intermediary table called -[pivot table](http://en.wikipedia.org/wiki/Pivot_table). To declare such relationships, we can customize -the [[ActiveRelation]] object by calling its [[ActiveRelation::via()]] or [[ActiveRelation::viaTable()]] -method. - -For example, if table `tbl_order` and table `tbl_item` are related via pivot table `tbl_order_item`, -we can declare the `items` relation in the `Order` class like the following: - -~~~ -class Order extends \yii\db\ActiveRecord -{ - public function getItems() - { - return $this->hasMany('Item', array('id' => 'item_id')) - ->viaTable('tbl_order_item', array('order_id' => 'id')); - } -} -~~~ - -[[ActiveRelation::via()]] method is similar to [[ActiveRelation::viaTable()]] except that -the first parameter of [[ActiveRelation::via()]] takes a relation name declared in the ActiveRecord class. -For example, the above `items` relation can be equivalently declared as follows: - -~~~ -class Order extends \yii\db\ActiveRecord -{ - public function getOrderItems() - { - return $this->hasMany('OrderItem', array('order_id' => 'id')); - } - - public function getItems() - { - return $this->hasMany('Item', array('id' => 'item_id')) - ->via('orderItems'); - } -} -~~~ - - -When you access the related objects the first time, behind the scene ActiveRecord performs a DB query -to retrieve the corresponding data and populate it into the related objects. No query will be performed -if you access the same related objects again. We call this *lazy loading*. For example, - -~~~ -// SQL executed: SELECT * FROM tbl_customer WHERE id=1 -$customer = Customer::find(1); -// SQL executed: SELECT * FROM tbl_order WHERE customer_id=1 -$orders = $customer->orders; -// no SQL executed -$orders2 = $customer->orders; -~~~ - - -Lazy loading is very convenient to use. However, it may suffer from performance -issue in the following scenario: - -~~~ -// SQL executed: SELECT * FROM tbl_customer LIMIT 100 -$customers = Customer::find()->limit(100)->all(); - -foreach ($customers as $customer) { - // SQL executed: SELECT * FROM tbl_order WHERE customer_id=... - $orders = $customer->orders; - // ...handle $orders... -} -~~~ - -How many SQL queries will be performed in the above code, assuming there are more than 100 customers in -the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query -is performed to bring back the customer's orders. - -To solve the above performance problem, you can use the so-called *eager loading* by calling [[ActiveQuery::with()]]: - -~~~ -// SQL executed: SELECT * FROM tbl_customer LIMIT 100 -// SELECT * FROM tbl_orders WHERE customer_id IN (1,2,...) -$customers = Customer::find()->limit(100) - ->with('orders')->all(); - -foreach ($customers as $customer) { - // no SQL executed - $orders = $customer->orders; - // ...handle $orders... -} -~~~ - -As you can see, only two SQL queries are needed for the same task. - - -Sometimes, you may want to customize the relational queries on the fly. It can be -done for both lazy loading and eager loading. For example, - -~~~ -$customer = Customer::find(1); -// lazy loading: SELECT * FROM tbl_order WHERE customer_id=1 AND subtotal>100 -$orders = $customer->getOrders()->where('subtotal>100')->all(); - -// eager loading: SELECT * FROM tbl_customer LIMIT 10 - SELECT * FROM tbl_order WHERE customer_id IN (1,2,...) AND subtotal>100 -$customers = Customer::find()->limit(100)->with(array( - 'orders' => function($query) { - $query->andWhere('subtotal>100'); - }, -))->all(); -~~~ - - -### Working with Relationships - -ActiveRecord provides the following two methods for establishing and breaking a -relationship between two ActiveRecord objects: - -- [[link()]] -- [[unlink()]] - -For example, given a customer and a new order, we can use the following code to make the -order owned by the customer: - -~~~ -$customer = Customer::find(1); -$order = new Order; -$order->subtotal = 100; -$customer->link('orders', $order); -~~~ - -The [[link()]] call above will set the `customer_id` of the order to be the primary key -value of `$customer` and then call [[save()]] to save the order into database. - - -### Data Input and Validation - -TBD - - -### Life Cycles of an ActiveRecord Object - -An ActiveRecord object undergoes different life cycles when it is used in different cases. -Subclasses or ActiveRecord behaviors may "inject" custom code in these life cycles through -method overriding and event handling mechanisms. - -When instantiating a new ActiveRecord instance, we will have the following life cycles: - -1. constructor -2. [[init()]]: will trigger an [[EVENT_INIT]] event - -When getting an ActiveRecord instance through the [[find()]] method, we will have the following life cycles: - -1. constructor -2. [[init()]]: will trigger an [[EVENT_INIT]] event -3. [[afterFind()]]: will trigger an [[EVENT_AFTER_FIND]] event - -When calling [[save()]] to insert or update an ActiveRecord, we will have the following life cycles: - -1. [[beforeValidate()]]: will trigger an [[EVENT_BEFORE_VALIDATE]] event -2. [[afterValidate()]]: will trigger an [[EVENT_AFTER_VALIDATE]] event -3. [[beforeSave()]]: will trigger an [[EVENT_BEFORE_INSERT]] or [[EVENT_BEFORE_UPDATE]] event -4. perform the actual data insertion or updating -5. [[afterSave()]]: will trigger an [[EVENT_AFTER_INSERT]] or [[EVENT_AFTER_UPDATE]] event - -Finally when calling [[delete()]] to delete an ActiveRecord, we will have the following life cycles: - -1. [[beforeDelete()]]: will trigger an [[EVENT_BEFORE_DELETE]] event -2. perform the actual data deletion -3. [[afterDelete()]]: will trigger an [[EVENT_AFTER_DELETE]] event - - -### Scopes - -A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are defined -in the ActiveRecord classes. They can be invoked through the [[ActiveQuery]] object that is created -via [[find()]] or [[findBySql()]]. The following is an example: - -~~~ -class Customer extends \yii\db\ActiveRecord -{ - // ... - - /** - * @param ActiveQuery $query - */ - public static function active($query) - { - $query->andWhere('status = 1'); - } -} - -$customers = Customer::find()->active()->all(); -~~~ - -In the above, the `active()` method is defined in `Customer` while we are calling it -through `ActiveQuery` returned by `Customer::find()`. - -Scopes can be parameterized. For example, we can define and use the following `olderThan` scope: - -~~~ -class Customer extends \yii\db\ActiveRecord -{ - // ... - - /** - * @param ActiveQuery $query - * @param integer $age - */ - public static function olderThan($query, $age = 30) - { - $query->andWhere('age > :age', array(':age' => $age)); - } -} - -$customers = Customer::find()->olderThan(50)->all(); -~~~ - -The parameters should follow after the `$query` parameter when defining the scope method, and they -can take default values like shown above. - -### Atomic operations and scenarios - -TBD diff --git a/docs/autoloader.md b/docs/autoloader.md deleted file mode 100644 index b7696d7d1cc..00000000000 --- a/docs/autoloader.md +++ /dev/null @@ -1,19 +0,0 @@ -Yii2 class loader -================= - -Yii 2 class loader is PSR-0 compliant. That means it can handle most of the PHP -libraries and frameworks out there. - -In order to autoload a library you need to set a root alias for it. - -PEAR-style libraries --------------------- - -```php -\Yii::setAlias('@Twig', '@app/vendors/Twig'); -``` - -References ----------- - -- YiiBase::autoload \ No newline at end of file diff --git a/docs/documentation_style_guide.md b/docs/documentation_style_guide.md new file mode 100644 index 00000000000..af7ae34b827 --- /dev/null +++ b/docs/documentation_style_guide.md @@ -0,0 +1,87 @@ +# Yii Documentation Style Guide + +Guidelines to go by when writing or editing any Yii documentation. + +*This needs to be expanded.* + +## General Style + +* Try to use an active voice. +* Use short, declarative sentences. +* Demonstrate ideas using code as much as possible. +* Never use "we". It's the Yii development team or the Yii core team. Better yet to put things in terms of the framework or the guide. +* Use the Oxford comma (e.g., "this, that, and the other" not "this, that and the other"). + +## Formatting + +* Use *italics* for emphasis, never capitalization, bold, or underlines. + +## Lists + +* Numeric lists should be complete sentences that end with periods. +* Bullet lists should be fragments that end with semicolon except the last item, which should end with a period. + +## Blocks + +Blocks use the Markdown `> Type: `. There are four block types: + +* `Warning`, for bad security things and other problems +* `Note`, to emphasize key concepts, things to avoid +* `Info`, general information (an aside); not as strong as a "Note" +* `Tip`, pro tips, extras, can be useful but may not be needed by everyone all the time + +The sentence after the colon should begin with a capital letter. + +When translating documentation, these Block indicators should not be translated. +Keeps them intact as they are and only translate the block content. +For translating the `Type` word, each guide translation should have a `blocktypes.json` file +containing the translations. The following shows an example for German: + +```json +{ + "Warning:": "Achtung:", + "Note:": "Hinweis:", + "Info:": "Info:", + "Tip:": "Tipp:" +} +``` + +## References + +* Yii 2.0 or Yii 2 (not Yii2 or Yii2.0) +* Each "page" of the guide is referred to as a "section". +* References to Code objects: + - Refer to classes using the full namespace: `yii\base\Model` + - Refer to class properties using the static syntax even if they are not static: `yii\base\Model::$validators` + - Refer to class methods using the static syntax even if they are not static and include parenthesis to make it clear, that it is a method: `yii\base\Model::validate()` + - references to code objects should be writting in `[[]]` to generate links to the API documentation. E.g. `[[yii\base\Model]]`, `[[yii\base\Model::$validators]]`, or `[[yii\base\Model::validate()]]`. + +## Capitalizations + +* Web, not web +* the guide or this guide, not the Guide + +## Validating the docs + +The following are some scripts that help find broken links and other issues in the guide: + +Find broken links (some false-positives may occur): + + grep -rniP "\[\[[^\],']+?\][^\]]" docs/guide* + grep -rniP "[^\[]\[[^\]\[,']+?\]\]" docs/guide* + +## Attribution of Translators + +The names of the translators will be listed among the guide authors in the +rendered versions of the guide. +Therefor in each guide directory for a different language than english a `translators.json` file +should be created that contains an array of names of the people who have participated in the translation. + +```json +[ + "Jane Doe", + "John Doe" +] +``` + +If you have contributed a significant part to the translation, feel free to send a pull request adding your name. diff --git a/docs/guide-ar/README.md b/docs/guide-ar/README.md new file mode 100644 index 00000000000..2134d2b9421 --- /dev/null +++ b/docs/guide-ar/README.md @@ -0,0 +1,205 @@ + Yii 2.0 الدليل التقني الخاص ببيئة العمل +=============================== + +تم تحرير هذا الملف اعتمادا على [الشروط الخاصة بتوثيف ال Yii](https://www.yiiframework.com/doc/terms/). + +جميع الحقوق محفوظة + +2014 (c) Yii Software LLC. + + +المقدمة +------------ + +* [عن بيئة العمل Yii](intro-yii.md) +* [التحديث من الإصدار 1.1](../guide/intro-upgrade-from-v1.md) + + +البداية من هنا +--------------- + +* [ماذا يجب أن تعرف عن بيئة العمل](start-prerequisites.md) +* [تثبيت ال Yii](start-installation.md) +* [تشغيل التطبيقات - Running Applications](start-workflow.md) +* [قل مرحبا - المشروع الأول](start-hello.md) +* [التعامل مع ال forms](start-forms.md) +* [التعامل مع قواعد البيانات](start-databases.md) +* [إنشاء الشيفرة البرمجية من خلال ال gii](start-gii.md) +* [ماذا الآن - الخطوة القادمة](start-looking-ahead.md) + + +الهيكلية الخاصة بالتطبيق (Application Structure) +--------------------- + +* [نظرة عامة عن الهيكلية الخاصة بالتطبيق](../guide/structure-overview.md) +* [Entry Scripts](../guide/structure-entry-scripts.md) +* [التطبيقات](../guide/structure-applications.md) +* [مكونات التطبيقات](../guide/structure-application-components.md) +* [Controllers](../guide/structure-controllers.md) +* [Models](../guide/structure-models.md) +* [Views](../guide/structure-views.md) +* [Modules](../guide/structure-modules.md) +* [Filters](../guide/structure-filters.md) +* [Widgets](../guide/structure-widgets.md) +* [Assets](../guide/structure-assets.md) +* [Extensions](../guide/structure-extensions.md) + + +التعامل مع ال requests +----------------- + +* [نظرة عامة عن التعامل مع ال requests](../guide/runtime-overview.md) +* [Bootstrapping](../guide/runtime-bootstrapping.md) +* [Routing and URL Creation](../guide/runtime-routing.md) +* [Requests](../guide/runtime-requests.md) +* [Responses](../guide/runtime-responses.md) +* [Sessions and Cookies](../guide/runtime-sessions-cookies.md) +* [Handling Errors - التحكم بالأخطاء](../guide/runtime-handling-errors.md) +* [Logging - تسجيل الحركات](../guide/runtime-logging.md) + + +المفاهيم الرئيسية (Key Concepts) +------------ + +* [Components](../guide/concept-components.md) +* [Properties](../guide/concept-properties.md) +* [Events](../guide/concept-events.md) +* [Behaviors](../guide/concept-behaviors.md) +* [Configurations](../guide/concept-configurations.md) +* [Aliases](../guide/concept-aliases.md) +* [Class Autoloading](../guide/concept-autoloading.md) +* [Service Locator](../guide/concept-service-locator.md) +* [Dependency Injection Container](../guide/concept-di-container.md) + + +التعامل مع قواعد البيانات +---------------------- + +* [Database Access Objects](../guide/db-dao.md): Connecting to a database, basic queries, transactions, and schema manipulation +* [Query Builder](../guide/db-query-builder.md): Querying the database using a simple abstraction layer +* [Active Record](../guide/db-active-record.md): The Active Record ORM, retrieving and manipulating records, and defining relations +* [Migrations](../guide/db-migrations.md): Apply version control to your databases in a team development environment +* [Sphinx](https://www.yiiframework.com/extension/yiisoft/yii2-sphinx/doc/guide) +* [Redis](https://www.yiiframework.com/extension/yiisoft/yii2-redis/doc/guide) +* [MongoDB](https://www.yiiframework.com/extension/yiisoft/yii2-mongodb/doc/guide) +* [ElasticSearch](https://www.yiiframework.com/extension/yiisoft/yii2-elasticsearch/doc/guide) + + +الحصول على البيانات من خلال المستخدمين +----------------------- + +* [Creating Forms](../guide/input-forms.md) +* [Validating Input](../guide/input-validation.md) +* [Uploading Files](../guide/input-file-upload.md) +* [Collecting Tabular Input](../guide/input-tabular-input.md) +* [Getting Data for Multiple Models](../guide/input-multiple-models.md) +* [Extending ActiveForm on the Client Side](../guide/input-form-javascript.md) + + +عرض البيانات +--------------- + +* [Data Formatting](../guide/output-formatting.md) +* [Pagination](../guide/output-pagination.md) +* [Sorting](../guide/output-sorting.md) +* [Data Providers](../guide/output-data-providers.md) +* [Data Widgets](../guide/output-data-widgets.md) +* [Working with Client Scripts](../guide/output-client-scripts.md) +* [Theming](../guide/output-theming.md) + + +الامان والحماية +-------- + +* [Security Overview](../guide/security-overview.md) +* [Authentication](../guide/security-authentication.md) +* [Authorization](../guide/security-authorization.md) +* [Working with Passwords](../guide/security-passwords.md) +* [Cryptography](../guide/security-cryptography.md) +* [Auth Clients](https://www.yiiframework.com/extension/yiisoft/yii2-authclient/doc/guide) +* [Best Practices](../guide/security-best-practices.md) + + +Caching التخزين المؤقت +------- + +* [Caching Overview](../guide/caching-overview.md) +* [Data Caching](../guide/caching-data.md) +* [Fragment Caching](../guide/caching-fragment.md) +* [Page Caching](../guide/caching-page.md) +* [HTTP Caching](../guide/caching-http.md) + + +RESTful Web Services +-------------------- + +* [Quick Start](../guide/rest-quick-start.md) +* [Resources](../guide/rest-resources.md) +* [Controllers](../guide/rest-controllers.md) +* [Routing](../guide/rest-routing.md) +* [Response Formatting](../guide/rest-response-formatting.md) +* [Authentication](../guide/rest-authentication.md) +* [Rate Limiting](../guide/rest-rate-limiting.md) +* [Versioning](../guide/rest-versioning.md) +* [Error Handling](../guide/rest-error-handling.md) + + +الأدوات المساعدة أثناء تطوير التطبيقات +----------------- + +* [Debug Toolbar and Debugger](https://www.yiiframework.com/extension/yiisoft/yii2-debug/doc/guide) +* [Generating Code using Gii](https://www.yiiframework.com/extension/yiisoft/yii2-gii/doc/guide) +* [Generating API Documentation](https://www.yiiframework.com/extension/yiisoft/yii2-apidoc) + + +فحص واختبار التطبيقات +------- + +* [Testing Overview](../guide/test-overview.md) +* [Testing environment setup](../guide/test-environment-setup.md) +* [Unit Tests](../guide/test-unit.md) +* [Functional Tests](../guide/test-functional.md) +* [Acceptance Tests](../guide/test-acceptance.md) +* [Fixtures](../guide/test-fixtures.md) + + +مواضيع وعناوين مميزة +-------------- + +* [Advanced Project Template](https://www.yiiframework.com/extension/yiisoft/yii2-app-advanced/doc/guide) +* [Building Application from Scratch](../guide/tutorial-start-from-scratch.md) +* [Console Commands](../guide/tutorial-console.md) +* [Core Validators](../guide/tutorial-core-validators.md) +* [Docker](../guide/tutorial-docker.md) +* [Internationalization](../guide/tutorial-i18n.md) +* [Mailing](../guide/tutorial-mailing.md) +* [Performance Tuning](../guide/tutorial-performance-tuning.md) +* [Shared Hosting Environment](../guide/tutorial-shared-hosting.md) +* [Template Engines](../guide/tutorial-template-engines.md) +* [Working with Third-Party Code](../guide/tutorial-yii-integration.md) +* [Using Yii as a micro framework](../guide/tutorial-yii-as-micro-framework.md) + + +Widgets +------- + +* [GridView](https://www.yiiframework.com/doc-2.0/yii-grid-gridview.html) +* [ListView](https://www.yiiframework.com/doc-2.0/yii-widgets-listview.html) +* [DetailView](https://www.yiiframework.com/doc-2.0/yii-widgets-detailview.html) +* [ActiveForm](https://www.yiiframework.com/doc-2.0/guide-input-forms.html#activerecord-based-forms-activeform) +* [Pjax](https://www.yiiframework.com/doc-2.0/yii-widgets-pjax.html) +* [Menu](https://www.yiiframework.com/doc-2.0/yii-widgets-menu.html) +* [LinkPager](https://www.yiiframework.com/doc-2.0/yii-widgets-linkpager.html) +* [LinkSorter](https://www.yiiframework.com/doc-2.0/yii-widgets-linksorter.html) +* [Bootstrap Widgets](https://www.yiiframework.com/extension/yiisoft/yii2-bootstrap/doc/guide) +* [jQuery UI Widgets](https://www.yiiframework.com/extension/yiisoft/yii2-jui/doc/guide) + + +Helpers +------- + +* [Helpers Overview](../guide/helper-overview.md) +* [ArrayHelper](../guide/helper-array.md) +* [Html](../guide/helper-html.md) +* [Url](../guide/helper-url.md) + diff --git a/docs/guide-ar/intro-yii.md b/docs/guide-ar/intro-yii.md new file mode 100644 index 00000000000..7d2eb504ed3 --- /dev/null +++ b/docs/guide-ar/intro-yii.md @@ -0,0 +1,55 @@ +#
ما هي بيئة العمل Yii
+ +

Yii هو إطار PHP عالي الأداء يعتمد على المكونات لتطوير تطبيقات الويب الحديثة بسرعة. +إن الاسم "Yii" (يُنطق بـ "يي" أو "[جي:]" يعني "بسيطًا وتطوريًا" باللغة الصينية. ومن الممكن ايضا + اعتباره اختصارًا لـ Yes It Is!

+ + +#
ما هي أفضل التطبيقات أو البرمجيات التي يمكن برمجتها وتتناسب مع ال Yii
+ +

+Yii هو إطار عام لبرمجة الويب ، مما يعني أنه يمكن استخدامه لتطوير كافة أنواع +تطبيقات الويب باستخدام PHP. وذلك بسبب البنية القائمة على البنية التركيبة لبيئة العمل وترابطها مع المكونات والتخزين المؤقت، وهو مناسب بشكل خاص لتطوير portals, forums, content management systems (CMS), e-commerce projects, RESTful Web services. وما إلى ذلك. +

+ + +#
كيف يمكن مقارنة بيئة العمل الخاصة بال Yii مع الأطر أو بيئات العمل الأخرى؟
+ +

+ إذا كنت بالفعل على دراية بإطار العمل الأخرى ، فيمكنك معرفة كيف تتم مقارنة ال Yii: +

+ +
    +
  • مثل معظم أطر عمل ال PHP ، يطبق Yii النمط المعماري MVC (Model-View-Controller).
  • +
  • ال Yii يتبنى الفلسفة التي تقول أن الشيفرة البرمجية يجب أن تكتب بأسهل طريقها وادقها، ولكنها بذات الوقت يجب أن تكون أنيقة الكتابة مظهرا ومضمونا (شكلا وتطيبقا).
  • +
  • ال Yii هو إطار متكامل (full stack) يوفر العديد من الميزات الجاهزة للإستخدام والمعدة مسبقا، مثل ال query builders وال ActiveRecord لقواعد البيانات العلاقئية (relational) وغير العلائقية (Nosql)، بالإضافة الى دعم وتجهيز ال RESTful API والتخزين المؤقت (caching) وغيرها الكثير.
  • +
  • من مميزات ال Yii إمكانية التعديل (استبدال جزء معين أو تخيصيص وإضافة) جزء معين على أغلب ال Yii core code، وبالإضافة الى هذا، يمكنك بناء ملحقات برمجية اعتمادا على ال core code، ومن ثم نشر هذه الشيفرة وتوزيعها واستخدامها دون وجود أي مشاكل أو صعوبة تذكر.
  • +
  • الأداء العالي هو الهدف الأساسي من ال Yii.
  • +
+ +

+ال Yii إطار عمل صمم من قبل فريق برمجي متكامل، فهو ليس مجرد عمل فردي ، بل يتكون من فريق تطوير أساسي وقوي ، بالإضافة إلى منتدى كبير +من المهنيين الذين يساهمون باستمرار في تطوير هذا الإطار. فريق المطورين الخاص بال Yii +يراقب عن كثب أحدث اتجاهات تطوير الويب وأفضل الممارسات والمميزات التي +وجدت في الأطر والمشاريع الأخرى. وتدرج بانتظام بإضافة أفضل الممارسات والميزات الى ال Yii عبر واجهات بسيطة وأنيقة. +

+ + + +#
الإصدارات الخاصة بال Yii
+ +

+ يتوفر لدى Yii حاليًا إصداران رئيسيان: 1.1 و 2.0. الإصدار 1.1 هو الجيل القديم وهو الآن في وضع الصيانة. الإصدار 2.0 هو إعادة كتابة وهيكلة كاملة لل Yii، تم اعتماد أحدث التقنيات والبروتوكولات فيها مثل including Composer, PSR, namespaces, traits والكثير من الأمور الأخرى، وفي هذه الإرشادات، سيكون الكلام كله موجها الى الإصدار الثاني من بيئة العمل ال Yii. +

+ +#
المتطلبات الأساسية للعمل على إطار ال Yii
+ +
    +
  • الإصدار PHP 7.4.0 أو أكثر
  • +
  • المعرفة الأساسية بمفاهيم البرمجة كائنية التوجه OOP
  • +
  • المعرفة بآخر وأحدث التقنيات الموجودة بال php مثل ال namespaces, traits، الفهم لهذه المفاهيم سيسهل عليك العمل كثيرا
  • +
+ +

+ ملاحظة: يمكن التحقق من توافق المتطلبات الخاصة بك مع ال yii من خلال الدخول الى الصفحة requirement الموجودة بال yii +

diff --git a/docs/guide-ar/start-databases.md b/docs/guide-ar/start-databases.md new file mode 100644 index 00000000000..d3e53b29008 --- /dev/null +++ b/docs/guide-ar/start-databases.md @@ -0,0 +1,290 @@ +#
التعامل مع قواعد البيانات
+ +

+ في هذا الجزء التعليمي ستتعلم آلية إنشاء صفحة جديدة تعرض بيانات يتم جلبها من قاعدة البيانات -في هذا المثال، البيانات تخص ال country-، هذه البيانات سيتم جلبها من جدول موجود في قاعدة البيانات يسمى ب country. لتحقيق هذا المهمة، ستقوم بعمل ال config الخاص بالإتصال بقاعدة بيانات، بالإضافة لإنشاء ال Active Record class، وتعريف ال action، وإنشاء view لهذه الصفحة. +

+ + +

+ في هذا الشرح ستتعلم كيف يمكنك القيام بما يلي: +

+ +
    +
  • إعداد ال connection الخاص بقاعدة البيانات
  • +
  • التعرف على ال active record.
  • +
  • إنشاء جمل إستعلام عن البياتات بإستخدام ال active record class
  • +
  • عرض البيانات داخل ال view من خلال ال paginated fashion.
  • +
+ +

+ ملاحظة: من أجل الانتهاء من هذا الجزء التعليمي، يجب أن يكون لديك المعرفة الأساسية والخبرة باستخدام قواعد البيانات. وعلى وجه الخصوص، يجب أن تعرف كيفية إنشاء قواعد البيانات، وكيفية تنفيذ ال statements SQL باستخدام أي DB client tool. +

+ +##
إعداد قاعدة البيانات
+ +

+ في البداية، عليك إنشاء قاعدة بيانات تسمى ب yii2basic، والتي ستستخدم لجلب البيانات الخاصة بالتطبيق، ويمكنك إستخدام أي من ال SQLite, MySql, PostgreSQL, MSSQL or Oracle database, ال Yii بشكل افتراضي بدعم العديد من قواعد البيانات والتي يمكنك إستخدامها مباشرة في التطبيق الخاص بك، ولتبسيط الأمور، ال MySql هي التي سيتم إستخدامها في في هذا الشرح. +

+ +

+ معلومة: إذا كنت ترغب بالحصول على خيارات متقدمة مثل دعم ال JSON الموجود داخل MariaDB، فيمكنك من إستخدام أحد ال Extension المذكوره بالأسفل للقيام بهذه المهمة بدلا من الإستغناء عن ال MySql، فإستخدام MariaDB بدلا عن ال MySql لم يعد صحيحا تماما. +

+ +

+ بعد قيامك بإنشاء قاعدة البيانات، سنقوم بإنشاء جدول إسمه country، ومن ثم سنقوم بإدخال بعض البيانات كعينة للإختيار، وللقيام بذلك، قم بتنفيذ الأوامر التالية: +

+ +```sql +CREATE TABLE `country` ( + `code` CHAR(2) NOT NULL PRIMARY KEY, + `name` CHAR(52) NOT NULL, + `population` INT(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); +``` + +

+ الآن، أصبح لديك قاعدة بيانات إسمها yii2basic، وتحوي بداخلها جدول بثلاث أعمدة يسمى ب country، وفيه 10 صفوف من البيانات. +

+ +##
إعدادات الإتصال الخاصة بقواعد البيانات - Configuring a DB Connection
+ +

+ قبل أن تكمل الشرح، تأكد من تثبيت ال PHP PDO وال PDO driver، بالنسبة لهذا المثال، فإننا سنستخدم ال driver الخاص بال MySql وهو ال pdo_mysql، وهذه هي المتطلبات الأساسية لبناء أي التطبيق اذا كان التطبيق يستخدم ال relational database. +

+ +

+ ملاحظة: يمكنك تقعيل ال PDO مباشرة من خلال الدخول الى php.ini ومن ثم حذف الفاصلة المنقوطة قبل السطر التالي: extension=php_pdo.dll + كما يمكنك تفعيل ال driver المطلوب عن طريق حذف الفاصلة المنقوطة قبل ال driver المقصود مثل: +extension=php_pdo_mysql.dll + ويمكنك الإطلاع على المزيد من هنا: +pdo installation +

+ +

+ بعد إتمام ما سبق، قم بفتح الملف config/db.php ومن ثم قم بتعديل ال parameters لتكون الإعدادات الخاصة بقاعدة البيانات صحيحة -الإعدادت الخاصة بك-، بشكل افتراضي، يحتوي الملف على ما يلي: +

+ + +```php + 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=yii2basic', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; +``` + +

+ يمثل ملف ال config/db.php أداة نموذجية تعتمد على الملفات للقيام بال configuration. يقوم ملف ال configuration بتحديد ال parameters المطلوبة لإنشاء وإعداد ال instance الخاص بال [[yii\db\Connection]]، ومن خلالها يمكنك إجراء عمليات الإستعلام على قاعدة البيانات. +

+ +

+ الإعدادات الخاصة بالإتصال بقاعدة البيانات والمذكورة في الملف أعلاه يمكن الوصول اليها من خلال التطبيق عن طريق تنفيذ الأمر التالي + Yii::$app->db +

+ +

+ معلومة: سيتم تضمين ملف ال config/db.php من خلال ال main application configuration والذي يتمثل بالملف config/web.php، والذي يقوم بدوره بتحديد كيف يمكن تهيئة ال instance الخاص بالتطبيق، لمزيد من المعلومات، يرجى الإطلاع على قسم ال Configurations. +

+ +

+ إذا كنت بحاجة إلى العمل مع إحدى قواعد البيانات الغير مدعومة بشكل إفتراضي من ال Yii، فيمكنك التحقق من الإضافات التالية: +

+ + + +##
إنشاء ال Active Record
+ +

+ لجلب البيانات وعرضها من جدول ال country، سنقوم بإضافة ال Active Record الى ال class المسمى ب country، والموجود في المسار models/Country.php. +

+ +```php + + يرث ال Country Class ال [[yii\db\ActiveRecord]]، ولذلك، أنت لست بحاجة لكتابة أي شيفرة برمجية بداخله، فقط الشيفرة التي تشاهدها بالأعلى. سيقوم ال Yii بشكل تلقائي بالحصول على إسم الجدول في قاعدة البيانات من خلال إسم ال Class. +

+ +

+معلومة: إذا لم يكن من الممكن إجراء مطابقة مباشرة بين اسم ال class واسم الجدول، فيمكنك تجاوز هذه المشكلة من خلال إستخدام الدالة [[yii\db\ActiveRecord::tableName()]] ، والتي ستقوم بعمل override على اسم الجدول. +

+ +

+ من خلال إستخدام ال Country class، يمكنك التحكم بكل سهولة بالبيانات الموجودة داخل جدول ال country، شاهد هذه الشيفرة البرمجية لتبسيط الفكرة: +

+ +```php +use app\models\Country; + +// get all rows from the country table and order them by "name" +$countries = Country::find()->orderBy('name')->all(); + +// get the row whose primary key is "US" +$country = Country::findOne('US'); + +// displays "United States" +echo $country->name; + +// modifies the country name to be "U.S.A." and save it to database +$country->name = 'U.S.A.'; +$country->save(); +``` + +

+ معلومة: يعتبر ال Active Record وسيلة قوية للوصول إلى بيانات قاعدة البيانات والتعامل معها بطريقة ال object oriented. + ستجد معلومات أكثر تفصيلاً في الجزء الخاص بال Active Record. بالإضافة الى ذلك، يمكنك التفاعل مباشرة مع قاعدة البيانات باستخدام lower-level data accessing والتي تسمى ب Database Access Objects. +

+ +##
إنشاء ال Action
+ +

+لعرض بيانات ال country للمستخدمين، يلزمك إنشاء action جديد، وبدلاً من وضع ال action الجديد في ال site controller كما فعلنا في المرات السابقة، سنقوم بإنشاء controller جديد، ومن ثم سنقوم بوضع ال action بداخله، والسبب المنطقي لهذا العمل أنك ستقوم بتجميع الشيفرة البرمجية المسؤولة عن أداء وظائف معينة في مكان واحد، وبهذا فإن جميع الإجرائات التي تخص ال country من المنطقي أن تكون موجودة داخل ال CountryController، والآن لنقم بإنشاء هذا ال controller الجديد، وال action الجديد وسيكون باسم index، كما هو موضح أدناه: +

+ +```php + 5, + 'totalCount' => $query->count(), + ]); + + $countries = $query->orderBy('name') + ->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); + + return $this->render('index', [ + 'countries' => $countries, + 'pagination' => $pagination, + ]); + } +} +``` +

+ قم بحفظ الشيفرة البرمجية التي بالأعلى داخل هذا الملف controllers/CountryController.php +

+ +

+ يقوم ال index action باستخدام ال Country::find()، وهذه الدالة موجودة من ضمن ال active record، وتقوم هذه الدالة على بناء الإستعلام الخاص بقاعدة البيانات مما يسمح باسترجاع جميع البيانات الموجودة بداخل جدول ال country، ولتحديد الحد الأعلى المسموح إرجاعه في كل request، يمكنك إستخدام ال [[yii\data\Pagination]] object كوسيلة مساعدة، ويقدم هذا ال object غرضين أساسيين وهما: +

+ +
    +
  • كتابة جملة استعلام محددة بعدد صفوف معين، وتبدأ من مكان معين، ويتم ذلك من خلال ال offset وال limit، وبهذا التعيين فأنت ستقوم بإرجاع صفحة واحدة من البيانات وفي كل مرة سيتم عرض 5 صفوف على الأكثر.
  • +
  • كما يستخدم في صفحة ال view لعرض ال pager مع قائمة بال page buttons، وذلك كما ستشاهد في الجزء التالي من هذا الموضوع.
  • +
+ +

+ في نهاية الشيفرة البرمجية الموجودة في ال index action، نقوم باستدعاء صفحة ال view المسمية ب index، ومن ثم نقوم بإرسال معلومات ال country المنظمة على شكل صفحات (pagination) ومن ثم عرضها. +

+ + +##
إنشاء View
+ +

+ الآن، سنقوم بإنشاء صفحة ال view والتي ستقوم بعرض المعلومات الخاصة بال country والقادمة من ال index action، ولذلك توجه الى المسار views, ومن ثم قم بإنشاء مجلد جديد بداخله باسم country، هذا المجلد سيحوي جمع ال views التي سيتم إستدعائها من ال country controller>. الآن، قم بإنشاء الصفحة index.php بداخل المسار views/country، ومن ثم قم بكتابة هذه الشيفرة البرمجية بداخلها: +

+ +```php + +

Countries

+
    + +
  • + code} ({$country->name})") ?>: + population ?> +
  • + +
+ + $pagination]) ?> +``` + +

+ في هذه الصفحة، هناك جزئيين رئيسيين، الجزء الأول هو المسؤول عن طباعة المعلومات الخاصة بال country داخل قائمة من نوع ul، والجزء الثاني عبارة عن widget يستخدم المعلومات الخاصة بال pagination والتي تم إرسالها من ال action، ليقوم بعد ذلك بعرض قائمة من ال page buttons، والتي بدورها ستقوم بعمل تحديث للمعلومات الموجودة بالصفحة بنائا على الصفحة المطلوبة، هذا ال widget يسمى ب LinkPager -[[yii\widgets\LinkPager]]-. +

+ +##
لنجرب المثال
+ +

+ لتشاهد آلية العمل لهذا المثال، والنتائج المتعلقة به، يمكنك إستخدام المتصفح والدخول الى الرابط التالي: +

+ +``` +https://hostname/index.php?r=country%2Findex +``` + +![Country List](../guide/images/start-country-list.png) + +

+في البداية، ستشاهد البيانات الخاصة بخمسة دول، وأسفل هذه الدول، ستشاهد pager فيه 4 أزرار، اذا قمت بالضغط على الزر رقم 2، فإنك ستشاهد المعلومات الخاصة بخمسة دول أخرى، والتي تمثل صفوف أخرى من البيانات تم جلبها من قاعدة البيانات. +
+تابع بعناية أكبر وستجد أن عنوان ال URL في المتصفح قد تغير أيضًا: +

+ +``` +https://hostname/index.php?r=country%2Findex&page=2 +``` + +

+ بالخفاء، يقوم ال [[yii\data\Pagination|Pagination]] بتقديم الدعم اللازم لكل الوظائف المطلوب تنفيذها لعمل الترقيم الخاص بال pagination: +

+ +
    +
  • في البداية، يمثل ال [[yii\data\Pagination|Pagination]] الصفحة الأولى التي تعكس جملة الإستعلام SELECT مع الجدول country مع العبارة LIMIT 5 OFFSET 0. ونتيجة لذلك، سيتم جلب البلدان الخمسة الأولى وعرضها.
  • +
  • ال [[yii\widgets\LinkPager|LinkPager]] widget يقوم على إستدعاء الأزرار الخاصة بأرقام الصفحات والتي يتم إنشاء ال Url الخاص بها من خلال ال [[yii\data\Pagination::createUrl()|Pagination]]، هذه ال Url تحتوي على parameter يسمى ب page، والذي يمثل بدورة مجموعة من الأرقام المختلفة والتي تمثل صفحات تعرض بيانات مختلفة كما هو في مثالنا عندما قمنا بالضغط على رقم 2.
  • +
  • عند قيامك بالنقر على الزر رقم 2، سينطلق request جديد الى ال country/index route، وحينها يقوم ال [[yii\data\Pagination|Pagination]] على قرائة ال page parameter من ال URL ,من ثم يقوم بتغيير رقم الصفحة الحالية الى الرقم الجديد، وهنا الرقم الجديد هو 2 بدلا من 1. كما أن جملة الإستعلام الجديدة ستحوي الشرط التالي LIMIT 5 OFFSET 5 لتقم بذلك بجلب الصفوف الخمسة الأخرى من البلدان لعرضها.
  • +
+ +##
الخلاصة
+ +

+ في هذا الجزء من التوثيق، لقد تعلمنا كيف يمكننا التعامل مع قواعد البيانات، وكيف يمكننا جلب البيانات وعرضها وتجزئتها لصفحات من خلال إستخدام ال [[yii\data\Pagination]] وال [[yii\widgets\LinkPager]]. +

+ +

+ في الجزء القادم من هذا التوثيق، ستتعلم كيف يمكنك إستخدام إخدى الوسائل المميزة والموجودة في ال Yii لإنشاء الشيفرة البرمجية الخاصة بك، والتي يطلق عليها اسم Gii، هذه الأداة ستساعدك على إنشاء وتنفيذ الشيفرات البرمجية التي يكثر كتابتها وإستخدامها بشكل دوري، مثل مجموعة العمليات Create-Read-Update-Delete، والتي يتم اختصارها ب CRUD، هذه الأداة ستقوم بالتعامل مع البيانات الموجدة بقاعدة البيانات، وفي الحقيقة، الشيفرة البرمجية الذي قمت بكتابته بالأعلى، يمكنك إنشائه بسهولة من خلال ال Gii بنقرة زر، لذلك فلننطلق الى الجزء التالي بأسرع وقت. +

diff --git a/docs/guide-ar/start-forms.md b/docs/guide-ar/start-forms.md new file mode 100644 index 00000000000..db97d4bf61c --- /dev/null +++ b/docs/guide-ar/start-forms.md @@ -0,0 +1,253 @@ +#
العمل مع ال Forms
+ +

+في هذا الموضوع سنتعلم كيفية إنشاء صفحة تحتوي على form للحصول على البيانات من خلال المستخدمين، وستعرض هذه الصفحة form يحتوي على حقل لإدخال الإسم وحقل إدخال للبريد الإلكتروني. +وبعد الحصول على المعلومات الخاصة بهذه الحقول من المستخدم، ستقوم الصفحة بطباعة القيم التي تم إدخالها. +

+ +

+ في هذا الشرح، ستقوم بإضافة action وصحفتين views، وستتعرف أيضا على طريقة إنشاء ال model. +

+ +

+من خلال هذا البرنامج التعليمي ، ستتعلم كيفية: +

+ +
    +
  • إنشاء model لتمثيل البيانات التي تم إدخالها من خلال المستخدم عن طريق ال form.
  • +
  • إنشاء rules للتحقق من صحة البيانات التي تم إدخالها.
  • +
  • بناء html form داخل صفحة ال view.
  • +
+ +##
إنشاء ال Model
+ +

+ يتم تمثيل البيانات التي يتم طلبها من خلال المستخدم عن طريق ال EntryForm model class كما هو موضح أدناه، ويتم حفظ هذا الملف داخل المسار models، ويكون إسم ال model ومساره في مثالنا هذا هو models/EntryForm.php. يرجى الرجوع إلى صفحة ال Class Autoloading للحصول على مزيد من التفاصيل حول طريقة التعامل مع التسمية الخاصة بال class في Yii. +

+ +```php + + هذا ال class يرث ال [[yii\base\Model]], وهو base class تم تصميمه من خلال ال Yii, وبشكل عام وظيفته هي تثمثيل البيانات الخاصة بأي نموذج. +

+ +

+معلومة: يتم إستخدام ال [[yii\base\Model]] كأصل لل model class ولا يرتبط بجداول قواعد البيانات. ويستخدم ال [[yii\db\ActiveRecord]] بالشكل الإعتيادي ليكون هو الأصل الذي من خلاله يتم الإرتباط بجداول بقواعد البيانات. +

+ +

+ يحتوي class ال EntryForm على متغيرين إثنين من نوع Public، هما name و email، واللذان يستخدمان في تخزين البيانات التي أدخلها المستخدم. كما يحتوي أيضًا على method باسم rules()، والتي تُرجع مجموعة +الشروط الخاصة بالبيانات للتحقق من صحتها. والشيفرة البرمجية الموجودة داخل ال rules method تعني: +

+ +
    +
  • كل من ال name وال email حقول الزامية (required).
  • +
  • ال email حقل يجب أن يحتوي بداخله قيمة صحيحة تعبر عن البريد الإلكتروني (القواعد النحوية لكتابة البريد الإلكتروني).
  • +
+ +

+ إذا كان لديك object من ال EntryForm ويحتوي على البيانات التي أدخلها المستخدم، فيمكنك حينها إستدعاء الدالة [[yii\base\Model::validate()|validate()]] للتحقق من صحة البيانات. اذا فشلت عملية التحقق من صحة البيانات، فسيؤدي ذلك إلى تغيير قيمة ال [[yii\base\Model::hasErrors|hasErrors]] إلى true ، بالإضافة الى ذلك يمكنك التعرف الى الأخطاء المتعلقة بهذه البيانات من خلال الدالة [[yii\base\Model::getErrors|errors]]. +

+ +```php +name = 'Qiang'; +$model->email = 'bad'; +if ($model->validate()) { + // Good! +} else { + // Failure! + // Use $model->getErrors() +} +``` + + +##
إنشاء Action
+ +

+ الآن، ستحتاج إلى إنشاء action جديد في ال site controller وليكن إسمه entry، والذي سيقوم بدوره باستخدام ال model الجديد الذي قمنا بإنشائه. هذه العملية تم شرحها سابقا في الجزء التالي من التوثيق Saying Hello - قل مرحبا. +

+ +```php +load(Yii::$app->request->post()) && $model->validate()) { + // valid data received in $model + + // do something meaningful here about $model ... + + return $this->render('entry-confirm', ['model' => $model]); + } else { + // either the page is initially displayed or there is some validation error + return $this->render('entry', ['model' => $model]); + } + } +} +``` +

+ أولا، يقوم ال action بإنشاء object من ال EntryForm. ثم يحاول تعبئة البيانات لل object من خلال ال $ _POST، والتي يتم تقديمها في ال Yii من خلال ال [[yii\web\Request::post()]]. +إذا تم ملء ال object بنجاح (على سبيل المثال، إذا قام المستخدم بإدخال البيانات داخل ال form ومن ثم قام بإرسالها(submitted html form))، فسيتم استدعاء ال [[yii\base\Model::validate()|validate()]] من خلال ال action للتأكد من صلاحية القيم المدخلة. +

+ + +

+ معلومة: يمثل التعبير Yii::$app ال Application instance الذي يمكن الوصول اليه من خلال ال singleton
(singleton globally accessible). وهو أيضا service locator بحيث يوفر الدعم لل components مثل ال request, response, db..الخ، لدعم وظائف محددة. مثلا في المثال الموجود في الأعلى، فإن ال request هو component من ال application instance والذي يستخدم للوصول الى البيانات الموجودة داخل ال $_POST. +

+ +

+ إذا كان كل شيء على ما يرام، فسوف يقوم ال action بجلب ال view التالية: entry-confirm، وذلك لتأكيد أن العملية قد تمت بنجاح بالنسبة للمستخدم، أما إن كانت البيانات غير صحيحة، أو لم يتم إرسال أي بيانات، فإن ال view entry هي التي سيتم جلبها وعرضها للمستخدم، حيث يتم عرض ال Html form، مع أي رسائل تحذير بخصوص الأخطاء التي تم العثور عليها من عملية التحقق. +

+ +

+ملاحظة: في هذا المثال البسيط، نعرض صفحة التأكيد فقط عند إرسال البيانات بشكل صحيح. عند الممارسة العملية، يجب عليك استخدام [[yii\web\Controller::refresh()|refresh()]] أو [[yii\web\Controller::redirect()|redirect()]] لتجنب أي مشكلة تحصل عن طريق ال resubmission والتي تندرج تحت العنوان form resubmission problems. +

+ +##
إنشاء ال views
+ +

+ أخيرا، سنقوم بإنشاء صفحتين لل views الأولى بإسم entry-confirm والثانية entry. وهاتين الصفحتين سيتم جلبهم من خلال ال entry action. +

+ +

+ ال entry-confirm ستقوم بكل بساطة بعرض الإسم والبريد الإلكتروني الذي تم إدخالهم من قبل المستخدم. ويجب حفظ هذه الصفحة بالمسار التالي: views/site/entry-confirm.php +

+ +```php + +

You have entered the following information:

+ +
    +
  • : name) ?>
  • +
  • : email) ?>
  • +
+``` +

+ صفحة ال entry ستقوم بعرض ال HTML form. هذه الصفحة يجب أن يتم حفظها داخل المسار التالي: views/site/entry.php +

+ +```php + + + + field($model, 'name') ?> + + field($model, 'email') ?> + +
+ 'btn btn-primary']) ?> +
+ + +``` +

+ تستخدم ال view أسلوب مميز لبناء ال Forms، وذلك عن طريق ال widget الذي يسمى ب [[yii\widgets\ActiveForm|ActiveForm]]. إن الأسلوب المستخدم في هذا ال widget يقوم على إستخدام كل من الدالة begin() و end() لجلب ال opening وال closing form tags على التوالي (فتحة ال tag، ثم الإغلاق الخاص بهذا ال tag)، وبين الفتحة والإغلاق يمكنك إنشاء الحقول عن طريق إستخدام الدالة [[yii\widgets\ActiveForm::field()|field()]]. في هذا المثال كان الحقل الأول في ال form يشير الى name data، والثاني يشير الى ال email data، وبعد هذه الحقول ستجد الدالة المستخدمة لإنشاء ال Submit button وهي [[yii\helpers\Html::submitButton()]]. +

+ +##
لنجرب المثال
+ +

+ لتشاهد آلية العمل لهذا المثال، والنتائج المتعلقة به، يمكنك إستخدام المتصفح والدخول الى الرابط التالي: +

+ +``` +https://hostname/index.php?r=site%2Fentry +``` + +

+عند دخولك الى الرابط السابق، سترى صفحة تعرض Html form يحتوي على حقلين لإدخال المعلومات. أمام كل حقل إدخال ستجد label يشير إلى البيانات المطلوب إدخالها. إذا قمت بالنقر فوق الزر "submit" بدون +أي إدخال، أو إذا لم تقم بكتابة عنوان البريد الإلكتروني بشكل صحيح، فستظهر لك رسالة خطأ بجوار الحقل المقصود. +

+ +![Form with Validation Errors](../guide/images/start-form-validation.png) + +

+ بعد إدخالك لإسم وبريد الكتروني صحيح، وقيامك بالنقر على زر submit، فإنك ستشاهد صفحة جديدة تقوم بعرض البيانات التي قمت بإدخالها. +

+ +![Confirmation of Data Entry](../guide/images/start-entry-confirmation.png) + +##
كيف ظهر الخطأ؟ هل هو سحر؟!
+ +

+قد تتساءل كيف يعمل ال Html form بالخفاء، وقد يبدو ذلك سحرا للوهلة الأولى، فهو يعرض ال label لكل حقل إدخال، ويعرض رسائل الخطأ إذا لم تقم بإدخال البيانات بشكل صحيح، وكل ذلك دون الحاجة لإعادة تحميل الصفحة. +

+ +

+ إن السحر الموجود لدينا هنا، هو كيفية العمل الخاصة بالشيفرة البرمجية لل form، والتي تعمل بالخفاء، إن إجراء التحقق عن صحة البيانات يتم في البداية من جانب العميل -client side- وذلك باستخدام الجافا سكربت، ومن ثم -بعد تجاوز التحقق الخاص بالجافا سكربت- بتم تنفيذ التحقق من جانب ال server-side عبر ال PHP. ال [[yii\widgets\ActiveForm]] ذكية بما فيه الكفاية لاستخراج ال rule الخاصة بالتحقق والتي قمت بإنشائها وتعريفها داخل ال EntryForm، ومن ثم تحويل هذه القواعد إلى شيفرة برمجية بالجافا سكربت قابلة للتنفيذ، ومن ثم استخدام هذه الشيفرة من قبل الجافا سكربت لإجراء التحقق من صحة البيانات. في حال قمت بإيقاف الجافا سكربت في المتصفح الخاص بك، سوف يستمر إجراء التحقق من جانب الخادم -server side-، كما هو موضح في ال action المسمى actionEntry(). وهذا يضمن صحة البيانات في جميع الظروف. +

+ +

+ تحذير: التحقق من جانب العميل -client side- يوفر تجربة أفضل للمستخدم، لكن يجب الأخذ بعين الإعتبار أن التحقق من جانب الخادم -server- مطلوب دائمًا، سواء تم التحقق من جانب العميل أم لا. +

+ +

+ يتم إنشاء ال labels الخاصة بحقول الإدخال بواسطة الدالة field()، وذلك من خلال إستخدام أسماء ال property الموجودة داخل ال model. على سبيل المثال، سيتم إنشاء ال label التالي Name للproperty التالية: name. +

+ +

+ كما يمكنك تعديل ال label الإفتراضي لأي حقل من خلال الشيفرة البرمجية التالية: +

+ +```php +field($model, 'name')->label('Your Name') ?> +field($model, 'email')->label('Your Email') ?> +``` + +

+ معلومة: يوفر ال Yii العديد من ال widgets لمساعدتك في إنشاء views معقدة وديناميكية بسرعة. كما أنك ستتعلم في وقت لاحق كيف يمكنك إنشاء widget جديد، وستكتشف أن الموضوع سهل وبسيط، مما سيدفعك إلى كتابة الشيفرة البرمجية الخاصة بك داخل ال widget، والذي بدوره سيجعل من هذه الشيفرة قابلة للتطوير والإستخدام في أكثر من مكان في المستقبل. +

+ +##
الخلاصة
+ +

+ في هذا الجزء من التوثيق، تحدثنا عن كل جزء في ال MVC architectural pattern، لقد تعلمت الآن كيف يمكنك إنشاء model class ليقوم بتمثيل البيانات الخاصة بالمستخدمين، ومن ثم التحقق منها. +

+ +

+ لقد تعلمت أيضًا كيفية الحصول على البيانات من المستخدمين، وكيفية عرض البيانات مرة أخرى في المتصفح. هذه المهمة يمكن أن تأخذ الكثير من الوقت عند تطوير أي تطبيق، ولكن، يوفر ال Yii العديد من ال widgets القوية، والتي تجعل من هذه المهمة أمرا سهلا للغاية. +

+ +

+ في الجزء القادم من هذا التوثيق، ستتعلم كيف يمكنك التعامل مع قواعد البيانات، والتي سنحتاجها -غالبا- مع كل تطبيق ستعمل عليه تقريبا. +

diff --git a/docs/guide-ar/start-gii.md b/docs/guide-ar/start-gii.md new file mode 100644 index 00000000000..074cbbe3a03 --- /dev/null +++ b/docs/guide-ar/start-gii.md @@ -0,0 +1,161 @@ +#
إنشاء الشيفرة البرمجية من خلال ال gii
+ +

+ في هذا الجزء التعليمي سنتعرف على آلية التعامل مع ال Gii، والذي يستخدم لإنتاج الشيفرة البرمجية الخاصة بمعظم الميزات والخصائص المشتركة في أغلب المواقع بشكل تلقائي، بالإضافة الى ذلك، فإن استخدام ال Gii لإنشاء الشيفرة البرمجية بشكل تلقائي يمثل مجموعة من المعلومات الصحيحة التي بتم إدخالها إعتمادا على التعليمات الموجودة في ال Gii Web Pages. +

+ +

+ من خلال هذا البرنامج التعليمي، ستتعلم كيفية: +

+ +
    +
  • تفعيل ال Gii داخل التطبيق الخاص بك
  • +
  • إستخدام ال Gii لإنشاء ال Active Record class
  • +
  • إستخدام ال Gii لإنشاء الشيفرة البرمجية الخاصة بال CRUD إعتمادا على الجداول الموجودة في قاعدة البيانات
  • +
  • تخصيص (customize) الشيفرة البرمجية التي سيتم إنتاجها من خلال ال Gii.
  • +
+ +##
البدء باستخدام ال Gii
+ +

+ يتم تقديم ال Gii داخل على ال Yii على أنه module، ويمكنك تفعيله من خلال الإعدادات الخاصة به والتي تجدها داخل ال application، وبالتحديد داخل ال property التالية [[yii\base\Application::modules|modules]]، واعتمادا على كيفية إنشائك للمشروع، فيمكنك إيجاد الشيفرة البرمجية التالية موجودة بشكل مسبق داخل ال config/web.php: +

+ +```php +$config = [ ... ]; + +if (YII_ENV_DEV) { + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; +} +``` + +

+ في الإعدادت الموجودة في الأعلى، فإن التطبيق سيقوم بتضمين وتفعيل ال gii في حال كانت الحالة الخاصة بالتطبيق هي development enviroment، بالإضافة الى ذلك، فإنه يجب تضمين واستخدام ال module gii، والموجود ضمن ال class التالي [[yii\gii\Module]]. +

+ + +

+ اذا قمت بالتحقق من ال entry script وبالتحديد صفحة ال web/index.php في التطبيق الخاص بك، ستجد هذه الأسطر، والتي يجب أن تجعل من ال YII_ENV_DEV ذات قيمة true. +

+ +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +

+ كل الشكر لهذا السطر البرمجي، التطبيق الآن أصبح بحالة ال development mode، وأصبح لديك ال Gii enabled بالفعل، والآن، يمكنك الوصول الى ال Gii من خلال عنوان ال URL التالي: +

+ +``` +https://hostname/index.php?r=gii +``` + +

+ ملاحظة: إذا كنت تحاول الوصول إلى Gii من جهاز آخر غير ال localhost، فسيتم رفض الوصول افتراضيًا لأغراض أمنية، ولكن، يمكنك إعداد ال Gii لإضافة مجموعة من ال IP Addresses المسموح لها بالوصول وذلك من خلال: +

+ +```php +'gii' => [ + 'class' => 'yii\gii\Module', + 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'] // عدل هذه حسب إحتياجاتك +], +``` + +![Gii](../guide/images/start-gii.png) + + +##
إنشاء ال Active Record Class من خلال ال Gii
+ +

+ لإستخدام ال Gii لإنشاء ال Active Record class, قم باختيار ال "Model Generator" (من خلال النقر على الرابط الموجود بالصفحة الرئيسية لل Gii)، ومن ثم قم بتعبئة ال form كما يلي: +

+ +
    +
  • إسم الجدول: country
  • +
  • Country :Model Class
  • +
+ +![Model Generator](../guide/images/start-gii-model.png) + +

+ والآن، قم بالنقر على الزر "Preview"، ستشاهد الآن models/Country.php قد تم إنشائها وإضافتها الى قائمة النتائج، اذا قمت بالنقر على إسم ال class، فإن المحتوى الخاص بهذا ال class سيتم عرضه. +

+ +

+ عند استخدام ال Gii، إذا كنت قد قمت بالفعل بإنشاء نفس الملف وستقوم بعمل overwriting عليه، فيمكنك النقر على زر diff الموجود بعد إسم ال class، لتشاهد الفرق بين الشيفرة البرمجية الحالية، والشيفرة البرمجية الجديدة. +

+ +![Model Generator Preview](../guide/images/start-gii-model-preview.png) + +

+ عند قيامك بعمل overwriting على ملف موجود، قم بالضغط على ال ckeckbox الموجودة بجانب كلمة overwrite، ومن ثم قم بالنقر على زر "Generate", اذا كان هذا الملف جديد، وغير موجود مسبقا، فيمكنك النقر مباشرة على "Generate"، بعد ذلك ستشاهد صفحة ال confirmation والتي تبين الشيفرات البرمجية التي تم إنشائها بنجاح. +

+ +

+ بعد فيامك بالنقر على زر Generate، فإنك ستشاهد صفحة ال confirmation page، والتي تقوم بدورها بتوضيح الشيفرات البرمجية التي تم إنشائها بنجاح، واذا كان الملف موجود، فإنك ستشاهد أيضا رسالة تعلمك بأن الملف قد تم تعديله وتمت إضافة الشيفرة الجديدة مكان القديمة. +

+ +##
إنشاء ال CRUD Code
+ +

+ ال CRUD هي اختصار ل Create, Read, Update, And Delete (إنشاء، وقرائة، وتحديث، وحذف)، والتي تمثل أكثر المهمات المطلوبة للتعامل مع البيانات على مواقع الويب. ولإنشاء ال CRUD باستخدام ال Gii، قم باختيار ال "CRUD Generator" (من خلال النقر على الرابط الموجود بالصفحة الرئيسية لل Gii)، وهنا وبالبنسبة للمثال الخاص بال "country"، يمكنك تعبئة ال from بما يلي: +

+ +* Model Class: `app\models\Country` +* Search Model Class: `app\models\CountrySearch` +* Controller Class: `app\controllers\CountryController` + +![CRUD Generator](../guide/images/start-gii-crud.png) + +

+ بعد ذلك، قم بالنقر على زر ال "Preivew"، وستشاهد قائمة بالملفات التي سيتم إنشائها كما في الصورة أدناه. +

+ +![CRUD Generator Preview](../guide/images/start-gii-crud-preview.png) + +

+ اذا قمت إنشاء الصفحتين controllers/CountryController.php و views/country/index.php عند حديثنا سابقا عند موضوع (التعامل مع قواعد البيانات)، فقم بالضغط على ال "overwrite" ليتم إستبدالهم. (الصفحات القديمة لا توجد فيها كل الخصائص التي سيتم إنتاجها من خلال ال Gii CRUD). +

+ +##
لنجرب المثال
+ +

+لتشاهد آلية العمل لهذا المثال، والنتائج المتعلقة به، يمكنك إستخدام المتصفح والدخول الى الرابط التالي: +

+ +``` +https://hostname/index.php?r=country%2Findex +``` + +

+ عند دخولك إلى الرابط أعلاه، ستشاهد مجموعة الدول التي تم إستدعائها من جدول ال country من قاعدة البيانات، ويمكنك التعامل مع هذا ال grid من حيث الترتيب أو التصفية بنائا على الشروط التي ستقوم بإدخالها في مربعات النص أعلى الأعمدة. +

+ +

+لكل دولة تم جلبها وعرضها داخل ال Grid، هناك مجموعة من الخيارات التي يمكنك التعامل معها بشكل إفتراضي، مثل ال view لعرض التفاصيل الخاصة بالدولة المختارة، أو تحديث المعلومات، الخاصة بالدولة، أو حذف هذه الدولة، بالإضافة إلى ذلك يمكنك النقر على "Create Country" الموجودة في أعلى ال Grid والتي ستأخذك بدورها الى صفحة تحتوي form لإنشاء ال country. +

+ +![Data Grid of Countries](../guide/images/start-gii-country-grid.png) + +![Updating a Country](../guide/images/start-gii-country-update.png) + +

+ فيما يلي قائمة بالملفات التي تم إنشاؤها من خلال ال Gii، في حالة رغبتك في التحقق من كيفية عمل هذه الميزات والإطلاع على الشيفرة البرمجية وتخصيصها حسب الرغبة: +

+ +* Controller: `controllers/CountryController.php` +* Models: `models/Country.php` and `models/CountrySearch.php` +* Views: `views/country/*.php` + +

+معلومة: تم تصميم ال Gii لتكون أداة إنشاء شيفرات برمجية قابلة للتخصيص بشكل كبير للغاية. اذا قمت باستخدامه بحكمة،فإنك ستقوم بتسريع وتيرة التطوير الخاصة بالتطبيق الخاص بك. لمزيد من التفاصيل، يرجى الذهاب إلى الجزء الخاص بال Gii. +

+ +##
الخلاصة
+ +

+في هذا الجزء من التوثيق، لقد تعلمنا آلية استخدام ال Gii لإنشاء الشيفرة البرمجية الخاصة بال CRUD، وتحدثنا عن الوظائف التي تقوم فيها، وكيف يمكننا من خلالها إتمام العمليات الخاصة بالبيانات من إدخال وتحديث وحذف وعرض للبيانات من قاعدة البيانات. +

diff --git a/docs/guide-ar/start-hello.md b/docs/guide-ar/start-hello.md new file mode 100644 index 00000000000..28faccb0d5c --- /dev/null +++ b/docs/guide-ar/start-hello.md @@ -0,0 +1,149 @@ +#
قل مرحبا - Saying Hello
+ +

+ في هذا الموضوع سنتعرف على كيفية إنشاء صفحة "Hello" جديدة في التطبيق الذي قمت بتثبيته، ولتحقيق ذلك، يجب عليك القيام بإنشاء action و view لهذه الصفحة: +

+ +
    +
  • سيقوم التطبيق بإرسال ال request الخاص بالصفحة إلى ال action.
  • +
  • وسيقوم ال action بدوره في جلب ال view التي تعرض كلمة "Hello" إلى المستخدم النهائي.
  • +
+ +

+ من خلال هذا البرنامج التعليمي ، ستتعلم ثلاثة أشياء: +

+ +
    +
  1. كيفية إنشاء action ليقوم بإستقبال ال request ومن ثم الرد (respond) عليها.
  2. +
  3. كيفية إنشاء view وإضافة المحتوى الى ال respond.
  4. +
  5. و كيفية إنشاء التطبيق لل requests التي يوجهها لل actions.
  6. +
+ +##
إنشاء ال Action
+ +

+ لإنشاء صفحة "Hello"، ستقوم بإنشاء say action والذي بدوره سيقوم بقراءة ال message parameter من ال request، ومن ثم عرض ال message مرة أخرى إلى المستخدم. إذا كان ال request لا يحمل معه ال message parameter فإن ال action سيقوم بطباعة message إفتراضية وهي "Hello". +

+ +

+ معلومة: ال Actions هي الكائنات(objects) التي يمكن للمستخدمين من الوصول اليها وتنفيذ ما في بداخلها بشكل مباشر. يتم تجميع هذه ال Actions بواسطة ال controllers. ونتيجة لذلك فإن ال response الراجعة للمستخدم ستكون هي نتيجة التنفيذ الخاصة بال action. +

+ +

+ يجب تعريف ال actions داخل ال controller، ولتبسيط الفكرة، سنقوم بتعريف ال say action داخل أحد ال controller الموجود مسبقا وهو ال siteController. هذا ال controller ستجده داخل المسار controllers/siteController.php. ومن هنا سنبدأ بإضافة ال action الجديد: +

+ +```php +render('say', ['message' => $message]); + } +} +``` + +

+ في الشيفرة البرمجية السابقة ، تم تعريف ال say action من خلال إنشاء الدالة actionSay داخل الكلاس siteController. يستخدم ال Yii كلمة action ك prefix للدوال للتميز بين الدوال ال action و ال non-action في ال controller، كما يستخدم الإسم الخاص بال action مباشرة بعد ال prefix، ويتم عمل mapping بين ال action method name وال action id ليستطيع المستخدم من الوصول للدالة المنشئة من خلال ال action id. +

+ +

+ عندما يتعلق الأمر بتسمية ال action الخاصة بك، يجب عليك أن تفهم كيف يقوم ال Yii بالتعامل مع ال action id، ال action id يجب أن يكون دائما أحرف lower case، وإذا إحتوى ال action id على عدة كلمات فإن الفاصل بينهم سيكون الداش (dashes) مثل create-comment، ويتم ربط ال action id بال action method name من خلال حذف ال dashes من ال IDs، ويتم عمل capitalizing لأول خرف من كل كلمة، وإضافة ال prefix الى الناتج السابق من التحويل. مثال: ال action id المسمى ب create-comment سيتم تحويله الى ال action method name التالي: actionCreateComment. +

+ +

+ في المثال السابق، يقوم ال action method على إستقبال parameter يسمى ب $message، والذي يملك قيمة إفتراضية وهي "Hello" (وهي مشابة تماما لطريقة وضع القيم الإفتراضية لل argument في ال PHP). عندما يستقبل التطبيق ال request ويحدد أن ال action المطلوب للتعامل مع الطلب (request) هو say، فإن التطبيق سيقوم بإسناد القيمة الموجودة بال request الى ال parameter الموجود بال action بشرط أن يكون الإسم الموجود بال request هو نفسه الموجود في ال action. ولتبسيط الموضوع يمكن القول أن ال request اذا احتوى على كلمة message وقيمة هذا ال parameter هي "GoodBye"، فإن ال $message الموجودة في ال action ستصبح قيمتها "GoodBye". +

+ + +

+ من خلال ال action method، يتم استدعاء ال [[yii\web\Controller::render()|render()]] لتقديم +الملف الخاص بال view والمسمى هنا ب say. أيضا فإن ال message يتم تمرريرها الى ال view مما يسمح لك باستخدام هذا ال parameter داخل ال view. النتيجة المرجعة لل view تتم معالجتها وإرجاعها من خلال ال action method، وهذه النتائج سيتم إستقبالها من خلال المتصفح الخاص بالمستخدم ليتم عرضها وكأنها (جزء من صفحة Html كاملة). +

+ +##
إنشاء ال View
+ +

+ ال Views هي شيفرات برمجية كتبت ﻹنشاء المحتوى المناسب بنائا على ال response الراجع اليها من خلال ال action. + بالنسبة إلى مثال "Hello" ، الآن سنقوم بإنشاء view مسمى ب say، والذي سيقوم بدوره بطباعة ال message التي تم إستلامها من ال action، شاهد المثال: +

+ + +```php + + +``` + +

+ صفحة ال view say يجب أن يتم حفظها داخل المسار التالي: views/site/say.php. عندما يتم إستدعاء الدالة [[yii\web\Controller::render()|render()]] من قبل ال action، فإنه سينظر للمسار على الشكل التالي: views/ControllerID/ViewName.php، بحيث يكون في مثالنا السابق ال ContollerID هو ال site وال viewName هو say. +

+ +

+ ملاحظة: في الشيفرة البرمجية أعلاه، تكون ال message مضمنة داخل ال [[yii\helpers\Html::encode()]] قبل أن يتم طباعتها، هذا الأمر ضروري لأن ال parameter التي تأتي من المستخدم النهائي لا يجب الوثوق بها، فهي يمكن أن تحتوي على شيفرات برمجية تستغل الضعف الحاص بك بموضوع الأمان مثل vulnerable to XSS attack عن طريق دمج JS code مع ال parameter. +

+ +

+ وبطبيعة الحال، يمكنك وضع المزيد من المحتوى في صفحة ال say view. ويمكن أن يتكون هذا المحتوى من HTML tag و plain text وحتى PHP statements. +في الواقع، تعد ال say view مجرد شيفرة برمجية بلغة ال PHP والتي يتم تنفيذها بواسطة [[yii\web\Controller::render()|render()]]. +المحتوى الذي سيتم طباعته من خلال ال view سيتم إرجاعه الى التطبيق من خلال ال response، وسيقوم التطبيق بدوره بإخراج هذه النتيجة إلى المستخدم النهائي. +

+ + +##
تطبيق المثال
+------------- + +

+بعد إنشاء ال action وصفحة ال view، يمكنك الوصول إلى الصفحة الجديدة عن طريق عنوان URL التالي: +

+ +``` +https://hostname/index.php?r=site%2Fsay&message=Hello+World +``` + +![Hello World](../guide/images/start-hello-world.png) + +

+ سينتج عن هذا ال URL صفحة تعرض "Hello World". هذه الصفحة لديها نفس ال Header و ال Footer لصفحات التطبيق الأخرى. +

+ +

+ إذا قمت بحذف ال message parameter من ال URL ، فسترى الصفحة تعرض كلمة " Hello " فقط. ويرجع ذلك إلى أن "message" يتم تمريرها ك parameter إلى ال actionSay()، وعندما يتم حذفها، سيتم استخدام القيمة الافتراضية وهي "Hello" بدلاً من ذلك. +

+ +

+ معلومة: تتشارك الصفحة الجديدة مع الصفحات الأخرى بالتطبيق بنفس ال Header وال Footer وذلك بسبب الدالة [[yii\web\Controller::render() | render ()]] والتي ستقوم بشكل تلقائي بتضمين النتيجة الخاصة بصفحة ال view say مع صفحة ال Layout، والتي يمكنك أن تجدها داخل المسار التالي: views/layouts/main.php +

+ +

+ ال r الموجودة في ال URL أعلاه يتطلب الكثير من الشرح. ولكن باختصار يمكن القوم أنها اختصار ل route ، وهو معرف فريد من نوعه(unique ID) للتطبيق بحيث يقوم بالتأشير على ال action، والصيغة الخاصة بال route هي ControllerID/ActionID. عندما يتلقى التطبيق request، فإنه سيتم التحقق من r parameter، فيقوم باستخدام الجزء ControllerID لتحديد ال controller class المطلوب ليقوم بعمل instance منه، ومن ثم يقوم ال controller باستخدام ال "ActionID" لتحديد ال action المطلوب والذي سيقوم بالعمل الفعلي. + في المثال الخاص بنا، فإن ال route هو "site/say"، وهذا ال route يتم معالجته ليستدعي ال controller class المسمى ب SiteController و ال action المسمى ب actionSay داخل هذا ال controller، ونتيجة لذلك سيتم فعليا إستدعاء الدالة SiteController::actionSay() لتقوم بمعالجة ال request كما يلزم. +

+ +

+ معلومة: مثل ال actions، تحتوي ال controllers أيضًا على Uniquely IDs يتم تعريفها واستخدامها من خلال التطبيق. تستخدم ال Controllers IDs نفس قواعد التسمية الخاصة بال action IDs، ويتم ذلك من خلال حذف ال dashes و capitalizing أول حرف من كل كلمة، ثم إضافة كلمة Controller الى الإسم الناتج -وهنا الإختلاف عن ال action ID-. مثال: ال controller ID المسمى ب "post-comment" ستم معالجته ليصبح PostCommentController. +

+ + +##
الخلاصة
+ +

+ في هذا الموضوع، قمنا بالتعرف على ال controller وال view كأجزاء من MVC architectural pattern، كما قمنا بإنشاء action داخل controller موجود ليستقبل specific request ويتحكم فيه، وقمنا أيضا بإنشاء view لعرض المحتوى. في هذا المثال البسيط، لم نتطرق الى ال model، وقمنا فقط باستخدام ال data بشكل مباشر من خلال ال message parameter. +

+ +

+ كما تعرفنا أيضا على ال routes في ال Yii، والتي تعمل بدورها كجسر بين ال user request وال controller actions. +

+ +

+ في الموضوع القادم، ستتعلم كيف يمكنك إنشاء model وكيف يمكنك إنشاء صفحة جديدة تحتوي على Html form. +

diff --git a/docs/guide-ar/start-installation.md b/docs/guide-ar/start-installation.md new file mode 100644 index 00000000000..600c0d8e94c --- /dev/null +++ b/docs/guide-ar/start-installation.md @@ -0,0 +1,303 @@ +#
تثبيت ال Yii
+ +

يمكنك تثبيت ال Yii بطريقتين ، الأولى باستخدام مدير الحزم Composer أو عن طريق تنزيل Archive File. الطريقة الأولى هي الطريقة المفضلة للعمل، ، لأنها تتيح لك تثبيت [extensions - ملحقات أو اضافات] جديدة، أو تحديث إطار العمل Yii ببساطة عن طريق تشغيل أمر واحد فقط. +

+ +

+ التثبيت الإفتراضي لل Yii ينتج عنه بنية تركيبة منظمة ومرتبة للمجلدات والملفات التي بداخلها، ويوفر هذا الكلام بعض المميزات التي يتم إضافتها وإنشائها بشكل تلقائي مثل صفحة تسجيل الدخول، ونموذج اتصل بنا...الخ، هذا الأمر سيشكل نقطة إنطلاق جيدة لبدء العمل على أي مشروع. +

+ +

+ في هذه الصفحة من التوثيق سنقوم بشرح ووصف كيف يمكن تثبيت إطار العمل Yii وبالتحديد Yii2 Basic Project Template. + هناك Template آخر موجود بإطار العمل Yii وهو Yii2 Advanced Project Template، وهو الأفضل للعمل وإنشاء المشاريع لفريق عمل برمجي، ولتطوير المشاريع متعددة الطبقات(multiple tires). +

+ +

+معلومة: قالب المشروع الأساسي (Basic) مناسب لتطوير 90% من تطبيقات الويب. ويختلف القالب المتقدم (Advanced Template) عن القالب الأساسي في كيفية تنظيم وهيكلة الشيفرة البرمجية. +اذا كنت جديدا في عالم تطوير تطبيقات الويب باستخدام ال Yii، فإننا نوصيك بقوة بأن تستخدم القالب الأساسي في بناء المشروع الخاص بك. +

+ + +##
تثبيت ال Yii من خلال (Composer)
+ +###
تثبيت ال Composer
+ +

+إن لم يكن لديك Composer مثبت مسبقا، فيمكنك السير بخطوات تثبيته من خلال الدخول الى هذا الرابط https://getcomposer.org/download/. +لتثبيت ال Composer في كل من نظامي Linux و Max OS X، يمكنك تنفيذ الأوامر التالية: +

+ +```bash +curl -sS https://getcomposer.org/installer | php +sudo mv composer.phar /usr/local/bin/composer +``` +

+ ولنظام ويندوز يمكنك تثبيت ال Composer-Setup.exe ومن ثم عمل run +

+ +

+يرجى الدخول الى Troubleshooting section of the Composer Documentation في حال واجهتك أي مشاكل متعلقة بال composer, وإذا كنت مستخدمًا جديدًا لل composer، ننصحك أيضًا بقراءة قسم الاستخدام الأساسي على الأقل من التوثيف الخاص بال composer. +

+ +

+ في هذا الدليل ، نفترض أنك قمت بتثبيت ال composer على مستوى جميع المشاريع (globally) بحيث تكون أوامر ال composer متاحة لجميع المشاريع من أي مكان. أما إذا كنت تستخدم ال composer.phar لمسار محدد فقط(local directory)، فيجب عليك ضبط الأومر وفقًا لذلك. +

+ +

+إذا كان ال composer مثبتًا من قبل، فتأكد من استخدام إصدار حديث. يمكنك تحديث ال composer عن طريق تنفيذ الأمر التالي composer self-update +

+ +

+ ملاحظة مهمة: أثناء تثبيت ال Yii ، سيحتاج ال composer إلى طلب(request) الكثير من المعلومات من ال Github Api. يعتمد عدد الطلبات على عدد dependencies التي يمتلكها التطبيق الخاص بك، وقد يكون هذا العدد أكبر من الحد المسموح به من قبل ال Github Api (Github API rate limit). إذا وصلت الى الحد الأعلى المسموح به من الطلبات، فقد يطلب منك ال composer بيانات تسجيل الدخول إلى Github، وذلك للحصول على رمز (token) للدخول إلى Github Api. اذا كانت عمليات الإتصال سريعة، فقد تصل إلى هذا الحد(limit) قبل أن يتمكن ال composer من التعامل معه ، لذالك نوصي بتكوين رمز الدخول(access token) قبل تثبيت ال Yii. يرجى الرجوع إلى التوثيق الخاص بال Composer والإطلاع على التعليمات الخاصة Github API tokens للحصول على الإرشادات اللازمة للقيام بذلك. +

+ +###
تثبيت ال Yii
+ +

+ من خلال ال Composer، يمكنك الآن تثبيت ال Yii من خلال تنفيذ سطر الأوامر التالي داخل أي مسار يمكن الوصول اليه من قبل الويب +

+ +```bash +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` +

+ سطر الأوامر السابق سيقوم بتثبيت أحدث نسخة مستقرة(stable) من إطار العمل Yii داخل مسار جديد اسمه basic، ويمكنك التعديل على سطر الأوامر السابق لتغيير اسم المشروع لأي اسم ترغب فيه. +

+ +

+معلومة: اذا واجهتك أي مشكلة عند تنفيذ السطر `composer create-project` فيمكنك الذهاب إلى قسم استكشاف الأخطاء في ال composer. +في معظم الأخطاء الشائعة، وعند حل المشكلة أو الخطأ، يمكنك إكمال التثبيت من خلال الدخول الى المسار `basic` ومن ثم تنفيذ الأمر التالي: `composer update`. +

+ +

+ تلميح: اذا كنت ترغب بتثبيت أحدث نسخة خاصة بالمطورين من ال Yii، فيمكنك ذلك من خلال إضافة الخيار stability وذلك من خلال سطر الأوامر التالي: +

+ +```bash + composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +``` +

+ ملاحظة: نسخة المطورين من ال Yii يجب أن يتم إستخدامها للمواقع الإلكترونية التي لن تصدر كنسخة نهائية للمستخدم(Not for production) لأن ذلك يمكن أن يسبب بإيقاف المشروع أو الشيفرة البرمجية الخاصة بك. +

+ +###
تثبيت ال Yii من خلال ال Archive File
+-------------------------- + +

+يتضمن تثبيت Yii من ملف أرشيف ثلاث خطوات وهي: +

+
    +
  1. تثبت الملف من خلال الموقع الرسمي yiiframework.com.
  2. +
  3. قم بفك ضغط الملف الذي تم تنزيله إلى مجلد يمكن الوصول إليه عبر الويب.
  4. +
  5. قم بتعديل ملف config/web.php عن طريق إدخال secret key ل cookieValidationKey +(يتم ذلك تلقائيًا إذا قمت بتثبيت ال Yii باستخدام Composer):
  6. +
+ + ```php + // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` + + +###
خيارات تثبيت أخرى
+-------------------------- + +

+توضح تعليمات التثبيت أعلاه كيفية تثبيت ال Yii ، والذي يقوم أيضًا بإنشاء تطبيق ويب أساسي(basic). +هذا النهج هو نقطة انطلاق جيدة لمعظم المشاريع، صغيرة كانت أو كبيرة. خصوصا اذا كنت قد بدأت تعلم ال Yii من وقت قريب. +

+لكن، هناك خيارات أخرى متاحة لتثبيت ال Yii وهي: +

+ +
    +
  • إذا كنت ترغب فقط في تثبيت ال core لإطار العمل Yii، وترغب ببناء المكونات الخاصة بإطار العمل كما ترغب أنت وبطريقتك أنت، يمكنك اتباع التعليمات كما هو موضح في هذه الصفحة Building Application from Scratch.
  • +
  • إذا كنت تريد البدء بتطبيق أكثر تعقيدًا وأكثر إحترافية، ويتناسب بشكل أفضل مع وجود فريق عمل تقني، +فأنت اذا سترغب بتثبيت ال Advanced Project Template +
  • +
+ + +###
تثبيت ال Assets
+-------------------------- + +

+ تعتمد ال Yii على حزم Bower و/أو NPM لتثبيت مكتبات ال (CSS و JavaScript). ويستخدم ال composer للحصول على هذه المكتبات ، مما يسمح بالحصول على إصدارات ال PHP و CSS/JavaScript في نفس الوقت. ويمكن تحقيق ذلك إما عن طريق استخدام asset-packagist.org أو من خلال ال composer asset plugin، يرجى الرجوع إلى Assets documentation لمزيد من التفاصيل. +

+قد ترغب في إدارة ال assets عبر ال native Bower/NPM أو استخدام ال CDN أو تجنب تثبيت ال assets بالكامل من حلال ال Composer ، ويمكن ذلك من خلال إضافة الأسطر التالية إلى "composer.json": +

+ +```json +"replace": { + "bower-asset/jquery": ">=1.11.0", + "bower-asset/inputmask": ">=3.2.0", + "bower-asset/punycode": ">=1.3.0", + "bower-asset/yii2-pjax": ">=2.0.0" +}, +``` + +

+ملاحظة: في حالة تجاوز تثبيت ال assets عبر ال Composer، فأنت المسؤول عن تثبيت ال assets وحل مشكلات التعارض بين الإصدارات والمكتبات المختلفة. وكن مستعدًا لعدم تناسق محتمل بين ملفات ال asstes والإضافات المختلفة. +

+ +###
التحقق من التثبيت
+-------------------------- + +

+ بعد الانتهاء من التثبيت، ستحتاج الى القيام بإعداد خادم الويب الخاص بك(your web server) (انظر القسم التالي) أو قم باستخدام built-in PHP web server عن طريق تنفيذ الأمر التالي داخل المسار web في المشروع الخاص بك: +

+ +```bash +php yii serve +``` + +

+ملاحظة: افتراضيًا ال HTTP-server يعمل على البورت 8080. ومع ذلك ، إذا كان هذا البورت قيد الاستخدام بالفعل أو كنت ترغب في تشغيل أكثر من تطبيق بهذه الطريقة، حينها سيلزمك تحديد البورت الذي يجب استخدامه. ما عليك سوى إضافة --port: +

+ +```bash +php yii serve --port=8888 +``` +

+ يمكنك استخدام الرابط الموجود في الأسفل للوصول الى تطبيق ال Yii الذي قمت بتثبيته وتنفيذ الأوامر السابقة عليه. +

+ +``` +http://localhost:8080/ +``` + +![Successful Installation of Yii](../guide/images/start-app-installed.png) + +

+ اذا كانت كل الإعدادات السابقة تعمل بشكل صحيح، فيجب أن ترى الصورة الموجودة بالأعلى "Congratulations!" على المتصفح. إذا لم يكن كذلك، يرجى التحقق مما إذا كان تثبيت الPHP الخاص بك متوافق مع متطلبات ال Yii. يمكنك التحقق من ذلك باستخدام أحد الأساليب التالية: +

+ +
    +
  • قم بنسخ الملف /requirements.php الى المسار /web/requirements.php بحيث يمكنك الوصول الى الصفحة من خلال الرابط التالي: http://localhost/requirements.php
  • +
  • قم بتنفيذ الأوامر التالية:
    + cd basic +
    php requirements.php
  • +
+ +

+ يجب عليك أن تقوم بتثبيت وإعداد ال PHP الخاص بك بحيث تلبي الحد الأدنى من متطلبات ال Yii. الأهم من ذلك يجب أن يكون الإصدار الخاص بال PHP أعلى أو يساوي 5.4. من الناحية المثالية أحدث إصدار يعمل مع ال Yii هو ال PHP 7. يجب عليك أيضًا تثبيت ال PDO PHP Extension. +

+ + +###
إعداد ال Web Servers
+----------------------- + +

+معلومة: يمكنك تخطي هذا الجزء الآن إذا كنت تختبر فقط إطار العمل Yii دون أي نية لنشر هذا التطبيق على الويب(بدون رفع التطبيق على production server). +

+ +

+ يجب أن يعمل التطبيق الذي تم تثبيته وفقًا للتعليمات المذكورة أعلاه مع أي من الخوادم ال Apache HTTP أو ال Nginx HTTP في كل من أنظمة التشغيل Windows, Mac OS X أو Linux ممن لديها إصدار أعلى أو يساوي PHP 5.4، كما أن ال Yii 2.0 متوافق مع ال Facebook HHVM، لكن، يجب أن تأخذ بعين الإعتبار أن ال HHVM يسلك في بعض الأحيان بطريقة مختلفة عن ال Native PHP، لذلك يجب أن تأخذ عناية إضافية عندما تعمل على ال HHVM. +

+ +

+ على ال production server، قد ترغب في إعداد خادم الويب الخاص بك بحيث يمكن الوصول إلى التطبيق +الخاص بك عبر ال URL التالي https://www.example.com/index.php بدلاً من https://www.example.com/basic/web/index.php. هذا الكلام يتطلب إنشاء إعداد يقوم بتوجيه ال document root الموجود على ال web server الى مجلد ال basic/web، كما قد ترغب أيضا بإخفاء ال index.php من ال URL كما هو موضح في ال Routing and URL Creation. في هذا الموضوع ستتعلم كيف يمكنك إعداد ال Apache أو ال Nginx server لتحقيق هذه الأهداف. +

+ +

+ معلومة: من خلال تعيين ال basic/web ك document root، فإنك بذلك تمنع أيضًا المستخدمين النهائيين من الوصول الى الشيفرة البرمجية الخاصة بالتطبيق الخاص بك، وتمنعهم من الوصول الى الملفات الحساسة والمهمة والمخزنة في sibling directories من basic/web، ويعبر رفض الوصول الى المجلدات الأخرى تحسينا أمنيا مهما، يساعد في الحفاظ على مستوى أعلى من الحماية. +

+ +

+معلومة: إذا كان سيتم تشغيل التطبيق الخاص بك في بيئة استضافة مشتركة(shared hosting) حيث ليس لديك الصلاحية لتعديل الإعدادات الخاصة بال web server، ستحتاج حينها الى تعديل في البنية الخاصة بالمشروع للحصول على أفضل أمان ممكن. يرجى الرجوع إلى Shared Hosting Environment لمزيد من المعلومات. +

+ +

+ معلومة: إذا كنت تقوم بتشغيل تطبيق ال Yii بوجود ال proxy، فقد تحتاج إلى إعداد التطبيق ليكون ضمن ال trusted proxies and header. +

+ +###
الإعدادات الموصى بها لل Apache
+----------------------- + +

+ استخدم الإعدادات التالية في ملف ال httpd.conf في Apache أو ضمن إعدادات ال virtual host. + ملاحظة: يجب عليك استبدال المسار التالي path/to/basic/web بالمسار الفعلي للتطبيق الخاص بك وصولا الى ال basic/web. +

+ +```apache +# Set document root to be "basic/web" +DocumentRoot "path/to/basic/web" + + + # use mod_rewrite for pretty URL support + RewriteEngine on + # If a directory or a file exists, use the request directly + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Otherwise forward the request to index.php + RewriteRule . index.php + + # if $showScriptName is false in UrlManager, do not allow accessing URLs with script name + RewriteRule ^index.php/ - [L,R=404] + + # ...other settings... + +``` + + +###
الإعدادات الموصى بها لل Nginx
+----------------------- + +

+ لاستخدام Nginx، يجب تثبيت PHP على أنه FPM SAPI، ويمكنك استخدام إعدادات ال Nginx التالية، مع الإنتباه على استبدال المسار من path/to/basic/web الى المسار الفعلي وصولا إلى basic/web بالإضافة الى إستبدال mysite.test إلى ال hostname الخاص بالتطبيق. +

+ + +```nginx +server { + charset utf-8; + client_max_body_size 128M; + + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 + + server_name mysite.test; + root /path/to/basic/web; + index index.php; + + access_log /path/to/basic/log/access.log; + error_log /path/to/basic/log/error.log; + + location / { + # Redirect everything that isn't a real file to index.php + try_files $uri $uri/ /index.php$is_args$args; + } + + # uncomment to avoid processing of calls to non-existing static files by Yii + #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + # try_files $uri =404; + #} + #error_page 404 /404.html; + + # deny accessing php files for the /assets directory + location ~ ^/assets/.*\.php$ { + deny all; + } + + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; + #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; + } + + location ~* /\. { + deny all; + } +} +``` +

+ عند استخدامك لهذا الإعداد، يجب عليك أيضًا تعيين cgi.fix_pathinfo = 0 في ملف php.ini + من أجل تجنب العديد من طلبات ال stat() الغير الضرورية للنظام. +

+ +

+ لاحظ أيضًا أنه عند تشغيل خادم HTTPS، تحتاج إلى إضافة fastcgi_param HTTPS on; +بحيث يمكنك إكتشاف إذا ما كان الاتصال آمنًا أم لا. +

diff --git a/docs/guide-ar/start-looking-ahead.md b/docs/guide-ar/start-looking-ahead.md new file mode 100644 index 00000000000..4b760f8dca6 --- /dev/null +++ b/docs/guide-ar/start-looking-ahead.md @@ -0,0 +1,36 @@ +#
ماذا الآن - الخطوة القادمة
+ +

+إذا قمت بقرائة الفصل الخاص ب "البداية من هنا"، فأنت الآن قادر على بناء تطبيق Yii متكامل، لقد تعلمت كيف يمكنك تنفيذ وإستخدام أكثر الخصائص والمميزات المشتركة، مثل جلب البيانات من قاعدة البيانات، وأخذ البيانات من المستخدمين، ومن ثم عرضها، كما تعلمت كيف يمكنك تقسيم البيانات الى صفحات، وقمنا باستخدام ال Gii وتعلمنا كيف يمكننا إنشاء شيفرة برمجية من خلاله بشكل تلقائي، ومن الممارسة تعلمنا أن إنشاء الشيفرة البرمجية من خلال ال Gii يجعل من عملية تطوير المواقع والوظائف المطلوبة أمرا بسيطا وسهلا للغابة، كل ما عليك القيام به هو تعبئة ال forms. +

+ +

+ في هذا الجزء من التوثيق سنعرض ملخص للمصادر المتاحة لل Yii، والتي ستساعدك في تحسين إنتاجيتك عند إستخدامك لبيئة العمل Yii. +

+ +
    +
  • + التوثيق +
      +
    • The Definitive Guide - الدليل الشامل: كما يشير الإسم، فإن هذا الدليل يحدد آلية عمل ال Yii بدقة عالية، ويوفر إرشادات حول كيفية إستخدام ال Yii، هذا الجزء الأكثر أهمية في ال Yii، والذي يجب عليك قرائته قبل كتابة أي Yii code (ملاحظة: جزء البداية من هنا، والذي قمنا بدراسته هو أحد هذه الأجزاء، ومن أهمها للبدء بإنشاء التطبيقات من خلال ال Yii).
    • +
    • The Class Reference - المرجع الخاص بال Class في هذا الجزء يتم تحديد الية إستخدام كل Class يقدمه ال Yii، في العادة يتم إستخدام هذا المرجع عند كتابة شيفرة برمجية وأنت ترغب في فهم آلية العمل ل Class معين، أو Method, او فهم Proporty معينة...الخ، من الأفضل إستخدام المرجع الخاص بال Class فقط عند فهم آلية العمل لل Yii.
    • +
    • The Wiki Articles - مقالات الويكي مقالات الويكي هي مجموعة من الخبرات العملية للمستخدمين، تمت كتابتها ونشرها على شكل مقالات لمشاركة الخبرات، ومعظم هذه الكتابات تكون مثل الوصفات الخاصة بالطبخ، موجودة لخدمة هدف معين، وحل مشكلة محددة باستخدام ال Yii، وبالرغم من أن هذه الكتابات قد لا تكون بجودة ودقة الدليل الشامل، الا أنها قد تغطي مواضيع أكثر، وتطرح أيضا حلولا مباشرة للإستخدام.
    • +
    • الكتب
    • +
    +
  • +
  • Extensions - الملحقات: تفتخر ال Yii بوجود مكتبة ضخمة من الملحقات التي تمت برمجتها وإضافتها من قبل المستخدمين المتطوعين الذين شاركوا أعمالهم وطورها لتجعل مهمة المطورين الآخرين أسهل وأسرع في تطوير التطبيفات المبنية بواسطة ال Yii.
  • +
  • + المجتمع + +
  • +
diff --git a/docs/guide-ar/start-prerequisites.md b/docs/guide-ar/start-prerequisites.md new file mode 100644 index 00000000000..03050b54079 --- /dev/null +++ b/docs/guide-ar/start-prerequisites.md @@ -0,0 +1,25 @@ +#
ماذا يجب أن تعرف قبل البدء بال Yii
+ +

+ منحنى التعلم الخاص بال Yii ليس حادًا مثل أطر PHP الأخرى، ولكن لا يزال هناك بعض الأشياء التي يجب أن تتعلمها قبل البدء بـال Yii. +

+ +##
PHP
+ +

+ ال Yii هو إطار عمل PHP، لذا تأكد من قراءة وفهم المرجع الرسمي الخاص بلغة ال PHP. عند البدء بتطوير المشاريع أو التطبيقات باستخدام ال Yii ، ستكتب التعليمات البرمجية بطريقة كائنية التوجه OOP، لذا تأكد من أنك على دراية بـمفاهيم ال OOP وكذلك ال namespaces. +

+ +##
البرمجة كائنية التوجه object oriented programming
+ +

+ كمبرمج أو مطور يرغب بالعمل على ال Yii، يجب عليك أن تمتلك المعرفة الأساسية للبرمجة كائنية التوجه OOP. إذا لم تكن على دراية بها ، فيمكنك تعلم ذلك من خلال واحدة من هذه الدورات المنتشرة مثل هذه الدورة من tuts+.
+ملاحظة: كلما زاد تعقيد التطبيق أو المشروع الذي تعمل عليه، كلما احتجت الى مستوى أعلى وإحترافي أكثر من مفاهيم ال OOP لحل وإدارة التعقديات التي ستترب على مثل هذه المشاريع. +

+ +##
Command line and composer
+ +

تستخدم ال Yii بشكل كبير de-facto standard PHP package manager، ال Composer، لذلك تأكد من قرائتك وفهمك لهذا الموضوع قبل أن تبدء. بالإضافة الى ذلك إذا لم تكن على دراية باستخدام سطر الأوامر (command line) ، فقد حان الوقت لبدء المحاولة. بمجرد تعلم الأساسيات ، لن ترغب في العمل بدون إستخدام سطر الأوامر.
+ال composer: ويترجم حرفيا الى كلمة "الملحن"، وهي عبارة عن أداة لإدارة المشاريع البرمجية والتي تسمح لك بتحديث وتنزيل المكتبات البرمجية المطلوبة للمشروع الخاص بك. +

+ diff --git a/docs/guide-ar/start-workflow.md b/docs/guide-ar/start-workflow.md new file mode 100644 index 00000000000..edc6977d876 --- /dev/null +++ b/docs/guide-ar/start-workflow.md @@ -0,0 +1,114 @@ +#
تشغيل التطبيقات
+ +

+بعد تثبيت ال Yii، سيكون لديك تطبيق Yii جاهز للعمل عليه ويمكن الوصول إليه عبر +الرابط التالي: https://hostname/basic/web/index.php أو https://hostname/index.php إعتمادا على الإعدادات +الخاصة بك (إعدادت ال web server). في هذا الجزء سنستعرض الوظائف ال built-in الموجودة في التطبيق الإفتراضي لإطار العمل Yii، وكيف يقوم بتنظيم الشيفرة البرمجية، وكيف يعالج (handling) هذا التطبيق الطلبات (requests) بشكل عام. +

+ +

+ معلومة: من أجل تبسيط الطرح، ومن خلال هذا البرنامج التعليمي " Getting Started - البداية من هنا"، من المفترض أنك قمت بتعيين basic/web ك document root لل Web server، وقد قمت أيضا بإعداد ال Url الذي يسمح لك بالوصول الى التطبيق المثبت من خلاله ليكون على الشكل التالي: https://hostname/index.php أو ما شابه ذلك. +اذا لم تقم بذلك، ولتلبية إحتياجاتك في هذا البرنامج التعليمي، يرجى ضبط ال Url كما هو موضح في هذه الصفحة. +يمكنك معرفة الضبط الخاص بال Web server من هنا: تثبيت ال Yii +

+ +

+ملاحظة: بخلاف إطار العمل نفسه(Yii framework)، بعد تثبيت ال template الخاص بالمشروع، يكون كل شيء في هذا التطبيق يخصك أنت، بحيث تملك الحرية في إضافة أو حذف أو تعديل كل ما تحتاج اليه. +

+ + +##
خصائص / وظائف التطبيق المثبت - Functionality
+ +

+ يحتوي ال Basic ِApplication Template الذي قمنا بتثبيته على أربع صفحات: +

+ +
    +
  • الصفحة الرئيسية(Homepage): يتم عرض هذه الصفحة من خلال الرابط التالي https://hostname/index.php
  • +
  • صفحة من نحن(About)
  • +
  • صفحة اتصل بنا (Contact): في هذه الصفحة يتم عرض form يسمح للأعشاء بالإتصال بك من خلال البريد الإلكتروني.
  • +
  • صفحة تسجيل الدخول (Login): في هذه الصفحة يتم عرض form يسمح للأعضاء بالحصول على الإذن لإستخدام الخصائص التي لا يجوز لغيرهم من الوصول اليها، قم بتجربة تسجيل الدخول من خلال استخدام admin/admin ولاحظ أن كلمة "Login" ستختفي من القائمة الرئيسية وستظهر محلها الكلمة "Logout"
  • +
+ +

+هذه الصفحات تشترك بامتلاكها common header and footer -الترويسة أعلى الصفحة، والذيل أسفل الصفحة-. ويحتوي ال header على القائمة الرئيسية (main menu) والتي بدورها تسمح لك بالتنقل بين الصفحات المختلفة. +

+ +

+ أيضا، يجب عليك أن تنظر الى ال toolbar الموجود في أسفل نافذة المتصفح. ال debugger tool هذه تعتبر كأداة مفيدة مقدمة من ال Yii لتسجيل وعرض الكثير من المعلومات وتصحيح الأخطاء، مثل log messages, response statuses, the database queries run وما إلى ذلك. +

+ +

+ بالإضافة إلى ال web application، يوجد هناك "console script" يسمى ب yii، والذي ستجده في المسار الرئيسي للتطبيق. هذا السكربت يمكن استخدامه لتشغيل المهام التي تعمل في الخفاء (background) أو لتنفيذ مهام الصيانة (ال maintenance).
ستجد الوصف الخاص بهذا السكربت + داخل هذه الصفحة Console Application Section. +

+ + + +##
هيكلية التطبيق - Application Structure
+ +

+ أكثر المسارات والملفات أهمية الموجودة داخل التطبيق (بافتراض أن ال application's root directory هو basic) هي: +

+ +``` +basic/ application base path + composer.json used by Composer, describes package information + config/ contains application and other configurations + console.php the console application configuration + web.php the Web application configuration + commands/ contains console command classes + controllers/ contains controller classes + models/ contains model classes + runtime/ contains files generated by Yii during runtime, such as logs and cache files + vendor/ contains the installed Composer packages, including the Yii framework itself + views/ contains view files + web/ application Web root, contains Web accessible files + assets/ contains published asset files (javascript and css) by Yii + index.php the entry (or bootstrap) script for the application + yii the Yii console command execution script +``` + +

+بشكل عام، يمكن تقسيم الملفات داخل التطبيق إلى نوعين: الاول تجده تحت المسار التالي: basic/web والثاني تجده بالمسارات الأخرى.
+وبنائا على ذلك، فإنه من الممكن الوصول إلى النوع الأول مباشرة عبر ال HTTP (أي من خلال المتصفح) ، بينما لا يمكن أن يكون ذلك للنوع الثاني. +

+ +

+ يعتمد ال Yii على إستخدام ال MVC، وال MVC هو أحد ال Architectural Pattern، وهي اختصار ل model-view-controller، +هذا الأسلوب ينعكس في تنظيم المسارات الخاصة بالملفات كما في الشكل أعلاه. يحتوي المسار models على جميع الكلاس (model classes) ، ويحتوي مسار ال views على جميع الصفحات التي ستستخدم في العرض (view scripts)، ويحتوي مسار الcontrollers على + جميع (controller classes) +

+ +

+ يوضح المخطط التالي ال static structure للتطبيق. +

+ +![Static Structure of Application](../guide/images/application-structure.png) + +

+ يحتوي كل تطبيق على نص برمجي يستخدم للدخول الى التطبيق (كبوابة بعدها يظهر التطبيق للناظر)، ويسمى هذا الجزء بال entry script، وهو يمثل الصفحة web/index.php، ويعد هذا المدخل النص البرمجي الوحيد الذي يمكن الوصول إليه من خلال ال PHP في التطبيق، ويعمل هذا ال entry script على أخذ ال request ومن ثم إنشاء instance خاص بالتطبيق ليستطيع التعامل معه (التعامل مع التطبيق ومكوناته). + يقوم application على معالجة ال request بمساعدة من ال components، ومن ثم بقوم التطبيق بإرسال ال request الى عناصر ال MVC، كما يتم استخدام ال Widgets في ال views للمساعدة في إنشاء العناصر المعقدة والمتغيرة (Complex & Dynamic user interface) لواجهة المستخدم. +

+ +##
دورة الحياة الخاصة بال request
+ +

+يوضح المخطط التالي كيفية معالجة التطبيق ل request معين. +

+ +![Request Lifecycle](../guide/images/request-lifecycle.png) + +
    +
  1. يقوم المستخدم بعمل request لل entry script web/index.php.
  2. +
  3. يقوم ال entry script على جلب الإعدادات الخاصة بالتطبيق ومن ثم إنشاء ال instance الخاص بالتطبيق ليستطيع التحكم ب request وإدارتها.
  4. +
  5. يقوم التطبيق بمعالجة ال requested route بمساعدة من ال request application component.
  6. +
  7. يقوم التطبيق على إنشاء instance من ال controller للتحكم بال request.
  8. +
  9. يقوم ال controller على إنشاء action instance مع مجموعة من الفلاتر(المرشحات) الخاصة بهذا ال action.
  10. +
  11. في حالة فشل أي فلتر، يتم إلغاء الإجراء.
  12. +
  13. في حال نجاح جميع الفلاتر ، يتم تنفيذ الإجراء.
  14. +
  15. يقوم ال action بجلب بعض البيانات الخاصة بال models, وفي الغالب ستكون من قاعدة البيانات إن أمكن ذلك.
  16. +
  17. سيقوم ال action بجلب ال view ليقوم بتقديم البيانات التي تم جلبها لل view.
  18. +
  19. عملية الجلب السابقة ستقوم على إرجاع النتائج الى response application component
  20. +
  21. بعد ذلك سيقوم ال response component بإرسال النتيجة النهائية الى المتصفح الخاص بالمستخدم.
  22. +
diff --git a/docs/guide-de/README.md b/docs/guide-de/README.md new file mode 100644 index 00000000000..b09aee46e61 --- /dev/null +++ b/docs/guide-de/README.md @@ -0,0 +1,207 @@ +Das umfassende Handbuch für Yii 2.0 +=================================== + +Dieses Tutorial wurde unter den [Bedingungen der Yii-Dokumentation](https://www.yiiframework.com/doc/terms/) veröffentlicht. + +Alle Rechte vorbehalten. + +2014 (c) Yii Software LLC. + + +Einführung +---------- + +* [Über Yii](intro-yii.md) +* [Upgrade von Version 1.1](intro-upgrade-from-v1.md) + + +Einstieg +-------- + +* [Yii installieren](start-installation.md) +* [Running Applications](start-workflow.md) +* [Hallo sagen](start-hello.md) +* [Arbeiten mit Formularen](start-forms.md) +* [Arbeiten mit Datenbanken](start-databases.md) +* [Code generieren mit Gii](start-gii.md) +* [Ausblick](start-looking-ahead.md) + + +Application Struktur +-------------------- + +* [Überblick](structure-overview.md) +* [Entry Scripts](structure-entry-scripts.md) +* [Applications](structure-applications.md) +* [Application Komponenten](structure-application-components.md) +* [Controller](structure-controllers.md) +* [Model](structure-models.md) +* [View](structure-views.md) +* **TBD** [Filters](structure-filters.md) +* **TBD** [Widgets](structure-widgets.md) +* **TBD** [Modules](structure-modules.md) +* [Assets](structure-assets.md) +* **TBD** [Extensions](structure-extensions.md) + + +Anfragen bearbeiten +------------------- + +* **TBD** [Bootstrapping](runtime-bootstrapping.md) +* **TBD** [Routing](runtime-routing.md) +* **TBD** [Requests](runtime-requests.md) +* **TBD** [Responses](runtime-responses.md) +* **TBD** [Sessions and Cookies](runtime-sessions-cookies.md) +* [Parsen und Generieren von URLs](runtime-url-handling.md) +* [Fehlerbehandlung](runtime-handling-errors.md) +* [Logging](runtime-logging.md) + + +Kern-Konzepte +------------- + +* [Komponenten](concept-components.md) +* [Eigenschaften](concept-properties.md) +* [Events (Ereignisse)](concept-events.md) +* [Behaviors](concept-behaviors.md) +* [Konfiguration](concept-configurations.md) +* [Aliase](concept-aliases.md) +* [Class Autoloading](concept-autoloading.md) +* [Service Locator](concept-service-locator.md) +* [Dependency Injection Container](concept-di-container.md) + + +Arbeiten mit Datenbanken +------------------------ + +* [Data Access Objects](db-dao.md) - Connecting to a database, basic queries, transactions and schema manipulation +* [Query Builder](db-query-builder.md) - Querying the database using a simple abstraction layer +* [Active Record](db-active-record.md) - The active record ORM, retrieving and manipulating records and defining relations +* [Migrations](db-migrations.md) - Version control your databases in a team development environment +* **TBD** [Sphinx](db-sphinx.md) +* **TBD** [Redis](db-redis.md) +* **TBD** [MongoDB](db-mongodb.md) +* **TBD** [ElasticSearch](db-elastic-search.md) + + +Eingabedaten verarbeiten +------------------------ + +* [Creating Forms](input-forms.md) +* [Validating Input](input-validation.md) +* **TBD** [Uploading Files](input-file-upload.md) +* **TBD** [Getting Data for Multiple Models](input-multiple-models.md) + + +Ausgabe von Daten +----------------- + +* **TBD** [Data Formatting](output-formatting.md) +* **TBD** [Pagination](output-pagination.md) +* **TBD** [Sorting](output-sorting.md) +* [Data Providers](output-data-providers.md) +* [Data Widgets](output-data-widgets.md) +* [Theming](output-theming.md) + + +Sicherheit +---------- + +* [Authentication](security-authentication.md) +* [Authorization](security-authorization.md) +* [Working with Passwords](security-passwords.md) +* **TBD** [Auth Clients](security-auth-clients.md) +* **TBD** [Best Practices](security-best-practices.md) + + +Caching +------- + +* [Overview](caching-overview.md) +* [Data Caching](caching-data.md) +* [Fragment Caching](caching-fragment.md) +* [Page Caching](caching-page.md) +* [HTTP Caching](caching-http.md) + + +RESTful Web Services +-------------------- + +* [Quick Start](rest-quick-start.md) +* [Resources](rest-resources.md) +* [Controllers](rest-controllers.md) +* [Routing](rest-routing.md) +* [Response Formatting](rest-response-formatting.md) +* [Authentication](rest-authentication.md) +* [Rate Limiting](rest-rate-limiting.md) +* [Versioning](rest-versioning.md) +* [Error Handling](rest-error-handling.md) + + +Development Tools +----------------- + +* [Debug Toolbar and Debugger](tool-debugger.md) +* [Generating Code using Gii](tool-gii.md) +* **TBD** [Generating API Documentation](tool-api-doc.md) + + +Testing +------- + +* [Overview](test-overview.md) +* **TBD** [Unit Tests](test-unit.md) +* **TBD** [Functional Tests](test-functional.md) +* **TBD** [Acceptance Tests](test-acceptance.md) +* [Fixtures](test-fixtures.md) + + +Yii erweitern +------------- + +* [Creating Extensions](extend-creating-extensions.md) +* [Customizing Core Code](extend-customizing-core.md) +* [Using 3rd-Party Libraries](extend-using-libs.md) +* **TBD** [Using Yii in 3rd-Party Systems](extend-embedding-in-others.md) +* **TBD** [Using Yii 1.1 and 2.0 Together](extend-using-v1-v2.md) +* [Using Composer](extend-using-composer.md) + + +Weitere Themen +-------------- + +* [Advanced Application Template](tutorial-advanced-app.md) +* [Building Application from Scratch](tutorial-start-from-scratch.md) +* [Console Commands](tutorial-console.md) +* [Core Validators](tutorial-core-validators.md) +* [Internationalization](tutorial-i18n.md) +* [Mailing](tutorial-mailing.md) +* [Performance Tuning](tutorial-performance-tuning.md) +* **TBD** [Shared Hosting Environment](tutorial-shared-hosting.md) +* [Template Engines](tutorial-template-engines.md) + + +Widgets +------- + +* GridView: link to demo page +* ListView: link to demo page +* DetailView: link to demo page +* ActiveForm: link to demo page +* Pjax: link to demo page +* Menu: link to demo page +* LinkPager: link to demo page +* LinkSorter: link to demo page +* [Bootstrap Widgets](bootstrap-widgets.md) +* [Jquery UI Widgets](jui-widgets.md) + + +Helfer-Klassen +-------------- + +* [Overview](helper-overview.md) +* **TBD** [ArrayHelper](helper-array.md) +* **TBD** [Html](helper-html.md) +* **TBD** [Url](helper-url.md) +* **TBD** [Security](helper-security.md) + diff --git a/docs/guide-de/blocktypes.json b/docs/guide-de/blocktypes.json new file mode 100644 index 00000000000..333037db3ba --- /dev/null +++ b/docs/guide-de/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Achtung:", + "Note:": "Hinweis:", + "Info:": "Info:", + "Tip:": "Tipp:" +} \ No newline at end of file diff --git a/docs/guide-de/translators.json b/docs/guide-de/translators.json new file mode 100644 index 00000000000..9e05c89b5dc --- /dev/null +++ b/docs/guide-de/translators.json @@ -0,0 +1,3 @@ +[ + "Carsten Brandt" +] \ No newline at end of file diff --git a/docs/guide-es/README.md b/docs/guide-es/README.md new file mode 100644 index 00000000000..c432298bb50 --- /dev/null +++ b/docs/guide-es/README.md @@ -0,0 +1,203 @@ +Guía Definitiva de Yii 2.0 +========================== + +Este tutorial se publica bajo los [Términos de Documentación Yii](https://www.yiiframework.com/doc/terms/). + +Todos los derechos reservados. + +2014 (c) Yii Software LLC. + + +Introducción +------------ + +* [Acerca de Yii](intro-yii.md) +* [Actualizar desde Yii 1.1](intro-upgrade-from-v1.md) + + +Primeros pasos +-------------- + +* [Qué necesita saber](start-prerequisites.md) +* [Instalar Yii](start-installation.md) +* [Funcionamiento de aplicaciones](start-workflow.md) +* [Hola a todos](start-hello.md) +* [Trabajar con formularios](start-forms.md) +* [Trabajar con bases de datos](start-databases.md) +* [Generar códigos con Gii](start-gii.md) +* [Adentrarse en Yii](start-looking-ahead.md) + + +Estructura de una aplicación +---------------------------- + +* [Información general](structure-overview.md) +* [Script de entrada](structure-entry-scripts.md) +* [Aplicaciones](structure-applications.md) +* [Componentes de una aplicación](structure-application-components.md) +* [Controladores](structure-controllers.md) +* [Modelos](structure-models.md) +* [Vistas](structure-views.md) +* [Filtros](structure-filters.md) +* [Widgets](structure-widgets.md) +* [Módulos](structure-modules.md) +* [Assets](structure-assets.md) +* [Extensiones](structure-extensions.md) + + +Gestión de las peticiones +------------------------- + +* [Información general](runtime-overview.md) +* [Bootstrapping](runtime-bootstrapping.md) +* [Routing y Creación de las URL](runtime-routing.md) +* [Peticiones (Requests)](runtime-requests.md) +* [Respuestas (Responses)](runtime-responses.md) +* [Sesiones (Sessions) y Cookies](runtime-sessions-cookies.md) +* [Gestión de errores](runtime-handling-errors.md) +* [Registro de anotaciones](runtime-logging.md) + + +Conceptos clave +--------------- + +* [Componentes](concept-components.md) +* [Propiedades](concept-properties.md) +* [Eventos](concept-events.md) +* [Comportamientos (Behaviors)](concept-behaviors.md) +* [Configuraciones](concept-configurations.md) +* [Alias](concept-aliases.md) +* [Autocarga de clases](concept-autoloading.md) +* [Localizador de servicios (Service Locator)](concept-service-locator.md) +* [Contenedor de inyección de dependencia](concept-di-container.md) + + +Trabajar con bases de datos +--------------------------- + +* [Objeto de acceso a datos](db-dao.md) - Conexión a una base de datos, consultas básicas, transacciones y + manipulación de esquemas +* [Constructor de consultas](db-query-builder.md) - Consulta de la base de datos utilizando una simple capa de + abstracción +* **TBD** [Active Record](db-active-record.md) - ORM Active Record, recuperación y manipulación de registros y + definición de relaciones +* **TBD** [Migraciones](db-migrations.md) - Control de versiones de bases de datos en el entorno de desarrollo en + equipo +* **TBD** [Sphinx](db-sphinx.md) +* **TBD** [Redis](db-redis.md) +* **TBD** [MongoDB](db-mongodb.md) +* **TBD** [ElasticSearch](db-elastic-search.md) + + +Obtener datos de los usuarios +----------------------------- + +* **TBD** [Crear formularios](input-forms.md) +* **TBD** [Validar datos](input-validation.md) +* **TBD** [Subir archivos](input-file-upload.md) +* **TBD** [Recogida de tabular input](input-tabular-input.md) +* **TBD** [Obtener datos para múltiples modelos](input-multiple-models.md) + + +Visualizar datos +---------------- + +* **TBD** [Formato de datos](output-formatting.md) +* **TBD** [Paginación](output-pagination.md) +* **TBD** [Ordenación](output-sorting.md) +* **TBD** [Proveedores de datos](output-data-providers.md) +* **TBD** [Widgets de datos](output-data-widgets.md) +* **TBD** [Trabajar con scripts de cliente](output-client-scripts.md) +* [Temas](output-theming.md) + + +Seguridad +--------- + +* **TBD** [Autenticación](security-authentication.md) +* **TBD** [Autorización](security-authorization.md) +* **TBD** [Trabajar con contraseñas](security-passwords.md) +* [Autenticar Clientes](https://www.yiiframework.com/extension/yiisoft/yii2-authclient/doc/guide) +* **TBD** [Buenas prácticas](security-best-practices.md) + + +Caché +----- + +* [Información general](caching-overview.md) +* [Caché de datos](caching-data.md) +* [Caché de fragmentos](caching-fragment.md) +* [Caché de páginas](caching-page.md) +* [Caché HTTP](caching-http.md) + + +Servicios Web RESTful +--------------------- + +* [Guía breve](rest-quick-start.md) +* [Recursos (Resources)](rest-resources.md) +* [Controladores](rest-controllers.md) +* [Gestión de rutas](rest-routing.md) +* [Formateo de respuestas](rest-response-formatting.md) +* [Autenticación](rest-authentication.md) +* [Límite de Rango](rest-rate-limiting.md) +* [Gestión de versiones](rest-versioning.md) +* [Gestión de errores](rest-error-handling.md) + + +Herramientas de Desarrollo +-------------------------- + +* [Depurador y Barra de Herramientas de Depuración](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-es/README.md) +* **TBD** [Generación de códigos con Gii](tool-gii.md) +* **TBD** [Generación de documentación de API](tool-api-doc.md) + + +Pruebas +------ + +* **TBD** [Información general](test-overview.md) +* **TBD** [Configuración del entorno de pruebas](test-environment-setup.md) +* **TBD** [Pruebas unitarias](test-unit.md) +* **TBD** [Pruebas funcionales](test-functional.md) +* **TBD** [Pruebas de aceptación](test-acceptance.md) +* **TBD** [Fixtures](test-fixtures.md) + + +Temas especiales +---------------- + +* **TBD** [Plantilla aplicación avanzada](tutorial-advanced-app.md) +* **TBD** [Creación de una aplicación desde cero](tutorial-start-from-scratch.md) +* **TBD** [Comandos de consola](tutorial-console.md) +* [Validadores del núcleo](tutorial-core-validators.md) +* **TBD** [Internacionalización](tutorial-i18n.md) +* **TBD** [Envío de correos electrónicos](tutorial-mailing.md) +* **TBD** [Mejora del rendimiento](tutorial-performance-tuning.md) +* **TBD** [Entorno de alojamiento compartido](tutorial-shared-hosting.md) +* **TBD** [Motores de plantillas](tutorial-template-engines.md) +* **TBD** [Trabajar con Código de Terceros](tutorial-yii-integration.md) + + +Widgets +------- + +* GridView: **TBD** link to demo page +* ListView: **TBD** link to demo page +* DetailView: **TBD** link to demo page +* ActiveForm: **TBD** link to demo page +* Pjax: **TBD** link to demo page +* Menu: **TBD** link to demo page +* LinkPager: **TBD** link to demo page +* LinkSorter: **TBD** link to demo page +* [Bootstrap Widgets](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide-es/README.md) +* [Jquery UI Widgets](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide-es/README.md) + + +Clases auxiliares +----------------- + +* [Información general](helper-overview.md) +* [ArrayHelper](helper-array.md) +* [Html](helper-html.md) +* [Url](helper-url.md) diff --git a/docs/guide-es/blocktypes.json b/docs/guide-es/blocktypes.json new file mode 100644 index 00000000000..7779e0aa5dc --- /dev/null +++ b/docs/guide-es/blocktypes.json @@ -0,0 +1,6 @@ +{ + "Warning:": "Aviso:", + "Note:": "Nota:", + "Info:": "Información:", + "Tip:": "Consejo:" +} diff --git a/docs/guide-es/caching-data.md b/docs/guide-es/caching-data.md new file mode 100644 index 00000000000..3adc25f5c86 --- /dev/null +++ b/docs/guide-es/caching-data.md @@ -0,0 +1,305 @@ +Almacenamiento de Datos en Caché +================================ + +El almacenamiento de datos en caché trata del almacenamiento de alguna variable PHP en caché y recuperarla más tarde del mismo. También es la base de algunas de las características avanzadas de almacenamiento en caché, tales como [el almacenamiento en caché de consultas a la base de datos](#query-caching) y [el almacenamiento en caché de contenido](caching-page.md). + +El siguiente código muestra el típico patrón de uso para el almacenamiento en caché, donde la variable `$cache` se refiere al [componente caché](#cache-components): + +```php +// intenta recuperar $data de la caché +$data = $cache->get($key); + +if ($data === false) { + + // $data no ha sido encontrada en la caché, calcularla desde cero + + // guardar $data en caché para así recuperarla la próxima vez + $cache->set($key, $data); +} + +// $data está disponible aquí +``` + + +## Componentes de Caché + +El almacenamiento de datos en caché depende de los llamados *cache components* (componentes de caché) los cuales +representan diferentes tipos de almacenamiento en caché, como por ejemplo en memoria, en archivos o en base de datos. + +Los Componentes de Caché están normalmente registrados como [componentes de la aplicación](structure-application-components.md) para que de esta forma puedan +ser configurados y accesibles globalmente. El siguiente código muestra cómo configurar el componente de aplicación +`cache` para usar [memcached](https://memcached.org/) con dos servidores caché: + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\MemCache', + 'servers' => [ + [ + 'host' => 'server1', + 'port' => 11211, + 'weight' => 100, + ], + [ + 'host' => 'server2', + 'port' => 11211, + 'weight' => 50, + ], + ], + ], +], +``` + +Puedes acceder al componente de caché usando la expresión `Yii::$app->cache`. + +Debido a que todos los componentes de caché soportan el mismo conjunto de APIs, podrías cambiar el componente de caché +subyacente por otro diferente mediante su reconfiguración en la configuración de la aplicación sin tener que modificar +el código que utiliza la caché. Por ejemplo, podrías modificar la configuración anterior para usar [[yii\caching\ApcCache|APC cache]]: + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\ApcCache', + ], +], +``` + +> Tip: Puedes registrar múltiples componentes de aplicación de caché. El componente llamado `cache` es usado por defecto por muchas clases caché-dependiente (ej. [[yii\web\UrlManager]]). + + +### Almacenamientos de Caché Soportados + +Yii proporciona varios componentes de caché que pueden almacenar datos en diferentes medios. A continuación +se muestra un listado con los componentes de caché disponibles: + +* [[yii\caching\ApcCache]]: utiliza la extensión de PHP [APC](https://www.php.net/manual/es/book.apc.php). Esta opción puede ser considerada como la más rápida de entre todas las disponibles para una aplicación centralizada. (ej. un servidor, no dedicado al balance de carga, etc). +* [[yii\caching\DbCache]]: utiliza una tabla de base de datos para almacenar los datos. Por defecto, se creará y usará como base de datos [SQLite3](https://sqlite.org/) en el directorio runtime. Se puede especificar explícitamente que base de datos va a ser utilizada configurando la propiedad `db`. +* [[yii\caching\DummyCache]]: dummy cache (caché tonta) que no almacena en caché nada. El propósito de este componente es simplificar el código necesario para chequear la disponibilidad de caché. Por ejemplo, durante el desarrollo o si el servidor no tiene soporte de caché actualmente, puede utilizarse este componente de caché. Cuando este disponible un soporte en caché, puede cambiarse el componente correspondiente. En ambos casos, puede utilizarse el mismo código `Yii::$app->cache->get($key)` para recuperar un dato sin la preocupación de que `Yii::$app->cache` pueda ser `null`. +* [[yii\caching\FileCache]]: utiliza un fichero estándar para almacenar los datos. Esto es adecuado para almacenar grandes bloques de datos (como páginas). +* [[yii\caching\MemCache]]: utiliza las extensiones de PHP [memcache](https://www.php.net/manual/es/book.memcache.php) y [memcached](https://www.php.net/manual/es/book.memcached.php). Esta opción puede ser considerada como la más rápida cuando la caché es manejada en una aplicación distribuida (ej. con varios servidores, con balance de carga, etc..) +* [[yii\redis\Cache]]: implementa un componente de caché basado en [Redis](https://redis.io/) que almacenan pares clave-valor (requiere la versión 2.6.12 de redis). +* [[yii\caching\WinCache]]: utiliza la extensión de PHP [WinCache](https://iis.net/downloads/microsoft/wincache-extension) ([ver también](https://www.php.net/manual/es/book.wincache.php)). + +> Tip: Puedes utilizar diferentes tipos de almacenamiento de caché en la misma aplicación. Una estrategia común es la de usar almacenamiento de caché en memoria para almacenar datos que son pequeños pero que son utilizados constantemente (ej. datos estadísticos), y utilizar el almacenamiento de caché en archivos o en base de datos para guardar datos que son grandes y utilizados con menor frecuencia (ej. contenido de página). + + +## API de Caché + +Todos los componentes de almacenamiento de caché provienen de la misma clase "padre" [[yii\caching\Cache]] y por lo tanto soportan la siguiente API: + +* [[yii\caching\Cache::get()|get()]]: recupera un elemento de datos de la memoria caché con una clave especificada. + Un valor nulo será devuelto si el elemento de datos no ha sido encontrado en la memoria caché o si ha expirado o ha sido invalidado. +* [[yii\caching\Cache::set()|set()]]: almacena un elemento de datos identificado por una clave en la memoria caché. +* [[yii\caching\Cache::add()|add()]]: almacena un elemento de datos identificado por una clave en la memoria caché si la clave no se encuentra en la memoria caché. +* [[yii\caching\Cache::mget()|mget()]]: recupera varios elementos de datos de la memoria caché con las claves especificadas. +* [[yii\caching\Cache::mset()|mset()]]: almacena múltiples elementos de datos en la memoria caché. Cada elemento se identifica por una clave. +* [[yii\caching\Cache::madd()|madd()]]: almacena múltiples elementos de datos en la memoria caché. Cada elemento se identifica con una clave. Si una clave ya existe en la caché, el elemento será omitido. +* [[yii\caching\Cache::exists()|exists()]]: devuelve un valor que indica si la clave especificada se encuentra en la memoria caché. +* [[yii\caching\Cache::delete()|delete()]]: elimina un elemento de datos identificado por una clave de la caché. +* [[yii\caching\Cache::flush()|flush()]]: elimina todos los elementos de datos de la cache. + +> Note: No Almacenes el valor boolean `false` en caché directamente porque el método [[yii\caching\Cache::get()|get()]] devuelve +el valor `false` para indicar que el dato no ha sido encontrado en la caché. Puedes poner `false` dentro de un array y cachear +este array para evitar este problema. + +Algunos sistemas de almacenamiento de caché, como por ejemplo MemCache, APC, pueden recuperar múltiples valores almacenados en modo de lote (batch), lo que puede reducir considerablemente la sobrecarga que implica la recuperación de datos almacenados en la caché. Las API [[yii\caching\Cache::mget()|mget()]] y [[yii\caching\Cache::madd()|madd()]] +se proporcionan para utilizar esta característica. En el caso de que el sistema de memoria caché no lo soportara, ésta sería simulada. + +Puesto que [[yii\caching\Cache]] implementa `ArrayAccess`, un componente de caché puede ser usado como un array. +El siguiente código muestra unos ejemplos: + +```php +$cache['var1'] = $value1; // equivalente a: $cache->set('var1', $value1); +$value2 = $cache['var2']; // equivalente a: $value2 = $cache->get('var2'); +``` + + +### Claves de Caché + +Cada elemento de datos almacenado en caché se identifica por una clave. Cuando se almacena un elemento de datos en la memoria caché, se debe especificar una clave. Más tarde, cuando se recupera el elemento de datos de la memoria caché, se debe proporcionar la clave correspondiente. + +Puedes utilizar una cadena o un valor arbitrario como una clave de caché. Cuando una clave no es una cadena de texto, ésta será automáticamente serializada en una cadena. + +Una estrategia común para definir una clave de caché es incluir en ella todos los factores determinantes en términos de un array. Por ejemplo, [[yii\db\Schema]] utiliza la siguiente clave para almacenar en caché la información del esquema de una tabla de base de datos: + +```php +[ + __CLASS__, // nombre de la clase del esquema + $this->db->dsn, // nombre del origen de datos de la conexión BD + $this->db->username, // usuario para la conexión BD + $name, // nombre de la tabla +]; +``` + +Como puedes ver, la clave incluye toda la información necesaria para especificar de una forma exclusiva una tabla de base de datos. + +Cuando en un mismo almacenamiento en caché es utilizado por diferentes aplicaciones, se debería especificar un prefijo único para las claves de la caché por cada una de las aplicaciones para así evitar conflictos. Esto puede hacerse mediante la configuración de la propiedad [[yii\caching\Cache::keyPrefix]]. Por ejemplo, en la configuración de la aplicación podrías escribir el siguiente código: + +```php +'components' => [ + 'cache' => [ + 'class' => 'yii\caching\ApcCache', + 'keyPrefix' => 'myapp', // un prefijo de clave de caché único + ], +], +``` + +Para garantizar la interoperabilidad, deberían utilizarse sólo caracteres alfanuméricos. + + +### Caducidad de Caché + +Un elemento de datos almacenado en la memoria caché permanecerá en ella para siempre, a menos que sea removida de alguna manera debido a alguna directiva de caché (ej. el espacio de almacenamiento en caché está lleno y los datos más antiguos se eliminan). Para cambiar este comportamiento, podrías proporcionar un parámetro de caducidad al llamar [[yii\caching\Cache::set()|set()]] para guardar el elemento de datos. El parámetro nos indica por cuántos segundos el elemento se mantendrá válido en memoria caché. Cuando llames [[yii\caching\Cache::get()|get()]] para recuperar el elemento, si el tiempo de caducidad ha pasado, el método devolverá `false`, indicando que el elemento de datos no ha sido encontrado en la memoria caché. Por ejemplo, + +```php +// guardar los datos en memoria caché al menos 45 segundos +$cache->set($key, $data, 45); + +sleep(50); + +$data = $cache->get($key); +if ($data === false) { + // $data ha caducado o no ha sido encontrado en la memoria caché +} +``` + + +### Dependencias de Caché + +Además de configurar el tiempo de caducidad, los datos almacenados en caché pueden también ser invalidados conforme a algunos cambios en la caché de dependencias. Por ejemplo, [[yii\caching\FileDependency]] representa la dependencia del tiempo de modificación del archivo. Cuando esta dependencia cambia, significa que el archivo correspondiente ha cambiado. Como resultado, cualquier contenido anticuado que sea encontrado en la caché debería ser invalidado y la llamada a [[yii\caching\Cache::get()|get()]] debería retornar falso. + +Una dependencia es representada como una instancia de [[yii\caching\Dependency]] o su clase hija. Cuando llamas [[yii\caching\Cache::set()|set()]] para almacenar un elemento de datos en la caché, puedes pasar el objeto de dependencia asociado. Por ejemplo, + +```php +// Crear una dependencia sobre el tiempo de modificación del archivo example.txt. +$dependency = new \yii\caching\FileDependency(['fileName' => 'example.txt']); + +// Los datos expirarán en 30 segundos. +// También podría ser invalidada antes si example.txt es modificado. +$cache->set($key, $data, 30, $dependency); + +// La caché comprobará si los datos han expirado. +// También comprobará si la dependencia ha cambiado. +// Devolverá `false` si se encuentran algunas de esas condiciones. +$data = $cache->get($key); +``` + +Aquí abajo se muestra un sumario de las dependencias disponibles: + +- [[yii\caching\ChainedDependency]]: la dependencia cambia si cualquiera de las dependencias en la cadena cambia. +- [[yii\caching\DbDependency]]: la dependencia cambia si el resultado de la consulta de la sentencia SQL especificada cambia. +- [[yii\caching\ExpressionDependency]]: la dependencia cambia si el resultado de la expresión de PHP especificada cambia. +- [[yii\caching\CallbackDependency]]: la dipendenza viene modificata se il risultato della callback PHP specificata cambia. +- [[yii\caching\FileDependency]]: la dependencia cambia si se modifica la última fecha de modificación del archivo. +- [[yii\caching\TagDependency]]: marca un elemento de datos en caché con un nombre de grupo. Puedes invalidar los elementos de datos almacenados en caché + con el mismo nombre del grupo a la vez llamando a [[yii\caching\TagDependency::invalidate()]]. + + +## Consultas en Caché + +Las consultas en caché es una característica especial de caché construido sobre el almacenamiento de caché de datos. Se +proporciona para almacenar en caché el resultado de consultas a la base de datos. + +Las consultas en caché requieren una [[yii\db\Connection|DB connection]] y un componente de aplicación caché válido. El uso básico de las consultas en memoria caché es el siguiente, asumiendo que `db` es una instancia de [[yii\db\Connection]]: + +```php +$result = $db->cache(function ($db) { + + // el resultado de la consulta SQL será servida de la caché + // si el cacheo de consultas está habilitado y el resultado de la consulta se encuentra en la caché + return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne(); + +}); +``` + +El cacheo de consultas puede ser usado tanto para [DAO](db-dao.md) como para [ActiveRecord](db-active-record.md): + +```php +$result = Customer::getDb()->cache(function ($db) { + return Customer::find()->where(['id' => 1])->one(); +}); +``` + +> Note: Algunos DBMS (ej. [MySQL](https://dev.mysql.com/doc/refman/5.6/en/query-cache.html)) también soporta el almacenamiento en caché desde el mismo servidor de la BD. Puedes optar por utilizar cualquiera de los mecanismos de memoria caché. El almacenamiento en caché de consultas previamente descrito tiene la ventaja que de que se puede especificar dependencias de caché de una forma flexible y son potencialmente mucho más eficientes. + + +### Configuraciones + +Las consultas en caché tienen tres opciones configurables globales a través de [[yii\db\Connection]]: + +* [[yii\db\Connection::enableQueryCache|enableQueryCache]]: activa o desactiva el cacheo de consultas. + Por defecto es `true`. Tenga en cuenta que para activar el cacheo de consultas, también necesitas tener una caché válida, especificada por [[yii\db\Connection::queryCache|queryCache]]. +* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: representa el número de segundos que un resultado de la consulta permanecerá válida en la memoria caché. Puedes usar 0 para indicar que el resultado de la consulta debe permanecer en la caché para siempre. Esta propiedad es el valor usado por defecto cuando [[yii\db\Connection::cache()]] es llamada sin especificar una duración. +* [[yii\db\Connection::queryCache|queryCache]]: representa el ID del componente de aplicación de caché. + Por defecto es `'cache'`. El almacenamiento en caché de consultas se habilita sólo si hay un componente de la aplicación de caché válida. + + +### Usos + +Puedes usar [[yii\db\Connection::cache()]] si tienes multiples consultas SQL que necesitas a aprovechar el cacheo de consultas. El uso es de la siguiente manera, + +```php +$duration = 60; // resultado de la consulta de caché durante 60 segundos. +$dependency = ...; // dependencia opcional + +$result = $db->cache(function ($db) { + + // ... realiza consultas SQL aquí ... + + return $result; + +}, $duration, $dependency); +``` + +Cualquier consulta SQL en una función anónima será cacheada durante el tiempo indicado con la dependencia especificada. +Si el resultado de la consulta se encuentra válida en la caché, la consulta se omitirá y el resultado se servirá de la caché en su lugar. Si no especificar el parámetro `$duration`, el valor [[yii\db\Connection::queryCacheDuration|queryCacheDuration]] será usado en su lugar. + +A veces dentro de `cache()`, puedes querer desactivar el cacheo de consultas para algunas consultas especificas. Puedes usar [[yii\db\Connection::noCache()]] en este caso. + +```php +$result = $db->cache(function ($db) { + + // consultas SQL que usan el cacheo de consultas + + $db->noCache(function ($db) { + + // consultas SQL que no usan el cacheo de consultas + + }); + + // ... + + return $result; +}); +``` + +Si lo deseas puedes usar el cacheo de consultas para una simple consulta, puedes llamar a [[yii\db\Command::cache()]] cuando construyas el comando. Por ejemplo, + +```php +// usa el cacheo de consultas y asigna la duración de la consulta de caché por 60 segundos +$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne(); +``` + +También puedes usar [[yii\db\Command::noCache()]] para desactivar el cacheo de consultas de un simple comando. Por ejemplo, + +```php +$result = $db->cache(function ($db) { + + // consultas SQL que usan cacheo de consultas + + // no usa cacheo de consultas para este comando + $customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne(); + + // ... + + return $result; +}); +``` + + +### Limitaciones + +El almacenamiento en caché de consultas no funciona con los resultados de consulta que contienen controladores de recursos. +Por ejemplo, cuando se utiliza el tipo de columna `BLOB` en algunos DBMS, el resultado de la consulta devolverá un recurso para manejar los datos de la columna. + +Algunos sistemas de almacenamiento caché tienen limitación de tamaño. Por ejemplo, memcache limita el tamaño máximo de cada entrada a 1MB. Por lo tanto, si el tamaño de un resultado de la consulta excede ese límite, el almacenamiento en caché fallará. diff --git a/docs/guide-es/caching-fragment.md b/docs/guide-es/caching-fragment.md new file mode 100644 index 00000000000..3d8cd174c18 --- /dev/null +++ b/docs/guide-es/caching-fragment.md @@ -0,0 +1,185 @@ +Caché de Fragmentos +=================== + +La Caché de Fragmentos se refiere al almacenamiento en caché de un fragmento, o sección, de una página Web. Por ejemplo, si +una página muestra un sumario de las ventas anuales en una tabla, podrías guardar esta tabla en memoria caché para +eliminar el tiempo necesario para generar esta tabla en cada petición (request). La caché de fragmentos está construido +sobre la [caché de datos](caching-data.md). + +Para usar la caché de fragmentos, utiliza el siguiente código en tu [vista (view)](structure-views.md): + + +```php +if ($this->beginCache($id)) { + + // ... generar contenido aquí ... + + $this->endCache(); +} +``` + +Es decir, encierra la lógica de la generación del contenido entre las llamadas [[yii\base\View::beginCache()|beginCache()]] y +[[yii\base\View::endCache()|endCache()]]. Si el contenido se encuentra en la memoria caché, [[yii\base\View::beginCache()|beginCache()]] +mostrará el contenido y devolverá `false`, saltandose así la lógica de generación del contenido. De lo contrario, el +código de generación se ejecutaría y al alcanzar la llamada [[yii\base\View::endCache()|endCache()]], el contenido +generado será capturado y almacenado en la memoria caché. + +Como en la [caché de datos](caching-data.md), un `$id` (clave) único es necesario para identificar un contenido guardado en +caché. + + +## Opciones de Caché + +Puedes especificar opciones adicionales para la caché de fragmentos pasando el array de opciones como segundo +parametro del método [[yii\base\View::beginCache()|beginCache()]]. Entre bastidores, este array de opciones se utiliza +para configurar el widget [[yii\widgets\FragmentCache]] que es en realidad el que implementa la funcionalidad de la caché +de fragmentos. + +### Duración + +Quizás la opción más utilizada en la caché de fragmentos es [[yii\widgets\FragmentCache::duration|duración]]. Ésta +especifica cuántos segundos el contenido puede permanecer como válido en la memoria caché. El siguiente código almacena +en la caché el fragmento de contenido para una hora a lo sumo: + +```php +if ($this->beginCache($id, ['duration' => 3600])) { + + // ... generar contenido aquí ... + + $this->endCache(); +} +``` + +Si la opción no está activada, se tomará el valor por defecto 60, lo que significa que el contenido almacenado en caché expirará en 60 segundos. + + +### Dependencias + +Como en la [caché de datos](caching-data.md#cache-dependencies), el fragmento de contenido que está siendo almacenado en caché +también puede tener dependencias. Por ejemplo, el contenido de un artículo que se muestre depende de si el mensaje se +modifica o no. + +Para especificar una dependencia, activa la opción [[yii\widgets\FragmentCache::dependency|dependencia]] (dependency), +que puede ser un objecto [[yii\caching\Dependency]] o un array de configuración para crear un objecto `Dependency`. El +siguiente código especifica que la caché de fragmento depende del cambio del valor de la columna `updated_at`: + +```php +$dependency = [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT MAX(updated_at) FROM post', +]; + +if ($this->beginCache($id, ['dependency' => $dependency])) { + + // ... generar contenido aquí ... + + $this->endCache(); +} +``` + + +### Variaciones + +El contenido almacenado en caché puede variar de acuerdo a ciertos parámetros. Por ejemplo, para una aplicación Web que +soporte multiples idiomas, la misma pieza del código de la vista puede generar el contenido almacenado en caché +en diferentes idiomas. Por lo tanto, es posible que desees hacer variaciones del mismo contenido almacenado en caché de +acuerdo con la actual selección del idioma en la aplicación. + +Para especificar variaciones en la memoria caché, configura la opción [[yii\widgets\FragmentCache::variations|variaciones]] +(variations), la cual deberá ser un array de valores escalares, cada uno de ellos representando un factor de variación. +Por ejemplo, para hacer que el contenido almacenado en la caché varíe por lenguaje, podrías usar el siguiente código: + +```php +if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { + + // ... generar código aquí ... + + $this->endCache(); +} +``` + + +### Alternando el Almacenamiento en Caché + +Puede que a veces quieras habilitar la caché de fragmentos únicamente cuando ciertas condiciones se cumplan. Por ejemplo, +para una página que muestra un formulario, tal vez quieras guardarlo en la caché cuando es inicialmente solicitado (a +través de una petición GET). Cualquier muestra posterior (a través de una petición POST) del formulario no debería ser +almacenada en caché ya que el formulario puede que contenga entradas del usuario. Para hacerlo, podrías configurar la +opción de [[yii\widgets\FragmentCache::enabled|activado]] (enabled), de la siguiente manera: + +```php +if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) { + + // ... generar contenido aquí ... + + $this->endCache(); +} +``` + + +## Almacenamiento en Caché Anidada + +El almacenamiento en caché de fragmentos se puede anidar. Es decir, un fragmento de caché puede ser encerrado dentro de +otro fragmento que también se almacena en caché. Por ejemplo, los comentarios se almacenan en una caché de fragmento +interno, y se almacenan conjuntamente con el contenido del artículo en un fragmento de caché exterior. El siguiente +código muestra cómo dos fragmentos de caché pueden ser anidados: + +```php +if ($this->beginCache($id1)) { + + // ... lógica de generación de contenido externa ... + + if ($this->beginCache($id2, $options2)) { + + // ... lógica de generación de contenido anidada ... + + $this->endCache(); + } + + // ... lógica de generación de contenido externa ... + + $this->endCache(); +} +``` + +Existen diferentes opciones de configuración para las cachés anidadas. Por ejemplo, las cachés internas y las cachés +externas pueden usar diferentes valores de duración. Aún cuando los datos almacenados en la caché externa sean invalidados, +la caché interna puede todavía proporcionar un fragmento válido. Sin embargo, al revés no es cierto. Si la caché externa +es evaluada como válida, seguiría proporcionando la misma copia en caché incluso después de que el contenido en la +caché interna haya sido invalidada. Por lo tanto, hay que tener mucho cuidado al configurar el tiempo de duración o las +dependencias de las cachés anidadas, de lo contrario los fragmentos internos que ya estén obsoletos se pueden seguir +manteniendo en el fragmento externo. + + +## Contenido Dinámico + +Cuando se usa la caché de fragmentos, podrías encontrarte en la situación que un fragmento grande de contenido es +relavitamente estático excepto en uno u otro lugar. Por ejemplo, la cabeza de una página (header) puede que muestre el +menú principal junto al nombre del usuario actual. Otro problema es que el contenido que está siendo almacenado en caché +puede que contenga código PHP que debe ser ejecutado en cada petición (por ejemplo, el código para registrar +un paquete de recursos (asset bundle)). En ambos casos, podríamos resolver el problema con lo que llamamos la +característica de *contenido dinámico*. + +Entendemos *contenido dinámico* como un fragmento de salida que no debería ser guardado en caché incluso si está +encerrado dentro de un fragmento de caché. Para hacer el contenido dinámico todo el tiempo, éste ha de ser generado ejecutando +cierto código PHP en cada petición, incluso si el contenido está siendo mostrado desde la caché. + +Puedes llamar a [[yii\base\View::renderDynamic()]] dentro de un fragmento almacenado en caché para insertar código +dinámico en el lugar deseado como, por ejemplo, de la siguiente manera, + +```php +if ($this->beginCache($id1)) { + + // ... lógica de generación de contenido ... + + echo $this->renderDynamic('return Yii::$app->user->identity->name;'); + + // ... lógica de generación de contenido ... + + $this->endCache(); +} +``` + +El método [[yii\base\View::renderDynamic()|renderDynamic()]] toma una pieza de código PHP como su parámetro. El valor +devuelto del código PHP se trata como contenido dinámico. El mismo código PHP será ejecutado en cada petición, +sin importar que esté dentro de un fragmento que está siendo servido desde la caché o no. diff --git a/docs/guide-es/caching-http.md b/docs/guide-es/caching-http.md new file mode 100644 index 00000000000..e364ff8d0ca --- /dev/null +++ b/docs/guide-es/caching-http.md @@ -0,0 +1,142 @@ +Caché HTTP +========== + +Además del almacenamiento de caché en el servidor que hemos descrito en secciones anteriores, las aplicaciones Web +pueden hacer uso de la caché en el lado del cliente para así ahorrar tiempo y recursos para generar y transmitir el +mismo contenido una y otra vez. + +Para usar la caché del lado del cliente, puedes configurar [[yii\filters\HttpCache]] como un filtro en el controlador +para aquellas acciones cuyo resultado deba estar almacenado en la caché en el lado del cliente. [[yii\filters\HttpCache|HttpCache]] +solo funciona en peticiones `GET` y `HEAD`. Puede manejar tres tipos de cabeceras (headers) HTTP relacionadas en este tipo de +consultas: + +* [[yii\filters\HttpCache::lastModified|Last-Modified]] +* [[yii\filters\HttpCache::etagSeed|Etag]] +* [[yii\filters\HttpCache::cacheControlHeader|Cache-Control]] + + +## La Cabecera `Last-Modified` + +La cabecera `Last-Modified` usa un sello de tiempo para indicar si la página ha sido modificada desde que el cliente la +almacena en la caché. + +Puedes configurar la propiedad [[yii\filters\HttpCache::lastModified]] para activar el envío de la cabecera `Last-Modified`. +La propiedad debe ser una llamada de retorno (callable) PHP que devuelva un timestamp UNIX sobre el tiempo de modificación de +la página. El formato de la función de llamada de retorno debe ser el siguiente, + +```php +/** + * @param Action $action el objeto acción que se está controlando actualmente + * @param array $params el valor de la propiedad "params" + * @return int un sello de tiempo UNIX que representa el tiempo de modificación de la página + */ +function ($action, $params) +``` + +El siguiente es un ejemplo haciendo uso de la cabecera `Last-Modified`: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['index'], + 'lastModified' => function ($action, $params) { + $q = new \yii\db\Query(); + return $q->from('post')->max('updated_at'); + }, + ], + ]; +} +``` + +El código anterior establece que la memoria caché HTTP debe ser habilitada únicamente por la acción `index`. Se debe +generar una cabecera HTTP `Last-Modified` basado en el último tiempo de actualización de los artículos. Cuando un +navegador visita la página `index` la primera vez, la página será generada en el servidor y enviada al navegador; Si el +navegador visita la misma página de nuevo y no ningún artículo modificado durante el período, el servidor no volverá a +regenerar la página, y el navegador usará la versión caché del lado del cliente. Como resultado, la representación del +lado del servidor y la transmisión del contenido de la página son ambos omitidos. + + +## La Cabecera `ETag` + +La cabecera "Entity Tag" (o para abreviar `ETag`) usa un hash para representar el contenido de una página. Si la página +ha sido cambiada, el hash también cambiará. Al comparar el hash guardado en el lado del cliente con el hash generado en +el servidor, la caché puede determinar si la página ha cambiado y deber ser retransmitida. + +Puedes configurar la propiedad [[yii\filters\HttpCache::etagSeed]] para activar el envío de la cabecera `ETag`. +La propiedad debe ser una función de retorno (callable) PHP que devuelva una semilla para la generación del hash de `ETag`. +El formato de la función de retorno es el siguiente: + +```php +/** + * @param Action $action el objeto acción que se está controlando actualmente + * @param array $params el valor de la propiedad "params" + * @return string una cadena usada como semilla para la generación del hash de ETag + */ +function ($action, $params) +``` + +El siguiente es un ejemplo de cómo usar la cabecera `ETag`: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['view'], + 'etagSeed' => function ($action, $params) { + $post = $this->findModel(\Yii::$app->request->get('id')); + return serialize([$post->title, $post->content]); + }, + ], + ]; +} +``` + +El código anterior establece que la caché HTTP debe ser activada únicamente para la acción `view`. Debería generar una +cabecera HTTP `ETag` basándose en el título y contenido del artículo consultado. Cuando un navegador visita la página +`view` por primera vez, la página se generará en el servidor y será enviada al navegador; Si el navegador visita la +misma página de nuevo y no ha ocurrido un cambio en el título o contenido del artículo, el servidor no volverá a generar +la página, y el navegador usará la versión guardada en la caché del lado del cliente. Como resultado, la representación del +lado del servidor y la transmisión del contenido de la página son ambos omitidos. + +ETags permiten estrategias de almacenamiento de caché más complejas y/o mucho más precisas que las cabeceras `Last-Modified`. +Por ejemplo, un ETag puede ser invalidado si el sitio Web ha cambiado de tema (theme). + +La generación de un ETag que requiera muchos recursos puede echar por tierra el propósito de estar usando `HttpCache` e +introducir una sobrecarga innecesaria, ya que debe ser re-evaluada en cada solicitud (request). Trata de encontrar una +expresión sencilla para invalidar la caché si la página ha sido modificada. + +> Note: En cumplimiento con [RFC 7232](https://datatracker.ietf.org/doc/html/rfc7232#section-2.4), + `HttpCache` enviará ambas cabeceras `ETag` y `Last-Modified` si ambas están configuradas. Y si el clientes envía tanto la cabecera `If-None-Match` como la cabecera `If-Modified-Since`, solo la primera será respetada. + +## La Cabecera `Cache-Control` + +La cabecera `Cache-Control` especifica la directiva general de la caché para páginas. Puedes enviarla configurando la +propiedad [[yii\filters\HttpCache::cacheControlHeader]] con el valor de la cabecera. Por defecto, la siguiente cabecera +será enviada: + +``` +Cache-Control: public, max-age=3600 +``` + +## Limitador de la Sesión de Caché + +Cuando una página utiliza la sesión, PHP enviará automáticamente cabeceras HTTP relacionadas con la caché tal y como se +especifican en `session.cache_limiter` de la configuración INI de PHP. Estas cabeceras pueden interferir o deshabilitar +el almacenamiento de caché que desees de `HttpCache`. Para evitar este problema, por defecto `HttpCache` deshabilitará +automáticamente el envío de estas cabeceras. Si deseas modificar este comportamiento, tienes que configurar la propiedad +[[yii\filters\HttpCache::sessionCacheLimiter]]. La propiedad puede tomar un valor de cadena, incluyendo `public`, `private`, +`private_no_expire`, and `nocache`. Por favor, consulta el manual PHP acerca de [session_cache_limiter()](https://www.php.net/manual/es/function.session-cache-limiter.php) +para una mejor explicación sobre esos valores. + + +## Implicaciones SEO + +Los robots de motores de búsqueda tienden a respetar las cabeceras de caché. Dado que algunos `crawlers` tienen limitado +el número de páginas que pueden rastrear por dominios dentro de un cierto período de tiempo, la introducción de cabeceras +de caché pueden ayudar a la indexación del sitio Web y reducir el número de páginas que deben ser procesadas. + diff --git a/docs/guide-es/caching-overview.md b/docs/guide-es/caching-overview.md new file mode 100644 index 00000000000..f943e311e80 --- /dev/null +++ b/docs/guide-es/caching-overview.md @@ -0,0 +1,18 @@ +El Almacenamiento en Caché +========================== + +El almacenamiento en caché es una forma económica y eficaz para mejorar el rendimiento de una aplicación web. Mediante +el almacenamiento de datos relativamente estáticos en la memoria caché y su correspondiente recuperación cuando éstos sean +solicidatos, la aplicación salvaría todo ese tiempo y recursos necesarios para volver a generarlos cada vez desde cero. + +El almacenamiento en caché se puede usar en diferentes niveles y lugares en una aplicación web. En el lado del servidor, al más bajo nivel, +la caché puede ser usada para almacenar datos básicos, tales como una una lista de los artículos más recientes obtenidos de una base de datos; +y en el más alto nivel, la caché puede ser usada para almacenar fragmentos o la totalidad de las páginas web, tales como el resultado del renderizado de los artículos más recientes. En el lado del cliente, el almacenamiento en caché HTTP puede ser utilizado para mantener +el contenido de la página que ha sido visitada más recientemente en el caché del navegador. + +Yii soporta los siguientes mecanismos de almacenamiento de caché: + +* [Caché de datos](caching-data.md) +* [Caché de fragmentos](caching-fragment.md) +* [Caché de páginas](caching-page.md) +* [Caché HTTP](caching-http.md) diff --git a/docs/guide-es/caching-page.md b/docs/guide-es/caching-page.md new file mode 100644 index 00000000000..60e064c34ba --- /dev/null +++ b/docs/guide-es/caching-page.md @@ -0,0 +1,41 @@ +Caché de Páginas +================ + +La caché de páginas se refiere a guardar el contenido de toda una página en el almacenamiento de caché del servidor. +Posteriormente, cuando la misma página sea requerida de nuevo, su contenido será devuelto desde la caché en vez de +volver a generarlo desde cero. + +El almacenamiento en caché de páginas está soportado por [[yii\filters\PageCache]], un [filtro de acción](structure-filters.md). +Puede ser utilizado de la siguiente forma en un controlador: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\PageCache', + 'only' => ['index'], + 'duration' => 60, + 'variations' => [ + \Yii::$app->language, + ], + 'dependency' => [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT COUNT(*) FROM post', + ], + ], + ]; +} +``` + +El código anterior establece que el almacenamiento de páginas en caché debe ser utilizado sólo en la acción `index`; el +contenido de la página debería almacenarse durante un máximo de 60 segundos y ser variado por el idioma actual de la +aplicación; además, el almacenamiento de la página en caché debería ser invalidado si el número total de +artículos ha cambiado. + +Como puedes ver, la caché de páginas es muy similar a la [caché de fragmentos](caching-fragment.md). Ambos soportan opciones +tales como `duration`, `dependencies`, `variations`, y `enabled`. Su principal diferencia es que la caché de páginas está +implementado como un [filtro de acción](structure-filters.md) mientras que la caché de fragmentos se hace en un [widget](structure-widgets.md). + +Puedes usar la [caché de fragmentos](caching-fragment.md) así como el [contenido dinámico](caching-fragment.md#dynamic-content) +junto con la caché de páginas. diff --git a/docs/guide-es/concept-aliases.md b/docs/guide-es/concept-aliases.md new file mode 100644 index 00000000000..7a8a6cfae60 --- /dev/null +++ b/docs/guide-es/concept-aliases.md @@ -0,0 +1,134 @@ +Alias +===== + +Loa alias son utilizados para representar rutas o URLs de manera que no tengas que escribir explícitamente rutas absolutas o URLs en tu +proyecto. Un alias debe comenzar con el signo `@` para ser diferenciado de una ruta normal de archivo y de URLs. Los alias definidos +sin el `@` del principio, serán prefijados con el signo `@`. + +Yii trae disponibles varios alias predefinidos. Por ejemplo, el alias `@yii` representa la ruta de instalación del +framework Yii; `@web` representa la URL base para la aplicación Web ejecutándose. + +Definir Alias +------------- + +Para definir un alias puedes llamar a [[Yii::setAlias()]] para una determinada ruta de archivo o URL. Por ejemplo, + +```php +// un alias de una ruta de archivos +Yii::setAlias('@foo', '/path/to/foo'); + +// una alias de un URL +Yii::setAlias('@bar', 'https://www.example.com/'); +``` + +> Note: Una ruta de archivo o URL en alias NO debe necesariamente referirse a un archivo o recurso existente. + +Dado un alias, puedes derivar un nuevo alias (sin necesidad de llamar [[Yii::setAlias()]]) anexando una barra diagonal `/` +seguida por uno o varios segmentos de la ruta. Llamamos los alias definidos a través de [[Yii::setAlias()]] +*alias de raíz* (root alias), mientras que los alias derivados de ellos *alias derivados* (derived aliases). Por ejemplo, +`@foo` es un alias de raíz, mientras que `@foo/bar/file.php` es un alias derivado. + +Puedes definir un alias usando otro alias (ya sea un alias de raíz o derivado): + +```php +Yii::setAlias('@foobar', '@foo/bar'); +``` + +Los alias de raíz están usualmente definidos durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación. +Por ejemplo, puedes llamar a [[Yii::setAlias()]] en el [script de entrada](structure-entry-scripts.md). +Por conveniencia, [Application](structure-applications.md) provee una propiedad modificable llamada `aliases` que puedes +configurar en la [configuración](concept-configurations.md) de la aplicación, como por ejemplo, + +```php +return [ + // ... + 'aliases' => [ + '@foo' => '/path/to/foo', + '@bar' => 'https://www.example.com/', + ], +]; +``` + + +Resolución de Alias +------------------- + +Puedes llamar [[Yii::getAlias()]] para resolver un alias de raíz en la ruta o URL que representa. El mismo método puede +además resolver un alias derivado en su correspondiente ruta de archivo o URL. Por ejemplo, + +```php +echo Yii::getAlias('@foo'); // muestra: /path/to/foo +echo Yii::getAlias('@bar'); // muestra: https://www.example.com +echo Yii::getAlias('@foo/bar/file.php'); // muestra: /path/to/foo/bar/file.php +``` + +La ruta de archivo/URL representado por un alias derivado está determinado por la sustitución de la parte de su alias raíz +con su correspondiente ruta/Url en el alias derivado. + +> Note: El método [[Yii::getAlias()]] no comprueba si la ruta/URL resultante hacer referencia a un archivo o recurso existente. + + +Un alias de raíz puede contener carácteres `/`. El método [[Yii::getAlias()]] es lo suficientemente inteligente para saber +qué parte de un alias es un alias de raíz y por lo tanto determinar correctamente la correspondiente ruta de archivo o URL. +Por ejemplo, + +```php +Yii::setAlias('@foo', '/path/to/foo'); +Yii::setAlias('@foo/bar', '/path2/bar'); +Yii::getAlias('@foo/test/file.php'); // muestra: /path/to/foo/test/file.php +Yii::getAlias('@foo/bar/file.php'); // muestra: /path2/bar/file.php +``` + +Si `@foo/bar` no está definido como un alias de raíz, la última declaración mostraría `/path/to/foo/bar/file.php`. + + +Usando Alias +------------ + +Los alias son utilizados en muchos lugares en Yii sin necesidad de llamar [[Yii::getAlias()]] para convertirlos +en rutas/URLs. Por ejemplo, [[yii\caching\FileCache::cachePath]] puede aceptar tanto una ruta de archivo como un alias +que represente la ruta de archivo, gracias al prefijo `@` el cual permite diferenciar una ruta de archivo +de un alias. + +```php +use yii\caching\FileCache; + +$cache = new FileCache([ + 'cachePath' => '@runtime/cache', +]); +``` + +Por favor, presta atención a la documentación API para ver si una propiedad o el parámetro de un método soporta alias. + + +Alias Predefinidos +------------------ + +Yii predefine un conjunto de alias para aliviar la necesidad de hacer referencia a rutas de archivo o URLs que son +utilizadas regularmente. La siguiente es la lista de alias predefinidos por Yii: + +- `@yii`: el directorio donde el archivo `BaseYii.php` se encuentra (también llamado el directorio del framework). +- `@app`: la [[yii\base\Application::basePath|ruta base]] de la aplicación que se está ejecutando actualmente. +- `@runtime`: la [[yii\base\Application::runtimePath|ruta de ejecución]] de la aplicación en ejecución. Por defecto `@app/runtime`. +- `@webroot`: el directorio raíz Web de la aplicación Web se está ejecutando actualmente. +- `@web`: la URL base de la aplicación web se ejecuta actualmente. Tiene el mismo valor que [[yii\web\Request::baseUrl]]. +- `@vendor`: el [[yii\base\Application::vendorPath|directorio vendor de Composer]]. Por defecto `@app/vendor`. +- `@bower`, el directorio raíz que contiene [paquetes bower](https://bower.io/). Por defecto `@vendor/bower`. +- `@npm`, el directorio raíz que contiene [paquetes npm](https://www.npmjs.com/). Por defecto `@vendor/npm`. + +El alias `@yii` se define cuando incluyes el archivo `Yii.php` en tu [script de entrada](structure-entry-scripts.md), +mientras que el resto de los alias están definidos en el constructor de la aplicación cuando se aplica la +[configuración](concept-configurations.md) de la aplicación. + + +Alias en Extensiones +-------------------- + +Un alias se define automáticamente por cada [extensión](structure-extensions.md) que ha sido instalada a través de Composer. +El alias es nombrado tras el `namespace` de raíz de la extensión instalada tal y como está declarada en su archivo `composer.json`, +y representa el directorio raíz de la extensión. Por ejemplo, si instalas la extensión `yiisoft/yii2-jui`, tendrás +automáticamente definido el alias `@yii/jui` durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación: + +```php +Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); +``` diff --git a/docs/guide-es/concept-autoloading.md b/docs/guide-es/concept-autoloading.md new file mode 100644 index 00000000000..598bf7a342d --- /dev/null +++ b/docs/guide-es/concept-autoloading.md @@ -0,0 +1,97 @@ +Autocarga de clases +=================== + +Yii depende del [mecanismo de autocarga de clases](https://www.php.net/manual/es/language.oop5.autoload.php) para localizar +e incluir los archivos de las clases requiridas. Proporciona un cargador de clases de alto rendimiento que cumple con el +[estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). +El cargador se instala cuando incluyes el archivo `Yii.php`. + +> Note: Para simplificar la descripción, en esta sección sólo hablaremos de la carga automática de clases. Sin embargo, + ten en cuenta que el contenido que describimos aquí también se aplica a la autocarga de interfaces y rasgos (Traits). + + +Usando el Autocargador de Yii +----------------------------- + +Para utilizar el cargador automático de clases de Yii, deberías seguir dos reglas básicas cuando desarrolles y nombres tus +clases: + +* Cada clase debe estar bajo un espacio de nombre (namespace). Por ejemplo `foo\bar\MyClass`. +* Cada clase debe estar guardada en un archivo individual cuya ruta está determinada por el siguiente algoritmo: + +```php +// $className es un nombre completo de clase con las iniciales barras invertidas. +$classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); +``` + +Por ejemplo, si el nombre de una clase es `foo\bar\MyClass`, el [alias](concept-aliases.md) la correspondiente ruta de +archivo de la clase sería `@foo/bar/MyClass.php`. Para que este sea capaz de ser resuelto como una ruta de archivo, ya sea +`@foo` o `@foo/bar` debe ser un [alias de raíz](concept-aliases.md#defining-aliases) (root alias). + +Cuando utilizas la [Plantilla de Aplicación Básica](start-installation.md), puede que pongas tus clases bajo el nivel superior +de espacio de nombres `app` para que de esta manera pueda ser automáticamente cargado por Yii sin tener la necesidad de +definir un nuevo alias. Esto es porque `@app` es un [alias predefinido](concept-aliases.md#predefined-aliases), y el +nombre de una clase tal como `app\components\MyClass` puede ser resuelto en el archivo de la clase `AppBasePath/components/MyClass.php`, +de acuerdo con el algoritmo previamente descrito. + +En la [Plantilla de Aplicación Avanzada](tutorial-advanced-app.md), cada nivel tiene su propio alias. Por ejemplo, el nivel +`front-end` tiene un alias de raíz `@frontend` mientras que el nivel `back-end` tiene `@backend`. Como resultado, es posible +poner las clases `front-end` bajo el espacio de nombres `frontend` mientras que las clases `back-end` pueden hacerlo bajo +`backend`. Esto permitirá que estas clases sean automaticamente cargadas por el autocargador de Yii. + + +Mapa de Clases +-------------- + +El autocargador de clases de Yii soporta el *mapa de clases*, que mapea nombres de clases to sus correpondientes rutas de +archvios. Cuando el autocargador esta cargando una clase, primero chequeará si la clase se encuentra en el mapa. Si es así, +el correspondiente archivo será incluido directamente sin más comprobación. Esto hace que la clase se cargue muy rápidamente. +De hecho, todas las clases de Yii son autocargadas de esta manera. + +Puedes añadir una clase al mapa de clases `Yii::$classMap` de la siguiente forma, + +```php +Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php'; +``` + +[Alias](concept-aliases.md) puede ser usado para especificar la ruta de archivos de clases. Deberías iniciar el mapeo de +clases en el proceso [bootstrapping](runtime-bootstrapping.md) de la aplicación para que de esta manera el mapa esté listo +antes de que tus clases sean usadas. + + +Usando otros Autocargadores +--------------------------- + +Debido a que Yii incluye Composer como un gestor de dependencias y extensions, es recomendado que también instales el +autocargador de Composer. Si estás usando alguna librería externa que requiere sus autocargadores, también deberías +instalarlos. + +Cuando se utiliza el cargador de clases automático de Yii conjuntamente con otros autocargadores, deberías incluir el +archivo `Yii.php` *después* de que todos los demás autocargadores se hayan instalado. Esto hará que el autocargador de +Yii sea el primero en responder a cualquier petición de carga automática de clases. Por ejemplo, el siguiente código ha +sido extraido del [script de entrada](structure-entry-scripts.md) de la [Plantilla de Aplicación Básica](start-installation.md). +La primera línea instala el autocargador de Composer, mientras que la segunda línea instala el autocargador de Yii. + +```php +require __DIR__ . '/../vendor/autoload.php'; +require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; +``` + +Puedes usar el autocargador de Composer sin el autocargador de Yii. Sin embargo, al hacerlo, la eficacia de la carga de +tus clases puede que se degrade, y además deberías seguir las reglas establecidas por Composer para que tus clases pudieran +ser autocargables. + +> Note: Si no deseas utilizar el autocargador de Yii, tendrás que crear tu propia versión del archivo `Yii.php` e + incluirlo en tu [script de entrada](structure-entry-scripts.md). + + +Carga Automática de Clases de Extensiones +----------------------------------------- + +El autocargador de Yii es capaz de autocargar clases de [extensiones](structure-extensions.md). El único requirimiento es +que la extensión especifique correctamente la sección de `autoload` (autocarga) en su archivo `composer.json`. Por favor, +consulta la [documentación de Composer](https://getcomposer.org/doc/04-schema.md#autoload) para más detalles acerca de la +especificación `autoload`. + +En el caso de que no quieras usar el autocargador de Yii, el autocargador de Composer podría cargar las clases de extensiones +por tí. diff --git a/docs/guide-es/concept-behaviors.md b/docs/guide-es/concept-behaviors.md new file mode 100644 index 00000000000..b46f35af0b5 --- /dev/null +++ b/docs/guide-es/concept-behaviors.md @@ -0,0 +1,340 @@ +Comportamientos +=============== + +Comportamientos son instancias de [[yii\base\Behavior]] o sus clases "hija". Comportamientos, también conocido como +[mixins](https://es.wikipedia.org/wiki/Mixin), te permiten mejorar la funcionalidad de un [[yii\base\Component|componente]] +existente sin necesidad de modificar su herencia de clases. +Cuando un comportamiento se une a un componente, "inyectará" sus métodos y propiedades dentro del componente, y podrás +acceder a esos métodos y propiedades como si hubieran estado definidos por la clase de componente. Además, un +comportamiento puede responder a [eventos](concept-events.md) disparados por el componente de modo que se pueda personalizar +o adaptar a la ejecución normal del código del componente. + + +Definiendo comportamientos +-------------------------- + +Para definir un comportamiento, se debe crear una clase que exiende [[yii\base\Behavior]], o se extiende una clase hija. Por ejemplo: + +```php +namespace app\components; + +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + public $prop1; + + private $_prop2; + + public function getProp2() + { + return $this->_prop2; + } + + public function setProp2($value) + { + $this->_prop2 = $value; + } + + public function foo() + { + // ... + } +} +``` +El código anterior define la clase de comportamiento (behavior) app\components\MyBehavior`, con dos propiedades -- +`prop1` y `prop2`--y un método `foo()`. Tenga en cuenta que la propiedad `prop2` +se define a través de la getter `getProp2()` y el setter `setProp2()`. Este caso es porque [[yii\base\Behavior]] extiende [[yii\base\BaseObject]] y por lo tanto se apoya en la definición de [propiedades](concept-properties.md) via getters y setters. + +Debido a que esta clase es un comportamiento, cuando está unido a un componente, el componente también tienen la propiedad `prop1` y `prop2` y el método `foo()`. + +> Tip: Dentro de un comportamiento, puede acceder al componente que el comportamiento está unido a través de la propiedad [[yii\base\Behavior::owner]]. + + +Gestión de eventos de componentes +--------------------------------- + +Si un comportamiento necesita responder a los acontecimientos desencadenados por el componente al que está unido, se debe reemplazar el método [[yii\base\Behavior::events()]]. Por ejemplo: + +```php +namespace app\components; + +use yii\db\ActiveRecord; +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + // ... + + public function events() + { + return [ + ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + public function beforeValidate($event) + { + // ... + } +} +``` + +El método [[yii\base\Behavior::events()|events()]] debe devolver una lista de eventos y sus correspondientes controladores. +El ejemplo anterior declara que el evento [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] existe y esta exists y define su controlador, `beforeValidate()`. Al especificar un controlador de eventos, puede utilizar uno de los siguientes formatos: + +* una cadena que se refiere al nombre de un método de la clase del comportamiento, como el ejemplo anterior +* un arreglo de objeto o nombre de clase, y un nombre de método como una cadena (sin paréntesis), ej., `[$object, 'methodName']`; +* una función anónima + +La firma de un controlador de eventos debe ser la siguiente, donde `$ event` refiere al parámetro de evento. Por favor, consulte la sección [Eventos](concept-events.md) para más detalles sobre los eventos. + +```php +function ($event) { +} +``` + + +Vinculando Comportamientos +-------------------------- + +Puedes vincular un comportamiento a un [[yii\base\Component|componente]] ya sea estática o dinámicamente. La primera forma +es la más comúnmente utilizada en la práctica. + +Para unir un comportamiento estáticamente, reemplaza el método [[yii\base\Component::behaviors()|behaviors()]] dde la clase de componente a la que se une el comportamiento. El método [[yii\base\Component::behaviors()|behaviors()]] debe devolver una lista de comportamiento [configuraciones](concept-configurations.md). +Cada configuración de comportamiento puede ser un nombre de clase de comportamiento o un arreglo de configuración: + +```php +namespace app\models; + +use yii\db\ActiveRecord; +use app\components\MyBehavior; + +class User extends ActiveRecord +{ + public function behaviors() + { + return [ + // anonymous behavior, behavior class name only + MyBehavior::class, + + // named behavior, behavior class name only + 'myBehavior2' => MyBehavior::class, + + // anonymous behavior, configuration array + [ + 'class' => MyBehavior::class, + 'prop1' => 'value1', + 'prop2' => 'value2', + ], + + // named behavior, configuration array + 'myBehavior4' => [ + 'class' => MyBehavior::class, + 'prop1' => 'value1', + 'prop2' => 'value2', + ] + ]; + } +} +``` + +Puedes asociciar un nombre a un comportamiento especificándolo en la clave de la matriz correspondiente a la configuración +del comportamiento. En este caso, el comportamiento puede ser llamado un *comportamiento nombrado* (named behavior). En +el ejemplo anterior, hay dos tipos de comportamientos nombrados: `myBehavior2` y `myBehavior4`. Si un comportamiento +no está asociado con un nombre, se le llama *comportamiento anónimo* (anonymous behavior). + +Para vincular un comportamiento dinámicamente, llama al método [[yii\base\Component::attachBehavior()]] desde el componente al +que se le va a unir el comportamiento: + +```php +use app\components\MyBehavior; + +// vincular un objeto comportamiento "behavior" +$component->attachBehavior('myBehavior1', new MyBehavior()); + +// vincular una clase comportamiento +$component->attachBehavior('myBehavior2', MyBehavior::class); + +// asociar una matriz de configuración +$component->attachBehavior('myBehavior3', [ + 'class' => MyBehavior::class, + 'prop1' => 'value1', + 'prop2' => 'value2', +]); +``` +Puede vincular múltiples comportamientos a la vez mediante el uso del método [[yii\base\Component::attachBehaviors()]]. Por ejemplo, + +```php +$component->attachBehaviors([ + 'myBehavior1' => new MyBehavior(), // un comportamiento nombrado + MyBehavior::class, // un comportamiento anónimo +]); +``` + +También puedes asociar comportamientos a traves de [configuraciones](concept-configurations.md) como el siguiente: + +```php +[ + 'as myBehavior2' => MyBehavior::class, + + 'as myBehavior3' => [ + 'class' => MyBehavior::class, + 'prop1' => 'value1', + 'prop2' => 'value2', + ], +] +``` + +Para más detalles, por favor visita la sección [Configuraciones](concept-configurations.md#configuration-format). + + +Usando comportamientos +---------------------- + +Para poder utilizar un comportamiento, primero tienes que unirlo a un [[yii\base\Component|componente]] según las instrucciones anteriores. Una vez que un comportamiento ha sido vinculado a un componente, su uso es sencillo. + +Puedes usar a una variable *pública* o a una [propiedad](concept-properties.md) definida por un `getter` y/o un `setter` +del comportamiento a través del componente con el que se ha vinculado: + +```php +// "prop1" es una propiedad definida en la clase comportamiento +echo $component->prop1; +$component->prop1 = $value; +``` + +También puedes llamar métodos *públicos* del comportamiento de una forma similar: + +```php +// foo() es un método público definido dentro de la clase comportamiento +$component->foo(); +``` + +Como puedes ver, aunque `$component` no tiene definida `prop1` y `bar()`, que se pueden utilizar como si son parte +de la definición de componentes debido al comportamiento vinculado. + +Si dos comportamientos definen la misma propiedad o método y ambos están vinculados con el mismo componente, el +comportamiento que ha sido vinculado *primero* tendrá preferencia cuando se esté accediendo a la propiedad o método. + +Un comportamiento puede estar asociado con un nombre cuando se une a un componente. Si este es el caso, es posible +acceder al objeto de comportamiento mediante el nombre, como se muestra a continuación, + +```php +$behavior = $component->getBehavior('myBehavior'); +``` + +También puedes acceder a todos los comportamientos vinculados al componente: + +```php +$behaviors = $component->getBehaviors(); +``` + + +Desasociar Comportamientos +-------------------------- + +Para desasociar un comportamiento, puedes llamar el método [[yii\base\Component::detachBehavior()]] con el nombre con el +que se le asoció: + +```php +$component->detachBehavior('myBehavior1'); +``` + +También puedes desvincular *todos* los comportamientos: + +```php +$component->detachBehaviors(); +``` + + +Utilizando `TimestampBehavior` +----------------------------- + +Para terminar, vamos a echar un vistazo a [[yii\behaviors\TimestampBehavior]]. Este comportamiento soporta de forma +automática la actualización de atributos timestamp de un modelo [[yii\db\ActiveRecord|Registro Activo]] +(Active Record) en cualquier momento donde se guarda el modelo (ej., en la inserción o actualización). + +Primero, vincula este comportamiento a la clase [[yii\db\ActiveRecord|Active Record]] que desees utilizar. + +```php +namespace app\models\User; + +use yii\db\ActiveRecord; +use yii\behaviors\TimestampBehavior; + +class User extends ActiveRecord +{ + // ... + + public function behaviors() + { + return [ + [ + 'class' => TimestampBehavior::class, + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], + ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], + ], + ], + ]; + } +} +``` + +La configuración del comportamiento anterior especifica que + +* cuando el registro está siendo insertado, el comportamiento debe asignar el sello de tiempo actual a los atributos + `created_at` y `updated_at`; +* cuando el registro está siendo actualizado, el comportamiento debe asignar el sello de tiempo actual al atributo + `updated_at`. + +Ahora si tienes un objeto `User` e intentas guardarlo, descubrirás que sus campos `created_at` y `updated_at` están +automáticamente actualizados con el sello de tiempo actual: + +```php +$user = new User; +$user->email = 'test@example.com'; +$user->save(); +echo $user->created_at; // muestra el sello tiempo actual (timestamp) +``` + +El comportamiento [[yii\behaviors\TimestampBehavior|TimestampBehavior]] también ofrece un método muy útil llamado +[[yii\behaviors\TimestampBehavior::touch()|touch()]], que asigna el sello de tiempo actual a un atributo especificado y lo guarda automáticamente en la base de datos: + +```php +$user->touch('login_time'); +``` + + +Comparación con Traits +---------------------- + +Mientras que los comportamientos son similares a [traits](https://www.php.net/manual/es/language.oop5.traits.php) en cuanto que ambos "inyectan" sus +métodos y propiedades a la clase primaria, son diferentes en muchos aspectos. Tal y como se describe abajo, los dos +tienen sus ventajas y desventajas. Son más como complementos el uno al otro en lugar de alternativas. + + +### Razones para utilizar comportamientos + +Las clases de comportamientos, como todas las clases, soportan herencias. Traits, por otro lado, pueden ser +considerados como un copia-y-pega de PHP. Ellos no soportan la herencia de clases. + +Los comportamientos pueden ser asociados y desasociados a un componente dinámicamente sin necesidad de que la clase del +componente sea modificada. Para usar un trait, debes modificar la clase que la usa. + +Los comportamientos son configurables mientras que los traits no. + +Los comportamientos pueden personalizar la ejecución de un componente al responder a sus eventos. + +Cuando hay un conflicto de nombre entre los diferentes comportamientos vinculados a un mismo componente, el conflicto es +automáticamente resuelto respetando al que ha sido asociado primero. +El conflicto de nombres en traits requiere que manualmente sean resueltos cambiando el nombre de las propiedades o métodos afectados. + + +### Razones para utilizar los Traits + +Los Traits son mucho más eficientes que los comportamientos debido a que los últimos son objetos que consumen tiempo y +memoria. + +Los IDEs (Programas de desarrollo) son más amigables con traits ya que son una construcción del lenguaje nativo. + diff --git a/docs/guide-es/concept-components.md b/docs/guide-es/concept-components.md new file mode 100644 index 00000000000..f4ebbe6aa60 --- /dev/null +++ b/docs/guide-es/concept-components.md @@ -0,0 +1,86 @@ +Componentes +=========== + +Los componentes son los principales bloques de construcción de las aplicaciones Yii. Los componentes son instancias de [[yii\base\Component]] o de una clase extendida. Las tres características principales que los componentes proporcionan +a las otras clases son: + +* [Propiedades](concept-properties.md) +* [Eventos](concept-events.md) +* [Comportamientos](concept-behaviors.md) + +Por separado y combinadas, estas características hacen que las clases Yii sean mucho mas personalizables y sean mucho más fáciles de usar. Por ejemplo, el incluido [[yii\jui\DatePicker|widget de selección de fecha]], un componente de la interfaz de usuario, puede ser utilizado en una [vista](structure-view.md) para generar un DatePicker interactivo: + +```php +use yii\jui\DatePicker; + +echo DatePicker::widget([ + 'language' => 'ru', + 'name' => 'country', + 'clientOptions' => [ + 'dateFormat' => 'yy-mm-dd', + ], +]); +``` + +Las propiedades del widget son fácilmente modificables porque la clase se extiende de [[yii\base\Component]]. + +Mientras que los componentes son muy potentes, son un poco más pesados que los objetos normales, debido al hecho de que necesitan más memoria y tiempo de CPU para poder soportar [eventos](concept-events.md) y [comportamientos](concept-behaviors.md) en particular. +Si tus componentes no necesitan estas dos características, deberías considerar extender tu componente directamente de [[yii\base\BaseObject]] en vez de [[yii\base\Component]]. De esta manera harás que tus componentes sean mucho más eficientes que objetos PHP normales, pero con el añadido soporte para [propiedades](concept-properties.md). + +Cuando extiendes tu clase de [[yii\base\Component]] o [[yii\base\BaseObject]], se recomienda que sigas las siguientes convenciones: + +- Si sobrescribes el constructor, especifica un parámetro `$config` como el *último* parámetro del constructor, y después pasa este parámetro al constructor padre. +- Siempre llama al constructor padre al *final* de su propio constructor. +- Si sobrescribes el método [[yii\base\BaseObject::init()]], asegúrese de llamar la implementación padre de `init` * al principio * de su método` init`. + +Por ejemplo: + +```php +namespace yii\components\MyClass; + +use yii\base\BaseObject; + +class MyClass extends BaseObject +{ + public $prop1; + public $prop2; + + public function __construct($param1, $param2, $config = []) + { + // ... inicialización antes de la configuración está siendo aplicada + + parent::__construct($config); + } + + public function init() + { + parent::init(); + + // ... inicialización después de la configuración esta siendo aplicada + } +} +``` + +Siguiendo esas directrices hará que tus componentes sean [configurables](concept-configurations.md) cuando son creados. Por ejemplo: + +```php +$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]); +// alternativamente +$component = \Yii::createObject([ + 'class' => MyClass::class, + 'prop1' => 3, + 'prop2' => 4, +], [1, 2]); +``` + +> Info: Mientras que el enfoque de llamar [[Yii::createObject()]] parece mucho más complicado, es mucho más potente debido al hecho de que se implementa en la parte superior de un [contenedor de inyección de dependencia](concept-di-container.md). + + +La clase [[yii\base\BaseObject]] hace cumplir el siguiente ciclo de vida del objeto: + +1. Pre-inicialización en el constructor. Puedes establecer los valores predeterminados de propiedades aquí. +2. Configuración del objeto a través de `$config`. La configuración puede sobrescribir los valores prdeterminados dentro del constructor. +3. Post-inicialización dentro de [[yii\base\BaseObject::init()|init()]]. Puedes sobrescribir este método para realizar comprobaciones de validez y normalización de las propiedades. +4. Llamadas a métodos del objeto. + +Los tres primeros pasos ocurren dentro del constructor del objeto. Esto significa que una vez obtengas la instancia de un objeto, ésta ha sido inicializada para que puedas utilizarla adecuadamente. diff --git a/docs/guide-es/concept-configurations.md b/docs/guide-es/concept-configurations.md new file mode 100644 index 00000000000..52a54a6dea4 --- /dev/null +++ b/docs/guide-es/concept-configurations.md @@ -0,0 +1,229 @@ +Configuración +============== + +Las configuraciones se utilizan ampliamente en Yii al crear nuevos objetos o inicializar los objetos existentes. Las configuraciones por lo general incluyen el nombre de la clase del objeto que se está creando, y una lista de los valores iniciales que deberían ser asignadas a las del [propiedades](concept-properties.md) objeto. Las configuraciones también pueden incluir una lista de manipuladores que deban imponerse a del objeto [eventos](concept-events.md) y/o una lista de [comportamientos](concept-behaviors.md) que también ha de atribuirse al objeto. + +A continuación, una configuración que se utiliza para crear e inicializar una conexión a base de datos: + +```php +$config = [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; + +$db = Yii::createObject($config); +``` +El método [[Yii::createObject()]] toma una matriz de configuración como su argumento, y crea un objeto intanciando la clase llamada en la configuración. Cuando se crea una instancia del objeto, el resto de la configuración se utilizará para inicializar las propiedades del objeto, controladores de eventos y comportamientos. + +Si usted ya tiene un objeto, puede usar [[Yii::configure()]] para inicializar las propiedades del objeto con una matriz de configuración: + +```php +Yii::configure($object, $config); +``` + +Tenga en cuenta que en este caso, la matriz de configuración no debe contener un elemento `class`. + +## Formato de Configuración + +El formato de una configuración se puede describir formalmente como: + +```php +[ + 'class' => 'ClassName', + 'propertyName' => 'propertyValue', + 'on eventName' => $eventHandler, + 'as behaviorName' => $behaviorConfig, +] +``` + +donde + +* El elemento `class` especifica un nombre de clase completo para el objeto que se está creando. +* Los elementos `propertyName` especifica los valores iniciales de la propiedad con nombre. Las claves son los nombres de las propiedades y los valores son los valores iniciales correspondientes. Sólo los miembros de variables públicas y [propiedades](concept-properties.md) definidas por getters/setters se pueden configurar. +* Los elementos `on eventName` especifican qué manipuladores deberán adjuntarse al del objeto [eventos](concept-events.md). Observe que las claves de matriz se forman prefijando nombres de eventos con `on`. Por favor, consulte la sección [Eventos](concept-events.md) para los formatos de controlador de eventos compatibles. +* Los elementos `as behaviorName` especifican qué [comportamientos](concept-behaviors.md) deben adjuntarse al objeto. Observe que las claves de matriz se forman prefijando nombres de comportamiento con `as`; el valor, `$behaviorConfig`, representa la configuración para la creación de un comportamiento, como una configuración normal descrita aquí. + +A continuación se muestra un ejemplo de una configuración con los valores de propiedad iniciales, controladores de eventos y comportamientos: + +```php +[ + 'class' => 'app\components\SearchEngine', + 'apiKey' => 'xxxxxxxx', + 'on search' => function ($event) { + Yii::info("Keyword searched: " . $event->keyword); + }, + 'as indexer' => [ + 'class' => 'app\components\IndexerBehavior', + // ... property init values ... + ], +] +``` + + +## Usando Configuraciones + +Las configuraciones se utilizan en muchos lugares en Yii. Al comienzo de esta sección, hemos demostrado cómo crear un objeto según una configuración mediante el uso de [[Yii::createObject()]]. En este apartado, vamos a describir configuraciones de aplicaciones y configuraciones widget - dos principales usos de configuraciones. + + +### Configuraciones de aplicación + +Configuración para una [aplicación](structure-applications.md) es probablemente una de las configuraciones más complejas. Esto se debe a que la clase [[yii\web\Application|aplicación]] tiene un montón de propiedades y eventos configurables. Más importante aún, su propiedad [[yii\web\Application::components|componentes]] que puede recibir una gran variedad de configuraciones para crear componentes que se registran a través de la aplicación. Lo siguiente es un resumen del archivo de configuración de la aplicación para la [plantilla básica de la aplicación](start-installation.md). + +```php +$config = [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require __DIR__ . '/../vendor/yiisoft/extensions.php', + 'components' => [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + ], +]; +``` + +La configuración no tiene una clave `class`. Esto es porque se utiliza como sigue en un [script de entrada](structure-entry-scripts.md), donde ya se le da el nombre de la clase, + +```php +(new yii\web\Application($config))->run(); +``` + +Para más detalles sobre la configuración de la propiedad `components` de una aplicación se puede encontrar en la sección [Aplicación](structure-applications.md) y la sección [Localizador de Servicio](concept-service-locator.md). + + +### Configuración Widget + +Cuando se utiliza [widgets](structure-widgets.md), a menudo es necesario utilizar las configuraciones para personalizar las propiedades de widgets. Tanto los metodos [[yii\base\Widget::widget()]] y [[yii\base\Widget::begin()]] pueden usarse para crear un widget. Toman un arreglo de configuración, como el siguiente, + +```php +use yii\widgets\Menu; + +echo Menu::widget([ + 'activateItems' => false, + 'items' => [ + ['label' => 'Home', 'url' => ['site/index']], + ['label' => 'Products', 'url' => ['product/index']], + ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest], + ], +]); +``` + +El código anterior crea un widget `Menu` e inicializa su propiedad `activeItems` en falsa. La propiedad `items` también se configura con elementos de menú que se muestran. + +Tenga en cuenta que debido a que el nombre de la clase ya está dado, la matriz de configuración no deben tener la clave `class`. + + +## Archivos de Configuración + +Cuando una configuración es muy compleja, una práctica común es almacenarla en uno o múltiples archivos PHP, conocidos como *archivos de configuración*. Un archivo de configuración devuelve un array de PHP que representa la configuración. Por ejemplo, es posible mantener una configuración de la aplicación en un archivo llamado `web.php`, como el siguiente, + +```php +return [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require __DIR__ . '/../vendor/yiisoft/extensions.php', + 'components' => require __DIR__ . '/components.php', +]; +``` + +Debido a que la configuración `componentes` es compleja también, se guarda en un archivo separado llamado `components.php` y "requerir" este archivo en `web.php` como se muestra arriba. El contenido de `components.php` es el siguiente, + +```php +return [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], +]; +``` + +Para obtener una configuración almacenada en un archivo de configuración, simplemente "requerir" este, como el siguiente: + +```php +$config = require 'path/to/web.php'; +(new yii\web\Application($config))->run(); +``` + + +## Configuraciones por Defecto + +El método [[Yii::createObject()]] es implementado en base a [contenedor de inyección de dependencia](concept-di-container.md). Le permite especificar un conjunto de los llamados *configuraciones predeterminadas* que se aplicarán a todos los casos de las clases especificadas cuando se crean utilizando [[Yii::createObject()]]. Las configuraciones por defecto se puede especificar llamando `Yii::$container->set()` en el código [bootstrapping](runtime-bootstrapping.md). + +Por ejemplo, si desea personalizar [[yii\widgets\LinkPager]] para que TODO enlace de búsqueda muestre como máximo 5 botones de página (el valor por defecto es 10), puede utilizar el siguiente código para lograr este objetivo, + +```php +\Yii::$container->set('yii\widgets\LinkPager', [ + 'maxButtonCount' => 5, +]); +``` + +Sin utilizar las configuraciones predeterminadas, usted tendría que configurar `maxButtonCount` en cada lugar en el que utiliza enlace paginador. + +## Constantes de Entorno + +Las configuraciones a menudo varían de acuerdo al entorno en que se ejecuta una aplicación. Por ejemplo, en el entorno de desarrollo, es posible que desee utilizar una base de datos llamada `mydb_dev`, mientras que en servidor de producción es posible que desee utilizar la base de datos `mydb_prod`. Para facilitar la conmutación de entornos, Yii proporciona una constante llamado `YII_ENV` que se puede definir en el [script de entrada](structure-entry-scripts.md) de su aplicación. Por ejemplo, + +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +Usted puede definir `YII_ENV` como uno de los valores siguientes: + +- `prod`: entorno de producción. La constante `YII_ENV_PROD` evaluará como verdadero. +Este es el valor por defecto de `YII_ENV` si no esta definida. +- `dev`: entorno de desarrollo. La constante `YII_ENV_DEV` evaluará como verdadero. +- `test`: entorno de pruebas. La constante `YII_ENV_TEST` evaluará como verdadero. + +Con estas constantes de entorno, puede especificar sus configuraciones condicionales basado en el entorno actual. Por ejemplo, la configuración de la aplicación puede contener el siguiente código para permitir que el [depurador y barra de herramientas de depuración](tool-debugger.md) en el entorno de desarrollo. + +```php +$config = [...]; + +if (YII_ENV_DEV) { + // configuration adjustments for 'dev' environment + $config['bootstrap'][] = 'debug'; + $config['modules']['debug'] = 'yii\debug\Module'; +} + +return $config; +``` diff --git a/docs/guide-es/concept-di-container.md b/docs/guide-es/concept-di-container.md new file mode 100644 index 00000000000..9c46419f077 --- /dev/null +++ b/docs/guide-es/concept-di-container.md @@ -0,0 +1,324 @@ +Contenedor de Inyección de Dependencias +======================================= + +Un contenedor de Inyección de Dependencias (ID), es un objeto que sabe como instancias y configurar objetos y sus +objetos dependientes. El [articulo de Martin](https://martinfowler.com/articles/injection.html) contiene una buena +explicación de porque son útiles los contenedores de ID. A continuación explicaremos como usar el contenedor de ID que +proporciona Yii. + +Inyección de Dependencias +------------------------- + +Yii proporciona la función de contenedor de ID mediante la clase [[yii\di\Container]]. Soporta los siguientes tipos +de ID: + +* Inyección de constructores; +* Inyección de setters y propiedades; +* Inyección de [llamadas de retorno PHP](https://www.php.net/manual/es/language.types.callable.php); + +### Inyección de Constructores + +El contenedor de ID soporta inyección de constructores con la ayuda de los indicios (hint) de tipo para los parámetros del +constructor. Los indicios de tipo le proporcionan información al contenedor para saber cuáles son las clases o +interfaces dependientes al usarse para crear un nuevo objeto. El contenedor intentara obtener las instancias de las +clases o interfaces dependientes y las inyectará dentro del nuevo objeto mediante el constructor. Por ejemplo, + +```php +class Foo +{ + public function __construct(Bar $bar) + { + } +} + +$foo = $container->get('Foo'); +// que es equivalente a: +$bar = new Bar; +$foo = new Foo($bar); +``` + +### Inyección de Setters y Propiedades + +La inyección de setters y propiedades se admite a través de [configuraciones](concept-configurations.md). Cuando se +registra una dependencia o se crea un nuevo objeto, se puede proporcionar una configuración que usará el contenedor +para inyectar las dependencias a través de sus correspondientes setters y propiedades. Por ejemplo, + +```php +use yii\base\BaseObject; + +class Foo extends BaseObject +{ + public $bar; + + private $_qux; + + public function getQux() + { + return $this->_qux; + } + + public function setQux(Qux $qux) + { + $this->_qux = $qux; + } +} + +$container->get('Foo', [], [ + 'bar' => $container->get('Bar'), + 'qux' => $container->get('Qux'), +]); +``` + +### Inyección de Llamadas de retorno PHP + +En este caso, el contenedor usará una llamada de retorno PHP registrada para construir una nueva instancia de una +clase. La llamada de retorno se responsabiliza de que dependencias debe inyectar al nuevo objeto creado. Por ejemplo, + +```php +$container->set('Foo', function ($container, $params, $config) { + return new Foo(new Bar); +}); + +$foo = $container->get('Foo'); +``` + +Registro de dependencias +------------------------ + +Se puede usar [[yii\di\Container::set()]] para registrar dependencias. El registro requiere un nombre de dependencia +así como una definición de dependencia. Un nombre de dependencia puede ser un nombre de clase, un nombre de interfaz, +o un nombre de alias; y una definición de dependencia puede ser un nombre de clase, un array de configuración, o una +llamada de retorno PHP. + +```php +$container = new \yii\di\Container; + +// registra un nombre de clase como tal. Puede se omitido. +$container->set('yii\db\Connection'); + +// registra una interfaz +// Cuando una clase depende de una interfaz, la clase correspondiente +// se instanciará como un objeto dependiente +$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); + +// registra un nombre de alias. Se puede usar $container->get('foo') +// para crear una instancia de Connection +$container->set('foo', 'yii\db\Connection'); + +// registrar una clase con configuración. La configuración +// se aplicara cuando la clase se instancie por get() +$container->set('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// registra un nombre de alias con configuración de clase +// En este caso, se requiere un elemento "clase" para especificar la clase +$container->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// registra una llamada de retorno de PHP +// La llamada de retorno sera ejecutada cada vez que se ejecute $container->get('db') +$container->set('db', function ($container, $params, $config) { + return new \yii\db\Connection($config); +}); + +// registra un componente instancia +// $container->get('pageCache') devolverá la misma instancia cada vez que se ejecute +$container->set('pageCache', new FileCache); +``` + +> Tip: Si un nombre de dependencia es el mismo que la definición de dependencia, no es necesario registrarlo con + el contenedor de ID. + +Una dependencia registrada mediante `set()` generará una instancia cada vez que se necesite la dependencia. Se puede +usar [[yii\di\Container::setSingleton()]] para registrar una dependencia que genere una única instancia: + +```php +$container->setSingleton('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); +``` + +Resolución de Dependencias +-------------------------- + +Una ves se hayan registrado las dependencias, se puede usar el contenedor de ID para crear nuevos objetos, y el +contenedor resolverá automáticamente las dependencias instanciándolas e inyectándolas dentro de los nuevos objetos +creados. La resolución de dependencias es recursiva, esto significa que si una dependencia tiene otras dependencias, +estas dependencias también se resolverán automáticamente. + +Se puede usar [[yii\di\Container::get()]] para crear nuevos objetos. El método obtiene el nombre de dependencia, que +puede ser un nombre de clase, un nombre de interfaz o un nombre de alias. El nombre de dependencia puede estar +registrado o no mediante `set()` o `setSingleton()`. Se puede proporcionar opcionalmente un listado de los parámetros +del constructor de clase y una [configuración](concept-configurations.md) para configurar los nuevos objetos creados. +Por ejemplo, + +```php +// "db" ha sido registrado anteriormente como nombre de alias +$db = $container->get('db'); + +// equivalente a: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); +$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); +``` + +Por detrás, el contenedor de ID efectúa mucho más trabajo la creación de un nuevo objeto. El contenedor primero +inspeccionará la clase constructora para encontrar los nombres de clase o interfaces dependientes y después +automáticamente resolverá estas dependencias recursivamente. + +El siguiente código muestra un ejemplo más sofisticado. La clase `UserLister` depende del un objeto que implementa la +interfaz `UserFinderInterface`; la clase `UserFinder` implementa la interfaz y depende del objeto `Connection`. Todas +estas dependencias se declaran a través de insinuaciones (hinting) de los parámetros del constructor de clase. Con el +registro de dependencia de propiedades, el contenedor de ID puede resolver las dependencias automáticamente y crear +una nueva instancia de `UserLister` con una simple llamada a `get('userLister')`. + +```php +namespace app\models; + +use yii\base\BaseObject; +use yii\db\Connection; +use yii\di\Container; + +interface UserFinderInterface +{ + function findUser(); +} + +class UserFinder extends BaseObject implements UserFinderInterface +{ + public $db; + + public function __construct(Connection $db, $config = []) + { + $this->db = $db; + parent::__construct($config); + } + + public function findUser() + { + } +} + +class UserLister extends BaseObject +{ + public $finder; + + public function __construct(UserFinderInterface $finder, $config = []) + { + $this->finder = $finder; + parent::__construct($config); + } +} + +$container = new Container; +$container->set('yii\db\Connection', [ + 'dsn' => '...', +]); +$container->set('app\models\UserFinderInterface', [ + 'class' => 'app\models\UserFinder', +]); +$container->set('userLister', 'app\models\UserLister'); + +$lister = $container->get('userLister'); + +// que es equivalente a: + +$db = new \yii\db\Connection(['dsn' => '...']); +$finder = new UserFinder($db); +$lister = new UserLister($finder); +``` + +Uso Practico +------------ + +Yii crea un contenedor de ID cuando se incluye el archivo `Yii.php` en el +[script de entrada](structure-entry-scripts.md) de la aplicación. Cuando se llama a [[Yii::createObject()]] el método +realmente llama al contenedor del método [[yii\di\Container::get()|get()]] para crear un nuevo objeto. Como se ha +comentado anteriormente, el contenedor de ID resolverá automáticamente las dependencias (si las hay) y las inyectará +dentro del nuevo objeto creado. Debido a que Yii utiliza [[Yii::createObject()]] en la mayor parte del núcleo (core) +para crear nuevo objetos, podemos personalizar los objetos globalmente para que puedan tratar con [[Yii::$container]]. + +Por ejemplo, se puede personalizar globalmenete el numero predeterminado de números de botones de paginación de +[[yii\widgets\LinkPager]]: + +```php +\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); +``` + +Ahora si se usa el widget en una vista con el siguiente código, la propiedad `maxButtonCount` será inicializada con +valor 5 en lugar de 10 que es el valor predeterminado definido en la clase. + +```php +echo \yii\widgets\LinkPager::widget(); +``` + +Se puede sobrescribir el valor establecido mediante el contenedor de ID, como a continuación: + +```php +echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); +``` + +Otro ejemplo es aprovechar la ventaja de la inyección automática de constructores de contenedores de ID. Asumiendo que +la clase controlador depende de otros objetos, tales como un servicio de reservas de hotel. Se puede declarar una +dependencia a través de un parámetro del constructor y permitir al contenedor de ID resolverla por nosotros. + +```php +namespace app\controllers; + +use yii\web\Controller; +use app\components\BookingInterface; + +class HotelController extends Controller +{ + protected $bookingService; + + public function __construct($id, $module, BookingInterface $bookingService, $config = []) + { + $this->bookingService = $bookingService; + parent::__construct($id, $module, $config); + } +} +``` + +Si se accede al controlador desde el navegador, veremos un error advirtiendo que `BookingInterface` no puede ser +instanciada. Esto se debe a que necesitamos indicar al contenedor de ID como tratar con esta dependencia: + +```php +\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); +``` + +Ahora si se accede al contenedor nuevamente, se creará una instancia de `app\components\BookingService` y se inyectará +a como tercer parámetro al constructor del controlador. + +Cuando Registrar Dependencias +----------------------------- + +El registro de dependencias debe hacerse lo antes posible debido a que las dependencias se necesitan cuando se crean +nuevos objetos. A continuación se listan practicas recomendadas: + +* Siendo desarrolladores de una aplicación, podemos registrar dependencias en el + [script de entrada](structure-entry-scripts.md) o en un script incluido en el script de entrada. +* Siendo desarrolladores de una [extension](structure-extensions.md) redistribuible, podemos registrar dependencias en + la clase de boostraping de la extensión. + +Resumen +------- + +Tanto la inyección de dependencias como el [localizador de servicios](concept-service-locator.md) son patrones de +diseño populares que permiten construir software con acoplamiento flexible y más fácil de testear. Se recomienda +encarecida la lectura del articulo de [Martin](https://martinfowler.com/articles/injection.html) para obtener una mejor +comprensión de la inyección de dependencias y de la localización de servicios. + +Yii implementa su propio [localizador de servicios](concept-service-locator.md) por encima del contenedor de ID. +Cuando un localizador de servicios intenta crear una nueva instancia de objeto, se desviará la llamada al contenedor +de ID. Este último resolverá las dependencias automáticamente como se ha descrito anteriormente. diff --git a/docs/guide-es/concept-events.md b/docs/guide-es/concept-events.md new file mode 100644 index 00000000000..664d6109b7a --- /dev/null +++ b/docs/guide-es/concept-events.md @@ -0,0 +1,292 @@ +Eventos +======= + +Los eventos permiten inyectar código dentro de otro código existente en ciertos puntos de ejecución. Se pueden adjuntar +código personalizado a un evento, cuando se lance (triggered), el código se ejecutará automáticamente. Por ejemplo, un +objeto mailer puede lanzar el evento `messageSent` cuando se envía un mensaje correctamente. Si se quiere rastrear +el correcto envío del mensaje, se puede, simplemente, añadir un código de seguimiento al evento `messageSent`. + +Yii introduce una clase base [[yii\base\Component]] para soportar eventos. Si una clase necesita lanzar un evento, +este debe extender a [[yii\base\Component]] o a una clase hija. + +Gestor de Eventos +----------------- + +Un gestor de eventos es una +[llamada de retorno PHP (PHP callback)](https://www.php.net/manual/es/language.types.callable.php) que se ejecuta cuando se +lanza el evento al que corresponde. Se puede usar cualquier llamada de retorno de las enumeradas a continuación: + +- una función de PHP global especificada como una cadena de texto (sin paréntesis), ej. `'trim'`; +- un método de objeto especificado como un array de un objeto y un nombre de método como una cadena de texto + (sin paréntesis), ej. `[$object, 'methodNAme']`; +- un método de clase estático especificado como un array de un nombre de clase y un método como una cadena de texto + (sin paréntesis), ej. `[$class, 'methodName']`; +- una función anónima, ej. `function ($event) { ... }`. + +La firma de un gestor de eventos es: + +```php +function ($event) { + // $event es un objeto de yii\base\Event o de una clase hija +} +``` + +Un gestor de eventos puede obtener la siguiente información acerca de un evento ya sucedido mediante el parámetro +`$event`: + +- [[yii\base\Event::name|nombre del evento]] +- [[yii\base\Event::sender|evento enviando]]: el objeto desde el que se ha ejecutado `trigger()` +- [[yii\base\Event::data|custom data]]: los datos que se proporcionan al adjuntar el gestor de eventos + (se explicará más adelante) + + +Añadir Gestores de Eventos +-------------------------- + +Se puede añadir un gestor a un evento llamando al método [[yii\base\Component::on()]]. Por ejemplo: + +```php +$foo = new Foo; + +// este gestor es una función global +$foo->on(Foo::EVENT_HELLO, 'function_name'); + +// este gestor es un método de objeto +$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); + +// este gestor es un método de clase estática +$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// este gestor es una función anónima +$foo->on(Foo::EVENT_HELLO, function ($event) { + // event handling logic +}); +``` + +También se pueden adjuntar gestores de eventos mediante [configuraciones](concept-configurations.md). Se pueden +encontrar más de talles en la sección [Configuraciones](concept-configurations.md#configuration-format). + +Cuando se adjunta un gestor de eventos, se pueden proporcionar datos adicionales como tercer parámetro de +[[yii\base\Component::on()]]. El gestor podrá acceder a los datos cuando se lance el evento y se ejecute el gestor. +Por ejemplo: + +```php +// El siguiente código muestra "abc" cuando se lanza el evento +// ya que $event->data contiene los datos enviados en el tercer parámetro de "on" +$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc'); + +function function_name($event) { + echo $event->data; +} +``` + +Ordenación de Gestores de Eventos +--------------------------------- + +Se puede adjuntar uno o más gestores a un único evento. Cuando se lanza un evento, se ejecutarán los gestores adjuntos +en el orden que se hayan añadido al evento. Si un gestor necesita parar la invocación de los gestores que le siguen, +se puede establecer la propiedad [[yii\base\Event::handled]] del parámetro `$event` para que sea `true`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + $event->handled = true; +}); +``` + +De forma predeterminada, cada nuevo gestor añadido se pone a la cola de la lista de gestores del evento. Por lo tanto, +el gestor se ejecutará en el último lugar cuando se lance el evento. Para insertar un nuevo gestor al principio de la +cola de gestores para que sea ejecutado primero, se debe llamar a [[yii\base\Component::on()]], pasando al cuarto +parámetro `$append` el valor `false`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + // ... +}, $data, false); +``` + +Lanzamiento de Eventos +---------------------- + +Los eventos se lanzan llamando al método [[yii\base\Component::trigger()]]. El método requiere un *nombre de evento*, +y de forma opcional un objeto de evento que describa los parámetros que se enviarán a los gestores de eventos. Por +ejemplo: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class Foo extends Component +{ + const EVENT_HELLO = 'hello'; + + public function bar() + { + $this->trigger(self::EVENT_HELLO); + } +} +``` + +Con el código anterior, cada llamada a `bar()` lanzará un evento llamado `hello` + +> Tip: Se recomienda usar las constantes de clase para representar nombres de eventos. En el anterior ejemplo, la + constante `EVENT_HELLO` representa el evento `hello`. Este enfoque proporciona tres beneficios. Primero, previene + errores tipográficos. Segundo, puede hacer que los IDEs reconozcan los eventos en las funciones de auto-completado. + Tercero, se puede ver que eventos soporta una clase simplemente revisando la declaración de constantes. + +A veces cuando se lanza un evento se puede querer pasar información adicional al gestor de eventos. Por ejemplo, un +mailer puede querer enviar la información del mensaje para que los gestores del evento `messageSent` para que los +gestores puedan saber las particularidades del mensaje enviado. Para hacerlo, se puede proporcionar un objeto de tipo +evento como segundo parámetro al método [[yii\base\Component::trigger()]]. El objeto de tipo evento debe ser una +instancia de la clase [[yii\base\Event]] o de su clase hija. Por ejemplo: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class MessageEvent extends Event +{ + public $message; +} + +class Mailer extends Component +{ + const EVENT_MESSAGE_SENT = 'messageSent'; + + public function send($message) + { + // ...enviando $message... + + $event = new MessageEvent; + $event->message = $message; + $this->trigger(self::EVENT_MESSAGE_SENT, $event); + } +} +``` + +Cuando se lanza el método [[yii\base\Component::trigger()]], se ejecutarán todos los gestores adjuntos al evento. + +Desadjuntar Gestores de Evento +------------------------------ + +Para desadjuntar un gestor de un evento, se puede ejecutar el método [[yii\base\Component::off()]]. Por ejemplo: + +```php +// el gestor es una función global +$foo->off(Foo::EVENT_HELLO, 'function_name'); + +// el gestor es un método de objeto +$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); + +// el gestor es un método estático de clase +$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// el gestor es una función anónima +$foo->off(Foo::EVENT_HELLO, $anonymousFunction); +``` + +Tenga en cuenta que en general no se debe intentar desadjuntar las funciones anónimas a no ser que se almacene donde +se ha adjuntado al evento. En el anterior ejemplo, se asume que la función anónima se almacena como variable +`$anonymousFunction`. + +Para desadjuntar TODOS los gestores de un evento, se puede llamar [[yii\base\Component::off()]] sin el segundo +parámetro: + +```php +$foo->off(Foo::EVENT_HELLO); +``` + +Nivel de Clase (Class-Level) Gestores de Eventos +------------------------------------------------ + +En las subsecciones anteriores se ha descrito como adjuntar un gestor a un evento a *nivel de instancia*. A veces, se +puede querer que un gestor responda todos los eventos de *todos* las instancias de una clase en lugar de una instancia +especifica. En lugar de adjuntar un gestor de eventos a una instancia, se puede adjuntar un gestor a *nivel de clase* +llamando al método estático [[yii\base\Event::on()]]. + +Por ejemplo, un objeto de tipo [Active Record](db-active-record.md) lanzará un evento +[[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] cada vez que inserte un nuevo registro en la base +de datos. Para poder registrar las inserciones efectuadas por *todos* los objetos +[Active Record](db-active-record.md), se puede usar el siguiente código: + +```php +use Yii; +use yii\base\Event; +use yii\db\ActiveRecord; + +Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::debug(get_class($event->sender) . ' is inserted'); +}); +``` + +Se invocará al gestor de eventos cada vez que una instancia de [[yii\db\ActiveRecord|ActiveRecord]], o de uno de sus +clases hijas, lance un evento de tipo [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]]. Se puede +obtener el objeto que ha lanzado el evento mediante `$event->sender` en el gestor. + +Cuando un objeto lanza un evento, primero llamará los gestores a nivel de instancia, y a continuación los gestores a +nivel de clase. + +Se puede lanzar un evento de tipo *nivel de clase* llamando al método estático [[yii\base\Event::trigger()]]. Un +evento de nivel de clase no se asocia a un objeto en particular. Como resultado, esto provocará solamente la +invocación de los gestores de eventos a nivel de clase. + +```php +use yii\base\Event; + +Event::on(Foo::class, Foo::EVENT_HELLO, function ($event) { + var_dump($event->sender); // displays "null" +}); + +Event::trigger(Foo::class, Foo::EVENT_HELLO); +``` + +Tenga en cuenta que en este caso, el `$event->sender` hace referencia al nombre de la clase que lanza el evento en +lugar de a la instancia del objeto. + +> Note: Debido a que los gestores a nivel de clase responderán a los eventos lanzados por cualquier instancia de la +clase, o cualquier clase hija, se debe usar con cuidado, especialmente en las clases de bajo nivel (low-level), tales +como [[yii\base\BaseObject]]. + +Para desadjuntar un gestor de eventos a nivel de clase, se tiene que llamar a [[yii\base\Event::off()]]. Por ejemplo: + +```php +// desadjunta $handler +Event::off(Foo::class, Foo::EVENT_HELLO, $handler); + +// desadjunta todos los gestores de Foo::EVENT_HELLO +Event::off(Foo::class, Foo::EVENT_HELLO); +``` + +Eventos Globales +---------------- + +Yii soporta los llamados *eventos globales*, que en realidad es un truco basado en el gestor de eventos descrito +anteriormente. El evento global requiere un Singleton globalmente accesible, tal como la instancia de +[aplicación](structure-applications.md) en si misma. + +Para crear un evento global, un evento remitente (event sender) llama al método `trigger()` del Singleton para lanzar +el evento, en lugar de llamar al propio método `trigger()` del remitente. De forma similar, los gestores de eventos se +adjuntan al evento del Singleton. Por ejemplo: + +```php +use Yii; +use yii\base\Event; +use app\components\Foo; + +Yii::$app->on('bar', function ($event) { + echo get_class($event->sender); // muestra "app\components\Foo" +}); + +Yii::$app->trigger('bar', new Event(['sender' => new Foo])); +``` + +Un beneficio de usar eventos globales es que no se necesita un objeto cuando se adjuntan gestores a un evento para que +sean lanzados por el objeto. En su lugar, los gestores adjuntos y el lanzamiento de eventos se efectúan en el +Singleton (ej. la instancia de la aplicación). + +Sin embargo, debido a que los `namespaces` de los eventos globales son compartidos por todas partes, se les deben +asignar nombres bien pensados, como puede ser la introducción de algún `namespace` +(ej. "frontend.mail.sent", "backend.mail.sent"). diff --git a/docs/guide-es/concept-properties.md b/docs/guide-es/concept-properties.md new file mode 100644 index 00000000000..7187278f1aa --- /dev/null +++ b/docs/guide-es/concept-properties.md @@ -0,0 +1,88 @@ +Propiedades +=========== + +En PHP, las variables miembro de clases también llamadas *propiedades*, son parte de la definición de la clase, y se +usan para representar el estado de una instancia de la clase (ej. para diferenciar una instancia de clase de otra). +A la práctica, a menudo, se puede querer gestionar la lectura o escritura de las propiedades de algunos momentos. Por +ejemplo, se puede querer eliminar los espacios en blanco (trim) de una cadena de texto cada vez que esta se asigne a +una propiedad de tipo `label`. Se *podría* usar el siguiente código para realizar esta tarea: + +```php +$object->label = trim($label); +``` + +La desventaja del código anterior es que se tendría que ejecutar `trim()` en todas las partes del código que pudieran +establecer la propiedad `label`. Si en el futuro, la propiedad `label` tiene que seguir otro funcionamiento, como por +ejemplo que la primera letra tiene que estar en mayúsculas, se tendrán que modificar todas las secciones de código que +asignen el valor a la propiedad `label`. La repetición de código conlleva a bugs, y es una practica que se tiene que +evitar en la medida de lo posible. + +Para solventar este problema, Yii introduce la clase base llamada [[yii\base\BaseObject]] que da soporte a la definición +de propiedades basada en los métodos de clase *getter* y *setter*. Si una clase necesita más funcionalidad, debe +extender a la clase [[yii\base\BaseObject]] o a alguna de sus hijas. + +> Info: Casi todas las clases del núcleo (core) en el framework Yii extienden a [[yii\base\BaseObject]] o a una de + sus clases hijas. Esto significa que siempre que se encuentre un getter o un setter en una clase del núcleo, se + puede utilizar como una propiedad. + +Un método getter es un método cuyo nombre empieza por la palabra `get`: un metodo setter empieza por `set`. El nombre +añadido detrás del prefijo `get` o `set` define el nombre de la propiedad. Por ejemplo, un getter `getLabel()` y/o un +setter `setLabel()` definen la propiedad `label`, como se muestra a continuación: + +```php +namespace app\components; + +use yii\base\BaseObject; + +class Foo extends BaseObject +{ + private $_label; + + public function getLabel() + { + return $this->_label; + } + + public function setLabel($value) + { + $this->_label = trim($value); + } +} +``` + +(Para ser claros, los métodos getter y setter crean la propiedad `label`, que en este caso hace una referencia interna +al nombre de atributo privado `_label`.) + +Las propiedades definidas por los getter y los setters se pueden usar como variables de clase miembro. La principal +diferencia radica en que cuando esta propiedad se lea, se ejecutará su correspondiente método getter; cuando se asigne +un valor a la propiedad, se ejecutará el correspondiente método setter. Por ejemplo: + +```php +// equivalente a $label = $object->getLabel(); +$label = $object->label; + +// equivalente a $object->setLabel('abc'); +$object->label = 'abc'; +``` + +Una propiedad definida por un getter sin un setter es de tipo *sólo lectura*. Si se intenta asignar un valor a esta +propiedad se producirá una excepción de tipo [[yii\base\InvalidCallException|InvalidCallException]]. Del mismo modo +que una propiedad definida con un setter pero sin getter será de tipo *sólo escritura*, cualquier intento de lectura +de esta propiedad producirá una excepción. No es común tener variables de tipo sólo escritura. + +Hay varias reglas especiales y limitaciones en las propiedades definidas mediante getters y setters: + +* Los nombres de estas propiedades son *case-insensitive*. Por ejemplo, `$object->label` y `$object->Label` son la + misma. Esto se debe a que los nombres de los métodos en PHP son case-insensitive. +* Si el nombre de una propiedad de este tipo es igual al de una variable miembro de la clase, la segunda tendrá + prioridad. Por ejemplo, si la anterior clase `Foo` tiene la variable miembro `label`, entonces la asignación + `$object->label = 'abc'` afectará a la *variable miembro* 'label'; no se ejecutará el método setter `setLabel()`. +* Estas variables no soportan la visibilidad. No hay diferencia en definir los métodos getter o setter en una + propiedad public, protected, o private. +* Las propiedades sólo se pueden definir por getters y setters *no estáticos*. Los métodos estáticos no se tratarán de + la misma manera. + +Volviendo de nuevo al problema descrito al principio de la guía, en lugar de ejecutar `trim()` cada vez que se asigne +un valor a `label`, ahora `trim()` sólo necesita ser invocado dentro del setter `setLabel()`. I si se tiene que añadir +un nuevo requerimiento, para que `label` empiece con una letra mayúscula, se puede modificar rápidamente el método ` +setLabel()` sin tener que modificar más secciones de código. El cambio afectará a cada asignación de `label`. diff --git a/docs/guide-es/concept-service-locator.md b/docs/guide-es/concept-service-locator.md new file mode 100644 index 00000000000..8a74ef24d72 --- /dev/null +++ b/docs/guide-es/concept-service-locator.md @@ -0,0 +1,71 @@ +Localizador de Servicios +======================== + +Un localizador de servicios es un objeto que sabe cómo proporcionar todo tipo de servicios (o componentes) que puede necesitar una aplicación. Dentro de un localizador de servicios, existe en cada componente como una única instancia, únicamente identificado por un ID. Se utiliza el ID para recuperar un componente desde el localizador de servicios. + +En Yii, un localizador de servicio es simplemente una instancia de [[yii\di\ServiceLocator]], o de una clase hija. + +El localizador de servicio más utilizado en Yii es el objeto *aplicación*, que se puede acceder a través de `\Yii::$app`. Los servicios que prestá son llamadas *componentes de la aplicación*, como los componentes `request`, `response`, and `urlManager`. Usted puede configurar estos componentes, o incluso cambiarlos por sus propias implementaciones fácilmente a través de la funcionalidad proporcionada por el localizador de servicios. + +Además del objeto de aplicación, cada objeto módulo es también un localizador de servicios. + +Para utilizar un localizador de servicios, el primer paso es registrar los componentes de la misma. Un componente se puede registrar a través de [[yii\di\ServiceLocator::set()]]. El código siguiente muestra diferentes maneras de registrarse componentes: + +```php +use yii\di\ServiceLocator; +use yii\caching\FileCache; + +$locator = new ServiceLocator; + +// register "cache" using a class name that can be used to create a component +$locator->set('cache', 'yii\caching\ApcCache'); + +// register "db" using a configuration array that can be used to create a component +$locator->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', +]); + +// register "search" using an anonymous function that builds a component +$locator->set('search', function () { + return new app\components\SolrService; +}); + +// register "pageCache" using a component +$locator->set('pageCache', new FileCache); +``` + +Una vez que el componente se ha registrado, usted puede acceder a él utilizando su ID, en una de las dos formas siguientes: + +```php +$cache = $locator->get('cache'); +// or alternatively +$cache = $locator->cache; +``` + +Como puede observarse, [[yii\di\ServiceLocator]] le permite acceder a un componente como una propiedad utilizando el ID de componente. Cuando acceda a un componente, por primera vez, [[yii\di\ServiceLocator]] utilizará la información de registro de componente para crear una nueva instancia del componente y devolverlo. Más tarde, si se accede de nuevo al componente, el localizador de servicio devolverá la misma instancia. + +Usted puede utilizar [[yii\di\ServiceLocator::has()]] para comprobar si un ID de componente ya ha sido registrada. +Si llama [[yii\di\ServiceLocator::get()]] con una identificación válida, se produce una excepción. + +Debido a que los localizadores de servicios a menudo se crean con [configuraciones](concept-configurations.md), se proporciona una propiedad que puede escribir el nombre [[yii\di\ServiceLocator::setComponents()|components]]. Esto le permite configurar y registrar varios componentes a la vez. El siguiente código muestra un arreglo de configuración que se puede utilizar para configurar una aplicación, al mismo tiempo que el registro de la "db", "cache" y "buscar" componentes: + +```php +return [ + // ... + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', + ], + 'cache' => 'yii\caching\ApcCache', + 'search' => function () { + return new app\components\SolrService; + }, + ], +]; +``` diff --git a/docs/guide-es/db-dao.md b/docs/guide-es/db-dao.md new file mode 100644 index 00000000000..62d8487f6d1 --- /dev/null +++ b/docs/guide-es/db-dao.md @@ -0,0 +1,636 @@ +Objetos de Acceso a Bases de Datos +================================== + +Construido sobre [PDO](https://www.php.net/manual/es/book.pdo.php), Yii DAO (Objetos de Acceso a Bases de Datos) proporciona una +API orientada a objetos para el acceso a bases de datos relacionales. Es el fundamento para otros métodos de acceso a bases de datos +más avanzados, incluyendo el [constructor de consultas](db-query-builder.md) y [active record](db-active-record.md). + +Al utilizar Yii DAO, principalmente vas a tratar con SQLs planos y arrays PHP. Como resultado, esta es la manera más eficiente +de acceder a las bases de datos. Sin embargo, como la sintaxis puede variar para las diferentes bases de datos, utilizando +Yii DAO también significa que tienes que tienes que tomar un esfuerzo adicional para crear una aplicación de database-agnostic. + +Yii DAO soporta las siguientes bases de datos: + +- [MySQL](https://www.mysql.com/) +- [MariaDB](https://mariadb.com/) +- [SQLite](https://sqlite.org/) +- [PostgreSQL](https://www.postgresql.org/): versión 8.4 o superior. +- [CUBRID](https://www.cubrid.org/): versión 9.3 o superior. +- [Oracle](https://www.oracle.com/database/) +- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): versión 2008 o superior. + +## Creando Conexiones DB + +Para acceder a una base de datos, primero necesitas conectarte a tu bases de datos mediante la creación +de una instancia de [[yii\db\Connection]]: + +```php +$db = new yii\db\Connection([ + 'dsn' => 'mysql:host=localhost;dbname=example', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); +``` + +Debido a una conexión DB a menudo necesita ser accedido en diferentes lugares, una práctica común es +configurarlo en términos de un [componente de aplicación](structure-application-components.md) como +se muestra a continuación: + +```php +return [ + // ... + 'components' => [ + // ... + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=example', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + ], + // ... +]; +``` + +Puedes acceder a la conexión DB mediante la expresión `Yii::$app->db`. + +> Tip: Puedes configurar múltiples componentes de aplicación DB si tu aplicación necesita acceder a múltiples bases de datos. + +Cuando configuras una conexión DB, deberías siempre especificar el Nombre de Origen de Datos (DSN) mediante la +propiedad [[yii\db\Connection::dsn|dsn]]. El formato del DSN varia para cada diferente base de datos. Por favor consulte el +[manual de PHP](https://www.php.net/manual/es/function.PDO-construct.php) para más detalles. Abajo están algunos ejemplos: + +* MySQL, MariaDB: `mysql:host=localhost;dbname=mydatabase` +* SQLite: `sqlite:/path/to/database/file` +* PostgreSQL: `pgsql:host=localhost;port=5432;dbname=mydatabase` +* CUBRID: `cubrid:dbname=demodb;host=localhost;port=33000` +* MS SQL Server (mediante sqlsrv driver): `sqlsrv:Server=localhost;Database=mydatabase` +* MS SQL Server (mediante dblib driver): `dblib:host=localhost;dbname=mydatabase` +* MS SQL Server (mediante mssql driver): `mssql:host=localhost;dbname=mydatabase` +* Oracle: `oci:dbname=//localhost:1521/mydatabase` + +Nota que si estás conectándote con una base de datos mediante ODBC, deberías configurar la propiedad [[yii\db\Connection::driverName]] +para que Yii pueda conocer el tipo de base de datos actual. Por ejemplo, + +```php +'db' => [ + 'class' => 'yii\db\Connection', + 'driverName' => 'mysql', + 'dsn' => 'odbc:Driver={MySQL};Server=localhost;Database=test', + 'username' => 'root', + 'password' => '', +], +``` + +Además de la propiedad [[yii\db\Connection::dsn|dsn]], a menudo es necesario configurar el [[yii\db\Connection::username|username]] +y [[yii\db\Connection::password|password]]. Por favor consulta [[yii\db\Connection]] para ver la lista completa de propiedades configurables. + +> Info: Cuando se crea una instancia de conexión DB, la conexión actual a la base de datos no se establece hasta que + ejecutes el primer SQL o llames explícitamente al método [[yii\db\Connection::open()|open()]]. + + +## Ejecutando Consultas SQL + +Una vez tienes instanciada una conexión a la base de datos, se pueden ejecutar consultas SQL tomando +los siguientes pasos: + +1. Crea un [[yii\db\Command]] con SQL plano; +2. Vincula parámetros (opcional); +3. Llama a uno de los métodos de ejecución SQL con [[yii\db\Command]]. + +El siguiente ejemplo muestra varias maneras de obtener datos de una base de datos: + +```php +$db = new yii\db\Connection(...); + +// retorna un conjunto de filas. Cada fila es un array asociativo de columnas de nombres y valores. +// un array vacío es retornado si no hay resultados +$posts = $db->createCommand('SELECT * FROM post') + ->queryAll(); + +// retorna una sola fila (la primera fila) +// `false` es retornado si no hay resultados +$post = $db->createCommand('SELECT * FROM post WHERE id=1') + ->queryOne(); + +// retorna una sola columna (la primera columna) +// un array vacío es retornado si no hay resultados +$titles = $db->createCommand('SELECT title FROM post') + ->queryColumn(); + +// retorna un escalar +// `false` es retornado si no hay resultados +$count = $db->createCommand('SELECT COUNT(*) FROM post') + ->queryScalar(); +``` + +> Note: Para preservar la precisión, los datos obtenidos de las bases de datos son todos representados como cadenas, incluso si el tipo de columna correspondiente +a la base de datos es numérico. + +> Tip: Si necesitas ejecutar una consulta SQL inmediatamente después de establecer una conexión (ej., para establecer una zona horaria o un conjunto de caracteres), +> puedes hacerlo con el evento [[yii\db\Connection::EVENT_AFTER_OPEN]]. Por ejemplo, +> +```php +return [ + // ... + 'components' => [ + // ... + 'db' => [ + 'class' => 'yii\db\Connection', + // ... + 'on afterOpen' => function($event) { + // $event->sender se refiere a la conexión DB + $event->sender->createCommand("SET time_zone = 'UTC'")->execute(); + } + ], + ], + // ... +]; +``` + + +### Parámetros Vinculados (Binding Parameters) + +Cuando creamos un comando DB para un SQL con parámetros, nosotros deberíamos casi siempre aprovechar el uso de los parámetros vinculados +para prevenir los ataques de inyección de SQL. Por ejemplo, + +```php +$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') + ->bindValue(':id', $_GET['id']) + ->bindValue(':status', 1) + ->queryOne(); +``` + +En la sentencia SQL, puedes incrustar uno o múltiples parámetros placeholders (ej. `:id` en el ejemplo anterior). Un parámetro +placeholder debería ser una cadena que empiece con dos puntos. A continuación puedes llamar a uno de los siguientes métodos para +unir los valores de los parámetros vinculados: + +* [[yii\db\Command::bindValue()|bindValue()]]: une un solo parámetro +* [[yii\db\Command::bindValues()|bindValues()]]: une múltiples parámetros en una sola llamada +* [[yii\db\Command::bindParam()|bindParam()]]: similar a [[yii\db\Command::bindValue()|bindValue()]] pero también + soporta las referencias de parámetros vinculados. + +El siguiente ejemplo muestra formas alternativas de vincular parámetros: + +```php +$params = [':id' => $_GET['id'], ':status' => 1]; + +$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status') + ->bindValues($params) + ->queryOne(); + +$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params) + ->queryOne(); +``` + +La vinculación parámetros es implementada mediante [sentencias preparadas (prepared statements)](https://www.php.net/manual/es/mysqli.quickstart.prepared-statements.php). +Además de prevenir ataques de inyección de SQL, también puede mejorar el rendimiento preparando una sola vez una sentencia SQL y ejecutándola múltiples veces con diferentes +parámetros. Por ejemplo, + +```php +$command = $db->createCommand('SELECT * FROM post WHERE id=:id'); + +$post1 = $command->bindValue(':id', 1)->queryOne(); +$post2 = $command->bindValue(':id', 2)->queryOne(); +``` + +Porque [[yii\db\Command::bindParam()|bindParam()]] soporta parámetros vinculados por referencias, el código de arriba también +puede ser escrito como lo siguiente: + +```php +$command = $db->createCommand('SELECT * FROM post WHERE id=:id') + ->bindParam(':id', $id); + +$id = 1; +$post1 = $command->queryOne(); + +$id = 2; +$post2 = $command->queryOne(); +``` + +Observe que vincula el placeholder a la variable `$id` antes de la ejecución, y entonces cambia el valor de esa variable +antes de cada subsiguiente ejecución (esto se hace a menudo con bucles). Ejecutando consultas de esta manera puede ser +bastante más eficiente que ejecutar una nueva consulta para cada valor diferente del parámetro. + + +### Ejecutando Consultas Non-SELECT + +El método `queryXyz()` introducidos en las secciones previas todos tratan con consultas SELECT los cuales recogen los +datos de la base de datos. Para las consultas que no devuelven datos, deberías llamar a el método [[yii\db\Command::execute()]] +en su lugar. Por ejemplo, + +```php +$db->createCommand('UPDATE post SET status=1 WHERE id=1') + ->execute(); +``` + +El método [[yii\db\Command::execute()]] retorna el número de filas afectadas por la ejecución SQL. + +Para consultas INSERT, UPDATE y DELETE, en vez de escribir SQLs planos, puedes llamar a [[yii\db\Command::insert()|insert()]], +[[yii\db\Command::update()|update()]], [[yii\db\Command::delete()|delete()]], respectivamente, construyen los correspondientes +SQLs. Estos métodos entrecomillan adecuadamente las tablas y los nombres de columnas y los valores de los parámetros vinculados. +Por ejemplo, + +```php +// INSERT (table name, column values) +$db->createCommand()->insert('user', [ + 'name' => 'Sam', + 'age' => 30, +])->execute(); + +// UPDATE (table name, column values, condition) +$db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); + +// DELETE (table name, condition) +$db->createCommand()->delete('user', 'status = 0')->execute(); +``` + +Puedes también llamar a [[yii\db\Command::batchInsert()|batchInsert()]] para insertar múltiples filas de una sola vez, +que es mucho más eficiente que insertar una fila de cada vez: + +```php +// table name, column names, column values +$db->createCommand()->batchInsert('user', ['name', 'age'], [ + ['Tom', 30], + ['Jane', 20], + ['Linda', 25], +])->execute(); +``` + + +## Entrecomillado de Tablas y Nombres de Columna + +Al escribir código de database-agnostic, entrecomillar correctamente los nombres de las tablas y las columnas es a menudo +un dolor de cabeza porque las diferentes bases de datos tienen diferentes reglas para entrecomillar los nombres. Para +solventar este problema, puedes usar la siguiente sintaxis de entrecomillado introducido por Yii: + +* `[[column name]]`: encierra con dobles corchetes el nombre de una columna que debe ser entrecomillado; +* `{{table name}}`: encierra con dobles llaves el nombre de una tabla que debe ser entrecomillado. + +Yii DAO automáticamente convertirá tales construcciones en un SQL con los correspondientes entrecomillados de los nombres de las columnas o tablas. +Por ejemplo, + +```php +// ejecuta esta SQL para MySQL: SELECT COUNT(`id`) FROM `employee` +$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") + ->queryScalar(); +``` + + +### Usando Prefijos de Tabla + +Si la mayoría de tus tablas de BD utilizan algún prefijo común en sus tablas, puedes usar la función de prefijo de tabla soportado +por Yii DAO. + +Primero, especifica el prefijo de tabla mediante la propiedad [[yii\db\Connection::tablePrefix]]: + +```php +return [ + // ... + 'components' => [ + // ... + 'db' => [ + // ... + 'tablePrefix' => 'tbl_', + ], + ], +]; +``` + +Luego en tu código, siempre que lo necesites para hacer referencia a una tabla cuyo nombre tiene un prefijo, utiliza la sintaxis +`{{%table name}}`. El carácter porcentaje se sustituye con el prefijo de la tabla que has especificado en la configuración de +la conexión DB. Por ejemplo, + +```php +// ejecuta esta SQL para MySQL: SELECT COUNT(`id`) FROM `tbl_employee` +$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") + ->queryScalar(); +``` + + +## Realización de Transacciones + +Cuando se ejecutan múltiples consultas relacionadas en una secuencia, puede que se tengan que envolver en una +transacción para asegurar la integridad de los datos y la consistencia de tu base de datos. Si cualquiera de las consultas +falla, la base de datos debe ser revertida al estado anterior como si ninguna de estas consultas se haya ejecutado. + +El siguiente código muestra una manera típica de usar transacciones: + +```php +$db->transaction(function($db) { + $db->createCommand($sql1)->execute(); + $db->createCommand($sql2)->execute(); + // ... ejecutando otras sentencias SQL +}); +``` + +El código de arriba es equivalente a lo siguiente: + +```php +$transaction = $db->beginTransaction(); + +try { + $db->createCommand($sql1)->execute(); + $db->createCommand($sql2)->execute(); + // ... ejecutando otras sentencias SQL + + $transaction->commit(); + +} catch(\Exception $e) { + + $transaction->rollBack(); + + throw $e; +} +``` + +Al llamar al método [[yii\db\Connection::beginTransaction()|beginTransaction()]], se inicia una nueva transacción. +La transacción se representa como un objeto [[yii\db\Transaction]] almacenado en la variable `$transaction`. Luego, +las consultas que se ejecutan están encerrados en un bloque `try...catch...`. Si todas las consultas son ejecutadas satisfactoriamente, +el método [[yii\db\Transaction::commit()|commit()]] es llamado para confirmar la transacción. De lo contrario, una excepción +se disparará y se capturará, y el método [[yii\db\Transaction::rollBack()|rollBack()]] es llamado para revertir +los cambios hechos por las consultas antes de que fallara la consulta en la transacción. + + +### Especificando los Niveles de Aislamiento + +Yii también soporta la configuración de [niveles de aislamiento] para tus transacciones. Por defecto, cuando comienza una nueva transacción, +utilizará el nivel de aislamiento definido por tu sistema de base de datos. Se puede sobrescribir el nivel de aislamiento por defecto de la +siguiente manera, + +```php +$isolationLevel = \yii\db\Transaction::REPEATABLE_READ; + +$db->transaction(function ($db) { + .... +}, $isolationLevel); + +// or alternatively + +$transaction = $db->beginTransaction($isolationLevel); +``` + +Yii proporciona cuatro constantes para los niveles de aislamiento más comunes: + +- [[\yii\db\Transaction::READ_UNCOMMITTED]] - el nivel más bajo, pueden ocurrir lecturas Dirty, lecturas + Non-repeatable y Phantoms. +- [[\yii\db\Transaction::READ_COMMITTED]] - evita lecturas Dirty. +- [[\yii\db\Transaction::REPEATABLE_READ]] - evita lecturas Dirty y lecturas Non-repeatable. +- [[\yii\db\Transaction::SERIALIZABLE]] - el nivel más fuerte, evita todos los problemas nombrados anteriormente. + +Además de usar las constantes de arriba para especificar los niveles de aislamiento, puedes también usar cadenas con +una sintaxis valida soportada por el DBMS que estés usando. Por ejemplo, en PostgreSQL, puedes utilizar `SERIALIZABLE READ ONLY DEFERRABLE`. + +Tenga en cuenta que algunos DBMS permiten configuraciones de niveles de aislamiento solo a nivel de conexión. Las transacciones subsiguientes +recibirá el mismo nivel de aislamiento , incluso si no se especifica ninguna. Al utilizar esta característica +es posible que necesites ajustar el nivel de aislamiento para todas las transacciones de forma explícitamente para evitar conflictos +en las configuraciones. +En el momento de escribir esto, solo MSSQL y SQLite serán afectadas. + +> Note: SQLite solo soporta dos niveles de aislamiento, por lo que solo se puede usar `READ UNCOMMITTED` y +`SERIALIZABLE`. El uso de otros niveles causará el lanzamiento de una excepción. + +> Note: PostgreSQL no permite configurar el nivel de aislamiento antes que la transacción empiece por lo que no se + puede especificar el nivel de aislamiento directamente cuando empieza la transacción. Se tiene que llamar a + [[yii\db\Transaction::setIsolationLevel()]] después de que la transacción haya empezado. + +[isolation levels]: https://es.wikipedia.org/wiki/Aislamiento_(ACID)#Niveles_de_aislamiento + + +### Transacciones Anidadas + +Si tu DBMS soporta Savepoint, puedes anidar múltiples transacciones como a continuación: + +```php +$db->transaction(function ($db) { + // outer transaction + + $db->transaction(function ($db) { + // inner transaction + }); +}); +``` + +O alternativamente, + +```php +$outerTransaction = $db->beginTransaction(); +try { + $db->createCommand($sql1)->execute(); + + $innerTransaction = $db->beginTransaction(); + try { + $db->createCommand($sql2)->execute(); + $innerTransaction->commit(); + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + + $outerTransaction->commit(); +} catch (Exception $e) { + $outerTransaction->rollBack(); +} +``` + + +## Replicación y División Lectura-Escritura + +Muchos DBMS soportan [replicación de bases de datos](https://es.wikipedia.org/wiki/Replicaci%C3%B3n_(inform%C3%A1tica)) para tener +una mejor disponibilidad de la base de datos y un mejor tiempo de respuesta del servidor. Con la replicación de bases +de datos, los datos están replicados en los llamados *servidores maestros* (master servers) y *servidores esclavos* +(slave servers). Todas las escrituras y actualizaciones deben hacerse en el servidor maestro, mientras que las lecturas +se efectuarán en los servidores esclavos. + +Para aprovechar las ventajas de la replicación de la base de datos y lograr una división de lecuta-escritura, se puede configurar +el componente [[yii\db\Connection]] como se muestra a continuación: + +```php +[ + 'class' => 'yii\db\Connection', + + // configuración para el maestro + 'dsn' => 'dsn for master server', + 'username' => 'master', + 'password' => '', + + // configuración para los esclavos + 'slaveConfig' => [ + 'username' => 'slave', + 'password' => '', + 'attributes' => [ + // utiliza un tiempo de espera de conexión más pequeña + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // listado de configuraciones de esclavos + 'slaves' => [ + ['dsn' => 'dsn for slave server 1'], + ['dsn' => 'dsn for slave server 2'], + ['dsn' => 'dsn for slave server 3'], + ['dsn' => 'dsn for slave server 4'], + ], +] +``` + +La configuración anterior especifica una configuración con un único maestro y múltiples esclavos. Uno de los esclavos +se conectará y se usará para ejecutar consultas de lectura, mientras que el maestro se usará para realizar consultas de +escritura. De este modo la división de lectura-escritura se logra automáticamente con esta configuración, Por ejemplo, + +```php +// crea una instancia de Connection usando la configuración anterior +$db = Yii::createObject($config); + +// consulta contra uno de los esclavos +$rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); + +// consulta contra el maestro +$db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); +``` + +> Info: Las consultas realizadas llamando a [[yii\db\Command::execute()]] se consideran consultas de escritura, + mientras que todas las demás se ejecutan mediante alguno de los métodos "query" de [[yii\db\Command]] son consultas + de lectura. Se puede obtener la conexión de esclavo activa mediante `$db->slave`. + +El componente `Connection` soporta el balanceo de carga y la conmutación de errores entre esclavos. Cuando se realiza +una consulta de lectura por primera vez, el componente `Connection` elegirá un esclavo aleatorio e intentará realizar +una conexión a este. Si el esclavo se encuentra "muerto", se intentará con otro. Si no está disponible ningún esclavo, se conectará al maestro. Configurando una [[yii\db\Connection::serverStatusCache|server status cache]], se recordarán los servidores +"muertos" por lo que no se intentará volver a conectar a ellos durante +[[yii\db\Connection::serverRetryInterval|certain period of time]]. + +> Info: En la configuración anterior, se especifica un tiempo de espera (timeout) de conexión de 10 segundos + para cada esclavo. Esto significa que si no se puede conectar a un esclavo en 10 segundos, este será considerado + como "muerto". Se puede ajustar el parámetro basado en el entorno actual. + +También se pueden configurar múltiples maestros con múltiples esclavos. Por ejemplo, + +```php +[ + 'class' => 'yii\db\Connection', + + // configuracion habitual para los maestros + 'masterConfig' => [ + 'username' => 'master', + 'password' => '', + 'attributes' => [ + // utilizar un tiempo de espera de conexión más pequeña + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // listado de configuraciones de maestros + 'masters' => [ + ['dsn' => 'dsn for master server 1'], + ['dsn' => 'dsn for master server 2'], + ], + + // configuración habitual para esclavos + 'slaveConfig' => [ + 'username' => 'slave', + 'password' => '', + 'attributes' => [ + // utilizar un tiempo de espera de conexión más pequeña + PDO::ATTR_TIMEOUT => 10, + ], + ], + + // listado de configuración de esclavos + 'slaves' => [ + ['dsn' => 'dsn for slave server 1'], + ['dsn' => 'dsn for slave server 2'], + ['dsn' => 'dsn for slave server 3'], + ['dsn' => 'dsn for slave server 4'], + ], +] +``` + +La configuración anterior especifica dos maestros y cuatro esclavos. El componente `Connection` también da soporte al +balanceo de carga y la conmutación de errores entre maestros igual que hace con los esclavos. La diferencia es que +cuando no se encuentra ningún maestro disponible se lanza una excepción. + +> Note: cuando se usa la propiedad [[yii\db\Connection::masters|masters]] para configurar uno o múltiples maestros, se + ignorarán todas las otras propiedades que especifiquen una conexión de base de datos + (ej. `dsn`, `username`, `password`), junto con el mismo objeto `Connection`. + +Por defecto. las transacciones usan la conexión del maestro. Y dentro de una transacción, todas las operaciones de DB usarán +la conexión del maestro. Por ejemplo, + +```php +// la transacción empieza con la conexión al maestro +$transaction = $db->beginTransaction(); + +try { + // las dos consultas se ejecutan contra el maestro + $rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); + $db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute(); + + $transaction->commit(); +} catch(\Exception $e) { + $transaction->rollBack(); + throw $e; +} +``` + +Si se quiere empezar la transacción con una conexión a un esclavo, se debe hacer explícitamente como se muestra a +continuación: + +```php +$transaction = $db->slave->beginTransaction(); +``` + +A veces, se puede querer forzar el uso de una conexión maestra para realizar una consulta de lectura. Se puede lograr +usando el método `useMaster()`: + +```php +$rows = $db->useMaster(function ($db) { + return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); +}); +``` + +También se puede utilizar directamente estableciendo `$db->enableSlaves` a `false` para que se redirijan todas las +consultas a la conexión del maestro. + +## Trabajando con Esquemas de Bases de Datos + +Yii DAO proporciona todo un conjunto de métodos que permites manipular el esquema de tu base de datos, tal como +crear nuevas tablas, borrar una columna de una tabla, etc. Estos métodos son listados a continuación: + +* [[yii\db\Command::createTable()|createTable()]]: crea una tabla +* [[yii\db\Command::renameTable()|renameTable()]]: renombra una tabla +* [[yii\db\Command::dropTable()|dropTable()]]: remueve una tabla +* [[yii\db\Command::truncateTable()|truncateTable()]]: remueve todas las filas de una tabla +* [[yii\db\Command::addColumn()|addColumn()]]: añade una columna +* [[yii\db\Command::renameColumn()|renameColumn()]]: renombra una columna +* [[yii\db\Command::dropColumn()|dropColumn()]]: remueve una columna +* [[yii\db\Command::alterColumn()|alterColumn()]]: altera una columna +* [[yii\db\Command::addPrimaryKey()|addPrimaryKey()]]: añade una clave primaria +* [[yii\db\Command::dropPrimaryKey()|dropPrimaryKey()]]: remueve una clave primaria +* [[yii\db\Command::addForeignKey()|addForeignKey()]]: añade una clave ajena +* [[yii\db\Command::dropForeignKey()|dropForeignKey()]]: remueve una clave ajena +* [[yii\db\Command::createIndex()|createIndex()]]: crea un indice +* [[yii\db\Command::dropIndex()|dropIndex()]]: remueve un indice + +Estos métodos puedes ser usados como se muestra a continuación: + +```php +// CREATE TABLE +$db->createCommand()->createTable('post', [ + 'id' => 'pk', + 'title' => 'string', + 'text' => 'text', +]); +``` + +También puedes recuperar la información de definición de una tabla a través +del método [[yii\db\Connection::getTableSchema()|getTableSchema()]] de una conexión DB. Por ejemplo, + +```php +$table = $db->getTableSchema('post'); +``` + +El método retorna un objeto [[yii\db\TableSchema]] que contiene la información sobre las columnas de las tablas, +claves primarias, claves ajenas, etc. Toda esta información principalmente es utilizada por el +[constructor de consultas](db-query-builder.md) y [active record](db-active-record.md) para ayudar a +escribir código database-agnostic. diff --git a/docs/guide-es/db-migrations.md b/docs/guide-es/db-migrations.md new file mode 100644 index 00000000000..bb797bcccfb --- /dev/null +++ b/docs/guide-es/db-migrations.md @@ -0,0 +1,939 @@ +Migración de Base de Datos +========================== + +Durante el curso de desarrollo y mantenimiento de una aplicación con base de datos, la estructura de dicha base de datos +evoluciona tanto como el código fuente. Por ejemplo, durante el desarrollo de una aplicación, +una nueva tabla podría ser necesaria; una vez que la aplicación se encuentra en producción, podría descrubrirse +que debería crearse un índice para mejorar el tiempo de ejecución de una consulta; y así sucesivamente. Debido a los cambios en la estructura de la base de datos +a menudo se requieren cambios en el código, Yii soporta la característica llamada *migración de base de datos*, la cual permite +tener un seguimiento de esos cambios en término de *migración de base de datos*, cuyo versionado es controlado +junto al del código fuente. + +Los siguientes pasos muestran cómo una migración puede ser utilizada por un equipo durante el desarrollo: + +1. Tim crea una nueva migración (por ej. crea una nueva table, cambia la definición de una columna, etc.). +2. Tim hace un commit con la nueva migración al sistema de control de versiones (por ej. Git, Mercurial). +3. Doug actualiza su repositorio desde el sistema de control de versiones y recibe la nueva migración. +4. Doug aplica dicha migración a su base de datos local de desarrollo, de ese modo sincronizando su base de datos + y reflejando los cambios que hizo Tim. + +Los siguientes pasos muestran cómo hacer una puesta en producción con una migración de base de datos: + +1. Scott crea un tag de lanzamiento en el repositorio del proyecto que contiene algunas migraciones de base de datos. +2. Scott actualiza el código fuente en el servidor de producción con el tag de lanzamiento. +3. Scott aplica cualquier migración de base de datos acumulada a la base de datos de producción. + +Yii provee un grupo de herramientas de línea de comandos que te permite: + +* crear nuevas migraciones; +* aplicar migraciones; +* revertir migraciones; +* re-aplicar migraciones; +* mostrar el historial y estado de migraciones. + +Todas esas herramientas son accesibles a través del comando `yii migrate`. En esta sección describiremos en detalle +cómo lograr varias tareas utilizando dichas herramientas. Puedes a su vez ver el uso de cada herramienta a través del comando +de ayuda `yii help migrate`. + +> Tip: las migraciones pueden no sólo afectar un esquema de base de datos sino también ajustar datos existentes para que encajen en el nuevo esquema, crear herencia RBAC + o también limpiar el cache. + + +## Creando Migraciones + +Para crear una nueva migración, ejecuta el siguiente comando: + +``` +yii migrate/create +``` + +El argumento requerido `name` da una pequeña descripción de la nueva migración. Por ejemplo, si +la migración se trata acerca de crear una nueva tabla llamada *news*, podrías utilizar el nombre `create_news_table` +y ejecutar el siguiente comando: + +``` +yii migrate/create create_news_table +``` + +> Note: Debido a que el argumento `name` será utilizado como parte del nombre de clase de la migración generada, + sólo debería contener letras, dígitos, y/o guines bajos. + +El comando anterior un nuevo archivo de clase PHP llamado `m150101_185401_create_news_table.php` +en el directorio `@app/migrations`. El archivo contendrá el siguiente código, que principalmente declara +una clase de tipo migración `m150101_185401_create_news_table` con el siguiente esqueleto de código: + +```php +_`, donde + +* `` se refiere a la marca de tiempo UTC en la cual el comando de migración fue ejecutado. +* `` es el mismo valor del argumento `name` provisto al ejecutar el comando. + +En la clase de la migración, se espera que tu escribas código en el método `up()`, que realiza los cambios en la base de datos. +Podrías también querer introducir código en el método `down()`, que debería revertir los cambios realizados por `up()`. El método `up()` es llamado +cuando actualizas la base de datos con esta migración, mientras que el método `down()` es llamado cuando reviertes dicha migración. +El siguiente código muestra cómo podrías implementar la clase de migración para crear la tabla `news`: + +```php +createTable('news', [ + 'id' => Schema::TYPE_PK, + 'title' => Schema::TYPE_STRING . ' NOT NULL', + 'content' => Schema::TYPE_TEXT, + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +> Info: No todas las migraciones son reversibles. Por ejemplo, si el método `up()` elimina un registro en una tabla, podrías + no ser capáz de recuperarla en el método `down()`. A veces, podrías ser simplemente demasiado perezoso para implementar + el método `down()`, debido a que no es muy común revertir migraciones de base de datos. En este caso, deberías devolver + `false` en el método `down()` para indicar que dicha migración no es reversible. + +La clase de migración de base de datos [[yii\db\Migration]] expone una conexión a la base de datos mediante la propiedad [[yii\db\Migration::db|db]]. +Puedes utilizar esto para manipular el esquema de la base de datos utilizando métodos como se describen en +[Trabajando con Esquemas de Base de Datos](db-dao.md#database-schema). + +En vez de utilizar tipos físicos, al crear tablas o columnas deberías utilizar los *tipos abstractos* +así las migraciones son independientes de algún DBMS específico. La clase [[yii\db\Schema]] define +un grupo de constantes que representan los tipos abstractos soportados. Dichas constantes son llamadas utilizando el formato +de `TYPE_`. Por ejemplo, `TYPE_PK` se refiere al tipo clave primaria auto-incremental; `TYPE_STRING` +se refiere al tipo string. Cuando se aplica una migración a una base de datos en particular, los tipos abstractos +serán traducidos a los tipos físicos correspondientes. En el caso de MySQL, `TYPE_PK` será transformado +en `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, mientras `TYPE_STRING` se vuelve `varchar(255)`. + +Puedes agregar restricciones adicionales al utilizar tipos abstractos. En el ejemplo anterior, ` NOT NULL` es agregado +a `Schema::TYPE_STRING` para especificar que la columna no puede ser `null`. + +> Info: El mapeo entre tipos abstractos y tipos físicos es especificado en + la propiedad [[yii\db\QueryBuilder::$typeMap|$typeMap]] en cada clase concreta `QueryBuilder`. + +Desde la versión 2.0.6, puedes hacer uso del recientemente introducido generador de esquemas, el cual provee una forma más conveniente de definir las columnas. +De esta manera, la migración anterior podría ser escrita así: + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +Existe una lista de todos los métodos disponibles para la definición de tipos de columna en la API de la documentación de [[yii\db\SchemaBuilderTrait]]. + + +## Generar Migraciones + +Desde la versión 2.0.7 la consola provee una manera muy conveniente de generar migraciones. + +Si el nombre de la migración tiene una forma especial, por ejemplo `create_xxx_table` o `drop_xxx_table` entonces el archivo de la migración generada +contendrá código extra, en este caso para crear/eliminar tablas. +A continuación se describen todas estas variantes. + +### Crear Tabla + +```php +yii migrate/create create_post_table +``` + +esto genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post_table extends Migration +{ + /** + * {@inheritdoc} + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() + ]); + } + + /** + * {@inheritdoc} + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +Para crear las columnas en ese momento, las puedes especificar vía la opción `--fields`. + +```php +yii migrate/create create_post_table --fields="title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post_table extends Migration +{ + /** + * {@inheritdoc} + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + } + + /** + * {@inheritdoc} + */ + public function down() + { + $this->dropTable('post'); + } +} + +``` + +Puedes especificar más parámetros para las columnas. + +```php +yii migrate/create create_post_table --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post_table extends Migration +{ + /** + * {@inheritdoc} + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } + + /** + * {@inheritdoc} + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +> Note: la clave primaria es automáticamente agragada y llamada `id` por defecto. Si quieres utilizar otro nombre puedes +> especificarlo así `--fields="name:primaryKey"`. + +#### Claves Foráneas + +Desde 2.0.8 el generador soporta claves foráneas utilizando la palabra clave `foreignKey`. + +```php +yii migrate/create create_post_table --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + * Has foreign keys to the tables: + * + * - `user` + * - `category` + */ +class m160328_040430_create_post_table extends Migration +{ + /** + * {@inheritdoc} + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'author_id' => $this->integer()->notNull(), + 'category_id' => $this->integer()->defaultValue(1), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + + // creates index for column `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); + + // add foreign key for table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); + + // add foreign key for table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * {@inheritdoc} + */ + public function down() + { + // drops foreign key for table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // drops foreign key for table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // drops index for column `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} +``` + +La posición de la palabra clave `foreignKey` en la descripción de la columna +no cambia el código generado. Esto significa: + +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +Todas generan el mismo código. + +La palabra clave `foreignKey` puede tomar un parámetro entre paréntesis el cual +será el nombre de la tabla relacionada por la clave foránea generada. Si no se pasa ningún parámetro +el nombre de la tabla será deducido en base al nombre de la columna. + +En el ejemplo anterior `author_id:integer:notNull:foreignKey(user)` generará +una columna llamada `author_id` con una clave foránea a la tabla `user` mientras +`category_id:integer:defaultValue(1):foreignKey` generará +`category_id` con una clave foránea a la tabla `category`. + +### Eliminar Tabla + +```php +yii migrate/create drop_post_table --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +class m150811_220037_drop_post_table extends Migration +{ + public function up() + { + $this->dropTable('post'); + } + + public function down() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } +} +``` + +### Agregar Columna + +Si el nombre de la migración está en la forma `add_xxx_column_to_yyy_table` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +Para agregar una columna: + +```php +yii migrate/create add_position_column_to_post_table --fields="position:integer" +``` + +genera + +```php +class m150811_220037_add_position_column_to_post_table extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### Eliminar Columna + +Si el nombre de la migración está en la forma `drop_xxx_column_from_yyy_table` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +```php +yii migrate/create drop_position_column_from_post_table --fields="position:integer" +``` + +genera + +```php +class m150811_220037_drop_position_column_from_post_table extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +### Agregar Tabla de Unión + +Si el nombre de la migración está en la forma `create_junction_table_for_xxx_and_yyy_tables` entonces se generará el código necesario +para una tabla de unión. + +```php +yii migrate/create create_junction_table_for_post_and_tag_tables --fields="created_at:dateTime" +``` + +genera + +```php +/** + * Handles the creation for table `post_tag`. + * Has foreign keys to the tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migration +{ + /** + * {@inheritdoc} + */ + public function up() + { + $this->createTable('post_tag', [ + 'post_id' => $this->integer(), + 'tag_id' => $this->integer(), + 'created_at' => $this->dateTime(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * {@inheritdoc} + */ + public function down() + { + // drops foreign key for table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +``` + +### Migraciones Transaccionales + +Al ejecutar migraciones complejas de BD, es importante asegurarse que todas las migraciones funcionen o fallen como una unidad +así la base de datos puede mantener integridad y consistencia. Para alcanzar este objetivo, se recomienda que +encierres las operación de la BD de cada migración en una [transacción](db-dao.md#performing-transactions). + +Una manera simple de implementar migraciones transaccionales es poniendo el código de las migraciones en los métodos `safeUp()` y `safeDown()`. +Estos métodos se diferencias con `up()` y `down()` en que son encerrados implícitamente en una transacción. +Como resultado, si alguna de las operaciones dentro de estos métodos falla, todas las operaciones previas son automáticamente revertidas. + +En el siguiente ejemplo, además de crear la tabla `news` también insertamos un registro inicial dentro de la dicha tabla. + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + + $this->insert('news', [ + 'title' => 'test 1', + 'content' => 'content 1', + ]); + } + + public function safeDown() + { + $this->delete('news', ['id' => 1]); + $this->dropTable('news'); + } +} +``` + +Ten en cuenta que usualmente cuando ejecutas múltiples operaciones en la BD en `safeUp()`, deberías revertir su orden de ejecución +en `safeDown()`. En el ejemplo anterior primero creamos la tabla y luego insertamos la finla en `safeUp()`; mientras +que en `safeDown()` primero eliminamos el registro y posteriormente eliminamos la tabla. + +> Note: No todos los DBMS soportan transacciones. Y algunas consultas a la BD no pueden ser puestas en transacciones. Para algunos ejemplos, + por favor lee acerca de [commits implícitos](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). En estos casos, + deberías igualmente implementar `up()` y `down()`. + + +### Métodos de Acceso a la Base de Datos + +La clase base [[yii\db\Migration]] provee un grupo de métodos que te permiten acceder y manipular bases de datos. +Podrías encontrar que estos métodos son nombrados de forma similar a los [métodos DAO](db-dao.md) provistos por la clase [[yii\db\Command]]. +Por ejemplo, el método [[yii\db\Migration::createTable()]] te permite crear una nueva tabla, +tal como lo hace [[yii\db\Command::createTable()]]. + +El beneficio de utilizar lo métodos provistos por [[yii\db\Migration]] es que no necesitas explícitamente +crear instancias de [[yii\db\Command]], y la ejecución de cada método mostrará automáticamente mensajes útiles +diciéndote qué operaciones de la base de datos se realizaron y cuánto tiempo tomaron. + +Debajo hay una lista de todos los métodos de acceso a la base de datos: + +* [[yii\db\Migration::execute()|execute()]]: ejecuta una declaración SQL +* [[yii\db\Migration::insert()|insert()]]: inserta un único registro +* [[yii\db\Migration::batchInsert()|batchInsert()]]: inserta múltiples registros +* [[yii\db\Migration::update()|update()]]: actualiza registros +* [[yii\db\Migration::delete()|delete()]]: elimina registros +* [[yii\db\Migration::createTable()|createTable()]]: crea una nueva tabla +* [[yii\db\Migration::renameTable()|renameTable()]]: renombra una tabla +* [[yii\db\Migration::dropTable()|dropTable()]]: elimina una tabla +* [[yii\db\Migration::truncateTable()|truncateTable()]]: elimina todos los registros de una tabla +* [[yii\db\Migration::addColumn()|addColumn()]]: agrega una columna +* [[yii\db\Migration::renameColumn()|renameColumn()]]: renombra una columna +* [[yii\db\Migration::dropColumn()|dropColumn()]]: elimina una columna +* [[yii\db\Migration::alterColumn()|alterColumn()]]: modifica una columna +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: agrega una clave primaria +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: elimina una clave primaria +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: agrega una clave foránea +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: elimina una clave foránea +* [[yii\db\Migration::createIndex()|createIndex()]]: crea un índice +* [[yii\db\Migration::dropIndex()|dropIndex()]]: elimina un índice +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: agrega un comentario a una columna +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: elimina un comentario de una columna +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: agrega un comentario a una tabla +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: elimina un comentario de una tabla + +> Info: [[yii\db\Migration]] no provee un método de consulta a la base de datos. Esto es porque normalmente no necesitas + mostrar mensajes detallados al traer datos de una base de datos. También se debe a que puedes utilizar el poderoso + [Query Builder](db-query-builder.md) para generar y ejecutar consultas complejas. + +> Note: Al manipular datos utilizando una migración podrías encontrar que utilizando tus clases [Active Record](db-active-record.md) +> para esto podría ser útil ya que algo de la lógica ya está implementada ahí. Ten en cuenta de todos modos, que en contraste con +> el código escrito en las migraciones, cuya naturaleza es permanecer constante por siempre, la lógica de la aplicación está sujeta a cambios. +> Entonces al utilizar Active Record en migraciones, los cambios en la lógica en la capa Active Record podrían accidentalmente romper +> migraciones existentes. Por esta razón, el código de las migraciones debería permanecer independiente de determinada lógica de la aplicación +> tal como clases Active Record. + + +## Aplicar Migraciones + +To upgrade a database to its latest structure, you should apply all available new migrations using the following command: +Para actualizar una base de datos a su última estructura, deberías aplicar todas las nuevas migraciones utilizando el siguiente comando: + +``` +yii migrate +``` + +Este comando listará todas las migraciones que no han sido aplicadas hasta el momento. Si confirmas que quieres aplicar +dichas migraciones, se correrá el método `up()` o `safeUp()` en cada clase de migración nueva, una tras otra, +en el orden de su valor de marca temporal. Si alguna de las migraciones falla, el comando terminará su ejecución sin aplicar +el resto de las migraciones. + +> Tip: En caso de no disponer de la línea de comandos en el servidor, podrías intentar utilizar +> la extensión [web shell](https://github.com/samdark/yii2-webshell). + +Por cada migración aplicada correctamente, el comando insertará un registro en la base de datos, en la tabla llamada +`migration` para registrar la correcta aplicación de la migración. Esto permitirá a la herramienta de migración identificar +cuáles migraciones han sido aplicadas y cuáles no. + +> Info: La herramienta de migración creará automáticamente la tabla `migration` en la base de datos especificada + en la opción [[yii\console\controllers\MigrateController::db|db]] del comando. Por defecto, la base de datos + es especificada en el [componente de aplicación](structure-application-components.md) `db`. + +A veces, podrías sólo querer aplicar una o algunas pocas migraciones, en vez de todas las migraciones disponibles. +Puedes hacer esto el número de migraciones que quieres aplicar al ejecutar el comando. +Por ejemplo, el siguiente comando intentará aplicar las tres siguientes migraciones disponibles: + +``` +yii migrate 3 +``` + +Puedes además explícitamente especificar una migración en particular a la cual la base de datos debería migrar +utilizando el comando `migrate/to` de acuerdo a uno de los siguientes formatos: + +``` +yii migrate/to 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/to "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/to m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/to 1392853618 # utiliza el tiempo UNIX +``` + +Si hubiera migraciones previas a la especificada sin aplicar, estas serán aplicadas antes de que la migración especificada +sea aplicada. + +Si la migración especificada ha sido aplicada previamente, cualquier migración aplicada posteriormente será revertida. + + +## Revertir Migraciones + +Para revertir (deshacer) una o varias migraciones ya aplicadas, puedes ejecutar el siguiente comando: + +``` +yii migrate/down # revierte la más reciente migración aplicada +yii migrate/down 3 # revierte las 3 últimas migraciones aplicadas +``` + +> Note: No todas las migraciones son reversibles. Intentar revertir tales migraciones producirá un error y detendrá + completamente el proceso de reversión. + + +## Rehacer Migraciones + +Rehacer (re-ejecutar) migraciones significa primero revertir las migraciones especificadas y luego aplicarlas nuevamente. Esto puede hacerse +de esta manera: + +``` +yii migrate/redo # rehace la más reciente migración aplicada +yii migrate/redo 3 # rehace las 3 últimas migraciones aplicadas +``` + +> Note: Si una migración no es reversible, no tendrás posibilidades de rehacerla. + + +## Listar Migraciones + +Para listar cuáles migraciones han sido aplicadas y cuáles no, puedes utilizar los siguientes comandos: + +``` +yii migrate/history # muestra las últimas 10 migraciones aplicadas +yii migrate/history 5 # muestra las últimas 5 migraciones aplicadas +yii migrate/history all # muestra todas las migraciones aplicadas + +yii migrate/new # muestra las primeras 10 nuevas migraciones +yii migrate/new 5 # muestra las primeras 5 nuevas migraciones +yii migrate/new all # muestra todas las nuevas migraciones +``` + + +## Modificar el Historial de Migraciones + +En vez de aplicar o revertir migraciones, a veces simplemente quieres marcar que tu base de datos +ha sido actualizada a una migración en particular. Esto sucede normalmente cuando cambias manualmente la base de datos +a un estado particular y no quieres que la/s migración/es de ese cambio sean re-aplicadas posteriormente. Puedes alcanzar este objetivo +con el siguiente comando: + +``` +yii migrate/mark 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/mark "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/mark m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/mark 1392853618 # utiliza el tiempo UNIX +``` + +El comando modificará la tabla `migration` agregando o eliminado ciertos registros para indicar que en la base de datos +han sido aplicadas las migraciones hasta la especificada. Ninguna migración será aplicada ni revertida por este comando. + + +## Personalizar Migraciones + +Hay varias maneras de personalizar el comando de migración. + + +### Utilizar Opciones de la Línea de Comandos + +El comando de migración trae algunas opciones de línea de comandos que pueden ser utilizadas para personalizar su comportamiento: + +* `interactive`: boolean (por defecto `true`), especificar si se debe ejecutar la migración en modo interactivo. + Cuando se indica `true`, se le pedirá confirmación al usuario antes de ejecutar ciertas acciones. + Puedes querer definirlo como `false` si el comando está siendo utilizado como un proceso de fondo. + +* `migrationPath`: string (por defecto `@app/migrations`), especifica el directorio que contiene todos los archivos + de clase de las migraciones. Este puede ser especificado tanto como una ruta a un directorio un [alias](concept-aliases.md) de ruta. + Ten en cuenta que el directorio debe existir, o el comando disparará un error. + +* `migrationTable`: string (por defecto `migration`), especifica el nombre de la tabla de la base de datos que almacena + información del historial de migraciones. Dicha tabla será creada por el comando en caso de que no exista. + Puedes también crearla manualmente utilizando la estructura `version varchar(255) primary key, apply_time integer`. + +* `db`: string (por defecto `db`), especifica el ID del [componente de aplicación](structure-application-components.md) de la base de datos. + Esto representa la base de datos que será migrada en este comando. + +* `templateFile`: string (por defecto `@yii/views/migration.php`), especifica la ruta al template + utilizado para generar el esqueleto de los archivos de clases de migración. Puede ser especificado tanto como una ruta a un archivo + como una [alias](concept-aliases.md) de una ruta. El template es un archivo PHP en el cual puedes utilizar una variable predefinida + llamada `$className` para obtener el nombre de clase de la migración. + +* `generatorTemplateFiles`: array (por defecto `[ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createTableMigration.php' + ]`), especifica los templates utilizados para generar las migraciones. Ver "[Generar Migraciones](#generating-migrations)" + para más detalles. + +* `fields`: array de strings de definiciones de columna utilizado por el código de migración. Por defecto `[]`. El formato de cada + definición es `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Por ejemplo, `--fields=name:string(12):notNull` produce + una columna string de tamaño 12 que es not `null`. + +El siguiente ejemplo muestra cómo se pueden utilizar estas opciones. + +Por ejemplo, si queremos migrar un módulo `forum` cuyos arhivos de migración +están ubicados dentro del directorio `migrations` del módulo, podemos utilizar el siguientedocs/guide-es/db-migrations.md +comando: + +``` +# realiza las migraciones de un módulo forum sin interacción del usuario +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 +``` + + +### Configurar el Comando Globalmente + +En vez de introducir los valores de las opciones cada vez que ejecutas un comandod e migración, podrías configurarlos +de una vez por todas en la configuración de la aplicación como se muestra a continuación: + +```php +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], + ], +]; +``` + +Con esta configuración, cada vez que ejecutes un comando de migración, la tabla `backend_migration` +será utilizada para registrar el historial de migraciones. No necesitarás volver a especificarla con la opción `migrationTable` +de la línea de comandos. + + +## Migrar Múltiples Bases de Datos + +Por defecto, las migraciones son aplicadas en la misma base de datos especificada en el [componente de aplicación](structure-application-components.md) `db`. +Si quieres que sean aplicadas en una base de datos diferente, puedes especificar la opción `db` como se muestra a continuación, + +``` +yii migrate --db=db2 +``` + +El comando anterior aplicará las migraciones en la base de datos `db2`. + +A veces puede suceder que quieras aplicar *algunas* de las migraciones a una base de datos, mientras algunas otras +a una base de datos distinta. Para lograr esto, al implementar una clase de migración debes especificar explícitamente el ID del componente DB +que la migración debe utilizar, como a continuación: + +```php +db = 'db2'; + parent::init(); + } +} +``` + +La migración anterior se aplicará a `db2`, incluso si especificas una base de datos diferente en la opción `db` de la +línea de comandos. Ten en cuenta que el historial aún será registrado in la base de datos especificada en la opción `db` de la línea de comandos. + +Si tienes múltiples migraciones que utilizan la misma base de datos, es recomandable que crees una clase base de migración +con el código `init()` mostrado. Entonces cada clase de migración puede extender de esa clase base. + +> Tip: Aparte de definir la propiedad [[yii\db\Migration::db|db]], puedes también operar en diferentes bases de datos + creando nuevas conexiones de base de datos en tus clases de migración. También puedes utilizar [métodos DAO](db-dao.md) + con esas conexiones para manipular diferentes bases de datos. + +Another strategy that you can take to migrate multiple databases is to keep migrations for different databases in +different migration paths. Then you can migrate these databases in separate commands like the following: +Otra estrategia que puedes seguir para migrar múltiples bases de datos es mantener las migraciones para diferentes bases de datos en +distintas rutas de migración. Entonces podrías migrar esas bases de datos en comandos separados como a continuación: + +``` +yii migrate --migrationPath=@app/migrations/db1 --db=db1 +yii migrate --migrationPath=@app/migrations/db2 --db=db2 +... +``` + +El primer comando aplicará las migraciones que se encuentran en `@app/migrations/db1` en la base de datos `db1`, el segundo comando +aplicará las migraciones que se encuentran en `@app/migrations/db2` en `db2`, y así sucesivamente. diff --git a/docs/guide-es/db-query-builder.md b/docs/guide-es/db-query-builder.md new file mode 100644 index 00000000000..0ca6212a7ae --- /dev/null +++ b/docs/guide-es/db-query-builder.md @@ -0,0 +1,470 @@ +Constructor de Consultas +======================== + +> Note: Esta sección está en desarrollo. + +Yii proporciona una capa de acceso básico a bases de datos como se describe en la sección +[Objetos de Acceso a Bases de Datos](db-dao.md). La capa de acceso a bases de datos proporciona un método de bajo +nivel (low-level) para interaccionar con la base de datos. Aunque a veces puede ser útil la escritura de sentencias +SQLs puras, en otras situaciones puede ser pesado y propenso a errores. Otra manera de tratar con bases de datos puede +ser el uso de Constructores de Consultas (Query Builder). El Constructor de Consultas proporciona un medio orientado a +objetos para generar las consultas que se ejecutarán. + +Un uso típico de Constructor de Consultas puede ser el siguiente: + +```php +$rows = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10) + ->all(); + +// que es equivalente al siguiente código: + +$query = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10); + +// Crear un comando. Se puede obtener la consulta SQL actual utilizando $command->sql +$command = $query->createCommand(); + +// Ejecutar el comando: +$rows = $command->queryAll(); +``` + +Métodos de Consulta +------------------- + +Como se puede observar, primero se debe tratar con [[yii\db\Query]]. En realidad, `Query` sólo se encarga de +representar diversa información de la consulta. La lógica para generar la consulta se efectúa mediante +[[yii\db\QueryBuilder]] cuando se llama al método `createCommand()`, y la ejecución de la consulta la efectúa +[[yii\db\Command]]. + +Se ha establecido, por convenio, que [[yii\db\Query]] proporcione un conjunto de métodos de consulta comunes que +construirán la consulta, la ejecutarán, y devolverán el resultado. Por ejemplo: + +- [[yii\db\Query::all()|all()]]: construye la consulta, la ejecuta y devuelve todos los resultados en formato de array. +- [[yii\db\Query::one()|one()]]: devuelve la primera fila del resultado. +- [[yii\db\Query::column()|column()]]: devuelve la primera columna del resultado. +- [[yii\db\Query::scalar()|scalar()]]: devuelve la primera columna en la primera fila del resultado. +- [[yii\db\Query::exists()|exists()]]: devuelve un valor indicando si la el resultado devuelve algo. +- [[yii\db\Query::count()|count()]]: devuelve el resultado de la consulta `COUNT`. Otros métodos similares incluidos + son `sum($q)`, `average($q)`, `max($q)`, `min($q)`, que soportan las llamadas funciones de agregación. El parámetro + `$q` es obligatorio en estos métodos y puede ser el nombre de la columna o expresión. + +Construcción de Consultas +------------------------- + +A continuación se explicará como construir una sentencia SQL que incluya varias clausulas. Para simplificarlo, usamos +`$query` para representar el objeto [[yii\db\Query]]: + +### `SELECT` + +Para formar una consulta `SELECT` básica, se necesita especificar que columnas y de que tablas se seleccionarán: + +```php +$query->select('id, name') + ->from('user'); +``` + +Las opciones de select se pueden especificar como una cadena de texto (string) separada por comas o como un array. La +sintaxis del array es especialmente útil cuando se forma la selección dinámicamente. + +```php +$query->select(['id', 'name']) + ->from('user'); +``` + +> Info: Se debe usar siempre el formato array si la clausula `SELECT` contiene expresiones SQL. Esto se debe a + que una expresión SQL como `CONCAT(first_name, last_name) AS full_name` puede contener comas. Si se junta con otra + cadena de texto de otra columna, puede ser que la expresión se divida en varias partes por comas, esto puede + conllevar a errores. + +Cuando se especifican columnas, se pueden incluir los prefijos de las tablas o alias de columnas, ej. `user.id`, +`user.id AS user_id`. Si se usa un array para especificar las columnas, también se pueden usar las claves del array +para especificar los alias de columna, ej. `['user_id' => 'user.id', 'user_name' => 'user.name']`. + +A partir de la versión 2.0.1, también se pueden seleccionar subconsultas como columnas. Por ejemplo: + +```php +$subQuery = (new Query)->select('COUNT(*)')->from('user'); +$query = (new Query)->select(['id', 'count' => $subQuery])->from('post'); +// $query representa la siguiente sentencia SQL: +// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post` +``` + +Para seleccionar filas distintas, se puede llamar a `distinct()`, como se muestra a continuación: + +```php +$query->select('user_id')->distinct()->from('post'); +``` + +### `FROM` + +Para especificar de que tabla(s) se quieren seleccionar los datos, se llama a `from()`: + +```php +$query->select('*')->from('user'); +``` + +Se pueden especificar múltiples tablas usando una cadena de texto separado por comas o un array. Los nombres de tablas +pueden contener prefijos de esquema (ej. `'public.user'`) y/o alias de tablas (ej. `'user u'). El método +entrecomillara automáticamente los nombres de tablas a menos que contengan algún paréntesis (que significa que se +proporciona la tabla como una subconsulta o una expresión de BD). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['user u', 'post p']); +``` + +Cuando se especifican las tablas como un array, también se pueden usar las claves de los arrays como alias de tablas +(si una tabla no necesita alias, no se usa una clave en formato texto). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['u' => 'user', 'p' => 'post']); +``` + +Se puede especificar una subconsulta usando un objeto `Query`. En este caso, la clave del array correspondiente se +usará como alias para la subconsulta. + +```php +$subQuery = (new Query())->select('id')->from('user')->where('status=1'); +$query->select('*')->from(['u' => $subQuery]); +``` + +### `WHERE` + +Habitualmente se seleccionan los datos basándose en ciertos criterios. El Constructor de Consultas tiene algunos +métodos útiles para especificarlos, el más poderoso de estos es `where`, y se puede usar de múltiples formas. + +La manera más simple para aplicar una condición es usar una cadena de texto: + +```php +$query->where('status=:status', [':status' => $status]); +``` + +Cuando se usan cadenas de texto, hay que asegurarse que se unen los parámetros de la consulta, no crear una consulta +mediante concatenación de cadenas de texto. El enfoque anterior es seguro, el que se muestra a continuación, no lo es: + +```php +$query->where("status=$status"); // Peligroso! +``` + +En lugar de enlazar los valores de estado inmediatamente, se puede hacer usando `params` o `addParams`: + +```php +$query->where('status=:status'); +$query->addParams([':status' => $status]); +``` + +Se pueden establecer múltiples condiciones en `where` usando el *formato hash*. + +```php +$query->where([ + 'status' => 10, + 'type' => 2, + 'id' => [4, 8, 15, 16, 23, 42], +]); +``` + +El código generará la el siguiente SQL: + +```sql +WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42)) +``` + +El valor NULO es un valor especial en las bases de datos, y el Constructor de Consultas lo gestiona inteligentemente. +Este código: + +```php +$query->where(['status' => null]); +``` + +da como resultado la siguiente cláusula WHERE: + +```sql +WHERE (`status` IS NULL) +``` + +También se pueden crear subconsultas con objetos de tipo `Query` como en el siguiente ejemplo: + +```php +$userQuery = (new Query)->select('id')->from('user'); +$query->where(['id' => $userQuery]); +``` + +que generará el siguiente código SQL: + +```sql +WHERE `id` IN (SELECT `id` FROM `user`) +``` + +Otra manera de usar el método es el formato de operando que es `[operator, operand1, operand2, ...]`. + +El operando puede ser uno de los siguientes (ver también [[yii\db\QueryInterface::where()]]): + +- `and`: los operandos deben concatenerase usando `AND`. por ejemplo, `['and', 'id=1', 'id=2']` generará + `id=1 AND id=2`. Si el operando es un array, se convertirá en una cadena de texto usando las reglas aquí descritas. + Por ejemplo, `['and', 'type=1', ['or', 'id=1', 'id=2']]` generará `type=1 AND (id=1 OR id=2)`. El método no + ejecutará ningún filtrado ni entrecomillado. + +- `or`: similar al operando `and` exceptuando que los operando son concatenados usando `OR`. + +- `between`: el operando 1 debe ser el nombre de columna, y los operandos 2 y 3 deben ser los valores iniciales y + finales del rango en el que se encuentra la columna. Por ejemplo, `['between', 'id', 1, 10]` generará + `id BETWEEN 1 AND 10`. + +- `not between`: similar a `between` exceptuando que `BETWEEN` se reemplaza por `NOT BETWEEN` en la condición + generada. + +- `in`: el operando 1 debe ser una columna o una expresión de BD. El operando 2 puede ser un array o un objeto de tipo + `Query`. Generará una condición `IN`. Si el operando 2 es un array, representará el rango de valores que puede + albergar la columna o la expresión de BD; Si el operando 2 es un objeto de tipo `Query`, se generará una subconsulta + y se usará como rango de la columna o de la expresión de BD. Por ejemplo, `['in', 'id', [1, 2, 3]]` generará + `id IN (1, 2, 3)`. El método entrecomillará adecuadamente el nombre de columna y filtrará los valores del rango. El + operando `in` también soporta columnas compuestas. En este caso, el operando 1 debe se un array de columnas, + mientras que el operando 2 debe ser un array de arrays o un objeto de tipo `Query` que represente el rango de las + columnas. + +- `not in`: similar que el operando `in` exceptuando que `IN` se reemplaza por `NOT IN` en la condición generada. + +- `like`: el operando 1 debe ser una columna o una expresión de BD, y el operando 2 debe ser una cadena de texto o un + array que represente los valores a los que tienen que asemejarse la columna o la expresión de BD.Por ejemplo, + `['like', 'name', 'tester']` generará `name LIKE '%tester%'`. Cuando se da el valor rango como un array, se + generarán múltiples predicados `LIKE` y se concatenaran usando `AND`. Por ejemplo, + `['like', 'name', ['test', 'sample']]` generará `name LIKE '%test%' AND name LIKE '%sample%'`. También se puede + proporcionar un tercer operando opcional para especificar como deben filtrarse los caracteres especiales en los + valores. El operando debe se un array que mapeen los caracteres especiales a sus caracteres filtrados asociados. Si + no se proporciona este operando, se aplicará el mapeo de filtrado predeterminado. Se puede usar `false` o un array + vacío para indicar que los valores ya están filtrados y no se necesita aplicar ningún filtro. Hay que tener en + cuenta que cuando se usa un el mapeo de filtrado (o no se especifica el tercer operando), los valores se encerraran + automáticamente entre un par de caracteres de porcentaje. + +> Note: Cuando se usa PostgreSQL también se puede usar +[`ilike`](https://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) en lugar de `like` para +filtrar resultados insensibles a mayúsculas (case-insensitive). + +- `or like`: similar al operando `like` exceptuando que se usa `OR` para concatenar los predicados `LIKE` cuando haya + un segundo operando en un array. + +- `not like`: similar al operando `like` exceptuando que se usa `LIKE` en lugar de `NOT LIKE` en las condiciones + generadas. + +- `or not like`: similar al operando `not like` exceptuando que se usa `OR` para concatenar los predicados `NOT LIKE`. + +- `exists`: requiere un operando que debe ser una instancia de [[yii\db\Query]] que represente la subconsulta. Esto + generará una expresión `EXISTS (sub-query)`. + +- `not exists`: similar al operando `exists` y genera una expresión `NOT EXISTS (sub-query)`. + +Adicionalmente se puede especificar cualquier cosa como operando: + +```php +$query->select('id') + ->from('user') + ->where(['>=', 'id', 10]); +``` + +Cuyo resultado será: + +```sql +SELECT id FROM user WHERE id >= 10; +``` + +Si se construyen partes de una condición dinámicamente, es muy convenientes usar `andWhere()` y `orWhere()`: + +```php +$status = 10; +$search = 'yii'; + +$query->where(['status' => $status]); +if (!empty($search)) { + $query->andWhere(['like', 'title', $search]); +} +``` + +En el caso que `$search` no este vacío, se generará el siguiente código SQL: + +```sql +WHERE (`status` = 10) AND (`title` LIKE '%yii%') +``` + +#### Construcción de Condiciones de Filtro + +Cuando se generan condiciones de filtro basadas en datos recibidos de usuarios (inputs), a menudo se quieren gestionar +de forma especial las "datos vacíos" para ignorarlos en los filtros. Por ejemplo, teniendo un formulario HTML que +obtiene el nombre de usuario y la dirección de correo electrónico. Si el usuario solo rellena el campo de nombre de +usuario, se puede querer generar una consulta para saber si el nombre de usuario recibido es valido. Se puede usar +`filterWhere()` para conseguirlo: + +```php +// $username y $email son campos de formulario rellenados por usuarios +$query->filterWhere([ + 'username' => $username, + 'email' => $email, +]); +``` + +El método `filterWhere()` es muy similar al método `where()`. La principal diferencia es que el `filterWhere()` +eliminará los valores vacíos de las condiciones proporcionadas. Por lo tanto si `$email` es "vació", la consulta +resultante será `...WHERE username=:username`; y si tanto `$username` como `$email` son "vacías", la consulta no +tendrá `WHERE`. + +Decimos que un valor es *vacío* si es nulo, una cadena de texto vacía, una cadena de texto que consista en espacios en +blanco o un array vacío. + +También se pueden usar `andFilterWhere()` y `orFilterWhere()` para añadir más condiciones de filtro. + +### `ORDER BY` + +Se pueden usar `orderBy` y `addOrderBy` para ordenar resultados: + +```php +$query->orderBy([ + 'id' => SORT_ASC, + 'name' => SORT_DESC, +]); +``` + +Aquí estamos ordenando por `id` ascendente y después por `name` descendente. + +### `GROUP BY` and `HAVING` + +Para añadir `GROUP BY` al SQL generado se puede usar el siguiente código: + +```php +$query->groupBy('id, status'); +``` + +Si se quieren añadir otro campo después de usar `groupBy`: + +```php +$query->addGroupBy(['created_at', 'updated_at']); +``` + +Para añadir la condición `HAVING` se pueden usar los métodos `having` y `andHaving` y `orHaving`. Los parámetros para +ellos son similares a los del grupo de métodos `where`: + +```php +$query->having(['status' => $status]); +``` + +### `LIMIT` and `OFFSET` + +Para limitar el resultado a 10 filas se puede usar `limit`: + +```php +$query->limit(10); +``` + +Para saltarse las 100 primeras filas, se puede usar: + +```php +$query->offset(100); +``` + +### `JOIN` + +Las clausulas `JOIN` se generan en el Constructor de Consultas usando el método join aplicable: + +- `innerJoin()` +- `leftJoin()` +- `rightJoin()` + +Este left join selecciona los datos desde dos tablas relacionadas en una consulta: + +```php +$query->select(['user.name AS author', 'post.title as title']) + ->from('user') + ->leftJoin('post', 'post.user_id = user.id'); +``` + +En el código, el primer parámetro del método `leftjoin` especifica la tabla a la que aplicar el join. El segundo +parámetro, define la condición del join. + +Si la aplicación de bases de datos soporta otros tipos de joins, se pueden usar mediante el método `join` genérico: + +```php +$query->join('FULL OUTER JOIN', 'post', 'post.user_id = user.id'); +``` + +El primer argumento es el tipo de join a realizar. El segundo es la tabla a la que aplicar el join, y el tercero es la condición: + +Como en `FROM`, también se pueden efectuar joins con subconsultas. Para hacerlo, se debe especificar la subconsulta +como un array que tiene que contener un elemento. El valor del array tiene que ser un objeto de tipo `Query` que +represente la subconsulta, mientras que la clave del array es el alias de la subconsulta. Por ejemplo: + +```php +$query->leftJoin(['u' => $subQuery], 'u.id=author_id'); +``` + +### `UNION` + +En SQL `UNION` agrega resultados de una consulta a otra consulta. Las columnas devueltas por ambas consultas deben +coincidir. En Yii para construirla, primero se pueden formar dos objetos de tipo query y después usar el método +`union`: + +```php +$query = new Query(); +$query->select("id, category_id as type, name")->from('post')->limit(10); + +$anotherQuery = new Query(); +$anotherQuery->select('id, type, name')->from('user')->limit(10); + +$query->union($anotherQuery); +``` + +Consulta por Lotes +--------------- + +Cuando se trabaja con grandes cantidades de datos, los métodos como [[yii\db\Query::all()]] no son adecuados ya que +requieren la carga de todos los datos en memoria. Para mantener los requerimientos de memoria reducidos, Yii +proporciona soporte a las llamadas consultas por lotes (batch query). Una consulta por lotes usa un cursor de datos y +recupera los datos en bloques. + +Las consultas por lotes se pueden usar del siguiente modo: + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->orderBy('id'); + +foreach ($query->batch() as $users) { + // $users is an array of 100 or fewer rows from the user table +} + +// o si se quieren iterar las filas una a una +foreach ($query->each() as $user) { + // $user representa uno fila de datos de la tabla user +} +``` + +Los métodos [[yii\db\Query::batch()]] y [[yii\db\Query::each()]] devuelven un objeto [[yii\db\BatchQueryResult]] que +implementa una interfaz `Iterator` y así se puede usar en el constructor `foreach`. Durante la primera iteración, se +efectúa una consulta SQL a la base de datos. Desde entonces, los datos se recuperan por lotes en las iteraciones. El +tamaño predeterminado de los lotes es 100, que significa que se recuperan 100 filas de datos en cada lote. Se puede +modificar el tamaño de los lotes pasando pasando un primer parámetro a los métodos `batch()` o `each()`. + +En comparación con [[yii\db\Query::all()]], las consultas por lotes sólo cargan 100 filas de datos en memoria cada +vez. Si el procesan los datos y después se descartan inmediatamente, las consultas por lotes, pueden ayudar a mantener +el uso de memora bajo un limite. + +Si se especifica que el resultado de la consulta tiene que ser indexado por alguna columna mediante +[[yii\db\Query::indexBy()]], las consultas por lotes seguirán manteniendo el indice adecuado. Por ejemplo, + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->indexBy('username'); + +foreach ($query->batch() as $users) { + // $users esta indexado en la columna "username" +} + +foreach ($query->each() as $username => $user) { +} +``` diff --git a/docs/guide-es/glossary.md b/docs/guide-es/glossary.md new file mode 100644 index 00000000000..63ce5cbd91c --- /dev/null +++ b/docs/guide-es/glossary.md @@ -0,0 +1,67 @@ +# A + +## alias + +Alias es un string utilizado por Yii para referirse a una clase o directorio tal como `@app/vendor`. + +## aplicación + +La aplicación es el objeto central durante la solicitud HTTP. Contiene un número de componentes con los que toma información de la solicitud y la envía al controlador apropiado para posterior procesamiento. + +El objeto de la aplicación es instanciado como un singleton por el script de entrada. El singleton de la aplicación puede ser accedido desde cualquier lugar a través de `\Yii::$app`. + +## assets + +Asset se refiere a un archivo de recurso. Típicamente contiene JavaScript o CSS pero puede ser cualquier otra cosa que sea accesible vía HTTP. + +## atributo + +Un atributo es una propiedad de un modelo (una variable miembro de clase o una propiedad mágica definida vía `__get()`/`__set()`) que almacena **datos de negocio**. + +# B + +## bundle + +Bundle, conocido como paquete en Yii 1.1, se refiere a un número de recursos y un archivo de configuración que describe dependencias y lista recursos. + +# C + +## configuración + +Configuración puede referirse tanto al proceso de establecer propiedades de un objeto como a un archivo de configuración que almacena la definición de propiedades para un objeto o clase de objetos. + +# E + +## extensión + +Extensión es un grupo de clases, paquete de recursos y configuraciones que agrega más características a la aplicación. + +# I + +## instalación + +Instalación es el proceso de preparar algo para trabajar, desde seguir un archivo léame hasta ejecutar un script preparado especialmente para tal fin. En el caso de Yii, define permisos y chequea los requerimientos para el funcionamiento del software. + +# M + +## módulo + +Módulo es una sub-aplicación que contiene elementos MVC en sí mismo, como modelos, vistas, controladores, etc. y puede ser utilizado dentro de la aplicación principal. Típicamente remitiendo las solicitudes al módulo en vez de manejándolo desde controladores. + +# N + +## namespace + +Namespace (espacio de nombres) se refiere a una [característica de PHP](https://www.php.net/manual/es/language.namespaces.php) activamente utilizada en Yii 2. + +# P + +## paquete + +[Ver bundle](#bundle). + +# V + +## vendor + +Vendor (proveedor) es una organización o un desarrollador individual que provee código en forma de extensiones, módulos o librerías. diff --git a/docs/guide-es/helper-array.md b/docs/guide-es/helper-array.md new file mode 100644 index 00000000000..7ba27ac7124 --- /dev/null +++ b/docs/guide-es/helper-array.md @@ -0,0 +1,387 @@ +ArrayHelper +=========== + +Adicionalmente al [rico conjunto de funciones para arrays de PHP](https://www.php.net/manual/es/book.array.php), el array helper de Yii proporciona +métodos estáticos adicionales permitiendo trabajar con arrays de manera más eficiente. + + +## Devolviendo Valores + +Recuperar valores de un array, un objeto o una estructura compleja usando PHP estándar es bastante +repetitivo. Tienes que comprobar primero si una clave existe con `isset`, después devolver el valor si existe, si no, +devolver un valor por defecto: + +```php +class User +{ + public $name = 'Alex'; +} + +$array = [ + 'foo' => [ + 'bar' => new User(), + ] +]; + +$value = isset($array['foo']['bar']->name) ? $array['foo']['bar']->name : null; +``` + +Yii proviene de un método muy conveniente para hacerlo: + +```php +$value = ArrayHelper::getValue($array, 'foo.bar.name'); +``` + +El primer argumento del método es de donde vamos a obtener el valor. El segundo argumento especifica como devolver el dato. Puede ser +de la siguiente manera: + +- Nombre de la clave del array o de la propiedad del objeto para recuperar el valor. +- Conjunto de puntos separados por las claves del array o los nombres de las propiedades del objeto. Esto se ha usado en el ejemplo anterior. +- Un callback que devuelve un valor. + +El callback se debería usar de la siguiente manera: + +```php +$fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) { + return $user->firstName . ' ' . $user->lastName; +}); +``` + +El tercer argumento opcional es el valor por defecto el cual es `null` si no se especifica. Podría ser utilizado de la siguiente manera: + +```php +$username = ArrayHelper::getValue($comment, 'user.username', 'Unknown'); +``` + +En caso de que quieras coger un valor y luego removerlo inmediatamente del array puedes usar el método `remove`: + +```php +$array = ['type' => 'A', 'options' => [1, 2]]; +$type = ArrayHelper::remove($array, 'type'); +``` + +Después de ejecutar el código el `$array` contendrá `['options' => [1, 2]]` y `$type` debe ser `A`. Tenga en cuenta que a diferencia del método +`getValue`, `remove` solo soporta nombres clave simples. + + +## Comprobando la Existencia de Claves + +`ArrayHelper::keyExists` funciona de la misma manera que [array_key_exists](https://www.php.net/manual/es/function.array-key-exists.php) +excepto que también soporta case-insensitive para la comparación de claves. Por ejemplo, + +```php +$data1 = [ + 'userName' => 'Alex', +]; + +$data2 = [ + 'username' => 'Carsten', +]; + +if (!ArrayHelper::keyExists('username', $data1, false) || !ArrayHelper::keyExists('username', $data2, false)) { + echo "Please provide username."; +} +``` + +## Recuperando Columnas + +A menudo necesitas obtener unos valores de una columna de las filas de datos u objetos de un array. Un ejemplo común es obtener una lista de IDs. + +```php +$data = [ + ['id' => '123', 'data' => 'abc'], + ['id' => '345', 'data' => 'def'], +]; +$ids = ArrayHelper::getColumn($array, 'id'); +``` + +El resultado será `['123', '345']`. + +Si se requieren transformaciones adicionales o la manera de obtener el valor es complejo, se podría especificar como segundo argumento +una función anónima : + +```php +$result = ArrayHelper::getColumn($array, function ($element) { + return $element['id']; +}); +``` + + +## Re-indexar Arrays + +Con el fin de indexar un array según una clave especificada, se puede usar el método `index`. La entrada debería ser +un array multidimensional o un array de objetos. `$key` puede ser tanto una clave del sub-array, un nombre de una propiedad +del objeto, o una función anónima que debe devolver el valor que será utilizado como clave. + +El atributo `$groups` es un array de claves, que será utilizado para agrupar el array de entrada en uno o más sub-arrays +basado en la clave especificada. + +Si el atributo `$key` o su valor por el elemento en particular es `null` y `$groups` no está definido, dicho elemento del array +será descartado. De otro modo, si `$groups` es especificado, el elemento del array será agregado al array resultante +sin una clave. + +Por ejemplo: + +```php +$array = [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], +]; +$result = ArrayHelper::index($array, 'id');'); +``` + +El resultado será un array asociativo, donde la clave es el valor del atributo `id` + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // El segundo elemento del array original es sobrescrito por el último elemento debido a que tiene el mismo id +] +``` + +Pasando una función anónima en `$key`, da el mismo resultado. + +```php +$result = ArrayHelper::index($array, function ($element) { + return $element['id']; +}); +``` + +Pasando `id` como tercer argumento, agrupará `$array` mediante `id`: + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel y no indexado en su segundo nivel: + +```php +[ + '123' => [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ], + '345' => [ // todos los elementos con este índice están presentes en el array resultante + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ] +] +``` + +Una función anónima puede ser usada también en el array agrupador: + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel, por `device` en su segundo nivel e +indexado por `data` en su tercer nivel: + +```php +[ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ] + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + ] + ] +] +``` + +## Construyendo Mapas (Maps) + +Con el fin de construir un mapa (pareja clave-valor) de un array multidimensional o un array de objetos puedes usar el método `map`. +Los parámetros `$from` y `$to` especifican los nombres de las claves o los nombres de las propiedades que serán configuradas en el mapa. Opcionalmente, se puede +agrupar en el mapa de acuerdo al campo de agrupamiento `$group`. Por ejemplo, + +```php +$array = [ + ['id' => '123', 'name' => 'aaa', 'class' => 'x'], + ['id' => '124', 'name' => 'bbb', 'class' => 'x'], + ['id' => '345', 'name' => 'ccc', 'class' => 'y'], +); + +$result = ArrayHelper::map($array, 'id', 'name'); +// el resultado es: +// [ +// '123' => 'aaa', +// '124' => 'bbb', +// '345' => 'ccc', +// ] + +$result = ArrayHelper::map($array, 'id', 'name', 'class'); +// el resultado es: +// [ +// 'x' => [ +// '123' => 'aaa', +// '124' => 'bbb', +// ], +// 'y' => [ +// '345' => 'ccc', +// ], +// ] +``` + + +## Ordenamiento Multidimensional + +El método `multisort` ayuda a ordenar un array de objetos o arrays anidados por una o varias claves. Por ejemplo, + +```php +$data = [ + ['age' => 30, 'name' => 'Alexander'], + ['age' => 30, 'name' => 'Brian'], + ['age' => 19, 'name' => 'Barney'], +]; +ArrayHelper::multisort($data, ['age', 'name'], [SORT_ASC, SORT_DESC]); +``` + +Después del ordenado obtendremos lo siguiente en `$data`: + +```php +[ + ['age' => 19, 'name' => 'Barney'], + ['age' => 30, 'name' => 'Brian'], + ['age' => 30, 'name' => 'Alexander'], +]; +``` + +El segundo argumento que especifica las claves para ordenar puede ser una cadena si se trata de una clave, un array en caso de que tenga múltiples claves +o una función anónima como la siguiente + +```php +ArrayHelper::multisort($data, function($item) { + return isset($item['age']) ? ['age', 'name'] : 'name'; +}); +``` + +El tercer argumento es la dirección. En caso de ordenar por una clave podría ser `SORT_ASC` o +`SORT_DESC`. Si ordenas por múltiples valores puedes ordenar cada valor diferentemente proporcionando un array de +direcciones de ordenación. + +El último argumento es un PHP sort flag que toma los mismos valores que los pasados a +PHP [sort()](https://www.php.net/manual/es/function.sort.php). + + +## Detectando Tipos de Array + +Es muy útil saber si un array es indexado o asociativo. He aquí un ejemplo: + +```php +// sin claves especificadas +$indexed = ['Qiang', 'Paul']; +echo ArrayHelper::isIndexed($indexed); + +// todas las claves son strings +$associative = ['framework' => 'Yii', 'version' => '2.0']; +echo ArrayHelper::isAssociative($associative); +``` + + +## Codificación y Decodificación de Valores HTML + +Con el fin de codificar o decodificar caracteres especiales en un array de strings con entidades HTML puedes usar lo siguiente: + +```php +$encoded = ArrayHelper::htmlEncode($data); +$decoded = ArrayHelper::htmlDecode($data); +``` + +Solo los valores se codifican por defecto. Pasando como segundo argumento `false` puedes codificar un array de claves también. +La codificación utilizará el charset de la aplicación y podría ser cambiado pasandole un tercer argumento. + + +## Fusionando Arrays + +```php + /** + * Fusiona recursivamente dos o más arrays en uno. + * Si cada array tiene un elemento con el mismo valor string de clave, el último + * sobrescribirá el anterior (difiere de array_merge_recursive). + * Se llegará a una fusión recursiva si ambos arrays tienen un elemento tipo array + * y comparten la misma clave. + * Para elementos cuyas claves son enteros, los elementos del array final + * serán agregados al array anterior. + * @param array $a array al que se va a fusionar + * @param array $b array desde el cual fusionar. Puedes especificar + * arrays adicionales mediante el tercer argumento, cuarto argumento, etc. + * @return array el array fusionado (los arrays originales no sufren cambios) + */ + public static function merge($a, $b) +``` + + +## Convirtiendo Objetos a Arrays + +A menudo necesitas convertir un objeto o un array de objetos a un array. El caso más común es convertir los modelos de active record +con el fin de servir los arrays de datos vía API REST o utilizarlos de otra manera. El siguiente código se podría utilizar para hacerlo: + +```php +$posts = Post::find()->limit(10)->all(); +$data = ArrayHelper::toArray($posts, [ + 'app\models\Post' => [ + 'id', + 'title', + // el nombre de la clave del resultado del array => nombre de la propiedad + 'createTime' => 'created_at', + // el nombre de la clave del resultado del array => función anónima + 'length' => function ($post) { + return strlen($post->content); + }, + ], +]); +``` + +El primer argumento contiene el dato que queremos convertir. En nuestro caso queremos convertir un modelo AR `Post`. + +El segundo argumento es el mapeo de conversión por clase. Estamos configurando un mapeo para el modelo `Post`. +Cada array de mapeo contiene un conjunto de mapeos. Cada mapeo podría ser: + +- Un campo nombre para incluir como está. +- Un par clave-valor del array deseado con un nombre clave y el nombre de la columna del modelo que tomará el valor. +- Un par clave-valor del array deseado con un nombre clave y una función anónima que retorne el valor. + +El resultado de la conversión anterior será: + + +```php +[ + 'id' => 123, + 'title' => 'test', + 'createTime' => '2013-01-01 12:00AM', + 'length' => 301, +] +``` + +Es posible proporcionar una manera predeterminada de convertir un objeto a un array para una clase especifica +mediante la implementación de la interfaz [[yii\base\Arrayable|Arrayable]] en esa clase. + +## Haciendo pruebas con Arrays + +A menudo necesitarás comprobar está en un array o un grupo de elementos es un sub-grupo de otro. +A pesar de que PHP ofrece `in_array()`, este no soporta sub-grupos u objetos de tipo `\Traversable`. + +Para ayudar en este tipo de pruebas, [[yii\helpers\ArrayHelper]] provee [[yii\helpers\ArrayHelper::isIn()|isIn()]] +y [[yii\helpers\ArrayHelper::isSubset()|isSubset()]] con la misma firma del método +[in_array()](https://www.php.net/manual/es/function.in-array.php). + +```php +// true +ArrayHelper::isIn('a', ['a']); +// true +ArrayHelper::isIn('a', new(ArrayObject['a'])); + +// true +ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) + +``` diff --git a/docs/guide-es/helper-html.md b/docs/guide-es/helper-html.md new file mode 100644 index 00000000000..99b556e697c --- /dev/null +++ b/docs/guide-es/helper-html.md @@ -0,0 +1,377 @@ +Clase auxiliar Html (Html helper) +================================= + +Todas las aplicaciones web generan grandes cantidades de marcado HTML (HTML markup). Si el marcado es estático, se +puede realizar de forma efectiva +[mezclando PHP y HTML en un mismo archivo](https://www.php.net/manual/es/language.basic-syntax.phpmode.php) pero cuando se +generan dinámicamente empieza a complicarse su gestión sin ayuda extra. Yii ofrece esta ayuda en forma de una clase auxiliar Html +que proporciona un conjunto de métodos estáticos para gestionar las etiquetas HTML más comúnmente usadas, sus opciones y contenidos. + +> Note: Si el marcado es casi estático, es preferible usar HTML directamente. No es necesario encapsularlo todo con +llamadas a la clase auxiliar Html. + +## Lo fundamental + + +Teniendo en cuenta que la construcción de HTML dinámico mediante la concatenación de cadenas de texto se complica +rápidamente, Yii proporciona un conjunto de métodos para manipular las opciones de etiquetas y la construcción de las +mismas basadas en estas opciones. + +### Generación de etiquetas + +El código de generación de etiquetas es similar al siguiente: + +```php +name), ['class' => 'username']) ?> +``` + +El primer argumento es el nombre de la etiqueta. El segundo es el contenido que se ubicará entre la etiqueta de +apertura y la de cierre. Hay que tener en cuenta que estamos usando `Html::encode`. Esto es debido a que el contenido +no se codifica automáticamente para permitir usar HTML cuando se necesite. La tercera opción es un array de opciones +HTML o, en otras palabras, los atributos de las etiquetas. En este array la clave representa el nombre del atributo +como podría ser `class`, `href` o `target` y el valor es su valor. + +El código anterior generará el siguiente HTML: + +```html +

samdark

+``` + +Si se necesita solo la apertura o el cierre de una etiqueta, se pueden usar los métodos `Html::beginTag()` y +`Html::endTag()`. + +Las opciones se usan en muchos métodos de la clase auxiliar Html y en varios widgets. En todos estos casos hay cierta +gestión adicional que se debe conocer: + +- Si un valor es `null`, el correspondiente atributo no se renderizará. +- Los atributos cuyos valores son de tipo booleano serán tratados como + [atributos booleanos](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes). +- Los valores de los atributos se codificarán en HTML usando [[yii\helpers\Html::encode()|Html::encode()]]. +- El atributo "data" puede recibir un array. En este caso, se "expandirá" y se renderizará una lista de atributos + `data` ej. `'data' => ['id' => 1, 'name' => 'yii']` se convierte en `data-id="1" data-name="yii"`. +- El atributo "data" puede recibir un JSON. Se gestionará de la misma manera que un array ej. + `'data' => ['params' => ['id' => 1, 'name' => 'yii'], 'status' => 'ok']` se convierte en + `data-params='{"id":1,"name":"yii"}' data-status="ok"`. + +### Formación de clases y estilos dinámicamente + +Cuando se construyen opciones para etiquetas HTML, a menudo nos encontramos con valores predeterminados que hay que +modificar. Para añadir o eliminar clases CSS se puede usar el siguiente ejemplo: + +```php +$options = ['class' => 'btn btn-default']; + +if ($type === 'success') { + Html::removeCssClass($options, 'btn-default'); + Html::addCssClass($options, 'btn-success'); +} + +echo Html::tag('div', 'Pwede na', $options); + +// cuando $type sea 'success' se renderizará +//
Pwede na
+``` + +Para hacer lo mismo con los estilos para el atributo `style`: + +```php +$options = ['style' => ['width' => '100px', 'height' => '100px']]; + +// devuelve style="width: 100px; height: 200px; position: absolute;" +Html::addCssStyle($options, 'height: 200px; positon: absolute;'); + +// devuelve style="position: absolute;" +Html::removeCssStyle($options, ['width', 'height']); +``` + +Cuando se usa [[yii\helpers\Html::addCssStyle()|addCssStyle()]] se puede especificar si un array de pares clave-valor +corresponde a nombres y valores de la propiedad CSS correspondiente o a una cadena de texto como por ejemplo +`width: 100px; height: 200px;`. Estos formatos se pueden "hacer" y "deshacer" usando +[[yii\helpers\Html::cssStyleFromArray()|cssStyleFromArray()]] y +[[yii\helpers\Html::cssStyleToArray()|cssStyleToArray()]]. El método +[[yii\helpers\Html::removeCssStyle()|removeCssStyle()]] acepta un array de propiedades que se eliminarán. Si sólo se +eliminara una propiedad, se puede especificar como una cadena de texto. + +## Codificación y Decodificación del contenido + + +Para que el contenido se muestre correctamente y de forma segura con caracteres especiales HTML el contenido debe ser +codificado. En PHP esto se hace con [htmlspecialchars](https://www.php.net/manual/es/function.htmlspecialchars.php) y +[htmlspecialchars_decode](https://www.php.net/manual/es/function.htmlspecialchars-decode.php). El problema con el uso +de estos métodos directamente es que se tiene que especificar la codificación y opciones extra cada vez. Ya que las +opciones siempre son las mismas y la codificación debe coincidir con la de la aplicación para prevenir problemas de +seguridad, Yii proporciona dos métodos simples y compactos: + +```php +$userName = Html::encode($user->name); +echo $userName; + +$decodedUserName = Html::decode($userName); +``` + +## Formularios + + +El trato con el marcado de formularios es una tarea repetitiva y propensa a errores. Por esto hay un grupo de métodos +para ayudar a gestionarlos. + +> Note: hay que considerar la opción de usar [[yii\widgets\ActiveForm|ActiveForm]] en caso de que se gestionen +formularios que requieran validaciones. + +### Creando formularios + +Se puede abrir un formulario con el método [[yii\helpers\Html::beginForm()|beginForm()]] como se muestra a +continuación: + +```php + $id], 'post', ['enctype' => 'multipart/form-data']) ?> +``` + +El primer argumento es la URL a la que se enviarán los datos del formulario. Se puede especificar en formato de ruta +de Yii con los parámetros aceptados por [[yii\helpers\Url::to()|Url::to()]]. El segundo es el método que se usará. +`post` es el método predeterminado. El tercero es un array de opciones para la etiqueta `form`. En este caso cambiamos +el método de codificación del formulario de `data` en una petición POST a `multipart/form-data`. Esto se requiere +cuando se quieren subir archivos. + +El cierre de la etiqueta `form` es simple: + +```php + +``` + +### Botones + +Para generar botones se puede usar el siguiente código: + +```php + 'teaser']) ?> + 'submit']) ?> + 'reset']) ?> +``` + +El primer argumento para los tres métodos es el título del botón y el segundo son las opciones. El título no está +codificado pero si se usan datos recibidos por el usuario, deben codificarse mediante +[[yii\helpers\Html::encode()|Html::encode()]]. + +### Inputs + +Hay dos grupos en los métodos input. Unos empiezan con `active` y se llaman inputs activos y los otros no empiezan +así. Los inputs activos obtienen datos del modelo y del atributo especificado y los datos de los inputs normales se +especifica directamente. + +Los métodos más genéricos son: + +```php +type, input name, input value, options +name, ['class' => $username]) ?> + +type, model, model attribute name, options + $username]) ?> +``` + +Si se conoce el tipo de input de antemano, es conveniente usar los atajos de los métodos: + +- [[yii\helpers\Html::buttonInput()]] +- [[yii\helpers\Html::submitInput()]] +- [[yii\helpers\Html::resetInput()]] +- [[yii\helpers\Html::textInput()]], [[yii\helpers\Html::activeTextInput()]] +- [[yii\helpers\Html::hiddenInput()]], [[yii\helpers\Html::activeHiddenInput()]] +- [[yii\helpers\Html::passwordInput()]] / [[yii\helpers\Html::activePasswordInput()]] +- [[yii\helpers\Html::fileInput()]], [[yii\helpers\Html::activeFileInput()]] +- [[yii\helpers\Html::textarea()]], [[yii\helpers\Html::activeTextarea()]] + +Los botones de opción (Radios) y las casillas de verificación (checkboxes) se especifican de forma un poco diferente: + +```php + 'I agree']); + 'agreement']) + + 'I agree']); + 'agreement']) +``` + +Las listas desplegables (dropdown list) se pueden renderizar como se muestra a continuación: + +```php + + + + + +``` + +El primer argumento es el nombre del input, el segundo es el valor seleccionado actualmente y el tercero es el array +de pares clave-valor donde la clave es la lista de valores y el valor del array es la lista a mostrar. + +Si se quiere habilitar la selección múltiple, se puede usar la lista seleccionable (checkbox list): + +```php + + +``` + +Si no, se puede usar la lista de opciones (radio list): + +```php + + +``` + +### Etiquetas y Errores + +De forma parecida que en los inputs hay dos métodos para generar etiquetas. El activo que obtiene los datos del modelo y +el no-activo que acepta los datos directamente: + +```php + 'label username']) ?> + 'label username']) +``` + +Para mostrar los errores del formulario de un modelo o modelos a modo de resumen puedes usar: + +```php + 'errors']) ?> +``` + +Para mostrar un error individual: + +```php + 'error']) ?> +``` + +### Input Names y Values + +Existen métodos para obtener names, IDs y values para los campos de entrada (inputs) basados en el modelo. Estos se +usan principalmente internamente pero a veces pueden resultar prácticos: + +```php +// Post[title] +echo Html::getInputName($post, 'title'); + +// post-title +echo Html::getInputId($post, 'title'); + +// mi primer post +echo Html::getAttributeValue($post, 'title'); + +// $post->authors[0] +echo Html::getAttributeValue($post, '[0]authors[0]'); +``` + +En el ejemplo anterior, el primer argumento es el modelo y el segundo es un atributo de expresión. En su forma más +simple es su nombre de atributo pero podría ser un nombre de atributo prefijado y/o añadido como sufijo con los +indices de un array, esto se usa principalmente para mostrar inputs en formatos de tablas: + +- `[0]content` se usa en campos de entrada de datos en formato de tablas para representar el atributo "content" para + el primer modelo del input en formato de tabla; +- `dates[0]` representa el primer elemento del array del atributo "dates"; +- `[0]dates[0]` representa el primer elemento del array del atributo "dates" para el primer modelo en formato de tabla. + +Para obtener el nombre de atributo sin sufijos o prefijos se puede usar el siguiente código: + +```php +// dates +echo Html::getAttributeName('dates[0]'); +``` + +## Estilos y scripts + + +Existen dos métodos para generar etiquetas que envuelvan estilos y scripts incrustados (embebbed): + +```php + + +Genera + + + + true]); + +Genera + + +``` + +Si se quiere enlazar un estilo externo desde un archivo CSS: + +```php + 'IE 5']) ?> + +genera + + +``` + +El primer argumento es la URL. El segundo es un array de opciones. Adicionalmente, para regular las opciones se puede +especificar: + +- `condition` para envolver `` por lo que el sólo se + incluirá si el navegador no soporta JavaScript o si lo ha deshabilitado el usuario. + +Para enlazar un archivo JavaScript: + +```php + +``` + +Es igual que con las CSS, el primer argumento especifica el enlace al fichero que se quiere incluir. Las opciones se +pueden pasar como segundo argumento. En las opciones se puede especificar `condition` del mismo modo que se puede usar +para `cssFile`. + +## Enlaces + + +Existe un método para generar hipervínculos a conveniencia: + +```php + $id], ['class' => 'profile-link']) ?> +``` + +El primer argumento es el título. No está codificado por lo que si se usan datos enviados por el usuario se tienen que +codificar usando `Html::encode()`. El segundo argumento es el que se introducirá en `href` de la etiqueta ` +``` + +## Imagenes + + +Para generar una etiqueta de tipo imagen se puede usar el siguiente ejemplo: + +```php + 'My logo']) ?> + +genera + +My logo +``` + +Aparte de los [alias](concept-aliases.md) el primer argumento puede aceptar rutas, parámetros y URLs. Del mismo modo +que [Url::to()](helper-url.md). + +## Listas + + +Las listas desordenadas se puede generar como se muestra a continuación: + +```php + function($item, $index) { + return Html::tag( + 'li', + $this->render('post', ['item' => $item]), + ['class' => 'post'] + ); +}]) ?> +``` + +Para generar listas ordenadas se puede usar `Html::ol()` en su lugar. diff --git a/docs/guide-es/helper-overview.md b/docs/guide-es/helper-overview.md new file mode 100644 index 00000000000..97a114f6c19 --- /dev/null +++ b/docs/guide-es/helper-overview.md @@ -0,0 +1,79 @@ +Helpers +======= + +> Note: Esta sección está en desarrollo. + +Yii ofrece muchas clases que ayudan a simplificar las tareas comunes de codificación, como manipulación de string o array, +generación de código HTML, y más. Estas clases helper están organizadas bajo el namespace `yii\helpers` y +son todo clases estáticas (lo que significa que sólo contienen propiedades y métodos estáticos y no deben ser instanciadas). + +Puedes usar una clase helper directamente llamando a uno de sus métodos estáticos, como a continuación: + +```php +use yii\helpers\Html; + +echo Html::encode('Test > test'); +``` + +> Note: Para soportar la [personalización de clases helper](#customizing-helper-classes), Yii separa cada clase helper del núcleo + en dos clases: una clase base (ej. `BaseArrayHelper`) y una clase concreta (ej. `ArrayHelper`). + Cuando uses un helper, deberías sólo usar la versión concreta y nunca usar la clase base. + + +Clases Helper del núcleo +------------------------ + +Las siguientes clases helper del núcleo son proporcionadas en los releases de Yii: + +- [ArrayHelper](helper-array.md) +- Console +- FileHelper +- [Html](helper-html.md) +- HtmlPurifier +- Image +- Inflector +- Json +- Markdown +- Security +- StringHelper +- [Url](helper-url.md) +- VarDumper + + +Personalizando Las Clases Helper +-------------------------------- + +Para personalizar una clase helper del núcleo (ej. [[yii\helpers\ArrayHelper]]), deberías crear una nueva clase extendiendo +de los helpers correspondientes a la clase base (ej. [[yii\helpers\BaseArrayHelper]]), incluyendo su namespace. Esta clase +será creada para remplazar la implementación original del framework. + +El siguiente ejemplo muestra como personalizar el método [[yii\helpers\ArrayHelper::merge()|merge()]] de la clase +[[yii\helpers\ArrayHelper]]: + +```php + + +Se pueden usar dos métodos para obtener URLs comunes: URL de inicio (home URL) y URL base (base URL) de la petición +(request) actual. Para obtener la URL de inicio se puede usar el siguiente código: + +```php +$relativeHomeUrl = Url::home(); +$absoluteHomeUrl = Url::home(true); +$httpsAbsoluteHomeUrl = Url::home('https'); +``` + +Si no se pasan parámetros, la URL generada es relativa. Se puede pasar `true`para obtener la URL absoluta del +esquema actual o especificar el esquema explícitamente (`https`, `http`). + +Para obtener la URL base de la petición actual, se puede usar el siguiente código: + +```php +$relativeBaseUrl = Url::base(); +$absoluteBaseUrl = Url::base(true); +$httpsAbsoluteBaseUrl = Url::base('https'); +``` + +El único parámetro del método funciona exactamente igual que para `Url::home()`. + + +## Creación de URLs + +Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El método utiliza [[\yii\web\UrlManager]] +para crear la URL: + +```php +$url = Url::toRoute(['product/view', 'id' => 42]); +``` + +Se puede especificar la ruta como una cadena de texto, ej. `site/index`. También se puede usar un array si se +quieren especificar parámetros para la URL que se esta generando. El formato del array debe ser: + +```php +// genera: /index.php?r=site%2Findex¶m1=value1¶m2=value2 +['site/index', 'param1' => 'value1', 'param2' => 'value2'] +``` + +Si se quiere crear una URL con un enlace, se puede usar el formato de array con el parámetro `#`. Por ejemplo, + +```php +// genera: /index.php?r=site/index¶m1=value1#name +['site/index', 'param1' => 'value1', '#' => 'name'] +``` + +Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (ej. `/site/index`), mientras que una ruta relativa +no la tiene (ej. `site/index` o `index`). Una ruta relativa se convertirá en una ruta absoluta siguiendo las siguientes reglas: + +- Si la ruta es una cadena vacía, se usará la [[\yii\web\Controller::route|route]] actual; +- Si la ruta no contiene barras (ej. `index`), se considerará que es el ID de una acción del controlador actual y + se antepondrá con [[\yii\web\Controller::uniqueId]]; +- Si la ruta no tiene barra inicial (ej. `site/index`), se considerará que es una ruta relativa del modulo actual y + se le antepondrá el [[\yii\base\Module::uniqueId|uniqueId]] del modulo. + +Desde la versión 2.0.2, puedes especificar una ruta en términos de [alias](concept-aliases.md). Si este es el caso, +el alias será convertido primero en la ruta real, la cual será entonces transformada en una ruta absoluta de acuerdo +a las reglas mostradas arriba. + +A continuación se muestran varios ejemplos del uso de este método: + +```php +// /index.php?r=site%2Findex +echo Url::toRoute('site/index'); + +// /index.php?r=site%2Findex&src=ref1#name +echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::toRoute(['@postEdit', 'id' => 100]); + +// https://www.example.com/index.php?r=site%2Findex +echo Url::toRoute('site/index', true); + +// https://www.example.com/index.php?r=site%2Findex +echo Url::toRoute('site/index', 'https'); +``` + +El otro método `Url::to()` es muy similar a [[toRoute()]]. La única diferencia es que este método requiere que la ruta +especificada sea un array. Si se pasa una cadena de texto, se tratara como una URL. + +El primer argumento puede ser: + +- un array: se llamará a [[toRoute()]] para generar la URL. Por ejemplo: `['site/index']`, + `['post/index', 'page' => 2]`. Se puede revisar [[toRoute()]] para obtener más detalles acerca de como especificar + una ruta. +- una cadena que empiece por `@`: se tratará como un alias, y se devolverá la cadena correspondiente asociada a este + alias. +- una cadena vacía: se devolverá la URL de la petición actual; +- una cadena de texto: se devolverá sin alteraciones. + +Cuando se especifique `$schema` (tanto una cadena de text como `true`), se devolverá una URL con información del host +(obtenida mediante [[\yii\web\UrlManager::hostInfo]]). Si `$url` ya es una URL absoluta, su esquema se reemplazará con +el especificado. + +A continuación se muestran algunos ejemplos de uso: + +```php +// /index.php?r=site%2Findex +echo Url::to(['site/index']); + +// /index.php?r=site%2Findex&src=ref1#name +echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); + +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::to(['@postEdit', 'id' => 100]); + +// the currently requested URL +echo Url::to(); + +// /images/logo.gif +echo Url::to('@web/images/logo.gif'); + +// images/logo.gif +echo Url::to('images/logo.gif'); + +// https://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', true); + +// https://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', 'https'); +``` + +Desde la versión 2.0.3, puedes utilizar [[yii\helpers\Url::current()]] para crear una URL a partir de la ruta +solicitada y los parámetros GET. Puedes modificar o eliminar algunos de los parámetros GET, o también agregar nuevos +pasando un parámetro `$params` al método. Por ejemplo, + +```php +// asume que $_GET = ['id' => 123, 'src' => 'google'], la ruta actual es "post/view" + +// /index.php?r=post%2Fview&id=123&src=google +echo Url::current(); + +// /index.php?r=post%2Fview&id=123 +echo Url::current(['src' => null]); +// /index.php?r=post%2Fview&id=100&src=google +echo Url::current(['id' => 100]); +``` + + +## Recordar URLs + +Hay casos en que se necesita recordar la URL y después usarla durante el procesamiento de una de las peticiones +secuenciales. Se puede logar de la siguiente manera: + +```php +// Recuerda la URL actual +Url::remember(); + +// Recuerda la URL especificada. Revisar Url::to() para ver formatos de argumentos. +Url::remember(['product/view', 'id' => 42]); + +// Recuerda la URL especificada con un nombre asignado +Url::remember(['product/view', 'id' => 42], 'product'); +``` + +En la siguiente petición se puede obtener la URL memorizada de la siguiente manera: + +```php +$url = Url::previous(); +$productUrl = Url::previous('product'); +``` + +## Chequear URLs relativas + +Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente código: + +```php +$isRelative = Url::isRelative('test/it'); +``` diff --git a/docs/guide-es/images/application-lifecycle.graphml b/docs/guide-es/images/application-lifecycle.graphml new file mode 100644 index 00000000000..850863ab263 --- /dev/null +++ b/docs/guide-es/images/application-lifecycle.graphml @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Entry script (index.php or yii) + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Load application config + + + + + + + + + + + + + + + + + + + + + Create application instance + + + + + + + + + + Folder 5 + + + + + + + + + + + + + + + + preInit() + + + + + + + + + + + + + + + + + Register error handler + + + + + + + + + + + + + + + + + Configure application properties + + + + + + + + + + + + + + + + + init() + + + + + + + + + + + + + + + + + bootstrap() + + + + + + + + + + + + + + + + + + + + + + + Run application + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + EVENT_BEFORE_REQUEST + + + + + + + + + + + + + + + + + + + + + Handle request + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Resolve request into route and parameters + + + + + + + + + + + + + + + + + Create module, controller and action + + + + + + + + + + + + + + + + + Run action + + + + + + + + + + + + + + + + + + + EVENT_AFTER_REQUEST + + + + + + + + + + + + + + + + + Send response to end user + + + + + + + + + + + + + + + + + + + Complete request processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration array + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exit status + + + + + + + + + + + + + + + + diff --git a/docs/guide-es/images/application-lifecycle.png b/docs/guide-es/images/application-lifecycle.png new file mode 100644 index 00000000000..d2cc804e721 Binary files /dev/null and b/docs/guide-es/images/application-lifecycle.png differ diff --git a/docs/guide-es/images/application-structure.graphml b/docs/guide-es/images/application-structure.graphml new file mode 100644 index 00000000000..dde73594e4b --- /dev/null +++ b/docs/guide-es/images/application-structure.graphml @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + + + + + componente +de +aplicación + + + + + + + + + + + + + + + + + script de entrada + + + + + + + + + + + + + + + + + aplicación + + + + + + + + + + + + + + + + + controlador + + + + + + + + + + + + + + + + + filtro + + + + + + + + + + + + + + + + + módulo + + + + + + + + + + + + + + + + + vista + + + + + + + + + + + + + + + + + modelo + + + + + + + + + + + + + + + + + widget + + + + + + + + + + + + + + + + + recursos + + + + + + + + + + + + + + + + + 1:1 + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 1..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + + + 0..* + + + + + + + + + + + + + + + + diff --git a/docs/guide-es/images/application-structure.png b/docs/guide-es/images/application-structure.png new file mode 100644 index 00000000000..8025791f56b Binary files /dev/null and b/docs/guide-es/images/application-structure.png differ diff --git a/docs/guide-es/images/rbac-access-check-1.graphml b/docs/guide-es/images/rbac-access-check-1.graphml new file mode 100644 index 00000000000..44078515cfa --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-1.png b/docs/guide-es/images/rbac-access-check-1.png new file mode 100644 index 00000000000..9bcaf9b2ec6 Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-1.png differ diff --git a/docs/guide-es/images/rbac-access-check-2.graphml b/docs/guide-es/images/rbac-access-check-2.graphml new file mode 100644 index 00000000000..c521d429eab --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-2.png b/docs/guide-es/images/rbac-access-check-2.png new file mode 100644 index 00000000000..38f6f246786 Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-2.png differ diff --git a/docs/guide-es/images/rbac-access-check-3.graphml b/docs/guide-es/images/rbac-access-check-3.graphml new file mode 100644 index 00000000000..8747cee0da7 --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-3.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-3.png b/docs/guide-es/images/rbac-access-check-3.png new file mode 100644 index 00000000000..22e60badb9e Binary files /dev/null and b/docs/guide-es/images/rbac-access-check-3.png differ diff --git a/docs/guide-es/images/rbac-hierarchy-1.graphml b/docs/guide-es/images/rbac-hierarchy-1.graphml new file mode 100644 index 00000000000..927b416d61e --- /dev/null +++ b/docs/guide-es/images/rbac-hierarchy-1.graphml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-1.png b/docs/guide-es/images/rbac-hierarchy-1.png new file mode 100644 index 00000000000..604195f8249 Binary files /dev/null and b/docs/guide-es/images/rbac-hierarchy-1.png differ diff --git a/docs/guide-es/images/rbac-hierarchy-2.graphml b/docs/guide-es/images/rbac-hierarchy-2.graphml new file mode 100644 index 00000000000..b81887b0e02 --- /dev/null +++ b/docs/guide-es/images/rbac-hierarchy-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-2.png b/docs/guide-es/images/rbac-hierarchy-2.png new file mode 100644 index 00000000000..2b03c3ecc1a Binary files /dev/null and b/docs/guide-es/images/rbac-hierarchy-2.png differ diff --git a/docs/guide-es/images/request-lifecycle.graphml b/docs/guide-es/images/request-lifecycle.graphml new file mode 100644 index 00000000000..533eaf084b1 --- /dev/null +++ b/docs/guide-es/images/request-lifecycle.graphml @@ -0,0 +1,834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + usuario + + + + + + + + + + + + + + + + + + + + modelo + + + + + + + + + + + + + + + + + base de datos + + + + + + + + + + + + + + + + + + + + + + + + + + + + vista + + + + + + + + + + + + + + + + + + + + controlador + + + + + + + + + + Folder 1 + + + + + + + + + + + + + + + + crear acción + + + + + + + + + + + + + + + + + ejecutar filtros + + + + + + + + + + + + + + + + + + + + acción + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + cargar modelo + + + + + + + + + + + + + + + + + renderizar vista + + + + + + + + + + + + + + + + + + + + + respuesta + + + + + + + + + + + + + + + + + petición + + + + + + + + + + + + + + + + + + + + aplicación + + + + + + + + + + Folder 2 + + + + + + + + + + + + + + + + resolver ruta + + + + + + + + + + + + + + + + + crear controlador + + + + + + + + + + + + + + + + + + + + + + script de entrada + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + cargar configuración de aplicación + + + + + + + + + + + + + + + + + ejecutar aplicación + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11 + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + 4 + + + + + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + 8 + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="66px" viewBox="0 0 57 66" enable-background="new 0 0 57 66" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3799" y1="-2276.8809" x2="27.6209" y2="-2306.6792" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_13_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + <path fill="#2068A3" stroke="#2068A3" d="M28.106,33.487c-8.112,0-12.688,4.312-12.688,10.437c0,7.422,12.688,10.438,12.688,10.438 + s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.487,28.106,33.487z M26.288,53.051c0,0-7.135-2.093-8.805-7.201 + c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_2_" cx="14.2417" cy="9.1006" r="53.247" gradientTransform="matrix(1 0 0 -1 0.04 65.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#74AEEE"/> + <stop offset="1" style="stop-color:#2068A3"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#2068A3" stroke-miterlimit="10" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.022,7.807-14.022,7.807s-10.472-2.484-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path fill="#5491CF" stroke="#2068A3" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397c-0.514,1.027-1.669,4.084-1.669,5.148 + c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.335-2.36c-3.601-1.419-4.071-3.063-5.89-4.854 + C12.523,47.135,12.878,45,13.404,44.173z"/> + <path fill="#5491CF" stroke="#2068A3" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617c0.516,1.025,3.617,3.693,3.617,6.617 + c0,5.186-10.27,8.576-16.698,9.145c1.429,4.938,11.372,1.293,13.804-0.313c3.563-2.354,4.563-5.133,7.854-3.705 + C47.754,49.045,48.006,46.574,45.777,43.924z"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + <path fill="none" stroke="#2068A3" stroke-linecap="round" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.623" cy="-2278.646" r="23.425" fx="23.0534" fy="-2281.1357" gradientTransform="matrix(1 0 0 -1 0.2803 -2252.9199)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="5761.7578" y1="11330.6484" x2="5785.3872" y2="11424.0977" gradientTransform="matrix(0.275 0 0 0.2733 -1558.9874 -3088.4209)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M27.958,6.333c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.083,13.952,36.271,6.268,27.958,6.333z"/> + <path id="Hair_Young_Brown_1_" fill="#CC9869" stroke="#99724F" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> + <path fill="#4B4B4B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M28.105,2 + C22.464,2,20.2,4.246,18.13,5.533C29.753,2.865,41.152,10.375,44.46,20.5C44.459,16.875,44.459,2,28.105,2z"/> + <path fill="#9B9B9B" stroke="#4B4B4B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M11.151,17.751 + C12.878,8.25,18.686,6.309,25.273,7.127C31.295,7.875,36.93,10.491,44.459,20.5C37.777,7.125,20.278-3.375,9.903,3.921 + C5.569,6.97,4.903,13.375,11.151,17.751z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + x="0px" y="0px" width="41px" height="48px" viewBox="-0.875 -0.887 41 48" enable-background="new -0.875 -0.887 41 48" + xml:space="preserve"> +<defs> +</defs> +<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-979.1445" x2="682.0508" y2="-979.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_1_)" d="M19.625,36.763C8.787,36.763,0,34.888,0,32.575v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,34.888,30.464,36.763,19.625,36.763z"/> +<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-973.1445" x2="682.0508" y2="-973.1445" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_2_)" d="M19.625,36.763c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.927-18.396,3.927 + c-9.481,0-17.396-1.959-18.396-3.927l-1.229,2C0,34.888,8.787,36.763,19.625,36.763z"/> +<path fill="#3C89C9" d="M19.625,26.468c10.16,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.554,5.438 + c-12.125,0-18.467-2.484-19.541-4.918C-0.127,29.125,9.465,26.468,19.625,26.468z"/> +<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-965.6948" x2="682.0508" y2="-965.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_3_)" d="M19.625,23.313C8.787,23.313,0,21.438,0,19.125v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,21.438,30.464,23.313,19.625,23.313z"/> +<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-959.6948" x2="682.0508" y2="-959.6948" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_4_)" d="M19.625,23.313c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926l-1.229,2C0,21.438,8.787,23.313,19.625,23.313z"/> +<path fill="#3C89C9" d="M19.476,13.019c10.161,0,19.625,2.775,19.625,2.775c-0.375,2.721-5.367,5.438-19.555,5.438 + c-12.125,0-18.467-2.485-19.541-4.918C-0.277,15.674,9.316,13.019,19.476,13.019z"/> +<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-952.4946" x2="682.0508" y2="-952.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#3C89C9"/> + <stop offset="0.1482" style="stop-color:#60A6DD"/> + <stop offset="0.3113" style="stop-color:#81C1F0"/> + <stop offset="0.4476" style="stop-color:#95D1FB"/> + <stop offset="0.5394" style="stop-color:#9CD7FF"/> + <stop offset="0.636" style="stop-color:#98D4FD"/> + <stop offset="0.7293" style="stop-color:#8DCAF6"/> + <stop offset="0.8214" style="stop-color:#79BBEB"/> + <stop offset="0.912" style="stop-color:#5EA5DC"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_5_)" d="M19.625,10.113C8.787,10.113,0,8.238,0,5.925v10c0,2.313,8.787,4.188,19.625,4.188 + c10.839,0,19.625-1.875,19.625-4.188v-10C39.25,8.238,30.464,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="642.8008" y1="-946.4946" x2="682.0508" y2="-946.4946" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="0.0039" style="stop-color:#9DD7FF"/> + <stop offset="0.2273" style="stop-color:#BDE5FF"/> + <stop offset="0.4138" style="stop-color:#D1EEFF"/> + <stop offset="0.5394" style="stop-color:#D9F1FF"/> + <stop offset="0.6155" style="stop-color:#D5EFFE"/> + <stop offset="0.6891" style="stop-color:#C9E7FA"/> + <stop offset="0.7617" style="stop-color:#B6DAF3"/> + <stop offset="0.8337" style="stop-color:#9AC8EA"/> + <stop offset="0.9052" style="stop-color:#77B0DD"/> + <stop offset="0.9754" style="stop-color:#4D94CF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<path fill="url(#SVGID_6_)" d="M19.625,10.113c10.839,0,19.625-1.875,19.625-4.188l-1.229-2c0,2.168-8.235,3.926-18.396,3.926 + c-9.481,0-17.396-1.959-18.396-3.926L0,5.925C0,8.238,8.787,10.113,19.625,10.113z"/> +<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.0293" y1="-943.4014" x2="680.8223" y2="-943.4014" gradientTransform="matrix(1 0 0 -1 -642.8008 -939.4756)"> + <stop offset="0" style="stop-color:#9CD7FF"/> + <stop offset="1" style="stop-color:#3C89C9"/> +</linearGradient> +<ellipse fill="url(#SVGID_7_)" cx="19.625" cy="3.926" rx="18.396" ry="3.926"/> +<path opacity="0.24" fill="#FFFFFF" enable-background="new " d="M31.04,45.982c0,0-4.354,0.664-7.29,0.781 + c-3.125,0.125-8.952,0-8.952,0l-2.384-10.292l0.044-2.108l-1.251-1.154L9.789,23.024l-0.082-0.119L9.5,20.529l-1.65-1.254 + L5.329,8.793c0,0,4.213,0.903,7.234,1.07s8.375,0.25,8.375,0.25l3,9.875l-0.25,1.313l1.063,2.168l2.312,9.645l-0.521,1.416 + l1.46,1.834L31.04,45.982z"/> +</svg> + + + + diff --git a/docs/guide-es/images/request-lifecycle.png b/docs/guide-es/images/request-lifecycle.png new file mode 100644 index 00000000000..e63742c791c Binary files /dev/null and b/docs/guide-es/images/request-lifecycle.png differ diff --git a/docs/guide-es/images/start-app-installed.png b/docs/guide-es/images/start-app-installed.png new file mode 100644 index 00000000000..442f1a22736 Binary files /dev/null and b/docs/guide-es/images/start-app-installed.png differ diff --git a/docs/guide-es/images/start-country-list.png b/docs/guide-es/images/start-country-list.png new file mode 100644 index 00000000000..8eb96ffbaaf Binary files /dev/null and b/docs/guide-es/images/start-country-list.png differ diff --git a/docs/guide-es/images/start-entry-confirmation.png b/docs/guide-es/images/start-entry-confirmation.png new file mode 100644 index 00000000000..f762083ab51 Binary files /dev/null and b/docs/guide-es/images/start-entry-confirmation.png differ diff --git a/docs/guide-es/images/start-form-validation.png b/docs/guide-es/images/start-form-validation.png new file mode 100644 index 00000000000..c2d6f91e439 Binary files /dev/null and b/docs/guide-es/images/start-form-validation.png differ diff --git a/docs/guide-es/images/start-gii-country-grid.png b/docs/guide-es/images/start-gii-country-grid.png new file mode 100644 index 00000000000..391472b3ed9 Binary files /dev/null and b/docs/guide-es/images/start-gii-country-grid.png differ diff --git a/docs/guide-es/images/start-gii-country-update.png b/docs/guide-es/images/start-gii-country-update.png new file mode 100644 index 00000000000..dc00ea71dee Binary files /dev/null and b/docs/guide-es/images/start-gii-country-update.png differ diff --git a/docs/guide-es/images/start-gii-crud-preview.png b/docs/guide-es/images/start-gii-crud-preview.png new file mode 100644 index 00000000000..04288669443 Binary files /dev/null and b/docs/guide-es/images/start-gii-crud-preview.png differ diff --git a/docs/guide-es/images/start-gii-crud.png b/docs/guide-es/images/start-gii-crud.png new file mode 100644 index 00000000000..4012b64dfd1 Binary files /dev/null and b/docs/guide-es/images/start-gii-crud.png differ diff --git a/docs/guide-es/images/start-gii-model-preview.png b/docs/guide-es/images/start-gii-model-preview.png new file mode 100644 index 00000000000..a398c493d26 Binary files /dev/null and b/docs/guide-es/images/start-gii-model-preview.png differ diff --git a/docs/guide-es/images/start-gii-model.png b/docs/guide-es/images/start-gii-model.png new file mode 100644 index 00000000000..bff0881e461 Binary files /dev/null and b/docs/guide-es/images/start-gii-model.png differ diff --git a/docs/guide-es/images/start-gii.png b/docs/guide-es/images/start-gii.png new file mode 100644 index 00000000000..3a1587a64ca Binary files /dev/null and b/docs/guide-es/images/start-gii.png differ diff --git a/docs/guide-es/images/start-hello-world.png b/docs/guide-es/images/start-hello-world.png new file mode 100644 index 00000000000..2772cdc9fb2 Binary files /dev/null and b/docs/guide-es/images/start-hello-world.png differ diff --git a/docs/guide-es/images/tutorial-console-help.png b/docs/guide-es/images/tutorial-console-help.png new file mode 100644 index 00000000000..15b8b66a039 Binary files /dev/null and b/docs/guide-es/images/tutorial-console-help.png differ diff --git a/docs/guide-es/input-file-upload.md b/docs/guide-es/input-file-upload.md new file mode 100644 index 00000000000..ac009a71280 --- /dev/null +++ b/docs/guide-es/input-file-upload.md @@ -0,0 +1,208 @@ +Subir Archivos +============== + +Subir archivos en Yii es normalmente realizado con la ayuda de [[yii\web\UploadedFile]], que encapsula cada archivo subido +en un objeto `UploadedFile`. Combinado con [[yii\widgets\ActiveForm]] y [modelos](structure-models.md), +puedes fácilmente implementar un mecanismo seguro de subida de archivos. + + +## Crear Modelos + +Al igual que al trabajar con entradas de texto plano, para subir un archivo debes crear una clase de modelo y utilizar un atributo +de dicho modelo para mantener la instancia del archivo subido. Debes también declarar una regla para validar la subida del archivo. +Por ejemplo, + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile + */ + public $imageFile; + + public function rules() + { + return [ + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], + ]; + } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } +} +``` + +En el código anterior, el atributo `imageFile` es utilizado para mantener una instancia del archivo subido. Este está asociado con +una regla de validación `file`, que utiliza [[yii\validators\FileValidator]] para asegurarse que el archivo a subir tenga extensión `png` o `jpg`. +El método `upload()` realizará la validación y guardará el archivo subido en el servidor. + +El validador `file` te permite chequear las extensiones, el tamaño, el tipo MIME, etc. Por favor consulta +la sección [Validadores del Framework](tutorial-core-validators.md#file) para más detalles. + +> Tip: Si estás subiendo una imagen, podrías considerar el utilizar el validador `image`. El validador `image` es + implementado a través de [[yii\validators\ImageValidator]], que verifica que un atributo haya recibido una imagen válida + que pueda ser tanto guardada como procesada utilizando la [Extensión Imagine](https://github.com/yiisoft/yii2-imagine). + + +## Renderizar Campos de Subida de Archivos + +A continuación, crea un campo de subida de archivo en la vista: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFile')->fileInput() ?> + + + + +``` + +Es importante recordad que agregues la opción `enctype` al formulario para que el archivo pueda ser subido apropiadamente. +La llamada a `fileInput()` renderizará un tag `` que le permitirá al usuario seleccionar el archivo a subir. + +> Tip: desde la versión 2.0.8, [[yii\widgets\ActiveField::fileInput|fileInput]] agrega la opción `enctype` al formulario + automáticamente cuando se utiliza una campo de subida de archivo. + +## Uniendo Todo + +Ahora, en una acción del controlador, escribe el código que una el modelo y la vista para implementar la subida de archivos: + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // el archivo se subió exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +En el código anterior, cuando se envía el formulario, el método [[yii\web\UploadedFile::getInstance()]] es llamado +para representar el archivo subido como una instancia de `UploadedFile`. Entonces dependemos de la validación del modelo +para asegurarnos que el archivo subido es válido y entonces subirlo al servidor. + + +## Uploading Multiple Files + +También puedes subir varios archivos a la vez, con algunos ajustes en el código de las subsecciones previas. + +Primero debes ajustar la clase del modelo, agregando la opción `maxFiles` en la regla de validación `file` para limitar +el número máximo de archivos a subir. Definir `maxFiles` como `0` significa que no hay límite en el número de archivos +a subir simultáneamente. El número máximo de archivos permitidos para subir simultáneamente está también limitado +por la directiva PHP [`max_file_uploads`](https://www.php.net/manual/es/ini.core.php#ini.max-file-uploads), +cuyo valor por defecto es 20. El método `upload()` debería también ser modificado para guardar los archivos uno a uno. + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile[] + */ + public $imageFiles; + + public function rules() + { + return [ + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], + ]; + } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } +} +``` + +En el archivo de la vista, debes agregar la opción `multiple` en la llamada a `fileInput()` de manera que el campo +pueda recibir varios archivos: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> + + + + +``` + +Y finalmente en la acción del controlador, debes llamar `UploadedFile::getInstances()` en vez de +`UploadedFile::getInstance()` para asignar un array de instancias `UploadedFile` a `UploadForm::imageFiles`. + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // el archivo fue subido exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` diff --git a/docs/guide-es/input-multiple-models.md b/docs/guide-es/input-multiple-models.md new file mode 100644 index 00000000000..366a9746008 --- /dev/null +++ b/docs/guide-es/input-multiple-models.md @@ -0,0 +1,85 @@ +Obtención de datos para los modelos de múltiples +================================ + +Cuando se trata de algunos datos complejos, es posible que puede que tenga que utilizar varios modelos diferentes para recopilar +la entrada del usuario. Por ejemplo, suponiendo que la información de inicio de sesión del usuario se almacena en la tabla `user`, +mientras que el perfil de usuario la información se almacena en la tabla `Profile`, es posible que desee para recoger los datos +de entrada sobre un usuario a través de un modelo `User` y un modelo `Profile`. Con el modelo de Yii y apoyo formulario, +puede solucionar este problema de una manera que no es mucho diferente de la manipulación de un solo modelo. + +En lo que sigue, vamos a mostrar cómo se puede crear un formulario que permitirá recoger datos tanto para los modelos `User` y +`Profile`. + +En primer lugar, la acción del controlador para la recogida de los datos del usuario y del perfil se puede escribir de la +siguiente manera, + +```php +namespace app\controllers; + +use Yii; +use yii\base\Model; +use yii\web\Controller; +use yii\web\NotFoundHttpException; +use app\models\User; +use app\models\Profile; + +class UserController extends Controller +{ + public function actionUpdate($id) + { + $user = User::findOne($id); + if (!$user) { + throw new NotFoundHttpException("The user was not found."); + } + + $profile = Profile::findOne($user->profile_id); + + if (!$profile) { + throw new NotFoundHttpException("The user has no profile."); + } + + $user->scenario = 'update'; + $profile->scenario = 'update'; + + if ($user->load(Yii::$app->request->post()) && $profile->load(Yii::$app->request->post())) { + $isValid = $user->validate(); + $isValid = $profile->validate() && $isValid; + if ($isValid) { + $user->save(false); + $profile->save(false); + return $this->redirect(['user/view', 'id' => $id]); + } + } + + return $this->render('update', [ + 'user' => $user, + 'profile' => $profile, + ]); + } +} +``` + +En la acción `update`, primero cargamos los modelos `User` y `Profile` que se actualicen desde la base de datos. Luego llamamos +[[yii\base\Model::load()]] para llenar estos dos modelos con la entrada del usuario. Si tiene éxito, se validará +los dos modelos y guardarlos. De lo contrario vamos a renderizar la vista `update` que tiene el siguiente contenido: + +```php + 'user-update-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($user, 'username') ?> + + ...other input fields... + + field($profile, 'website') ?> + + 'btn btn-primary']) ?> + +``` + +Como se puede ver, en el `update` vista que haría que los campos de entrada utilizando dos modelos `User` y `Profile`. diff --git a/docs/guide-es/input-validation.md b/docs/guide-es/input-validation.md new file mode 100644 index 00000000000..dd461e355f7 --- /dev/null +++ b/docs/guide-es/input-validation.md @@ -0,0 +1,715 @@ +Validación de Entrada +===================== + +Como regla básica, nunca debes confiar en los datos recibidos de un usuario final y deberías validarlo siempre +antes de ponerlo en uso. + +Dado un [modelo](structure-models.md) poblado con entradas de usuarios, puedes validar esas entradas llamando al +método [[yii\base\Model::validate()]]. Dicho método devolverá un valor booleano indicando si la validación +tuvo éxito o no. En caso de que no, puedes obtener los mensajes de error de la propiedad [[yii\base\Model::errors]]. Por ejemplo, + +```php +$model = new \app\models\ContactForm(); + +// poblar los atributos del modelo desde la entrada del usuario +$model->load(\Yii::$app->request->post()); +// lo que es equivalente a: +// $model->attributes = \Yii::$app->request->post('ContactForm'); + +if ($model->validate()) { + // toda la entrada es válida +} else { + // la validación falló: $errors es un array que contienen los mensajes de error + $errors = $model->errors; +} +``` + + +## Declarar Reglas + +Para hacer que `validate()` realmente funcione, debes declarar reglas de validación para los atributos que planeas validar. +Esto debería hacerse sobrescribiendo el método [[yii\base\Model::rules()]]. El siguiente ejemplo muestra cómo +son declaradas las reglas de validación para el modelo `ContactForm`: + +```php +public function rules() +{ + return [ + // los atributos name, email, subject y body son obligatorios + [['name', 'email', 'subject', 'body'], 'required'], + + // el atributo email debe ser una dirección de email válida + ['email', 'email'], + ]; +} +``` + +El método [[yii\base\Model::rules()|rules()]] debe devolver un array de reglas, la cual cada una +tiene el siguiente formato: + +```php +[ + // requerido, especifica qué atributos deben ser validados por esta regla. + // Para un sólo atributo, puedes utilizar su nombre directamente + // sin tenerlo dentro de un array + ['attribute1', 'attribute2', ...], + + // requerido, especifica de qué tipo es la regla. + // Puede ser un nombre de clase, un alias de validador, o el nombre de un método de validación + 'validator', + + // opcional, especifica en qué escenario/s esta regla debe aplicarse + // si no se especifica, significa que la regla se aplica en todos los escenarios + // Puedes también configurar la opción "except" en caso de que quieras aplicar la regla + // en todos los escenarios salvo los listados + 'on' => ['scenario1', 'scenario2', ...], + + // opcional, especifica atributos adicionales para el objeto validador + 'property1' => 'value1', 'property2' => 'value2', ... +] +``` + +Por cada regla debes especificar al menos a cuáles atributos aplica la regla y cuál es el tipo de la regla. +Puedes especificar el tipo de regla de las siguientes maneras: + +* el alias de un validador propio del framework, tal como `required`, `in`, `date`, etc. Por favor consulta + [Validadores del núcleo](tutorial-core-validators.md) para la lista completa de todos los validadores incluidos. +* el nombre de un método de validación en la clase del modelo, o una función anónima. Consulta la + subsección [Validadores en Línea](#inline-validators) para más detalles. +* el nombre completo de una clase de validador. Por favor consulta la subsección [Validadores Independientes](#standalone-validators) + para más detalles. + +Una regla puede ser utilizada para validar uno o varios atributos, y un atributo puede ser validado por una o varias reglas. +Una regla puede ser aplicada en ciertos [escenarios](structure-models.md#scenarios) con tan sólo especificando la opción `on`. +Si no especificas una opción `on`, significa que la regla se aplicará en todos los escenarios. + +Cuando el método `validate()` es llamado, este sigue los siguientes pasos para realiza la validación: + +1. Determina cuáles atributos deberían ser validados obteniendo la lista de atributos de [[yii\base\Model::scenarios()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estos atributos son llamados *atributos activos*. +2. Determina cuáles reglas de validación deberían ser validados obteniendo la lista de reglas de [[yii\base\Model::rules()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estas reglas son llamadas *reglas activas*. +3. Utiliza cada regla activa para validar cada atributo activo que esté asociado a la regla. + Las reglas de validación son evaluadas en el orden en que están listadas. + +De acuerdo a los pasos de validación mostrados arriba, un atributo será validado si y sólo si +es un atributo activo declarado en `scenarios()` y está asociado a una o varias reglas activas +declaradas en `rules()`. + +> Note: Es práctico darle nombre a las reglas, por ej: +> +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> Puedes utilizarlas en una subclase del modelo: +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + + +### Personalizar Mensajes de Error + +La mayoría de los validadores tienen mensajes de error por defecto que serán agregados al modelo siendo validado cuando sus atributos +fallan la validación. Por ejemplo, el validador [[yii\validators\RequiredValidator|required]] agregará +el mensaje "Username no puede estar vacío." a un modelo cuando falla la validación del atributo `username` al utilizar esta regla. + +Puedes especificar el mensaje de error de una regla especificado la propiedad `message` al declarar la regla, +como a continuación, + +```php +public function rules() +{ + return [ + ['username', 'required', 'message' => 'Por favor escoge un nombre de usuario.'], + ]; +} +``` + +Algunos validadores pueden soportar mensajes de error adicionales para describir más precisamente las causas +del fallo de validación. Por ejemplo, el validador [[yii\validators\NumberValidator|number]] soporta +[[yii\validators\NumberValidator::tooBig|tooBig]] y [[yii\validators\NumberValidator::tooSmall|tooSmall]] +para describir si el fallo de validación es porque el valor siendo validado es demasiado grande o demasiado pequeño, respectivamente. +Puedes configurar estos mensajes de error tal como cualquier otroa propiedad del validador en una regla de validación. + + +### Eventos de Validación + +Cuando el método [[yii\base\Model::validate()]] es llamado, este llamará a dos métodos que puedes sobrescribir para personalizar +el proceso de validación: + +* [[yii\base\Model::beforeValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_BEFORE_VALIDATE]]. + Puedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de pre procesamiento + (por ej. normalizar datos de entrada) antes de que ocurra la validación en sí. El método debe devolver un booleano que indique + si la validación debe continuar o no. +* [[yii\base\Model::afterValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_AFTER_VALIDATE]]. + uedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de post procesamiento después + de completada la validación. + + +### Validación Condicional + +Para validar atributos sólo en determinadas condiciones, por ej. la validación de un atributo depende +del valor de otro atributo puedes utilizar la propiedad [[yii\validators\Validator::when|when]] +para definir la condición. Por ejemplo, + +```php + ['state', 'required', 'when' => function($model) { + return $model->country == 'USA'; + }] +``` + +La propiedad [[yii\validators\Validator::when|when]] toma un método invocable PHP con la siguiente firma: + +```php +/** + * @param Model $model el modelo siendo validado + * @param string $attribute al atributo siendo validado + * @return bool si la regla debe ser aplicada o no + */ +function ($model, $attribute) +``` + +Si también necesitas soportar validación condicional del lado del cliente, debes configurar +la propiedad [[yii\validators\Validator::whenClient|whenClient]], que toma un string que representa una función JavaScript +cuyo valor de retorno determina si debe aplicarse la regla o no. Por ejemplo, + +```php + ['state', 'required', 'when' => function ($model) { + return $model->country == 'USA'; + }, 'whenClient' => "function (attribute, value) { + return $('#country').val() == 'USA'; + }"] +``` + + +### Filtro de Datos + +La entrada del usuario a menudo debe ser filtrada o pre procesada. Por ejemplo, podrías querer eliminar los espacions alrededor +de la entrada `username`. Puedes utilizar reglas de validación para lograrlo. + +Los siguientes ejemplos muestran cómo eliminar esos espacios en la entrada y cómo transformar entradas vacías en `null` utilizando +los validadores del framework [trim](tutorial-core-validators.md#trim) y [default](tutorial-core-validators.md#default): + +```php +return [ + [['username', 'email'], 'trim'], + [['username', 'email'], 'default'], +]; +``` + +También puedes utilizar el validador más general [filter](tutorial-core-validators.md#filter) para realizar filtros +de datos más complejos. + +Como puedes ver, estas reglas de validación no validan la entrada realmente. En cambio, procesan los valores +y los guardan en el atributo siendo validado. + + +### Manejando Entradas Vacías + +Cuando los datos de entrada son enviados desde formularios HTML, a menudo necesitas asignar algunos valores por defecto a las entradas +si estas están vacías. Puedes hacerlo utilizando el validador [default](tutorial-core-validators.md#default). Por ejemplo, + +```php +return [ + // convierte "username" y "email" en `null` si estos están vacíos + [['username', 'email'], 'default'], + + // convierte "level" a 1 si está vacío + ['level', 'default', 'value' => 1], +]; +``` + +Por defecto, una entrada se considera vacía si su valor es un string vacío, un array vacío o `null`. +Puedes personalizar la lógica de detección de valores vacíos configurando la propiedad [[yii\validators\Validator::isEmpty]] +con una función PHP invocable. Por ejemplo, + +```php + ['agree', 'required', 'isEmpty' => function ($value) { + return empty($value); + }] +``` + +> Note: La mayoría de los validadores no manejan entradas vacías si su propiedad [[yii\validators\Validator::skipOnEmpty]] toma + el valor por defecto `true`. Estas serán simplemente salteadas durante la validación si sus atributos asociados reciben una entrada vacía. + Entre los [validadores del framework](tutorial-core-validators.md), sólo `captcha`, `default`, `filter`, + `required`, y `trim` manejarán entradas vacías. + + +## Validación Ad Hoc + +A veces necesitas realizar *validación ad hoc* para valores que no están ligados a ningún modelo. + +Si sólo necesitas realizar un tipo de validación (por ej: validar direcciones de email), podrías llamar +al método [[yii\validators\Validator::validate()|validate()]] de los validadores deseados, como a continuación: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); + +if ($validator->validate($email, $error)) { + echo 'Email válido.'; +} else { + echo $error; +} +``` + +> Note: No todos los validadores soportan este tipo de validación. Un ejemplo es el validador del framework [unique](tutorial-core-validators.md#unique), + que está diseñado para trabajar sólo con un modelo. + +Si necesitas realizar varias validaciones contro varios valores, puedes utilizar [[yii\base\DynamicModel]], +que soporta declarar tanto los atributos como las reglas sobre la marcha. Su uso es como a continuación: + +```php +public function actionSearch($name, $email) +{ + $model = DynamicModel::validateData(compact('name', 'email'), [ + [['name', 'email'], 'string', 'max' => 128], + ['email', 'email'], + ]); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +El método [[yii\base\DynamicModel::validateData()]] crea una instancia de `DynamicModel`, define los atributos +utilizando los datos provistos (`name` e `email` en este ejemplo), y entonces llama a [[yii\base\Model::validate()]] +con las reglas provistas. + +Alternativamente, puedes utilizar la sintaxis más "clásica" para realizar la validación ad hoc: + +```php +public function actionSearch($name, $email) +{ + $model = new DynamicModel(compact('name', 'email')); + $model->addRule(['name', 'email'], 'string', ['max' => 128]) + ->addRule('email', 'email') + ->validate(); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +Después de la validación, puedes verificar si la validación tuvo éxito o no llamando al +método [[yii\base\DynamicModel::hasErrors()|hasErrors()]], obteniendo así los errores de validación de la +propiedad [[yii\base\DynamicModel::errors|errors]], como haces con un modelo normal. +Puedes también acceder a los atributos dinámicos definidos a través de la instancia del modelo, por ej., +`$model->name` y `$model->email`. + + +## Crear Validadores + +Además de los [validadores del framework](tutorial-core-validators.md) incluidos en los lanzamientos de Yii, puedes también +crear tus propios validadores. Puedes crear validadores en línea o validadores independientes. + + +### Validadores en Línea + +Un validador en línea es uno definido en términos del método de un modelo o una función anónima. La firma +del método/función es: + +```php +/** + * @param string $attribute el atributo siendo validado actualmente + * @param mixed $params el valor de los "parámetros" dados en la regla + */ +function ($attribute, $params) +``` + +Si falla la validación de un atributo, el método/función debería llamar a [[yii\base\Model::addError()]] para guardar +el mensaje de error en el modelo de manera que pueda ser recuperado más tarde y presentado a los usuarios finales. + +Debajo hay algunos ejemplos: + +```php +use yii\base\Model; + +class MyForm extends Model +{ + public $country; + public $token; + + public function rules() + { + return [ + // un validador en línea definido como el método del modelo validateCountry() + ['country', 'validateCountry'], + + // un validador en línea definido como una función anónima + ['token', function ($attribute, $params) { + if (!ctype_alnum($this->$attribute)) { + $this->addError($attribute, 'El token debe contener letras y dígitos.'); + } + }], + ]; + } + + public function validateCountry($attribute, $params) + { + if (!in_array($this->$attribute, ['USA', 'Web'])) { + $this->addError($attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +> Note: Por defecto, los validadores en línea no serán aplicados si sus atributos asociados reciben entradas vacías + o si alguna de sus reglas de validación ya falló. Si quieres asegurarte de que una regla siempre sea aplicada, + puedes configurar las reglas [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] y/o [[yii\validators\Validator::skipOnError|skipOnError]] + como `false` en las declaraciones de las reglas. Por ejemplo: +> +> ```php +> [ +> ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false], +> ] +> ``` + + +### Validadores Independientes + +Un validador independiente es una clase que extiende de [[yii\validators\Validator]] o sus sub clases. Puedes implementar +su lógica de validación sobrescribiendo el método [[yii\validators\Validator::validateAttribute()]]. Si falla la validación +de un atributo, llama a [[yii\base\Model::addError()]] para guardar el mensaje de error en el modelo, tal como haces +con los [validadores en línea](#inline-validators). + + +Por ejemplo, el validador en línea de arriba podría ser movida a una nueva clase [[components/validators/CountryValidator]]. + +```php +namespace app\components; + +use yii\validators\Validator; + +class CountryValidator extends Validator +{ + public function validateAttribute($model, $attribute) + { + if (!in_array($model->$attribute, ['USA', 'Web'])) { + $this->addError($model, $attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +Si quieres que tu validador soporte la validación de un valor sin modelo, deberías también sobrescribir +el método[[yii\validators\Validator::validate()]]. Puedes también sobrescribir [[yii\validators\Validator::validateValue()]] +en vez de `validateAttribute()` y `validate()` porque por defecto los últimos dos métodos son implementados +llamando a `validateValue()`. + +Debajo hay un ejemplo de cómo podrías utilizar la clase del validador de arriba dentro de tu modelo. + +```php +namespace app\models; + +use Yii; +use yii\base\Model; +use app\components\validators\CountryValidator; + +class EntryForm extends Model +{ + public $name; + public $email; + public $country; + + public function rules() + { + return [ + [['name', 'email'], 'required'], + ['country', CountryValidator::class], + ['email', 'email'], + ]; + } +} +``` + + +## Validación del Lado del Cliente + +La validación del lado del cliente basada en JavaScript es deseable cuando la entrada del usuario proviene de formularios HTML, dado que +permite a los usuarios encontrar errores más rápido y por lo tanto provee una mejor experiencia. Puedes utilizar o implementar +un validador que soporte validación del lado del cliente *en adición a* validación del lado del servidor. + +> Info: Si bien la validación del lado del cliente es deseable, no es una necesidad. Su principal propósito es proveer al usuario una mejor + experiencia. Al igual que datos de entrada que vienen del los usuarios finales, nunca deberías confiar en la validación del lado del cliente. Por esta razón, + deberías realizar siempre la validación del lado del servidor llamando a [[yii\base\Model::validate()]], como + se describió en las subsecciones previas. + + +### Utilizar Validación del Lado del Cliente + +Varios [validadores del framework](tutorial-core-validators.md) incluyen validación del lado del cliente. Todo lo que necesitas hacer +es solamente utilizar [[yii\widgets\ActiveForm]] para construir tus formularios HTML. Por ejemplo, `LoginForm` mostrado abajo declara dos +reglas: una utiliza el validador del framework [required](tutorial-core-validators.md#required), el cual es soportado tanto en +lado del cliente como del servidor; y el otro usa el validador en línea `validatePassword`, que es sólo soportado de lado +del servidor. + +```php +namespace app\models; + +use yii\base\Model; +use app\models\User; + +class LoginForm extends Model +{ + public $username; + public $password; + + public function rules() + { + return [ + // username y password son ambos requeridos + [['username', 'password'], 'required'], + + // password es validado por validatePassword() + ['password', 'validatePassword'], + ]; + } + + public function validatePassword() + { + $user = User::findByUsername($this->username); + + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Username o password incorrecto.'); + } + } +} +``` + +El formulario HTML creado en el siguiente código contiene dos campos de entrada: `username` y `password`. +Si envias el formulario sin escribir nada, encontrarás que los mensajes de error requiriendo que +escribas algo aparecen sin que haya comunicación alguna con el servidor. + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + + +``` + +Detrás de escena, [[yii\widgets\ActiveForm]] leerá las reglas de validación declaradas en el modelo +y generará el código JavaScript apropiado para los validadores que soportan validación del lado del cliente. Cuando un usuario +cambia el valor de un campo o envia el formulario, se lanzará la validación JavaScript del lado del cliente. + +Si quieres deshabilitar la validación del lado del cliente completamente, puedes configurar +la propiedad [[yii\widgets\ActiveForm::enableClientValidation]] como `false`. También puedes deshabilitar la validación +del lado del cliente de campos individuales configurando su propiedad [[yii\widgets\ActiveField::enableClientValidation]] +como `false`. Cuando `enableClientValidation` es configurado tanto a nivel de campo como a nivel de formulario, +tendrá prioridad la primera. + +### Implementar Validación del Lado del Cliente + + +Para crear validadores que soportan validación del lado del cliente, debes implementar +el método [[yii\validators\Validator::clientValidateAttribute()]], que devuelve una pieza de código JavaScript +que realiza dicha validación. Dentro del código JavaScript, puedes utilizar las siguientes +variables predefinidas: + +- `attribute`: el nombre del atributo siendo validado. +- `value`: el valor siendo validado. +- `messages`: un array utilizado para contener los mensajes de error de validación para el atributo. +- `deferred`: un array con objetos diferidos puede ser insertado (explicado en la subsección siguiente). + +En el siguiente ejemplo, creamos un `StatusValidator` que valida si la entrada es un status válido +contra datos de status existentes. El validador soporta tato tanto validación del lado del servidor como del lado del cliente. + +```php +namespace app\components; + +use yii\validators\Validator; +use app\models\Status; + +class StatusValidator extends Validator +{ + public function init() + { + parent::init(); + $this->message = 'Entrada de Status Inválida.'; + } + + public function validateAttribute($model, $attribute) + { + $value = $model->$attribute; + if (!Status::find()->where(['id' => $value])->exists()) { + $model->addError($attribute, $this->message); + } + } + + public function clientValidateAttribute($model, $attribute, $view) + { + $statuses = json_encode(Status::find()->select('id')->asArray()->column()); + $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return << Tip: El código de arriba muestra principalmente cómo soportar validación del lado del cliente. En la práctica, +> puedes utilizar el validador del framework [in](tutorial-core-validators.md#in) para alcanzar el mismo objetivo. Puedes +> escribir la regla de validación como a como a continuación: +> +> ```php +> [ +> ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], +> ] +> ``` + +> Tip: Si necesitas trabajar con validación del lado del cliente manualmente, por ejemplo, agregar campos dinámicamente o realizar alguna lógica de UI, +> consulta [Trabajar con ActiveForm vía JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) +> en el Yii 2.0 Cookbook. + +### Validación Diferida + +Si necesitas realizar validación del lado del cliente asincrónica, puedes crear [Objetos Diferidos](https://api.jquery.com/category/deferred-object/). +Por ejemplo, para realizar validación AJAX personalizada, puedes utilizar el siguiente código: + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + + deferred.push(def); +JS; +} +``` + +> Note: El método `resolve()` debe ser llamado después de que el atributo ha sido validado. De otra manera la validación + principal del formulario no será completada. + +Por simplicidad, el array `deferred` está equipado con un método de atajo, `add()`, que automáticamente crea un +Objeto Diferido y lo agrega al array `deferred`. Utilizando este método, puedes simplificar el ejemplo de arriba de esta manera, + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + }); +JS; +} +``` + + +### Validación AJAX + +Algunas validaciones sólo pueden realizarse del lado del servidor, debido a que sólo el servidor tiene la información necesaria. +Por ejemplo, para validar si un nombre de usuario es único o no, es necesario revisar la tabla de usuarios del lado del servidor. +Puedes utilizar validación basada en AJAX en este caso. Esta lanzará una petición AJAX de fondo para validar +la entrada mientras se mantiene la misma experiencia de usuario como en una validación del lado del cliente regular. + +Para habilitar la validación AJAX individualmente un campo de entrada, configura la propiedad [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] +de ese campo como `true` y especifica un único `id` de formulario: + +```php +use yii\widgets\ActiveForm; + +$form = ActiveForm::begin([ + 'id' => 'registration-form', +]); + +echo $form->field($model, 'username', ['enableAjaxValidation' => true]); + +// ... + +ActiveForm::end(); +``` + +Para habiliar la validación AJAX en el formulario entero, configura [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] +como `true` a nivel del formulario: + +```php +$form = ActiveForm::begin([ + 'id' => 'contact-form', + 'enableAjaxValidation' => true, +]); +``` + +> Note: Cuando la propiedad `enableAjaxValidation` es configurada tanto a nivel de campo como a nivel de formulario, + la primera tendrá prioridad. + +Necesitas también preparar el servidor para que pueda manejar las peticiones AJAX. +Esto puede alcanzarse con una porción de código como la siguiente en las acciones del controlador: + +```php +if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { + Yii::$app->response->format = Response::FORMAT_JSON; + return ActiveForm::validate($model); +} +``` + +El código de arriba chequeará si la petición actual es AJAX o no. Si lo es, responderá +esta petición ejecutando la validación y devolviendo los errores en formato JSON. + +> Info: Puedes también utilizar [Validación Diferida](#deferred-validation) para realizar validación AJAX. + De todos modos, la característica de validación AJAX descrita aquí es más sistemática y requiere menos esfuerzo de escritura de código. + +Cuando tanto `enableClientValidation` como `enableAjaxValidation` son definidas como `true`, la petición de validación AJAX será lanzada +sólo después de una validación del lado del cliente exitosa. diff --git a/docs/guide-es/intro-upgrade-from-v1.md b/docs/guide-es/intro-upgrade-from-v1.md new file mode 100644 index 00000000000..7a2e5a5c5a6 --- /dev/null +++ b/docs/guide-es/intro-upgrade-from-v1.md @@ -0,0 +1,540 @@ +Actualizar desde Yii 1.1 +======================== + +Existen muchas diferencias entre las versiones 1.1 y 2.0 de Yii ya que el framework fue completamente reescrito +en su segunda versión. +Como resultado, actualizar desde la versión 1.1 no es tan trivial como actualizar entre versiones menores. En esta +guía encontrarás las diferencias más grandes entre estas dos versiones. + +Si no has utilizado Yii 1.1 antes, puedes saltarte con seguridad esta sección e ir directamente a "[Comenzando con Yii](start-installation.md)". + +Es importante anotar que Yii 2.0 introduce más características de las que van a ser cubiertas en este resumen. Es altamente recomendado +que leas a través de toda la guía definitiva para aprender acerca de todas ellas. Hay muchas posibilidades de que algo que hayas desarrollado anteriormente para extender Yii, sea ahora parte del núcleo de la librería. + + +Instalación +------------ + +Yii 2.0 adopta íntegramente [Composer](https://getcomposer.org/), el administrador de paquetes de facto de PHP. +Tanto la instalación del núcleo del framework como las extensiones se manejan a través de Composer. Por favor consulta +la sección [Comenzando con la Aplicación Básica](start-installation.md) para aprender a instalar Yii 2.0. Si quieres crear extensiones +o transformar extensiones de Yii 1.1 para que sean compatibles con Yii 2.0, consulta +la sección [Creando Extensiones](structure-extensions.md#creating-extensions) de la guía. + + +Requerimientos de PHP +--------------------- + +Yii 2.0 requiere PHP 5.4 o mayor, lo que es un gran progreso ya que Yii 1.1 funcionaba con PHP 5.2. +Como resultado, hay muchas diferencias a nivel del lenguaje a las que deberías prestar atención. +Abajo hay un resumen de los mayores cambios en relación a PHP: + +- [Namespaces](https://www.php.net/manual/es/language.namespaces.php). +- [Funciones anónimas](https://www.php.net/manual/es/functions.anonymous.php). +- La sintaxis corta de Arrays `[...elementos...]` es utilizada en vez de `array(...elementos...)`. +- Etiquetas cortas de `echo`. Ahora en las vistas se usa ` 'MyClass', + 'property1' => 'abc', + 'property2' => 'cde', +], [$param1, $param2]); +``` + +Se puede encontrar más detalles acerca del tema en la sección [Configuración](concept-configurations.md). + + +Eventos +------- + +En Yii 1, los eventos eran creados definiendo un método `on` (ej., `onBeforeSave`). En Yii 2, puedes utilizar cualquier nombre de evento. +Ahora puedes disparar un evento utilizando el método [[yii\base\Component::trigger()|trigger()]]: + +```php +$event = new \yii\base\Event; +$component->trigger($eventName, $event); +``` + +Para conectar un manejador a un evento, utiliza el método [[yii\base\Component::on()|on()]]: + +```php +$component->on($eventName, $handler); +// Para desconectar el manejador, utiliza: +// $component->off($eventName, $handler); +``` + +Hay muchas mejoras en lo que respecta a eventos. Para más detalles, consulta la sección [Eventos](concept-events.md). + + +Alias +----- + +Yii 2.0 extiende el uso de alias tanto para archivos/directorios como URLs. Yii 2.0 ahora requiere que cada +alias comience con el carácter `@`, para diferenciarlos de rutas o URLs normales. +Por ejemplo, el alias `@yii` corresponde al directorio donde Yii se encuentra instalado. Los alias +están soportados en la mayor parte del núcleo. Por ejemplo, [[yii\caching\FileCache::cachePath]] puede tomar tanto +una ruta de directorios normal como un alias. + +Un alias está estrechamente relacionado con un namespace de la clase. Se recomienda definir un alias +por cada namespace raíz, y así poder utilizar el autoloader de Yii sin otra configuración. +Por ejemplo, debido a que `@yii` se refiere al directorio de instalación, una clase +como `yii\web\Request` puede ser auto-cargada. Si estás utilizando una librería de terceros, +como Zend Framework, puedes definir un alias `@Zend` que se refiera al directorio de instalación +de ese framework. Una vez realizado esto, Yii será capaz de auto-cargar cualquier clase de Zend Framework también. + +Se puede encontrar más detalles del tema en la sección [Alias](concept-aliases.md). + + +Vistas +------ + +El cambio más significativo con respecto a las vistas en Yii 2 es que la variable especial `$this` dentro de una vista +ya no se refiere al controlador o widget actual. En vez de eso, `$this` ahora se refiere al objeto de la *vista*, un concepto nuevo +introducido en Yii 2.0. El objeto *vista* es del tipo [[yii\web\View]], que representa la parte de las vistas +en el patrón MVC. Si quieres acceder al controlador o al widget correspondiente desde la propia vista, puedes utilizar `$this->context`. + +Para renderizar una vista parcial (partial) dentro de otra vista, se utiliza `$this->render()`, no `$this->renderPartial()`. La llamada a `render` además tiene que ser mostrada explícitamente a través de `echo`, +ya que el método `render()` devuelve el resultado de la renderización en vez de mostrarlo directamente. Por ejemplo: + +```php +echo $this->render('_item', ['item' => $item]); +``` + +Además de utilizar PHP como el lenguaje principal de plantillas (templates), Yii 2.0 está también equipado con soporte +oficial de otros dos motores de plantillas populares: Smarty y Twig. El motor de plantillas de Prado ya no está soportado. +Para utilizar esos motores, necesitas configurar el componente `view` de la aplicación, definiendo la propiedad [[yii\base\View::$renderers|View::$renderers]]. +Por favor consulta la sección [Motores de Plantillas](tutorial-template-engines.md) +para más detalles. + + +Modelos +------- + +Yii 2.0 utiliza [[yii\base\Model]] como modelo base, algo similar a `CModel` en 1.1. +La clase `CFormModel` ha sido descartada por completo. Ahora, en Yii 2 debes extender de [[yii\base\Model]] para crear clases de modelos basados en formularios. + +Yii 2.0 introduce un nuevo método llamado [[yii\base\Model::scenarios()|scenarios()]] para declarar escenarios soportados, +y para indicar bajo que escenario un atributo necesita ser validado, puede ser considerado seguro o no, etc. Por ejemplo: + +```php +public function scenarios() +{ + return [ + 'backend' => ['email', 'role'], + 'frontend' => ['email', '!role'], + ]; +} +``` + +En el ejemplo anterior, se declaran dos escenarios: `backend` y `frontend`. Para el escenario `backend` son considerados seguros +ambos atributos, `email` y `role`, y pueden ser asignados masivamente. Para el escenario `frontend`, `email` puede ser asignado +masivamente mientras `role` no. Tanto `email` como `role` deben ser validados utilizando reglas (rules). + +El método [[yii\base\Model::rules()|rules()]] aún es utilizado para declara reglas de validación. +Ten en cuenta que dada la introducción de [[yii\base\Model::scenarios()|scenarios()]], ya no existe el validador `unsafe`. + +En la mayoría de los casos, no necesitas sobrescribir [[yii\base\Model::scenarios()|scenarios()]] si el método [[yii\base\Model::rules()|rules()]] +especifica completamente los escenarios que existirán, y si no hay necesidad de declarar atributos inseguros (`unsafe`). + +Para aprender más detalles de modelos, consulta la sección [Modelos](structure-models.md). + + +Controladores +------------- + +Yii 2.0 utiliza [[yii\web\Controller]] como controlador base, similar a `CWebController` en Yii 1.1. +[[yii\base\Action]] es la clase base para clases de acciones. + +El impacto más obvio de estos cambios en tu código es que que cada acción del controlador debe devolver el contenido +que quieres mostrar en vez de mostrarlo directamente: + +```php +public function actionView($id) +{ + $model = \app\models\Post::findOne($id); + if ($model) { + return $this->render('view', ['model' => $model]); + } else { + throw new \yii\web\NotFoundHttpException; + } +} +``` + +Por favor, consulta la sección [Controladores](structure-controllers.md) para más detalles acerca de los controladores. + + +Widgets +------- + +Yii 2.0 utiliza [[yii\base\Widget]] como clase base de los widgets, similar a `CWidget` en Yii 1.1. + +Para obtener mejor soporte del framework en IDEs, Yii 2.0 introduce una nueva sintaxis para utilizar widgets. +Los métodos estáticos [[yii\base\Widget::begin()|begin()]], [[yii\base\Widget::end()|end()]], y [[yii\base\Widget::widget()|widget()]] +fueron incorporados, y deben utilizarse así: + +```php +use yii\widgets\Menu; +use yii\widgets\ActiveForm; + +// Ten en cuenta que debes pasar el resultado a "echo" para mostrarlo +echo Menu::widget(['items' => $items]); + +// Pasando un array para inicializar las propiedades del objeto +$form = ActiveForm::begin([ + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']], +]); +... campos del formulario aquí ... +ActiveForm::end(); +``` + +Consulta la sección [Widgets](structure-widgets.md) para más detalles. + + +Temas +------ + +Los temas funcionan completamente diferente en Yii 2.0. Ahora están basados en un mecanismo de mapeo de rutas, +que mapea la ruta de un archivo de la vista de origen a uno con un tema aplicado. Por ejemplo, si el mapeo de ruta de un tema es +`['/web/views' => '/web/themes/basic']`, entonces la versión con el tema aplicado del archivo +`/web/views/site/index.php` será `/web/themes/basic/site/index.php`. Por esta razón, ahora los temas pueden ser +aplicados a cualquier archivo de la vista, incluso una vista renderizada fuera del contexto de un controlador o widget. + +Además, el componente `CThemeManager` ya no existe. En cambio, `theme` es una propiedad configurable del componente `view` +de la aplicación. + +Consulta la sección [Temas](output-theming.md) para más detalles. + + +Aplicaciones de Consola +----------------------- + +Las aplicaciones de consola ahora están organizadas en controladores, tal como aplicaciones Web. Estos controladores +deben extender de [[yii\console\Controller]], similar a `CConsoleCommand` en 1.1. + +Para correr un comando de consola, utiliza `yii `, donde `` se refiere a la ruta del controlador +(ej. `sitemap/index`). Los argumentos anónimos adicionales son pasados como parámetros al método de la acción correspondiente +del controlador, mientras que los argumentos especificados son pasados de acuerdo a las declaraciones en [[yii\console\Controller::options()]]. + +Yii 2.0 soporta la generación automática de información de ayuda de los comandos a través de los bloques de comentarios del archivo. + +Por favor consulta la sección [Comandos de Consola](tutorial-console.md) para más detalles. + + +I18N +---- + +Yii 2.0 remueve el formateador de fecha y números previamente incluido en favor del [módulo de PHP PECL intl](https://pecl.php.net/package/intl). + +La traducción de mensajes ahora es ejecutada vía el componente `i18n` de la aplicación. +Este componente maneja un grupo de mensajes origen, lo que te permite utilizar diferentes mensajes +basados en categorías. + +Por favor, consulta la sección [Internacionalización](tutorial-i18n.md) para más información. + + +Filtros de Acciones +------------------- + +Los filtros de acciones son implementados a través de comportamientos. Para definir un +nuevo filtro personalizado, se debe extender de [[yii\base\ActionFilter]]. Para utilizar el filtro, conecta la clase del filtro +al controlador como un comportamiento. Por ejemplo, para utilizar el filtro [[yii\filters\AccessControl]], deberías tener +el siguiente código en el controlador: + +```php +public function behaviors() +{ + return [ + 'access' => [ + 'class' => 'yii\filters\AccessControl', + 'rules' => [ + ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], + ], + ], + ]; +} +``` + +Consulta la sección [Filtrando](structure-filters.md) para una mayor información acerca del tema. + + +Assets +------ + +Yii 2.0 introduce un nuevo concepto llamado *asset bundle* que reemplaza el concepto de script package encontrado en Yii 1.1. + +Un asset bundle es una colección de archivos assets (ej. archivos JavaScript, archivos CSS, imágenes, etc.) dentro de un directorio. +Cada asset bundle está representado por una clase que extiende de [[yii\web\AssetBundle]]. +Al registrar un asset bundle a través de [[yii\web\AssetBundle::register()]], haces que los assets de dicho bundle sean accesibles +vía Web. A diferencia de Yii 1, la página que registra el bundle contendrá automáticamente las referencias a los archivos +JavaScript y CSS especificados en el bundle. + +Por favor, consulta la sección [Manejando Assets](structure-assets.md) para más detalles. + + +Helpers +------- + +Yii 2.0 introduce muchos helpers estáticos comúnmente utilizados, incluyendo: + +* [[yii\helpers\Html]] +* [[yii\helpers\ArrayHelper]] +* [[yii\helpers\StringHelper]] +* [[yii\helpers\FileHelper]] +* [[yii\helpers\Json]] + +Por favor, consulta la sección [Información General de Helpers](helper-overview.md) para más detalles. + +Formularios +----------- + +Yii 2.0 introduce el concepto de *campo* (field) para construir formularios utilizando [[yii\widgets\ActiveForm]]. Un campo +es un contenedor que consiste en una etiqueta, un input, un mensaje de error y/o texto de ayuda. +Un campo es representado como un objeto [[yii\widgets\ActiveField|ActiveField]]. +Utilizando estos campos, puedes crear formularios más legibles que antes: + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> +
+ +
+ +``` + +Por favor, consulta la sección [Creando Formularios](input-forms.md) para más detalles. + + +Constructor de Consultas +------------------------ + +En Yii 1.1, la generación de consultas a la base de datos estaba dividida en varias clases, incluyendo `CDbCommand`, +`CDbCriteria`, y `CDbCommandBuilder`. Yii 2.0 representa una consulta a la base de datos en términos de un objeto [[yii\db\Query|Query]] +que puede ser convertido en una declaración SQL con la ayuda de [[yii\db\QueryBuilder|QueryBuilder]] detrás de la escena. +Por ejemplo: + +```php +$query = new \yii\db\Query(); +$query->select('id, name') + ->from('user') + ->limit(10); + +$command = $query->createCommand(); +$sql = $command->sql; +$rows = $command->queryAll(); +``` + +Lo mejor de todo, dichos métodos de generación de consultas pueden ser también utilizados mientras se trabaja con [Active Record](db-active-record.md). + +Consulta la sección [Constructor de Consultas](db-query-builder.md) para más detalles. + + +Active Record +------------- + +Yii 2.0 introduce muchísimos cambios con respecto a [Active Record](db-active-record.md). Los dos más obvios se relacionan a +la generación de consultas y al manejo de relaciones. + +La clase de Yii 1.1 `CDbCriteria` es reemplazada por [[yii\db\ActiveQuery]] en Yii 2. Esta clase extiende de [[yii\db\Query]], +y por lo tanto hereda todos los métodos de generación de consultas. +Para comenzar a generar una consulta, llamas al método [[yii\db\ActiveRecord::find()]]: + +```php +// Recibe todos los clientes *activos* y ordenados por su ID: +$customers = Customer::find() + ->where(['status' => $active]) + ->orderBy('id') + ->all(); +``` + +Para declarar una relación, simplemente define un método getter que devuelva un objeto [[yii\db\ActiveQuery|ActiveQuery]]. +El nombre de la propiedad definida en el getter representa el nombre de la relación. Por ejemplo, el siguiente código declara +una relación `orders` (en Yii 1.1, las relaciones se declaraban centralmente en el método `relations()`): + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getOrders() + { + return $this->hasMany('Order', ['customer_id' => 'id']); + } +} +``` + +Ahora puedes utilizar `$customer->orders` para acceder a las órdenes de la tabla relacionada. También puedes utilizar el siguiente +código para realizar una consulta relacional 'sobre la marcha' con una condición personalizada: + +```php +$orders = $customer->getOrders()->andWhere('status=1')->all(); +``` + +Cuando se utiliza la carga temprana (eager loading) de la relación, Yii 2.0 lo hace diferente de 1.1. En particular, en 1.1 una declaración JOIN +sería creada para seleccionar tanto los registros de la tabla primaria como los relacionados. En Yii 2.0, dos declaraciones SQL son ejecutadas +sin utilizar un JOIN: la primera trae todos los modelos primarios, mientras que la segunda trae los registros relacionados +utilizando como condición la clave primaria de los primarios. + +En vez de devolver objetos [[yii\db\ActiveRecord|ActiveRecord]], puedes conectar el método [[yii\db\ActiveQuery::asArray()|asArray()]] +mientras generas una consulta que devuelve un gran número de registros. Esto causará que el resultado de la consulta sea devuelto como +arrays, lo que puede reducir significativamente la necesidad de tiempo de CPU y memoria si el número de registros es grande. +Por ejemplo: + +```php +$customers = Customer::find()->asArray()->all(); +``` + +Otro cambio es que ya no puedes definir valores por defecto a los atributos a través de propiedades públicas. +Si lo necesitaras, debes definirlo en el método `init` de la clase del registro en cuestión. + +```php +public function init() +{ + parent::init(); + $this->status = self::STATUS_NEW; +} +``` + +Anteriormente, solía haber algunos problemas al sobrescribir el constructor de una clase ActiveRecord en 1.1. Estos ya no están presentes en +Yii 2.0. Ten en cuenta que al agregar parámetros al constructor podrías llegar a tener que sobrescribir [[yii\db\ActiveRecord::instantiate()]]. + +Hay muchos otros cambios y mejoras con respecto a ActiveRecord. Por favor, consulta +la sección [Active Record](db-active-record.md) para más detalles. + + +Active Record Behaviors +----------------------- + +En 2.0, hemos eliminado la clase del comportamiento base `CActiveRecordBehavior`. Si desea crear un comportamiento Active Record, usted tendrá que extender directamente de `yii\base\Behavior`. Si la clase de comportamiento debe responder a algunos eventos propios, usted tiene que sobrescribir los métodos `events()` como se muestra a continuación, + +```php +namespace app\components; + +use yii\db\ActiveRecord; +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + // ... + + public function events() + { + return [ + ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + public function beforeValidate($event) + { + // ... + } +} +``` + + +User e IdentityInterface +------------------------ + +La clase `CWebUser` de 1.1 es reemplazada por [[yii\web\User]], y la clase `CUserIdentity` ha dejado de existir. +En cambio, ahora debes implementar [[yii\web\IdentityInterface]] el cual es mucho más directo de usar. +El template de proyecto avanzado provee un ejemplo así. + +Consulta las secciones [Autenticación](security-authentication.md), [Autorización](security-authorization.md), y [Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-es/README.md) para más detalles. + + + +Manejo de URLs +-------------- + +El manejo de URLs en Yii 2 es similar al de 1.1. Una mejora mayor es que el manejador actual ahora soporta parámetros opcionales. +Por ejemplo, si tienes una regla declarada como a continuación, entonces coincidirá tanto con `post/popular` como con `post/1/popular`. +En 1.1, tendrías que haber creado dos reglas diferentes para obtener el mismo resultado + +```php +[ + 'pattern' => 'post//', + 'route' => 'post/index', + 'defaults' => ['page' => 1], +] +``` + +Por favor, consulta la sección [Documentación del Manejo de URLs](runtime-routing.md) para más detalles. + +Un cambio importante en la convención de nombres para rutas es que los nombres en CamelCase de controladores +y acciones ahora son convertidos a minúsculas y cada palabra separada por un guión, por ejemplo el id del controlador +`CamelCaseController` será `camel-case`. +Consulta la sección acerca de [IDs de controladores](structure-controllers.md#controller-ids) y [IDs de acciones](structure-controllers.md#action-ids) para más detalles. + + +Utilizar Yii 1.1 y 2.x juntos +----------------------------- + +Si tienes código en Yii 1.1 que quisieras utilizar junto con Yii 2.0, por favor consulta +la sección [Utilizando Yii 1.1 y 2.0 juntos](tutorial-yii-integration.md). + diff --git a/docs/guide-es/intro-yii.md b/docs/guide-es/intro-yii.md new file mode 100644 index 00000000000..03df67906fa --- /dev/null +++ b/docs/guide-es/intro-yii.md @@ -0,0 +1,60 @@ +¿Qué es Yii? +============ + +Yii es un framework de PHP de alto rendimiento, basado en componentes para desarrollar aplicaciones web +modernas en poco tiempo. El nombre Yii significa "simple y evolutivo" en chino. También se puede considerar como el acrónimo +de _**Yes It Is**_ (que en inglés significa _**Sí, lo es**_)! + + +¿En qué es mejor Yii? +----------------------- + +Yii es un framework genérico de programación web, lo que significa que se puede utilizar para desarrollar todo tipo de aplicaciones web en PHP. +Debido a su arquitectura basada en componentes y a su sofisticada compatibilidad de caché, es especialmente apropiado para el desarrollo +de aplicaciones de gran envergadura, como páginas web, foros, sistemas de gestión de contenidos (CMS), proyectos de comercio electrónico, +servicios web compatibles con la arquitectura REST y muchos más. + + +¿Cómo se compara Yii con otros frameworks? +-------------------------------------- + +Si estás ya familiarizado con otros framework, puedes apreciar como se compara Yii con ellos: + +- Como la mayoría de los framework de PHP, Yii implementa el patrón de diseño MVC (Modelo-Vista-Controlador) y + promueve la organización de código basada en este patrón. +- La filosofía de Yii consiste en escribir el código de manera simple y elegante, sin sobrediseñar nunca por el + mero hecho de seguir un patrón de diseño determinado. +- Yii es un framework completo (full stack) que provee muchas características probadas y listas para usar, como los +constructores de consultas y la clase ActiveRecord para las bases de datos relacionales y NoSQL, +la compatibilidad con la arquitectura REST para desarrollar API, la compatibilidad de caché en varios niveles +y muchas más. +- Yii es extremadamente extensible. Puedes personalizar o reemplazar prácticamente cualquier pieza de código de base, +como se puede también aprovechar su sólida arquitectura de extensiones para utilizar o desarrollar extensiones distribuibles. +- El alto rendimiento es siempre la meta principal de Yii. + +Yii no es un proyecto de un sola persona, detrás de Yii hay un [sólido equipo de desarrollo](https://www.yiiframework.com/team/), +así como una gran comunidad en la que numerosos profesionales contribuyen constantemente a su desarrollo. +El equipo de desarrollo de Yii se mantiene atento a las últimas tendencias de desarrollo web, así como a las mejores prácticas y características de otros frameworks y proyectos. +Las buenas prácticas y características más relevantes de otros proyectos se incorporan regularmente a la base del framework y se exponen a través de interfaces simples y elegantes. + +[about_yii]: https://www.yiiframework.com/about/ + +Versiones de Yii +---------------- + +Actualmente existen dos versiones principales de Yii: la versión 1.1 y la versión 2.0. Para la versión 1.1, que es de la generación anterior, actualmente solo se ofrece mantenimiento. +La versión 2.0 está completamente reescrita y adopta las últimas tecnologías y protocolos, incluidos Composer, PSR, namespaces, traits, etc. +La versión 2.0 representa la actual generación del framework y su desarrollo recibirá el principal esfuerzo en los próximos años. +Esta guía está basada principalmente en la versión 2.0. del framework. + + +Requisitos y Prerequisitos +-------------------------- + +Yii 2.0 requiere PHP 7.4.0 o una versión posterior y corre de mejor manera en la última versión de PHP. Se pueden encontrar requisitos más detallados de características individuales +ejecutando el script de comprobación incluido en cada lanzamiento de Yii. + +Para utilizar Yii se requieren conocimientos básicos de programación orientada a objetos (POO), porque el framework Yii se basa íntegramente en esta tecnología. +Yii 2.0 hace uso también de las últimas características de PHP, como [namespaces](https://www.php.net/manual/es/language.namespaces.php) +y [traits](https://www.php.net/manual/es/language.oop5.traits.php). Comprender estos conceptos te ayudará a entender mejor Yii 2.0. + diff --git a/docs/guide-es/output-client-scripts.md b/docs/guide-es/output-client-scripts.md new file mode 100644 index 00000000000..a21963fddc6 --- /dev/null +++ b/docs/guide-es/output-client-scripts.md @@ -0,0 +1,98 @@ +Trabajar con Scripts del Cliente +================================ + +> Note: Esta sección se encuentra en desarrollo. + +### Registrar scripts + +Con el objeto [[yii\web\View]] puedes registrar scripts. Hay dos métodos dedicados a esto: +[[yii\web\View::registerJs()|registerJs()]] para scripts en línea +[[yii\web\View::registerJsFile()|registerJsFile()]] para scripts externos. +Los scripts en línea son útiles para configuración y código generado dinámicamente. +El método para agregarlos puede ser utilizado así: + +```php +$this->registerJs("var options = ".json_encode($options).";", View::POS_END, 'my-options'); +``` + +El primer argumento es el código JS real que queremos insertar en la página. El segundo argumento +determina en qué parte de la página debería ser insertado el script. Los valores posibles son: + +- [[yii\web\View::POS_HEAD|View::POS_HEAD]] para la sección head. +- [[yii\web\View::POS_BEGIN|View::POS_BEGIN]] justo después de la etiqueta ``. +- [[yii\web\View::POS_END|View::POS_END]] justo antes de cerrar la etiqueta ``. +- [[yii\web\View::POS_READY|View::POS_READY]] para ejecutar código en el evento `ready` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. +- [[yii\web\View::POS_LOAD|View::POS_LOAD]] para ejecutar código en el evento `load` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. + +El último argumento es un ID único del script, utilizado para identificar el bloque de código y reemplazar otro con el mismo ID +en vez de agregar uno nuevo. En caso de no proveerlo, el código JS en sí será utilizado como ID. + +Un script externo puede ser agregado de esta manera: + +```php +$this->registerJsFile('https://example.com/js/main.js', ['depends' => [\yii\web\JqueryAsset::class]]); +``` + +Los argumentos para [[yii\web\View::registerJsFile()|registerJsFile()]] son similares a los de +[[yii\web\View::registerCssFile()|registerCssFile()]]. En el ejemplo anterior, +registramos el archivo `main.js` con dependencia de `JqueryAsset`. Esto quiere decir que el archivo `main.js` +será agregado DESPUÉS de `jquery.js`. Si esta especificación de dependencia, el orden relativo entre +`main.js` y `jquery.js` sería indefinido. + +Como para [[yii\web\View::registerCssFile()|registerCssFile()]], es altamente recomendable que utilices +[asset bundles](structure-assets.md) para registrar archivos JS externos más que utilizar [[yii\web\View::registerJsFile()|registerJsFile()]]. + + +### Registrar asset bundles + +Como mencionamos anteriormente, es preferible utilizar asset bundles en vez de usar CSS y JavaScript directamente. Puedes obtener detalles +de cómo definir asset bundles en la sección [gestor de assets](structure-assets.md) de esta guía. Utilizar asset bundles +ya definidos es muy sencillo: + +```php +\frontend\assets\AppAsset::register($this); +``` + + + +### Registrar CSS + +Puedes registrar CSS utilizando [[yii\web\View::registerCss()|registerCss()]] o [[yii\web\View::registerCssFile()|registerCssFile()]]. +El primero registra un bloque de código CSS mientras que el segundo registra un archivo CSS externo. Por ejemplo, + +```php +$this->registerCss("body { background: #f00; }"); +``` + +El código anterior dará como resultado que se agregue lo siguiente a la sección head de la página: + +```html + +``` + +Si quieres especificar propiedades adicionales a la etiqueta style, pasa un array de claves-valores como tercer argumento. +Si necesitas asegurarte que haya sólo una etiqueta style utiliza el cuarto argumento como fue mencionado en las descripciones de meta etiquetas. + +```php +$this->registerCssFile("https://example.com/css/themes/black-and-white.css", [ + 'depends' => [BootstrapAsset::class], + 'media' => 'print', +], 'css-print-theme'); +``` + +El código de arriba agregará un link al archivo CSS en la sección head de la página. + +* El primer argumento especifica el archivo CSS a ser registrado. +* El segundo argumento especifica los atributos HTML de la etiqueta `` resultante. La opción `depends` + es especialmente tratada. Esta especifica de qué asset bundles depende este archivo CSS. En este caso, depende + del asset bundle [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. Esto significa que el archivo CSS será agregado + *después* de los archivos CSS de [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. +* El último argumento especifica un ID que identifica al archivo CSS. Si no es provisto, se utilizará la URL + del archivo. + + +Es altamente recomendable que ustilices [asset bundles](structure-assets.md) para registrar archivos CSS en vez de +utilizar [[yii\web\View::registerCssFile()|registerCssFile()]]. Utilizar asset bundles te permite combinar y comprimir +varios archivos CSS, deseable en sitios web de tráfico alto. diff --git a/docs/guide-es/output-data-providers.md b/docs/guide-es/output-data-providers.md new file mode 100644 index 00000000000..0802ee0aa34 --- /dev/null +++ b/docs/guide-es/output-data-providers.md @@ -0,0 +1,421 @@ +Proveedores de datos +==================== + +En las secciones sobre [paginación](output-pagination.md) y [ordenación](output-sorting.md) se +describe como permitir a los usuarios finales elegir que se muestre una página de datos en +particular, y ordenar los datos por algunas columnas. Como la tarea de paginar y ordenar datos +es muy común, Yii proporciona un conjunto de clases *proveedoras de datos* para encapsularla. + +Un proveedor de datos es una clase que implementa la interfaz [[yii\data\DataProviderInterface]]. +Básicamente se encarga de obtener datos paginados y ordenados. Normalmente se usa junto con +[_widgets_ de datos](output-data-widgets.md) para que los usuarios finales puedan paginar y +ordenar datos de forma interactiva. + +Yii incluye las siguientes clases proveedoras de datos: + +* [[yii\data\ActiveDataProvider]]: usa [[yii\db\Query]] o [[yii\db\ActiveQuery]] para consultar datos de bases de datos y devolverlos como _arrays_ o instancias [Active Record](db-active-record.md). +* [[yii\data\SqlDataProvider]]: ejecuta una sentencia SQL y devuelve los datos de la base de datos como _arrays_. +* [[yii\data\ArrayDataProvider]]: toma un _array_ grande y devuelve una rodaja de él basándose en las especificaciones de paginación y ordenación. + +El uso de todos estos proveedores de datos comparte el siguiente patrón común: + +```php +// Crear el proveedor de datos configurando sus propiedades de paginación y ordenación +$provider = new XyzDataProvider([ + 'pagination' => [...], + 'sort' => [...], +]); + +// Obtener los datos paginados y ordenados +$models = $provider->getModels(); + +// Obtener el número de elementos de la página actual +$count = $provider->getCount(); + +// Obtener el número total de elementos entre todas las páginas +$totalCount = $provider->getTotalCount(); +``` + +Se puede especificar los comportamientos de paginación y ordenación de un proveedor de datos +configurando sus propiedades [[yii\data\BaseDataProvider::pagination|pagination]] y +[[yii\data\BaseDataProvider::sort|sort]], que corresponden a las configuraciones para +[[yii\data\Pagination]] y [[yii\data\Sort]] respectivamente. También se pueden configurar a +`false` para inhabilitar las funciones de paginación y/u ordenación. + +Los [_widgets_ de datos](output-data-widgets.md), como [[yii\grid\GridView]], tienen una +propiedad llamada `dataProvider` que puede tomar una instancia de un proveedor de datos y +mostrar los datos que proporciona. Por ejemplo, + +```php +echo yii\grid\GridView::widget([ + 'dataProvider' => $dataProvider, +]); +``` + +Estos proveedores de datos varían principalmente en la manera en que se especifica la fuente de +datos. En las siguientes secciones se explica el uso detallado de cada uno de estos proveedores +de datos. + + +## Proveedor de datos activo + +Para usar [[yii\data\ActiveDataProvider]], hay que configurar su propiedad +[[yii\data\ActiveDataProvider::query|query]]. +Puede tomar un objeto [[yii\db\Query] o [[yii\db\ActiveQuery]]. En el primer caso, los datos +devueltos serán _arrays_. En el segundo, los datos devueltos pueden ser _arrays_ o instancias de +[Active Record](db-active-record.md). Por ejemplo: + + +```php +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['state_id' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ], + 'sort' => [ + 'defaultOrder' => [ + 'created_at' => SORT_DESC, + 'title' => SORT_ASC, + ] + ], +]); + +// Devuelve un array de objetos Post +$posts = $provider->getModels(); +``` + +En el ejemplo anterior, si `$query` se crea el siguiente código, el proveedor de datos +devolverá _arrays_ en bruto. + +```php +use yii\db\Query; + +$query = (new Query())->from('post')->where(['state' => 1]); +``` + +> Note: Si una consulta ya tiene la cláusula `orderBy`, las nuevas instrucciones de ordenación + dadas por los usuarios finales (mediante la configuración de `sort`) se añadirán a la cláusula + `orderBy` previa. Las cláusulas `limit` y `offset` que pueda haber se sobrescribirán por la + petición de paginación de los usuarios finales (mediante la configuración de `pagination`). + +Por omisión, [[yii\data\ActiveDataProvider]] usa el componente `db` de la aplicación como +conexión con la base de datos. Se puede indicar una conexión con base de datos diferente +configurando la propiedad [[yii\data\ActiveDataProvider::db]]. + + +## Proveedor de datos SQL + +[[yii\data\SqlDataProvider]] funciona con una sentencia SQL en bruto, que se usa para obtener +los datos requeridos. +Basándose en las especificaciones de [[yii\data\SqlDataProvider::sort|sort]] y +[[yii\data\SqlDataProvider::pagination|pagination]], el proveedor ajustará las cláusulas +`ORDER BY` y `LIMIT` de la sentencia SQL acordemente para obtener sólo la página de datos +solicitados en el orden deseado. + +Para usar [[yii\data\SqlDataProvider]], hay que especificar las propiedades +[[yii\data\SqlDataProvider::sql|sql]] y [[yii\data\SqlDataProvider::totalCount|totalCount]]. +Por ejemplo: + +```php +use yii\data\SqlDataProvider; + +$count = Yii::$app->db->createCommand(' + SELECT COUNT(*) FROM post WHERE status=:status +', [':status' => 1])->queryScalar(); + +$provider = new SqlDataProvider([ + 'sql' => 'SELECT * FROM post WHERE status=:status', + 'params' => [':status' => 1], + 'totalCount' => $count, + 'pagination' => [ + 'pageSize' => 10, + ], + 'sort' => [ + 'attributes' => [ + 'title', + 'view_count', + 'created_at', + ], + ], +]); + +// Devuelve un array de filas de datos +$models = $provider->getModels(); +``` + +> Info: La propiedad [[yii\data\SqlDataProvider::totalCount|totalCount]] se requiere sólo si se + necesita paginar los datos. Esto es porque el proveedor modificará la sentencia SQL + especificada vía [[yii\data\SqlDataProvider::sql|sql]] para que devuelva sólo la pagina de + datos solicitada. El proveedor sigue necesitando saber el número total de elementos de datos + para calcular correctamente el número de páginas. + + +## Proveedor de datos de _arrays_ + +Se recomienda usar [[yii\data\ArrayDataProvider]] cuando se trabaja con un _array_ grande. +El proveedor permite devolver una página de los datos del _array_ ordenados por una o varias +columnas. Para usar [[yii\data\ArrayDataProvider]], hay que especificar la propiedad +[[yii\data\ArrayDataProvider::allModels|allModels]] como el _array_ grande. Los elementos +del _array_ grande pueden ser _arrays_ asociativos (por ejemplo resultados de consultas de +[DAO](db-dao.md) u objetos (por ejemplo instancias de [Active Record](db-active-record.md). +Por ejemplo: + +```php +use yii\data\ArrayDataProvider; + +$data = [ + ['id' => 1, 'name' => 'name 1', ...], + ['id' => 2, 'name' => 'name 2', ...], + ... + ['id' => 100, 'name' => 'name 100', ...], +]; + +$provider = new ArrayDataProvider([ + 'allModels' => $data, + 'pagination' => [ + 'pageSize' => 10, + ], + 'sort' => [ + 'attributes' => ['id', 'name'], + ], +]); + +// Obtener las filas de la página solicitada +$rows = $provider->getModels(); +``` + +> Note: En comparación con [Active Data Provider](#active-data-provider) y + [SQL Data Provider](#sql-data-provider), Array Data Provider es menos eficiente porque + requiere cargar *todos* los datos en memoria. + + +## Trabajar con las claves de los datos + +Al utilizar los elementos de datos devueltos por un proveedor de datos, con frecuencia +necesita identificar cada elemento de datos con una clave única. +Por ejemplo, si los elementos de datos representan información de los clientes, puede querer +usar el ID de cliente como la clave de cada conjunto de datos de un cliente. +Los proveedores de datos pueden devolver una lista de estas claves correspondientes a los +elementos de datos devueltos por [[yii\data\DataProviderInterface::getModels()]]. +Por ejemplo: + +```php +use yii\data\ActiveDataProvider; + +$query = Post::find()->where(['status' => 1]); + +$provider = new ActiveDataProvider([ + 'query' => $query, +]); + +// Devuelve un array de objetos Post +$posts = $provider->getModels(); + +// Devuelve los valores de las claves primarias correspondientes a $posts +$ids = $provider->getKeys(); +``` + +En el ejemplo superior, como se le proporciona a [[yii\data\ActiveDataProvider]] un objeto +[[yii\db\ActiveQuery]], es lo suficientemente inteligente como para devolver los valores de +las claves primarias como las claves. También puede indicar explícitamente cómo se deben +calcular los valores de la clave configurando [[yii\data\ActiveDataProvider::key]] con un +nombre de columna o un invocable que calcule los valores de la clave. Por ejemplo: + +```php +// Utiliza la columna «slug» como valores de la clave +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => 'slug', +]); + +// Utiliza el resultado de md5(id) como valores de la clave +$provider = new ActiveDataProvider([ + 'query' => Post::find(), + 'key' => function ($model) { + return md5($model->id); + } +]); +``` + + +## Creación de un proveedor de datos personalizado + +Para crear su propio proveedor de datos personalizado, debe implementar +[[yii\data\DataProviderInterface]]. +Una manera más fácil es extender [[yii\data\BaseDataProvider]], que le permite centrarse +en la lógica central del proveedor de datos. En particular, esencialmente necesita +implementar los siguientes métodos: + +- [[yii\data\BaseDataProvider::prepareModels()|prepareModels()]]: prepara los modelos + de datos que estarán disponibles en la página actual y los devuelve como un _array_. +- [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: acepta un _array_ de + modelos de datos disponibles actualmente y devuelve las claves asociadas a ellos. +- [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: devuelve un valor + que indica el número total de modelos de datos en el proveedor de datos. + +Debajo se muestra un ejemplo de un proveedor de datos que lee datos CSV eficientemente: + +```php +fileObject = new SplFileObject($this->filename); + } + + /** + * {@inheritdoc} + */ + protected function prepareModels() + { + $models = []; + $pagination = $this->getPagination(); + + if ($pagination === false) { + // En caso de que no haya paginación, leer todas las líneas + while (!$this->fileObject->eof()) { + $models[] = $this->fileObject->fgetcsv(); + $this->fileObject->next(); + } + } else { + // En caso de que haya paginación, leer sólo una única página + $pagination->totalCount = $this->getTotalCount(); + $this->fileObject->seek($pagination->getOffset()); + $limit = $pagination->getLimit(); + + for ($count = 0; $count < $limit; ++$count) { + $models[] = $this->fileObject->fgetcsv(); + $this->fileObject->next(); + } + } + + return $models; + } + + /** + * {@inheritdoc} + */ + protected function prepareKeys($models) + { + if ($this->key !== null) { + $keys = []; + + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + + return $keys; + } + + return array_keys($models); + } + + /** + * {@inheritdoc} + */ + protected function prepareTotalCount() + { + $count = 0; + + while (!$this->fileObject->eof()) { + $this->fileObject->next(); + ++$count; + } + + return $count; + } +} +``` + +## Filtrar proveedores de datos usando filtros de datos + +Si bien puede construir condiciones para un proveedor de datos activo manualmente tal +y como se describe en las secciones [Filtering Data](output-data-widgets.md#filtering-data) +y [Separate Filter Form](output-data-widgets.md#separate-filter-form) de la guía de +_widgets_ de datos, Yii tiene filtros de datos que son muy útiles si necesita +condiciones de filtro flexibles. Los filtros de datos se pueden usar así: + +```php +$filter = new ActiveDataFilter([ + 'searchModel' => 'app\models\PostSearch' +]); + +$filterCondition = null; + +// Puede cargar los filtros de datos de cualquier fuente. +// Por ejemplo, si prefiere JSON en el cuerpo de la petición, +// use Yii::$app->request->getBodyParams() aquí abajo: +if ($filter->load(\Yii::$app->request->get())) { + $filterCondition = $filter->build(); + if ($filterCondition === false) { + // Serializer recibiría errores + return $filter; + } +} + +$query = Post::find(); +if ($filterCondition !== null) { + $query->andWhere($filterCondition); +} + +return new ActiveDataProvider([ + 'query' => $query, +]); +``` + +El propósito del modelo `PostSearch` es definir por qué propiedades y valores se permite filtrar: + +```php +use yii\base\Model; + +class PostSearch extends Model +{ + public $id; + public $title; + + public function rules() + { + return [ + ['id', 'integer'], + ['title', 'string', 'min' => 2, 'max' => 200], + ]; + } +} +``` + +Los filtros de datos son bastante flexibles. Puede personalizar cómo se construyen +las condiciones y qué operadores se permiten. +Para más detalles consulte la documentación de la API en [[\yii\data\DataFilter]]. diff --git a/docs/guide-es/output-data-widgets.md b/docs/guide-es/output-data-widgets.md new file mode 100644 index 00000000000..06db9fe4b69 --- /dev/null +++ b/docs/guide-es/output-data-widgets.md @@ -0,0 +1,245 @@ +Widgets de datos +================ + +Yii proporciona un conjunto de [widgets](structure-widgets.md) que se pueden usar para mostrar datos. +Mientras que el _widget_ [DetailView](#detail-view) se puede usar para mostrar los datos de un único +registro, [ListView](#list-view) y [GridView](#grid-view) se pueden usar para mostrar una lista o +tabla de registros de datos proporcionando funcionalidades como paginación, ordenación y filtro. + + +DetailView +---------- + +El _widget_ [[yii\widgets\DetailView|DetailView]] muestra los detalles de un único +[[yii\widgets\DetailView::$model|modelo]] de datos. + +Se recomienda su uso para mostrar un modelo en un formato estándar (por ejemplo, cada atributo del +modelo se muestra como una fila en una tabla). El modelo puede ser tanto una instancia o subclase +de [[\yii\base\Model]] como un [active record](db-active-record.md) o un _array_ asociativo. + +DetailView usa la propiedad [[yii\widgets\DetailView::$attributes|$attributes]] para determinar +qué atributos del modelo se deben mostrar y cómo se deben formatear. +En la [sección sobre formateadores](output-formatting.html) se pueden ver las opciones de formato +disponibles. + +Un uso típico de DetailView sería así: + +```php +echo DetailView::widget([ + 'model' => $model, + 'attributes' => [ + 'title', // atributo title (en texto plano) + 'description:html', // atributo description formateado como HTML + [ // nombre del propietario del modelo + 'label' => 'Owner', + 'value' => $model->owner->name, + 'contentOptions' => ['class' => 'bg-red'], // atributos HTML para personalizar el valor + 'captionOptions' => ['tooltip' => 'Tooltip'], // atributos HTML para personalizar la etiqueta + ], + 'created_at:datetime', // fecha de creación formateada como datetime + ], +]); +``` + +Recuerde que a diferencia de [[yii\widgets\GridView|GridView]], que procesa un conjunto de modelos, +[[yii\widgets\DetailView|DetailView]] sólo procesa uno. Así que la mayoría de las veces no hay +necesidad de usar funciones anónimas ya que `$model` es el único modelo a mostrar y está disponible +en la vista como una variable. + +Sin embargo, en algunos casos el uso de una función anónima puede ser útil. Por ejemplo cuando +`visible` está especificado y se desea impedir el cálculo de `value` en case de que evalúe a `false`: + +```php +echo DetailView::widget([ + 'model' => $model, + 'attributes' => [ + [ + 'attribute' => 'owner', + 'value' => function ($model) { + return $model->owner->name; + }, + 'visible' => \Yii::$app->user->can('posts.owner.view'), + ], + ], +]); +``` + + +ListView +-------- + +El _widget_ [[yii\widgets\ListView|ListView]] se usa para mostrar datos de un +[proveedor de datos](output-data-providers.md). +Cada modelo de datos se representa usando el [[yii\widgets\ListView::$itemView|fichero de vista]] +indicado. +Como proporciona de serie funcionalidades tales como paginación, ordenación y filtro, +es útil tanto para mostrar información al usuario final como para crear una interfaz +de usuario de gestión de datos. + +Un uso típico es el siguiente: + +```php +use yii\widgets\ListView; +use yii\data\ActiveDataProvider; + +$dataProvider = new ActiveDataProvider([ + 'query' => Post::find(), + 'pagination' => [ + 'pageSize' => 20, + ], +]); + +echo ListView::widget([ + 'dataProvider' => $dataProvider, + 'itemView' => '_post', +]); +``` + +El fichero de vista `_post` podría contener lo siguiente: + +```php + +
+

title) ?>

+ + text) ?> +
+``` + +En el fichero de vista anterior, el modelo de datos actual está disponible como `$model`. +Además están disponibles las siguientes variables: + +- `$key`: mixto, el valor de la clave asociada a este elemento de datos. +- `$index`: entero, el índice empezando por cero del elemento de datos en el array de elementos devuelto por el proveedor de datos. +- `$widget`: ListView, esta instancia del _widget_. + +Si se necesita pasar datos adicionales a cada vista, se puede usar la propiedad +[[yii\widgets\ListView::$viewParams|$viewParams]] para pasar parejas clave-valor como las siguientes: + +```php +echo ListView::widget([ + 'dataProvider' => $dataProvider, + 'itemView' => '_post', + 'viewParams' => [ + 'fullView' => true, + 'context' => 'main-page', + // ... + ], +]); +``` + +Entonces éstas también estarán disponibles en la vista como variables. + + +GridView +-------- + +La cuadrícula de datos o [[yii\grid\GridView|GridView]] es uno de los _widgets_ de Yii +más potentes. Es extremadamente útil si necesita construir rápidamente la sección de +administración del sistema. Recibe los datos de un [proveedor de datos](output-data-providers.md) +y representa cada fila usando un conjunto de [[yii\grid\GridView::columns|columnas]] +que presentan los datos en forma de tabla. + +Cada fila de la tabla representa los datos de un único elemento de datos, y una columna +normalmente representa un atributo del elemento (algunas columnas pueden corresponder a +expresiones complejas de los atributos o a un texto estático). + +El mínimo código necesario para usar GridView es como sigue: + +```php +use yii\grid\GridView; +use yii\data\ActiveDataProvider; + +$dataProvider = new ActiveDataProvider([ + 'query' => Post::find(), + 'pagination' => [ + 'pageSize' => 20, + ], +]); +echo GridView::widget([ + 'dataProvider' => $dataProvider, +]); +``` + +El código anterior primero crea un proveedor de datos y a continuación usa GridView +para mostrar cada atributo en cada fila tomados del proveedor de datos. La tabla +mostrada está equipada de serie con las funcionalidades de ordenación y paginación. + + +### Columnas de la cuadrícula + +Las columnas de la tabla se configuran en términos de clase [[yii\grid\Column]], que +se configuran en la propiedad [[yii\grid\GridView::columns|columns]] de la configuración +del GridView. +Dependiendo del tipo y ajustes de las columnas éstas pueden presentar los datos de +diferentes maneras. +La clase predefinida es [[yii\grid\DataColumn]], que representa un atributo del modelo +por el que se puede ordenar y filtrar. + + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + // Columnas sencillas definidas por los datos contenidos en $dataProvider. + // Se usarán los datos de la columna del modelo. + 'id', + 'username', + // Un ejemplo más complejo. + [ + 'class' => 'yii\grid\DataColumn', // Se puede omitir, ya que es la predefinida. + 'value' => function ($data) { + return $data->name; // $data['name'] para datos de un array, por ejemplo al usar SqlDataProvider. + }, + ], + ], +]); +``` + +Observe que si no se especifica la parte [[yii\grid\GridView::columns|columns]] de la +configuración, Yii intenta mostrar todas las columnas posibles del modelo del proveedor +de datos. + + +### Clases de columna + +Las columnas de la cuadrícula se pueden personalizar usando diferentes clases de columna: + +```php +echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'columns' => [ + [ + 'class' => 'yii\grid\SerialColumn', // <-- aquí + // puede configurar propiedades adicionales aquí + ], +``` + +Además de las clases de columna proporcionadas por Yii que se revisarán más abajo, +puede crear sus propias clases de columna. + +Cada clase de columna extiende [[yii\grid\Column]] de modo que hay algunas opciones +comunes que puede establecer al configurar las columnas de una cuadrícula. + +- [[yii\grid\Column::header|header]] permite establecer el contenida para la fila cabecera +- [[yii\grid\Column::footer|footer]] permite establece el contenido de la fila al pie +- [[yii\grid\Column::visible|visible]] define si la columna debería ser visible. +- [[yii\grid\Column::content|content]] le permite pasar una función PHP válida que devuelva datos para una fila. El formato es el siguiente: + + ```php + function ($model, $key, $index, $column) { + return 'una cadena'; + } + ``` + +Puede indicar varias opciones HTML del contenedor pasando _arrays_ a: + +- [[yii\grid\Column::headerOptions|headerOptions]] +- [[yii\grid\Column::footerOptions|footerOptions]] +- [[yii\grid\Column::filterOptions|filterOptions]] +- [[yii\grid\Column::contentOptions|contentOptions]] + diff --git a/docs/guide-es/output-pagination.md b/docs/guide-es/output-pagination.md new file mode 100644 index 00000000000..9f70cb0ad11 --- /dev/null +++ b/docs/guide-es/output-pagination.md @@ -0,0 +1,72 @@ +Paginación +========== + +Cuando hay muchos datos a mostrar en una sola página, una estrategia común es mostrarlos en varias +páginas y en cada una de ellas mostrar sólo una pequeña porción de datos. Esta estrategia es conocida como *paginación*. + +Yii utiliza el objeto [[yii\data\Pagination]] para representar la información acerca del esquema de paginación. En particular, + +* [[yii\data\Pagination::$totalCount|cuenta total]] especifica el número total de ítems de datos. Ten en cuenta que + este es normalmente un número mucho mayor que el número de ítems necesarios a mostrar en una simple página. +* [[yii\data\Pagination::$pageSize|tamaño de página]] especifica cuántos ítems de datos contiene cada página. El valor + por defecto es 20. +* [[yii\data\Pagination::$page|página actual]] da el número de la página actual (comenzando desde 0). El valor + por defecto es 0, lo que sería la primera página. + +Con un objeto [[yii\data\Pagination]] totalmente especificado, puedes obtener y mostrar datos en partes. Por ejemplo, +si estás recuperando datos de una base de datos, puedes especificar las cláusulas `OFFSET` y `LIMIT` de la consulta a la BD +correspondientes a los valores provistos por la paginación. A continuación hay un ejemplo, + +```php +use yii\data\Pagination; + +// construye una consulta a la BD para obtener todos los artículos con status = 1 +$query = Article::find()->where(['status' => 1]); + +// obtiene el número total de artículos (pero no recupera los datos de los artículos todavía) +$count = $query->count(); + +// crea un objeto paginación con dicho total +$pagination = new Pagination(['totalCount' => $count]); + +// limita la consulta utilizando la paginación y recupera los artículos +$articles = $query->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); +``` + +¿Qué página de artículos devolverá el ejemplo de arriba? Depende de si se le es pasado un parámetro llamado `page`. +Por defecto, la paginación intentará definir la [[yii\data\Pagination::$page|página actual]] con +el valor del parámetro `page`. Si el parámetro no es provisto, entonces tomará por defecto el valor 0. + +Para facilitar la construcción de elementos UI que soporten paginación, Yii provee el widget [[yii\widgets\LinkPager]], +que muestra una lista de botones de navegación que el usuario puede presionar para indicar qué página de datos debería mostrarse. +El widget toma un objeto de paginación y tal manera conoce cuál es la página actual y cuántos botones +debe mostrar. Por ejemplo, + +```php +use yii\widgets\LinkPager; + +echo LinkPager::widget([ + 'pagination' => $pagination, +]); +``` + +Si quieres construir los elementos de UI manualmente, puedes utilizar [[yii\data\Pagination::createUrl()]] para generar URLs que +dirigirán a las distintas páginas. El método requiere un parámetro de página y generará una URL apropiadamente formada +contieniendo el parámetro de página. Por ejemplo, + +```php +// especifica la ruta que la URL generada debería utilizar +// Si no lo especificas, se utilizará la ruta de la petición actual +$pagination->route = 'article/index'; + +// muestra: /index.php?r=article%2Findex&page=100 +echo $pagination->createUrl(100); + +// muestra: /index.php?r=article%2Findex&page=101 +echo $pagination->createUrl(101); +``` + +> Tip: puedes personalizar el parámetro `page` de la consulta configurando + la propiedad [[yii\data\Pagination::pageParam|pageParam]] al crear el objeto de la paginación. diff --git a/docs/guide-es/output-theming.md b/docs/guide-es/output-theming.md new file mode 100644 index 00000000000..2b3a36ecc79 --- /dev/null +++ b/docs/guide-es/output-theming.md @@ -0,0 +1,99 @@ +Temas +===== + +> Note: Esta sección está en desarrollo. + +Un tema (theme) es un directorio de archivos y de vistas (views) y layouts. Cada archivo de este directorio +sobrescribe el archivo correspondiente de una aplicación cuando se renderiza. Una única aplicación puede usar +múltiples temas para que pueden proporcionar experiencias totalmente diferentes. Solo se puede haber un único tema +activo. + +> Note: Los temas no están destinados a ser redistribuidos ya que están demasiado ligados a la aplicación. Si se + quiere redistribuir una apariencia personalizada, se puede considerar la opción de + [asset bundles](structure-assets.md) de archivos CSS y Javascript. + +Configuración de un Tema +------------------------ + +La configuración de un tema se especifica a través del componente `view` de la aplicación. Para establecer que un tema +trabaje con vistas de aplicación básicas, la configuración de la aplicación debe contener lo siguiente: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => ['@app/views' => '@app/themes/basic'], + 'baseUrl' => '@web/themes/basic', + ], + ], +], +``` + +En el ejemplo anterior, el `pathMap` define un mapa (map) de las rutas a las que se aplicará el tema mientras que +`baseUrl` define la URL base para los recursos a los que hacen referencia los archivos del tema. + +En nuestro caso `pathMap` es `['@app/views' => '@app/themes/basic']`. Esto significa que cada vista de `@app/views` +primero se buscará en `@app/themes/basic` y si existe, se usará la vista del directorio del tema en lugar de la vista +original. + +Por ejemplo, con la configuración anterior, la versión del tema para la vista `@app/views/site/index.php` será +`@app/themes/basic/site/index.php`. Básicamente se reemplaza `@app/views` en `@app/views/site/index.php` por +`@app/themes/basic`. + +### Temas para Módulos + +Para utilizar temas en los módulos, el `pathMap` debe ser similar al siguiente: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/modules' => '@app/themes/basic/modules', // <-- !!! + ], + ], + ], +], +``` + +Esto permite aplicar el tema a `@app/modules/blog/views/comment/index.php` con la vista +`@app/themes/basic/modules/blog/views/comment/index.php`. + +### Temas para Widgets + +Para utilizar un tema en una vista que se encuentre en `@app/widgets/currency/views/index.php`, se debe aplicar la +siguiente configuración para el componente vista, tema: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => ['@app/widgets' => '@app/themes/basic/widgets'], + ], + ], +], +``` + +Con la configuración anterior, se puede crear una versión de la vista `@app/widgets/currency/index.php` para que se +aplique el tema en `@app/themes/basic/widgets/currency/views/index.php`. + +Uso de Multiples Rutas +---------------------- + +Es posible mapear una única ruta a múltiples rutas de temas. Por ejemplo: + +```php +'pathMap' => [ + '@app/views' => [ + '@app/themes/christmas', + '@app/themes/basic', + ], +] +``` + +En este caso, primero se buscara la vista en `@app/themes/christmas/site/index.php`, si no se encuentra, se intentará +en `@app/themes/basic/site/index.php`. Si la vista no se encuentra en ninguna de rutas especificadas, se usará la +vista de aplicación. + +Esta capacidad es especialmente útil si se quieren sobrescribir algunas rutas temporal o condicionalmente. diff --git a/docs/guide-es/rest-authentication.md b/docs/guide-es/rest-authentication.md new file mode 100644 index 00000000000..3e23aedfecc --- /dev/null +++ b/docs/guide-es/rest-authentication.md @@ -0,0 +1,127 @@ +Autenticación +============= + +A diferencia de las aplicaciones Web, las API RESTful son usualmente sin estado (stateless), lo que permite que las sesiones o las cookies +no sean usadas. Por lo tanto, cada petición debe llevar alguna suerte de credenciales de autenticación, +porque la autenticación del usuario no puede ser mantenida por las sesiones o las cookies. Una práctica común +es enviar una pieza (token) secreta de acceso con cada petición para autenticar al usuario. Dado que una pieza de autenticación +puede ser usada para identificar y autenticar solamente a un usuario, **la API de peticiones tiene que ser siempre enviado +vía HTTPS para prevenir ataques tipo "man-in-the-middle" (MitM) **. + +Hay muchas maneras de enviar una token (pieza) de acceso: + +* [Autenticación Básica HTTP](https://es.wikipedia.org/wiki/Autenticaci%C3%B3n_de_acceso_b%C3%A1sica): la pieza de acceso + es enviada como nombre de usuario. Esto sólo debe de ser usado cuando la pieza de acceso puede ser guardada + de forma segura en la parte del API del consumidor. Por ejemplo, el API del consumidor es un programa ejecutándose en un servidor. +* Parámetro de la consulta: la pieza de acceso es enviada como un parámetro de la consulta en la URL de la API, p.e., + `https://example.com/users?access-token=xxxxxxxx`. Debido que muchos servidores dejan los parámetros de consulta en los logs del servidor, + esta aproximación suele ser usada principalmente para servir peticiones `JSONP` + que no usen las cabeceras HTTP para enviar piezas de acceso. +* [OAuth 2](https://oauth.net/2/): la pieza de acceso es obtenida por el consumidor por medio de una autorización del servidor + y enviada al API del servidor según el protocolo + OAuth 2 [tokens HTTP del portador](https://datatracker.ietf.org/doc/html/rfc6750). + +Yii soporta todos los métodos anteriores de autenticación. Puedes crear nuevos métodos de autenticación de una forma fácil. + +Para activar la autenticación para tus APIs, sigue los pasos siguientes: + +1. Configura el componente `user` de la aplicación: + - Define la propiedad [[yii\web\User::enableSession|enableSession]] como `false`. + - Define la propiedad [[yii\web\User::loginUrl|loginUrl]] como `null` para mostrar un error HTTP 403 en vez de redireccionar a la pantalla de login. +2. Especifica cuál método de autenticación planeas usar configurando el comportamiento (behavior) `authenticator` en tus + clases de controladores REST. +3. Implementa [[yii\web\IdentityInterface::findIdentityByAccessToken()]] en tu [[yii\web\User::identityClass|clase de identidad de usuarios]]. + +El paso 1 no es necesario pero sí recomendable para las APIs RESTful, pues son sin estado (stateless). +Cuando [[yii\web\User::enableSession|enableSession]] es `false`, el estado de autenticación del usuario puede NO persistir entre peticiones usando sesiones. +Si embargo, la autenticación será realizada para cada petición, lo que se consigue en los pasos 2 y 3. + +> Tip:Puedes configurar [[yii\web\User::enableSession|enableSession]] del componente de la aplicación `user` en la configuración +> de las aplicaciones si estás desarrollando APIs RESTful en términos de un aplicación. Si desarrollas un módulo de las APIs RESTful, +> puedes poner la siguiente línea en el método del módulo `init()`, tal y como sigue: +> +> ```php +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` + +Por ejemplo, para usar HTTP Basic Auth, puedes configurar el comportamiento (behavior) `authenticator` como sigue, + +```php +use yii\filters\auth\HttpBasicAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => HttpBasicAuth::class, + ]; + return $behaviors; +} +``` + +Si quieres implementar los tres métodos de autenticación explicados antes, puedes usar `CompositeAuth` de la siguiente manera, + +```php +use yii\filters\auth\CompositeAuth; +use yii\filters\auth\HttpBasicAuth; +use yii\filters\auth\HttpBearerAuth; +use yii\filters\auth\QueryParamAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => CompositeAuth::class, + 'authMethods' => [ + HttpBasicAuth::class, + HttpBearerAuth::class, + QueryParamAuth::class, + ], + ]; + return $behaviors; +} +``` + +Cada elemento en `authMethods` debe de ser el nombre de una clase de método de autenticación o un array de configuración. + + +La implementación de `findIdentityByAccessToken()` es específico de la aplicación. Por ejemplo, en escenarios simples +cuando cada usuario sólo puede tener un token de acceso, puedes almacenar este token en la columna `access_token` +en la tabla de usuario. El método debe de ser inmediatamente implementado en la clase `User` como sigue, + +```php +use yii\db\ActiveRecord; +use yii\web\IdentityInterface; + +class User extends ActiveRecord implements IdentityInterface +{ + public static function findIdentityByAccessToken($token, $type = null) + { + return static::findOne(['access_token' => $token]); + } +} +``` + +Después que la autenticación es activada, tal y como se describe arriba, para cada petición de la API, el controlador solicitado +puede intentar autenticar al usuario en su evento `beforeAction()`. + +Si la autenticación tiene éxito, el controlador realizará otras comprobaciones (como son límite del ratio, autorización) +y entonces ejecutar la acción. La identidad del usuario autenticado puede ser recuperada via `Yii::$app->user->identity`. + +Si la autenticación falla, una respuesta con estado HTTP 401 será devuelta junto con otras cabeceras apropiadas +(tal como la cabecera para autenticación básica HTTP `WWW-Authenticate`). + + +## Autorización + +Después de que un usuario se ha autenticado, probablementer querrás comprobar si él o ella tiene los permisos para realizar +la acción solicitada. Este proceso es llamado *autorización (authorization)* y está cubierto en detalle +en la [Sección de Autorización](security-authorization.md). + +Si tus controladores extienden de [[yii\rest\ActiveController]], puedes sobreescribir +el método [[yii\rest\ActiveController::checkAccess()|checkAccess()]] para realizar la comprobación de la autorización. +El método será llamado por las acciones contenidas en [[yii\rest\ActiveController]]. diff --git a/docs/guide-es/rest-controllers.md b/docs/guide-es/rest-controllers.md new file mode 100644 index 00000000000..724fd567fe3 --- /dev/null +++ b/docs/guide-es/rest-controllers.md @@ -0,0 +1,156 @@ +Controladores +============= + +Después de crear las clases de recursos y especificar cómo debe ser el formato de datos de recursos, el siguiente paso +es crear acciones del controlador para exponer los recursos a los usuarios finales a través de las APIs RESTful. + +Yii ofrece dos clases controlador base para simplificar tu trabajo de crear acciones REST: +[[yii\rest\Controller]] y [[yii\rest\ActiveController]]. La diferencia entre estos dos controladores +es que este último proporciona un conjunto predeterminado de acciones que están específicamente diseñado para trabajar con +los recursos representados como [Active Record](db-active-record.md). Así que si estás utilizando [Active Record](db-active-record.md) +y te sientes cómodo con las acciones integradas provistas, podrías considerar extender tus controladores +de [[yii\rest\ActiveController]], lo que te permitirá crear potentes APIs RESTful con un mínimo de código. + +Ambos [[yii\rest\Controller]] y [[yii\rest\ActiveController]] proporcionan las siguientes características, +algunas de las cuales se describen en detalle en las siguientes secciones: + +* Método de Validación HTTP; +* [Negociación de contenido y formato de datos](rest-response-formatting.md); +* [Autenticación](rest-authentication.md); +* [Límite de Rango](rest-rate-limiting.md). + +[[yii\rest\ActiveController]] además provee de las siguientes características: + +* Un conjunto de acciones comunes necesarias: `index`, `view`, `create`, `update`, `delete`, `options`; +* La autorización del usuario de acuerdo a la acción y recurso solicitado. + + +## Creando Clases de Controlador + +Al crear una nueva clase de controlador, una convención para nombrar la clase del controlador es utilizar +el nombre del tipo de recurso en singular. Por ejemplo, para servir información de usuario, +el controlador puede ser nombrado como `UserController`. + +Crear una nueva acción es similar a crear una acción para una aplicación Web. La única diferencia +es que en lugar de renderizar el resultado utilizando una vista llamando al método `render()`, para las acciones REST +regresas directamente los datos. El [[yii\rest\Controller::serializer|serializer]] y el +[[yii\web\Response|response object]] se encargarán de la conversión de los datos originales +al formato solicitado. Por ejemplo, + +```php +public function actionView($id) +{ + return User::findOne($id); +} +``` + + +## Filtros + +La mayoría de las características API REST son proporcionadas por [[yii\rest\Controller]] son implementadas en los términos de [filtros](structure-filters.md). +En particular, los siguientes filtros se ejecutarán en el orden en que aparecen: + +* [[yii\filters\ContentNegotiator|contentNegotiator]]: soporta la negociación de contenido, que se explica en + la sección [Formateo de respuestas](rest-response-formatting.md); +* [[yii\filters\VerbFilter|verbFilter]]: soporta métodos de validación HTTP; +* [[yii\filters\auth\AuthMethod|authenticator]]: soporta la autenticación de usuarios, que se explica en + la sección [Autenticación](rest-authentication.md); +* [[yii\filters\RateLimiter|rateLimiter]]: soporta la limitación de rango, que se explica en + la sección [Límite de Rango](rest-rate-limiting.md). + +Estos filtros se declaran nombrándolos en el método [[yii\rest\Controller::behaviors()|behaviors()]]. +Puede sobrescribir este método para configurar filtros individuales, desactivar algunos de ellos, o añadir los tuyos. +Por ejemplo, si sólo deseas utilizar la autenticación básica HTTP, puede escribir el siguiente código: + +```php +use yii\filters\auth\HttpBasicAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => HttpBasicAuth::class, + ]; + return $behaviors; +} +``` + + +## Extendiendo `ActiveController` + +Si tu clase controlador extiende de [[yii\rest\ActiveController]], debe establecer +su propiedad [[yii\rest\ActiveController::modelClass|modelClass]] con el nombre de la clase del recurso +que planeas servir a través de este controlador. La clase debe extender de [[yii\db\ActiveRecord]]. + + +### Personalizando Acciones + +Por defecto, [[yii\rest\ActiveController]] provee de las siguientes acciones: + +* [[yii\rest\IndexAction|index]]: listar recursos página por página; +* [[yii\rest\ViewAction|view]]: devolver el detalle de un recurso específico; +* [[yii\rest\CreateAction|create]]: crear un nuevo recurso; +* [[yii\rest\UpdateAction|update]]: actualizar un recurso existente; +* [[yii\rest\DeleteAction|delete]]: eliminar un recurso específico; +* [[yii\rest\OptionsAction|options]]: devolver los métodos HTTP soportados. + +Todas esta acciones se declaran a través de método [[yii\rest\ActiveController::actions()|actions()]]. +Puedes configurar estas acciones o desactivar alguna de ellas sobrescribiendo el método `actions()`, como se muestra a continuación, + +```php +public function actions() +{ + $actions = parent::actions(); + + // disable the "delete" and "create" actions + unset($actions['delete'], $actions['create']); + + // customize the data provider preparation with the "prepareDataProvider()" method + $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; + + return $actions; +} + +public function prepareDataProvider() +{ + // prepare and return a data provider for the "index" action +} +``` + +Por favor, consulta las referencias de clases de acciones individuales para aprender las opciones de configuración disponibles para cada una. + + +### Realizando Comprobación de Acceso + +Al exponer los recursos a través de RESTful APIs, a menudo es necesario comprobar si el usuario actual tiene permiso +para acceder y manipular el/los recurso solicitado/s. Con [[yii\rest\ActiveController]], esto puede lograrse +sobrescribiendo el método [[yii\rest\ActiveController::checkAccess()|checkAccess()]] como a continuación, + +```php +/** + * Checks the privilege of the current user. + * + * This method should be overridden to check whether the current user has the privilege + * to run the specified action against the specified data model. + * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * + * @param string $action the ID of the action to be executed + * @param \yii\base\Model $model the model to be accessed. If `null`, it means no specific model is being accessed. + * @param array $params additional parameters + * @throws ForbiddenHttpException if the user does not have access + */ +public function checkAccess($action, $model = null, $params = []) +{ + // check if the user can access $action and $model + // throw ForbiddenHttpException if access should be denied + if ($action === 'update' || $action === 'delete') { + if ($model->author_id !== \Yii::$app->user->id) + throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s articles that you\'ve created.', $action)); + } +} +``` + +El método `checkAccess()` será llamado por defecto en las acciones predeterminadas de [[yii\rest\ActiveController]]. Si creas +nuevas acciones y también deseas llevar a cabo la comprobación de acceso, debe llamar a este método de forma explícita en las nuevas acciones. + +> Tip: Puedes implementar `checkAccess()` mediante el uso del [Componente Role-Based Access Control (RBAC)](security-authorization.md). diff --git a/docs/guide-es/rest-error-handling.md b/docs/guide-es/rest-error-handling.md new file mode 100644 index 00000000000..f5a663ee747 --- /dev/null +++ b/docs/guide-es/rest-error-handling.md @@ -0,0 +1,94 @@ +Manejo de errores +================= + +Cuando se maneja una petición de API RESTful, si ocurre un error en la petición del usuario o si algo inesperado +ocurre en el servidor, simplemente puedes lanzar una excepción para notificar al usuario que algo erróneo ocurrió. +Si puedes identificar la causa del error (p.e., el recurso solicitado no existe), debes considerar lanzar una excepción +con el código HTTP de estado apropiado (p.e., [[yii\web\NotFoundHttpException]] representa un código de estado 404). +Yii enviará la respuesta a continuación con el correspondiente código de estado HTTP y el texto. Yii puede incluir también +la representación serializada de la excepción en el cuerpo de la respuesta. +Por ejemplo: + +``` +HTTP/1.1 404 Not Found +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 +} +``` + +La siguiente lista sumariza los códigos de estado HTTP que son usados por el framework REST: + +* `200`: OK. Todo ha funcionado como se esperaba. +* `201`: El recurso ha creado con éxito en respuesta a la petición `POST`. La cabecera de situación `Location` + contiene la URL apuntando al nuevo recurso creado. +* `204`: La petición ha sido manejada con éxito y el cuerpo de la respuesta no tiene contenido (como una petición `DELETE`). +* `304`: El recurso no ha sido modificado. Puede usar la versión en caché. +* `400`: Petición errónea. Esto puede estar causado por varias acciones de el usuario, como proveer un JSON no válido + en el cuerpo de la petición, proveyendo parámetros de acción no válidos, etc. +* `401`: Autenticación fallida. +* `403`: El usuario autenticado no tiene permitido acceder a la API final. +* `404`: El recurso pedido no existe. +* `405`: Método no permitido. Por favor comprueba la cabecera `Allow` por los métodos HTTP permitidos. +* `415`: Tipo de medio no soportado. El tipo de contenido pedido o el número de versión no es válido. +* `422`: La validación de datos ha fallado (en respuesta a una petición `POST` , por ejemplo). Por favor, comprueba en el cuerpo de la respuesta el mensaje detallado. +* `429`: Demasiadas peticiones. La petición ha sido rechazada debido a un limitación de rango. +* `500`: Error interno del servidor. Esto puede estar causado por errores internos del programa. + + +## Personalizar la Respuesta al Error + +A veces puedes querer personalizar el formato de la respuesta del error por defecto . Por ejemplo, en lugar de depender +del uso de diferentes estados HTTP para indicar los diferentes errores, puedes querer usar siempre el estado HTTP 200 +y encapsular el código de estado HTTP real como parte de una estructura JSON en la respuesta, como se muestra a continuación, + +``` +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "success": false, + "data": { + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 + } +} +``` + +Para lograrlo, puedes responder al evento `beforeSend` del componente `response` en la configuración de la aplicación: + +```php +return [ + // ... + 'components' => [ + 'response' => [ + 'class' => 'yii\web\Response', + 'on beforeSend' => function ($event) { + $response = $event->sender; + if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { + $response->data = [ + 'success' => $response->isSuccessful, + 'data' => $response->data, + ]; + $response->statusCode = 200; + } + }, + ], + ], +]; +``` + +El anterior código reformateará la respuesta (sea exitosa o fallida) como se explicó cuando +`suppress_response_code` es pasado como un parámetro `GET`. diff --git a/docs/guide-es/rest-quick-start.md b/docs/guide-es/rest-quick-start.md new file mode 100644 index 00000000000..89d940c1867 --- /dev/null +++ b/docs/guide-es/rest-quick-start.md @@ -0,0 +1,203 @@ +Guía Breve +========== + +Yii ofrece todo un conjunto de herramientas para simplificar la tarea de implementar un +servicio web APIs RESTful. +En particular, Yii soporta las siguientes características sobre APIs RESTful; + +* Prototipado rápido con soporte para APIs comunes para [Active Record](db-active-record.md); +* Formato de respuesta de negocio (soporta JSON y XML por defecto); +* Personalización de objetos serializados con soporte para campos de salida seleccionables; +* Formateo apropiado de colecciones de datos y validación de errores; +* Soporte para [HATEOAS](https://es.wikipedia.org/wiki/HATEOAS); +* Eficiente enrutamiento con una adecuada comprobación del verbo(verb) HTTP; +* Incorporado soporte para las `OPTIONS` y `HEAD` verbos; +* Autenticación y autorización; +* Cacheo de datos y cacheo HTTP; +* Limitación de rango; + + +A continuación, utilizamos un ejemplo para ilustrar como se puede construir un conjunto de APIs RESTful con un esfuerzo mínimo de codificación. + +Supongamos que deseas exponer los datos de los usuarios vía APIs RESTful. Los datos de usuario son almacenados en la tabla DB `user`, +y ya tienes creado la clase [[yii\db\ActiveRecord|ActiveRecord]] `app\models\User` para acceder a los datos del usuario. + + +## Creando un controlador + +Primero, crea una clase controladora `app\controllers\UserController` como la siguiente, + +```php +namespace app\controllers; + +use yii\rest\ActiveController; + +class UserController extends ActiveController +{ + public $modelClass = 'app\models\User'; +} +``` + +La clase controladora extiende de [[yii\rest\ActiveController]]. Especificado por [[yii\rest\ActiveController::modelClass|modelClass]] +como `app\models\User`, el controlador sabe que modelo puede ser usado para recoger y manipular sus datos. + + +## Configurando las reglas de las URL + +A continuación, modifica la configuración del componente `urlManager` en la configuración de tu aplicación: + +```php +'urlManager' => [ + 'enablePrettyUrl' => true, + 'enableStrictParsing' => true, + 'showScriptName' => false, + 'rules' => [ + ['class' => 'yii\rest\UrlRule', 'controller' => 'user'], + ], +] +``` + +La configuración anterior principalmente añade una regla URL para el controlador `user` de manera +que los datos de user pueden ser accedidos y manipulados con URLs amigables y verbos HTTP significativos. + + +## Habilitando entradas JSON + +Para permitir que la API acepte datos de entrada con formato JSON, configura la propiedad [[yii\web\Request::$parsers|parsers]] +del componente de aplicación `request` para usar [[yii\web\JsonParser]] para entradas JSON: + +```php +'request' => [ + 'parsers' => [ + 'application/json' => 'yii\web\JsonParser', + ] +] +``` + +> Tip: La configuración anterior es opcional. Sin la configuración anterior, la API sólo reconocería + `application/x-www-form-urlencoded` y `multipart/form-data` como formatos de entrada. + + +## Probándolo + +Con la mínima cantidad de esfuerzo, tienes ya finalizado tu tarea de crear las APIs RESTful +para acceder a los datos de user. Las APIs que tienes creado incluyen: + +* `GET /users`: una lista de todos los usuarios página por página; +* `HEAD /users`: muestra la información general de la lista de usuarios; +* `POST /users`: crea un nuevo usuario; +* `GET /users/123`: devuelve los detalles del usuario 123; +* `HEAD /users/123`: muestra la información general del usuario 123; +* `PATCH /users/123` y `PUT /users/123`: actualiza el usuario 123; +* `DELETE /users/123`: elimina el usuario 123; +* `OPTIONS /users`: muestra los verbos compatibles respecto al punto final `/users`; +* `OPTIONS /users/123`: muestra los verbos compatibles respecto al punto final `/users/123`. + +> Info: Yii automáticamente pluraliza los nombres de los controladores para usarlo en los puntos finales. +> Puedes configurar esto usando la propiedad [[yii\rest\UrlRule::$pluralize]]. + +Puedes acceder a tus APIs con el comando `curl` de la siguiente manera, + +``` +$ curl -i -H "Accept:application/json" "http://localhost/users" + +HTTP/1.1 200 OK +... +X-Pagination-Total-Count: 1000 +X-Pagination-Page-Count: 50 +X-Pagination-Current-Page: 1 +X-Pagination-Per-Page: 20 +Link: ; rel=self, + ; rel=next, + ; rel=last +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +[ + { + "id": 1, + ... + }, + { + "id": 2, + ... + }, + ... +] +``` + +Intenta cambiar el tipo de contenido aceptado para ser `application/xml`, y verá que el resultado +se devuelve en formato XML: + +``` +$ curl -i -H "Accept:application/xml" "http://localhost/users" + +HTTP/1.1 200 OK +... +X-Pagination-Total-Count: 1000 +X-Pagination-Page-Count: 50 +X-Pagination-Current-Page: 1 +X-Pagination-Per-Page: 20 +Link: ; rel=self, + ; rel=next, + ; rel=last +Transfer-Encoding: chunked +Content-Type: application/xml + + + + + 1 + ... + + + 2 + ... + + ... + +``` + +El siguiente comando creará un nuevo usuario mediante el envío de una petición POST con los datos del usuario en formato JSON: + +``` +$ curl -i -H "Accept:application/json" -H "Content-Type:application/json" -XPOST "http://localhost/users" -d '{"username": "example", "email": "user@example.com"}' + +HTTP/1.1 201 Created +... +Location: http://localhost/users/1 +Content-Length: 99 +Content-Type: application/json; charset=UTF-8 + +{"id":1,"username":"example","email":"user@example.com","created_at":1414674789,"updated_at":1414674789} +``` + +> Tip: También puedes acceder a tus APIs a través del navegador web introduciendo la URL `http://localhost/users`. + Sin embargo, es posible que necesites algunos plugins para el navegador para enviar cabeceras especificas en la petición. + +Como se puede ver, en las cabeceras de la respuesta, hay información sobre la cuenta total, número de páginas, etc. +También hay enlaces que permiten navegar por otras páginas de datos. Por ejemplo, `http://localhost/users?page=2` +le daría la página siguiente de los datos de usuario. + +Utilizando los parámetros `fields` y `expand`, puedes también especificar que campos deberían ser incluidos en el resultado. +Por ejemplo, la URL `http://localhost/users?fields=id,email` sólo devolverá los campos `id` y `email`. + + +> Info: Puedes haber notado que el resultado de `http://localhost/users` incluye algunos campos sensibles, +> tal como `password_hash`, `auth_key`. Seguramente no quieras que éstos aparecieran en el resultado de tu API. +> Puedes y deberías filtrar estos campos como se describe en la sección [Response Formatting](rest-response-formatting.md). + + +## Resumen + +Utilizando el framework Yii API RESTful, implementa un punto final API en términos de una acción de un controlador, y utiliza +un controlador para organizar las acciones que implementan los puntos finales para un sólo tipo de recurso. + +Los recursos son representados como modelos de datos que extienden de la clase [[yii\base\Model]]. +Si estás trabajando con bases de datos (relacionales o NoSQL), es recomendable utilizar [[yii\db\ActiveRecord|ActiveRecord]] +para representar los recursos. + +Puedes utilizar [[yii\rest\UrlRule]] para simplificar el enrutamiento de los puntos finales de tu API. + +Aunque no es obligatorio, es recomendable que desarrolles tus APIs RESTful como una aplicación separada, diferente de +tu WEB front end y tu back end para facilitar el mantenimiento. diff --git a/docs/guide-es/rest-rate-limiting.md b/docs/guide-es/rest-rate-limiting.md new file mode 100644 index 00000000000..fffc810f8ea --- /dev/null +++ b/docs/guide-es/rest-rate-limiting.md @@ -0,0 +1,44 @@ +Limitando el rango (rate) +========================= + +Para prevenir el abuso, puedes considerar añadir un *límitación del rango (rate limiting)* para tus APIs. Por ejemplo, +puedes querer limitar el uso del API de cada usuario a un máximo de 100 llamadas al API dentro de un periodo de 10 minutos. +Si se reciben demasiadas peticiones de un usuario dentro del periodo de tiempo declarado, deveríá devolverse una respuesta con código de estado 429 (que significa "Demasiadas peticiones"). + +Para activar la limitación de rango, la clase [[yii\web\User::identityClass|user identity class]] debe implementar [[yii\filters\RateLimitInterface]]. +Este interface requiere la implementación de tres métodos: + +* `getRateLimit()`: devuelve el número máximo de peticiones permitidas y el periodo de tiempo (p.e., `[100, 600]` significa que como mucho puede haber 100 llamadas al API dentro de 600 segundos). +* `loadAllowance()`: devuelve el número de peticiones que quedan permitidas y el tiempo (fecha/hora) UNIX + con el último límite del rango que ha sido comprobado. +* `saveAllowance()`: guarda ambos, el número restante de peticiones permitidas y el tiempo actual (fecha/hora) UNIX . + +Puedes usar dos columnas en la tabla de usuario para guardar la información de lo permitido y la fecha/hora (timestamp). Con ambas definidas, +entonces `loadAllowance()` y `saveAllowance()` pueden ser utilizados para leer y guardar los valores de las dos columnas correspondientes al actual usuario autenticado. +Para mejorar el desempeño, también puedes considerar almacenar esas piezas de información en caché o almacenamiento NoSQL. + +Una vez que la clase de identidad implementa la interfaz requerida, Yii utilizará automáticamente [[yii\filters\RateLimiter]] +configurado como un filtro de acción para que [[yii\rest\Controller]] compruebe el límite de rango. El limitador de rango +lanzará una excepeción [[yii\web\TooManyRequestsHttpException]] cuando el límite del rango sea excedido. + +Puedes configurar el limitador de rango +en tu clase controlador REST como sigue: + +```php +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['rateLimiter']['enableRateLimitHeaders'] = false; + return $behaviors; +} +``` + +Cuando se activa el límite de rango, por defecto todas las respuestas serán enviadas con la siguiente cabecera HTTP conteniendo +información sobre el límite actual de rango: + +* `X-Rate-Limit-Limit`, el máximo número de peticiones permitidas en un periodo de tiempo +* `X-Rate-Limit-Remaining`, el número de peticiones restantes en el periodo de tiempo actual +* `X-Rate-Limit-Reset`, el número de segundos a esperar para pedir el máximo número de peticiones permitidas + +Puedes desactivar estas cabeceras configurando [[yii\filters\RateLimiter::enableRateLimitHeaders]] a `false`, +tal y como en el anterior ejemplo. diff --git a/docs/guide-es/rest-resources.md b/docs/guide-es/rest-resources.md new file mode 100644 index 00000000000..81af3525da6 --- /dev/null +++ b/docs/guide-es/rest-resources.md @@ -0,0 +1,190 @@ +Recursos +========= + +Las APIs RESTful lo son todos para acceder y manipular *recursos (resources)*. Puedes observar los recursos en el paradigma MVC en [Modelos (models)](structure-models.md) . + +Mientras que no hay restricción a cómo representar un recurso, en YII usualmente, puedes representar recursos como objetos de la clase [[yii\base\Model]] o sus clases hijas (p.e. [[yii\db\ActiveRecord]]), por las siguientes razones: + +* [[yii\base\Model]] implementa el interface [[yii\base\Arrayable]] , el cual te permite personalizar como exponer los datos de los recursos a travès de las APIs RESTful. +* [[yii\base\Model]] soporta [Validación de entrada (input validation)](input-validation.md), lo cual es muy usado en las APIs RESTful para soportar la entrada de datos. +* [[yii\db\ActiveRecord]] provee un poderoso soporte para el acceso a datos en bases de datos y su manipulación, lo que lo le hace servir perfectamente si sus recursos de datos están en bases de datos. + +En esta sección, vamos principalmente a describir como la clase con recursos que extiende de [[yii\base\Model]] (o sus clases hijas) puede especificar qué datos puede ser devueltos vía las APIs RESTful. Si la clase de los recursos no extiende de [[yii\base\Model]], entonces todas las variables públicas miembro serán devueltas. + + +## Campos (fields) + +Cuando incluimos un recurso en una respuesta de la API RESTful, el recurso necesita ser serializado en una cadena. +Yii divide este proceso en dos pasos. Primero, el recurso es convertido en un array por [[yii\rest\Serializer]]. +Segundo, el array es serializado en una cadena en el formato requerido (p.e. JSON, XML) por [[yii\web\ResponseFormatterInterface|response formatters]]. El primer paso es en el que debes de concentrarte principalmente cuando desarrolles una clase de un recurso. + +Sobreescribiendo [[yii\base\Model::fields()|fields()]] y/o [[yii\base\Model::extraFields()|extraFields()]], +puedes especificar qué datos, llamados *fields*, en el recursos, pueden ser colocados en el array que le representa. +La diferencia entre estos dos métodos es que el primero especifica el conjunto de campos por defecto que deben ser incluidos en el array que los representa, mientras que el último especifica campos adicionales que deben de ser incluidos en el array si una petición del usuario final para ellos vía el parámetro de consulta `expand`. Por ejemplo, + +``` +// devuelve todos los campos declarados en fields() +http://localhost/users + +// sólo devuelve los campos id y email, provistos por su declaración en fields() +http://localhost/users?fields=id,email + +// devuelve todos los campos en fields() y el campo profile siempre y cuando esté declarado en extraFields() +http://localhost/users?expand=profile + +// sólo devuelve los campos id, email y profile, siempre y cuando ellos estén declarados en fields() y extraFields() +http://localhost/users?fields=id,email&expand=profile +``` + + +### Sobreescribiendo `fields()` + +Por defecto, [[yii\base\Model::fields()]] devuelve todos los atributos de los modelos como si fueran campos, mientras [[yii\db\ActiveRecord::fields()]] sólo devuelve los atributos que tengan datos en la base de datos. + +Puedes sobreescribir `fields()` para añadir, quitar, renombrar o redefinir campos. El valor de retorno de `fields()` ha de estar en un array. Las claves del array son los nombres de los campos y los valores del array son las correspondientes definiciones de los campos que pueden ser tanto nombres de propiedades/atributos o funciones anónimas que devuelven los correspondientes valores del los campos. En el caso especial de que el nombre de un campo sea el mismo que su definición puedes omitir la clave en el array. Por ejemplo, + +```php +// explícitamente lista cada campo, siendo mejor usarlo cuando quieras asegurarte que los cambios +// en una tabla de la base de datos o en un atributo del modelo no provoque el cambio de tu campo (para mantener la compatibilidad anterior). +public function fields() +{ + return [ + // el nombre de campo es el mismo nombre del atributo + 'id', + // el nombre del campo es "email", su atributo se denomina "email_address" + 'email' => 'email_address', + // el nombre del campo es "name", su valor es definido está definido por una función anónima de retrollamada (callback) + 'name' => function () { + return $this->first_name . ' ' . $this->last_name; + }, + ]; +} + +// el ignorar algunos campos, es mejor usarlo cuando heredas de una implementación padre +// y pones en la lista negra (blacklist) algunos campos sensibles +public function fields() +{ + $fields = parent::fields(); + + // quita los campos con información sensible + unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); + + return $fields; +} +``` + +> Warning: Dado que, por defecto, todos los atributos de un modelo pueden ser incluidos en la devolución del API, debes +> examinar tus datos para estar seguro de que no contiene información sensible. Si se da este tipo de información, +> debes sobreescribir `fields()` para filtrarlos. En el ejemplo anterior, escogemos +> quitar `auth_key`, `password_hash` y `password_reset_token`. + + +### Sobreescribiendo `extraFields()` + +Por defecto, [[yii\base\Model::extraFields()]] no devuelve nada, mientras que [[yii\db\ActiveRecord::extraFields()]] devuelve los nombres de las relaciones que tienen datos (populated) obtenidos de la base de datos. + +El formato de devolución de los datos de `extraFields()` es el mismo que el de `fields()`. Usualmente, `extraFields()` es principalmente usado para especificar campos cuyos valores sean objetos. Por ejemplo, dado la siguiente declaración de campo, + +```php +public function fields() +{ + return ['id', 'email']; +} + +public function extraFields() +{ + return ['profile']; +} +``` + +la petición `http://localhost/users?fields=id,email&expand=profile` puede devolver los siguientes datos en formato JSON : + +```php +[ + { + "id": 100, + "email": "100@example.com", + "profile": { + "id": 100, + "age": 30, + } + }, + ... +] +``` + + +## Enlaces (Links) + +[HATEOAS](https://es.wikipedia.org/wiki/HATEOAS), es una abreviación de Hipermedia es el Motor del Estado de la Aplicación (Hypermedia as the Engine of Application State), promueve que las APIs RESTfull devuelvan información que permita a los clientes descubrir las acciones que soportan los recursos devueltos. El sentido de HATEOAS es devolver un conjunto de hiperenlaces con relación a la información, cuando los datos de los recursos son servidos por las APIs. + +Las clases con recursos pueden soportar HATEOAS implementando el interfaz [[yii\web\Linkable]] . El interfaz contiene sólo un método [[yii\web\Linkable::getLinks()|getLinks()]] el cual debe de de devolver una lista de [[yii\web\Link|links]]. +Típicamente, debes devolver al menos un enlace `self` representando la URL al mismo recurso objeto. Por ejemplo, + +```php +use yii\db\ActiveRecord; +use yii\web\Link; +use yii\web\Linkable; +use yii\helpers\Url; + +class User extends ActiveRecord implements Linkable +{ + public function getLinks() + { + return [ + Link::REL_SELF => Url::to(['user/view', 'id' => $this->id], true), + ]; + } +} +``` + +Cuando un objeto `User` es devuelto en una respuesta, puede contener un elemento `_links` representando los enlaces relacionados con el usuario, por ejemplo, + +``` +{ + "id": 100, + "email": "user@example.com", + // ... + "_links" => { + "self": { + "href": "https://example.com/users/100" + } + } +} +``` + + +## Colecciones + +Los objetos de los recursos pueden ser agrupados en *collections*. Cada colección contiene una lista de recursos objeto del mismo tipo. + +Las colecciones pueden ser representadas como arrays pero, es usualmente más deseable representarlas como [proveedores de datos (data providers)](output-data-providers.md). Esto es así porque los proveedores de datos soportan paginación y ordenación de los recursos, lo cual es comunmente necesario en las colecciones devueltas con las APIs RESTful. Por ejemplo, la siguiente acción devuelve un proveedor de datos sobre los recursos post: + +```php +namespace app\controllers; + +use yii\rest\Controller; +use yii\data\ActiveDataProvider; +use app\models\Post; + +class PostController extends Controller +{ + public function actionIndex() + { + return new ActiveDataProvider([ + 'query' => Post::find(), + ]); + } +} +``` + +Cuando un proveedor de datos está enviando una respuesta con el API RESTful, [[yii\rest\Serializer]] llevará la actual página de los recursos y los serializa como un array de recursos objeto. Adicionalmente, [[yii\rest\Serializer]] +puede incluir también la información de paginación a través de las cabeceras HTTP siguientes: + +* `X-Pagination-Total-Count`: Número total de recursos; +* `X-Pagination-Page-Count`: Número de páginas; +* `X-Pagination-Current-Page`: Página actual (iniciando en 1); +* `X-Pagination-Per-Page`: Número de recursos por página; +* `Link`: Un conjunto de enlaces de navegación permitiendo al cliente recorrer los recursos página a página. + +Un ejemplo se puede ver en la sección [Inicio rápido (Quick Start)](rest-quick-start.md#trying-it-out). diff --git a/docs/guide-es/rest-response-formatting.md b/docs/guide-es/rest-response-formatting.md new file mode 100644 index 00000000000..3ec0049f9f5 --- /dev/null +++ b/docs/guide-es/rest-response-formatting.md @@ -0,0 +1,157 @@ +Formato de Respuesta +==================== + +Cuando se maneja una petición al API RESTful, una aplicación realiza usualmente los siguientes pasos que están relacionados +con el formato de la respuesta: + +1. Determinar varios factores que pueden afectar al formato de la respuesta, como son el tipo de medio, lenguaje, versión, etc. + Este proceso es también conocido como [negociación de contenido (content negotiation)](https://es.wikipedia.org/wiki/Negociaci%C3%B3n_de_contenido). +2. La conversión de objetos recurso en arrays, como está descrito en la sección [Recursos (Resources)](rest-resources.md). + Esto es realizado por la clase [[yii\rest\Serializer]]. +3. La conversión de arrays en cadenas con el formato determinado por el paso de negociación de contenido. Esto es + realizado por los [[yii\web\ResponseFormatterInterface|formatos de respuesta]] registrados + con la propiedad [[yii\web\Response::formatters|formatters]] del + [componente de la aplicación](structure-application-components.md) `response`. + + +## Negociación de contenido (Content Negotiation) + +Yii soporta la negociación de contenido a través del filtro [[yii\filters\ContentNegotiator]]. La clase de controlador base +del API RESTful [[yii\rest\Controller]] está equipada con este filtro bajo el nombre `contentNegotiator`. +El filtro provee tanto un formato de respuesta de negociación como una negociación de lenguaje. Por ejemplo, si la petición API RESTful +contiene la siguiente cabecera, + +``` +Accept: application/json; q=1.0, */*; q=0.1 +``` + +puede obtener una respuesta en formato JSON, como a continuación: + +``` +$ curl -i -H "Accept: application/json; q=1.0, */*; q=0.1" "http://localhost/users" + +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +X-Powered-By: PHP/5.4.20 +X-Pagination-Total-Count: 1000 +X-Pagination-Page-Count: 50 +X-Pagination-Current-Page: 1 +X-Pagination-Per-Page: 20 +Link: ; rel=self, + ; rel=next, + ; rel=last +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +[ + { + "id": 1, + ... + }, + { + "id": 2, + ... + }, + ... +] +``` + +Detrás de escena, antes de que sea ejecutada una acción del controlador del API RESTful, el filtro [[yii\filters\ContentNegotiator]] +comprobará la cabecera HTTP `Accept` de la petición y definirá el [[yii\web\Response::format|response format]] +como `'json'`. Después de que la acción sea ejecutada y devuelva el objeto recurso o la colección resultante, +[[yii\rest\Serializer]] convertirá el resultado en un array. Y finalmente, [[yii\web\JsonResponseFormatter]] +serializará el array en una cadena JSON incluyéndola en el cuerpo de la respuesta. + +Por defecto, el API RESTful soporta tanto el formato JSON como el XML. Para soportar un nuevo formato, debes configurar +la propiedad [[yii\filters\ContentNegotiator::formats|formats]] del filtro `contentNegotiator` tal y como sigue, +en las clases del controlador del API: + +```php +use yii\web\Response; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_HTML; + return $behaviors; +} +``` + +Las claves de la propiedad `formats` son los tipos MIME soportados, mientras que los valores son los nombres de formato de respuesta correspondientes, +los cuales deben ser soportados en [[yii\web\Response::formatters]]. + + +## Serialización de Datos + +Como hemos descrito antes, [[yii\rest\Serializer]] es la pieza central responsable de convertir +objetos recurso o colecciones en arrays. Reconoce objetos tanto implementando [[yii\base\ArrayableInterface]] +como [[yii\data\DataProviderInterface]]. El primer formateador es implementado principalmente para objetos recursos, +mientras que el segundo para recursos collección. + +Puedes configurar el serializador definiendo la propiedad [[yii\rest\Controller::serializer]] con un array de configuración. +Por ejemplo, a veces puedes querer ayudar a simplificar el trabajo de desarrollo del cliente incluyendo información de la paginación +directamente en el cuerpo de la respuesta. Para hacer esto, configura la propiedad [[yii\rest\Serializer::collectionEnvelope]] +como sigue: + +```php +use yii\rest\ActiveController; + +class UserController extends ActiveController +{ + public $modelClass = 'app\models\User'; + public $serializer = [ + 'class' => 'yii\rest\Serializer', + 'collectionEnvelope' => 'items', + ]; +} +``` + +Puedes obtener la respuesta que sigue para la petición `http://localhost/users`: + +``` +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +X-Powered-By: PHP/5.4.20 +X-Pagination-Total-Count: 1000 +X-Pagination-Page-Count: 50 +X-Pagination-Current-Page: 1 +X-Pagination-Per-Page: 20 +Link: ; rel=self, + ; rel=next, + ; rel=last +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "items": [ + { + "id": 1, + ... + }, + { + "id": 2, + ... + }, + ... + ], + "_links": { + "self": { + "href": "http://localhost/users?page=1" + }, + "next": { + "href": "http://localhost/users?page=2" + }, + "last": { + "href": "http://localhost/users?page=50" + } + }, + "_meta": { + "totalCount": 1000, + "pageCount": 50, + "currentPage": 1, + "perPage": 20 + } +} +``` diff --git a/docs/guide-es/rest-routing.md b/docs/guide-es/rest-routing.md new file mode 100644 index 00000000000..dcb6bddfd3f --- /dev/null +++ b/docs/guide-es/rest-routing.md @@ -0,0 +1,92 @@ +Enrutamiento +============ + +Con las clases de controlador y recurso preparadas, puedes acceder a los recursos usando una URL como +`http://localhost/index.php?r=user/create`, parecida a la que usas con aplicaciones Web normales. + +En la práctica, querrás usualmente usar URLs limpias y obtener ventajas de los verbos HTTP. +Por ejemplo, una petición `POST /users` significaría acceder a la acción `user/create`. +Esto puede realizarse fácilmente configurando el componente de la aplicación `urlManager` +como sigue: + +```php +'urlManager' => [ + 'enablePrettyUrl' => true, + 'enableStrictParsing' => true, + 'showScriptName' => false, + 'rules' => [ + ['class' => 'yii\rest\UrlRule', 'controller' => 'user'], + ], +] +``` + +En comparación con la gestión de URL en las aplicaciones Web, lo principalmente nuevo de lo anterior es el uso de +[[yii\rest\UrlRule]] para el enrutamiento de las peticiones con el API RESTful. Esta clase especial de regla URL creará +un conjunto completo de reglas URL hijas para soportar el enrutamiento y creación de URL para el/los controlador/es especificados. +Por ejemplo, el código anterior es equivalente a las siguientes reglas: + +```php +[ + 'PUT,PATCH users/' => 'user/update', + 'DELETE users/' => 'user/delete', + 'GET,HEAD users/' => 'user/view', + 'POST users' => 'user/create', + 'GET,HEAD users' => 'user/index', + 'users/' => 'user/options', + 'users' => 'user/options', +] +``` + +Y los siguientes puntos finales del API son mantenidos por esta regla: + +* `GET /users`: lista de todos los usuarios página a página; +* `HEAD /users`: muestra ĺa información resumen del usuario listado; +* `POST /users`: crea un nuevo usuario; +* `GET /users/123`: devuelve los detalles del usuario 123; +* `HEAD /users/123`: muestra la información resumen del usuario 123; +* `PATCH /users/123` y `PUT /users/123`: actualizan al usuario 123; +* `DELETE /users/123`: borra el usuario 123; +* `OPTIONS /users`: muestra los verbos soportados de acuerdo al punto final `/users`; +* `OPTIONS /users/123`: muestra los verbos soportados de acuerdo al punto final `/users/123`. + +Puedes configurar las opciones `only` y `except` para explícitamente listar cuáles acciones soportar o cuáles +deshabilitar, respectivamente. Por ejemplo, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'except' => ['delete', 'create', 'update'], +], +``` + +También puedes configurar las propiedades `patterns` o `extraPatterns` para redefinir patrones existentes o añadir nuevos soportados por esta regla. +Por ejemplo, para soportar una nueva acción `search` para el punto final `GET /users/search`, configura la opción `extraPatterns` como sigue, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'extraPatterns' => [ + 'GET search' => 'search', + ], +] +``` + +Puedes haber notado que el ID del controlador `user` aparece en formato plural `users` en los puntos finales de las URLs. +Esto se debe a que [[yii\rest\UrlRule]] automáticamente pluraliza los IDs de los controladores al crear reglas URL hijas. +Puedes desactivar este comportamiento definiendo la propiedad [[yii\rest\UrlRule::pluralize]] como `false`. + +> Info: La pluralización de los IDs de los controladores es realizada por [[yii\helpers\Inflector::pluralize()]]. Este método respeta + reglas especiales de pluralización. Por ejemplo, la palabra `box` (caja) será pluralizada como `boxes` en vez de `boxs`. + +En caso de que la pluralización automática no encaje en tus requerimientos, puedes además configurar la propiedad +[[yii\rest\UrlRule::controller]] para especificar explícitamente cómo mapear un nombre utilizado en un punto final URL +a un ID de controlador. Por ejemplo, el siguiente código mapea el nombre `u` al ID del controlador `user`. + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => ['u' => 'user'], +] +``` diff --git a/docs/guide-es/rest-versioning.md b/docs/guide-es/rest-versioning.md new file mode 100644 index 00000000000..b7dcedbca2b --- /dev/null +++ b/docs/guide-es/rest-versioning.md @@ -0,0 +1,111 @@ +Versionado +========== + +Una buena API ha de ser *versionada*: los cambios y las nuevas características son implementadas en las nuevas versiones del API, en vez de estar continuamente modificando sólo una versión. Al contrario que en las aplicaciones Web, en las cuales tienes total control del código de ambas partes lado del cliente y lado del servidor, +las APIs están destinadas a ser usadas por los clientes fuera de tu control. Por esta razón, compatibilidad hacia atrás (BC Backward compatibility) +de las APIs ha de ser mantenida siempre que sea posible. Si es necesario un cambio que puede romper la BC, debes de introducirla en la nueva versión del API, e incrementar el número de versión. Los clientes que la usan pueden continuar usando la antigua versión de trabajo del API; los nuevos y actualizados clientes pueden obtener la nueva funcionalidad de la nueva versión del API. + +> Tip: referirse a [Semántica del versionado](https://semver.org/) +para más información en el diseño del número de versión del API. + +Una manera común de implementar el versionado de la API es embeber el número de versión en las URLs de la API. +Por ejemplo, `https://example.com/v1/users` se refiere al punto final `/users` de la versión 1 de la API. + +Otro método de versionado de la API, +la cual está ganando predominancia recientemente, es poner el número de versión en las cabeceras de la petición HTTP. Esto se suele hacer típicamente a través la cabecera `Accept`: + +``` +// vía parámetros +Accept: application/json; version=v1 +// vía de el tipo de contenido del proveedor +Accept: application/vnd.company.myapp-v1+json +``` + +Ambos métodos tienen sus pros y sus contras, y hay gran cantidad de debates sobre cada uno. Debajo puedes ver una estrategia +práctica para el versionado de la API que es una mezcla de estos dos métodos: + +* Pon cada versión superior de la implementación de la API en un módulo separado cuyo ID es el número de la versión mayor (p.e. `v1`, `v2`). + Naturalmente, las URLs de la API contendrán números de versión mayores. +* Dentro de cada versión mayor (y por lo tanto, dentro del correspondiente módulo), usa la cabecera de HTTP `Accept` + para determinar el número de la versión menor y escribe código condicional para responder a la menor versión como corresponde. + +Para cada módulo sirviendo una versión mayor, el módulo debe incluir las clases de recursos y y controladores +que especifican la versión. Para separar mejor la responsabilidad del código, puedes conservar un conjunto común de +clases base de recursos y controladores, y hacer subclases de ellas en cada versión individual del módulo. Dentro de las subclases, +impementa el código concreto como por ejemplo `Model::fields()`. + +Tu código puede estar organizado como lo que sigue: + +``` +api/ + common/ + controllers/ + UserController.php + PostController.php + models/ + User.php + Post.php + modules/ + v1/ + controllers/ + UserController.php + PostController.php + models/ + User.php + Post.php + v2/ + controllers/ + UserController.php + PostController.php + models/ + User.php + Post.php +``` + +La configuración de tu aplicación puede tener este aspecto: + +```php +return [ + 'modules' => [ + 'v1' => [ + 'basePath' => '@app/modules/v1', + 'controllerNamespace' => 'app\modules\v1\controllers', + ], + 'v2' => [ + 'basePath' => '@app/modules/v2', + 'controllerNamespace' => 'app\modules\v2\controllers', + ], + ], + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'enableStrictParsing' => true, + 'showScriptName' => false, + 'rules' => [ + ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']], + ['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']], + ], + ], + ], +]; +``` + +Como consecuencia del código anterior, `https://example.com/v1/users` devolverá la lista de usuarios en la versión 1, mientras +`https://example.com/v2/users` devolverá la versión 2 de los usuarios. + +Gracias a los módulos, el código de las diferentes principales versiones puede ser aislado. Pero los módulos hacen posible +reutilizar el código a través de los módulos vía clases base comunes y otros recursos compartidos. + +Para tratar con versiones menores, puedes tomar ventaja de la característica de negociación de contenido +provista por el comportamiento (behavior) [[yii\filters\ContentNegotiator|contentNegotiator]]. El comportamiento `contentNegotiator` +definirá la propiedad [[yii\web\Response::acceptParams]] cuando determina qué tipo +de contenido soportar. + +Por ejemplo, si una petición es enviada con la cabecera HTTP `Accept: application/json; version=v1`, +después de la negociación de contenido, [[yii\web\Response::acceptParams]] contendrá el valor `['version' => 'v1']`. + +Basado en la información de versión contenida en `acceptParams`, puedes escribir código condicional en lugares +como acciones, clases de recursos, serializadores, etc. para proveer la funcionalidad apropiada. + +Dado que por definición las versiones menores requireren mantener la compatibilidad hacia atrás, con suerte no tendrás demasiadas +comprobaciones de versión en tu código. De otra manera, probablemente puede ocurrir que necesites crear una versión mayor. diff --git a/docs/guide-es/runtime-bootstrapping.md b/docs/guide-es/runtime-bootstrapping.md new file mode 100644 index 00000000000..db4a973ccec --- /dev/null +++ b/docs/guide-es/runtime-bootstrapping.md @@ -0,0 +1,24 @@ +Bootstrapping +============= + +El Bootstrapping hace referencia al proceso de preparar el entorno antes de que una aplicación se inicie para resolver y procesar una petición entrante. El se ejecuta en dos lugares: el [script de entrada](structure-entry-scripts.md) y la [aplicación](structure-applications.md). + +En el [script de entrada](structure-entry-scripts.md), se registran los cargadores automáticos de clase para diferentes librerías. Esto incluye el cargador automático de Composer a través de su fichero ‘autoload.php’ y del cargador automático de Yii a través del fichero de clase ‘Yii’. El script de entrada después carga la [configuración](concept-configurations.md) de la aplicación y crea una instancia de la [aplicación](structure-applications.md). + +El constructor de la aplicación, ejecuta el siguiente trabajo de bootstrapping: + +Llama a [[yii\base\Application::preInit()|preInit()]], que configura algunas propiedades de alta prioridad de la aplicación, como [[yii\base\Application::basePath|basePath]]. +Registra el [[yii\base\Application::errorHandler|error handler]]. +Inicializa las propiedades de aplicación usando la configuración de la aplicación dada. +Llama a [[yii\base\Application::init()|init()]] que a su vez llama a [[yii\base\Application::bootstrap()|bootstrap()]] para ejecutar componentes de bootstrapping. +Incluye el archivo de manifiesto de extensiones ‘vendor/yiisoft/extensions.php’ +Crea y ejecuta [componentes de bootstrap](structure-extensions.md#bootstrapping-classes) declarados por las extensiones. +Crea y ejecuta [componentes de aplicación](structure-application-components.md) y/o [módulos](structure-modules.md) que se declaran en la [propiedad bootstrap](structure-applications.md#bootstrap) de la aplicación. + +Debido a que el trabajo de bootstrapping se tiene que ejecutar antes de gestionar *todas* las peticiones, es muy importante mantener este proceso ligero y optimizado lo máximo que sea posible. + +Intenta no registrar demasiados componentes de bootstrapping. Un componente de bootstrapping sólo es necesario si tiene que interaccionar en todo el ciclo de vida de la gestión de la petición. Por ejemplo, si un modulo necesita registrar reglas de análisis de URL adicionales, se debe incluirse en la [propiedad bootstrap](structure-applications.md#bootstrap) para que la nueva regla de URL tenga efecto antes de que sea utilizada para resolver peticiones. + +En modo de producción, hay que habilitar la cache bytecode, así como [APC](https://www.php.net/manual/es/book.apc.php), para minimizar el tiempo necesario para incluir y analizar archivos PHP. + +Algunas grandes aplicaciones tienen [configuraciones](concept-configurations.md) de aplicación muy complejas que están dividida en muchos archivos de configuración más pequeños. diff --git a/docs/guide-es/runtime-handling-errors.md b/docs/guide-es/runtime-handling-errors.md new file mode 100644 index 00000000000..f272207abb5 --- /dev/null +++ b/docs/guide-es/runtime-handling-errors.md @@ -0,0 +1,223 @@ +Gestión de Errores +================== + +Yii incluye un [[yii\web\ErrorHandler|error handler]] que permite una gestión de errores mucho más práctica que +anteriormente. En particular, el gestor de errores de Yii hace lo siguiente para mejorar la gestión de errores: + +* Todos los errores no fatales (ej. advertencias (warning), avisos (notices)) se convierten en excepciones capturables. +* Las excepciones y los errores fatales de PHP se muestran con una pila de llamadas (call stack) de información + detallada y lineas de código fuente. +* Soporta el uso de [acciones de controlador](structure-controllers.md#actions) dedicadas para mostrar errores. +* Soporta diferentes formatos de respuesta (response) de errores. + +El [[yii\web\ErrorHandler|error handler]] esta habilitado de forma predeterminada. Se puede deshabilitar definiendo la +constante `YII_ENABLE_ERROR_HANDLER` con valor `false` en el [script de entrada (entry script)](structure-entry-scripts.md) de la aplicación. + + +## Uso del Gestor de Errores + +El [[yii\web\ErrorHandler|error handler]] se registra como un [componente de aplicación](structure-application-components.md) llamado `errorHandler`. +Se puede configurar en la configuración de la aplicación como en el siguiente ejemplo: + +```php +return [ + 'components' => [ + 'errorHandler' => [ + 'maxSourceLines' => 20, + ], + ], +]; +``` + +Con la anterior configuración, el numero del lineas de código fuente que se mostrará en las páginas de excepciones será como máximo de 20. + +Como se ha mencionado, el gestor de errores convierte todos los errores de PHP no fatales en excepciones capturables. +Esto significa que se puede usar el siguiente código para tratar los errores PHP: + +```php +use Yii; +use yii\base\ErrorException; + +try { + 10/0; +} catch (ErrorException $e) { + Yii::warning("Division by zero."); +} + +// la ejecución continua ... +``` + +Si se quiere mostrar una página de error que muestra al usuario que su petición no es válida o no es la esperada, se +puede simplemente lanzar una excepción de tipo [[yii\web\HttpException|HTTP exception]], como podría ser +[[yii\web\NotFoundHttpException]]. El gestor de errores establecerá correctamente el código de estado HTTP de la +respuesta y usará la vista de error apropiada para mostrar el mensaje. + +```php +use yii\web\NotFoundHttpException; + +throw new NotFoundHttpException(); +``` + + +## Personalizar la Visualización de Errores + +El [[yii\web\ErrorHandler|error handler]] ajusta la visualización del error conforme al valor de la constante `YII_DEBUG`. +Cuando `YII_DEBUG` es `true` (es decir, en modo depuración (debug)), el gestor de errores mostrara las +excepciones con una pila detallada de información y con lineas de código fuente para ayudar a depurar. Y cuando la variable `YII_DEBUG` es `false`, +solo se mostrará el mensaje de error para prevenir la revelación de información sensible de la aplicación. + +> Info: Si una excepción es descendiente de [[yii\base\UserException]], no se mostrará la pila de llamadas +independientemente del valor de `YII_DEBUG`. Esto es debido a que se considera que estas excepciones se deben a +errores cometidos por los usuarios y los desarrolladores no necesitan corregirlas. + +De forma predeterminada, el [[yii\web\ErrorHandler|error handler]] muestra los errores usando dos [vistas](structure-views.md): + +* `@yii/views/errorHandler/error.php`: se usa cuando deben mostrarse los errores SIN la información de la pila de + llamadas. Cuando `YII_DEBUG` es falos, este es el único error que se mostrara. +* `@yii/views/errorHandler/exception.php`: se usa cuando los errores deben mostrarse CON la información de la pila de llamadas. + +Se pueden configurar las propiedades [[yii\web\ErrorHandler::errorView|errorView]] y [[yii\web\ErrorHandler::exceptionView|exceptionView]] +el gestor de errores para usar nuestros propias vistas para personalizar la visualización de los errores. + + +### Uso de Acciones de Error + +Una mejor manera de personalizar la visualización de errores es usar un [acción](structure-controllers.md) de error +dedicada. Para hacerlo, primero se debe configurar la propiedad [[yii\web\ErrorHandler::errorAction|errorAction]] del +componente `errorHandler` como en el siguiente ejemplo: + +```php +return [ + 'components' => [ + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], + ] +]; +``` + +La propiedad [[yii\web\ErrorHandler::errorAction|errorAction]] vincula una [ruta](structure-controllers.md#routes) a +una acción. La configuración anterior declara que cuando un error tiene que mostrarse sin la pila de información de +llamadas, se debe ejecutar la acción `site/error`. + +Se puede crear una acción `site/error` como se hace a continuación, + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; + +class SiteController extends Controller +{ + public function actions() + { + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; + } +} +``` + +El código anterior define la acción `error` usando la clase [[yii\web\ErrorAction]] que renderiza un error usando la +vista llamada `error`. + +Además, usando [[yii\web\ErrorAction]], también se puede definir la acción `error` usando un método de acción como en el siguiente ejemplo, + +```php +public function actionError() +{ + $exception = Yii::$app->errorHandler->exception; + if ($exception !== null) { + return $this->render('error', ['exception' => $exception]); + } +} +``` + +Ahora se debe crear un archivo de vista ubicado en `views/sites/error.php`. En este archivo de vista, se puede acceder +a las siguientes variables si se define el error como un [[yii\web\ErrorAction]]: + +* `name`: el nombre del error; +* `message`: el mensaje del error; +* `exception`: el objeto de excepción a través del cual se puede obtener más información útil, tal como el código de + estado HTTP, el código de error, la pila de llamadas del error, etc. + +> Info: Tanto la [plantilla de aplicación básica](start-installation.md) como la [plantilla de aplicación avanzada](tutorial-advanced-app.md), +ya incorporan la acción de error y la vista de error. + +> Note: Si necesitas redireccionar en un gestor de error, hazlo de la siguiente manera: +> ```php +> Yii::$app->getResponse()->redirect($url)->send(); +> return; +> ``` + + +### Personalizar el Formato de Respuesta de Error + +El gestor de errores muestra los errores de siguiente la configuración del formato de las +[respuestas](runtime-responses.md). Si el [[yii\web\Response::format response format]] es `html`, se usará la vista de +error o excepción para mostrar los errores tal y como se ha descrito en la anterior subsección. Para otros tipos de +formatos de respuesta, el gestor de errores asignara la representación del array de la excepción a la propiedad +[[yii\web\Response::data]] que posteriormente podrá convertirse al formato deseado. Por ejemplo, si el formato de +respuesta es `json`, obtendremos la siguiente respuesta: + +``` +HTTP/1.1 404 Not Found +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 +} +``` + +Se puede personalizar el formato de respuestas de error respondiendo al evento `beforeSend` del componente `response` +en la configuración de la aplicación: + +```php +return [ + // ... + 'components' => [ + 'response' => [ + 'class' => 'yii\web\Response', + 'on beforeSend' => function ($event) { + $response = $event->sender; + if ($response->data !== null) { + $response->data = [ + 'success' => $response->isSuccessful, + 'data' => $response->data, + ]; + $response->statusCode = 200; + } + }, + ], + ], +]; +``` + +El código anterior reformateará la respuesta de error como en el siguiente ejemplo: + +``` +HTTP/1.1 200 OK +Date: Sun, 02 Mar 2014 05:31:43 GMT +Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y +Transfer-Encoding: chunked +Content-Type: application/json; charset=UTF-8 + +{ + "success": false, + "data": { + "name": "Not Found Exception", + "message": "The requested resource was not found.", + "code": 0, + "status": 404 + } +} +``` diff --git a/docs/guide-es/runtime-logging.md b/docs/guide-es/runtime-logging.md new file mode 100644 index 00000000000..bb01a21f8f6 --- /dev/null +++ b/docs/guide-es/runtime-logging.md @@ -0,0 +1,398 @@ +Registro de anotaciones +======================= + +Yii proporciona un poderoso framework dedicado al registro de anotaciones (logging) que es altamente personalizable y +extensible. Usando este framework se pueden guardar fácilmente anotaciones (logs) de varios tipos de mensajes, +filtrarlos, y unificarlos en diferentes destinos que pueden ser archivos, bases de datos o emails. + + +Usar el framework de registro de anotaciones de Yii involucra los siguientes pasos: + +* Registrar [mensajes de las anotaciones](#log-messages) en distintos lugares del código; +* Configurar los [destinos de las anotaciones](#log-targets) en la configuración de la aplicación para filtrar y + exportar los mensajes de las anotaciones; +* Examinar los mensajes filtrados de los las anotaciones exportadas para diferentes destinos + (ej. [Yii debugger](tool-debugger.md)). + +En esta sección, se describirán principalmente los dos primeros pasos. + +## Anotación de Messages + +Registrar mensajes de anotación es tan simple como llamar a uno de los siguientes métodos de registro de anotaciones. + +* [[Yii::debug()]]: registra un mensaje para trazar el funcionamiento de una sección de código. Se usa principalmente + para tareas de desarrollo. +* [[Yii::info()]]: registra un mensaje que transmite información útil. +* [[Yii::warning()]]: registra un mensaje de advertencia que indica que ha sucedido algo inesperado. +* [[Yii::error()]]: registra un error fatal que debe ser investigado tan pronto como sea posible. + +Estos métodos registran mensajes de varios *niveles de severidad* y *categorías*. Comparten el mismo registro de +función `function ($message, $category = 'application')`, donde `$message` representa el mensaje del registro que +tiene que ser registrado, mientras que `$category` es la categoría del registro de mensaje. El código del siguiente +ejemplo registra la huella del mensaje para la categoría `application`: + +```php +Yii::debug('start calculating average revenue'); +``` + +> Info: Los mensajes de registro pueden ser tanto cadenas de texto como datos complejos, como arrays u objetos. + Es responsabilidad de los [destinos de registros](#log-targets) tratar los mensajes de registro de manera apropiada. + De forma predeterminada, si un mensaje de registro no es una cadena de texto, se exporta como si fuera un string + llamando a [[yii\helpers\VarDumper::export()]]. + +Para organizar mejor y filtrar los mensajes de registro, se recomienda especificar una categoría apropiada para cada +mensaje de registro. Se puede elegir un sistema de nombres jerárquicos por categorías que facilite a los +[destino de registros](#log-targets) el filtrado de mensajes basándose en categorías. Una manera simple pero +efectiva de organizarlos es usar la constante predefinida (magic constant) de PHP `__METHOD__` como nombre de +categoría. Además este es el enfoque que se usa en el código del núcleo (core) del framework Yii. Por ejemplo, + +```php +Yii::debug('start calculating average revenue', __METHOD__); +``` + +La constante `__METHOD__` equivale al nombre del método (con el prefijo del nombre completo del nombre de clase) donde +se encuentra la constante. Por ejemplo, es igual a la cadena `'app\controllers\RevenueController::calculate'` si la +linea anterior de código se llamara dentro de este método. + +> Info: Los métodos de registro de anotaciones descritos anteriormente en realidad son accesos directos al + método [[yii\log\Logger::log()|log()]] del [[yii\log\Logger|logger object]] que es un singleton accesible a través + de la expresión `Yii::getLogger()`. Cuando se hayan registrado suficientes mensajes o cuando la aplicación haya + finalizado, el objeto de registro llamará [[yii\log\Dispatcher|message dispatcher]] para enviar los mensajes de + registro registrados a los [destiinos de registros](#log-targets). + +## Destino de Registros + +Un destino de registro es una instancia de la clase [[yii\log\Target]] o de una clase hija. Este filtra los +mensajes de registro por sus niveles de severidad y sus categorías y después los exporta a algún medio. Por ejemplo, +un [[yii\log\DbTarget|database target]] exporta los mensajes de registro filtrados a una tabla de base de datos, +mientras que un [[yii\log\EmailTarget|email target]] exporta los mensajes de registro a una dirección de correo +electrónico específica. + +Se pueden registrar múltiples destinos de registros en una aplicación configurándolos en la +[aplicación de componente](structure-application-components.md) `log` dentro de la configuración de aplicación, como +en el siguiente ejemplo: + +```php +return [ + // el componente log tiene que cargarse durante el proceso de bootstrapping + 'bootstrap' => ['log'], + + 'components' => [ + 'log' => [ + 'targets' => [ + [ + 'class' => 'yii\log\DbTarget', + 'levels' => ['error', 'warning'], + ], + [ + 'class' => 'yii\log\EmailTarget', + 'levels' => ['error'], + 'categories' => ['yii\db\*'], + 'message' => [ + 'from' => ['log@example.com'], + 'to' => ['admin@example.com', 'developer@example.com'], + 'subject' => 'Database errors at example.com', + ], + ], + ], + ], + ], +]; +``` + +> Note: El componente `log` debe cargarse durante el proceso de [bootstrapping](runtime-bootstrapping.md) para que +pueda enviar los mensajes de registro a los destinos inmediatamente. Este es el motivo por el que se lista en el +array `bootstrap` como se muestra más arriba. + +En el anterior código, se registran dos destinos de registros en la propiedad [[yii\log\Dispatcher::targets]] + +* el primer destino gestiona los errores y las advertencias y las guarda en una tabla de la base de datos; +* el segundo destino gestiona mensajes los mensajes de error de las categorías cuyos nombres empiecen por + `yii\db\` y los envía por email a las direcciones `admin@example.com` y `developer@example.com`. + +Yii incluye los siguientes destinos. En la API de documentación se pueden referencias a estas clases e +información de configuración y uso. + +* [[yii\log\DbTarget]]: almacena los mensajes de registro en una tabla de la base de datos. +* [[yii\log\EmailTarget]]: envía los mensajes de registro a direcciones de correo preestablecidas. +* [[yii\log\FileTarget]]: guarda los menajes de registro en archivos. +* [[yii\log\SyslogTarget]]: guarda los mensajes de registro en el syslog llamando a la función PHP `syslog()`. + +A continuación, se describirá las características más comunes de todos los destinos de registros. + +### Filtrado de Mensajes + +Se pueden configurar las propiedades [[yii\log\Target::levels|levels]] y [[yii\log\Target::categories|categories]] +para cada destino de registros, con estas se especifican los niveles de severidad y las categorías de mensajes que +deberán procesar sus destinos. + +La propiedad [[yii\log\Target::levels|levels]] es un array que consta de uno o varios de los siguientes valores: + +* `error`: correspondiente a los mensajes registrados por [[Yii::error()]]. +* `warning`: correspondiente a los mensajes registrados por [[Yii::warning()]]. +* `info`: correspondiente a los mensajes registrados por [[Yii::info()]]. +* `trace`: correspondiente a los mensajes registrados por [[Yii::debug()]]. +* `profile`: correspondiente a los mensajes registrados por [[Yii::beginProfile()]] y [[Yii::endProfile()]], que se + explicará más detalladamente en la subsección [Perfiles](#performance-profiling). + +Si no se especifica la propiedad [[yii\log\Target::levels|levels]], significa que el destino procesará los +mensajes de *cualquier* nivel de severidad. + +La propiedad [[yii\log\Target::categories|categories]] es un array que consta de categorías de mensaje o patrones. El +destino sólo procesará mensajes de las categorías que se puedan encontrar o si coinciden con algún patrón listado +en el array. Un patrón de categoría es un nombre de categoría al que se le añade un asterisco `*` al final. Un nombre +de categoría coincide con un patrón si empieza por el mismo prefijo que el patrón. Por ejemplo, +`yii\db\Command::execute` y `yii\db\Command::query` que se usan como nombres de categoría para los mensajes +registrados en la clase [[yii\db\Command]], coinciden con el patrón `yii\db\*`. + +Si no se especifica la propiedad [[yii\log\Target::categories|categories]], significa que el destino procesará +los mensajes de *todas* las categorías. + +Además añadiendo las categorías en listas blancas (whitelisting) mediante la propiedad +[[yii\log\Target::categories|categories]], también se pueden añadir ciertas categorías en listas negras (blacklist) +configurando la propiedad [[yii\log\Target::except|except]]. Si se encuentra la categoría de un mensaje o coincide +algún patrón con esta propiedad, NO será procesada por el destino. + +La siguiente configuración de destinos especifica que el destino solo debe procesar los mensajes de error y +de advertencia de las categorías que coincidan con alguno de los siguientes patrones `yii\db\*` o +`yii\web\HttpException:*`, pero no con `yii\web\HttpException:404`. + +```php +[ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], + 'categories' => [ + 'yii\db\*', + 'yii\web\HttpException:*', + ], + 'except' => [ + 'yii\web\HttpException:404', + ], +] +``` + +> Info: Cuando se captura una excepción de tipo HTTP por el [gestor de errores](runtime-handling-errors.md), se + registrará un mensaje de error con el nombre de categoría con formato `yii\web\HttpException:ErrorCode`. Por + ejemplo, la excepción [[yii\web\NotFoundHttpException]] causará un mensaje de error del tipo + `yii\web\HttpException:404`. + +### Formato de los Mensajes + +Los destinos exportan los mensajes de registro filtrados en cierto formato. Por ejemplo, is se instala un +destino de registros de la calse [[yii\log\FileTarget]], encontraremos un registro similar en el archivo de +registro `runtime/log/app.log`: + +``` +2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug +``` + +De forma predeterminada los mensajes de registro se formatearan por [[yii\log\Target::formatMessage()]] como en el +siguiente ejemplo: + +``` +Timestamp [IP address][User ID][Session ID][Severity Level][Category] Message Text +``` + +Se puede personalizar el formato configurando la propiedad [[yii\log\Target::prefix]] que es un PHP ejecutable y +devuelve un prefijo de mensaje personalizado. Por ejemplo, el siguiente código configura un destino de registro +anteponiendo a cada mensaje de registro el ID de usuario (se eliminan la dirección IP y el ID por razones de +privacidad). + +```php +[ + 'class' => 'yii\log\FileTarget', + 'prefix' => function ($message) { + $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null; + $userID = $user ? $user->getId(false) : '-'; + return "[$userID]"; + } +] +``` + +Además de prefijos de mensaje, destinos de registros también añaden alguna información de contexto en cada lote +de mensajes de registro. De forma predeterminada, se incluyen los valores de las siguientes variables globales de +PHP: `$_GET`, `$_POST`, `$_FILES`, `$_COOKIE`, `$_SESSION` y `$_SERVER`. Se puede ajustar el comportamiento +configurando la propiedad [[yii\log\Target::logVars]] con los nombres de las variables globales que se quieran incluir +con el destino del registro. Por ejemplo, la siguiente configuración de destino de registros especifica que +sólo se añadirá al mensaje de registro el valor de la variable `$_SERVER`. + +```php +[ + 'class' => 'yii\log\FileTarget', + 'logVars' => ['_SERVER'], +] +``` + +Se puede configurar `logVars` para que sea un array vacío para deshabilitar totalmente la inclusión de información de +contexto. O si se desea implementar un método propio de proporcionar información de contexto se puede sobrescribir el +método [[yii\log\Target::getContextMessage()]]. + +### Nivel de Seguimiento de Mensajes + +Durante el desarrollo, a veces se quiere visualizar de donde proviene cada mensaje de registro. Se puede lograr +configurando la propiedad [[yii\log\Dispatcher::traceLevel|traceLevel]] del componente `log` como en el siguiente +ejemplo: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [...], + ], + ], +]; +``` + +La configuración de aplicación anterior establece el [[yii\log\Dispatcher::traceLevel|traceLevel]] para que sea 3 si +`YII_DEBUG` esta habilitado y 0 si esta deshabilitado. Esto significa que si `YII_DEBUG` esta habilitado, a cada +mensaje de registro se le añadirán como mucho 3 niveles de la pila de llamadas del mensaje que se este registrando; y +si `YII_DEBUG` está deshabilitado, no se incluirá información de la pila de llamadas. + +> Info: Obtener información de la pila de llamadas no es trivial. Por lo tanto, sólo se debe usar esta + característica durante el desarrollo o cuando se depura la aplicación. + +### Liberación (Flushing) y Exportación de Mensajes + +Como se ha comentado anteriormente, los mensajes de registro se mantienen en un array por el +[[yii\log\Logger|logger object]]. Para limitar el consumo de memoria de este array, el componente encargado del +registro de mensajes enviará los mensajes registrados a los [destinos de registros](#log-targets) cada vez que el +array acumule un cierto número de mensajes de registro. Se puede personalizar el número configurando la propiedad +[[yii\log\Dispatcher::flushInterval|flushInterval]] del componente `log`: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'flushInterval' => 100, // el valor predeterminado es 1000 + 'targets' => [...], + ], + ], +]; +``` + +> Info: También se produce la liberación de mensajes cuando la aplicación finaliza, esto asegura que los + destinos de los registros reciban los mensajes de registro. + +Cuando el [[yii\log\Logger|logger object]] libera los mensajes de registro enviándolos a los +[destinos de registros](#log-targets), estos no se exportan inmediatamente. La exportación de mensajes solo se +produce cuando un destino de registros acumula un cierto número de mensajes filtrados. Se puede personalizar este +número configurando la propiedad [[yii\log\Target::exportInterval|exportInterval]] de un +[destinos de registros](#log-targets) individual, como se muestra a continuación, + +```php +[ + 'class' => 'yii\log\FileTarget', + 'exportInterval' => 100, // el valor predeterminado es 1000 +] +``` + +Debido al nivel de configuración de la liberación y exportación de mensajes, de forma predeterminada cuando se llama a +`Yii::debug()` o cualquier otro método de registro de mensajes, NO veremos el registro de mensaje inmediatamente en +los destinos de registros. Esto podría ser un problema para algunas aplicaciones de consola de ejecución +prolongada (long-running). Para hacer que los mensajes de registro aparezcan inmediatamente en los destinos de +registro se deben establecer [[yii\log\Dispatcher::flushInterval|flushInterval]] y +[[yii\log\Target::exportInterval|exportInterval]] para que tengan valor 1 como se muestra a continuación: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'flushInterval' => 1, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'exportInterval' => 1, + ], + ], + ], + ], +]; +``` + +> Note: El uso frecuente de liberación y exportación puede degradar el rendimiento de la aplicación. + +### Conmutación de Destinos de Registros + +Se puede habilitar o deshabilitar un destino de registro configuración su propiedad +[[yii\log\Target::enabled|enabled]]. Esto se puede llevar a cabo a mediante la configuración del destino de +registros o con la siguiente declaración PHP de código: + +```php +Yii::$app->log->targets['file']->enabled = false; +``` + +El código anterior requiere que se asocie un destino como `file`, como se muestra a continuación usando las +claves de texto en el array `targets`: + +```php +return [ + 'bootstrap' => ['log'], + 'components' => [ + 'log' => [ + 'targets' => [ + 'file' => [ + 'class' => 'yii\log\FileTarget', + ], + 'db' => [ + 'class' => 'yii\log\DbTarget', + ], + ], + ], + ], +]; +``` + +### Creación de Nuevos Destinos + +La creación de nuevas clases de destinos de registro es muy simple. Se necesita implementar el método +[[yii\log\Target::export()]] enviando el contenido del array [[yii\log\Target::messages]] al medio designado. Se puede +llamar al método [[yii\log\Target::formatMessage()]] para formatear los mensajes. Se pueden encontrar más detalles de +destinos de registros en las clases incluidas en la distribución de Yii. + +## Perfilado de Rendimiento + +El Perfilado de rendimiento es un tipo especial de registro de mensajes que se usa para medir el tiempo que tardan en +ejecutarse ciertos bloques de código y encontrar donde están los cuellos de botella de rendimiento. Por ejemplo, la +clase [[yii\db\Command]] utiliza el perfilado de rendimiento para encontrar conocer el tiempo que tarda cada consulta +a la base de datos. + +Para usar el perfilado de rendimiento, primero debemos identificar los bloques de código que tienen que ser +perfilados, para poder enmarcar su contenido como en el siguiente ejemplo: + +```php +\Yii::beginProfile('myBenchmark'); + +... Empieza el perfilado del bloque de código ... + +\Yii::endProfile('myBenchmark'); +``` + +Donde `myBenchmark` representa un token único para identificar el bloque de código. Después cuando se examine el +resulte del perfilado, se podrá usar este token para encontrar el tiempo que ha necesitado el correspondiente bloque +de código. + +Es importante asegurarse de que los pares de `beginProfile` y `endProfile` estén bien anidados. Por ejemplo, + +```php +\Yii::beginProfile('block1'); + + // código que será perfilado + + \Yii::beginProfile('block2'); + // más código para perfilar + \Yii::endProfile('block2'); + +\Yii::endProfile('block1'); +``` + +Si nos dejamos el `\Yii::endProfile('block1')` o lo intercambiamos `\Yii::endProfile('block1')` con +`\Yii::endProfile('block2')`, el perfilado de rendimiento no funcionará. + +Se registra un mensaje de registro con el nivel de severidad `profile` para cada bloque de código que se haya +perfilado. Se puede configurar el [destino del registro](#log-targets) para reunir todos los mensajes y exportarlos. +El [depurador de Yii](tool-debugger.md) incluye un panel de perfilado de rendimiento que muestra los resultados de +perfilado. diff --git a/docs/guide-es/runtime-overview.md b/docs/guide-es/runtime-overview.md new file mode 100644 index 00000000000..7ac529eda76 --- /dev/null +++ b/docs/guide-es/runtime-overview.md @@ -0,0 +1,24 @@ +Información General +=============== + +Cada vez que una aplicación Yii gestiona una petición, se somete a un flujo de trabajo similar. + +1. Un usuario hace una petición al [script de entrada](structure-entry-scripts.md) ‘web/index.php’. +2. El script de entrada carga la [configuración](concept-configurations.md) y crea una instancia de la + [aplicación](structure-applications.md) para gestionar la petición. +3. La aplicación resuelve la [ruta](runtime-routing.md) solicitada con la ayuda del componente + [petición](runtime-requests.md) de la aplicación. +4. La aplicación crea una instancia del [controlador](structure-controllers.md) para gestionar la petición. +5. El controlador crea una instancia de la [acción](structure-controllers.md) y ejecuta los filtros para la acción. +6. Si algún filtro falla, se cancela la acción. +7. Si pasa todos los filtros, se ejecuta la acción. +8. La acción carga un modelo de datos, posiblemente de la base de datos. +9. La acción renderiza una vista, proporcionándole el modelo de datos. +10. El resultado renderizado se devuelve al componente [respuesta](runtime-responses.md) de la aplicación. +11. El componente respuesta envía el resultado renderizado al navegador del usuario. + +El siguiente diagrama muestra como una aplicación gestiona una petición. + +![Request Lifecycle](images/request-lifecycle.png) + +En esta sección, se describirá en detalle cómo funcionan algunos de estos pasos. diff --git a/docs/guide-es/runtime-requests.md b/docs/guide-es/runtime-requests.md new file mode 100644 index 00000000000..52bb6d2c3f3 --- /dev/null +++ b/docs/guide-es/runtime-requests.md @@ -0,0 +1,141 @@ +Peticiones +========== + +Las peticiones (requests) hechas a una aplicación son representadas como objetos [[yii\web\Request]] que proporcionan +información como parámetros de la petición, cabeceras HTTP, cookies, etc. Dada una petición, se puede acceder al +objeto request correspondiente a través del [componente de aplicación](structure-application-components.md) `request` +que, por defecto, es una instancia de [[yii\web\Request]]. En esta sección se describirá como hacer uso de este +componente en las aplicaciones. + +## Parámetros de Request + +Para obtener los parámetros de la petición, se puede llamar a los métodos [[yii\web\Request::get()|get()]] y +[[yii\web\Request::post()|post()]] del componente `request`. Estos devuelven los valores de `$_GET` y `$_POST`, +respectivamente. Por ejemplo: + +```php +$request = Yii::$app->request; + +$get = $request->get(); +// equivalente a: $get = $_GET; + +$id = $request->get('id'); +// equivalente a: $id = isset($_GET['id']) ? $_GET['id'] : null; + +$id = $request->get('id', 1); +// equivalente a: $id = isset($_GET['id']) ? $_GET['id'] : 1; + +$post = $request->post(); +// equivalente a: $post = $_POST; + +$name = $request->post('name'); +// equivalente a: $name = isset($_POST['name']) ? $_POST['name'] : null; + +$name = $request->post('name', ''); +// equivalente a: $name = isset($_POST['name']) ? $_POST['name'] : ''; +``` + +> Info: En lugar de acceder directamente a `$_GET` y `$_POST` para obtener los parámetros de la petición, es + recomendable que se obtengan mediante el componente `request` como en el ejemplo anterior. Esto facilitará la + creación de tests ya que se puede simular una componente de request con datos de peticiones personalizados. + +Cuando se implementan [APIs RESTful](rest-quick-start.md), a menudo se necesita obtener parámetros enviados desde el +formulario a través de PUT, PATCH u otros [métodos de request](runtime-requests.md#request-methods). Se pueden obtener +estos parámetros llamando a los métodos [[yii\web\Request::getBodyParam()]]. Por ejemplo: + +```php +$request = Yii::$app->request; + +// devuelve todos los parámetros +$params = $request->bodyParams; + +// devuelve el parámetro "id" +$param = $request->getBodyParam('id'); +``` + +> Info: A diferencia de los parámetros `GET`, los parámetros enviados desde el formulario a través de `POST`, + `PUT`, `PATCH`, etc. se envían en el cuerpo de la petición. El componente `request` convierte los parámetros cuando + se acceda a él a través de los métodos descritos anteriormente. Se puede personalizar la manera en como los + parámetros se convierten configurando la propiedad [[yii\web\Request::parsers]]. + +## Métodos de Request + +Se puede obtener el método HTTP usado por la petición actual a través de la expresión `Yii::$app->request->method`. Se +proporcionan un conjunto de propiedades booleanas para comprobar si el método actual es de un cierto tipo. Por ejemplo: + +```php +$request = Yii::$app->request; + +if ($request->isAjax) { // la request es una request AJAX } +if ($request->isGet) { // el método de la request es GET } +if ($request->isPost) { // el método de la request es POST } +if ($request->isPut) { // el método de la request es PUT } +``` + +## URLs de Request + +El componente `request` proporciona muchas maneras de inspeccionar la URL solicitada actualmente. + +Asumiendo que la URL que se está solicitando es `https://example.com/admin/index.php/product?id=100`, se pueden obtener +varias partes de la URL explicadas en los siguientes puntos: + +* [[yii\web\Request::url|url]]: devuelve `/admin/index.php/product?id=100`, que es la URL sin la parte de información + del host. +* [[yii\web\Request::absoluteUrl|absoluteUrl]]: devuelve `https://example.com/admin/index.php/product?id=100`, que es + la URL entera, incluyendo la parte de información del host. +* [[yii\web\Request::hostInfo|hostInfo]]: devuelve `https://example.com`, que es la parte de información del host + dentro de la URL. +* [[yii\web\Request::pathInfo|pathInfo]]: devuelve `/product`, que es la parte posterior al script de entrada y + anterior al interrogante (query string) +* [[yii\web\Request::queryString|queryString]]: devuelve `id=100`, que es la parte posterior al interrogante. +* [[yii\web\Request::baseUrl|baseUrl]]: devuelve `/admin`, que es la parte posterior a la información del host y + anterior al nombre de script de entrada. +* [[yii\web\Request::scriptUrl|scriptUrl]]: devuelve `/admin/index.php`, que es la URL sin la información del la ruta + ni la query string. +* [[yii\web\Request::serverName|serverName]]: devuelve `example.com`, que es el nombre del host dentro de la URL. +* [[yii\web\Request::serverPort|serverPort]]: devuelve 80, que es el puerto que usa el servidor web. + +## Cabeceras HTTP + +Se pueden obtener la información de las cabeceras HTTP a través de [[yii\web\HeaderCollection|header collection]] +devueltas por la propiedad [[yii\web\Request::headers]]. Por ejemplo: + +```php +// $headers es un objeto de yii\web\HeaderCollection +$headers = Yii::$app->request->headers; + +// devuelve el valor Accept de la cabecera +$accept = $headers->get('Accept'); + +if ($headers->has('User-Agent')) { // la cabecera contiene un User-Agent } +``` + +El componente `request` también proporciona soporte para acceder rápidamente a las cabeceras usadas más comúnmente, +incluyendo: + +* [[yii\web\Request::userAgent|userAgent]]: devuelve el valor de la cabecera `User-Agen`. +* [[yii\web\Request::contentType|contentType]]: devuelve el valor de la cabecera `Content-Type` que indica el tipo + MIME de los datos del cuerpo de la petición. +* [[yii\web\Request::acceptableContentTypes|acceptableContentTypes]]: devuelve los tipos de contenido MIME aceptado + por los usuarios, ordenados por puntuación de calidad. Los que tienen mejor puntuación, se devolverán primero. +* [[yii\web\Request::acceptableLanguages|acceptableLanguages]]: devuelve los idiomas aceptados por el usuario. Los + idiomas devueltos son ordenados según su orden de preferencia. El primer elemento representa el idioma preferido. + +Si la aplicación soporta múltiples idiomas y se quiere mostrar las páginas en el idioma preferido por el usuario, se +puede usar el método de negociación de idioma [[yii\web\Request::getPreferredLanguage()]]. Este método obtiene una +lista de idiomas soportados por la aplicación, comparados con +[[yii\web\Request::acceptableLanguages|acceptableLanguages]], y devuelve el idioma más apropiado. + +> Tip: También se puede usar el filtro [[yii\filters\ContentNegotiator|ContentNegotiator]] para determinar +diatónicamente el content type y el idioma que debe usarse en la respuesta. El filtro implementa la negociación de +contenido en la parte superior de las propiedades y métodos descritos anteriormente. + +## Información del cliente + +Se puede obtener el nombre del host y la dirección IP de la máquina cliente a través de +[[yii\web\Request::userHost|userHost]] y [[yii\web\Request::userIP|userIP]], respectivamente. Por ejemplo: + +```php +$userHost = Yii::$app->request->userHost; +$userIP = Yii::$app->request->userIP; +``` diff --git a/docs/guide-es/runtime-responses.md b/docs/guide-es/runtime-responses.md new file mode 100644 index 00000000000..d24f7b7b90e --- /dev/null +++ b/docs/guide-es/runtime-responses.md @@ -0,0 +1,265 @@ +Respuestas +========== + +Cuando una aplicación finaliza la gestión de una [petición (request)](runtime-requests.md), genera un objeto +[[yii\web\Response|response]] y lo envía al usuario final. El objeto response contiene información tal como el código +de estado (status code) HTTP, las cabeceras (headers) HTTP y el cuerpo (body). El objetivo final del desarrollo de una +aplicación Web es esencialmente construir objetos response para varias peticiones. + +En la mayoría de casos principalmente se debe tratar con +[componentes de aplicación](structure-application-components.md) de tipo `response` que, por defecto, son una +instancia de [[yii\web\Response]]. Sin embargo, Yii permite crear sus propios objetos `response` y enviarlos al +usuario final tal y como se explica a continuación. + +En esta sección, se describirá como generar y enviar respuestas a usuarios finales. + +## Códigos de Estado + +Una de las primeras cosas que debería hacerse cuando se genera una respuesta es indicar si la petición se ha +gestionado correctamente. Esto se indica asignando la propiedad [[yii\web\Response::statusCode]] a la que se le puede +asignar cualquier valor válido dentro de los +[códigos de estado HTTP](https://tools.ietf.org/html/rfc2616#section-10). Por ejemplo, para indicar que la +petición se ha gestionado correctamente, se puede asignar el código de estado a 200, como en el siguiente ejemplo: + +```php +Yii::$app->response->statusCode = 200; +``` + +Sin embargo, en la mayoría de casos nos es necesario asignar explícitamente el código de estado. Esto se debe a que el +valor por defecto de [[yii\web\Response::statusCode]] es 200. Y si se quiere indicar que la petición ha fallado, se +puede lanzar una excepción HTTP apropiada como en el siguiente ejemplo: + +```php +throw new \yii\web\NotFoundHttpException; +``` + +Cuando el [error handler](runtime-handling-errors.md) captura una excepción, obtendrá el código de estado de la +excepción y lo asignará a la respuesta. En el caso anterior, la excepción [[yii\web\NotFoundHttpException]] está +asociada al estado HTTP 404. En Yii existen las siguientes excepciones predefinidas. + +* [[yii\web\BadRequestHttpException]]: código de estado 400. +* [[yii\web\ConflictHttpException]]: código de estado 409. +* [[yii\web\ForbiddenHttpException]]: código de estado 403. +* [[yii\web\GoneHttpException]]: código de estado 410. +* [[yii\web\MethodNotAllowedHttpException]]: código de estado 405. +* [[yii\web\NotAcceptableHttpException]]: código de estado 406. +* [[yii\web\NotFoundHttpException]]: código de estado 404. +* [[yii\web\ServerErrorHttpException]]: código de estado 500. +* [[yii\web\TooManyRequestsHttpException]]: código de estado 429. +* [[yii\web\UnauthorizedHttpException]]: código de estado 401. +* [[yii\web\UnsupportedMediaTypeHttpException]]: código de estado 415. + +Si la excepción que se quiere lanzar no se encuentra en la lista anterior, se puede crear una extendiendo +[[yii\web\HttpException]], o directamente lanzando un código de estado, por ejemplo: + +```php +throw new \yii\web\HttpException(402); +``` + +## Cabeceras HTTP + +Se puede enviar cabeceras HTTP modificando el [[yii\web\Response::headers|header collection]] en el componente +`response`. Por ejemplo: + +```php +$headers = Yii::$app->response->headers; + +// añade una cabecera Pragma. Las cabeceras Pragma existentes NO se sobrescribirán. +$headers->add('Pragma', 'no-cache'); + +// asigna una cabecera Pragma. Cualquier cabecera Pragma existente será descartada. +$headers->set('Pragma', 'no-cache'); + +// Elimina las cabeceras Pragma y devuelve los valores de las eliminadas en un array +$values = $headers->remove('Pragma'); +``` + +> Info: Los nombres de las cabeceras case insensitive, es decir, no discriminan entre mayúsculas y minúsculas. +Además, las nuevas cabeceras registradas no se enviarán al usuario hasta que se llame al método +[[yii\web\Response::send()]]. + +## Cuerpo de la Respuesta + +La mayoría de las respuestas deben tener un cuerpo que contenga el contenido que se quiere mostrar a los usuarios +finales. + +Si ya se tiene un texto de cuerpo con formato, se puede asignar a la propiedad [[yii\web\Response::content]] del +response. Por ejemplo: + +```php +Yii::$app->response->content = 'hello world!'; +``` + +Si se tiene que dar formato a los datos antes de enviarlo al usuario final, se deben asignar las propiedades +[[yii\web\Response::format|format]] y [[yii\web\Response::data|data]]. La propiedad [[yii\web\Response::format|format]] + especifica que formato debe tener [[yii\web\Response::data|data]]. Por ejemplo: + +```php +$response = Yii::$app->response; +$response->format = \yii\web\Response::FORMAT_JSON; +$response->data = ['message' => 'hello world']; +``` + +Yii soporta a los siguientes formatos de forma predeterminada, cada uno de ellos implementado por una clase +[[yii\web\ResponseFormatterInterface|formatter]]. Se pueden personalizar los formatos o añadir nuevos sobrescribiendo +la propiedad [[yii\web\Response::formatters]]. + +* [[yii\web\Response::FORMAT_HTML|HTML]]: implementado por [[yii\web\HtmlResponseFormatter]]. +* [[yii\web\Response::FORMAT_XML|XML]]: implementado por [[yii\web\XmlResponseFormatter]]. +* [[yii\web\Response::FORMAT_JSON|JSON]]: implementado por [[yii\web\JsonResponseFormatter]]. +* [[yii\web\Response::FORMAT_JSONP|JSONP]]: implementado por [[yii\web\JsonResponseFormatter]]. + +Mientras el cuerpo de la respuesta puede ser mostrado de forma explicita como se muestra a en el anterior ejemplo, en +la mayoría de casos se puede asignar implícitamente por el valor de retorno de los métodos de +[acción](structure-controllers.md). El siguiente, es un ejemplo de uso común: + +```php +public function actionIndex() +{ + return $this->render('index'); +} +``` + +La acción `index` anterior, devuelve el resultado renderizado de la vista `index`. El valor devuelto será recogido por +el componente `response`, se le aplicará formato y se enviará al usuario final. + +Por defecto, el formato de respuesta es [[yii\web\Response::FORMAT_HTML|HTML]], sólo se debe devolver un string en un +método de acción. Si se quiere usar un formato de respuesta diferente, se debe asignar antes de devolver los datos. +Por ejemplo: + +```php +public function actionInfo() +{ + \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; + return [ + 'message' => 'hello world', + 'code' => 100, + ]; +} +``` + +Como se ha mencionado, además de usar el componente de aplicación `response` predeterminado, también se pueden crear +objetos response propios y enviarlos a los usuarios finales. Se puede hacer retornando un objeto en el método de +acción, como en el siguiente ejemplo: + +```php +public function actionInfo() +{ + return \Yii::createObject([ + 'class' => 'yii\web\Response', + 'format' => \yii\web\Response::FORMAT_JSON, + 'data' => [ + 'message' => 'hello world', + 'code' => 100, + ], + ]); +} +``` + +> Note: Si se crea un objeto response propio, no se podrán aprovechar las configuraciones asignadas para el componente +`response` en la configuración de la aplicación. Sin embargo, se puede usar la +[inyección de dependencias](concept-di-container.md) para aplicar la configuración común al nuevo objeto response. + +## Redirección del Navegador + +La redirección del navegador se basa en el envío de la cabecera HTTP `Location`. Debido a que esta característica se +usa comúnmente, Yii proporciona soporte especial para ello. + +Se puede redirigir el navegador a una URL llamando al método [[yii\web\Response::redirect()]]. El método asigna la +cabecera de `Location` apropiada con la URL proporcionada y devuelve el objeto response él mismo. En un método de +acción, se puede acceder a él mediante el acceso directo [[yii\web\Controller::redirect()]] como en el siguiente +ejemplo: + +```php +public function actionOld() +{ + return $this->redirect('https://example.com/new', 301); +} +``` + +En el ejemplo anterior, el método de acción devuelve el resultado del método `redirect()`. Como se ha explicado antes, +el objeto response devuelto por el método de acción se usará como respuesta enviándola al usuario final. + +En otros sitios que no sean los métodos de acción, se puede llamar a [[yii\web\Response::redirect()]] directamente +seguido por una llamada al método [[yii\web\Response::send()]] para asegurar que no habrá contenido extra en la +respuesta. + +```php +\Yii::$app->response->redirect('https://example.com/new', 301)->send(); +``` + +> Info: De forma predeterminada, el método [[yii\web\Response::redirect()]] asigna el estado de respuesta al +código de estado 302 que indica al navegador que recurso solicitado está *temporalmente* alojado en una URI diferente. +Se puede enviar un código de estado 301 para expresar que el recurso se ha movido de forma *permanente*. + +Cuando la petición actual es de una petición AJAX, el hecho de enviar una cabecera `Location` no causará una +redirección del navegador automática. Para resolver este problema, el método [[yii\web\Response::redirect()]] asigna +una cabecera `X-Redirect` con el valor de la URL de redirección. En el lado del cliente se puede escribir código +JavaScript para leer la esta cabecera y redireccionar el navegador como corresponda. + +> Info: Yii contiene el archivo JavaScript `yii.js` que proporciona un conjunto de utilidades comunes de +JavaScript, incluyendo la redirección de navegador basada en la cabecera `X-Redirect`. Por tanto, si se usa este +fichero JavaScript (registrándolo *asset bundle* [[yii\web\YiiAsset]]), no se necesitará escribir nada más para tener +soporte en redirecciones AJAX. + +## Enviar Archivos + +Igual que con la redirección, el envío de archivos es otra característica que se basa en cabeceras HTTP especificas. +Yii proporciona un conjunto de métodos para dar soporte a varias necesidades del envío de ficheros. Todos ellos +incorporan soporte para el rango de cabeceras HTTP. + +* [[yii\web\Response::sendFile()]]: envía un fichero existente al cliente. +* [[yii\web\Response::sendContentAsFile()]]: envía un string al cliente como si fuera un archivo. +* [[yii\web\Response::sendStreamAsFile()]]: envía un *file stream* existente al cliente como si fuera un archivo. + +Estos métodos tienen la misma firma de método con el objeto response como valor de retorno. Si el archivo que se envía +es muy grande, se debe considerar usar [[yii\web\Response::sendStreamAsFile()]] porque es más efectivo en términos de +memoria. El siguiente ejemplo muestra como enviar un archivo en una acción de controlador. + +```php +public function actionDownload() +{ + return \Yii::$app->response->sendFile('ruta/del/fichero.txt'); +} +``` + +Si se llama al método de envío de ficheros fuera de un método de acción, también se debe llamar al método +[[yii\web\Response::send()]] después para asegurar que no se añada contenido extra a la respuesta. + +```php +\Yii::$app->response->sendFile('ruta/del/fichero.txt')->send(); +``` + +Algunos servidores Web tienen un soporte especial para enviar ficheros llamado *X-Sendfile*. La idea es redireccionar +la petición para un fichero a un servidor Web que servirá el fichero directamente. Como resultado, la aplicación Web +puede terminar antes mientras el servidor Web envía el fichero. Para usar esta funcionalidad, se puede llamar a +[[yii\web\Response::xSendFile()]]. La siguiente lista resume como habilitar la característica `X-Sendfile` para +algunos servidores Web populares. + +- Apache: [X-Sendfile](https://tn123.org/mod_xsendfile) +- Lighttpd v1.4: [X-LIGHTTPD-send-file](https://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) +- Lighttpd v1.5: [X-Sendfile](https://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) +- Nginx: [X-Accel-Redirect](https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/) +- Cherokee: [X-Sendfile and X-Accel-Redirect](https://www.cherokee-project.com/doc/other_goodies.html#x-sendfile) + +## Enviar la Respuesta + +El contenido en una respuesta no se envía al usuario hasta que se llama al método [[yii\web\Response::send()]]. De +forma predeterminada, se llama a este método automáticamente al final de [[yii\base\Application::run()]]. Sin embargo, +se puede llamar explícitamente a este método forzando el envío de la respuesta inmediatamente. + +El método [[yii\web\Response::send()]] sigue los siguientes pasos para enviar una respuesta: + +1. Lanza el evento [[yii\web\Response::EVENT_BEFORE_SEND]]. +2. Llama a [[yii\web\Response::prepare()]] para convertir el [[yii\web\Response::data|response data]] en + [[yii\web\Response::content|response content]]. +3. Lanza el evento [[yii\web\Response::EVENT_AFTER_PREPARE]]. +4. Llama a [[yii\web\Response::sendHeaders()]] para enviar las cabeceras HTTP registradas. +5. Llama a [[yii\web\Response::sendContent()]] para enviar el contenido del cuerpo de la respuesta. +6. Lanza el evento [[yii\web\Response::EVENT_AFTER_SEND]]. + +Después de llamar a [[yii\web\Response::send()]] por primera vez, cualquier llamada a este método será ignorada. Esto +significa que una vez se envíe una respuesta, no se le podrá añadir más contenido. + +Como se puede observar, el método [[yii\web\Response::send()]] lanza varios eventos útiles. Al responder a estos +eventos, es posible ajustar o decorar la respuesta. diff --git a/docs/guide-es/runtime-routing.md b/docs/guide-es/runtime-routing.md new file mode 100644 index 00000000000..1a538183ab2 --- /dev/null +++ b/docs/guide-es/runtime-routing.md @@ -0,0 +1,660 @@ +Enrutamiento y Creación de URLS +=============================== + +Cuando una aplicación Yii empieza a procesar una URL solicitada, lo primero que hace es convertir la URL en una +[ruta](structure-controllers.md#routes). Luego se usa la ruta para instanciar la +[acción de controlador](structure-controllers.md) correspondiente para gestionar la petición. A este proceso se +le llama *enrutamiento*. + +El proceso inverso se llama *creación de URLs*, y crea una URL a partir de una ruta dada y unos parámetros de consulta (query) asociados. Cuando posteriormente se solicita la URL creada, el proceso de enrutamiento puede resolverla y +convertirla en la ruta original con los parámetros asociados. + +La principal pieza encargada del enrutamiento y de la creación de URLs es [[yii\web\UrlManager|URL manager]], que se +registra como el componente de aplicación `urlManager`. El [[yii\web\UrlManager|URL manager]] proporciona el método +[[yii\web\UrlManager::parseRequest()|parseRequest()]] para convertir una petición entrante en una ruta y sus +parámetros asociados y el método [[yii\web\UrlManager::createUrl()|createUrl()]] para crear una URL a partir de una +ruta dada y sus parámetros asociados. + +Configurando el componente `urlManager` en la configuración de la aplicación, se puede dotar a la aplicación de +reconocimiento arbitrario de formatos de URL sin modificar el código de la aplicación existente. Por ejemplo, se +puede usar el siguiente código para crear una URL para la acción `post/view`: + +```php +use yii\helpers\Url; + +// Url::to() llama a UrlManager::createUrl() para crear una URL +$url = Url::to(['post/view', 'id' => 100]); +``` + +Dependiendo de la configuración de `urlManager`, la URL generada puede asemejarse a alguno de los siguientes (u otro) +formato. Y si la URL creada se solicita posteriormente, se seguirá convirtiendo en la ruta original y los valores de +los parámetros. + +``` +/index.php?r=post/view&id=100 +/index.php/post/100 +/posts/100 +``` + + +## Formatos de URL + +El [[yii\web\UrlManager|URL manager]] soporta dos formatos de URL: el formato predeterminado de URL y el formato URL +amigable (pretty URL). + +El formato de URL predeterminado utiliza un parámetro de consulta llamado `r` para representar la ruta y los +parámetros normales de la petición para representar los parámetros asociados con la ruta. Por ejemplo, la URL +`/index.php?r=post/view&id=100` representa la ruta `post/view` y 100 es el valor del parámetro `id` de la consulta. +El formato predeterminado de URL no requiere ningún tipo de configuración para [[yii\web\UrlManager|URL manager]] y +funciona en cualquier configuración de servidor Web. + +El formato de URL amigable utiliza la ruta adicional a continuación del nombre del script de entrada (entry script) +para representar la ruta y los parámetros de consulta. Por ejemplo, La ruta en la URL `/index.php/post/100` es +`/post/100` que puede representar la ruta `post/view` y el parámetro de consulta `id` 100 con una +[[yii\web\UrlManager::rules|URL rule]] apropiada. Para poder utilizar el formato de URL amigable, se tendrán que +diseñar una serie de [[yii\web\UrlManager::rules|URL rules]] de acuerdo con el requerimiento actual acerca de como +deben mostrarse las URLs. + +Se puede cambiar entre los dos formatos de URL conmutando la propiedad +[[yii\web\UrlManager::enablePrettyUrl|enablePrettyUrl]] del [[yii\web\UrlManager|URL manager]] sin cambiar ningún +otro código de aplicación. + +## Enrutamiento + +El Enrutamiento involucra dos pasos. El primero, la petición (request) entrante se convierte en una ruta y sus +parámetros de consulta asociados. En el segundo paso, se crea la correspondiente +[acción de controlador](structure-controllers.md) para la ruta convertida para que gestione la petición. + +Cuando se usa el formato predefinido de URL, convertir una petición en una ruta es tan simple como obtener los valores +del parámetro de consulta `GET` llamado `r`. + +Cuando se usa el formato de URL amigable, el [[yii\web\UrlManager|URL manager]] examinará las +[[yii\web\UrlManager::rules|URL rules]] registradas para encontrar alguna que pueda convertir la petición en una ruta. +Si no se encuentra tal regla, se lanzará una excepción de tipo [[yii\web\NotFoundHttpException]]. + +Una vez que la petición se ha convertido en una ruta, es el momento de crear la acción de controlador identificada +por la ruta. La ruta se desglosa en múltiples partes a partir de las barras que contenga. Por ejemplo, `site/index` +será desglosado en `site` e `index`. Cada parte is un ID que puede hacer referencia a un modulo, un controlador o una +acción. Empezando por la primera parte de la ruta, la aplicación, sigue los siguientes pasos para generar +(si los hay), controladores y acciones: + +1. Establece la aplicación como el modulo actual. +2. Comprueba si el [[yii\base\Module::controllerMap|controller map]] del modulo actual contiene un ID actual. Si lo + tiene, se creará un objeto controlador de acuerdo con la configuración del controlador encontrado en el mapa, y + se seguirá el Paso 5 para gestionar la parte restante de la ruta. +3. Comprueba si el ID hace referencia a un modulo listado en la propiedad [[yii\base\Module::modules|modules]] del + módulo actual. Si está listado, se crea un modulo de acuerdo con la configuración encontrada en el listado de + módulos, y se seguirá el Paso 2 para gestionar la siguiente parte de la ruta bajo el contexto de la creación de un + nuevo módulo. +4. Trata el ID como si se tratara de un ID de controlador y crea un objeto controlador. Sigue el siguiente paso con la parte restante de la ruta. +5. El controlador busca el ID en su [[yii\base\Controller::actions()|action map]]. Si lo encuentra, crea una acción de acuerdo con la configuración encontrada en el mapa. De otra forma, el controlador intenta crear una acción en linea definida por un método de acción correspondiente al ID actual. + +Si ocurre algún error entre alguno de los pasos anteriores, se lanzará una excepción de tipo +[[yii\web\NotFoundHttpException]], indicando el fallo de proceso de enrutamiento. + +### Ruta Predeterminada + +Cuando una petición se convierte en una ruta vacía, se usa la llamada *ruta predeterminada*. Por defecto, la ruta +predeterminada es `site/index`, que hace referencia a la acción `index` del controlador `site`. Se puede personalizar +configurando la propiedad [[yii\web\Application::defaultRoute|defaultRoute]] de la aplicación en la configuración de +aplicación como en el siguiente ejemplo: + +```php +[ + // ... + 'defaultRoute' => 'main/index', +]; +``` + + +### Ruta `catchAll` + +A veces, se puede querer poner la aplicación Web en modo de mantenimiento temporalmente y mostrar la misma pagina de +información para todas las peticiones. Hay varias maneras de lograr este objetivo. Pero una de las maneras más simples +es configurando la propiedad [[yii\web\Application::catchAll]] como en el siguiente ejemplo de configuración de +aplicación: + +```php +[ + // ... + 'catchAll' => ['site/offline'], +]; +``` + +Con la anterior configuración, se usar la acción `site/offline` para gestionar todas las peticiones entrantes. + +La propiedad `catchAll` debe tener un array cuyo primer elemento especifique una ruta, y el resto de elementos +(pares nombre-valor) especifiquen los parámetros [ligados a la acción](structure-controllers.md#action-parameters). + +## Creación de URLs + +Yii proporciona un método auxiliar (helper method) [[yii\helpers\Url::to()]] para crear varios tipos de URLs a partir +de las rutas dadas y sus parámetros de consulta asociados. Por ejemplo, + +```php +use yii\helpers\Url; + +// crea una URL para la ruta: /index.php?r=post/index +echo Url::to(['post/index']); + +// crea una URL para la ruta con parámetros: /index.php?r=post/view&id=100 +echo Url::to(['post/view', 'id' => 100]); + +// crea una URL interna: /index.php?r=post/view&id=100#contentecho +Url::to(['post/view', 'id' => 100, '#' => 'content']); + +// crea una URL absoluta: https://www.example.com/index.php?r=post/index +echo Url::to(['post/index'], true); + +// crea una URL absoluta usando el esquema https: https://www.example.com/index.php?r=post/index +echo Url::to(['post/index'], 'https'); +``` + +Hay que tener en cuenta que en el anterior ejemplo, asumimos que se está usando el formato de URL predeterminado. +Si habilita el formato de URL amigable, las URLs creadas serán diferentes, de acuerdo con las +[[yii\web\UrlManager::rules|URL rules]] que se usen. + +La ruta que se pasa al método [[yii\helpers\Url::to()]] es context sensitive. Esto quiere decir que puede ser una ruta +*relativa* o una ruta *absoluta* que serán tipificadas de acuerdo con las siguientes reglas: + +- Si una ruta es una cadena vacía, se usará la [[yii\web\Controller::route|route]] solicitada actualmente. +- Si la ruta no contiene ninguna barra `/`, se considerará que se trata de un ID de acción del controlador actual y se + le antepondrá el valor [[\yii\web\Controller::uniqueId|uniqueId]] del controlador actual. +- Si la ruta no tiene barra inicial, se considerará que se trata de una ruta relativa al modulo actual y se le + antepondrá el valor [[\yii\base\Module::uniqueId|uniqueId]] del modulo actual. + +Por ejemplo, asumiendo que el modulo actual es `admin` y el controlador actual es `post`, + +```php +use yii\helpers\Url; + +// la ruta solicitada: /index.php?r=admin/post/index +echo Url::to(['']); + +// una ruta relativa solo con ID de acción: /index.php?r=admin/post/index +echo Url::to(['index']); + +// una ruta relativa: /index.php?r=admin/post/index +echo Url::to(['post/index']); + +// una ruta absoluta: /index.php?r=post/index +echo Url::to(['/post/index']); +``` + +El método [[yii\helpers\Url::to()]] se implementa llamando a los métodos +[[yii\web\UrlManager::createUrl()|createUrl()]] y [[yii\web\UrlManager::createAbsoluteUrl()|createAbsoluteUrl()]] del +[[yii\web\UrlManager|URL manager]]. En las próximas sub-secciones, explicaremos como configurar el +[[yii\web\UrlManager|URL manager]] para personalizar el formato de las URLs generadas. + +El método [[yii\helpers\Url::to()]] también soporta la creación de URLs NO relacionadas con rutas particulares. +En lugar de pasar un array como su primer paramento, se debe pasar una cadena de texto. Por ejemplo, + +```php +use yii\helpers\Url; + +// la URL solicitada actualmente: /index.php?r=admin/post/index +echo Url::to(); + +// una URL con alias: https://example.comYii::setAlias('@example', 'https://example.com/'); +echo Url::to('@example'); + +// una URL absoluta: https://example.com/images/logo.gif +echo Url::to('/images/logo.gif', true);``` +``` + +Además del método `to()`, la clase auxiliar [[yii\helpers\Url]] también proporciona algunos otros métodos de creación +de URLs. Por ejemplo, + +```php +use yii\helpers\Url; + +// URL de la página inicial: /index.php?r=site/index +echo Url::home(); + +// la URL base, útil si la aplicación se desarrolla en una sub-carpeta de la carpeta raíz (root) Web +echo Url::base(); + +// la URL canónica de la actual URL solicitada// visitar https://en.wikipedia.org/wiki/Canonical_link_element +echo Url::canonical(); + +// recuerda la actual URL solicitada y la recupera más tarde requestsUrl::remember(); +echo Url::previous(); +``` + + +## Uso de URLs Amigables + +Para utilizar URLs amigables, hay que configurar el componente `ulrManager` en la configuración de la aplicación como +en el siguiente ejemplo: + +```php +[ + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'enableStrictParsing' => true, + 'rules' => [ + // ... + ], + ], + ], +] +``` + +La propiedad [[yii\web\UrlManager::enablePrettyUrl|enablePrettyUrl]] es obligatoria ya que alterna el formato de URL +amigable. El resto de propiedades son opcionales. Sin embargo, la anterior configuración es la más común. + +* [[yii\web\UrlManager::showScriptName|showScriptName]]: esta propiedad determina si el script de entrada debe ser + incluido en las URLs generadas. Por ejemplo, en lugar de crear una URL `/index.php/post/100`, estableciendo la + propiedad con valor `true`, la URL que se generará sera `/post/100`. +* [[yii\web\UrlManager::enableStrictParsing|enableStrictParsing]]: esta propiedad determina si se habilita la + conversión de petición estricta, si se habilita, la URL solicitada tiene que encajar al menos con uno de las + [[yii\web\UrlManager::rules|rules]] para poder ser tratada como una petición valida, o se lanzará una + [[yii\web\NotFoundHttpException]]. Si la conversión estricta esta deshabilitada, cuando ninguna de las + [[yii\web\UrlManager::rules|rules]] coincida con la URL solicitada, la parte de información de la URL se tratará + como si fuera la ruta solicitada. +* [[yii\web\UrlManager::rules|rules]]: esta propiedad contiene una lista de las reglas que especifican como convertir + y crear URLs. Esta es la propiedad principal con la que se debe trabajar para crear URLs que satisfagan el formato + de un requerimiento particular de la aplicación. + +> Note: Para ocultar el nombre del script de entrada en las URLs generadas, además de establecer el + [[yii\web\UrlManager::showScriptName|showScriptName]] a falso, puede ser necesaria la configuración del servidor Web + para que identifique correctamente que script PHP debe ejecutarse cuando se solicita una URL que no lo especifique. + Si se usa el servidor Web Apache, se puede utilizar la configuración recomendada descrita en la sección de + [Instalación](start-installation.md#recommended-apache-configuration). + + +### Reglas de URL + +Una regla de URL es una instancia de [[yii\web\UrlRule]] o de una clase hija. Cada URL consiste en un patrón utilizado +para cotejar la parte de información de ruta de las URLs, una ruta, y algunos parámetros de consulta. Una URL puede +usarse para convertir una petición si su patrón coincide con la URL solicitada y una regla de URL pude usarse para +crear una URL si su ruta y sus nombres de parámetros coinciden con los que se hayan dado. + +Cuando el formato de URL amigables está habilitado, el [[yii\web\UrlManager|URL manager]] utiliza las reglas de URL +declaradas en su propiedad [[yii\web\UrlManager::rules|rules]] para convertir las peticiones entrantes y crear URLs. +En particular, para convertir una petición entrante, el [[yii\web\UrlManager|URL manager]] examina las reglas en el +orden que se han declarado y busca la *primera* regla que coincida con la URL solicitada. La regla que coincide es la +que se usa para convertir la URL en una ruta y sus parámetros asociados. De igual modo, para crear una URL, el +[[yii\web\UrlManager|URL manager]] busca la primera regla que coincida con la ruta dad y los parámetros y la utiliza +para crear una URL. + +Se pueden configurar las [[yii\web\UrlManager::rules]] como un array con claves, siendo los patrones y las reglas sus +correspondientes rutas. Cada pareja patrón-ruta construye una regla de URL. Por ejemplo, la siguiente configuración de +configuración de [[yii\web\UrlManager::rules|rules]] declara dos reglas de URL. La primera regla coincide con una URL +`posts` y la mapea a la ruta `post/index`. La segunda regla coincide con una URL que coincida con la expresión regular +`post/(\d+)` y la mapea a la ruta `post/view` y el parámetro llamado `id`. + +```php +[ + 'posts' => 'post/index', + 'post/' => 'post/view', +] +``` + +> Información; El patrón en una regla se usa para encontrar coincidencias en la parte de información de la URL. + Por ejemplo, la parte de información de la ruta `/index.php/post/100?source=ad` es `post/100` + (la primera barra y la ultima son ignoradas) que coincide con el patrón `post/(\d+)`. + +Entre la declaración de reglas de URL como pares de patrón-ruta, también se pueden declarar como arrays de +configuración. Cada array de configuración se usa para configurar un único objeto de tipo regla de URL. Este proceso +se necesita a menudo cuando se quieren configurar otras propiedades de la regla de URL. Por ejemplo, + +```php +[ + // ... otras reglas de URL ... + + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], +] +``` + +De forma predeterminada si no se especifica la opción `class` en la configuración de una regla, se utilizará la clase +predeterminada [[yii\web\UrlRule]]. + + +### Parameters Asociativos + +Una regla de URL puede asociarse a una determinado grupo de parámetros de consulta que se hayan sido especificados en +el patrón con el formato ``, donde `ParamName` especifica el nombre del parámetro y `RegExp` es una +expresión regular opcional que se usa para encontrar los valores de los parámetros. Si no se especifica `RegExp` +significa que el parámetro debe ser una cadena de texto sin ninguna barra. + +> Note: Solo se pueden especificar expresiones regulares para los parámetros. La parte restante del patrón se + considera texto plano. + +Cuando se usa una regla para convertir una URL, esta rellenara los parámetros asociados con los valores que coincidan +con las partes correspondientes de la URL, y estos parámetros serán accesibles posteriormente mediante `$_GET` por el +componente de aplicación `request`. Cuando se usa una regla para crear una URL, esta obtendrá los valores de los +parámetros proporcionados y los insertara donde se hayan declarado los parámetros. + +Vamos a utilizar algunos ejemplos para ilustrar como funcionan los parámetros asociativos. Asumiendo que hemos +declarado las siguientes tres URLs: + +```php +[ + 'posts' => 'post/index', + 'post/' => 'post/view', + 'posts//' => 'post/index', +] +``` + +Cuando se usen las reglas para convertir URLs: + +- `/index.php/posts` se convierte en la ruta `post/index` usando la primera regla; +- `/index.php/posts/2014/php` se convierte en la ruta `post/index`, el parámetro `year` cuyo valor es 2014 y el + parámetro `category` cuyo valor es `php` usando la tercera regla; +- `/index.php/post/100` se convierte en la ruta `post/view` y el parámetro `id` cuyo valor es 100 usando la segunda + regla; +- `/index.php/posts/php` provocara una [[yii\web\NotFoundHttpException]] cuando + [[yii\web\UrlManager::enableStrictParsing]] sea `true`, ya que no coincide ninguno de los parámetros . Si + [[yii\web\UrlManager::enableStrictParsing]] es `false` (valor predeterminado), se devolverá como ruta la parte de + información `posts/php`. + +Y cuando las se usen las reglas para crear URLs: + +- `Url::to(['post/index'])` genera `/index.php/posts` usando la primera regla; +- `Url::to(['post/index', 'year' => 2014, 'category' => 'php'])` genera `/index.php/posts/2014/php` usando la tercera + regla; +- `Url::to(['post/view', 'id' => 100])` genera `/index.php/post/100` usando la segunda regla; +- `Url::to(['post/view', 'id' => 100, 'source' => 'ad'])` genera `/index.php/post/100?source=ad` usando la segunda + regla. Debido a que el parámetro `source` no se especifica en la regla, se añade como un parámetro de consulta en + la URL generada. +- `Url::to(['post/index', 'category' => 'php'])` genera `/index.php/post/index?category=php` no usa ninguna de las + reglas. Hay que tener en cuenta que si no se aplica ninguna de las reglas, la URL se genera simplemente añadiendo + la parte de información de la ruta y todos los parámetros como parte de la consulta. + + +### Parametrización de Rutas + +Se pueden incrustar nombres de parámetros en la ruta de una regla de URL. Esto permite a la regla de URL poder ser +usada para que coincida con varias rutas. Por ejemplo, la siguiente regla incrusta los parámetros `controller` y +`action` en las rutas. + +```php +[ + '//' => '/', + '/' => '/view', + 's' => '/index', +] +``` + +Para convertir una URL `index.php/comment/100/create`, se aplicará la primera regla, que establece el parámetro +`controller` a `comment` y el parámetro `action` a `create`. Por lo tanto la ruta `/` se resuelve +como `comment/create`. + +Del mismo modo, para crear una URL para una ruta `comment/index`, se aplicará la tercera regla, que crea una URL +`/index.php/comments`. + +> Info: Mediante la parametrización de rutas es posible reducir el numero de reglas de URL e incrementar + significativamente el rendimiento del [[yii\web\UrlManager|URL manager]]. + +De forma predeterminada, todos los parámetros declarados en una regla son requeridos. Si una URL solicitada no +contiene un parámetro en particular, o si se esta creando una URL sin un parámetro en particular, la regla no se +aplicará. Para establecer algunos parámetros como opcionales, se puede configurar la propiedad de +[[yii\web\UrlRule::defaults|defaults]] de una regla. Los parámetros listados en esta propiedad son opcionales y se +usarán los parámetros especificados cuando estos no se proporcionen. + +En la siguiente declaración de reglas, los parámetros `page` y `tag` son opcionales y cuando no se proporcionen, se +usarán los valores 1 y cadena vacía respectivamente. + +```php +[ + // ... otras reglas ... + [ + 'pattern' => 'posts//', + 'route' => 'post/index', + 'defaults' => ['page' => 1, 'tag' => ''], + ], +] +``` + +La regla anterior puede usarse para convertir o crear cualquiera de las siguientes URLs: + +* `/index.php/posts`: `page` es 1, `tag` es ''. +* `/index.php/posts/2`: `page` es 2, `tag` es ''. +* `/index.php/posts/2/news`: `page` es 2, `tag` es `'news'`. +* `/index.php/posts/news`: `page` es 1, `tag` es `'news'`. + +Sin usar ningún parámetro opcional, se tendrían que crear 4 reglas para lograr el mismo resultado. + + +### Reglas con Nombres de Servidor + +Es posible incluir nombres de servidores Web en los parámetros de las URLs. Esto es practico principalmente cuando una +aplicación debe tener distintos comportamientos paro diferentes nombres de servidores Web. Por ejemplo, las siguientes +reglas convertirán la URL `https://admin.example.com/login` en la ruta `admin/user/login` y +`https://www.example.com/login` en `site/login`. + +```php +[ + 'https://admin.example.com/login' => 'admin/user/login', + 'https://www.example.com/login' => 'site/login', +] +``` + +También se pueden incrustar parámetros en los nombres de servidor para extraer información dinámica de ellas. Por +ejemplo, la siguiente regla convertirá la URL `https://en.example.com/posts` en la ruta `post/index` y el parámetro +`language=en`. + +```php +[ + 'http://.example.com/posts' => 'post/index', +] +``` + +> Note: Las reglas con nombres de servidor NO deben incluir el subdirectorio del script de entrada (entry script) en + sus patrones. Por ejemplo, is la aplicación se encuentra en `https://www.example.com/sandbox/blog`, entonces se debe + usar el patrón `https://www.example.com/posts` en lugar de `https://www.example.com/sandbox/blog/posts`. Esto + permitirá que la aplicación se pueda desarrollar en cualquier directorio sin la necesidad de cambiar el código de la + aplicación. + +### Sufijos de URL + +Se puede querer añadir sufijos a las URLs para varios propósitos. Por ejemplo, se puede añadir `.html`a las URLs para +que parezcan URLs para paginas HTML estáticas; también se puede querer añadir `.json` a las URLs para indicar el tipo +de contenido que se espera encontrar en una respuesta (response). Se puede lograr este objetivo configurando la +propiedad [[yii\web\UrlManager::suffix]] como en el siguiente ejemplo de configuración de aplicación: + +```php +[ + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'enableStrictParsing' => true, + 'suffix' => '.html', + 'rules' => [ + // ... + ], + ], + ], +] +``` + +La configuración anterior permitirá al [[yii\web\UrlManager|URL manager]] reconocer las URLs solicitadas y a su vez +crear URLs con el sufijo `.html`. + +> Tip: Se puede establecer `/` como el prefijo de URL para que las URLs finalicen con una barra. + +> Note: Cuando se configura un sufijo de URL, si una URL solicitada no tiene el sufijo, se considerará como una URL + desconocida. Esta es una practica recomendada para SEO (optimización en motores de búsqueda). + +A veces, se pueden querer usar sufijos diferentes para URLs diferentes. Esto se puede conseguir configurando la +propiedad [[yii\web\UrlRule::suffix|suffix]] de una regla de URL individual. Cuando una regla de URL tiene la +propiedad establecida, anulará el sufijo estableciendo a nivel de [[yii\web\UrlManager|URL manager]]. Por ejemplo, la +siguiente configuración contiene una regla de URL personalizada que usa el sufijo `.json` en lugar del sufijo global +`.html`. + +```php +[ + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'enableStrictParsing' => true, + 'suffix' => '.html', + 'rules' => [ + // ... + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], + ], + ], + ], +] +``` + + +### Métodos HTTP + +Cuando se implementan APIs RESTful, normalmente se necesita que ciertas URLs se conviertan en otras de acuerdo con el +método HTTP que se esté usando. Esto se puede hacer fácilmente prefijando los métodos HTTP soportados como los +patrones de las reglas. Si una regla soporta múltiples métodos HTTP, hay que separar los nombres de los métodos con +comas. Por ejemplo, la siguiente regla usa el mismo patrón `post/` para dar soporte a diferentes métodos HTTP. +Una petición para `PUT post/100` se convertirá en `post/create`, mientras que una petición `GET post/100` se +convertirá en `post/view`. + +```php +[ + 'PUT,POST post/' => 'post/create', + 'DELETE post/' => 'post/delete', + 'post/' => 'post/view', +] +``` + +> Note: Si una regla de URL contiene algún método HTTP en su patrón, la regla solo se usará para aplicar conversiones. + Se omitirá cuando se llame a [[yii\web\UrlManager|URL manager]] para crear URLs. + +> Tip: Para simplificar el enrutamiento en APIs RESTful, Yii proporciona una clase de reglas de URL + [[yii\rest\UrlRule]] especial que es bastante eficiente y soporta ciertas características como pluralización de IDs + de controladores. Para conocer más detalles, se puede visitar la sección [Enrutamiento](rest-routing.md) acerca de + el desarrollo de APIs RESTful. + + +### Personalización de Reglas + +En los anteriores ejemplos, las reglas de URL se han declarado principalmente en términos de pares de patrón-ruta. +Este es un método de acceso directo que se usa a menudo. En algunos escenarios, se puede querer personalizar la regla +de URL configurando sus otras propiedades, tales como [[yii\web\UrlRule::suffix]]. Esto se puede hacer usando una +array completo de configuración para especificar una regla. El siguiente ejemplo se ha extraído de la subsección +[Sufijos de URL](#url-suffixes). + +```php +[ + // ... otras reglas de URL ... + + [ + 'pattern' => 'posts', + 'route' => 'post/index', + 'suffix' => '.json', + ], +] +``` + +> Info: De forma predeterminada si no se especifica una opción `class` para una configuración de regla, se + usará la clase predeterminada [[yii\web\UrlRule]]. + + +### Adición de Reglas Dinámicamente + +Las reglas de URL se pueden añadir dinámicamente en el [[yii\web\UrlManager|URL manager]]. A menudo se necesita por +[módulos](structure-modules.md) redistribubles que se encargan de gestionar sus propias reglas de URL. Para que las +reglas añadidas dinámicamente tenga efecto durante el proceso de enrutamiento, se deben añadir durante la etapa +[bootstrapping](runtime-bootstrapping.md). Para los módulos, esto significa que deben implementar +[[yii\base\BootstrapInterface]] y añadir las reglas en el método +[[yii\base\BootstrapInterface::bootstrap()|bootstrap()]] como en el siguiente ejemplo: + +```php +public function bootstrap($app) +{ + $app->getUrlManager()->addRules([ + // declaraciones de reglas aquí + ], false); +} +``` + +Hay que tener en cuenta se deben añadir estos módulos en [[yii\web\Application::bootstrap]] para que puedan participar +en el proceso de [bootstrapping](runtime-bootstrapping.md) + +### Creación de Clases de Reglas + +A pesar del hecho de que de forma predeterminada la clase [[yii\web\UrlRule]] lo suficientemente flexible para la +mayoría de proyectos, hay situaciones en las que se tiene que crear una clase de reglas propia. Por ejemplo, en un +sitio Web de un concesionario de coches, se puede querer dar soporte a las URL con el siguiente formato +`/Manufacturer/Model`, donde tanto `Manufacturer` como `Model` tengan que coincidir con algún dato almacenado una +tabla de la base de datos. De forma predeterminada, la clase regla no puede gestionar estas reglas ya que se base en +patrones estáticos declarados. + +Podemos crear la siguiente clase de reglas de URL para solucionar el problema. + +```php +namespace app\components; + +use yii\web\UrlRuleInterface; +use yii\base\BaseObject; + +class CarUrlRule extends BaseObject implements UrlRuleInterface +{ + + public function createUrl($manager, $route, $params) + { + if ($route === 'car/index') { + if (isset($params['manufacturer'], $params['model'])) { + return $params['manufacturer'] . '/' . $params['model']; + } elseif (isset($params['manufacturer'])) { + return $params['manufacturer']; + } + } + return false; // no se aplica esta regla + } + + public function parseRequest($manager, $request) + { + $pathInfo = $request->getPathInfo(); + if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) { + // comprueba $matches[1] y $matches[3] para ver + // si coincide con un *manufacturer* y un *model* en la base de datos + // Si coinciden, establece $params['manufacturer'] y/o $params['model'] + // y devuelve ['car/index', $params] + } + return false; // no se aplica la regla + } +} +``` + +Y usa la nueva clase de regla en la configuración de [[yii\web\UrlManager::rules]]: + +```php +[ + // ... otras reglas ... + + [ + 'class' => 'app\components\CarUrlRule', + // ... configura otras propiedades ... + ], +] +``` + +## Consideración del Rendimiento + +Cuando se desarrolla una aplicación Web compleja, es importante optimizar las reglas de URL para que tarden el mínimo +tiempo posible en convertir las peticiones y crear URLs. + +Usando rutas parametrizadas se puede reducir el numero de reglas de URL que a su vez significa una mejor en el +rendimiento. + +Cuando se convierten o crean URLs, el [[yii\web\UrlManager|URL manager]] examina las reglas de URL en el orden en que +han sido declaradas. Por lo tanto, se debe tener en cuenta el orden de las reglas de URL y anteponer las reglas más +especificas y/o las que se usen más a menudo. + +Si algunas URLs comparten el mismo prefijo en sus patrones o rutas, se puede considerar usar [[yii\web\GroupUrlRule]] +ya que puede ser más eficiente al ser examinado por [[yii\web\UrlManager|URL manager]] como un grupo. Este suele ser +el caso cuando una aplicación se compone por módulos, y cada uno tiene su propio conjunto de reglas con un ID de +módulo para sus prefijos más comunes. diff --git a/docs/guide-es/runtime-sessions-cookies.md b/docs/guide-es/runtime-sessions-cookies.md new file mode 100644 index 00000000000..c409fa05328 --- /dev/null +++ b/docs/guide-es/runtime-sessions-cookies.md @@ -0,0 +1,291 @@ +Sesiones (Sessions) y Cookies +============================= + +Las sesiones y las cookies permiten la persistencia de datos a través de múltiples peticiones de usuario. En PHP plano, debes acceder a ellos a través de las variables globales `$_SESSION` y `$_COOKIE`, respectivamente. Yii encapsula las sesiones y las cookies como objetos y por lo tanto te permite acceder a ellos de manera orientada a objetos con estupendas mejoras adicionales. + + +## Sesiones + +Como las [peticiones](runtime-requests.md) y las [respuestas](runtime-responses.md), puedes acceder a las sesiones vía el [componente de la aplicación](structure-application-components.md) `session` el cual es una instancia de [[yii\web\Session]], por defecto. + + +### Abriendo y cerrando sesiones + +Para abrir y cerrar una sesión, puedes hacer lo siguiente: + +```php +$session = Yii::$app->session; + +// comprueba si una sesión está ya abierta +if ($session->isActive) ... + +// abre una sesión +$session->open(); + +// cierra una sesión +$session->close(); + +// destruye todos los datos registrados por la sesión. +$session->destroy(); +``` + +Puedes llamar a [[yii\web\Session::open()|open()]] y [[yii\web\Session::close()|close()]] múltiples veces sin causar errores. Esto ocurre porque internamente los métodos verificarán primero si la sesión está ya abierta. + + +### Accediendo a los datos de sesión + +Para acceder a los datos almacenados en sesión, puedes hacer lo siguiente: + +```php +$session = Yii::$app->session; + +// devuelve una variable de sesión. Los siguientes usos son equivalentes: +$language = $session->get('language'); +$language = $session['language']; +$language = isset($_SESSION['language']) ? $_SESSION['language'] : null; + +// inicializa una variable de sesión. Los siguientes usos son equivalentes: +$session->set('language', 'en-US'); +$session['language'] = 'en-US'; +$_SESSION['language'] = 'en-US'; + +// remueve la variable de sesión. Los siguientes usos son equivalentes: +$session->remove('language'); +unset($session['language']); +unset($_SESSION['language']); + +// comprueba si una variable de sesión existe. Los siguientes usos son equivalentes: +if ($session->has('language')) ... +if (isset($session['language'])) ... +if (isset($_SESSION['language'])) ... + +// recorre todas las variables de sesión. Los siguientes usos son equivalentes: +foreach ($session as $name => $value) ... +foreach ($_SESSION as $name => $value) ... +``` + +> Info: Cuando accedas a los datos de sesión a través del componente `session`, una sesión será automáticamente abierta si no lo estaba antes. Esto es diferente accediendo a los datos de sesión a través de `$_SESSION`, el cual requiere llamar explícitamente a `session_start()`. + +Cuando trabajas con datos de sesiones que son arrays, el componte `session` tiene una limitación que te previene directamente de modificar un elemento del array. Por ejemplo, + +```php +$session = Yii::$app->session; + +// el siguiente código no funciona +$session['captcha']['number'] = 5; +$session['captcha']['lifetime'] = 3600; + +// el siguiente código funciona: +$session['captcha'] = [ + 'number' => 5, + 'lifetime' => 3600, +]; + +// el siguiente código también funciona: +echo $session['captcha']['lifetime']; +``` + +Puedes usar las siguientes soluciones para arreglar este problema: + +```php +$session = Yii::$app->session; + +// directamente usando $_SESSION (asegura te de que Yii::$app->session->open() ha sido llamado) +$_SESSION['captcha']['number'] = 5; +$_SESSION['captcha']['lifetime'] = 3600; + +// devuelve el valor del array, lo modifica y a continuación lo guarda +$captcha = $session['captcha']; +$captcha['number'] = 5; +$captcha['lifetime'] = 3600; +$session['captcha'] = $captcha; + +// usa un ArrayObject en vez de un array +$session['captcha'] = new \ArrayObject; +... +$session['captcha']['number'] = 5; +$session['captcha']['lifetime'] = 3600; + +// almacena los datos en un array con un prefijo común para las claves +$session['captcha.number'] = 5; +$session['captcha.lifetime'] = 3600; +``` + +Para un mejor rendimiento y legibilidad del código, recomendamos la última solución. Es decir, en vez de almacenar un array como una única variable de sesión, almacena cada elemento del array como una variable de sesión que comparta el mismo prefijo clave con otros elementos del array. + + +### Personalizar el almacenamiento de sesión + +Por defecto la clase [[yii\web\Session]] almacena los datos de sesión como ficheros en el servidor. Yii también provee de las siguientes clases de sesión que implementan diferentes almacenamientos de sesión: + +* [[yii\web\DbSession]]: almacena los datos de sesión en una tabla en la base de datos. +* [[yii\web\CacheSession]]: almacena los datos de sesión en una caché con la ayuda de la configuración del [componente caché](caching-data.md#cache-components). +* [[yii\redis\Session]]: almacena los datos de sesión usando [redis](https://redis.io/) como medio de almacenamiento. +* [[yii\mongodb\Session]]: almacena los datos de sesión en [MongoDB](https://www.mongodb.com/). + +Todas estas clases de sesión soportan los mismos métodos de la API. Como consecuencia, puedes cambiar el uso de diferentes almacenamientos de sesión sin la necesidad de modificar el código de tu aplicación que usa sesiones. + +> Note: si quieres acceder a los datos de sesión vía `$_SESSION` mientras estás usando un almacenamiento de sesión personalizado, debes asegurar te que la sesión está ya empezada por [[yii\web\Session::open()]]. Esto ocurre porque los manipuladores de almacenamiento de sesión personalizado son registrados sin este método. + +Para aprender como configurar y usar estas clases de componentes, por favor consulte la documentación de la API. Abajo está un ejemplo que muestra como configurar [[yii\web\DbSession]] en la configuración de la aplicación para usar una tabla en la base de datos como almacenamiento de sesión: + +```php +return [ + 'components' => [ + 'session' => [ + 'class' => 'yii\web\DbSession', + // 'db' => 'mydb', // el identificador del componente de aplicación DB connection. Por defecto'db'. + // 'sessionTable' => 'my_session', // nombre de la tabla de sesión. Por defecto 'session'. + ], + ], +]; +``` + +También es necesario crear la siguiente tabla de la base de datos para almacenar los datos de sesión: + +```sql +CREATE TABLE session +( + id CHAR(40) NOT NULL PRIMARY KEY, + expire INTEGER, + data BLOB +) +``` + +donde 'BLOB' se refiere al BLOB-type de tu DBMS preferida. Abajo está el tipo BLOB que puedes usar para algunos DBMS populares: + +- MySQL: LONGBLOB +- PostgreSQL: BYTEA +- MSSQL: BLOB + +> Note: De acuerdo con la configuración de php.ini `session.hash_function`, puedes necesitar ajustar el tamaño de la columna `id`. Por ejemplo, si `session.hash_function=sha256`, deberías usar el tamaño 64 en vez de 40. + + +### Flash Data + +Flash data es una clase especial de datos de sesión que, una vez se inicialice en la primera petición, estará sólo disponible durante la siguiente petición y automáticamente se borrará después. Flash data es comúnmente usado para implementar mensajes que deberían ser mostrados una vez a usuarios finales, tal como mostrar un mensaje de confirmación después de que un usuario envíe un formulario con éxito. + +Puedes inicializar y acceder a flash data a través del componente de aplicación `session`. Por ejemplo, + +```php +$session = Yii::$app->session; + +// Petición #1 +// inicializa el mensaje flash nombrado como "postDeleted" +$session->setFlash('postDeleted', 'You have successfully deleted your post.'); + +// Petición #2 +// muestra el mensaje flash nombrado "postDeleted" +echo $session->getFlash('postDeleted'); + +// Petición #3 +// $result será `false` ya que el mensaje flash ha sido borrado automáticamente +$result = $session->hasFlash('postDeleted'); +``` + +Al igual que los datos de sesión regulares, puede almacenar datos arbitrarios como flash data. + +Cuando llamas a [[yii\web\Session::setFlash()]], sobrescribirá cualquier Flash data que tenga el mismo nombre. +Para añadir un nuevo flash data a el/los existes con el mismo nombre, puedes llamar a [[yii\web\Session::addFlash()]]. +Por ejemplo: + +```php +$session = Yii::$app->session; + +// Petición #1 +// añade un pequeño mensaje flash bajo el nombre de "alerts" +$session->addFlash('alerts', 'You have successfully deleted your post.'); +$session->addFlash('alerts', 'You have successfully added a new friend.'); +$session->addFlash('alerts', 'You are promoted.'); + +// Petición #2 +// $alerts es un array de mensajes flash bajo el nombre de "alerts" +$alerts = $session->getFlash('alerts'); +``` + +> Note: Intenta no usar a la vez [[yii\web\Session::setFlash()]] con [[yii\web\Session::addFlash()]] para flash data + del mismo nombre. Esto ocurre porque el último método elimina el flash data dentro del array así que puedes añadir un nuevo flash data con el mismo nombre. Como resultado, cuando llamas a [[yii\web\Session::getFlash()]], puedes encontrarte algunas veces que te está devolviendo un array mientras que otras veces te está devolviendo un string, esto depende del orden que invoques a estos dos métodos. + + +## Cookies + +Yii representa cada cookie como un objeto de [[yii\web\Cookie]]. Tanto [[yii\web\Request]] como [[yii\web\Response]] +mantienen una colección de cookies vía la propiedad de llamada `cookies`. La colección de cookie en la antigua representación son enviadas en una petición, mientras la colección de cookie en esta última representa las cookies que van a ser enviadas al usuario. + + +### Leyendo Cookies + +Puedes recuperar las cookies en la petición actual usando el siguiente código: + +```php +// devuelve la colección de cookie (yii\web\CookieCollection) del componente "request" +$cookies = Yii::$app->request->cookies; + +// devuelve el valor "language" de la cookie. Si la cookie no existe, retorna "en" como valor por defecto. +$language = $cookies->getValue('language', 'en'); + +// una manera alternativa de devolver el valor "language" de la cookie +if (($cookie = $cookies->get('language')) !== null) { + $language = $cookie->value; +} + +// puedes también usar $cookies como un array +if (isset($cookies['language'])) { + $language = $cookies['language']->value; +} + +// comprueba si hay una cookie con el valor "language" +if ($cookies->has('language')) ... +if (isset($cookies['language'])) ... +``` + + +### Enviando Cookies + +Puedes enviar cookies a usuarios finales usando el siguiente código: + +```php +// devuelve la colección de cookie (yii\web\CookieCollection) del componente "response" +$cookies = Yii::$app->response->cookies; + +// añade una nueva cookie a la respuesta que se enviará +$cookies->add(new \yii\web\Cookie([ + 'name' => 'language', + 'value' => 'zh-CN', +])); + +// remueve una cookie +$cookies->remove('language'); +// equivalente a lo siguiente +unset($cookies['language']); +``` + +Además de [[yii\web\Cookie::name|name]], [[yii\web\Cookie::value|value]] las propiedades que se muestran en los anteriores ejemplos, la clase [[yii\web\Cookie]] también define otras propiedades para representar toda la información posible de las cookies, tal como [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]]. Puedes configurar estas propiedades según sea necesario para preparar una cookie y luego añadirlo a la colección de cookies de la respuesta. + +> Note: Para mayor seguridad, el valor por defecto de [[yii\web\Cookie::httpOnly]] es `true`. Esto ayuda a mitigar el riesgo del acceso a la cookie protegida por script desde el lado del cliente (si el navegador lo soporta). Puedes leer el [httpOnly wiki article](https://owasp.org/www-community/HttpOnly) para más detalles. + + +### Validación de la Cookie + +Cuando estás leyendo y enviando cookies a través de los componentes `request` y `response` como mostramos en las dos últimas subsecciones, cuentas con el añadido de seguridad de la validación de cookies el cual protege las cookies de ser modificadas en el lado del cliente. Esto se consigue con la firma de cada cookie con una cadena hash, el cual permite a la aplicación saber si una cookie ha sido modificada en el lado del cliente o no. Si es así, la cookie no será accesible a través de [[yii\web\Request::cookies|cookie collection]] del componente `request`. + +> Info: Si falla la validación de una cookie, aún puedes acceder a la misma a través de `$_COOKIE`. Esto sucede porque librerías de terceros pueden manipular de forma propia las cookies, lo cual no implica la validación de las mismas. + +La validación de cookies es habilitada por defecto. Puedes desactivar lo ajustando la propiedad [[yii\web\Request::enableCookieValidation]] a `false`, aunque se recomienda encarecidamente que no lo haga. + +> Note: Las cookies que son directamente leídas/enviadas vía `$_COOKIE` y `setcookie()` no serán validadas. + +Cuando estás usando la validación de cookie, puedes especificar una [[yii\web\Request::cookieValidationKey]] el cual se usará para generar los strings hash mencionados anteriormente. Puedes hacerlo mediante la configuración del componente `request` en la configuración de la aplicación: + +```php +return [ + 'components' => [ + 'request' => [ + 'cookieValidationKey' => 'fill in a secret key here', + ], + ], +]; +``` + +> Info: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] es crítico para la seguridad de tu aplicación. + Sólo debería ser conocido por personas de confianza. No lo guardes en sistemas de control de versiones. diff --git a/docs/guide-es/security-authentication.md b/docs/guide-es/security-authentication.md new file mode 100644 index 00000000000..1a75e505788 --- /dev/null +++ b/docs/guide-es/security-authentication.md @@ -0,0 +1,167 @@ +Authentication +============== + +La autenticación es el proceso de verificar la identidad de un usuario. Usualmente se usa un identificador (ej. un `username` o una dirección de correo electrónico) y una token secreto (ej. una contraseña o un token de acceso) para juzgar si el usuario es quien dice ser. La autenticación es la base de la función de inicio de sesión. + +Yii proporciona un marco de autenticación que conecta varios componentes para soportar el inicio de sesión. Para utilizar este marco, usted necesita principalmente hacer el siguiente trabajo: + +* Configurar el componente de la aplicación [[yii\web\User|user]]; +* Crear una clase que implemente la interfaz [[yii\web\IdentityInterface]]. + +## Configurando [[yii\web\User]] + +El componente [[yii\web\User|user]] gestiona el estado de autenticación del usuario. Requiere que especifiques una [[yii\web\User::identityClass|clase de identidad]] la cual contiene la lógica de autenticación. En la siguiente configuración de la aplicación, la [[yii\web\User::identityClass|clase identity]] para [[yii\web\User|user]] está configurada para ser `app\models\User` cuya implementación se explica en la siguiente subsección: + +```php +return [ + 'components' => [ + 'user' => [ + 'identityClass' => 'app\models\User', + ], + ], +]; +``` + +## Implementando [[yii\web\IdentityInterface]] +La [[yii\web\User::identityClass|clase identity]] debe implementar la [[yii\web\IdentityInterface]] que contiene los siguientes métodos: +* [[yii\web\IdentityInterface::findIdentity()|findIdentity()]]: busca una instancia de la clase identidad usando el ID de usuario especificado. Este método se utiliza cuando se necesita mantener el estado de inicio de sesión (login) a través de la sesión. + +* [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]: busca una instancia de la clase de identidad usando el token de acceso especificado. Este método se utiliza cuando se necesita autenticar a un usuario mediante un único token secreto (ej. en una aplicación RESTful sin estado). +* [[yii\web\IdentityInterface::getId()|getId()]]: devuelve el ID del usuario representado por esta instancia de identidad. +* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: devuelve una clave utilizada para validar la sesión y el auto-login en caso de que esté habilitado. +* [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: implementa la lógica para verificar la clave de autenticación. + +Si no se necesita un método en particular, se podría implementar con un cuerpo vacío, Por ejemplo, Si un método en particular no es necesario, puedes implementarlo con un cuerpo vacío. Por ejemplo, si tu aplicación es una aplicación RESTful pura sin estado, sólo necesitarás implementar [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]] y [[yii\web\IdentityInterface::getId()|getId()]] dejando el resto de métodos con un cuerpo vacío. O si tu aplicación utiliza autenticación sólo de sesión, necesitarías implementar todos los métodos excepto findIdentityByAccessToken(). + +En el siguiente ejemplo, una clase [[yii\web\User::identityClass|identity]] es implementada como una clase [Active Record](db-active-record.md) asociada con la tabla de base de datos `user`. + +```php + $token]); + } + + /** + * @return int|string ID del usuario actual + */ + public function getId() + { + return $this->id; + } + + /** + * @return string|null llave de autenticación del usuario actual + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @param string $authKey + * @return bool|null si la llave de autenticación es válida para el usuario actual + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey; + } +} +``` + +Puede utilizar el siguiente código para generar una clave de autenticación para cada usuario y almacenarla en la tabla `user`: + +```php +class User extends ActiveRecord implements IdentityInterface +{ + ...... + + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + if ($this->isNewRecord) { + $this->auth_key = \Yii::$app->security->generateRandomString(); + } + return true; + } + return false; + } +} +``` + +> Nota: No confundas la clase de identidad `User` con [[yii\web\User]]. La primera es la clase que implementa la lógica de autenticación. Suele implementarse como una clase [Active Record](db-active-record.md) asociada a algún almacenamiento persistente para guardar la información de las credenciales del usuario. Esta última es una clase de componente de aplicación responsable de gestionar el estado de autenticación del usuario. + +## Usando [[yii\web\User]] +Principalmente se usa [[yii\web\User]] en términos del componente de aplicación `user`. + +Puede detectar la identidad del usuario actual usando la expresión `Yii::$app->user->identity`. Devuelve una instancia de la clase [[yii\web\User::identityClass|identity]] que representa al usuario actualmente conectado, o `null` si el usuario actual no está autenticado (es decir, es un invitado). El siguiente código muestra como recuperar otra información relacionada con la autenticación desde [[yii\web\User]]: + +```php +// El usuario actual identificado. `null` si el usuario no esta autenticado. +$identity = Yii::$app->user->identity; + +// El ID del usuario actual. `null` si el usuario no esta autenticado. +$id = Yii::$app->user->id; + +// si el usuario actual es un invitado (No autenticado) +$isGuest = Yii::$app->user->isGuest; +``` + +Para acceder a un usuario, puede utilizar el siguiente código: + +```php +// encontrar una identidad de usuario con el nombre de usuario especificado. +// tenga en cuenta que es posible que desee comprobar la contraseña si es necesario +$identity = User::findOne(['username' => $username]); + +// inicia la sesión del usuario. +Yii::$app->user->login($identity); +``` + +El método [[yii\web\User::login()]] establece la identidad del usuario actual a [[yii\web\User]]. Si la sesión es [[yii\web\User::enableSession|habilitada]], mantendrá la identidad en la sesión para que el estado de autenticación del usuario se mantenga durante toda la sesión. Si el login basado en cookies (es decir, inicio de sesión "recordarme") está [[yii\web\User::enableAutoLogin|habilitado]], también guardará la identidad en una cookie para que el estado de autenticación del usuario pueda ser recuperado desde la cookie mientras la cookie permanezca válida. + +Para habilitar el login basado en cookies, necesita configurar [[yii\web\User::enableAutoLogin]] como `true` en la configuración de la aplicación. También necesita proporcionar un parámetro de tiempo de duración cuando llame al método [[yii\web\User::login()]]. + +Para cerrar la sesión de un usuario, basta con llamar a: + +```php +Yii::$app->user->logout(); +``` + +Tenga en cuenta que cerrar la sesión de un usuario sólo tiene sentido cuando la sesión está activada. El método limpiará el estado de autenticación del usuario tanto de la memoria como de la sesión. Y por defecto, también destruirá *todos* los datos de sesión del usuario. Si desea mantener los datos de sesión, debe llamar a `Yii::$app->user->logout(false)`, en su lugar. + +## Eventos de Autenticación +La clase [[yii\web\User]] genera algunos eventos durante los procesos de inicio y cierre de sesión. +* [[yii\web\User::EVENT_BEFORE_LOGIN|EVENT_BEFORE_LOGIN]]: levantado al comienzo de [[yii\web\User::login()]]. Si el manejador del evento establece la propiedad [[yii\web\UserEvent::isValid|isValid]] del objeto evento a `false`, el proceso de inicio de sesión será cancelado. +* [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]]: se produce después de un inicio de sesión exitoso. +* [[yii\web\User::EVENT_BEFORE_LOGOUT|EVENT_BEFORE_LOGOUT]]: levantado al comienzo de [[yii\web\User::logout()]]. Si el manejador del evento establece la propiedad [[yii\web\UserEvent::isValid|isValid]] del objeto evento a `false`, el proceso de cierre de sesión será cancelado. +* [[yii\web\User::EVENT_AFTER_LOGOUT|EVENT_AFTER_LOGOUT]]: se produce después de un cierre de sesión exitoso. +Usted puede responder a estos eventos para implementar características como auditoria de inicio de sesión, estadísticas de usuarios en línea. Por ejemplo, en el manejador para [[yii\web\User::EVENT_AFTER_LOGIN|EVENT_AFTER_LOGIN]], puede registrar la hora de inicio de sesión y la dirección IP en la tabla `user`. diff --git a/docs/guide-es/security-authorization.md b/docs/guide-es/security-authorization.md new file mode 100644 index 00000000000..2e7590f326b --- /dev/null +++ b/docs/guide-es/security-authorization.md @@ -0,0 +1,527 @@ +Autorización +============ + +Autorización esl el proceso de verificación de que un usuario tenga sugifientes permisos para realizar algo. Yii provee +dos métodos de autorización: Filtro de Control de Acceso y Control Basado en Roles (ACF y RBAC por sus siglas en inglés). + + +## Filtro de Control de Acceso + +Filtro de Control de Acceso (ACF) es un único método de autorización implementado como [[yii\filters\AccessControl]], el cual +es mejor utilizado por aplicaciones que sólo requieran un control de acceso simple. Como su nombre lo indica, ACF es +un [filtro](structure-filters.md) de acción que puede ser utilizado en un controlador o en un módulo. Cuando un usuario solicita +la ejecución de una acción, ACF comprobará una lista de [[yii\filters\AccessControl::rules|reglas de acceso]] +para determinar si el usuario tiene permitido acceder a dicha acción. + +El siguiente código muestra cómo utilizar ACF en el controlador `site`: + +```php +use yii\web\Controller; +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::class, + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['login', 'signup'], + 'roles' => ['?'], + ], + [ + 'allow' => true, + 'actions' => ['logout'], + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +} +``` + +En el código anterior, ACF es adjuntado al controlador `site` en forma de behavior (comportamiento). Esta es la forma típica de utilizar +un filtro de acción. La opción `only` especifica que el ACF debe ser aplicado solamente a las acciones `login`, `logout` y `signup`. +Las acciones restantes en el controlador `site` no están sujetas al control de acceso. La opción `rules` lista +las [[yii\filters\AccessRule|reglas de acceso]], y se lee como a continuación: + +- Permite a todos los usuarios invitados (sin autenticar) acceder a las acciones `login` y `signup`. La opción `roles` + contiene el signo de interrogación `?`, que es un código especial para representar a los "invitados". +- Permite a los usuarios autenticados acceder a la acción `logout`. El signo `@` es otro código especial que representa + a los "usuarios autenticados". + +ACF ejecuta la comprobación de autorización examinando las reglas de acceso una a una desde arriba hacia abajo hasta que encuentra +una regla que aplique al contexto de ejecución actual. El valor `allow` de la regla que coincida será entonces utilizado +para juzgar si el usuario está autorizado o no. Si ninguna de las reglas coincide, significa que el usuario NO está autorizado, +y el ACF detendrá la ejecución de la acción. + +Cuando el ACF determina que un usuario no está autorizado a acceder a la acción actual, toma las siguientes medidas por defecto: + +* Si el usuario es un invitado, llamará a [[yii\web\User::loginRequired()]] para redireccionar el navegador a la pantalla de login. +* Si el usuario está autenticado, lanzará una excepeción [[yii\web\ForbiddenHttpException]]. + +Puedes personalizar este comportamiento configurando la propiedad [[yii\filters\AccessControl::denyCallback]] como a continuación: + +```php +[ + 'class' => AccessControl::class, + ... + 'denyCallback' => function ($rule, $action) { + throw new \Exception('No tienes los suficientes permisos para acceder a esta página'); + } +] +``` + +Las [[yii\filters\AccessRule|Reglas de Acceso]] soportan varias opciones. Abajo hay un resumen de las mismas. +También puedes extender de [[yii\filters\AccessRule]] para crear tus propias clases de reglas de acceso personalizadas. + + * [[yii\filters\AccessRule::allow|allow]]: especifica si la regla es de tipo "allow" (permitir) o "deny" (denegar). + + * [[yii\filters\AccessRule::actions|actions]]: especifica con qué acciones coinciden con esta regla. Esta debería ser +un array de IDs de acciones. La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las acciones. + + * [[yii\filters\AccessRule::controllers|controllers]]: especifica con qué controladores coincide +esta regla. Esta debería ser un array de IDs de controladores. Cada ID de controlador es prefijado con el ID del módulo (si existe). +La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, significa que la regla se aplica a todos los controladores. + + * [[yii\filters\AccessRule::roles|roles]]: especifica con qué roles de usuarios coincide esta regla. + Son reconocidos dos roles especiales, y son comprobados vía [[yii\web\User::isGuest]]: + + - `?`: coincide con el usuario invitado (sin autenticar) + - `@`: coincide con el usuario autenticado + + El utilizar otro nombre de rol invocará una llamada a [[yii\web\User::can()]], que requiere habilitar RBAC + (a ser descrito en la próxima subsección). Si la opción está vacía o no definida, significa que la regla se aplica a todos los roles. + + * [[yii\filters\AccessRule::ips|ips]]: especifica con qué [[yii\web\Request::userIP|dirección IP del cliente]] coincide esta regla. +Una dirección IP puede contener el caracter especial `*` al final de manera que coincidan todas las IPs que comiencen igual. +Por ejemplo, '192.168.*' coincide con las direcciones IP en el segmento '192.168.'. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las direcciones IP. + + * [[yii\filters\AccessRule::verbs|verbs]]: especifica con qué método de la solicitud (por ej. `GET`, `POST`) coincide esta regla. +La comparación no distingue minúsculas de mayúsculas. + + * [[yii\filters\AccessRule::matchCallback|matchCallback]]: especifica una función PHP invocable que debe ser llamada para determinar +si la regla debe ser aplicada. + + * [[yii\filters\AccessRule::denyCallback|denyCallback]]: especifica una función PHP invocable que debe ser llamada cuando esta regla +deniegue el acceso. + +Debajo hay un ejemplo que muestra cómo utilizar la opción `matchCallback`, que te permite escribir lógica de comprabación de acceso +arbitraria: + +```php +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::class, + 'only' => ['special-callback'], + 'rules' => [ + [ + 'actions' => ['special-callback'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], + ], + ], + ]; + } + + // Callback coincidente llamado! Esta página sólo puede ser accedida cada 31 de Octubre + public function actionSpecialCallback() + { + return $this->render('happy-halloween'); + } +} +``` + + +## Control de Acceso Basado en Roles (RBAC) + +El Control de Acceso Basado en Roles (RBAC) provee una simple pero poderosa manera centralizada de control de acceso. Por favos consulta +la [Wikipedia](https://en.wikipedia.org/wiki/Role-based_access_control) para más detalles sobre comparar RBAC +con otros mecanismos de control de acceso más tradicionales. + +Yii implementa una Jerarquía General RBAC, siguiendo el [modelo NIST RBAC](https://csrc.nist.gov/CSRC/media/Publications/conference-paper/1992/10/13/role-based-access-controls/documents/ferraiolo-kuhn-92.pdf). +Esto provee la funcionalidad RBAC a través de [componente de la aplicación](structure-application-components.md) [[yii\rbac\ManagerInterface|authManager]]. + +Utilizar RBAC envuelve dos cosas. La primera es construir los datos de autorización RBAC, y la segunda +es utilizar esos datos de autorización para comprobar el acceso en los lugares donde se necesite. + +Para facilitar la próxima descripción, necesitamos primero instroducir algunos conceptos RBAC básicos. + + +### Conceptos Básicos + +Un rol representa una colección de *permisos* (por ej. crear posts, actualizar posts). Un rol puede ser asignado +a uno o varios usuarios. Para comprobar que un usuario cuenta con determinado permiso, podemos comprobar si el usuario tiene asignado +un rol que cuente con dicho permiso. + +Asociado a cada rol o permiso, puede puede haber una *regla*. Una regla representa una porción de código que será +ejecutada durante la comprobación de acceso para determinar si el rol o permiso correspondiente aplica al usuario actual. +Por ejemplo, el permiso "actualizar post" puede tener una regla que compruebe que el usuario actual es el autor del post. +Durante la comprobación de acceso, si el usuario NO es el autor del post, se considerará que el/ella no cuenta con el permiso "actualizar post". + +Tanto los roles como los permisos pueden ser organizados en una jerarquía. En particular, un rol puede consistir en otros roles o permisos; +y un permiso puede consistir en otros permisos. Yii implementa una jerarquía de *orden parcial*, que incluye +una jerarquía de *árbol* especial. Mientras que un rol puede contener un permiso, esto no sucede al revés. + + +### Configurar RBAC + +Antes de definir todos los datos de autorización y ejecutar la comprobación de acceso, necesitamos configurar el +componente de la aplicación [[yii\base\Application::authManager|authManager]]. Yii provee dos tipos de administradores de autorización: +[[yii\rbac\PhpManager]] y [[yii\rbac\DbManager]]. El primero utiliza un archivo PHP para almacenar los datos +de autorización, mientras que el segundo almacena dichos datos en una base de datos. Puedes considerar utilizar el primero si tu aplicación +no requiere una administración de permisos y roles muy dinámica. + + +#### Utilizar `PhpManager` + +El siguiente código muestra cómo configurar `authManager` en la configuración de nuestra aplicación utilizando la clase [[yii\rbac\PhpManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + ], + // ... + ], +]; +``` + +El `authManager` ahora puede ser accedido vía `\Yii::$app->authManager`. + +Por defecto, [[yii\rbac\PhpManager]] almacena datos RBAC en archivos bajo el directorio `@app/rbac`. Asegúrate de que el directorio +y todos sus archivos son tienen permiso de escritura para el proceso del servidor Web si la jerarquía de permisos necesita ser modoficada en línea. + + +#### Utilizar `DbManager` + +El sigiente código muestra cómo configurar `authManager` en la configuración de la aplicación utilizando la clase [[yii\rbac\DbManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', + ], + // ... + ], +]; +``` +> Note: si estás utilizando el template yii2-basic-app, existe el archivo de configuración `config/console.php` donde + necesita declararse `authManager` adicionalmente a `config/web.php`. +> En el caso de yii2-advanced-app, `authManager` sólo debe declararse en `common/config/main.php`. + +`DbManager` utiliza cuatro tablas de la BD para almacenar los datos: + +- [[yii\rbac\DbManager::$itemTable|itemTable]]: la tabla para almacenar los ítems de autorización. Por defecto "auth_item". +- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: la tabla para almacentar la jerarquía de los ítems de autorización. Por defecto "auth_item_child". +- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: la tabla para almacenar las asignaciones de los ítems de autorización. Por defecto "auth_assignment". +- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: la tabla para almacenar las reglas. Por defecto "auth_rule". + +Antes de continuar, necesitas crear las tablas respectivas en la base de datos. Para hacerlo, puedes utilizar las migraciones contenidas en `@yii/rbac/migrations`: + +`yii migrate --migrationPath=@yii/rbac/migrations` + +El `authManager` puede ahora ser accedido vía `\Yii::$app->authManager`. + + +### Construir los Datos de Autorización + +Construir los datos de autorización implica las siguientes tareas: + +- definir roles y permisos; +- establecer relaciones entre roles y permisos; +- definir reglas; +- asociar reglas con roles y permisos; +- asignar roles a usuarios. + +Dependiendo de los requerimientos de flexibilidad en la autorización, las tareas se pueden lograr de diferentes maneras. + +Si la jerarquía de permisos no cambia en absoluto y tienes un número fijo de usuarios puede crear un +[comando de consola](tutorial-console.md#create-command) que va a inicializar los datos de autorización una vez a través de las API que ofrece por `authManager`: + +```php +authManager; + + // agrega el permiso "createPost" + $createPost = $auth->createPermission('createPost'); + $createPost->description = 'Create a post'; + $auth->add($createPost); + + // agrega el permiso "updatePost" + $updatePost = $auth->createPermission('updatePost'); + $updatePost->description = 'Update post'; + $auth->add($updatePost); + + // agrega el rol "author" y le asigna el permiso "createPost" + $author = $auth->createRole('author'); + $auth->add($author); + $auth->addChild($author, $createPost); + + // agrega el rol "admin" y le asigna el permiso "updatePost" + // más los permisos del rol "author" + $admin = $auth->createRole('admin'); + $auth->add($admin); + $auth->addChild($admin, $updatePost); + $auth->addChild($admin, $author); + + // asigna roles a usuarios. 1 y 2 son IDs devueltos por IdentityInterface::getId() + // usualmente implementado en tu modelo User. + $auth->assign($author, 2); + $auth->assign($admin, 1); + } +} +``` + +> Note: Si estas utilizando el template avanzado, necesitas poner tu `RbacController` dentro del directorio `console/controllers` + y cambiar el espacio de nombres a `console\controllers`. + +Después de ejecutar el comando `yii rbac/init`, obtendremos la siguiente jerarquía: + +![Simple RBAC hierarchy](images/rbac-hierarchy-1.png "Simple RBAC hierarchy") + +"Author" puede crear un post, "admin" puede actualizar posts y hacer todo lo que puede hacer "author". + +Si tu aplicación permite el registro de usuarios, necesitas asignar los roles necesarios para cada usuario nuevo. Por ejemplo, para que todos +los usuarios registrados tengan el rol "author", en el template de aplicación avanzada debes modificar `frontend\models\SignupForm::signup()` +como a continuación: + +```php +public function signup() +{ + if ($this->validate()) { + $user = new User(); + $user->username = $this->username; + $user->email = $this->email; + $user->setPassword($this->password); + $user->generateAuthKey(); + $user->save(false); + + // las siguientes tres líneas fueron agregadas + $auth = Yii::$app->authManager; + $authorRole = $auth->getRole('author'); + $auth->assign($authorRole, $user->getId()); + + return $user; + } + + return null; +} +``` + +Para aplicaciones que requieren un control de acceso complejo con una actualización constante en los datos de autorización, puede ser necesario +desarrollar una interfaz especial (por ej. un panel de administración) utilizando las APIs ofrecidas por `authManager`. + + +### Utilizar Reglas + +Como se había mencionado, las reglas agregan restricciones adicionales a los roles y permisos. Una regla es una clase extendida +de [[yii\rbac\Rule]]. Debe implementar al método [[yii\rbac\Rule::execute()|execute()]]. En la jerarquía que creamos +previamente, "author" no puede editar su propio post. Vamos a arreglarlo. Primero necesitamos una regla para comprobar que el usuario actual es el autor del post: + +```php +namespace app\rbac; + +use yii\rbac\Rule; + +/** + * Comprueba si authorID coincide con el usuario pasado como parámetro + */ +class AuthorRule extends Rule +{ + public $name = 'isAuthor'; + + /** + * @param string|int $user el ID de usuario. + * @param Item $item el rol o permiso asociado a la regla + * @param array $params parámetros pasados a ManagerInterface::checkAccess(). + * @return bool un valor indicando si la regla permite al rol o permiso con el que está asociado. + */ + public function execute($user, $item, $params) + { + return isset($params['post']) ? $params['post']->createdBy == $user : false; + } +} +``` + +La regla anterior comprueba si el `post` fue creado por `$user`. Crearemos un permiso especial, `updateOwnPost`, en el comando que hemos utilizado +anteriormente: + +```php +$auth = Yii::$app->authManager; + +// agrega la regla +$rule = new \app\rbac\AuthorRule; +$auth->add($rule); + +// agrega el permiso "updateOwnPost" y le asocia la regla. +$updateOwnPost = $auth->createPermission('updateOwnPost'); +$updateOwnPost->description = 'Update own post'; +$updateOwnPost->ruleName = $rule->name; +$auth->add($updateOwnPost); + +// "updateOwnPost" será utilizado desde "updatePost" +$auth->addChild($updateOwnPost, $updatePost); + +// permite a "author" editar sus propios posts +$auth->addChild($author, $updateOwnPost); +``` + +Ahora tenemos la siguiente jerarquía: + +![RBAC hierarchy with a rule](images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") + + +### Comprobación de Acceso + +Con los datos de autorización listos, la comprobación de acceso se hace con una simple llamada al método [[yii\rbac\ManagerInterface::checkAccess()]]. +Dado que la mayoría de la comprobación de acceso se hace sobre el usuario actual, para mayor comodidad Yii proporciona el atajo +[[yii\web\User::can()]], que puede ser utilizado como a continuación: + +```php +if (\Yii::$app->user->can('createPost')) { + // crear el post +} +``` + +Si el usuario actual es Jane con `ID=1`, comenzamos desde `createPost` y tratamos de alcanzar a `Jane`: + +![Access check](images/rbac-access-check-1.png "Access check") + +Con el fin de comprobar si un usuario puede actualizar un post, necesitamos pasarle un parámetro adicional requerido por `AuthorRule`, descrito antes: + +```php +if (\Yii::$app->user->can('updatePost', ['post' => $post])) { + // actualizar post +} +``` + +Aquí es lo que sucede si el usuario actual es John: + + +![Access check](images/rbac-access-check-2.png "Access check") + +Comenzamos desde `updatePost` y pasamos por `updateOwnPost`. Con el fin de pasar la comprobación de acceso, `AuthorRule` +debe devolver `true` desde su método `execute()`. El método recive `$params` desde la llamada al método `can()`, cuyo valor es +`['post' => $post]`. Si todo está bien, vamos a obtener `author`, el cual es asignado a John. + +En caso de Jane es un poco más simple, ya que ella es un "admin": + +![Access check](images/rbac-access-check-3.png "Access check") + + +### Utilizar Roles por Defecto + +Un rol por defecto es un rol que esta asignado *implícitamente* a *todos* los usuarios. La llamada a [[yii\rbac\ManagerInterface::assign()]] +no es necesaria, y los datos de autorización no contienen su información de asignación. + +Un rol por defecto es usualmente asociado con una regla que determina si el rol aplica al usuario siendo verificado. + +Los roles por defecto se utilizan a menudo en aplicaciones que ya tienen algún tipo de asignación de roles. Por ejemplo, una aplicación +puede tener una columna "grupo" en su tabla de usuario para representar a qué grupo de privilegio pertenece cada usuario. +Si cada grupo privilegio puede ser conectado a un rol de RBAC, se puede utilizar la función de rol por defecto para asignar +cada usuario a un rol RBAC automáticamente. Usemos un ejemplo para mostrar cómo se puede hacer esto. + +Suponga que en la tabla de usuario, usted tiene una columna `group` que utiliza 1 para representar el grupo administrador y 2 al grupo autor. +Planeas tener dos roles RBAC, `admin` y `author`, para representar los permisos de estos dos grupos, respectivamente. +Puede configurar los datos RBAC de la siguiente manera, + + +```php +namespace app\rbac; + +use Yii; +use yii\rbac\Rule; + +/** + * Comprueba si el grupo coincide + */ +class UserGroupRule extends Rule +{ + public $name = 'userGroup'; + + public function execute($user, $item, $params) + { + if (!Yii::$app->user->isGuest) { + $group = Yii::$app->user->identity->group; + if ($item->name === 'admin') { + return $group == 1; + } elseif ($item->name === 'author') { + return $group == 1 || $group == 2; + } + } + return false; + } +} + +$auth = Yii::$app->authManager; + +$rule = new \app\rbac\UserGroupRule; +$auth->add($rule); + +$author = $auth->createRole('author'); +$author->ruleName = $rule->name; +$auth->add($author); +// ... agrega permisos hijos a $author ... + +$admin = $auth->createRole('admin'); +$admin->ruleName = $rule->name; +$auth->add($admin); +$auth->addChild($admin, $author); +// ... agrega permisos hijos a $admin ... +``` + +Tenga en cuenta que en el ejemplo anterior, dado que "author" es agregado como hijo de "admin", cuando implementes el método `execute()` +de la clase de la regla, necesitas respetar esta jerarquía. Esto se debe a que cuando el nombre del rol es "author", +el método `execute()` devolverá `true` si el grupo de usuario es tanto 1 como 2 (lo que significa que el usuario se encuentra en +cualquiera de los dos grupos, "admin" o "author"). + +Luego, configura `authManager` enumerando los dos roles en [[yii\rbac\BaseManager::$defaultRoles]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + 'defaultRoles' => ['admin', 'author'], + ], + // ... + ], +]; +``` + +Ahora si realizas una comprobación de acceso, tanto el rol `admin` y como el rol `author` serán comprobados evaluando +las reglas asociadas con ellos. Si la regla devuelve `true`, significa que la regla aplica al usuario actual. +Basado en la implementación de la regla anterior, esto significa que si el valor `group` en un usuario es 1, el rol `admin` +se aplicaría al usuario; y si el valor de `group` es 2, se le aplicaría el rol `author`. diff --git a/docs/guide-es/security-passwords.md b/docs/guide-es/security-passwords.md new file mode 100644 index 00000000000..679f081cfda --- /dev/null +++ b/docs/guide-es/security-passwords.md @@ -0,0 +1,31 @@ +Trabajar con Passwords +====================== + +La mayoría de los desarrolladores saben que los passwords no deben ser guardados en texto plano, pero muchos desarrolladores aún creen +que es seguro aplicar a los passowrds hash `md5` o `sha1`. Hubo un tiempo cuando utilizar esos algoritmos de hash mencionados era suficiente, +pero el hardware moderno hace posible que ese tipo de hash e incluso más fuertes, puedan revertirse rápidamente utilizando ataques de fuerza bruta. + +Para poder proveer de una seguridad mayor para los passwords de los usuarios, incluso en el peor de los escenarios (tu aplicación sufre una brecha de seguridad), +necesitas utilizar un algoritmo que resista los ataques de fuerza bruta. La mejor elección actualmente es `bcrypt`. +En PHP, puedes generar un hash `bcrypt` utilizando la [función crypt](https://www.php.net/manual/en/function.crypt.php). Yii provee +dos funciones auxiliares que hacen que `crypt` genere y verifique los hash más fácilmente. + +Cuando un usuario provee un password por primera vez (por ej., en la registración), dicho password necesita ser pasado por un hash: + + +```php +$hash = Yii::$app->getSecurity()->generatePasswordHash($password); +``` + +El hash puede estar asociado con el atributo del model correspondiente, de manera que pueda ser almacenado en la base de datos para uso posterior. + +Cuando un usuario intenta ingresar al sistema, el password enviado debe ser verificado con el password con hash almacenado previamente: + + +```php +if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { + // todo en orden, dejar ingresar al usuario +} else { + // password erróneo +} +``` diff --git a/docs/guide-es/start-databases.md b/docs/guide-es/start-databases.md new file mode 100644 index 00000000000..d12cf82db05 --- /dev/null +++ b/docs/guide-es/start-databases.md @@ -0,0 +1,263 @@ +Trabajar con Bases de Datos +=========================== + +En esta sección, explicaremos cómo crear una nueva página para mostrar datos de países traídos de una tabla de la +base de datos llamada `country`. Para lograr este objetivo, configurarás una conexión a la base de datos, +crearás una clase [Active Record](db-active-record.md), una [acción](structure-controllers.md) +y una [vista](structure-views.md). + +A lo largo de este tutorial, aprenderás a + +* configurar una conexión a la base de datos; +* definir una clase Active Record; +* realizar consultas a la base de datos utilizando la clase Active Record; +* mostrar datos en una vista con paginación incluida. + +Ten en cuenta que para finalizar esta sección, deberás tener al menos conocimientos básicos y experiencia con bases de datos. +En particular, deberás ser capaz de crear una base de datos y saber ejecutar consultas SQL usando alguna herramienta de cliente de base de datos. + + +Preparar una Base de Datos +-------------------------- + +Para empezar, crea una base de datos llamada `yii2basic` de la cual tomarás los datos en la aplicación. +Puedes elegir entre una base de datos SQLite, MySQL, PostgreSQL, MSSQL u Oracle, dado que Yii incluye soporte para varios motores. Por simplicidad, usaremos MySQL en la siguiente descripción. + +A continuación, crea una tabla llamada `country` e inserta algunos datos de ejemplo. Puedes utilizar las siguientes declaraciones SQL. + +```sql +CREATE TABLE `country` ( + `code` CHAR(2) NOT NULL PRIMARY KEY, + `name` CHAR(52) NOT NULL, + `population` INT(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `country` VALUES ('AU','Australia',24016400); +INSERT INTO `country` VALUES ('BR','Brazil',205722000); +INSERT INTO `country` VALUES ('CA','Canada',35985751); +INSERT INTO `country` VALUES ('CN','China',1375210000); +INSERT INTO `country` VALUES ('DE','Germany',81459000); +INSERT INTO `country` VALUES ('FR','France',64513242); +INSERT INTO `country` VALUES ('GB','United Kingdom',65097000); +INSERT INTO `country` VALUES ('IN','India',1285400000); +INSERT INTO `country` VALUES ('RU','Russia',146519759); +INSERT INTO `country` VALUES ('US','United States',322976000); +``` + +Al final, tendrás una base de datos llamada `yii2basic`, y dentro de esta, una tabla llamada `country` con diez registros en ella. + +Configurar una conexión a la Base de Datos +------------------------------------------ + +Asegúrate de tener instalado la extensión de PHP [PDO](https://www.php.net/manual/es/book.pdo.php) y el driver +de PDO para el motor que estés utilizando (ej. `pdo_mysql` para MySQL). Este es un requisito básico si tu aplicación +va a utilizar bases de datos relacionales. + +Abre el archivo `config/db.php` y ajusta el contenido dependiendo de la configuración a tu base de datos. Por defecto, +el archivo contiene el siguiente contenido: + +```php + 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=yii2basic', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; +``` + +El archivo `config/db.php` representa la típica [configuración](concept-configurations.md) basada en archivos. Este archivo de configuración en particular +especifica los parámetros necesarios para crear e inicializar una instancia de [[yii\db\Connection]] a través de la cual puedes realizar +consultas SQL contra la base de datos subyacente. + +La conexión a la base de datos realizada anteriormente puede ser accedida mediante `Yii::$app->db`. + +> Info: El archivo `config/db.php` será incluido en el archivo principal de configuración `config/web.php`, + el cual especifica cómo la instancia de la [aplicación](structure-applications.md) debe ser inicializada. + Para más información, consulta la sección [Configuraciones](concept-configurations.md). + +Si necesitas trabajar con bases de datos cuyo soporte no está incluído en Yii, revisa las siguientes extensiones: + +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) + + +Crear un Active Record +---------------------- + +Para representar y extraer datos de la tabla `country`, crea una clase [Active Record](db-active-record.md) +llamada `Country` y guárdala en el archivo `models/Country.php`. + +```php + Info: Si no se puede realizar un emparejamiento entre el nombre de la clase y la tabla, puedes +sobrescribir el método [[yii\db\ActiveRecord::tableName()]] para especificar explícitamente el nombre de la tabla asiciada. + +Utilizando la clase `Country`, puedes manipular los datos de la tabla `country` fácilmente, como se muestra en los siguiente ejemplos: + +```php +use app\models\Country; + +// obtiene todos los registros de la tabla country ordenándolos por "name" +$countries = Country::find()->orderBy('name')->all(); + +// obtiene el registro cuya clave primaria es "US" +$country = Country::findOne('US'); + +// muestra "United States" +echo $country->name; + +// cambia el nombre del país a "U.S.A." y lo guarda en la base de datos +$country->name = 'U.S.A.'; +$country->save(); +``` + +> Info: Active Record es una potente forma de acceder y manipular datos de una base de datos de una manera orientada a objetos. +Puedes encontrar información más detallada acerca de [Active Record](db-active-record.md). Además de Active Record, puedes utilizar un método de acceso de bajo nivel llamado [Data Access Objects](db-dao.md). + + +Crear una Acción +---------------- + +Para mostrar el país a los usuarios, necesitas crear una acción. En vez de hacerlo en el controlador `site` +como lo hiciste en las secciones previas, tiene más sentido crear un nuevo controlador que englobe todas las +acciones de manipulación de datos de la tabla country. Llama a este nuevo controlador `CountryController` y define +una acción `index` en él, como se muestra a continuación: + +```php + 5, + 'totalCount' => $query->count(), + ]); + + $countries = $query->orderBy('name') + ->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); + + return $this->render('index', [ + 'countries' => $countries, + 'pagination' => $pagination, + ]); + } +} +``` + +Guarda el código anterior en el archivo `controllers/CountryController.php`. + +La acción `index` llama a `Country::find()` para generar una consulta a la base de datos y traer todos los datos de la tabla `country`. +Para limitar la cantidad de registros traídos en cada petición, la consulta es paginada con la ayuda de un objeto +[[yii\data\Pagination]]. El objeto `Pagination` sirve para dos propósitos: + +* Define las cláusulas `offset` y `limit` de la consulta SQL para así sólo devolver una sola página de datos + (5 registros por página como máximo). +* Es utilizado en la vista para mostrar un paginador que consiste en una lista de botones que representan a cada página, + tal como será explicado en la siguiente sub-sección. + +Al final, la acción `index` renderiza una vista llamada `index` y le pasa los datos de países así como la información +de paginación relacionada. + + +Crear una Vista +--------------- + +Bajo el directorio `views`, crea primero un sub-directorio llamado `country`. Este será usado para contener +todas las vistas renderizadas por el controlador `country`. +Dentro del directorio `views/country`, crea un archivo llamado `index.php` con el siguiente contenido: + +```php + +

Países

+
    + +
  • + name} ({$country->code})") ?>: + population ?> +
  • + +
+ + $pagination]) ?> +``` + +La vista consiste en dos partes. En la primera, los datos de países son recorridos y renderizados como una lista HTML. +En la segunda parte, un widget [[yii\widgets\LinkPager]] es renderizado usando la información de paginación pasada desde la acción. +El widget `LinkPager` muestra una lista de botones que representan las páginas disponibles. Haciendo click en cualquiera +de ellas mostrará los datos de países de la página correspondiente. + + +Probándolo +---------- + +Para ver cómo funciona, utiliza a la siguiente URL en tu navegador: + +``` +https://hostname/index.php?r=country%2Findex +``` + +![Lista de Países](images/start-country-list.png) + +Verás una página que muestra cinco países. Y debajo de todos los países, verás un paginador con cuatro botones. +Si haces click en el botón "2", verás que la página muestra otros cinco países de la base de datos. +Observa más cuidadosamente y verás que la URL en el navegador cambia a + +``` +https://hostname/index.php?r=country%2Findex&page=2 +``` + +Entre bastidores, [[yii\data\Pagination|Pagination]] está realizando su magia. + +* Inicialmente, [[yii\data\Pagination|Pagination]] representa la primera página, que agrega a la consulta SQL + a la base de datos con la cláusula `LIMIT 5 OFFSET 0`. Como resultado, los primeros cinco países serán traídos y mostrados. +* El widget [[yii\widgets\LinkPager|LinkPager]] renderiza los botones de páginas usando las URLs + creadas por [[yii\data\Pagination::createUrl()|Pagination]]. Las URLs contendrán el parámetro `page` + representando los números de páginas. +* Si haces click en el botón "2", se lanza y maneja una nueva petición a la ruta `country/index`. + [[yii\data\Pagination|Pagination]] lee el parámetro `page` y define el número de página actual como "2". + Por consiguiente, la consulta a la base de datos tendrá la cláusula `LIMIT 5 OFFSET 5` y devolverá los + siguientes cinco países para mostrar. + + +Resumen +------- + +En esta sección has aprendido cómo trabajar con una base de datos. También has aprendido cómo traer y mostrar +datos paginados con la ayuda de [[yii\data\Pagination]] y [[yii\widgets\LinkPager]]. + +En la siguiente sección, aprenderás a utilizar la poderosa herramienta de generación de código llamada [Gii](tool-gii.md), +para ayudarte a implementar rápidamente algunas características comunes, como crear operaciones de Alta-Baja-Modificación +(ABM, o CRUD en inglés) de los datos guardados en la base de datos. De hecho, el código que acabas de escribir fue +generado automáticamente a través de esta herramienta. diff --git a/docs/guide-es/start-forms.md b/docs/guide-es/start-forms.md new file mode 100644 index 00000000000..fceddd5836d --- /dev/null +++ b/docs/guide-es/start-forms.md @@ -0,0 +1,221 @@ +Trabajando con Formularios +========================== + +En esta sección, describiremos como crear una nueva página para solicitar información de los usuarios. +La página mostrará un formulario con un campo de input para el nombre y un campo de input para el email. +Después de recibir estos datos del usuario, la página le mostrará la información de vuelta al usuario para la confirmación. + +Para lograr este objetivo, además de crear una [acción](structure-controllers.md) y +dos [vistas](structure-views.md), también crearás un [modelo](structure-models.md). + +A través de este tutorial, aprenderás + +* Cómo crear un [modelo](structure-models.md) para representar los datos ingresados por un usuario; +* Cómo declarar reglas para validar los datos ingresado por los usuarios; +* Cómo construir un formulario HTML en una [vista](structure-views.md). + + +Creando un Modelo +----------------- + +Para representar los datos ingresados por un usuario, crea una clase modelo `EntryForm` cómo se muestra abajo y +guarda la clase en el archivo `models/EntryForm.php`. Por favor, visita la sección [Autocargando Clases](concept-autoloading.md) +para obtener más detalles acerca de la convención de nombres de los archivos de clase. + +```php + +------------------ + +Luego, crea una acción `entry` en el controlador `site`, como lo hiciste en la sección anterior. + +```php +load(Yii::$app->request->post()) && $model->validate()) { + // validar los datos recibidos en el modelo + + // aquí haz algo significativo con el modelo ... + + return $this->render('entry-confirm', ['model' => $model]); + } else { + // la página es mostrada inicialmente o hay algún error de validación + return $this->render('entry', ['model' => $model]); + } + } +} +``` + +La acción primero crea un objeto `EntryForm`. Luego intenta poblar el modelo +con los datos del `$_POST` que es proporcionado por Yii a través de [[yii\web\Request::post()]]. +Si el modelo es llenado satisfactoriamente (ej., el usuario ha enviado el formulario HTML), +llamará a [[yii\base\Model::validate()|validate()]] para asegurarse que los datos ingresados +son válidos. + +Si todo está bien, la acción mostrará una vista llamada `entry-confirm` para confirmar +con el usuario que acepta los datos que ha ingresado. De otra manera, la vista `entry` será +mostrada, y mostrará el formulario HTML junto con los mensajes de error de validación (si es que hay alguno). + +> Info: La expresión `Yii::$app` representa la instancia de la [aplicación](structure-applications.md) + que es un singleton globalmente accesible. También es un [service locator](concept-service-locator.md) (localizador de servicio) + que provee los componentes, tales como `request`, `response`, `db`, etc. para soportar funcionalidades específicas. + En el código de arriba, el componente `request` es utilizado para acceder los datos `$_POST`. + + +Creando Vistas +-------------- + +Finalmente, crea dos vistas llamadas `entry-confirm` y `entry` que sean mostradas por la acción `entry`, +tal y como fue descrito en la última sub-sección. + +La vista `entry-confirm` simplemente muestra los datos de name y email. Ésta debe ser guardada como el archivo `views/site/entry-confirm.php`. + +```php + +

You have entered the following information:

+ +
    +
  • : name) ?>
  • +
  • : email) ?>
  • +
+``` + +La vista `entry` muestra un formulario HTML. Debe ser guardado como el archivo `views/site/entry.php`. + +```php + + + + field($model, 'name') ?> + + field($model, 'email') ?> + +
+ 'btn btn-primary']) ?> +
+ + +``` + +La vista utiliza un poderoso [widget](structure-widgets.md) llamado [[yii\widgets\ActiveForm|ActiveForm]] para +construir el formulario HTML. Los métodos `begin()` y `end()` del widget muestran, respectivamente, las etiquetas de +apertura y cierre del formulario. Entre las llamadas de los dos métodos, los campos de input son creados por el +método [[yii\widgets\ActiveForm::field()|field()]]. El primer campo input es del dato "name", +y el segundo del dato "email". Después de los campos de input, el método [[yii\helpers\Html::submitButton()]] +es llamado para general el botón de submit (enviar). + + +Probándolo +---------- + +Para ver cómo funciona, utiliza tu navegador para ir al siguiente URL: + +``` +https://hostname/index.php?r=site/entry +``` + +Verás una página que muestra un formulario con dos campos de input. Adelante de cada campo de input, será mostrada también +una etiqueta indicando que dato necesitas ingresar. Si haces click en el botón de envío (Submit) sin ingresar nada, +o si ingresas una dirección de correo inválida, verás un mensaje de error que se mostrará al lado del campo que tiene problemas. + +![Formulario con Errores de Validación](images/start-form-validation.png) + +Después de ingresar un nombre y dirección de correo válidos y haciendo click en el botón de envío (Submit), verás una nueva página +mostrando los datos que acabas de ingresar. + +![Confirmación de los Datos de Entrada](images/start-entry-confirmation.png) + + + +### Magia Explicada + +Te estarás preguntando cómo funciona toda esa automatización del formulario HTML, porque parece casi mágico que pueda +mostrar una etiqueta para cada campo de input y mostrar los mensajes de error si no ingresas los datos correctamente +sin recargar la página. + +Si, la validación de los datos se realiza en el lado del cliente utilizando JavaScript así como también en el lado del servidor. +[[yii\widgets\ActiveForm]] es lo suficientemente inteligente como para extraer las reglas de validación que has declarado en `EntryForm`, +convertirlas en código Javascript, y utilizar el JavaScript para realizar la validación de los datos. En caso de que hayas deshabilitado +JavaScript en tu navegador, la validación se realizará igualmente en el lado del servidor, como se muestra en +el método `actionEntry()`. Esto garantiza la validez de los datos en cualquier circunstancias. + +Las etiquetas de los campos de input son generados por el método `field()` basado en los nombres de las propiedades del modelo. +Por ejemplo, la etiqueta `Name` será generada de la propiedad `name`. Puedes personalizar una etiqueta con +el siguiente código: + +```php +field($model, 'name')->label('Tu Nombre') ?> +field($model, 'email')->label('Tu Email') ?> +``` + +> Info: Yii provee muchos widgets para ayudarte a construir rápidamente vistas complejas y dinámicas. + Como aprenderás más adelante, escribir un nuevo widget es extremadamente fácil. Puedes convertir mucho del + código de tus vistas en widgets reutilizables para simplificar el desarrollo de las vistas en un futuro. + + +Resumen +------- + +En esta sección, has tocado cada parte del patrón de diseño MVC. Ahora has aprendido +a crear una clase modelo para representar los datos del usuario y validarlos. + +También has aprendido como obtener datos de los usuarios y como mostrarlos de vuelta. Esta es una tarea que +puede tomarte mucho tiempo cuando estás desarrollando una aplicación. Yii provee poderosos widgets +para hacer muy fácil esta tarea. + +En la próxima sección, aprenderás como trabajar con bases de datos que son necesarias en casi cualquier aplicación. diff --git a/docs/guide-es/start-gii.md b/docs/guide-es/start-gii.md new file mode 100644 index 00000000000..9c059c8058f --- /dev/null +++ b/docs/guide-es/start-gii.md @@ -0,0 +1,131 @@ +Generando Código con Gii +======================== + +En esta sección, explicaremos cómo utilizar [Gii](tool-gii.md) para generar código que automáticamente +implementa algunas de las características más comunes de una aplicación. Para lograrlo, todo lo que tienes que hacer es +ingresar la información de acuerdo a las instrucciones mostradas en la páginas web de Gii. + +A lo largo de este tutorial, aprenderás + +* Cómo activar Gii en tu aplicación; +* Cómo utilizar Gii para generar una clase Active Record; +* Cómo utilizar Gii para generar el código que implementa las operaciones ABM de una tabla de la base de datos. +* Cómo personalizar el código generado por Gii. + + +Comenzando con Gii +------------------ + +[Gii](tool-gii.md) está provisto por Yii en forma de [módulo](structure-modules.md). Puedes habilitar Gii +configurándolo en la propiedad [[yii\base\Application::modules|modules]] de la aplicación. Dependiendo de cómo hayas creado tu aplicación, podrás encontrar que el siguiente código ha sido ya incluido en el archivo de configuración `config/web.php`: + +```php +$config = [ ... ]; + +if (YII_ENV_DEV) { + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; +} +``` + +La configuración dice que al estar en el [entorno de desarrollo](concept-configurations.md#environment-constants), +la aplicación debe incluir el módulo llamado `gii`, cuya clase es [[yii\gii\Module]]. + +Si chequeas el [script de entrada](structure-entry-scripts.md) `web/index.php` de tu aplicación, encontrarás la línea +que esencialmente define la constante `YII_ENV_DEV` como verdadera -`true`. + +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +De esta manera, tu aplicación habrá habilitado Gii, y puedes acceder al módulo a través de la siguiente URL: + +``` +https://hostname/index.php?r=gii +``` + +![Gii](images/start-gii.png) + + +Generando una Clase Active Record +--------------------------------- + +Para poder generar una clase Active Record con Gii, selecciona "Model Generator" (haciendo click en el vínculo que existe en la página inicial del modulo Gii). Después, completa el formulario de la siguiente manera, + +* Table Name: `country` +* Model Class: `Country` + +![Model Generator](images/start-gii-model.png) + +Haz click el el botón "Preview". Verás que `models/Country.php` está mostrado listado como la clase resultante que ha de ser creada. Puedes hacer click en el nombre de la clase para previsualizar su contenido. + +Al utilizar Gii, si habías creado previamente el mismo archivo y puede ser sobrescrito, si haces click +en el botón `diff` cercano al nombre del archivo, verás las diferencias entre el código a ser generado +y la versión existente del mismo. + +![Previsualización del Model Generator](images/start-gii-model-preview.png) + +Para sobrescribir un archivo existente, marca el checkbox que se encuentra al lado de "overwrite" y posteriormente haz click en el botón "Generate". + +Después, verás una página de confirmación indicando que el código ha sido generado correctamente y tu archivo `models/Country.php` +ha sido sobrescrito con el nuevo código generado. + + +Generando código de ABM (CRUD en inglés) +---------------------------------------- + +En computación, CRUD es el acrónimo de Crear, Obtener, Actualizar y Borrar (del inglés: Create, Read, Update y Delete) +representando la cuatro funciones con datos más comunes en la mayoría de sitios Web. +El acrónimo ABM es Altas, Bajas y Modificaciones. Para generar un ABM, selecciona "CRUD Generator" y completa el formulario de esta manera: + +* Model Class: `app\models\Country` +* Search Model Class: `app\models\CountrySearch` +* Controller Class: `app\controllers\CountryController` + +![Generador de ABM](images/start-gii-crud.png) + +Al hacer click en el botón "Preview" verás la lista de archivos a ser generados. + +Si has creado previamente los archivos `controllers/CountryController.php` y +`views/country/index.php` (en la sección sobre bases de datos de esta guía), asegúrate de seleccionar el checkbox "overwrite" para reemplazarlos. (Las versiones anteriores no disponían de un soporte ABM (CRUD) completo.) + + +Probándolo +---------- + +Para ver cómo funciona, accede desde tu navegador a la siguiente URL: + +``` +https://hostname/index.php?r=country/index +``` + +Verás una grilla de datos mostrando los países de la base de datos. Puedes ordenar la grilla +o filtrar los resultados escribiendo alguna condición en los encabezados de las columnas. + +Por cada país mostrado en la grilla, puedes elegir entre visualizar el registro, actualizarlo o eliminarlo. +Puedes incluso hacer click en el botón "Create Country" que se encuentra sobre la grilla y así cargar +un nuevo país en la base de datos. + +![Grilla de Países](images/start-gii-country-grid.png) + +![Actualizando un País](images/start-gii-country-update.png) + +La siguiente es la lista de archivos generados por Gii, en el caso de que quieras inspeccionar cómo el ABM ha sido generado, +o por si desearas personalizarlos: + +* Controlador: `controllers/CountryController.php` +* Modelos: `models/Country.php` y `models/CountrySearch.php` +* Vistas: `views/country/*.php` + +> Info: Gii está diseñado para ser una herramienta altamente configurable. Utilizándola con sabiduría + puede acelerar enormemente la velocidad de desarrollo de tu aplicación. Para más detalles, consulta la + sección [Gii](tool-gii.md). + + +Resumen +------- + +En esta sección, has aprendido a utilizar Gii para generar el código que implementa completamente las características +de un ABM de acuerdo a una determinada tabla de la base de datos. diff --git a/docs/guide-es/start-hello.md b/docs/guide-es/start-hello.md new file mode 100644 index 00000000000..40eee8b8609 --- /dev/null +++ b/docs/guide-es/start-hello.md @@ -0,0 +1,139 @@ +Diciendo Hola +============= + +Esta sección describe cómo crear la típica página "Hola Mundo" (Hello World en inglés) en tu aplicación. +Para lograr este objetivo, vas a crear una [acción](structure-controllers.md#creating-actions) y +una [vista](structure-views.md): + +* La aplicación enviará la petición de la página a la acción +* y la acción regresará el render de la vista que muestra la palabra "Hola" al usuario final. + +A lo largo de este tutorial, aprenderás tres cosas: + +1. Cómo crear una [acción](structure-controllers.md) para responder peticiones (request), +2. Cómo crear una [vista](structure-views.md) para armar el contenido de la respuesta, y +3. Cómo una aplicación envía peticiones a las [acciones](structure-controllers.md#creating-actions). + + +Creando una Acción +------------------ + +Para la tarea "Hola", crearás una [acción](structure-controllers.md#creating-actions) `say` que lee +un parámetro `message` de la petición y muestra este mensaje de vuelta al usuario. Si la petición +no provee un parámetro `message`, la acción mostrará el mensaje por defecto "Hola". + +> Info: Las [acciones](structure-controllers.md#creating-actions) son objetos que los usuarios finales pueden utilizar directamente para + su ejecución. Las acciones están agrupadas por [controladores](structure-controllers.md) (controllers). El resultado de la ejecución de + una acción es la respuesta que el usuario final recibirá. + +Las acciones deben ser declaradas en [controladores](structure-controllers.md). Para simplificar, puedes +declarar la acción `say` en el controlador `SiteController` existente. Este controlador está definido +en el archivo de clase `controllers/SiteController.php`. Aquí está el inicio de la nueva acción: + +```php +render('say', ['message' => $message]); + } +} +``` + +En el código de arriba, la acción `say` está definida por un método llamado `actionSay` en la clase `SiteController`. +Yii utiliza el prefijo `action` para diferenciar los métodos de acciones de otros métodos en las clases de los controladores. +El nombre que le sigue al prefijo `action` se mapea al ID de la acción. + +Cuando se trata de nombrar las acciones, debes entender como Yii trata los ID de las acciones. Los ID de las acciones siempre son +referenciados en minúscula. Si un ID de acción requiere múltiples palabras, estas serán concatenadas con guiones +(ej., `crear-comentario`). Los nombres de los métodos de las acciones son mapeados a los ID de las acciones +removiendo los guiones, colocando en mayúscula la primera letra de cada palabra, y colocando el prefijo `action` al resultado. Por ejemplo, +el ID de la acción `crear-comentario` corresponde al nombre de método de acción `actionCrearComentario`. + +El método de acción en nuestro ejemplo toma un parámetro `$message`, el cual tiene como valor por defecto `"Hola"` (de la misma manera +que se coloca un valor por defecto a un argumento en cualquier función o método en PHP). Cuando una aplicación +recibe una petición y determina que la acción `say` es responsable de manejar dicha petición, la aplicación llenará +el parámetro con el parámetro que tenga el mismo nombre en la petición. En otras palabras, si la petición incluye un +parámetro `message` con el valor de `"Adios"`, la variable `$message` dentro de la acción será sustituida por este valor. + +Dentro del método de acción, [[yii\web\Controller::render()|render()]] es llamado para hacer render (mostrar o visualizar) un +archivo [vista](structure-views.md) (template) llamado `say`. El parámetro `message` tambien es pasado al view para que pueda ser utilizado ahí. +El resultado es devuelto al método de la acción. Ese resultado será recibido por la aplicación y mostrado al usuario final en el +navegador (como parte de una página HTML completa). + + +Creando una Vista +----------------- + +Las [vistas](structure-views.md) son scripts que escribes para generar una respuesta de contenido. +Para la tarea "Hola", vas a crear una vista `say` que imprime el parámetro `message` recibido desde el método action, y pasado por la acción a la vista: + +```php + + +``` + +La vista `say` debe ser guardada en el archivo `views/site/say.php`. Cuando el método [[yii\web\Controller::render()|render()]] +es llamado en una acción, buscará un archivo PHP llamado `views/ControllerID/NombreVista.php`. + +Nota que en el código de arriba, el parámetro `message` es procesado por [[yii\helpers\Html::encode()|HTML-encoded]] +antes de ser impreso. Esto es necesario ya que el parámetro viene de un usuario final, haciéndolo vulnerable a +[ataques cross-site scripting (XSS)](https://es.wikipedia.org/wiki/Cross-site_scripting) pudiendo inyectar código de Javascript malicioso dentro del parámetro. + +Naturalmente, puedes colocar mas contenido en la vista `say`. El contenido puede consistir de etiquetas HTML, texto plano, e inclusive código PHP. +De hecho, la vista `say` es sólo un script PHP que es ejecutado por el método [[yii\web\Controller::render()|render()]]. +El contenido impreso por el script de la vista será regresado a la aplicación como la respuesta del resultado. La aplicación a cambio mostrará el resultado al usuario final. + + +Probándolo +---------- + +Después de crear la acción y la vista, puedes acceder a la nueva página abriendo el siguiente URL: + +``` +https://hostname/index.php?r=site%2Fsay&message=Hello+World +``` + +![Hello World](images/start-hello-world.png) + +Esta URL resultará en una página mostrando "Hello World". La página comparte el mismo encabezado y pie de página de las otras páginas de la aplicación. + +Si omites el parámetro `message` en el URL, verás que la página muestra sólo "Hola". Esto es porque `message` es pasado como un parámetro al método `actionSay()`, +y cuando es omitido, el valor por defecto `"Hola"` será utilizado. + +> Info: La nueva página comparte el mismo encabezado y pie de página que otras páginas porque el método [[yii\web\Controller::render()|render()]] + automáticamente inyectará el resultado de la vista `say` en el [layout](structure-views.md#layouts), que en este + caso está localizada en `views/layouts/main.php`. + +El parámetro `r` en el URL de arriba requiere más explicación. Se refierea a [route](runtime-routing.md) (ruta), y es el ID amplio y único de una aplicación +que refiere a una acción. El formato de las rutas es `ControllerID/ActionID`. Cuando la aplicación recibe una petición, revisará este parámetro, +utilizando la parte del `ControllerID` para determinar cual clase de controlador será inicializado para manejar la petición. Entonces, el controlador utilizará +la parte `ActionID` para determinar cual acción debe ser inizializada para hacer realmente el trabajo. +En este ejemplo, la ruta `site/say` será respondida por la clase controlador `SiteController` y la acción `say`. Como resultado, +el método `SiteController::actionSay()` será llamado para manejar el requerimiento. + +> Info: Al igual que las acciones, los controladores tambien tienen ID únicos que los identifican en una aplicación. + Los ID de los Controladores utilizan las mismas reglas de nombrado que los ID de las acciones. Los nombres de las clases de los controladores son derivados de los ID de los controladores removiendo los guiones de los ID, colocando la primera letra en mayúscula en cada palabra, y colocando el sufijo `Controller` al resultado. Por ejemplo, el ID del controlador `post-comentario` corresponde + al nombre de clase del controlador `PostComentarioController`. + + +Resumen +------- + +En esta sección, has tocado las partes del controlador y la vista del patrón de diseño MVC. +Has creado una acción como parte de un controlador para manejar una petición específica. Y también has creado una vista para armar el contenido de la respuesta. +En este simple ejemplo, ningún modelo ha sido involucrado ya que el único dato que fue utilizado fue el parámetro `message`. + +También has aprendido acerca de las rutas en Yii, que actúan como puentes entre la petición del usuario y las acciones del controlador. + +En la próxima sección, aprenderás como crear un modelo, y agregar una nueva página que contenga un formulario HTML. diff --git a/docs/guide-es/start-installation.md b/docs/guide-es/start-installation.md new file mode 100644 index 00000000000..ff67d897d88 --- /dev/null +++ b/docs/guide-es/start-installation.md @@ -0,0 +1,238 @@ +Instalar Yii +============ + +Puedes instalar Yii de dos maneras, utilizando el administrador de paquetes [Composer](https://getcomposer.org/) o descargando un archivo comprimido. +La forma recomendada es la primera, ya que te permite instalar nuevas [extensions](structure-extensions.md) o actualizar Yii con sólo ejecutar un comando. + +La instalación estándar de Yii cuenta tanto con el framework como un template de proyecto instalados. +Un template de proyecto es un proyecto Yii funcional que implementa algunas características básicas como: login, formulario de contacto, etc. +El código está organizado de una forma recomendada. Por lo tanto, puede servir como un buen punto de partida para tus proyectos. + +En esta y en las próximas secciones, describiremos cómo instalar Yii con el llamado *Template de Proyecto Básico* +y cómo implementar nuevas características por encima del template. Yii también provee otro template llamado +[Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) qué es mejor para desarrollar aplicaciones con varios niveles +en el entorno de un equipo de desarrollo. + +> Info: El Template de Proyecto Básico es adecuado para desarrollar el 90 porciento de las aplicaciones Web. Difiere del + Template de Proyecto Avanzado principalmente en cómo está organizado el código. Si eres nuevo en Yii, te recomendamos + utilizar el Template de Proyecto Básico por su simplicidad pero funcionalidad suficiente. + + +Instalando via Composer +------------------------------- + +Si aún no tienes Composer instalado, puedes hacerlo siguiendo las instrucciones que se encuentran en +[getcomposer.org](https://getcomposer.org/download/). En Linux y Mac OS X, se ejecutan los siguientes comandos: + +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` + +En Windows, tendrás que descargar y ejecutar [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). + +Por favor, consulta la [Documentación de Composer](https://getcomposer.org/doc/) si encuentras algún problema +o deseas obtener un conocimiento más profundo sobre su utilización. + +Si ya tienes composer instalado, asegúrate de tener una versión actualizada. Puedes actualizar Composer +ejecutando el comando `composer self-update` + +Teniendo Composer instalado, puedes instalar Yii ejecutando los siguientes comandos en un directorio accesible vía Web: + +```bash +composer global require "fxp/composer-asset-plugin:^1.4.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` + +El primer comando instala [composer asset plugin](https://github.com/fxpio/composer-asset-plugin), +que permite administrar dependencias de paquetes bower y npm a través de Composer. Sólo necesitas ejecutar este comando +una vez. El segundo comando instala Yii en un directorio llamado `basic`. Puedes elegir un nombre de directorio diferente si así lo deseas. + +> Note: Durante la instalación, Composer puede preguntar por tus credenciales de acceso de Github. Esto es normal ya que Composer +> necesita obtener suficiente límite de acceso de la API para traer la información de dependencias de Github. Para más detalles, +> consulta la [documentación de Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +> Tip: Si quieres instalar la última versión de desarrollo de Yii, puedes utilizar uno de los siguientes comandos, +> que agregan una [opción de estabilidad](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` +> +> Ten en cuenta que la versión de desarrollo de Yii no debería ser utilizada en producción ya que podría romper tu código actual. + + +Instalar desde un Archivo Comprimido +------------------------------------ + +Instalar Yii desde un archivo comprimido involucra tres pasos: + +1. Descargar el archivo desde [yiiframework.com](https://www.yiiframework.com/download/yii2-basic). +2. Descomprimirlo en un directorio accesible vía Web. +3. Modificar el archivo `config/web.php` introduciendo una clave secreta para el ítem de configuración `cookieValidationKey` + (esto se realiza automáticamente si estás instalando Yii a través de Composer): + + ```php + // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` + + +Otras Opciones de Instalación +----------------------------- + +Las instrucciones anteriores muestran cómo instalar Yii, lo que también crea una aplicación Web lista para ser usada. +Este es un buen punto de partida para la mayoría de proyectos, tanto grandes como pequeños. Es especialmente adecuado si recién +estás aprendiendo a utilizar Yii. + +Pero también hay otras opciones de instalación disponibles: + +* Si sólo quieres instalar el núcleo del framework y entonces crear una nueva aplicación desde cero, + puedes seguir las instrucciones explicadas en [Generando una Aplicación desde Cero](tutorial-start-from-scratch.md). +* Si quisieras comenzar con una aplicación más avanzada, más adecuada para un entorno de desarrollo de equipo, + deberías considerar instalar el [Template de Aplicación Avanzada](tutorial-advanced-app.md). + + +Verificando las Instalación +--------------------------- + +Una vez finalizada la instalación, o bien configura tu servidor web (mira la sección siguiente) o utiliza +el [servidor web incluido en PHP](https://www.php.net/manual/es/features.commandline.webserver.php) ejecutando el siguiente +comando de consola estando parado en el directorio `web` de la aplicación: + +```bash +php yii serve +``` + +> Note: Por defecto el servidor HTTP escuchará en el puerto 8080. De cualquier modo, si el puerto está en uso o deseas +servir varias aplicaciones de esta manera, podrías querer especificar qué puerto utilizar. Sólo agrega el argumento --port: + +```bash +php yii serve --port=8888 +``` + +Puedes utilizar tu navegador para acceder a la aplicación instalada de Yii en la siguiente URL: + +``` +http://localhost:8080/. +``` + +![Instalación Correcta de Yii](images/start-app-installed.png) + +Deberías ver la página mostrando "Congratulations!" en tu navegador. Si no ocurriera, por favor chequea que la instalación +de PHP satisfaga los requerimientos de Yii. Esto puedes hacerlo usando cualquiera de los siguientes procedimientos: + +* Copiando `/requirements.php` a `/web/requirements.php` y visitando la URL `http://localhost/basic/requirements.php` en tu navegador +* Corriendo los siguientes comandos: + + ```bash + cd basic + php requirements.php + ``` + +Deberías configurar tu instalación de PHP para que satisfaga los requisitos mínimos de Yii. Lo que es más importante, +debes tener PHP 5.4 o mayor. También deberías instalar la [Extensión de PHP PDO](https://www.php.net/manual/es/pdo.installation.php) +y el correspondiente driver de base de datos (como `pdo_mysql` para bases de datos MySQL), si tu aplicación lo necesitara. + + +Configurar Servidores Web +------------------------- + +> Info: Puedes saltear esta sección por ahora si sólo estás probando Yii sin intención + de poner la aplicación en un servidor de producción. + +La aplicación instalada siguiendo las instrucciones mencionadas debería estar lista para usar tanto +con un [servidor HTTP Apache](https://httpd.apache.org/) como con un [servidor HTTP Nginx](https://nginx.org/), +en Windows, Mac OS X, o Linux utilizando PHP 5.4 o mayor. Yii 2.0 también es compatible con [HHVM](https://hhvm.com/) +de Facebook. De todos modos, hay algunos casos donde HHVM se comporta diferente del +PHP oficial, por lo que tendrás que tener cuidados extra al utilizarlo. + +En un servidor de producción, podrías querer configurar el servidor Web para que la aplicación sea accedida +a través de la URL `https://www.example.com/index.php` en vez de `https://www.example.com/basic/web/index.php`. Tal configuración +require apuntar el document root de tu servidor Web a la carpeta `basic/web`. También podrías +querer ocultar `index.php` de la URL, como se describe en la sección [Parseo y Generación de URLs](runtime-url-handling.md). +En esta sub-sección, aprenderás a configurar tu servidor Apache o Nginx para alcanzar estos objetivos. + +> Info: Al definir `basic/web` como document root, también previenes que los usuarios finales accedan +al código privado o archivos con información sensible de tu aplicación que están incluidos en los directorios del mismo nivel +que `basic/web`. Denegando el acceso es una importante mejora en la seguridad. + +> Info: En caso de que tu aplicación corra en un entorno de hosting compartido donde no tienes permisos para modificar +la configuración del servidor Web, aún puedes ajustar la estructura de la aplicación para mayor seguridad. Por favor consulta +la sección [Entorno de Hosting Compartido](tutorial-shared-hosting.md) para más detalles. + + +### Configuración Recomendada de Apache + +Utiliza la siguiente configuración del archivo `httpd.conf` de Apache dentro de la configuración del virtual host. Ten en cuenta +que deberás reemplazar `path/to/basic/web` con la ruta real a `basic/web`. + +```apache +# Definir el document root como "basic/web" +DocumentRoot "path/to/basic/web" + + + # utiliza mod_rewrite para soporte de URLs amigables + RewriteEngine on + # Si el directorio o archivo existe, utiliza la petición directamente + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Sino, redirige la petición a index.php + RewriteRule . index.php + + # ...otras configuraciones... + +``` + + +### Configuración Recomendada de Nginx + +Para utilizar [Nginx](https://wiki.nginx.org/), debes instalar PHP como un [FPM SAPI](https://www.php.net/manual/es/install.fpm.php). +Utiliza la siguiente configuración de Nginx, reemplazando `path/to/basic/web` con la ruta real a +`basic/web` y `mysite.test` con el hostname real a servir. + +``` +server { + charset utf-8; + client_max_body_size 128M; + + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 + + server_name mysite.test; + root /path/to/basic/web; + index index.php; + + access_log /path/to/basic/log/access.log; + error_log /path/to/basic/log/error.log; + + location / { + # Redireccionar a index.php todo lo que no sea un archivo real + try_files $uri $uri/ /index.php$is_args$args; + } + + # descomentar para evitar el procesamiento de llamadas de Yii a archivos estáticos no existente + #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + # try_files $uri =404; + #} + #error_page 404 /404.html; + + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; + #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; + } + + location ~ /\.(ht|svn|git) { + deny all; + } +} +``` + +Al utilizar esta configuración, también deberías definir `cgi.fix_pathinfo=0` en el archivo `php.ini`, y así +evitar muchas llamadas innecesarias del sistema a `stat()`. + +Ten en cuenta también que al correr un servidor HTTPS, deberás agregar `fastcgi_param HTTPS on;` así Yii puede +detectar propiamente si la conexión es segura. diff --git a/docs/guide-es/start-looking-ahead.md b/docs/guide-es/start-looking-ahead.md new file mode 100644 index 00000000000..a881087c011 --- /dev/null +++ b/docs/guide-es/start-looking-ahead.md @@ -0,0 +1,35 @@ +Mirando Hacia Adelante +====================== + +Si has leído el capítulo "Comenzando con Yii" completo, has creado una aplicación completa en Yii. En el proceso, has aprendido cómo implementar algunas +características comúnmente necesitadas, tales como obtener datos del usuario a través de formularios HTML, traer datos desde la base de datos, +y mostrar datos utilizando paginación. También has aprendido a utilizar [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) para generar +código automáticamente. Utilizar Gii para la generación de código transforma la carga en el proceso de tu desarrollo Web en una tarea tan simple como solamente completar unos formularios. + +Esta sección resumirá los recursos disponibles de Yii que te ayudarán a ser más productivo al utilizar el framework. + +* Documentación + - [La Guía Definitiva](https://www.yiiframework.com/doc-2.0/guide-README.html): + Como su nombre lo indica, la guía define precisamente cómo debería trabajar Yii y provee guías generales + acerca de su utilización. Es el tutorial más importante de Yii, y el que deberías leer + antes de escribir cualquier código en Yii. + - [La Referencia de Clases](https://www.yiiframework.com/doc-2.0/index.html): + Esta especifica el uso de cada clase provista por Yii. Debería ser utilizada principalmente cuando estás escribiendo + código y deseas entender el uso de una clase, método o propiedad en particular. El uso de la referencia de clases es mejor luego de un entendimiento contextual del framework. + - [Los Artículos de la Wiki](https://www.yiiframework.com/wiki/?tag=yii2): + Los artículos de la wiki son escritos por usuarios de Yii basados en sus propias experiencias. La mayoría de ellos están escritos + como recetas de cocina, y muestran cómo resolver problemas particulares utilizando Yii. Si bien la calidad de estos + puede no ser tan buena como la de la Guía Definitiva, son útiles ya que cubren un espectro muy amplio + de temas y puede proveer a menudo soluciones listas para usar. + - [Libros](https://www.yiiframework.com/books) +* [Extensiones](https://www.yiiframework.com/extensions/): + Yii puede hacer alarde de una librería de miles de extensiones contribuidas por usuarios, que pueden fácilmente conectadas a tu aplicación, haciendo que el desarrollo de la misma sea todavía más fácil y rápido. +* Comunidad + - Foro: + - Chat IRC: El canal #yii en la red Libera () + - Chat Gitter: + - GitHub: + - Facebook: + - Twitter: + - LinkedIn: + - Stackoverflow: diff --git a/docs/guide-es/start-prerequisites.md b/docs/guide-es/start-prerequisites.md new file mode 100644 index 00000000000..d4771f8d998 --- /dev/null +++ b/docs/guide-es/start-prerequisites.md @@ -0,0 +1,29 @@ +# Qué necesita saber + +La curva de aprendizaje de Yii no es tan empinada como en otros _frameworks_ en PHP, +pero todavía hay algunas cosas que debería aprender antes de empezar con Yii. + +## PHP + +Yii es un _framework_ (base estructurada de desarrollo) en PHP, así que asegúrese de +[leer y comprender la referencia del lenguaje](https://www.php.net/manual/es/langref.php). +Al desarrollar con Yii deberá escribir código de manera orientada a objetos, así que +asegúrese de estar familiarizado con +[clases y objetos](https://www.php.net/manual/es/language.oop5.basic.php) así como con +[espacios de nombres](https://www.php.net/manual/es/language.namespaces.php). + +## Programación orientada a objetos + +Se requiere una comprensión básica de la programación orientada a objetos. Si no está +familiarizado con ella, diríjase a alguno de los muchos tutoriales disponibles, como +[el de tuts+](https://code.tutsplus.com/tutorials/object-oriented-php-for-beginners--net-12762). + +Observe que cuanto más complicada sea su aplicación, más conceptos avanzados de la +POO deberá aprender para gestionar con éxito esa complejidad. + +## Línea de órdenes y composer + +Yii usa profusamente el gestor de paquetes _de facto_ de PHP, [Composer](https://getcomposer.org/), +así que asegúrese de leer y comprender su [guía](https://getcomposer.org/doc/01-basic-usage.md). +Si no está familiarizado con el uso de la línea de órdenes, es hora de empezar a probarla. +Una vez que aprenda los fundamentos, nunca querrá trabajar sin ella. diff --git a/docs/guide-es/start-workflow.md b/docs/guide-es/start-workflow.md new file mode 100644 index 00000000000..e564b8f6978 --- /dev/null +++ b/docs/guide-es/start-workflow.md @@ -0,0 +1,102 @@ +Corriendo Aplicaciones +====================== + +Después de haber instalado Yii, tienes una aplicación totalmente funcional a la que se puede acceder a través de +la URL `https://hostname/basic/web/index.php` o `https://hostname/index.php`, dependiendo de tu configuración. +Esta sección será una introducción a la funcionalidad incluida de la aplicación, cómo se organiza el código, +y cómo la aplicación maneja los requests en general. + +> Info: Por simplicidad, en el transcurso de este tutorial "Para Empezar", se asume que has definido `basic/web` + como el document root de tu servidor Web, y configurado la URL de acceso a tu aplicación para que sea `https://hostname/index.php` + o similar. + Dependiendo de tus necesidades, por favor ajusta dichas URLs. + +Ten en cuenta que a diferencia del framework en sí, después de que el template de proyecto es instalado, este es todo tuyo. Eres libre de agregar o eliminar +código modificar todo según tu necesidad. + + +Funcionalidad +------------- + +La aplicación básica contiene 4 páginas: + +* página principal, mostrada cuando se accede a la URL `https://hostname/index.php`, +* página "Acerca de (About)", +* la página "Contacto (Contact)", que muestra un formulario de contacto que permite a los usuarios finales contactarse vía email, +* y la página "Login", que muestra un formulario para loguearse que puede usarse para autenticar usuarios. + Intenta loguearte con "admin/admin", y verás que el elemento "Login" del menú principal cambiará a "Logout". + +Estas páginas comparten un encabezado y un pie. El encabezado contiene una barra con el menú principal que permite +la navegación entre las diferentes páginas. + +También deberías ver una barra en la parte inferior de la ventana del navegador. +Esta es la útil [herramienta de depuración](tool-debugger.md) provista por Yii para registrar y mostrar mucha información de depuración, tal como los mensajes de log, response status, las consultas ejecutadas a la base de datos, y más. + +Adicionalmente a la aplicación web, hay un script de consola llamado `yii`, localizado en el directorio base de la aplicación. +El script puede ser utilizado para ejecutar tareas de fondo y tareas de mantenimiento de la aplicación, las cuales son descritas +en la [Sección de Aplicación de Consola](tutorial-console.md). + + +Estructura de la aplicación +--------------------------- + +Los archivos y directorios más importantes en tu aplicación son (asumiendo que la raíz de la aplicación es `basic`): + +``` +basic/ base path de la aplicación + composer.json archivo utilizado por Composer, describe información de sus paquetes y librerías + config/ contiene la configuración de las aplicaciones (y otras) + console.php configuración de la aplicación de consola + web.php configuración de la aplicación web + commands/ contiene las clases de comandos de consola + controllers/ contiene las clases de los controladores + models/ contienes las clases del modelo + runtime/ contiene archivos generados por Yii en tiempo de ejecución, como archivos de log y cache + vendor/ contiene los paquetes y librerías instalados por Composer, incluyendo el propio núcleo de Yii + views/ contiene los archivos de vistas (templates) + web/ raíz web de la aplicación, contiene los archivos accesibles vía Web + assets/ contiene los assets publicados (javascript y css) por Yii + index.php el script de entrada (o bootstrap) de la aplicación + yii el script de ejecución de los comandos de consola de Yii +``` + +En general, los archivos de la aplicación pueden ser divididos en dos: aquellos bajo `basic/web` y aquellos bajo otros directorios. +Los primeros pueden accederse directo por HTTP (ej., en un navegador), mientras que los últimos no pueden ni deben ser accedidos así. + +Yii implementa el patrón de diseño [modelo-vista-controlador (MVC)](https://wikipedia.org/wiki/Model-view-controller), +que es reflejado en la estructura de directorios utilizada. El directorio `models` contiene todas las [clases del modelo](structure-models.md), +el directorio `views` contiene todas las [vistas (templates)](structure-views.md), y el directorio `controllers` contiene +todas las [clases de controladores](structure-controllers.md). + +El siguiente diagrama muestra la estructura estática de una aplicación. + +![Estructura Estática de una Aplicación](images/application-structure.png) + +Cada aplicación tiene un script de entrada `web/index.php` que es el único script PHP accesible vía web. +El script de entrada toma una petición (request) entrante y crea una instancia de una [aplicación](structure-applications.md) para manejarlo. +La [aplicación](structure-applications.md) resuelve la petición (request) con la ayuda de sus [componentes](concept-components.md), +y la envía al resto de los elementos MVC. Los [widgets](structure-widgets.md) son usados en las [vistas](structure-views.md) +para ayudar a construir elementos de interfaz complejos y dinámicos. + + +Ciclo de Vida de una Petición (Request) +--------------------------------------- + +El siguiente diagrama muestra cómo una aplicación maneja una petición. + +![Ciclo de Vida de un Request](images/request-lifecycle.png) + +1. Un usuario realiza una petición al [script de entrada](structure-entry-scripts.md) `web/index.php`. +2. El script de entrada carga la [configuración](concept-configurations.md) de la aplicación y crea + una instancia de la [aplicación](structure-applications.md) para manejar la consulta. +3. La aplicación resuelve la [ruta](runtime-routing.md) solicitada con la ayuda del + componente [request](runtime-requests.md) de la aplicación. +4. La aplicación crea una instancia de un [controlador](structure-controllers.md) para manejar la petición. +5. El controlador crea una instancia de una [acción](structure-controllers.md) y ejecuta los filtros de dicha acción. +6. Si alguno de los filtros falla, la acción es cancelada. +7. Si todos los filtros pasan, la acción es ejecutada. +8. La acción carga datos del modelo, posiblemente de la base de datos. +9. La acción renderiza una vista, pasándole los datos del modelo cargado. +10. El resultado de la renderización es pasado al componente [response](runtime-responses.md) de la aplicación. +11. El componente response envía el resultado de la renderización al navegador del usuario. + diff --git a/docs/guide-es/structure-application-components.md b/docs/guide-es/structure-application-components.md new file mode 100644 index 00000000000..e4bdb928ad1 --- /dev/null +++ b/docs/guide-es/structure-application-components.md @@ -0,0 +1,93 @@ +Componentes de la Aplicación +============================ + +Las aplicaciones son [service locators](concept-service-locators.md) (localizadores de servicios). Ellas albergan +un grupo de los llamados *componentes de aplicación* que proveen diferentes servicios para procesar el `request` (petición). +Por ejemplo, el componente `urlManager` es responsable por rutear Web `requests` (peticiones) a los controladores apropiados; +el componente `db` provee servicios relacionados a base de datos; y así sucesivamente. + +Cada componente de la aplicación tiene un ID que lo identifica de forma inequívoca de otros en la misma aplicación. +Puedes acceder a un componente de la aplicación con la siguiente expresión: + +```php +\Yii::$app->ComponentID +``` + +Por ejemplo, puedes utilizar `\Yii::$app->db` para obtener la [[yii\db\Connection|conexión a la base de datos]], +y `\Yii::$app->cache` para obtener el [[yii\caching\Cache|cache primario]] registrado con la aplicación. + +Estos componentes pueden ser cualquier objeto. Puedes registrarlos configurando la propiedad [[yii\base\Application::components]] +en las [configuraciones de la aplicación](structure-applications.md#application-configurations). +Por ejemplo: + +```php +[ + 'components' => [ + // registra el componente "cache" utilizando el nombre de clase + 'cache' => 'yii\caching\ApcCache', + + // registra el componente "db" utilizando un array de configuración + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', + ], + + // registra el componente "search" utilizando una función anónima + 'search' => function () { + return new app\components\SolrService; + }, + ], +] +``` + +> Info: A pesar de que puedes registrar tantos componentes como desees, deberías hacerlo con criterio. + Los componente de la aplicación son como variables globales. Abusando demasiado de ellos puede resultar en + un código más difícil de mantener y testear. En muchos casos, puedes simplemente crear un componente local + y utilizarlo únicamente cuando sea necesario. + + +## Componentes del Núcleo de la Aplicación + +Yii define un grupo de componentes del *núcleo* con IDs fijos y configuraciones por defecto. Por ejemplo, +el componente [[yii\web\Application::request|request]] es utilizado para recolectar información acerca +del `request` del usuario y resolverlo en una [ruta](runtime-routing.md); el componente [[yii\base\Application::db|db]] +representa una conexión a la base de datos a través del cual realizar consultas a la misma. +Es con ayuda de estos componentes del núcleo que Yii puede manejar los `request` del usuario. + +A continuación, hay una lista de componentes predefinidos en el núcleo. Puedes configurarlos y personalizarlos +como lo haces con componentes normales de la aplicación. Cuando configuras un componente del núcleo, +si no especificas su nombre de clase, la clase por defecto será utilizada. + +* [[yii\web\AssetManager|assetManager]]: maneja los `assets bundles` y su publicación. + Consulta la sección [Menajando Assets](output-assets.md) para más detalles. +* [[yii\db\Connection|db]]: representa una conexión a la base de datos a través de la cual puedes realizar consultas a la misma. + Ten en cuenta que cuando configuras este componente, debes especificar el nombre de clase así como otras + propiedades requeridas por el mismo, como [[yii\db\Connection::dsn]]. + Por favor consulta la sección [Data Access Objects](db-dao.md) para más detalles. +* [[yii\base\Application::errorHandler|errorHandler]]: maneja errores y excepciones de PHP. + Por favor consulta la sección [Handling Errors](tutorial-handling-errors.md) para más detalles. +* [[yii\base\Formatter|formatter]]: da formato a los datos cuando son mostrados a los usuarios. Por ejemplo, un número + puede ser mostrado usando un separador de miles, una fecha en una forma extensa. + Por favor consulta la sección [Formato de Datos](output-formatting.md) para más detalles. +* [[yii\i18n\I18N|i18n]]: soporta traducción y formato de mensajes. + Por favor consulta la sección [Internacionalización](tutorial-i18n.md) para más detalles. +* [[yii\log\Dispatcher|log]]: maneja a dónde dirigir los logs. + Por favor consulta la sección [Logging](tutorial-logging.md) para más detalles. +* [[yii\swiftmailer\Mailer|mail]]: soporta construcción y envío de emails. + Por favor consulta la sección [Enviando Emails](tutorial-mailing.md) para más detalles. +* [[yii\base\Application::response|response]]: representa la respuesta enviada a los usuarios. + Por favor consulta la sección [Responses](runtime-responses.md) para más detalles. +* [[yii\base\Application::request|request]]: representa el `request` recibido de los usuarios. + Por favor consulta la sección [Requests](runtime-requests.md) para más detalles. +* [[yii\web\Session|session]]: representa la información de sesión. Este componente sólo está disponible + en [[yii\web\Application|alpicaciones Web]]. + Por favor consulta la sección [Sessions and Cookies](runtime-sessions-cookies.md) para más detalles. +* [[yii\web\UrlManager|urlManager]]: soporta el parseo y generación de URLs. + Por favor consulta la sección [URL Parsing and Generation](runtime-url-handling.md) para más detalles. +* [[yii\web\User|user]]: representa la información e autenticación del usuario. Este componente sólo está disponible + en [[yii\web\Application|aplicaciones Web]] + Por favor consulta la sección [Autenticación](security-authentication.md) para más detalles. +* [[yii\web\View|view]]: soporta el renderizado de las vistas. + Por favor consulta la sección [Vistas](structure-views.md) para más detalles. diff --git a/docs/guide-es/structure-applications.md b/docs/guide-es/structure-applications.md new file mode 100644 index 00000000000..d01e1a6ec2c --- /dev/null +++ b/docs/guide-es/structure-applications.md @@ -0,0 +1,591 @@ +Aplicaciones +============ + +Las `Applications` (aplicaciones) son objetos que gobiernan la estructura total y el ciclo de vida de las aplicaciones +hechas en Yii. +Cada aplicación Yii contiene un objeto `Application` que es creado en el [script de entrada](structure-entry-scripts.md) +y es globalmente accesible a través de la expresión `\Yii::$app`. + +> Info: Dependiendo del contexto, cuando decimos "una aplicación", puede significar tanto un objeto Application + o un sistema desarrollado en Yii. + +Hay dos tipos de aplicaciones: [[yii\web\Application|aplicaciones Web]] y +[[yii\console\Application|aplicaciones de consola]]. Como el nombre lo indica, la primera maneja principalmente +Web requests mientras que la última maneja requests (peticiones) de la línea de comandos. + + +## Configuraciones de las Aplicaciones + +Cuando un [script de entrada](structure-entry-scripts.md) crea una aplicación, cargará +una [configuración](concept-configurations.md) y la aplicará a la aplicación, como se muestra a continuación: + +```php +require __DIR__ . '/../vendor/autoload.php'; +require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; + +// carga la configuración de la aplicación +$config = require __DIR__ . '/../config/web.php'; + +// instancia y configura la aplicación +(new yii\web\Application($config))->run(); +``` + +Principalmente, las [configuraciones](concept-configurations.md) de una aplicación especifican +como inicializar las propiedades de un objeto `application`. Debido a que estas configuraciones +suelen ser complejas, son usualmente guardadas en [archivos de configuración](concept-configurations.md#configuration-files), +como en el archivo `web.php` del ejemplo anterior. + + +## Propiedades de la Aplicación + +Hay muchas propiedades importantes en la aplicación que deberían configurarse en en la configuración de la aplicación. +Estas propiedades suelen describir el entorno en el cual la aplicación está corriendo. +Por ejemplo, las aplicaciones necesitan saber cómo cargar [controladores](structure-controllers.md), +dónde guardar archivos temporales, etc. A continuación, resumiremos esas propiedades. + + +### Propiedades Requeridas + +En cualquier aplicación, debes configurar al menos dos propiedades: [[yii\base\Application::id|id]] +y [[yii\base\Application::basePath|basePath]]. + + +#### [[yii\base\Application::id|id]] + +La propiedad [[yii\base\Application::id|id]] especifica un ID único que diferencia una aplicación de otras. +Es mayormente utilizada a nivel programación. A pesar de que no es un requerimiento, para una mejor interoperabilidad, +se recomienda utilizar sólo caracteres alfanuméricos. + + +#### [[yii\base\Application::basePath|basePath]] + +La propiedad [[yii\base\Application::basePath|basePath]] especifica el directorio raíz de una aplicación. +Es el directorio que alberga todos los archivos protegidos de un sistema. Bajo este directorio, +tendrás normalmente sub-directorios como `models`, `views`, `controllers`, que contienen el código fuente +correspondiente al patrón MVC. + +Puedes configurar la propiedad [[yii\base\Application::basePath|basePath]] usando la ruta a un directorio +o un [alias](concept-aliases.md). En ambas formas, el directorio debe existir, o se lanzará una excepción. +La ruta será normalizada utilizando la función `realpath()`. + +La propiedad [[yii\base\Application::basePath|basePath]] es utilizada a menudo derivando otras rutas +(ej. la ruta `runtime`). Por esta razón, un alias llamado `@app` está predefinido para representar esta ruta. +Rutas derivadas pueden ser entonces creadas a partir de este alias (ej. `@app/runtime` para referirse al directorio `runtime`). + + +### Propiedades Importantes + +Las propiedades descritas en esta subsección a menudo necesita ser configurada porque difieren entre las +diferentes aplicaciones. + + +#### [[yii\base\Application::aliases|aliases]] + +Esta propiedad te permite definir un grupo de [alias](concept-aliases.md) en términos de un array (matriz). +Las claves del array son los nombres de los alias, y los valores su correspondiente definición. +Por ejemplo: + +```php +[ + 'aliases' => [ + '@name1' => 'path/to/path1', + '@name2' => 'path/to/path2', + ], +] +``` + +Esta propiedad está provista de tal manera que puedas definir alias en términos de configuraciones de la aplicación +en vez de llamadas al método [[Yii::setAlias()]]. + + +#### [[yii\base\Application::bootstrap|bootstrap]] + +Esta es una propiedad importante. Te permite definir un array de los componentes que deben ejecutarse +durante el [[yii\base\Application::bootstrap()|proceso de `bootstrapping`]] de la aplicación. +Por ejemplo, si quieres personalizar las [reglas de URL](runtime-url-handling.md) de un [módulo](structure-modules.md), +podrías listar su ID como un elemento de este array. + +Cada componente listado en esta propiedad puede ser especificado en cualquiera de los siguientes formatos: + +- el ID de un componente como está especificado vía [`components`](#components). +- el ID de un módulo como está especificado vía [`modules`](#modules). +- un nombre de clase. +- un array de configuración. + +Por ejemplo: + +```php +[ + 'bootstrap' => [ + // un ID de componente o de módulo + 'demo', + + // un nombre de clase + 'app\components\TrafficMonitor', + + // un array de configuración + [ + 'class' => 'app\components\Profiler', + 'level' => 3, + ] + ], +] +``` + +Durante el proceso de `bootstrapping`, cada componente será instanciado. Si la clase del componente +implementa [[yii\base\BootstrapInterface]], también se llamará a su método [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]]. + +Otro ejemplo práctico se encuentra en la configuración del [Template de Aplicación Básica](start-installation.md), +donde los módulos `debug` y `gii` son configurados como componentes `bootstrap` cuando la aplicación está +corriendo en un entorno de desarrollo, + +```php +if (YII_ENV_DEV) { + // ajustes en la configuración del entorno 'dev' (desarrollo) + $config['bootstrap'][] = 'debug'; + $config['modules']['debug'] = 'yii\debug\Module'; + + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = 'yii\gii\Module'; +} +``` + +> Note: Agregar demasiados componentes `bootstrap` degradará la performance de tu aplicación debido a que + por cada request, se necesita correr el mismo grupo de componentes. Por lo tanto, utiliza componentes `bootstrap` con criterio. + + +#### [[yii\web\Application::catchAll|catchAll]] + +Esta propiedad está solamente soportada por [[yii\web\Application|aplicaciones Web]]. Especifica +la [acción de controlador](structure-controllers.md) que debería manejar todos los requests (peticiones) del usuario. +Es mayormente utilizada cuando una aplicación está en "modo de mantenimiento" y necesita que todas las peticiones +sean capturadas por una sola acción. + +La configuración es un array cuyo primer elemento especifica la ruta de la acción. +El resto de los elementos del array (pares clave-valor) especifica los parámetros a ser enviados a la acción. +Por ejemplo: + +```php +[ + 'catchAll' => [ + 'offline/notice', + 'param1' => 'value1', + 'param2' => 'value2', + ], +] +``` + + +#### [[yii\base\Application::components|components]] + +Esta es la propiedad más importante. Te permite registrar una lista de componentes llamados [componentes de aplicación](#structure-application-components.md) +que puedes utilizar en otras partes de tu aplicación. Por ejemplo: + +```php +[ + 'components' => [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'user' => [ + 'identityClass' => 'app\models\User', + 'enableAutoLogin' => true, + ], + ], +] +``` + +Cada componente de la aplicación es un par clave-valor del array. La clave representa el ID del componente, +mientras que el valor representa el nombre de la clase del componente o una [configuración](concept-configurations.md). + +Puedes registrar cualquier componente en una aplicación, y el componente puede ser globalmente accedido utilizando +la expresión `\Yii::$app->ComponentID`. + +Por favor, lee la sección [Componentes de la Aplicación](structure-application-components.md) para mayor detalle. + + +#### [[yii\base\Application::controllerMap|controllerMap]] + +Esta propiedad te permite mapear un ID de controlador a una clase de controlador arbitraria. Por defecto, Yii mapea +ID de controladores a clases de controladores basado en una [convención](#controllerNamespace) (ej. el ID `post` será mapeado +a `app\controllers\PostController`). Configurando esta propiedad, puedes saltear esa convención +para controladores específicos. En el siguiente ejemplo, `account` será mapeado a +`app\controllers\UserController`, mientras que `article` será mapeado a `app\controllers\PostController`. + +```php +[ + 'controllerMap' => [ + 'account' => 'app\controllers\UserController', + 'article' => [ + 'class' => 'app\controllers\PostController', + 'enableCsrfValidation' => false, + ], + ], +] +``` + +Las claves de este array representan los ID de los controladores, mientras que los valores representan +los nombres de clase de dichos controladores o una [configuración](concept-configurations.md). + + +#### [[yii\base\Application::controllerNamespace|controllerNamespace]] + +Esta propiedad especifica el `namespace` bajo el cual las clases de los controladores deben ser ubicados. Por defecto es +`app\controllers`. Si el ID es `post`, por convención el controlador correspondiente (sin +`namespace`) será `PostController`, y el nombre completo (cualificado) de la clase `app\controllers\PostController`. + +Las clases de controladores pueden ser ubicados también en sub-directorios del directorio correspondiente a este `namespace`. +Por ejemplo, dado el ID de controlador `admin/post`, el nombre completo de la clase sería `app\controllers\admin\PostController`. + +Es importante que el nombre completo de la clase del controlador sea [auto-cargable](concept-autoloading.md) +y el `namespace` actual de la clase coincida con este valor. De otro modo, recibirás +un error "Page Not Found" ("Página no Encontrada") cuando accedas a la aplicación. + +En caso de que quieras romper con la convención cómo se comenta arriba, puedes configurar la propiedad [controllerMap](#controllerMap). + + +#### [[yii\base\Application::language|language]] + +Esta propiedad especifica el idioma en el cual la aplicación debería mostrar el contenido a los usuarios. +El valor por defecto de esta propiedad es `en`, referido a English. Deberías configurar esta propiedad +si tu aplicación necesita soporte multi-idioma. + +El valor de esta propiedad determina varios aspectos de la [internacionalización](tutorial-i18n.md), +incluido la traducción de mensajes, formato de fecha y números, etc. Por ejemplo, el widget [[yii\jui\DatePicker]] +utilizará el valor de esta propiedad para determinar en qué idioma el calendario debe ser mostrado y cómo dar formato +a la fecha. + +Se recomienda que especifiques el idioma en términos de una [Código de idioma IETF](https://es.wikipedia.org/wiki/Código_de_idioma_IETF). +Por ejemplo, `en` se refiere a English, mientras que `en-US` se refiere a English (United States). + +Se pueden encontrar más detalles de este aspecto en la sección [Internacionalización](tutorial-i18n.md). + + +#### [[yii\base\Application::modules|modules]] + +Esta propiedad especifica los [módulos](structure-modules.md) que contiene la aplicación. + +Esta propiedad toma un array con los nombre de clases de los módulos o [configuraciones](concept-configurations.md) con las claves siendo +los IDs de los módulos. Por ejemplo: + +```php +[ + 'modules' => [ + // módulo "booking" especificado con la clase del módulo + 'booking' => 'app\modules\booking\BookingModule', + + // módulo "comment" especificado usando un array de configuración + 'comment' => [ + 'class' => 'app\modules\comment\CommentModule', + 'db' => 'db', + ], + ], +] +``` + +Por favor consulta la sección [Módulos](structure-modules.md) para más detalles. + + +#### [[yii\base\Application::name|name]] + +Esta propiedad especifica el nombre de la aplicación que será mostrado a los usuarios. Al contrario de +[[yii\base\Application::id|id]], que debe tomar un valor único, el valor de esta propiedad existe principalmente +para propósito de visualización y no tiene porqué ser única. + +No siempre necesitas configurar esta propiedad si en tu aplicación no va a ser utilizada. + + +#### [[yii\base\Application::params|params]] + +Esta propiedad especifica un array con parámetros accesibles desde cualquier lugar de tu aplicación. +En vez de usar números y cadenas fijas por todos lados en tu código, es una buena práctica definirlos como +parámetros de la aplicación en un solo lugar y luego utilizarlos donde los necesites. Por ejemplo, podrías definir el tamaño +de las imágenes en miniatura de la siguiente manera: + +```php +[ + 'params' => [ + 'thumbnail.size' => [128, 128], + ], +] +``` + +Entonces, cuando necesites acceder a esa configuración en tu aplicación, podrías hacerlo utilizando el código siguiente: + +```php +$size = \Yii::$app->params['thumbnail.size']; +$width = \Yii::$app->params['thumbnail.size'][0]; +``` + +Más adelante, si decides cambiar el tamaño de las miniaturas, sólo necesitas modificarlo en la configuración de la aplicación +sin necesidad de tocar el código que lo utiliza. + + +#### [[yii\base\Application::sourceLanguage|sourceLanguage]] + +Esta propiedad especifica el idioma en el cual la aplicación está escrita. El valor por defecto es `'en-US'`, +referido a English (United States). Deberías configurar esta propiedad si el contenido de texto en tu código no está en inglés. + +Como la propiedad [language](#language), deberías configurar esta propiedad siguiendo el [Código de idioma IETF](https://es.wikipedia.org/wiki/Código_de_idioma_IETF). +Por ejemplo, `en` se refiere a English, mientras que `en-US` se refiere a English (United States). + +Puedes encontrar más detalles de esta propiedad en la sección [Internacionalización](tutorial-i18n.md). + + +#### [[yii\base\Application::timeZone|timeZone]] + +Esta propiedad es provista como una forma alternativa de definir el `time zone` de PHP por defecto en tiempo de ejecución. +Configurando esta propiedad, escencialmente estás llamando a la función de PHP [date_default_timezone_set()](https://www.php.net/manual/es/function.date-default-timezone-set.php). +Por ejemplo: + +```php +[ + 'timeZone' => 'America/Los_Angeles', +] +``` + + +#### [[yii\base\Application::version|version]] + +Esta propiedad especifica la versión de la aplicación. Es por defecto `'1.0'`. No hay total necesidad de configurarla +si tu no la usarás en tu código. + + +### Propiedades Útiles + +Las propiedades especificadas en esta sub-sección no son configuradas normalmente ya que sus valores por defecto +estipulan convenciones comunes. De cualquier modo, aún puedes configurarlas en caso de que quieras romper con la convención. + + +#### [[yii\base\Application::charset|charset]] + +Esta propiedad especifica el `charset` que la aplicación utiliza. El valor por defecto es `'UTF-8'`, que debería ser mantenido +tal cual para la mayoría de las aplicaciones a menos que estés trabajando con sistemas legados que utilizan muchos datos no-unicode. + + +#### [[yii\base\Application::defaultRoute|defaultRoute]] + +Esta propiedad especifica la [ruta](runtime-routing.md) que una aplicación debería utilizar si el `request` +no especifica una. La ruta puede consistir el ID de un sub-módulo, el ID de un controlador, y/o el ID de una acción. +Por ejemplo, `help`, `post/create`, `admin/post/create`. Si el ID de la acción no se especifica, tomará el valor por defecto +especificado en [[yii\base\Controller::defaultAction]]. + +Para [[yii\web\Application|aplicaciones Web]], el valor por defecto de esta propiedad es `'site'`, lo que significa que el +controlador `SiteController` y su acción por defecto serán usados. Como resultado, si accedes a la aplicación sin +especificar una ruta, mostrará el resultado de `app\controllers\SiteController::actionIndex()`. + +Para [[yii\console\Application|aplicaciones de consola]], el valor por defecto es `'help'`, lo que significa que el comando +[[yii\console\controllers\HelpController::actionIndex()]] debería ser utilizado. Como resultado, si corres el comando `yii` +sin proveer ningún argumento, mostrará la información de ayuda. + + +#### [[yii\base\Application::extensions|extensions]] + +Esta propiedad especifica la lista de [extensiones](structure-extensions.md) que se encuentran instaladas y son utilizadas +por la aplicación. +Por defecto, tomará el array devuelto por el archivo `@vendor/yiisoft/extensions.php`. El archivo `extensions.php` +es generado y mantenido automáticamente cuando utilizas [Composer](https://getcomposer.org) para instalar extensiones. +Por lo tanto, en la mayoría de los casos no necesitas configurarla. + +En el caso especial de que quieras mantener las extensiones a mano, puedes configurar la propiedad como se muestra a continuación: + +```php +[ + 'extensions' => [ + [ + 'name' => 'nombre de la extensión', + 'version' => 'número de versión', + 'bootstrap' => 'BootstrapClassName', // opcional, puede ser también un array de configuración + 'alias' => [ // opcional + '@alias1' => 'to/path1', + '@alias2' => 'to/path2', + ], + ], + + // ... más extensiones como las de arriba ... + + ], +] +``` + +Como puedes ver, la propiedad toma un array de especificaciones de extensiones. Cada extensión es especificada mediante un array +que consiste en los elementos `name` y `version`. Si una extensión necesita ser ejecutada durante el proceso de [`bootstrap`](runtime-bootstrapping.md), +un elemento `bootstrap` puede ser especificado con un nombre de clase o un array de [configuración](concept-configurations.md). +Una extensión también puede definir algunos [alias](concept-aliases.md). + + +#### [[yii\base\Application::layout|layout]] + +Esta propiedad especifica el valor del `layout` por defecto que será utilizado al renderizar una [vista](structure-views.md). +El valor por defecto es `'main'`, y se refiere al archivo `main.php` bajo el [`layout path`](#layoutPath) definido. +Si tanto el [`layout path`](#layoutPath) y el [`view path`](#viewPath) están utilizando los valores por defecto, +el archivo `layout` puede ser representado con el alias `@app/views/layouts/main.php`. + +Puedes configurar esta propiedad con el valor `false` si quieres desactivar el `layout` por defecto, aunque esto sería un +caso muy raro. + + +#### [[yii\base\Application::layoutPath|layoutPath]] + +Esta propiedad especifica el lugar por defecto donde deben buscarse los archivos `layout`. El valor por defecto +es el sub-directorio `layouts` bajo el [`view path`](#viewPath). Si el [`view path`](#viewPath) usa su valor por defecto, +el `layout path` puede ser representado con el alias `@app/views/layouts`. + +Puedes configurarlo como un directorio o utilizar un [alias](concept-aliases.md). + + +#### [[yii\base\Application::runtimePath|runtimePath]] + +Esta propiedad especifica dónde serán guardados los archivos temporales, como archivos de log y de cache, pueden ser generados. +El valor por defecto de esta propiedad es el alias `@app/runtime`. + +Puedes configurarlo como un directorio o utilizar un [alias](concept-aliases.md). Ten en cuenta que el +directorio debe tener permisos de escritura por el proceso que corre la aplicación. También este directorio debe estar protegido +de ser accedido por usuarios finales, ya que los archivos generados pueden tener información sensible. + +Para simplificar el acceso a este directorio, Yii trae predefinido el alias `@runtime` para él. + + +#### [[yii\base\Application::viewPath|viewPath]] + +Esta propiedad especifica dónde están ubicados los archivos de la vista. El valor por defecto de esta propiedad está +representado por el alias `@app/views`. Puedes configurarlo como un directorio o utilizar un [alias](concept-aliases.md). + + +#### [[yii\base\Application::vendorPath|vendorPath]] + +Esta propiedad especifica el directorio `vendor` que maneja [Composer](https://getcomposer.org). Contiene +todas las librerías de terceros utilizadas por tu aplicación, incluyendo el núcleo de Yii. Su valor por defecto +está representado por el alias `@app/vendor`. + +Puedes configurarlo como un directorio o utilizar un [alias](concept-aliases.md). Cuando modificas esta propiedad, +asegúrate de ajustar la configuración de Composer en concordancia. + +Para simplificar el acceso a esta ruta, Yii trae predefinido el alias `@vendor`. + + +#### [[yii\console\Application::enableCoreCommands|enableCoreCommands]] + +Esta propiedad está sólo soportada por [[yii\console\Application|aplicaciones de consola]]. +Especifica si los comandos de consola incluidos en Yii deberían estar habilitados o no. +Por defecto está definido como `true`. + + +## Eventos de la Aplicación + +Una aplicación dispara varios eventos durante su ciclo de vida al manejar un `request`. Puedes conectar +manejadores a dichos eventos en la configuración de la aplicación como se muestra a continuación: + +```php +[ + 'on beforeRequest' => function ($event) { + // ... + }, +] +``` + +El uso de la sintáxis `on nombreEvento` es descrita en la sección [Configuraciones](concept-configurations.md#configuration-format). + +Alternativamente, puedes conectar manejadores de eventos durante el [`proceso de bootstrapping`](runtime-bootstrapping.md) +después de que la instancia de la aplicación es creada. Por ejemplo: + +```php +\Yii::$app->on(\yii\base\Application::EVENT_BEFORE_REQUEST, function ($event) { + // ... +}); +``` + +### [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] + +Este evento es disparado *before* (antes) de que la aplicación maneje el `request`. El nombre del evento es `beforeRequest`. + +Cuando este evento es disparado, la instancia de la aplicación ha sido configurada e inicializada. Por lo tanto es un +buen lugar para insertar código personalizado vía el mecanismo de eventos para interceptar dicho manejo del `request`. +Por ejemplo, en el manejador del evento, podrías definir dinámicamente la propiedad [[yii\base\Application::language]] +basada en algunos parámetros. + + +### [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_AFTER_REQUEST]] + +Este evento es disparado *after* (después) de que una aplicación finaliza el manejo de un `request` pero *before* (antes) de enviar el `response` (respuesta). +El nombre del evento es `afterRequest`. + +Cuando este evento es disparado, el manejo del `request` está finalizado y puedes aprovechar para realizar algún +post-proceso del mismo o personalizar el `response` (respuesta). + +Ten en cuenta que el componente [[yii\web\Response|response]] también dispara algunos eventos mientras está enviando el contenido +a los usuarios finales. Estos eventos son disparados *after* (después) de este evento. + + +### [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_ACTION]] + +Este evento es disparado *before* (antes) de ejecutar cualquier [acción de controlador](structure-controllers.md). +El nombre de este evento es `beforeAction`. + +El parámetro evento es una instancia de [[yii\base\ActionEvent]]. Un manejador de eventos puede definir +la propiedad [[yii\base\ActionEvent::isValid]] como `false` para detener la ejecución de una acción. +Por ejemplo: + +```php +[ + 'on beforeAction' => function ($event) { + if (..alguna condición..) { + $event->isValid = false; + } else { + } + }, +] +``` + +Ten en cuenta que el mismo evento `beforeAction` también es disparado por [módulos](structure-modules.md) +y [controladores)(structure-controllers.md). Los objectos aplicación son los primeros en disparar este evento, +seguidos por módulos (si los hubiera), y finalmente controladores. Si un manejador de eventos define [[yii\base\ActionEvent::isValid]] +como `false`, todos los eventos siguientes NO serán disparados. + + +### [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_AFTER_ACTION]] + +Este evento es disparado *after* (después) de ejecutar cualquier [acción de controlador](structure-controllers.md). +El nombre de este evento es `afterAction`. + +El parámetro evento es una instancia de [[yii\base\ActionEvent]]. A través de la +propiedad [[yii\base\ActionEvent::result]], un manejador de eventos puede acceder o modificar el resultado de una acción. +Por ejemplo: + +```php +[ + 'on afterAction' => function ($event) { + if (..alguna condición...) { + // modificar $event->result + } else { + } + }, +] +``` + +Ten en cuenta que el mismo evento `afterAction` también es disparado por [módulo](structure-modules.md) +y [controladores)(structure-controllers.md). Estos objetos disparan el evento en orden inverso +que los de `beforeAction`. Esto quiere decir que los controladores son los primeros en dispararlo, +seguido por módulos (si los hubiera), y finalmente aplicaciones. + + +## Ciclo de Vida de una Aplicación + +Cuando un [script de entrada](structure-entry-scripts.md) está siendo ejecutado para manejar un `request`, +una aplicación experimenta el siguiente ciclo de vida: + +1. El script de entrada carga el array de configuración de la aplicación. +2. El script de entrada crea una nueva instancia de la aplicación: + * Se llama a [[yii\base\Application::preInit()|preInit()]], que configura algunas propiedades + de alta prioridad de la aplicación, como [[yii\base\Application::basePath|basePath]]. + * Registra el [[yii\base\Application::errorHandler|manejador de errores]]. + * Configura las propiedades de la aplicación. + * Se llama a [[yii\base\Application::init()|init()]] con la subsiguiente llamada a + [[yii\base\Application::bootstrap()|bootstrap()]] para correr componentes `bootstrap`. +3. El script de entrada llama a [[yii\base\Application::run()]] para correr la aplicación: + * Dispara el evento [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]]. + * Maneja el `request`: lo resuelve en una [route (ruta)](runtime-routing.md) con los parámetros asociados; + crea el módulo, controlador y objetos acción como se especifica en dicha ruta; y entonces ejecuta la acción. + * Dispara el evento [[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]]. + * Envía el `response` (respuesta) al usuario. +4. El script de entrada recibe el estado de salida de la aplicación y completa el proceso del `request`. diff --git a/docs/guide-es/structure-assets.md b/docs/guide-es/structure-assets.md new file mode 100644 index 00000000000..7edbf5d1b1b --- /dev/null +++ b/docs/guide-es/structure-assets.md @@ -0,0 +1,645 @@ +Assets +====== + +Un asset en Yii es un archivo al que se puede hacer referencia en una página Web. Puede ser un archivo CSS, un archivo +JavaScript, una imagen o un archivo de video, etc. Los assets se encuentran en los directorios públicos de la web y se +sirven directamente por los servidores Web. + +A menudo es preferible gestionar los assets mediante programación. Por ejemplo, cuando se usa el widget +[[yii\jui\DatePicker]] en una página, éste incluirá automáticamente los archivos CSS y JavaScript requeridos, en vez +de tener que buscar los archivos e incluirlos manualmente. Y cuando se actualice el widget a una nueva versión, ésta +usará de forma automática la nueva versión de los archivos asset. +En este tutorial, se describirá la poderosa capacidad que proporciona la gestión de assets en Yii. + +## Asset Bundles + +Yii gestiona los assets en unidades de *asset bundle*. Un asset bundle es simplemente un conjunto de assets +localizados en un directorio. Cuando se registra un asset bundle en una [vista](structure-views.md), éste incluirá los +archivos CSS y JavaScript del bundle en la página Web renderizada. + +## Definición de Asset Bundles + +Los asset bundles son descritos como clases PHP que extienden a [[yii\web\AssetBundle]]. El nombre del bundle es +simplemente su correspondiente nombre de la classe PHP que debe ser [autocargable](concept-autoloading.md). En una +clase asset bundle, lo más habitual es especificar donde se encuentran los archivos asset, que archivos CSS y +JavaScript contiene el bundle, y como depende este bundle de otros bundles. + +El siguiente código define el asset bundle principal que se usa en +[la plantilla de aplicación básica](start-installation.md): + +```php + + +Según la localización de los assets, se pueden clasificar como: + +* assets fuente (source assets): los assets se encuentran junto con el código fuente PHP, al que no se puede acceder + directamente a través de la Web. Para usar los assets fuente en una página, deben ser copiados en un directorio + público y transformados en los llamados assets publicados. El proceso se llama *publicación de assets* que será + descrito a continuación. +* assets publicados (published assets): los archivos assets se encuentran en el directorio Web y son accesibles vía Web. +* assets externos (external assets): los archivos assets se encuentran en un servidor Web diferente al de la aplicación. + +Cuando se define una clase asset bundle, si se especifica la propiedad [[yii\web\AssetBundle::sourcePath|sourcePath]], +significa que cualquier asset listado que use rutas relativas será considerado como un asset fuente. Si no se +especifica la propiedad, significa que los assets son assets publicados (se deben especificar +[[yii\web\AssetBundle::basePath|basePath]] y +[[yii\web\AssetBundle::baseUrl|baseUrl]] para hacerle saber a Yii dónde se encuentran.) + +Se recomienda ubicar los assets que correspondan a la aplicación en un directorio Web para evitar publicaciones de +assets innecesarias. Por esto en el anterior ejemplo `AppAsset` especifica [[yii\web\AssetBundle::basePath|basePath]] +en vez de [[yii\web\AssetBundle::sourcePath|sourcePath]]. + +Para las [extensiones](structure-extensions.md), por el hecho de que sus assets se encuentran junto con el código +fuente, en directorios que no son accesibles para la Web, se tiene que especificar la propiedad +[[yii\web\AssetBundle::sourcePath|sourcePath]] cuando se definan clases asset bundle para ellas. + +> Note: No se debe usar `@webroot/assets` como [[yii\web\AssetBundle::sourcePath|source path]]. Este directorio se usa + por defecto por el [[yii\web\AssetManager|asset manager]] para guardar los archivos asset publicados temporalmente y + pueden ser eliminados. + +### Dependencias de los Asset + +Cuando se incluyen múltiples archivos CSS o JavaScript en una página Web, tienen que cumplir ciertas órdenes para +evitar problemas de sobrescritura. Por ejemplo, si se usa un widget jQuery UI en una página Web, tenemos que +asegurarnos de que el archivo JavaScript jQuery se incluya antes que el archivo JavaScript jQuery UI. A esto se le +llama ordenar las dependencias entre archivos. + +Las dependencias de los assets se especifican principalmente a través de la propiedad [[yii\AssetBundle::depends]]. +En el ejemplo `AppAsset`, el asset bundle depende de otros dos asset bundles [[yii\web\YiiAsset]] y +[[yii\bootstrap\BootstrapAsset]], que significa que los archivos CSS y JavaScript en `AppAsset` se incluirán *después* +que los archivos de los dos bundles dependientes. + +Las dependencias son transitivas. Esto significa, que si un bundle A depende de un bundle B que depende de C, A +dependerá de C, también. + +### Opciones de los Assets + +Se pueden especificar las propiedades [[yii\web\AssetBundle::cssOptions|cssOptions]] y +[[yii\web\AssetBundle::jsOptions|jsOptions]] para personalizar la forma en que los archivos CSS y JavaScript serán +incluidos en una página. Los valores de estas propiedades serán enviadas a los métodos +[[yii\web\View::registerCssFile()]] y [[yii\web\View::registerJsFile()]], respectivamente cuando las +[vistas](structure-views.md) los llamen para incluir los archivos CSS y JavaScript. + +> Note: Las opciones que se especifican en una clase bundle se aplican a *todos* los archivos CSS/JavaScript de un + bundle. Si se quiere usar diferentes opciones para diferentes archivos, se deben crear assets bundles separados y + usar un conjunto de opciones para cada bundle. + +Por ejemplo, para incluir una archivo CSS condicionalmente para navegadores que como IE9 o anteriores, se puede usar la + siguiente opción: + +```php +public $cssOptions = ['condition' => 'lte IE9']; +``` + +Esto provoca que un archivo CSS dentro de un bundle sea incluido usando los siguientes tags HTML: + +```html + +``` + +Para envolver el tag del enlace con `