diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76a6e0b..a0f20c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,7 @@ services: - docker:dind variables: + DOCKER_YII2_PHP_IMAGE: yiisoftware/yii2-php:7.1-apache DOCKER_MYSQL_IMAGE: percona:5.7 DOCKER_POSTGRES_IMAGE: postgres:9.3 diff --git a/.travis.yml b/.travis.yml index 3efb1ef..741ad24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,9 +46,14 @@ cache: - $HOME/.composer/cache - $HOME/.npm -# try running against postgres 9.3 +# try running against postgres 9.6 addons: - postgresql: "9.3" + postgresql: "9.6" + apt: + sources: + - mysql-5.7-trusty + packages: + - mysql-server code_climate: repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b @@ -119,6 +124,7 @@ before_script: - | if [ $TASK_TESTS_PHP == 1 ]; then travis_retry mysql -e 'CREATE DATABASE `yiitest`;'; + mysql -e "SET GLOBAL sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';"; mysql -e "CREATE USER 'travis'@'localhost' IDENTIFIED WITH mysql_native_password;"; mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'localhost' WITH GRANT OPTION;"; psql -U postgres -c 'CREATE DATABASE yiitest;'; diff --git a/Dockerfile b/Dockerfile index 88761db..77ffdfd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM yiisoftware/yii2-php:7.1-apache +ARG DOCKER_YII2_PHP_IMAGE +FROM ${DOCKER_YII2_PHP_IMAGE} # Project source-code WORKDIR /project diff --git a/README.md b/README.md index 2bc59f1..40cf36e 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Yii 2 is a modern framework designed to be a solid foundation for your PHP appli It is fast, secure and efficient and works right out of the box pre-configured with reasonable defaults. The framework is easy to adjust to meet your needs, because Yii has been designed to be flexible. -[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2/v/stable.png)](https://packagist.org/packages/yiisoft/yii2) -[![Total Downloads](https://poser.pugx.org/yiisoft/yii2/downloads.png)](https://packagist.org/packages/yiisoft/yii2) +[![Latest Stable Version](https://img.shields.io/packagist/v/yiisoft/yii2.svg)](https://packagist.org/packages/yiisoft/yii2) +[![Total Downloads](https://img.shields.io/packagist/dt/yiisoft/yii2.svg)](https://packagist.org/packages/yiisoft/yii2) [![Build Status](https://img.shields.io/travis/yiisoft/yii2.svg)](http://travis-ci.org/yiisoft/yii2) [![Code Coverage](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/coverage.png?s=31d80f1036099e9d6a3e4d7738f6b000b3c3d10e)](https://scrutinizer-ci.com/g/yiisoft/yii2/) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/quality-score.png?s=b1074a1ff6d0b214d54fa5ab7abbb90fc092471d)](https://scrutinizer-ci.com/g/yiisoft/yii2/) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index a88e6cd..7a4e3b2 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -60,7 +60,7 @@ class ClassmapController extends Controller if (strpos($file, $root) !== 0) { throw new Exception("Something wrong: $file\n"); } - $path = str_replace('\\', '/', substr($file, strlen($root))); + $path = str_replace('\\', '/', substr($file, \strlen($root))); $map[$path] = " 'yii" . substr(str_replace('/', '\\', $path), 0, -4) . "' => YII2_PATH . '$path',"; } ksort($map); diff --git a/build/controllers/DevController.php b/build/controllers/DevController.php index 53fa402..5d64da5 100644 --- a/build/controllers/DevController.php +++ b/build/controllers/DevController.php @@ -24,19 +24,25 @@ use yii\helpers\FileHelper; */ class DevController extends Controller { + /** + * {@inheritdoc} + */ public $defaultAction = 'all'; - /** * @var bool whether to use HTTP when cloning github repositories */ public $useHttp = 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', @@ -98,17 +104,17 @@ class DevController extends Controller */ public function actionRun($command) { - $command = implode(' ', func_get_args()); + $command = implode(' ', \func_get_args()); // root of the dev repo - $base = dirname(dirname(__DIR__)); + $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)); + $displayDir = substr($dir, \strlen($base)); $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD); chdir($dir); passthru($command); @@ -137,7 +143,7 @@ class DevController extends Controller public function actionApp($app, $repo = null) { // root of the dev repo - $base = dirname(dirname(__DIR__)); + $base = \dirname(\dirname(__DIR__)); $appDir = "$base/apps/$app"; if (!file_exists($appDir)) { @@ -188,7 +194,7 @@ class DevController extends Controller public function actionExt($extension, $repo = null) { // root of the dev repo - $base = dirname(dirname(__DIR__)); + $base = \dirname(\dirname(__DIR__)); $extensionDir = "$base/extensions/$extension"; if (!file_exists($extensionDir)) { @@ -229,12 +235,12 @@ class DevController extends Controller } /** - * @inheritdoc + * {@inheritdoc} */ public function options($actionID) { $options = parent::options($actionID); - if (in_array($actionID, ['ext', 'app', 'all'], true)) { + if (\in_array($actionID, ['ext', 'app', 'all'], true)) { $options[] = 'useHttp'; } diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 1d3fea1..5d894ca 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -11,6 +11,7 @@ use Yii; use yii\console\Controller; use yii\helpers\Console; use yii\helpers\FileHelper; +use yii\helpers\Json; /** * PhpDocController is there to help maintaining PHPDoc annotation in class files. @@ -21,6 +22,9 @@ use yii\helpers\FileHelper; */ class PhpDocController extends Controller { + /** + * {@inheritdoc} + */ public $defaultAction = 'property'; /** * @var bool whether to update class docs directly. Setting this to false will just output docs @@ -82,7 +86,7 @@ class PhpDocController extends Controller $nFilesUpdated = 0; foreach ($files as $file) { $contents = file_get_contents($file); - $sha = sha1($contents); + $hash = $this->hash($contents); // fix line endings $lines = preg_split('/(\r\n|\n|\r)/', $contents); @@ -94,10 +98,10 @@ class PhpDocController extends Controller $lines = array_values($this->fixLineSpacing($lines)); $newContent = implode("\n", $lines); - if ($sha !== sha1($newContent)) { + if ($hash !== $this->hash($newContent)) { + file_put_contents($file, $newContent); $nFilesUpdated++; } - file_put_contents($file, $newContent); $nFilesTotal++; } @@ -106,13 +110,18 @@ class PhpDocController extends Controller } /** - * @inheritdoc + * {@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 = []; @@ -132,7 +141,7 @@ class PhpDocController extends Controller '/generators/extension/default/AutoloadExample.php', ], 'swiftmailer' => [ - '/Logger.php', + 'src/Logger.php', ], 'twig' => [ '/Extension.php', @@ -147,13 +156,9 @@ class PhpDocController extends Controller } if ($root === null) { - $root = dirname(YII2_PATH); + $root = \dirname(YII2_PATH); $extensionPath = "$root/extensions"; - foreach (scandir($extensionPath) as $extension) { - if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { - Yii::setAlias("@yii/$extension", "$extensionPath/$extension"); - } - } + $this->setUpExtensionAliases($extensionPath); $except = [ '/apps/', @@ -172,11 +177,11 @@ class PhpDocController extends Controller } } } elseif (preg_match('~extensions/([\w-]+)[\\\\/]?$~', $root, $matches)) { - $extensionPath = dirname(rtrim($root, '\\/')); + $extensionPath = \dirname(rtrim($root, '\\/')); $this->setUpExtensionAliases($extensionPath); - $extension = $matches[1]; - Yii::setAlias("@yii/$extension", "$root"); + [, $extension] = $matches; + Yii::setAlias("@yii/$extension", (string)$root); if (is_file($autoloadFile = Yii::getAlias("@yii/$extension/vendor/autoload.php"))) { include $autoloadFile; } @@ -195,11 +200,11 @@ class PhpDocController extends Controller // return []; // } } elseif (preg_match('~apps/([\w-]+)[\\\\/]?$~', $root, $matches)) { - $extensionPath = dirname(dirname(rtrim($root, '\\/'))) . '/extensions'; + $extensionPath = \dirname(\dirname(rtrim($root, '\\/'))) . '/extensions'; $this->setUpExtensionAliases($extensionPath); - $appName = $matches[1]; - Yii::setAlias("@app-$appName", "$root"); + [, $appName] = $matches; + Yii::setAlias("@app-$appName", (string)$root); if (is_file($autoloadFile = Yii::getAlias("@app-$appName/vendor/autoload.php"))) { include $autoloadFile; } @@ -230,14 +235,30 @@ class PhpDocController extends Controller 'vendor/', ]), ]; + 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); + } + } + } } } } @@ -285,6 +306,7 @@ class PhpDocController extends Controller /** * Markdown aware fix of whitespace issues in doc comments. + * @param array $lines */ protected function fixDocBlockIndentation(&$lines) { @@ -326,7 +348,7 @@ class PhpDocController extends Controller $codeBlock = !$codeBlock; $listIndent = ''; } elseif (preg_match('/^(\s*)([0-9]+\.|-|\*|\+) /', $docLine, $matches)) { - $listIndent = str_repeat(' ', strlen($matches[0])); + $listIndent = str_repeat(' ', \strlen($matches[0])); $tag = false; $lines[$i] = $indent . ' * ' . $docLine; continue; @@ -340,6 +362,10 @@ class PhpDocController extends Controller } } + /** + * @param string $line + * @return string + */ protected function fixParamTypes($line) { return preg_replace_callback('~@(param|return) ([\w\\|]+)~i', function ($matches) { @@ -532,10 +558,10 @@ class PhpDocController extends Controller if (trim($oldDoc) != trim($newDoc)) { $fileContent = explode("\n", file_get_contents($file)); $start = $ref->getStartLine() - 2; - $docStart = $start - count(explode("\n", $oldDoc)) + 1; + $docStart = $start - \count(explode("\n", $oldDoc)) + 1; $newFileContent = []; - $n = count($fileContent); + $n = \count($fileContent); for ($i = 0; $i < $n; $i++) { if ($i > $start || $i < $docStart) { $newFileContent[] = $fileContent[$i]; @@ -562,7 +588,7 @@ class PhpDocController extends Controller protected function cleanDocComment($doc) { $lines = explode("\n", $doc); - $n = count($lines); + $n = \count($lines); for ($i = 0; $i < $n; $i++) { $lines[$i] = rtrim($lines[$i]); if (trim($lines[$i]) == '*' && trim($lines[$i + 1]) == '*') { @@ -603,7 +629,7 @@ class PhpDocController extends Controller // if no properties or other tags where present add properties at the end if ($propertyPosition === false) { - $propertyPosition = count($lines) - 2; + $propertyPosition = \count($lines) - 2; } $finalDoc = ''; @@ -626,22 +652,22 @@ class PhpDocController extends Controller $namespace = $namespace['name']; $classes = $this->match('#\n(?:abstract )?class (?\w+)( extends .+)?( implements .+)?\n\{(?.*)\n\}(\n|$)#', $file); - if (count($classes) > 1) { + 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) { + if (\count($classes) < 1) { $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.*)\n\}(\n|$)#', $file); - if (count($interfaces) == 1) { + if (\count($interfaces) == 1) { return false; - } elseif (count($interfaces) > 1) { + } elseif (\count($interfaces) > 1) { $this->stderr("[ERR] There should be only one interface in a file: $fileName\n", Console::FG_RED); } else { $traits = $this->match('#\ntrait (?\w+)\n\{(?.*)\n\}(\n|$)#', $file); - if (count($traits) == 1) { + if (\count($traits) == 1) { return false; - } elseif (count($traits) > 1) { + } elseif (\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); @@ -682,13 +708,13 @@ class PhpDocController extends Controller ksort($props); - if (count($props) > 0) { + if (\count($props) > 0) { $phpdoc .= " *\n"; foreach ($props as $propName => &$prop) { $docline = ' * @'; $docline .= 'property'; // Do not use property-read and property-write as few IDEs support complex syntax. $note = ''; - if (isset($prop['get']) && isset($prop['set'])) { + 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.'; @@ -706,7 +732,7 @@ class PhpDocController extends Controller } if (!$parentSetter) { $note = ' This property is read-only.'; -// $docline .= '-read'; + //$docline .= '-read'; } } elseif (isset($prop['set'])) { // check if parent class has getter defined @@ -721,7 +747,7 @@ class PhpDocController extends Controller } if (!$parentGetter) { $note = ' This property is write-only.'; -// $docline .= '-write'; + //$docline .= '-write'; } } else { continue; @@ -773,11 +799,24 @@ class PhpDocController extends Controller return ''; } - return strtoupper(substr($str, 0, 1)) . substr($str, 1) . ($str[strlen($str) - 1] != '.' ? '.' : ''); + 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); + } } diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index 32f5c1a..336c46c 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -82,7 +82,7 @@ class ReleaseController extends Controller 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 = \dirname(\dirname(__DIR__)); } $this->basePath = rtrim($this->basePath, '\\/'); return parent::beforeAction($action); @@ -109,7 +109,7 @@ class ReleaseController extends Controller foreach ($items as $item) { $this->stdout("fetching tags for $item..."); if ($item === 'framework') { - $this->gitFetchTags("{$this->basePath}"); + $this->gitFetchTags((string)$this->basePath); } elseif (strncmp('app-', $item, 4) === 0) { $this->gitFetchTags("{$this->basePath}/apps/" . substr($item, 4)); } else { @@ -183,7 +183,7 @@ class ReleaseController extends Controller */ public function actionRelease(array $what) { - if (count($what) > 1) { + if (\count($what) > 1) { $this->stdout("Currently only one simultaneous release is supported.\n"); return 1; } @@ -315,7 +315,7 @@ class ReleaseController extends Controller */ public function actionSortChangelog(array $what) { - if (count($what) > 1) { + if (\count($what) > 1) { $this->stdout("Currently only one simultaneous release is supported.\n"); return 1; } @@ -376,7 +376,7 @@ class ReleaseController extends Controller { foreach ($what as $w) { if (strncmp('app-', $w, 4) === 0) { - if (!empty($limit) && !in_array('app', $limit)) { + 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))) { @@ -386,7 +386,7 @@ class ReleaseController extends Controller $this->ensureGitClean($appPath); } } elseif ($w === 'framework') { - if (!empty($limit) && !in_array('framework', $limit)) { + 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")) { @@ -396,7 +396,7 @@ class ReleaseController extends Controller $this->ensureGitClean($fwPath); } } else { - if (!empty($limit) && !in_array('ext', $limit)) { + 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")) { @@ -414,7 +414,7 @@ class ReleaseController extends Controller { $this->stdout("\n"); $this->stdout($h = "Preparing framework release version $version", Console::BOLD); - $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + $this->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); @@ -552,7 +552,7 @@ class ReleaseController extends Controller { $this->stdout("\n"); $this->stdout($h = "Preparing release for application $name version $version", Console::BOLD); - $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + $this->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); @@ -670,7 +670,7 @@ class ReleaseController extends Controller { $this->stdout("\n"); $this->stdout($h = "Preparing release for extension $name version $version", Console::BOLD); - $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + $this->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); @@ -795,7 +795,11 @@ class ReleaseController extends Controller protected function gitFetchTags($path) { - chdir($path); + try { + chdir($path); + } catch (\yii\base\ErrorException $e) { + throw new Exception('Failed to getch 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); @@ -817,7 +821,7 @@ class ReleaseController extends Controller $headline = $version . ' ' . date('F d, Y'); $this->sed( '/' . $v . ' under development\n(-+?)\n/', - $headline . "\n" . str_repeat('-', strlen($headline)) . "\n", + $headline . "\n" . str_repeat('-', \strlen($headline)) . "\n", $this->getChangelogs($what) ); } @@ -825,7 +829,7 @@ class ReleaseController extends Controller 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"; + $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 = [ @@ -874,7 +878,7 @@ class ReleaseController extends Controller $state = 'end'; } // add continued lines to the last item to keep them together - if (!empty(${$state}) && trim($line !== '') && strpos($line, '- ') !== 0) { + if (!empty(${$state}) && trim($line) !== '' && strncmp($line, '- ', 2) !== 0) { end(${$state}); ${$state}[key(${$state})] .= "\n" . $line; } else { @@ -919,7 +923,7 @@ class ReleaseController extends Controller protected function getChangelogs($what) { $changelogs = []; - if (in_array('framework', $what)) { + if (\in_array('framework', $what)) { $changelogs[] = $this->getFrameworkChangelog(); } @@ -947,13 +951,13 @@ class ReleaseController extends Controller protected function composerSetStability($what, $version) { $apps = []; - if (in_array('app-advanced', $what)) { + if (\in_array('app-advanced', $what)) { $apps[] = $this->basePath . '/apps/advanced/composer.json'; } - if (in_array('app-basic', $what)) { + if (\in_array('app-basic', $what)) { $apps[] = $this->basePath . '/apps/basic/composer.json'; } - if (in_array('app-benchmark', $what)) { + if (\in_array('app-benchmark', $what)) { $apps[] = $this->basePath . '/apps/benchmark/composer.json'; } if (empty($apps)) { diff --git a/build/controllers/Utf8Controller.php b/build/controllers/Utf8Controller.php index 64e8c0a..ca5175d 100644 --- a/build/controllers/Utf8Controller.php +++ b/build/controllers/Utf8Controller.php @@ -29,7 +29,7 @@ class Utf8Controller extends Controller public function actionCheckGuide($directory = null) { if ($directory === null) { - $directory = dirname(dirname(__DIR__)) . '/docs'; + $directory = \dirname(\dirname(__DIR__)) . '/docs'; } if (is_file($directory)) { $files = [$directory]; @@ -90,7 +90,7 @@ class Utf8Controller extends Controller } $hexcode = dechex($this->unicodeOrd($char)); - $hexcode = str_repeat('0', max(4 - strlen($hexcode), 0)) . $hexcode; + $hexcode = str_repeat('0', max(4 - \strlen($hexcode), 0)) . $hexcode; $this->stdout(" at $line:$pos FOUND $what: 0x$hexcode '$char' http://unicode-table.com/en/$hexcode/\n"); } @@ -105,20 +105,20 @@ class Utf8Controller extends Controller */ private function unicodeOrd($c) { - $h = ord($c[0]); + $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); + return ($h & 0x1F) << 6 | (\ord($c[1]) & 0x3F); } elseif ($h <= 0xEF) { - return ($h & 0x0F) << 12 | (ord($c[1]) & 0x3F) << 6 - | (ord($c[2]) & 0x3F); + 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 ($h & 0x0F) << 18 | (\ord($c[1]) & 0x3F) << 12 + | (\ord($c[2]) & 0x3F) << 6 + | (\ord($c[3]) & 0x3F); } return false; diff --git a/docs/guide-es/concept-events.md b/docs/guide-es/concept-events.md index 2eee4ce..d39979d 100644 --- a/docs/guide-es/concept-events.md +++ b/docs/guide-es/concept-events.md @@ -218,7 +218,7 @@ use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { - Yii::trace(get_class($event->sender) . ' is inserted'); + Yii::debug(get_class($event->sender) . ' is inserted'); }); ``` diff --git a/docs/guide-es/db-migrations.md b/docs/guide-es/db-migrations.md index c71fcf3..2c44709 100644 --- a/docs/guide-es/db-migrations.md +++ b/docs/guide-es/db-migrations.md @@ -204,7 +204,7 @@ esto genera class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -214,7 +214,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -238,7 +238,7 @@ genera class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -250,7 +250,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -275,7 +275,7 @@ genera class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -287,7 +287,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -320,7 +320,7 @@ genera class m160328_040430_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -368,7 +368,7 @@ class m160328_040430_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -522,7 +522,7 @@ genera class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -569,7 +569,7 @@ class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migra } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { diff --git a/docs/guide-es/runtime-logging.md b/docs/guide-es/runtime-logging.md index a09ec93..bb01a21 100644 --- a/docs/guide-es/runtime-logging.md +++ b/docs/guide-es/runtime-logging.md @@ -20,7 +20,7 @@ En esta sección, se describirán principalmente los dos primeros pasos. Registrar mensajes de anotación es tan simple como llamar a uno de los siguientes métodos de registro de anotaciones. -* [[Yii::trace()]]: registra un mensaje para trazar el funcionamiento de una sección de código. Se usa principalmente +* [[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. @@ -32,7 +32,7 @@ tiene que ser registrado, mientras que `$category` es la categoría del registro ejemplo registra la huella del mensaje para la categoría `application`: ```php -Yii::trace('start calculating average revenue'); +Yii::debug('start calculating average revenue'); ``` > Info: Los mensajes de registro pueden ser tanto cadenas de texto como datos complejos, como arrays u objetos. @@ -47,7 +47,7 @@ efectiva de organizarlos es usar la constante predefinida (magic constant) de PH 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::trace('start calculating average revenue', __METHOD__); +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 @@ -131,7 +131,7 @@ La propiedad [[yii\log\Target::levels|levels]] es un array que consta de uno o v * `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::trace()]]. +* `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). @@ -290,7 +290,7 @@ número configurando la propiedad [[yii\log\Target::exportInterval|exportInterva ``` Debido al nivel de configuración de la liberación y exportación de mensajes, de forma predeterminada cuando se llama a -`Yii::trace()` o cualquier otro método de registro de mensajes, NO veremos el registro de mensaje inmediatamente en +`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 diff --git a/docs/guide-es/structure-filters.md b/docs/guide-es/structure-filters.md index 3c6cd2c..a6be6c2 100644 --- a/docs/guide-es/structure-filters.md +++ b/docs/guide-es/structure-filters.md @@ -94,7 +94,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("Action '{$action->uniqueId}' spent $time second."); + Yii::debug("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } } diff --git a/docs/guide-fr/caching-data.md b/docs/guide-fr/caching-data.md index d3cd58d..6d23274 100644 --- a/docs/guide-fr/caching-data.md +++ b/docs/guide-fr/caching-data.md @@ -100,8 +100,8 @@ Yii prend en charge un large panel de supports de stockage pour cache. Ce qui su (une version de redis égale ou supérieure à 2.6.12 est nécessaire). * [[yii\caching\WinCache]]: utilise le [WinCache](http://iis.net/downloads/microsoft/wincache-extension) PHP ([voir aussi l'extension](http://php.net/manual/en/book.wincache.php)). -* [[yii\caching\XCache]]: utilise l'extension PHP [XCache](http://xcache.lighttpd.net/). -* [[yii\caching\ZendDataCache]]: utilise le +* [[yii\caching\XCache]] _(deprecated)_: utilise l'extension PHP [XCache](http://xcache.lighttpd.net/). +* [[yii\caching\ZendDataCache]] _(deprecated)_: utilise le [cache de données Zend](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) en tant que médium de cache sous-jacent. diff --git a/docs/guide-fr/concept-events.md b/docs/guide-fr/concept-events.md index 46a6196..000e150 100644 --- a/docs/guide-fr/concept-events.md +++ b/docs/guide-fr/concept-events.md @@ -263,7 +263,7 @@ Pour gérer l'évenement `EVENT_DANCE` déclenché par n'importe laquelle de ces ```php Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) { - Yii::trace(get_class($event->sender) . ' danse'); // enregistrer le message disant que le chien ou le développeur danse. + Yii::debug(get_class($event->sender) . ' danse'); // enregistrer le message disant que le chien ou le développeur danse. }) ``` diff --git a/docs/guide-fr/db-migrations.md b/docs/guide-fr/db-migrations.md index e44a4ac..6498cdf 100644 --- a/docs/guide-fr/db-migrations.md +++ b/docs/guide-fr/db-migrations.md @@ -169,7 +169,7 @@ génère class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -179,7 +179,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -203,7 +203,7 @@ génère class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -215,7 +215,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -240,7 +240,7 @@ génère class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -252,7 +252,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -284,7 +284,7 @@ génère class m160328_040430_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -332,7 +332,7 @@ class m160328_040430_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -477,7 +477,7 @@ génère class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -524,7 +524,7 @@ class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migra } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { diff --git a/docs/guide-fr/output-data-providers.md b/docs/guide-fr/output-data-providers.md index 4f8b6c2..392ca2c 100644 --- a/docs/guide-fr/output-data-providers.md +++ b/docs/guide-fr/output-data-providers.md @@ -220,7 +220,7 @@ class CsvDataProvider extends BaseDataProvider /** - * @inheritdoc + * {@inheritdoc} */ public function init() { @@ -231,7 +231,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareModels() { @@ -260,7 +260,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareKeys($models) { @@ -282,7 +282,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareTotalCount() { diff --git a/docs/guide-fr/output-data-widgets.md b/docs/guide-fr/output-data-widgets.md index b5fa958..dbe0ff1 100644 --- a/docs/guide-fr/output-data-widgets.md +++ b/docs/guide-fr/output-data-widgets.md @@ -561,7 +561,7 @@ class UserView extends ActiveRecord { /** - * @inheritdoc + * {@inheritdoc} */ public static function tableName() { @@ -574,7 +574,7 @@ class UserView extends ActiveRecord } /** - * @inheritdoc + * {@inheritdoc} */ public function rules() { @@ -584,7 +584,7 @@ class UserView extends ActiveRecord } /** - * @inheritdoc + * {@inheritdoc} */ public function attributeLabels() { diff --git a/docs/guide-fr/runtime-logging.md b/docs/guide-fr/runtime-logging.md index bb0896a..8cd79ce 100644 --- a/docs/guide-fr/runtime-logging.md +++ b/docs/guide-fr/runtime-logging.md @@ -16,7 +16,7 @@ Dans cette section, nous décrivons principalement les deux premières étapes. Enregistrer des messages est aussi simple que d'appeler une des méthodes suivantes : -* [[Yii::trace()]]: enregistre un message pour garder une trace de comment un morceau de code fonctionne. Cela est utilisé principalement en développement. +* [[Yii::debug()]]: enregistre un message pour garder une trace de comment un morceau de code fonctionne. Cela est utilisé principalement en développement. * [[Yii::info()]]: enregistre un message qui contient quelques informations utiles. * [[Yii::warning()]]: enregistre un message d'avertissement qui indique que quelque chose d'inattendu s'est produit. * [[Yii::error()]]: enregistre une erreur fatale qui doit être analysée dès que possible. @@ -24,7 +24,7 @@ Enregistrer des messages est aussi simple que d'appeler une des méthodes suivan Ces méthodes enregistrent les messages à différents niveaux de sévérité et dans différentes catégories. Elles partagent la même signature `function ($message, $category = 'application')`, où `$message` représente le message à enregistrer, tandis que `$category` est la catégorie de ce message. Le code de l'exemple qui suit enregistre un message de trace dans la catégorie `application`: ```php -Yii::trace('start calculating average revenue'); +Yii::debug('start calculating average revenue'); ``` > Info: les messages enregistrés peuvent être des chaînes de caractères aussi bien que des données complexes telles que des tableaux ou des objets. Il est de la responsabilité des [cibles d'enregistrement](#log-targets) de traiter correctement ces messages. Par défaut, si un message enregistré n'est pas un chaîne de caractères, il est exporté comme une chaîne de caractères en appelant la méthode [[yii\helpers\VarDumper::export()]]. @@ -32,7 +32,7 @@ Yii::trace('start calculating average revenue'); Pour mieux organiser et filtrer les messages enregistrés, il est recommandé que vous spécifiiez une catégorie appropriée pour chacun des messages. Vous pouvez choisir une schéma de nommage hiérarchisé pour les catégories, ce qui facilitera le filtrage des messages par les [cibles d'enregistrement](#log-targets) sur la base de ces catégories. Un schéma de nommage simple et efficace est d'utiliser la constante magique `__METHOD__` de PHP dans les noms de catégorie. Par exemple : ```php -Yii::trace('start calculating average revenue', __METHOD__); +Yii::debug('start calculating average revenue', __METHOD__); ``` La constante magique `__METHOD__` est évaluée comme le nom de la méthode (préfixée par le nom pleinement qualifié de la classe), là où la constante apparaît. Par exemple, elle est égale à `'app\controllers\RevenueController::calculate'` si la ligne suivante est utilisée dans cette méthode. @@ -100,7 +100,7 @@ La propriété [[yii\log\Target::levels|levels]] accepte un tableau constitué d * `error`: correspondant aux messages enregistrés par [[Yii::error()]]. * `warning`: correspondant aux messages enregistrés par [[Yii::warning()]]. * `info`: correspondant aux messages enregistrés par [[Yii::info()]]. -* `trace`: correspondant aux messages enregistrés par [[Yii::trace()]]. +* `trace`: correspondant aux messages enregistrés par [[Yii::debug()]]. * `profile`: correspondant aux messages enregistrés par [[Yii::beginProfile()]] et [[Yii::endProfile()]], et qui sera expliqué en détails dans la sous-section [Profilage de la performance](#performance-profiling). Si vous ne spécifiez pas la propriété [[yii\log\Target::levels|levels]], cela signifie que la cible traitera les messages de *n'importe quel* niveau de sévérité. @@ -222,7 +222,7 @@ Lorsque l'[[yii\log\Logger|objet *logger*]] purge les messages enregistrés vers ] ``` -À cause des niveaux de purge et d'exportation, par défaut, lorsque vous appelez `Yii::trace()` ou toute autre méthode d'enregistrement, vous ne voyez PAS immédiatement le message enregistré dans la cible. Cela peut représenter un problème pour pour certaines applications de console qui durent longtemps. Pour faire en sorte que les messages apparaissent immédiatement dans les cibles d'enregistrement, vous devriez définir les propriétés [[yii\log\Dispatcher::flushInterval|flushInterval]] et [[yii\log\Target::exportInterval|exportInterval]] toutes deux à 1, comme montré ci-après : +À cause des niveaux de purge et d'exportation, par défaut, lorsque vous appelez `Yii::debug()` ou toute autre méthode d'enregistrement, vous ne voyez PAS immédiatement le message enregistré dans la cible. Cela peut représenter un problème pour pour certaines applications de console qui durent longtemps. Pour faire en sorte que les messages apparaissent immédiatement dans les cibles d'enregistrement, vous devriez définir les propriétés [[yii\log\Dispatcher::flushInterval|flushInterval]] et [[yii\log\Target::exportInterval|exportInterval]] toutes deux à 1, comme montré ci-après : ```php return [ diff --git a/docs/guide-fr/structure-filters.md b/docs/guide-fr/structure-filters.md index 4fda236..9a7a6ca 100644 --- a/docs/guide-fr/structure-filters.md +++ b/docs/guide-fr/structure-filters.md @@ -72,7 +72,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("Action '{$action->uniqueId}' spent $time second."); + Yii::debug("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } } diff --git a/docs/guide-ja/concept-events.md b/docs/guide-ja/concept-events.md index 7200c9b..96eddfb 100644 --- a/docs/guide-ja/concept-events.md +++ b/docs/guide-ja/concept-events.md @@ -209,7 +209,7 @@ use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { - Yii::trace(get_class($event->sender) . ' が挿入されました'); + Yii::debug(get_class($event->sender) . ' が挿入されました'); }); ``` @@ -292,7 +292,7 @@ class Developer extends Component implements DanceEventInterface ```php Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) { - Yii::trace(get_class($event->sender) . ' が躍り上がって喜んだ。'); // 犬または開発者が躍り上がって喜んだことをログに記録。 + Yii::debug(get_class($event->sender) . ' が躍り上がって喜んだ。'); // 犬または開発者が躍り上がって喜んだことをログに記録。 }) ``` diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md index a347f0e..c70dd6a 100644 --- a/docs/guide-ja/db-migrations.md +++ b/docs/guide-ja/db-migrations.md @@ -196,7 +196,7 @@ yii migrate/create create_post_table class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -206,7 +206,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -230,7 +230,7 @@ yii migrate/create create_post_table --fields="title:string,body:text" class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -242,7 +242,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -267,7 +267,7 @@ yii migrate/create create_post_table --fields="title:string(12):notNull:unique,b class m150811_220037_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -279,7 +279,7 @@ class m150811_220037_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -313,7 +313,7 @@ yii migrate/create create_post_table --fields="author_id:integer:notNull:foreign class m160328_040430_create_post_table extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -361,7 +361,7 @@ class m160328_040430_create_post_table extends Migration } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { @@ -521,7 +521,7 @@ yii migrate/create create_junction_table_for_post_and_tag_tables --fields="creat class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migration { /** - * @inheritdoc + * {@inheritdoc} */ public function up() { @@ -568,7 +568,7 @@ class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migra } /** - * @inheritdoc + * {@inheritdoc} */ public function down() { diff --git a/docs/guide-ja/output-data-providers.md b/docs/guide-ja/output-data-providers.md index 18402dd..60032ac 100644 --- a/docs/guide-ja/output-data-providers.md +++ b/docs/guide-ja/output-data-providers.md @@ -250,7 +250,7 @@ class CsvDataProvider extends BaseDataProvider /** - * @inheritdoc + * {@inheritdoc} */ public function init() { @@ -261,7 +261,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareModels() { @@ -290,7 +290,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareKeys($models) { @@ -312,7 +312,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareTotalCount() { diff --git a/docs/guide-ja/output-data-widgets.md b/docs/guide-ja/output-data-widgets.md index b5367ea..fb296af 100644 --- a/docs/guide-ja/output-data-widgets.md +++ b/docs/guide-ja/output-data-widgets.md @@ -637,7 +637,7 @@ class UserView extends ActiveRecord { /** - * @inheritdoc + * {@inheritdoc} */ public static function tableName() { @@ -650,7 +650,7 @@ class UserView extends ActiveRecord } /** - * @inheritdoc + * {@inheritdoc} */ public function rules() { @@ -660,7 +660,7 @@ class UserView extends ActiveRecord } /** - * @inheritdoc + * {@inheritdoc} */ public function attributeLabels() { diff --git a/docs/guide-ja/runtime-logging.md b/docs/guide-ja/runtime-logging.md index f644b63..5a4f308 100644 --- a/docs/guide-ja/runtime-logging.md +++ b/docs/guide-ja/runtime-logging.md @@ -17,7 +17,7 @@ Yii のロギングフレームワークを使うためには、下記のステ ログメッセージを記録することは、次のログ記録メソッドのどれかを呼び出すだけの簡単なことです。 -* [[Yii::trace()]]: コードの断片がどのように走ったかをトレースするメッセージを記録します。主として開発のために使用します。 +* [[Yii::debug()]]: コードの断片がどのように走ったかをトレースするメッセージを記録します。主として開発のために使用します。 * [[Yii::info()]]: 何らかの有用な情報を伝えるメッセージを記録します。 * [[Yii::warning()]]: 何か予期しないことが発生したことを示す警告メッセージを記録します。 * [[Yii::error()]]: 出来るだけ早急に調査すべき致命的なエラーを記録します。 @@ -27,7 +27,7 @@ Yii のロギングフレームワークを使うためには、下記のステ 次のコードサンプルは、トレースメッセージをデフォルトのカテゴリである `application` の下に記録するものです。 ```php -Yii::trace('平均収益の計算を開始'); +Yii::debug('平均収益の計算を開始'); ``` > Info: ログメッセージは文字列でも、配列やオブジェクトのような複雑なデータでも構いません。 @@ -40,7 +40,7 @@ Yii::trace('平均収益の計算を開始'); これは、Yii フレームワークのコアコードでも使われている方法です。例えば、 ```php -Yii::trace('平均収益の計算を開始', __METHOD__); +Yii::debug('平均収益の計算を開始', __METHOD__); ``` `__METHOD__` という定数は、それが出現する場所のメソッド名 (完全修飾のクラス名が前置されます) として評価されます。 @@ -116,7 +116,7 @@ Yii は下記のログターゲットをあらかじめ内蔵しています。 * `error`: [[Yii::error()]] によって記録されたメッセージに対応。 * `warning`: [[Yii::warning()]] によって記録されたメッセージに対応。 * `info`: [[Yii::info()]] によって記録されたメッセージに対応。 -* `trace`: [[Yii::trace()]] によって記録されたメッセージに対応。 +* `trace`: [[Yii::debug()]] によって記録されたメッセージに対応。 * `profile`: [[Yii::beginProfile()]] と [[Yii::endProfile()]] によって記録されたメッセージに対応。 これについては、[プロファイリング](#performance-profiling) の項で詳細に説明します。 @@ -255,7 +255,7 @@ return [ ] ``` -デフォルトの状態では、吐き出しとエクスポートの間隔の設定のために、`Yii::trace()` やその他のログ記録メソッドを呼んでも、ただちには、ログメッセージはログターゲットに出現しません。 +デフォルトの状態では、吐き出しとエクスポートの間隔の設定のために、`Yii::debug()` やその他のログ記録メソッドを呼んでも、ただちには、ログメッセージはログターゲットに出現しません。 このことは、長時間にわたって走るコンソールアプリケーションでは、問題になる場合もあります。 各ログメッセージがただちにログターゲットに出現するようにするためには、下記のように、[[yii\log\Dispatcher::flushInterval|flushInterval]] と [[yii\log\Target::exportInterval|exportInterval]] の両方を 1 に設定しなければなりません。 diff --git a/docs/guide-ja/structure-filters.md b/docs/guide-ja/structure-filters.md index f91e7bc..9bc9cf8 100644 --- a/docs/guide-ja/structure-filters.md +++ b/docs/guide-ja/structure-filters.md @@ -83,7 +83,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("アクション '{$action->uniqueId}' は $time 秒を消費。"); + Yii::debug("アクション '{$action->uniqueId}' は $time 秒を消費。"); return parent::afterAction($action, $result); } } diff --git a/docs/guide-pt-BR/concept-events.md b/docs/guide-pt-BR/concept-events.md index c89d482..77719ab 100644 --- a/docs/guide-pt-BR/concept-events.md +++ b/docs/guide-pt-BR/concept-events.md @@ -192,7 +192,7 @@ use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { - Yii::trace(get_class($event->sender) . ' is inserted'); + Yii::debug(get_class($event->sender) . ' is inserted'); }); ``` diff --git a/docs/guide-pt-BR/output-data-providers.md b/docs/guide-pt-BR/output-data-providers.md index 872a253..c49626b 100644 --- a/docs/guide-pt-BR/output-data-providers.md +++ b/docs/guide-pt-BR/output-data-providers.md @@ -228,7 +228,7 @@ class CsvDataProvider extends BaseDataProvider protected $fileObject; // SplFileObject é muito conveniente para procurar uma linha específica em um arquivo /** - * @inheritdoc + * {@inheritdoc} */ public function init() { @@ -239,7 +239,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareModels() { @@ -265,7 +265,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareKeys($models) { @@ -285,7 +285,7 @@ class CsvDataProvider extends BaseDataProvider } /** - * @inheritdoc + * {@inheritdoc} */ protected function prepareTotalCount() { diff --git a/docs/guide-pt-BR/runtime-logging.md b/docs/guide-pt-BR/runtime-logging.md index d487244..555df02 100644 --- a/docs/guide-pt-BR/runtime-logging.md +++ b/docs/guide-pt-BR/runtime-logging.md @@ -16,7 +16,7 @@ Nesta seção, vamos descrever principalmente os dois primeiros passos. Gravar mensagens de log é tão simples como chamar um dos seguintes métodos de registro: -* [[Yii::trace()]]: gravar uma mensagem para rastrear como um determinado trecho de código é executado. Isso é principalmente para o uso de desenvolvimento. +* [[Yii::debug()]]: gravar uma mensagem para rastrear como um determinado trecho de código é executado. Isso é principalmente para o uso de desenvolvimento. * [[Yii::info()]]: gravar uma mensagem que transmite algumas informações úteis. * [[Yii::warning()]]: gravar uma mensagem de aviso que indica que algo inesperado aconteceu. * [[Yii::error()]]: gravar um erro fatal que deve ser investigado o mais rápido possível. @@ -24,7 +24,7 @@ Gravar mensagens de log é tão simples como chamar um dos seguintes métodos de Estes métodos gravam mensagens de log em vários *níveis* e *categorias*. Eles compartilham a mesma assinatura de função `function ($message, $category = 'application')`, onde `$message` significa a mensagem de log a ser gravada, enquanto `$category` é a categoria da mensagem de log. O código no exemplo a seguir registra uma mensagem de rastreamento sob a categoria padrão `application`: ```php -Yii::trace('start calculating average revenue'); +Yii::debug('start calculating average revenue'); ``` > Observação: Mensagens de log podem ser strings, bem como dados complexos, tais como arrays ou objetos. É da responsabilidade dos [destinos de log](#log-targets) lidar adequadamente com as mensagens de log. Por padrão, se uma mensagem de log não for uma string, ela será exportada como uma string chamando [[yii\helpers\VarDumper::export()]]. @@ -32,7 +32,7 @@ Yii::trace('start calculating average revenue'); Para melhor organizar e filtrar as mensagens de log, é recomendável que você especifique uma categoria apropriada para cada mensagem de log. Você pode escolher um esquema de nomenclatura hierárquica para as categorias, o que tornará mais fácil para os [destinos de log](#log-targets) filtrar mensagens com base em suas categorias. Um esquema de nomes simples, mas eficaz é usar a constante mágica PHP `__METHOD__` para os nomes das categorias. Esta é também a abordagem utilizada no código central do framework Yii. Por exemplo, ```php -Yii::trace('start calculating average revenue', __METHOD__); +Yii::debug('start calculating average revenue', __METHOD__); ``` A constante `__METHOD__` corresponde ao nome do método (prefixado com o caminho completo do nome da classe) onde a constante aparece. Por exemplo, é igual a string `'app\controllers\RevenueController::calculate'` se o código acima for chamado dentro deste método. @@ -100,7 +100,7 @@ A propriedade [[yii\log\Target::levels|levels]] é um array que consiste em um o * `error`: corresponde a mensagens logadas por [[Yii::error()]]. * `warning`: corresponde a mensagens logadas por [[Yii::warning()]]. * `info`: corresponde a mensagens logadas por [[Yii::info()]]. -* `trace`: corresponde a mensagens logadas por [[Yii::trace()]]. +* `trace`: corresponde a mensagens logadas por [[Yii::debug()]]. * `profile`: corresponde a mensagens logadas por [[Yii::beginProfile()]] e [[Yii::endProfile()]], que será explicado em mais detalhes na subseção [Perfil de Desempenho](#performance-profiling). Se você não especificar a propriedade [[yii\log\Target::levels|levels]], significa que o alvo de log processará mensagens de *qualquer* nível. @@ -218,7 +218,7 @@ Quando o [[yii\log\Logger|logger object]] libera mensagens de log para os [alvos ] ``` -Devido a configuração de nível, liberação e exportação, por padrão quando você chama `Yii::trace()` ou qualquer outro método de log, você NÃO verá a mensagem de log imediatamente no destino. Isto poderia ser um problema para algumas aplicações console de longa execução. Para fazer cada mensagem de log aparecer imediatamente no destino, você deve configurar ambos [[yii\log\Dispatcher::flushInterval|flushInterval]] e [[yii\log\Target::exportInterval|exportInterval]] para 1, como mostrado a seguir: +Devido a configuração de nível, liberação e exportação, por padrão quando você chama `Yii::debug()` ou qualquer outro método de log, você NÃO verá a mensagem de log imediatamente no destino. Isto poderia ser um problema para algumas aplicações console de longa execução. Para fazer cada mensagem de log aparecer imediatamente no destino, você deve configurar ambos [[yii\log\Dispatcher::flushInterval|flushInterval]] e [[yii\log\Target::exportInterval|exportInterval]] para 1, como mostrado a seguir: ```php return [ diff --git a/docs/guide-pt-BR/structure-filters.md b/docs/guide-pt-BR/structure-filters.md index 4048afc..2aecde8 100644 --- a/docs/guide-pt-BR/structure-filters.md +++ b/docs/guide-pt-BR/structure-filters.md @@ -105,7 +105,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("Action '{$action->uniqueId}' spent $time second."); + Yii::debug("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } } diff --git a/docs/guide-ru/caching-data.md b/docs/guide-ru/caching-data.md index 25072e6..ec7bf2b 100644 --- a/docs/guide-ru/caching-data.md +++ b/docs/guide-ru/caching-data.md @@ -252,7 +252,7 @@ $result = Customer::getDb()->cache(function ($db) { ### Очистка кэша -Для очистки всего кэша, вы можете вызвать [[yii\caching\Cache::clear()]]. +Для очистки всего кэша, вы можете вызвать [[yii\caching\Cache::flush()]]. Также вы можете очистить кэш из консоли, вызвав `yii cache/clear`. - `yii cache`: отображает список доступных кэширующих компонентов приложения diff --git a/docs/guide-ru/concept-events.md b/docs/guide-ru/concept-events.md index d3ec8a5..bd01fd2 100644 --- a/docs/guide-ru/concept-events.md +++ b/docs/guide-ru/concept-events.md @@ -188,7 +188,7 @@ use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { - Yii::trace(get_class($event->sender) . ' добавлен'); + Yii::debug(get_class($event->sender) . ' добавлен'); }); ``` @@ -266,7 +266,7 @@ class Developer extends Component implements DanceEventInterface ```php Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) { - Yii::trace(get_class($event->sender) . ' just danced'); // Оставит запись в журнале о том, что кто-то танцевал + Yii::debug(get_class($event->sender) . ' just danced'); // Оставит запись в журнале о том, что кто-то танцевал }); ``` diff --git a/docs/guide-ru/input-validation.md b/docs/guide-ru/input-validation.md index dacd5f9..6e3cca6 100644 --- a/docs/guide-ru/input-validation.md +++ b/docs/guide-ru/input-validation.md @@ -358,8 +358,8 @@ class MyForm extends Model public function validateCountry($attribute, $params) { - if (!in_array($this->$attribute, ['USA', 'Web'])) { - $this->addError($attribute, 'Страна должна быть либо "USA" или "Web".'); + if (!in_array($this->$attribute, ['USA', 'Indonesia'])) { + $this->addError($attribute, 'Страна должна быть либо "USA" или "Indonesia".'); } } } @@ -384,7 +384,9 @@ class MyForm extends Model Вы можете реализовать свою логику проверки путем переопределения метода [[yii\validators\Validator::validateAttribute()]]. Если атрибут не прошел проверку, вызвать [[yii\base\Model::addError()]], -чтобы сохранить сообщение об ошибке в модели, как это делают [встроенные валидаторы](#inline-validators). Например: +чтобы сохранить сообщение об ошибке в модели, как это делают [встроенные валидаторы](#inline-validators). + +Валидация может быть помещена в отдельный класс [[components/validators/CountryValidator]]. В этом случае можно использовать метод [[yii\validators\Validator::addError()]] для того, чтобы добавить своё сообщение об ошибке в модель: ```php namespace app\components; @@ -395,8 +397,8 @@ class CountryValidator extends Validator { public function validateAttribute($model, $attribute) { - if (!in_array($model->$attribute, ['USA', 'Web'])) { - $this->addError($model, $attribute, 'Страна должна быть либо "USA" или "Web".'); + if (!in_array($model->$attribute, ['USA', 'Indonesia'])) { + $this->addError($model, $attribute, 'Страна должна быть либо "{country1}" либо "{country2}".', ['country1' => 'USA', 'country2' => 'Indonesia']); } } } diff --git a/docs/guide-ru/runtime-logging.md b/docs/guide-ru/runtime-logging.md index cf7bcb1..84390da 100644 --- a/docs/guide-ru/runtime-logging.md +++ b/docs/guide-ru/runtime-logging.md @@ -14,7 +14,7 @@ Yii предоставляет мощную, гибко настраиваему Запись сообщений лога осуществляется вызовом одного из следующих методов: -* [[Yii::trace()]]: записывает сообщения для отслеживания выполнения кода приложения. Используется, в основном, при разработке. +* [[Yii::debug()]]: записывает сообщения для отслеживания выполнения кода приложения. Используется, в основном, при разработке. * [[Yii::info()]]: записывает сообщение, содержащее какую-либо полезную информацию. * [[Yii::warning()]]: записывает *тревожное* сообщение при возникновении неожиданного события. * [[Yii::error()]]: записывает критическую ошибку, на которую нужно, как можно скорее, обратить внимаение. @@ -22,7 +22,7 @@ Yii предоставляет мощную, гибко настраиваему Эти методы позволяют записывать сообщения разных *уровней важности* и *категорий*. Они имеют одинаковое описание функции `function ($message, $category = 'application')`, где `$message` передает сообщение для записи, а `$category` - категорию сообщения. В следующем примере будет записано *trace* сообщение с категорией по умолчанию `application`: ```php -Yii::trace('start calculating average revenue'); +Yii::debug('start calculating average revenue'); ``` > Note: Сообщение может быть как строкой так и объектом или массивом. За корректную работу с содержимым сообщения отвечают [цели лога](#log-targets). По умолчанию, если сообщение не является строкой, оно будет приведено к строковому типу при помощи [[yii\helpers\VarDumper::export()]]. @@ -30,7 +30,7 @@ Yii::trace('start calculating average revenue'); Для упрощения работы с сообщениями лога и их фильтрации, рекомендуется явно указывать подходящую категорию для каждого сообщения. Возможно использование иерархической системы именования категорий, что значительно упростит [целям лога](#log-targets) фильтрацию сообщений по категориям. Простым и эффективным способом именования категорий является использование магической PHP константы `__METHOD__`. Такой подход используется в ядре фреймворка Yii. Например, ```php -Yii::trace('начало вычисления среднего дохода', __METHOD__); +Yii::debug('начало вычисления среднего дохода', __METHOD__); ``` Константа `__METHOD__` вычисляется как имя метода (включая полное имя класса), в котором она использована. Например, её значение будет вычислено как `'app\controllers\RevenueController::calculate'`, если показанный выше код вызывается в соответствующем методе. @@ -101,7 +101,7 @@ return [ * `error`: соответствует сообщениям, сохраненным методом [[Yii::error()]]. * `warning`: соответствует сообщениям, сохраненным методом [[Yii::warning()]]. * `info`: соответствует сообщениям, сохраненным методом [[Yii::info()]]. -* `trace`: соответствует сообщениям, сохраненным методом [[Yii::trace()]]. +* `trace`: соответствует сообщениям, сохраненным методом [[Yii::debug()]]. * `profile`: соответствует сообщениям, сохраненным методами [[Yii::beginProfile()]] и [[Yii::endProfile()]], подробнее о которых написано в подразделе [Профилирование производительности](#performance-profiling). Если свойство [[yii\log\Target::levels|levels]] не задано, цель логов будет обрабатывать сообщения с *любым* уровнем важности. @@ -222,7 +222,7 @@ return [ ] ``` -Из-за того, что значения максимального количества сообщений для передачи и выгрузки по умолчанию достаточно велико, при вызове метода `Yii::trace()`, или любого другого метода логгирования, сообщение не появится сразу в файле или таблице базы данных. Такое поведение может стать проблемой, например, в консольных приложениях с большим временем исполнения. Для того, чтобы все сообщения логов сразу же попадали в лог, необходимо установить значения свойств [[yii\log\Dispatcher::flushInterval|flushInterval]] и [[yii\log\Target::exportInterval|exportInterval]] равными 1, например так: +Из-за того, что значения максимального количества сообщений для передачи и выгрузки по умолчанию достаточно велико, при вызове метода `Yii::debug()`, или любого другого метода логгирования, сообщение не появится сразу в файле или таблице базы данных. Такое поведение может стать проблемой, например, в консольных приложениях с большим временем исполнения. Для того, чтобы все сообщения логов сразу же попадали в лог, необходимо установить значения свойств [[yii\log\Dispatcher::flushInterval|flushInterval]] и [[yii\log\Target::exportInterval|exportInterval]] равными 1, например так: ```php return [ diff --git a/docs/guide-ru/structure-filters.md b/docs/guide-ru/structure-filters.md index 7dabb95..df4af7f 100644 --- a/docs/guide-ru/structure-filters.md +++ b/docs/guide-ru/structure-filters.md @@ -87,7 +87,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("Action '{$action->uniqueId}' spent $time second."); + Yii::debug("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } } diff --git a/docs/guide-zh-CN/concept-events.md b/docs/guide-zh-CN/concept-events.md index d00392e..e8782a6 100644 --- a/docs/guide-zh-CN/concept-events.md +++ b/docs/guide-zh-CN/concept-events.md @@ -182,7 +182,7 @@ use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) { - Yii::trace(get_class($event->sender) . ' is inserted'); + Yii::debug(get_class($event->sender) . ' is inserted'); }); ``` diff --git a/docs/guide-zh-CN/db-active-record.md b/docs/guide-zh-CN/db-active-record.md index a3d85aa..fca3c99 100644 --- a/docs/guide-zh-CN/db-active-record.md +++ b/docs/guide-zh-CN/db-active-record.md @@ -796,7 +796,7 @@ use yii\db\ActiveRecord; class Comment extends ActiveRecord { /** - * @inheritdoc + * {@inheritdoc} * @return CommentQuery */ public static function find() diff --git a/docs/guide-zh-CN/structure-filters.md b/docs/guide-zh-CN/structure-filters.md index 68f8ae6..0c83922 100644 --- a/docs/guide-zh-CN/structure-filters.md +++ b/docs/guide-zh-CN/structure-filters.md @@ -84,7 +84,7 @@ class ActionTimeFilter extends ActionFilter public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; - Yii::trace("Action '{$action->uniqueId}' spent $time second."); + Yii::debug("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } } diff --git a/docs/guide/caching-data.md b/docs/guide/caching-data.md index 54d789d..45b123f 100644 --- a/docs/guide/caching-data.md +++ b/docs/guide/caching-data.md @@ -319,6 +319,13 @@ $result = Customer::getDb()->cache(function ($db) { The query caching described above has the advantage that you may specify flexible cache dependencies and are potentially more efficient. +Since 2.0.14 you can use the following shortcuts: + +```php +(new Query())->cache(7200)->all(); +// and +User::find()->cache(7200)->all(); +``` ### Clearing Cache diff --git a/docs/guide/caching-fragment.md b/docs/guide/caching-fragment.md index a9fef47..5a5e9a1 100644 --- a/docs/guide/caching-fragment.md +++ b/docs/guide/caching-fragment.md @@ -174,3 +174,6 @@ if ($this->beginCache($id1)) { The [[yii\base\View::renderDynamic()|renderDynamic()]] method takes a piece of PHP code as its parameter. The return value of the PHP code is treated as the dynamic content. The same PHP code will be executed for every request, no matter the enclosing fragment is being served from cached or not. + +> Note: since version 2.0.14 a dynamic content API is exposed via the [[yii\base\DynamicContentAwareInterface]] interface and its [[yii\base\DynamicContentAwareTrait]] trait. + As an example, you may refer to the [[yii\widgets\FragmentCache]] class. diff --git a/docs/guide/concept-events.md b/docs/guide/concept-events.md index ba79137..61cec01 100644 --- a/docs/guide/concept-events.md +++ b/docs/guide/concept-events.md @@ -370,7 +370,7 @@ $foo = new Foo(); $foo->on('foo.event.*', function ($event) { // triggered for any event, which name starts on 'foo.event.' - Yii::trace('trigger event: ' . $event->name); + Yii::debug('trigger event: ' . $event->name); }); ``` @@ -382,7 +382,7 @@ use Yii; Event::on('app\models\*', 'before*', function ($event) { // triggered for any class in namespace 'app\models' for any event, which name starts on 'before' - Yii::trace('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender)); + Yii::debug('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender)); }); ``` @@ -394,7 +394,7 @@ use Yii; Event::on('*', '*', function ($event) { // triggered for any event at any class - Yii::trace('trigger event: ' . $event->name); + Yii::debug('trigger event: ' . $event->name); }); ``` diff --git a/docs/guide/db-active-record.md b/docs/guide/db-active-record.md index 8bb765f..a7938fc 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -472,7 +472,7 @@ $customer->loadDefaultValues(); ### Attributes Typecasting -Being populated by query results [[yii\db\ActiveRecord]] performs automatic typecast for its attribute values, using +Being populated by query results, [[yii\db\ActiveRecord]] performs automatic typecast for its attribute values, using information from [database table schema](db-dao.md#database-schema). This allows data retrieved from table column declared as integer to be populated in ActiveRecord instance with PHP integer, boolean with boolean and so on. However, typecasting mechanism has several limitations: @@ -490,7 +490,33 @@ converted during saving process. > Tip: you may use [[yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute values typecasting on ActiveRecord validation or saving. + +Since 2.0.14, Yii ActiveRecord supports complex data types, such as JSON or multidimensional arrays. + +#### JSON in MySQL and PostgreSQL + +After data population, the value from JSON column will be automatically decoded from JSON according to standard JSON +decoding rules. + +To save attribute value to a JSON column, ActiveRecord will automatically create a [[yii\db\JsonExpression|JsonExpression]] object +that will be encoded to a JSON string on [QueryBuilder](db-query-builder.md) level. + +#### Arrays in PostgreSQL + +After data population, the value from Array column will be automatically decoded from PgSQL notation to an [[yii\db\ArrayExpression|ArrayExpression]] +object. It implements PHP `ArrayAccess` interface, so you can use it as an array, or call `->getValue()` to get the array itself. + +To save attribute value to an array column, ActiveRecord will automatically create an [[yii\db\ArrayExpression|ArrayExpression]] object +that will be encoded by [QueryBuilder](db-query-builder.md) to an PgSQL string representation of array. + +You can also use conditions for JSON columns: + +```php +$query->andWhere(['=', 'json', new ArrayExpression(['foo' => 'bar']) +``` +To learn more about expressions building system read the [Query Builder – Adding custom Conditions and Expressions](db-query-builder.md#adding-custom-conditions-and-expressions) +article. ### Updating Multiple Rows diff --git a/docs/guide/db-dao.md b/docs/guide/db-dao.md index c850717..e485c14 100644 --- a/docs/guide/db-dao.md +++ b/docs/guide/db-dao.md @@ -257,6 +257,21 @@ Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ ])->execute(); ``` +Another useful method is [[yii\db\Command::upsert()|upsert()]]. Upsert is an atomic operation that inserts rows into +a database table if they do not already exist (matching unique constraints), or update them if they do: + +```php +Yii::$app->db->createCommand()->upsert('pages', [ + 'name' => 'Front page', + 'url' => 'http://example.com/', // url is unique + 'visits' => 0, +], [ + 'visits' => new \yii\db\Expression('visits + 1'), +], $params)->execute(); +``` + +The code above will either insert a new page record or increment its visit counter atomically. + Note that the aforementioned methods only create the query and you always have to call [[yii\db\Command::execute()|execute()]] to actually run them. diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md index b23d1e7..1086f44 100644 --- a/docs/guide/db-query-builder.md +++ b/docs/guide/db-query-builder.md @@ -160,12 +160,12 @@ are in the ["Quoting Tables" section of the "Database Access Objects" guide](gui ### [[yii\db\Query::where()|where()]] The [[yii\db\Query::where()|where()]] method specifies the `WHERE` fragment of a SQL query. You can use one of -the three formats to specify a `WHERE` condition: +the four formats to specify a `WHERE` condition: - string format, e.g., `'status=1'` - hash format, e.g. `['status' => 1, 'type' => 2]` - operator format, e.g. `['like', 'name', 'test']` - +- object format, e.g. `new LikeCondition('name', 'LIKE', 'test')` #### String Format @@ -255,6 +255,9 @@ the operator can be one of the following: - `between`: operand 1 should be the column name, and operand 2 and 3 should be the starting and ending values of the range that the column is in. For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. + In case you need to build a condition where value is between two columns (like `11 BETWEEN min_id AND max_id`), + you should use [[yii\db\conditions\BetweenColumnsCondition|BetweenColumnsCondition]]. + See [Conditions – Object Format](#object-format) chapter to learn more about object definition of conditions. - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` in the generated condition. @@ -306,6 +309,41 @@ the operator can be one of the following: Using the Operator Format, Yii internally uses parameter binding so in contrast to the [string format](#string-format), here you do not have to add parameters manually. +#### Object Format + +Object Form is available since 2.0.14 and is both most powerful and most complex way to define conditions. +You need to follow it either if you want to build your own abstraction over query builder or if you want to implement +your own complex conditions. + +Instances of condition classes are immutable. Their only purpose is to store condition data and provide getters +for condition builders. Condition builder is a class that holds the logic that transforms data +stored in condition into the SQL expression. + +Internally the formats described above are implicitly converted to object format prior to building raw SQL, +so it is possible to combine formats in a single condition: + +```php +$query->andWhere(new OrCondition([ + new InCondition('type', 'in', $types), + ['like', 'name', '%good%'], + 'disabled=false' +])) +``` + +Conversion from operator format into object format is performed according to +[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]] property, that maps operators names +to representative class names: + +- `AND`, `OR` -> `yii\db\conditions\ConjunctionCondition` +- `NOT` -> `yii\db\conditions\NotCondition` +- `IN`, `NOT IN` -> `yii\db\conditions\InCondition` +- `BETWEEN`, `NOT BETWEEN` -> `yii\db\conditions\BetweenCondition` + +And so on. + +Using the object format makes it possible to create your own conditions or to change the way default ones are built. +See [Creating Custom Conditions and Expressions](#creating-custom-conditions-and-expressions) chapter to learn more. + #### Appending Conditions @@ -758,3 +796,170 @@ $unbufferedDb->close(); ``` > Note: unbuffered query uses less memory on the PHP-side, but can increase the load on the MySQL server. It is recommended to design your own code with your production practice for extra massive data, [for example, divide the range for integer keys, loop them with Unbuffered Queries](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257). + +### Adding custom Conditions and Expressions + +As it was mentioned in [Conditions – Object Format](#object-format) chapter, is is possible to create custom condition +classes. For example, let's create a condition that will check that specific columns are less than some value. +Using the operator format, it would look like the following: + +```php +[ + 'and', + '>', 'posts', $minLimit, + '>', 'comments', $minLimit, + '>', 'reactions', $minLimit, + '>', 'subscriptions', $minLimit +] +``` + +When such condition applied once, it is fine. In case it is used multiple times in a single query it can +be optimized a lot. Let's create a custom condition object to demonstrate it. + +Yii has a [[yii\db\conditions\ConditionInterface|ConditionInterface]], that must be used to mark classes, that represent +a condition. It requires `fromArrayDefinition()` method implementation, in order to make possible to create condition +from array format. In case you don't need it, you can implement this method with exception throwing. + +Since we create our custom condition class, we can build API that suits our task the most. + +```php +namespace app\db\conditions; + +class AllGreaterCondition implements \yii\db\conditions\ConditionInterface +{ + private $columns; + private $value; + + /** + * @param string[] $columns Array of columns that must be greater, than $value + * @param mixed $value the value to compare each $column against. + */ + public function __construct(array $columns, $value) + { + $this->columns = $columns; + $this->value = $value; + } + + public static function fromArrayDefinition($operator, $operands) + { + throw new InvalidArgumentException('Not implemented yet, but we will do it later'); + } + + public function getColumns() { return $this->columns; } + public function getValue() { return $this->vaule; } +} +``` + +So we can create a condition object: + +```php +$conditon = new AllGreaterCondition(['col1', 'col2'], 42); +``` + +But `QueryBuilder` still does not know, to to make an SQL condition out of this object. +Now we need to create a builder for this condition. It must implement [[yii\db\ExpressionBuilderInterface]] +that requires us to implement a `build()` method. + +```php +namespace app\db\conditions; + +class AllGreaterConditionBuilder implements \yii\db\ExpressionBuilderInterface +{ + use \yii\db\Condition\ExpressionBuilderTrait; // Contains constructor and `queryBuilder` property. + + /** + * @param AllGreaterCondition $condition the condition to be built + * @param array $params the binding parameters. + */ + public function build(ConditionInterface $condition, &$params) + { + $value = $condition->getValue(); + + $conditions = []; + foreach ($condition->getColumns() as $column) { + $conditions[] = new SimpleCondition($column, '>', $value); + } + + return $this->queryBuider->buildCondition(new AndCondition($conditions), $params); + } +} +``` + +Then simple let [[yii\db\QueryBuilder|QueryBuilder]] know about our new condition – add a mapping for it to +the `expressionBuilders` array. It could be done right from the application configuration: + +```php +'db' => [ + 'class' => 'yii\db\mysql\Connection', + // ... + 'queryBuilder' => [ + 'expressionBuilders' => [ + 'app\db\conditions\AllGreaterCondition' => 'app\db\conditions\AllGreaterConditionBuilder', + ], + ], +], +``` + +Now we can use our condition in `where()`: + +```php +$query->andWhere(new AllGreaterCondition(['posts', 'comments', 'reactions', 'subscriptions'], $minValue)); +``` + +If we want to make it possible to create our custom condition using operator format, we should declare it in +[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]]: + +```php +'db' => [ + 'class' => 'yii\db\mysql\Connection', + // ... + 'queryBuilder' => [ + 'expressionBuilders' => [ + 'app\db\conditions\AllGreaterCondition' => 'app\db\conditions\AllGreaterConditionBuilder', + ], + 'conditionClasses' => [ + 'ALL>' => 'app\db\conditions\AllGreaterCondition', + ], + ], +], +``` + +And create a real implementation of `AllGreaterCondition::fromArrayDefinition()` method +in `app\db\conditions\AllGreaterCondition`: + +```php +namespace app\db\conditions; + +class AllGreaterCondition implements \yii\db\conditions\ConditionInterface +{ + // ... see the implementation above + + public static function fromArrayDefinition($operator, $operands) + { + return new static($operands[0], $operands[1]); + } +} +``` + +After that, we can create our custom condition using shorter operator format: + +```php +$query->andWhere(['ALL>', ['posts', 'comments', 'reactions', 'subscriptions'], $minValue]); +``` + +You might notice, that there was two concepts used: Expressions and Conditions. There is a [[yii\db\ExpressionInterface]] +that should be used to mark objects, that require an Expression Builder class, that implements +[[yii\db\ExpressionBuilderInterface]] to be built. Also there is a [[yii\db\condition\ConditionInterface]], that extends +[[yii\db\ExpressionInterface|ExpressionInterface]] and should be used to objects, that can be created from array definition +as it was shown above, but require builder as well. + +To summarise: + +- Expression – is a Data Transfer Object (DTO) for a dataset, that can be somehow compiled to some SQL +statement (an operator, string, array, JSON, etc). +- Condition – is an Expression superset, that aggregates multiple Expressions (or scalar values) that can be compiled +to a single SQL condition. + +You can create your own classes that implement [[yii\db\ExpressionInterface|ExpressionInterface]] to hide the complexity +of transforming data to SQL statements. You will learn more about other examples of Expressions in the +[next article](db-active-record.md); diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index a82ab1b..a60b3f6 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -387,8 +387,8 @@ class MyForm extends Model public function validateCountry($attribute, $params, $validator) { - if (!in_array($this->$attribute, ['USA', 'Web'])) { - $this->addError($attribute, 'The country must be either "USA" or "Web".'); + if (!in_array($this->$attribute, ['USA', 'Indonesia'])) { + $this->addError($attribute, 'The country must be either "USA" or "Indonesia".'); } } } @@ -422,7 +422,8 @@ fails the validation, call [[yii\base\Model::addError()]] to save the error mess with [inline validators](#inline-validators). -For example the inline validator above could be moved into new [[components/validators/CountryValidator]] class. +For example, the inline validator above could be moved into new [[components/validators/CountryValidator]] class. +In this case we can use [[yii\validators\Validator::addError()]] to set customized message for the model. ```php namespace app\components; @@ -433,8 +434,8 @@ class CountryValidator extends Validator { public function validateAttribute($model, $attribute) { - if (!in_array($model->$attribute, ['USA', 'Web'])) { - $this->addError($model, $attribute, 'The country must be either "USA" or "Web".'); + if (!in_array($model->$attribute, ['USA', 'Indonesia'])) { + $this->addError($model, $attribute, 'The country must be either "{country1}" or "{country2}".', ['country1' => 'USA', 'country2' => 'Indonesia']); } } } diff --git a/docs/guide/rest-resources.md b/docs/guide/rest-resources.md index 3141244..32629a3 100644 --- a/docs/guide/rest-resources.md +++ b/docs/guide/rest-resources.md @@ -38,13 +38,17 @@ in the array if an end user requests for them via the `expand` query parameter. // returns all fields as declared in fields() http://localhost/users -// only returns field id and email, provided they are declared in fields() +// only returns "id" and "email" fields, provided they are declared in fields() http://localhost/users?fields=id,email -// returns all fields in fields() and field profile if it is in extraFields() +// returns all fields in fields() and field "profile" if it is in extraFields() http://localhost/users?expand=profile -// only returns field id, email and profile, provided they are in fields() and extraFields() +// returns all fields in fields(), "profile" if it is in extraFields() and "author" from profile if +// it is in extraFields() of profile model +http://localhost/comments?expand=post.author + +// only returns "id" and "email" provided they are in fields() and "profile" if it is in extraFields() http://localhost/users?fields=id,email&expand=profile ``` diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md index e046066..2c67ca2 100644 --- a/docs/guide/start-installation.md +++ b/docs/guide/start-installation.md @@ -111,6 +111,30 @@ But there are other installation options available: you may consider installing the [Advanced Project Template](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). +Installing Assets +----------------- + +Yii relies on [Bower](http://bower.io/) and/or [NPM](https://www.npmjs.org/) packages for the asset (CSS and JavaScript) libraries installation. +It uses Composer to obtain these libraries, allowing PHP and CSS/JavaScript package versions to resolve at the same time. +This can be achieved either by usage of [asset-packagist.org](https://asset-packagist.org) or [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/). +Please refer to [Assets documentation](structure-assets.md) for more details. + +You may want to either manage your assets via native Bower/NPM client, use CDN or avoid assets installation entirely. +In order to prevent assets installation via Composer, add the following lines to your '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" +}, +``` + +> Note: in case of bypassing asset installation via Composer, you are responsible for the assets installation and resolving +> version collisions. Be prepared for possible inconsistencies among asset files from different extensions. + + Verifying the Installation -------------------------- diff --git a/docs/internals/release.md b/docs/internals/release.md index 35d092d..c5c0eb7 100644 --- a/docs/internals/release.md +++ b/docs/internals/release.md @@ -53,13 +53,13 @@ You may run it with `--update` to fetch tags for all repos to get the newest inf Making a framework release includes the following commands (apps are always released together with the framework): - ./build release framework - ./build release app-basic - ./build release app-advanced + ./build/build release framework + ./build/build release app-basic + ./build/build release app-advanced Making an extension release includes only one command (e.g. for redis): - ./build release redis + ./build/build release redis The default release command will release a new minor version from the currently checked out branch. To release another version than the default, you have to specify it using the `--version` option, e.g. diff --git a/framework/BaseYii.php b/framework/BaseYii.php index 00c740a..6325a0e 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use yii\base\InvalidArgumentException; use yii\base\InvalidConfigException; +use yii\base\UnknownClassException; use yii\di\Container; use yii\di\Instance; use yii\helpers\VarDumper; @@ -90,7 +91,7 @@ class BaseYii */ public static function getVersion() { - return '2.0.14-dev'; + return '2.0.15-dev'; } /** @@ -425,6 +426,7 @@ class BaseYii * @param string|array $message the message to be logged. This can be a simple string or a more * complex data structure, such as array. * @param string $category the category of the message. + * @since 2.0.14 */ public static function debug($message, $category = 'application') { diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 18798d9..f1c08c7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -43,31 +43,34 @@ Yii Framework 2 Change Log - Chg #15481: Removed `yii\BaseYii::powered()` method (Kolyunya, samdark) 2.0.14 under development +2.0.15 under development +------------------------ + +- no changes in this release. + + +2.0.14 February 18, 2018 ------------------------ -- Enh #13996: Added `yii\web\View::registerJsVar()` method that allows registering JavaScript variables (Eseperio, samdark) -- Enh #9771: Assign hidden input with its own set of HTML options via `$hiddenOptions` in activeFileInput `$options` (HanafiAhmat) -- Bug #15536: Fixed `yii\widgets\ActiveForm::init()` for call `parent::init()` (panchenkodv) -- Enh #14806: Added $placeFooterAfterBody option for GridView (terehru) -- Bug #14711: Fixed `yii\web\ErrorHandler` displaying exception message in non-debug mode (samdark) -- Enh #13814: MySQL unique index names can now contain spaces (df2) -- Bug #15300: Fixed "Cannot read property 'style' of undefined" error at the error screen (vitorarantes) -- Bug #15540: Fixed `yii\db\ActiveRecord::with()` unable to use relation defined via attached behavior in case `asArray` is enabled (klimov-paul) -- Enh #15426: Added abilitiy to create and drop database views (igravity, vladis84) -- Enh #10186: Use native `hash_equals` in `yii\base\Security::compareString()` if available, throw exception if non-strings are compared (aotd1, samdark) -- Bug #15122: Fixed `yii\db\Command::getRawSql()` to properly replace expressions (hiscaler, samdark) -- Enh #15496: CSRF token is now regenerated on changing identity (samdark, rhertogh) -- Enh #15417: Added `yii\validators\FileValidator::$minFiles` (vladis84) - Bug #8983: Only truncate the original log file for rotation (matthewyang, developeruz) +- Bug #9342: Fixed `yii\db\ActiveQueryTrait` to apply `indexBy` after relations population in order to prevent excess queries (sammousa, silverfire) +- Bug #11401: Fixed `yii\web\DbSession` concurrency issues when writing and regenerating IDs (samdark, andreasanta, cebe) +- Bug #13034: Fixed `normalizePath` for windows network shares that start with two backslashes (developeruz) - Bug #14135: Fixed `yii\web\Request::getBodyParam()` crashes on object type body params (klimov-paul) - Bug #14157: Add support for loading default value `CURRENT_TIMESTAMP` of MySQL `datetime` field (rossoneri) - Bug #14276: Fixed I18N format with dotted parameters (developeruz) +- Bug #14296: Fixed log targets to throw exception in case log can not be properly exported (bizley) - Bug #14484: Fixed `yii\validators\UniqueValidator` for target classes with a default scope (laszlovl, developeruz) - Bug #14604: Fixed `yii\validators\CompareValidator` `compareAttribute` does not work if `compareAttribute` form ID has been changed (mikk150) +- Bug #14711 (CVE-2018-6010): Fixed `yii\web\ErrorHandler` displaying exception message in non-debug mode (samdark) +- Bug #14811: Fixed `yii\filters\HttpCache` to work with PHP 7.2 (samdark) +- Bug #14859: Fixed OCI DB `defaultSchema` failure when `masterConfig` is used (lovezhl456) - Bug #14903: Fixed route with extra dashes is executed controller while it should not (developeruz) - Bug #14916: Fixed `yii\db\Query::each()` iterator key starts from 1 instead of 0 (Vovan-VE) -- Bug #15046: Throw an `yii\web\HeadersAlreadySentException` if headers were sent before web response (dmirogin) - Bug #14980: Fix looping in `yii\i18n\MessageFormatter` tokenize pattern if pattern is invalid (uaoleg, developeruz) +- Bug #15031: Fixed incorrect string type length detection for OCI DB schema (Murolike) +- Bug #15046: Throw an `yii\web\HeadersAlreadySentException` if headers were sent before web response (dmirogin) +- Bug #15122: Fixed `yii\db\Command::getRawSql()` to properly replace expressions (hiscaler, samdark) - Bug #15142: Fixed array params replacing in `yii\helpers\BaseUrl::current()` (IceJOKER) - Bug #15169: Fixed translating a string when NULL parameter is passed (developeruz) - Bug #15194: Fixed `yii\db\QueryBuilder::insert()` to preserve passed params when building a `INSERT INTO ... SELECT` query for MSSQL, PostgreSQL and SQLite (sergeymakinen) @@ -75,6 +78,7 @@ Yii Framework 2 Change Log - Bug #15234: Fixed `\yii\widgets\LinkPager` removed `tag` from `disabledListItemSubTagOptions` (SDKiller) - Bug #15249: Controllers in subdirectories were not visible in commands list (IceJOKER) - Bug #15270: Resolved potential race conditions when writing generated php-files (kalessil) +- Bug #15300: Fixed "Cannot read property 'style' of undefined" error at the error screen (vitorarantes) - Bug #15301: Fixed `ArrayHelper::filter()` to work properly with `0` in values (hhniao) - Bug #15302: Fixed `yii\caching\DbCache` so that `getValues` now behaves the same as `getValue` with regards to streams (edwards-sj) - Bug #15317: Regenerate CSRF token if an empty value is given (sammousa) @@ -89,39 +93,89 @@ Yii Framework 2 Change Log - Bug #15440: Fixed `yii\behaviors\AttributeTypecastBehavior::$attributeTypes` auto-detection fails for rule, which specify attribute with '!' prefix (klimov-paul) - Bug #15462: Fixed `accessChecker` configuration error (developeruz) - Bug #15494: Fixed missing `WWW-Authenticate` header (developeruz) +- Bug #15522: Fixed `yii\db\ActiveRecord::refresh()` method does not use an alias in the condition (vladis84) +- Bug #15523: `yii\web\Session` settings could now be configured after session is started (StalkAlex, rob006, daniel1302, samdark) +- Bug #15536: Fixed `yii\widgets\ActiveForm::init()` for call `parent::init()` (panchenkodv) +- Bug #15540: Fixed `yii\db\ActiveRecord::with()` unable to use relation defined via attached behavior in case `asArray` is enabled (klimov-paul) - Bug #15553: Fixed `yii\validators\NumberValidator` incorrectly validate resource (developeruz) +- Bug #15621: Fixed `yii\web\User::getIdentity()` returning `null` if an exception had been thrown when it was called previously (brandonkelly) +- Bug #15628: Fixed `yii\validators\DateValidator` to respect time when the `format` property is set to UNIX Epoch format (silverfire, gayHacker) +- Bug #15644: Avoid wrong default selection on a dropdown, checkbox list, and radio list, when a option has a key equals to zero (berosoboy) +- Bug #15658: Fixed `yii\filters\auth\HttpBasicAuth` not to switch identity, when user is already authenticated and identity does not get changed (silverfire) +- Bug #15662: Fixed `yii\log\FileTarget` not to create log directory during init process (alexeevdv) - Enh #3087: Added `yii\helpers\BaseHtml::error()` "errorSource" option to be able to customize errors display (yanggs07, developeruz, silverfire) - Enh #3250: Added support for events partial wildcard matching (klimov-paul) - Enh #5515: Added default value for `yii\behaviors\BlameableBehavior` for cases when the user is guest (dmirogin) - Enh #6844: `yii\base\ArrayableTrait::toArray()` now allows recursive `$fields` and `$expand` (bboure) +- Enh #7640: Implemented custom data types support. Added JSON support for MySQL and PostgreSQL, array support for PostgreSQL (silverfire, cebe) - Enh #7988: Added `\yii\helpers\Console::errorSummary()` and `\yii\helpers\Json::errorSummary()` (developeruz) - Enh #7996: Short syntax for verb in GroupUrlRule (schojniak, developeruz) +- Enh #8092: ExistValidator for relations (developeruz) +- Enh #8527: Added `yii\i18n\Locale` component having `getCurrencySymbol()` method (amarox, samdark) - Enh #8752: Allow specify `$attributeNames` as a string for `yii\base\Model` `validate()` method (developeruz) - Enh #9137: Added `Access-Control-Allow-Method` header for the OPTIONS request (developeruz) - Enh #9253: Allow `variations` to be a string for `yii\filters\PageCache` and `yii\widgets\FragmentCache` (schojniak, developeruz) +- Enh #9771: Assign hidden input with its own set of HTML options via `$hiddenOptions` in activeFileInput `$options` (HanafiAhmat) +- Enh #10186: Use native `hash_equals` in `yii\base\Security::compareString()` if available, throw exception if non-strings are compared (aotd1, samdark) +- Enh #11611: Added `BetweenColumnsCondition` to build SQL condition like `value BETWEEN col1 and col2` (silverfire) - Enh #12623: Added `yii\helpers\StringHelper::matchWildcard()` replacing usage of `fnmatch()`, which may be unreliable (klimov-paul) +- Enh #13019: Support JSON in SchemaBuilderTrait (zhukovra, undefinedor) +- Enh #13425: Added caching of dynamically added URL rules with `yii\web\UrlManager::addRules()` (scriptcube, silverfire) +- Enh #13465: Added `yii\helpers\FileHelper::findDirectories()` method (ArsSirek, developeruz) +- Enh #13618: Active Record now resets related models after corresponding attributes updates (Kolyunya, rob006) +- Enh #13679: Added `yii\behaviors\CacheableWidgetBehavior` (Kolyunya) +- Enh #13814: MySQL unique index names can now contain spaces (df2) +- Enh #13879: Added upsert support for `yii\db\QueryBuilder`, `yii\db\Command`, and `yii\db\Migration` (sergeymakinen) - Enh #13919: Added option to add comment for created table to migration console command (mixartemev, developeruz) +- Enh #13996: Added `yii\web\View::registerJsVar()` method that allows registering JavaScript variables (Eseperio, samdark) - Enh #14043: Added `yii\helpers\IpHelper` (silverfire, cebe) +- Enh #14254: add an option to specify whether validator is forced to always use master DB for `yii\validators\UniqueValidator` and `yii\validators\ExistValidator` (rossoneri, samdark) - Enh #14355: Added ability to pass an empty array as a parameter in console command (developeruz) +- Enh #14488: Added support for X-Forwarded-Host to `yii\web\Request`, fixed `getServerPort()` usage (si294r, samdark) +- Enh #14538: Added `yii\behaviors\AttributeTypecastBehavior::typecastAfterSave` property (littlefuntik, silverfire) +- Enh #14546: Added `dataDirectory` property into `BaseActiveFixture` (leandrogehlen) - Enh #14568: Refactored migration templates to use `safeUp()` and `safeDown()` methods (Kolyunya) +- Enh #14638: Added `yii\db\SchemaBuilderTrait::tinyInteger()` (rob006) +- Enh #14643: Added `yii\web\ErrorAction::$layout` property to conveniently set layout from error action config (swods, cebe, samdark) - Enh #14662: Added support for custom `Content-Type` specification to `yii\web\JsonResponseFormatter` (Kolyunya) +- Enh #14732, #11218, #14810, #10855: It is now possible to pass `yii\db\Query` anywhere, where `yii\db\Expression` was supported (silverfire) +- Enh #14806: Added $placeFooterAfterBody option for GridView (terehru) - Enh #15024: `yii\web\Pjax` widget does not prevent CSS files from sending anymore because they are handled by client-side plugin correctly (onmotion) +- Enh #15047: `yii\db\Query::select()` and `yii\db\Query::addSelect()` now check for duplicate column names (wapmorgan) +- Enh #15076: Improve `yii\db\QueryBuilder::buildColumns()` to throw exception on invalid input (hiscaler) +- Enh #15120: Refactored dynamic caching introducing `DynamicContentAwareInterface` and `DynamicContentAwareTrait` (sergeymakinen) - Enh #15135: Automatic completion for help in bash and zsh (Valkeru) +- Enh #15216: Added `yii\web\ErrorHandler::$traceLine` to allow opening file at line clicked in IDE (vladis84) - Enh #15219: Added `yii\filters\auth\HttpHeaderAuth` (bboure) - Enh #15221: Added support for specifying `--camelCase` console options in `--kebab-case` (brandonkelly) - Enh #15221: Added support for the `--