diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 92b768d..bd7f6ab 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -27,6 +27,10 @@ class PhpDocController extends Controller * for copy and paste. */ public $updateFiles = true; + /** + * @var bool whether to add copyright header to php files. This should be skipped in application code. + */ + public $skipFrameworkRequirements = false; /** @@ -83,7 +87,9 @@ class PhpDocController extends Controller // fix line endings $lines = preg_split('/(\r\n|\n|\r)/', $contents); - $this->fixFileDoc($lines); + if (!$this->skipFrameworkRequirements) { + $this->fixFileDoc($lines); + } $this->fixDocBlockIndentation($lines); $lines = array_values($this->fixLineSpacing($lines)); @@ -105,7 +111,7 @@ class PhpDocController extends Controller */ public function options($actionID) { - return array_merge(parent::options($actionID), ['updateFiles']); + return array_merge(parent::options($actionID), ['updateFiles', 'skipFrameworkRequirements']); } protected function findFiles($root, $needsInclude = true) @@ -169,11 +175,7 @@ class PhpDocController extends Controller } elseif (preg_match('~extensions/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { $extensionPath = dirname(rtrim($root, '\\/')); - foreach (scandir($extensionPath) as $extension) { - if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { - Yii::setAlias("@yii/$extension", "$extensionPath/$extension"); - } - } + $this->setUpExtensionAliases($extensionPath); list(, $extension) = $matches; Yii::setAlias("@yii/$extension", "$root"); @@ -194,6 +196,22 @@ class PhpDocController extends Controller // if ($extension === 'composer') { // return []; // } + } elseif (preg_match('~apps/([\w\d-]+)[\\\\/]?$~', $root, $matches)) { + + $extensionPath = dirname(dirname(rtrim($root, '\\/'))) . '/extensions'; + $this->setUpExtensionAliases($extensionPath); + + list(, $appName) = $matches; + Yii::setAlias("@app-$appName", "$root"); + if (is_file($autoloadFile = Yii::getAlias("@app-$appName/vendor/autoload.php"))) { + include($autoloadFile); + } + + $except[] = '/runtime/'; + $except[] = '/vendor/'; + $except[] = '/tests/'; + $except[] = '/docs/'; + } $root = FileHelper::normalizePath($root); $options = [ @@ -219,6 +237,15 @@ class PhpDocController extends Controller return FileHelper::findFiles($root, $options); } + private function setUpExtensionAliases($extensionPath) + { + foreach (scandir($extensionPath) as $extension) { + if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { + Yii::setAlias("@yii/$extension", "$extensionPath/$extension"); + } + } + } + /** * Fix file PHPdoc */ @@ -459,7 +486,11 @@ class PhpDocController extends Controller if (!$ref->isSubclassOf('yii\base\Object') && $className != 'yii\base\Object') { $this->stderr("[INFO] Skipping class $className as it is not a subclass of yii\\base\\Object.\n", Console::FG_BLUE, Console::BOLD); + return false; + } + if ($ref->isSubclassOf('yii\db\BaseActiveRecord')) { + $this->stderr("[INFO] Skipping class $className as it is an ActiveRecord class, property handling is not supported yet.\n", Console::FG_BLUE, Console::BOLD); return false; } @@ -484,11 +515,13 @@ class PhpDocController extends Controller } } - if (!$seenSince) { - $this->stderr("[ERR] No @since found in class doc in file: $file\n", Console::FG_RED); - } - if (!$seenAuthor) { - $this->stderr("[ERR] No @author found in class doc in file: $file\n", Console::FG_RED); + if (!$this->skipFrameworkRequirements) { + if (!$seenSince) { + $this->stderr("[ERR] No @since found in class doc in file: $file\n", Console::FG_RED); + } + if (!$seenAuthor) { + $this->stderr("[ERR] No @author found in class doc in file: $file\n", Console::FG_RED); + } } if (trim($oldDoc) != trim($newDoc)) { @@ -563,6 +596,12 @@ class PhpDocController extends Controller unset($lines[$i]); } } + + // if no properties or other tags where present add properties at the end + if ($propertyPosition === false) { + $propertyPosition = count($lines) - 2; + } + $finalDoc = ''; foreach ($lines as $i => $line) { $finalDoc .= $line . "\n"; @@ -589,13 +628,13 @@ class PhpDocController extends Controller return false; } if (count($classes) < 1) { - $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.+)\n\}(\n|$)#', $file); + $interfaces = $this->match('#\ninterface (?\w+)( extends .+)?\n\{(?.*)\n\}(\n|$)#', $file); if (count($interfaces) == 1) { return false; } 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); + $traits = $this->match('#\ntrait (?\w+)\n\{(?.*)\n\}(\n|$)#', $file); if (count($traits) == 1) { return false; } elseif (count($traits) > 1) { diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index 65b8f49..8560b86 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -12,9 +12,30 @@ use yii\base\Exception; use yii\console\Controller; use yii\helpers\ArrayHelper; use yii\helpers\Console; +use yii\helpers\FileHelper; /** - * ReleaseController is there to help preparing releases + * ReleaseController is there to help preparing releases. + * + * Get a version overview: + * + * ./build release/info + * + * run it with `--update` to fetch tags for all repos: + * + * ./build release/info --update + * + * Make a framework release (apps are always in line with framework): + * + * ./build release framework + * ./build release app-basic + * ./build release app-advanced + * + * Make an extension release (e.g. for redis): + * + * ./build release redis + * + * Be sure to check the help info for individual sub-commands: * * @author Carsten Brandt * @since 2.0 @@ -66,30 +87,35 @@ class ReleaseController extends Controller */ public function actionInfo() { - $extensions = [ + $items = [ 'framework', + 'app-basic', + 'app-advanced', ]; $extensionPath = "{$this->basePath}/extensions"; foreach (scandir($extensionPath) as $extension) { if (ctype_alpha($extension) && is_dir($extensionPath . '/' . $extension)) { - $extensions[] = $extension; + $items[] = $extension; } } if ($this->update) { - foreach($extensions as $extension) { - if ($extension === 'framework') { - continue; + foreach($items as $item) { + $this->stdout("fetching tags for $item..."); + if ($item === 'framework') { + $this->gitFetchTags("{$this->basePath}"); + } elseif (strncmp('app-', $item, 4) === 0) { + $this->gitFetchTags("{$this->basePath}/apps/" . substr($item, 4)); + } else { + $this->gitFetchTags("{$this->basePath}/extensions/$item"); } - $this->stdout("fetching tags for $extension..."); - $this->gitFetchTags("{$this->basePath}/extensions/$extension"); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); } } else { $this->stdout("\nInformation may be outdated, re-run with `--update` to fetch latest tags.\n\n"); } - $versions = $this->getCurrentVersions($extensions); + $versions = $this->getCurrentVersions($items); $nextVersions = $this->getNextVersions($versions, self::PATCH); // print version table @@ -141,8 +167,12 @@ class ReleaseController extends Controller * committing and pushing them. Each git command must be confirmed and can be skipped individually. * You may adjust changes in a separate shell or your IDE while the command is waiting for confirmation. * - * @param array $what what do you want to release? this can either be an extension name such as `redis` or `bootstrap`, - * or `framework` if you want to release a new version of the framework itself. + * @param array $what what do you want to release? this can either be: + * + * - an extension name such as `redis` or `bootstrap`, + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * - or `framework` if you want to release a new version of the framework itself. + * * @return int */ public function actionRelease(array $what) @@ -167,8 +197,10 @@ class ReleaseController extends Controller $this->stdout("\n"); $this->stdout("Before you make a release briefly go over the changes and check if you spot obvious mistakes:\n\n", Console::BOLD); - $this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n"); - $this->stdout("- are all new `@since` tags for this relase version?\n"); + if (strncmp('app-', reset($what), 4) !== 0) { + $this->stdout("- no accidentally added CHANGELOG lines for other versions than this one?\n"); + $this->stdout("- are all new `@since` tags for this relase version?\n"); + } $travisUrl = reset($what) === 'framework' ? '' : '-'.reset($what); $this->stdout("- are unit tests passing on travis? https://travis-ci.org/yiisoft/yii2$travisUrl/builds\n"); $this->stdout("- other issues with code changes?\n"); @@ -184,6 +216,8 @@ class ReleaseController extends Controller foreach($what as $ext) { if ($ext === 'framework') { $this->releaseFramework("{$this->basePath}/framework", $newVersions['framework']); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->releaseApplication(substr($ext, 4), "{$this->basePath}/apps/" . substr($ext, 4), $newVersions[$ext]); } else { $this->releaseExtension($ext, "{$this->basePath}/extensions/$ext", $newVersions[$ext]); } @@ -192,10 +226,75 @@ class ReleaseController extends Controller return 0; } - protected function printWhat(array $what, $newVersions, $versions) + /** + * This will generate application packages for download page. + * + * Usage: + * + * ``` + * ./build/build release/package app-basic + * ``` + * + * @param array $what what do you want to package? this can either be: + * + * - an application indicated by prefix `app-`, e.g. `app-basic`, + * + * @return int + */ + public function actionPackage(array $what) { + $this->validateWhat($what, ['app']); + $versions = $this->getCurrentVersions($what); + + $this->stdout("You are about to generate packages for the following things:\n\n"); + foreach($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(" - "); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(" application version "); + } elseif ($ext === 'framework') { + $this->stdout(" - Yii Framework version "); + } else { + $this->stdout(" - "); + $this->stdout($ext, Console::FG_RED); + $this->stdout(" extension version "); + } + $this->stdout($versions[$ext], Console::BOLD); + $this->stdout("\n"); + } + $this->stdout("\n"); + + $packagePath = "{$this->basePath}/packages"; + $this->stdout("Packages will be stored in $packagePath\n\n"); + + if (!$this->confirm('Continue?', false)) { + $this->stdout("Canceled.\n"); + return 1; + } + foreach($what as $ext) { if ($ext === 'framework') { + throw new Exception('Can not package framework.'); + } elseif (strncmp('app-', $ext, 4) === 0) { + $this->packageApplication(substr($ext, 4), $versions[$ext], $packagePath); + } else { + throw new Exception('Can not package extension.'); + } + } + + $this->stdout("\ndone. verify the versions composer installed above and push it to github!\n\n"); + + return 0; + } + + protected function printWhat(array $what, $newVersions, $versions) + { + foreach($what as $ext) { + if (strncmp('app-', $ext, 4) === 0) { + $this->stdout(" - "); + $this->stdout(substr($ext, 4), Console::FG_RED); + $this->stdout(" application version "); + } elseif ($ext === 'framework') { $this->stdout(" - Yii Framework version "); } else { $this->stdout(" - "); @@ -221,15 +320,34 @@ class ReleaseController extends Controller } } - protected function validateWhat(array $what) + /** + * @param array $what list of items + * @param array $limit list of things to allow, or empty to allow any, can be `app`, `framework`, `extension` + * @throws \yii\base\Exception + */ + protected function validateWhat(array $what, $limit = []) { foreach($what as $w) { - if ($w === 'framework') { + if (strncmp('app-', $w, 4) === 0) { + if (!empty($limit) && !in_array('app', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } + if (!is_dir($appPath = "{$this->basePath}/apps/" . substr($w, 4))) { + throw new Exception("Application path does not exist: \"{$appPath}\"\n"); + } + $this->ensureGitClean($appPath); + } elseif ($w === 'framework') { + if (!empty($limit) && !in_array('framework', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } if (!is_dir($fwPath = "{$this->basePath}/framework")) { throw new Exception("Framework path does not exist: \"{$this->basePath}/framework\"\n"); } $this->ensureGitClean($fwPath); } else { + if (!empty($limit) && !in_array('ext', $limit)) { + throw new Exception("Only the following types are allowed: ".implode(', ', $limit)."\n"); + } if (!is_dir($extPath = "{$this->basePath}/extensions/$w")) { throw new Exception("Extension path for \"$w\" does not exist: \"{$this->basePath}/extensions/$w\"\n"); } @@ -241,27 +359,12 @@ class ReleaseController extends Controller protected function releaseFramework($frameworkPath, $version) { - throw new Exception('NOT IMPLEMENTED COMPLETELY YET'); - $this->stdout("\n"); $this->stdout($h = "Preparing framework release version $version", Console::BOLD); $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); - if ($this->confirm('Run `git checkout master`?', true)) { - chdir($frameworkPath); - exec('git checkout master', $output, $ret); // TODO add compatibility for other release branches - if ($ret != 0) { - throw new Exception('Command "git checkout master" failed with code ' . $ret); - } - } - - if ($this->confirm('Run `git pull`?', true)) { - chdir($frameworkPath); - exec('git pull', $output, $ret); - if ($ret != 0) { - throw new Exception('Command "git pull" failed with code ' . $ret); - } - } + $this->runGit('git checkout master', $frameworkPath); // TODO add compatibility for other release branches + $this->runGit('git pull', $frameworkPath); // TODO add compatibility for other release branches // checks @@ -271,38 +374,57 @@ class ReleaseController extends Controller // adjustments - - $this->stdout('prepare classmap...'); + $this->stdout('prepare classmap...', Console::BOLD); $this->dryRun || Yii::$app->runAction('classmap', [$frameworkPath]); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - $this->stdout('fixing various PHPdoc style issues...'); - $this->dryRun || Yii::$app->runAction('php-doc/fix', [$frameworkPath]); + $this->stdout('updating mimetype magic file...', Console::BOLD); + $this->dryRun || Yii::$app->runAction('mime-type', ["$frameworkPath/helpers/mimeTypes.php"]); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - $this->stdout('updating PHPdoc @property annotations...'); - $this->dryRun || Yii::$app->runAction('php-doc/property', [$frameworkPath]); + $this->stdout("fixing various PHPdoc style issues...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$frameworkPath]); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - $this->stdout('updating mimetype magic file...'); - $this->dryRun || Yii::$app->runAction('mime-type', ["$frameworkPath/helpers/mimeTypes.php"]); + $this->stdout("updating PHPdoc @property annotations...\n", Console::BOLD); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$frameworkPath]); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - $this->stdout('sorting changelogs...'); + $this->stdout('sorting changelogs...', Console::BOLD); $this->dryRun || $this->resortChangelogs(['framework'], $version); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - $this->stdout('closing changelogs...'); + $this->stdout('closing changelogs...', Console::BOLD); $this->dryRun || $this->closeChangelogs(['framework'], $version); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); $this->stdout('updating Yii version...'); - $this->dryRun || $this->updateYiiVersion($frameworkPath, $version);; + $this->dryRun || $this->updateYiiVersion($frameworkPath, $version); $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); - // TODO Commit and push + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit("git diff --color", $frameworkPath); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while(!$this->confirm("Type `yes` to continue, `no` to view git diff again. Continue?")); - // TODO tag and push + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->runGit("git commit -a -m \"release version $version\"", $frameworkPath); + $this->runGit("git tag -a $version -m\"version $version\"", $frameworkPath); + $this->runGit("git push origin master", $frameworkPath); + $this->runGit("git push --tags", $frameworkPath); + + $this->stdout("\n\n"); + $this->stdout("CONGRATULATIONS! You have just released extension ", Console::FG_YELLOW, Console::BOLD); + $this->stdout('framework', Console::FG_RED, Console::BOLD); + $this->stdout(" version ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); // TODO release applications // $this->composerSetStability($what, $version); @@ -325,6 +447,157 @@ class ReleaseController extends Controller // $this->updateYiiVersion($devVersion); // } + + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout('opening changelogs...', Console::BOLD); + $nextVersion = $this->getNextVersions(['framework' => $version], self::PATCH); // TODO support other versions + $this->dryRun || $this->openChangelogs(['framework'], $nextVersion['framework']); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout('updating Yii version...'); + $this->dryRun || $this->updateYiiVersion($frameworkPath, $nextVersion['framework'] . '-dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + + $this->stdout("\n"); + $this->runGit("git diff --color", $frameworkPath); + $this->stdout("\n\n"); + $this->runGit("git commit -a -m \"prepare for next release\"", $frameworkPath); + $this->runGit("git push origin master", $frameworkPath); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- wait for your changes to be propagated to the repo and create a tag $version on https://github.com/yiisoft/yii2-framework\n\n"); + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion['framework']} and {$nextVersion2['framework']}: https://github.com/yiisoft/yii2/milestones\n"); + $this->stdout("- create a release on github.\n"); + $this->stdout("- release news and announcement.\n"); + $this->stdout("- update the website (will be automated soon and is only relevant for the new website).\n"); + $this->stdout("\n"); + $this->stdout("- release applications: ./build/build release app-basic\n"); + $this->stdout("- release applications: ./build/build release app-advanced\n"); + + $this->stdout("\n"); + + } + + protected function releaseApplication($name, $path, $version) + { + $this->stdout("\n"); + $this->stdout($h = "Preparing release for application $name version $version", Console::BOLD); + $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); + + $this->runGit('git checkout master', $path); // TODO add compatibility for other release branches + $this->runGit('git pull', $path); // TODO add compatibility for other release branches + + // adjustments + + $this->stdout("fixing various PHPdoc style issues...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/fix', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating PHPdoc @property annotations...\n", Console::BOLD); + $this->setAppAliases($name, $path); + $this->dryRun || Yii::$app->runAction('php-doc/property', [$path, 'skipFrameworkRequirements' => true]); + $this->resetAppAliases(); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], $version); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $this->stdout("\nIn the following you can check the above changes using git diff.\n\n"); + do { + $this->runGit("git diff --color", $path); + $this->stdout("\n\n\nCheck whether the above diff is okay, if not you may change things as needed before continuing.\n"); + $this->stdout("You may abort the program with Ctrl + C and reset the changes by running `git checkout -- .` in the repo.\n\n"); + } while(!$this->confirm("Type `yes` to continue, `no` to view git diff again. Continue?")); + + $this->stdout("\n\n"); + $this->stdout(" **** RELEASE TIME! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout(" **** Commit, Tag and Push it! ****\n", Console::FG_YELLOW, Console::BOLD); + $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); + + $this->runGit("git commit -a -m \"release version $version\"", $path); + $this->runGit("git tag -a $version -m\"version $version\"", $path); + $this->runGit("git push origin master", $path); + $this->runGit("git push --tags", $path); + + $this->stdout("\n\n"); + $this->stdout("CONGRATULATIONS! You have just released application ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($name, Console::FG_RED, Console::BOLD); + $this->stdout(" version ", Console::FG_YELLOW, Console::BOLD); + $this->stdout($version, Console::BOLD); + $this->stdout("!\n\n", Console::FG_YELLOW, Console::BOLD); + + // prepare next release + + $this->stdout("Time to prepare the next release...\n\n", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("updating composer stability...\n", Console::BOLD); + $this->dryRun || $this->composerSetStability(["app-$name"], 'dev'); + $this->stdout("done.\n", Console::FG_GREEN, Console::BOLD); + + $nextVersion = $this->getNextVersions(["app-$name" => $version], self::PATCH); // TODO support other versions + + $this->stdout("\n"); + $this->runGit("git diff --color", $path); + $this->stdout("\n\n"); + $this->runGit("git commit -a -m \"prepare for next release\"", $path); + $this->runGit("git push origin master", $path); + + $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); + + $this->stdout("\n\nThe following steps are left for you to do manually:\n\n"); + $nextVersion2 = $this->getNextVersions($nextVersion, self::PATCH); // TODO support other versions + $this->stdout("- close the $version milestone on github and open new ones for {$nextVersion["app-$name"]} and {$nextVersion2["app-$name"]}: https://github.com/yiisoft/yii2-app-$name/milestones\n"); + $this->stdout("- Create Application packages and upload them to github: ./build release/package app-$name\n"); + + $this->stdout("\n"); + } + + private $_oldAlias; + + protected function setAppAliases($app, $path) + { + $this->_oldAlias = Yii::getAlias('@app'); + switch($app) { + case 'basic': + Yii::setAlias('@app', $path); + break; + case 'advanced': + // setup @frontend, @backend etc... + require("$path/common/config/bootstrap.php"); + break; + } + } + + protected function resetAppAliases() + { + Yii::setAlias('@app', $this->_oldAlias); + } + + protected function packageApplication($name, $version, $packagePath) + { + FileHelper::createDirectory($packagePath); + + $this->runCommand("composer create-project yiisoft/yii2-app-$name $name $version", $packagePath); + // clear cookie validation key in basic app + if (is_file($configFile = "$packagePath/$name/config/web.php")) { + $this->sed( + "/'cookieValidationKey' => '.*?',/", + "'cookieValidationKey' => '',", + $configFile + ); + } + $this->runCommand("tar zcf yii-$name-app-$version.tgz $name", $packagePath); } protected function releaseExtension($name, $path, $version) @@ -405,6 +678,22 @@ class ReleaseController extends Controller } + protected function runCommand($cmd, $path) + { + $this->stdout("running $cmd ...", Console::BOLD); + if ($this->dryRun) { + $this->stdout("dry run, command `$cmd` not executed.\n"); + return; + } + chdir($path); + exec($cmd, $output, $ret); + if ($ret != 0) { + echo implode("\n", $output); + throw new Exception("Command \"$cmd\" failed with code " . $ret); + } + $this->stdout("\ndone.\n", Console::BOLD, Console::FG_GREEN); + } + protected function runGit($cmd, $path) { if ($this->confirm("Run `$cmd`?", true)) { @@ -446,7 +735,7 @@ class ReleaseController extends Controller protected function checkComposer($fwPath) { - if (!$this->confirm("\nNot yet automated: Please check if composer.json dependencies in framework dir match the on in repo root. Continue?", false)) { + if (!$this->confirm("\nNot yet automated: Please check if composer.json dependencies in framework dir match the one in repo root. Continue?", false)) { exit; } } @@ -626,6 +915,8 @@ class ReleaseController extends Controller foreach($what as $ext) { if ($ext === 'framework') { chdir("{$this->basePath}/framework"); + } elseif (strncmp('app-', $ext, 4) === 0) { + chdir("{$this->basePath}/apps/" . substr($ext, 4)); } else { chdir("{$this->basePath}/extensions/$ext"); } diff --git a/composer.lock b/composer.lock index 465110c..bf95a51 100644 --- a/composer.lock +++ b/composer.lock @@ -4,40 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "a1ba02f94426ee64a99dfec5c5c45114", + "hash": "d2005b487c5ff761d806b9a94bfe4cac", + "content-hash": "de99885237d7d9364d74fb5f93389801", "packages": [ { "name": "bower-asset/jquery", - "version": "2.1.4", + "version": "2.2.3", "source": { "type": "git", - "url": "https://github.com/jquery/jquery.git", - "reference": "7751e69b615c6eca6f783a81e292a55725af6b85" + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery/zipball/7751e69b615c6eca6f783a81e292a55725af6b85", - "reference": "7751e69b615c6eca6f783a81e292a55725af6b85", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198", + "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198", "shasum": "" }, - "require-dev": { - "bower-asset/qunit": "1.14.0", - "bower-asset/requirejs": "2.1.10", - "bower-asset/sinon": "1.8.1", - "bower-asset/sizzle": "2.1.1-patch2" - }, "type": "bower-asset-library", "extra": { "bower-asset-main": "dist/jquery.js", "bower-asset-ignore": [ - "**/.*", - "build", - "dist/cdn", - "speed", - "test", - "*.md", - "AUTHORS.txt", - "Gruntfile.js", "package.json" ] }, @@ -45,6 +32,7 @@ "MIT" ], "keywords": [ + "browser", "javascript", "jquery", "library" @@ -52,16 +40,16 @@ }, { "name": "bower-asset/jquery.inputmask", - "version": "3.1.63", + "version": "3.2.7", "source": { "type": "git", "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "c40c7287eadc31e341ebbf0c02352eb55b9cbc48" + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/c40c7287eadc31e341ebbf0c02352eb55b9cbc48", - "reference": "c40c7287eadc31e341ebbf0c02352eb55b9cbc48", + "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", "shasum": "" }, "require": { @@ -70,23 +58,17 @@ "type": "bower-asset-library", "extra": { "bower-asset-main": [ - "./dist/inputmask/jquery.inputmask.js", - "./dist/inputmask/jquery.inputmask.extensions.js", - "./dist/inputmask/jquery.inputmask.date.extensions.js", - "./dist/inputmask/jquery.inputmask.numeric.extensions.js", - "./dist/inputmask/jquery.inputmask.phone.extensions.js", - "./dist/inputmask/jquery.inputmask.regex.extensions.js" + "./dist/inputmask/inputmask.js" ], "bower-asset-ignore": [ - "**/.*", - "qunit/", - "nuget/", - "tools/", - "js/", - "*.md", - "build.properties", - "build.xml", - "jquery.inputmask.jquery.json" + "**/*", + "!dist/*", + "!dist/inputmask/*", + "!dist/min/*", + "!dist/min/inputmask/*", + "!extra/bindings/*", + "!extra/dependencyLibs/*", + "!extra/phone-codes/*" ] }, "license": [ @@ -132,16 +114,16 @@ }, { "name": "bower-asset/yii2-pjax", - "version": "dev-master", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0" + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/3f20897307cca046fca5323b318475ae9dac0ca0", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", "shasum": "" }, "require": { @@ -154,18 +136,15 @@ ".travis.yml", "Gemfile", "Gemfile.lock", + "CONTRIBUTING.md", "vendor/", "script/", "test/" - ], - "branch-alias": { - "dev-master": "2.0.3-dev" - } + ] }, "license": [ "MIT" - ], - "time": "2015-03-08 21:03:11" + ] }, { "name": "cebe/markdown", @@ -173,12 +152,12 @@ "source": { "type": "git", "url": "https://github.com/cebe/markdown.git", - "reference": "e14d3da8f84eefa3792fd22b5b5ecba9c98d2e18" + "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/e14d3da8f84eefa3792fd22b5b5ecba9c98d2e18", - "reference": "e14d3da8f84eefa3792fd22b5b5ecba9c98d2e18", + "url": "https://api.github.com/repos/cebe/markdown/zipball/e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", + "reference": "e2a490ceec590bf5bfd1b43bd424fb9dceceb7c5", "shasum": "" }, "require": { @@ -225,20 +204,20 @@ "markdown", "markdown-extra" ], - "time": "2015-03-20 11:07:08" + "time": "2016-03-18 13:28:11" }, { "name": "ezyang/htmlpurifier", - "version": "v4.6.0", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" + "reference": "ae1828d955112356f7677c465f94f7deb7d27a40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40", + "reference": "ae1828d955112356f7677c465f94f7deb7d27a40", "shasum": "" }, "require": { @@ -269,7 +248,7 @@ "keywords": [ "html" ], - "time": "2013-11-30 08:25:19" + "time": "2015-08-05 01:03:42" }, { "name": "yiisoft/yii2-composer", @@ -277,12 +256,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "6b2a2b3bb83d4b3dd791f76e64c48973ce01bac6" + "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/6b2a2b3bb83d4b3dd791f76e64c48973ce01bac6", - "reference": "6b2a2b3bb83d4b3dd791f76e64c48973ce01bac6", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", + "reference": "f5fe6ba58dbc92b37daed5d9bd94cda777852ee4", "shasum": "" }, "require": { @@ -316,7 +295,7 @@ "extension installer", "yii2" ], - "time": "2015-06-11 19:55:58" + "time": "2016-04-14 08:46:37" } ], "packages-dev": [ @@ -326,12 +305,12 @@ "source": { "type": "git", "url": "https://github.com/cebe/indent.git", - "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776" + "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/indent/zipball/c500ed74d30ed2d7e085f9cf07f8092d32d70776", - "reference": "c500ed74d30ed2d7e085f9cf07f8092d32d70776", + "url": "https://api.github.com/repos/cebe/indent/zipball/0f33ba3cb567726a726e7024072232839a0d7cd0", + "reference": "0f33ba3cb567726a726e7024072232839a0d7cd0", "shasum": "" }, "bin": [ @@ -345,13 +324,11 @@ "authors": [ { "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" + "email": "mail@cebe.cc" } ], "description": "a small tool to convert text file indentation", - "time": "2014-05-23 14:40:08" + "time": "2015-11-22 14:46:59" }, { "name": "doctrine/instantiator", @@ -359,12 +336,12 @@ "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/416fb8ad1d095a87f1d21bc40711843cd122fd4a", + "reference": "416fb8ad1d095a87f1d21bc40711843cd122fd4a", "shasum": "" }, "require": { @@ -405,7 +382,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2016-03-31 10:24:22" }, { "name": "phpdocumentor/reflection-docblock", @@ -462,18 +439,20 @@ "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "5700f75b23b0dd3495c0f495fe33a5e6717ee160" + "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/5700f75b23b0dd3495c0f495fe33a5e6717ee160", - "reference": "5700f75b23b0dd3495c0f495fe33a5e6717ee160", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b02221e42163be673f9b44a0bc92a8b4907a7c6d", + "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1" + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -481,7 +460,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -514,20 +493,20 @@ "spy", "stub" ], - "time": "2015-06-30 10:26:46" + "time": "2016-02-21 17:41:21" }, { "name": "phpunit/php-code-coverage", - "version": "dev-master", + "version": "2.2.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "40103f9b335f1d84daed099f180b9de12ba080e7" + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40103f9b335f1d84daed099f180b9de12ba080e7", - "reference": "40103f9b335f1d84daed099f180b9de12ba080e7", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", "shasum": "" }, "require": { @@ -576,7 +555,7 @@ "testing", "xunit" ], - "time": "2015-08-04 03:45:55" + "time": "2015-10-06 15:47:00" }, { "name": "phpunit/php-file-iterator", @@ -668,7 +647,7 @@ }, { "name": "phpunit/php-timer", - "version": "dev-master", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", @@ -713,12 +692,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9" + "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9", - "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cab6c6fefee93d7b7c3a01292a0fe0884ea66644", + "reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644", "shasum": "" }, "require": { @@ -754,7 +733,7 @@ "keywords": [ "tokenizer" ], - "time": "2015-06-19 03:43:16" + "time": "2015-09-23 14:46:55" }, { "name": "phpunit/phpunit", @@ -762,12 +741,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0fe5ed323f0017624a65746153ae3fd69c308c1b" + "reference": "3c4becbce99732549949904c47b76ffe602a7595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0fe5ed323f0017624a65746153ae3fd69c308c1b", - "reference": "0fe5ed323f0017624a65746153ae3fd69c308c1b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c4becbce99732549949904c47b76ffe602a7595", + "reference": "3c4becbce99732549949904c47b76ffe602a7595", "shasum": "" }, "require": { @@ -781,7 +760,7 @@ "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": ">=1.0.6", + "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", @@ -826,7 +805,7 @@ "testing", "xunit" ], - "time": "2015-08-02 08:24:28" + "time": "2016-04-25 09:17:33" }, { "name": "phpunit/phpunit-mock-objects", @@ -834,12 +813,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ca2e42cad8446a05691cc4327b7ce3ed45bfdb11" + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ca2e42cad8446a05691cc4327b7ce3ed45bfdb11", - "reference": "ca2e42cad8446a05691cc4327b7ce3ed45bfdb11", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", "shasum": "" }, "require": { @@ -882,7 +861,7 @@ "mock", "xunit" ], - "time": "2015-08-02 07:29:52" + "time": "2015-10-02 06:51:40" }, { "name": "sebastian/comparator", @@ -954,24 +933,24 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "6899b3e33bfbd386d88b5eea5f65f563e8793051" + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6899b3e33bfbd386d88b5eea5f65f563e8793051", - "reference": "6899b3e33bfbd386d88b5eea5f65f563e8793051", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "~4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -994,11 +973,11 @@ } ], "description": "Diff implementation", - "homepage": "http://www.github.com/sebastianbergmann/diff", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ "diff" ], - "time": "2015-06-22 14:15:55" + "time": "2015-12-08 07:14:41" }, { "name": "sebastian/environment", @@ -1006,12 +985,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", - "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", "shasum": "" }, "require": { @@ -1048,7 +1027,7 @@ "environment", "hhvm" ], - "time": "2015-08-03 06:14:51" + "time": "2016-02-26 18:40:46" }, { "name": "sebastian/exporter", @@ -1056,12 +1035,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "66fb20c9e1b3617651c3f66be7a1747479d96ba5" + "reference": "f88f8936517d54ae6d589166810877fb2015d0a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/66fb20c9e1b3617651c3f66be7a1747479d96ba5", - "reference": "66fb20c9e1b3617651c3f66be7a1747479d96ba5", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f88f8936517d54ae6d589166810877fb2015d0a2", + "reference": "f88f8936517d54ae6d589166810877fb2015d0a2", "shasum": "" }, "require": { @@ -1115,20 +1094,20 @@ "export", "exporter" ], - "time": "2015-07-30 09:58:30" + "time": "2015-08-09 04:23:41" }, { "name": "sebastian/global-state", - "version": "dev-master", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23af31f402993cfd94e99cbc4b782e9a78eb0e97" + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23af31f402993cfd94e99cbc4b782e9a78eb0e97", - "reference": "23af31f402993cfd94e99cbc4b782e9a78eb0e97", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", "shasum": "" }, "require": { @@ -1166,7 +1145,7 @@ "keywords": [ "global state" ], - "time": "2015-06-21 15:11:22" + "time": "2015-10-12 03:26:01" }, { "name": "sebastian/recursion-context", @@ -1174,12 +1153,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" + "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", - "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", + "reference": "7ff5b1b3dcc55b8ab8ae61ef99d4730940856ee7", "shasum": "" }, "require": { @@ -1219,7 +1198,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-06-21 08:04:50" + "time": "2016-01-28 05:39:29" }, { "name": "sebastian/version", @@ -1261,21 +1240,18 @@ "version": "2.8.x-dev", "source": { "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "6eef1477f3c4451b6024fadb1254009be4602908" + "url": "https://github.com/symfony/yaml.git", + "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/6eef1477f3c4451b6024fadb1254009be4602908", - "reference": "6eef1477f3c4451b6024fadb1254009be4602908", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940", + "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0.0" - }, "type": "library", "extra": { "branch-alias": { @@ -1285,7 +1261,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1303,17 +1282,20 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-07-29 07:12:56" + "time": "2016-03-29 19:00:15" } ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "bower-asset/jquery": 0 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.4.0", "ext-mbstring": "*", + "ext-ctype": "*", "lib-pcre": "*" }, "platform-dev": [] diff --git a/docs/guide-id/README.md b/docs/guide-id/README.md index c7e7c9d..57256a8 100644 --- a/docs/guide-id/README.md +++ b/docs/guide-id/README.md @@ -1,9 +1,9 @@ -Panduan Defitif Untuk Yii 2.0 +Panduan Definitif Untuk Yii 2.0 =============================== Tutorial ini dirilis di bawah [Persyaratan Dokumentasi Yii] (http://www.yiiframework.com/doc/terms/). -Seluruh hak cipta. +Seluruh hak cipta dilindungi. 2014 (c) Yii Software LLC. @@ -23,7 +23,7 @@ Mulai * [Mengatakan Hello] (start-hello.md) * [Bekerja dengan Form] (start-forms.md) * [Bekerja dengan Database] (start-databases.md) -* [Membangkitkan Kode dengan Gii] (start-gii.md) +* [Membuat Kode Otomatis dengan Gii] (start-gii.md) * [Menatap ke Depan] (start-looking-ahead.md) @@ -66,7 +66,7 @@ Konsep Pokok * [Perilaku] (concept-behaviors.md) * [Konfigurasi] (concept-configurations.md) * [Alias] (concept-aliases.md) -* [Class autoloading] (concept-autoloading.md) +* [Class Autoloading] (concept-autoloading.md) * [Layanan Locator] (concept-service-locator.md) * [Dependency Injection] (concept-di-container.md) @@ -76,7 +76,7 @@ Bekerja dengan Database * [Data Access Objects] (db-dao.md): Menghubungkan ke database, query dasar, transaksi, dan manipulasi skema * [Query Builder] (db-query-builder.md): Query database menggunakan lapisan abstraksi sederhana -* [Rekaman Aktif] (db-active-record.md): The Rekaman Aktif ORM, mengambil dan memanipulasi catatan, dan mendefinisikan hubungan +* [Active Record] (db-active-record.md): ORM Active Record, mengambil dan memanipulasi catatan, dan mendefinisikan hubungan * [Migrasi] (db-migrations.md): Terapkan kontrol versi untuk database Anda dalam lingkungan pengembangan tim * [Sphinx] (https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide/README.md) * [Redis] (https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/README.md) @@ -111,8 +111,8 @@ Keamanan * [Otentikasi] (security-authentication.md) * [Otorisasi] (security-authorization.md) -* [Bekerja dengan Sandi] (security-passwords.md) -* [Tupoksi Klien] (https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) +* [Bekerja dengan Kata Sandi] (security-passwords.md) +* [Otentikasi Klien] (https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) * [Praktik Terbaik] (security-best-practices.md) @@ -144,8 +144,8 @@ Alat Pengembangan ----------------- * [Debug Toolbar dan Debugger] (https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) -* [Kode Membangkitkan menggunakan Gii] (https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) -* ** TBD ** [Membangkitkan API Documentation] (https://github.com/yiisoft/yii2-apidoc) +* [Membuat Kode Otomatis dengan Gii] (https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) +* ** TBD ** [Membuat API Documentation] (https://github.com/yiisoft/yii2-apidoc) Pengujian @@ -169,12 +169,12 @@ Topik Khusus * [Internasionalisasi] (tutorial-i18n.md) * [Mailing] (tutorial-mailing.md) * [Penyetelan Performa] (tutorial-performance-tuning.md) -* [Shared Hosting Lingkungan] (tutorial-shared-hosting.md) +* [Lingkungan Shared Hosting] (tutorial-shared-hosting.md) * [Template Engine] (tutorial-template-engines.md) * [Bekerja dengan Kode Pihak Ketiga] (tutorial-yii-integration.md) -widget +Widget ------- * GridView: ** TBD ** Link ke demo halaman @@ -195,4 +195,4 @@ Alat Bantu * [Tinjauan] (helper-overview.md) * [ArrayHelper] (helper-array.md) * [Html] (helper-html.md) -* [Url] (helper-url.md) \ No newline at end of file +* [Url] (helper-url.md) diff --git a/docs/guide-id/intro-upgrade-from-v1.md b/docs/guide-id/intro-upgrade-from-v1.md index ef7debc..df26874 100644 --- a/docs/guide-id/intro-upgrade-from-v1.md +++ b/docs/guide-id/intro-upgrade-from-v1.md @@ -1,21 +1,21 @@ Upgrade dari Versi 1.1 ========================== -Ada banyak perbedaan antara versi 1.1 dan 2.0 karena Yii sebagai framework benar-benar ditulis ulang untuk 2.0. -Akibatnya, upgrade dari versi 1.1 tidak mudah seperti upgrade antara versi minor. Dalam panduan ini Anda akan +Ada banyak perbedaan antara versi 1.1 dan 2.0 karena Yii Framework benar-benar ditulis ulang di versi 2.0. +Akibatnya, upgrade dari versi 1.1 tidak mudah seperti upgrade untuk versi minor. Dalam panduan ini Anda akan menemukan perbedaan utama antara dua versi. -Jika Anda belum pernah menggunakan Yii 1.1 sebelumnya, Anda dapat dengan aman melewati bagian ini dan mengubah langsung ke "[Persiapan](start-installation.md)". +Jika Anda belum pernah menggunakan Yii 1.1 sebelumnya, Anda dapat dengan aman melewati bagian ini dan menuju ke "[Persiapan](start-installation.md)". -Harap dicatat bahwa Yii 2.0 memperkenalkan fitur baru dari tercakup dalam ringkasan ini. Hal ini sangat dianjurkan -Anda membaca melalui panduan definitif keseluruhan untuk belajar tentang mereka semua. Kemungkinannya adalah bahwa -beberapa fitur yang sebelumnya Anda harus kembangkan sendiri sekarang menjadi bagian dari kode inti. +Harap dicatat bahwa Yii 2.0 memperkenalkan lebih banyak fitur baru dari yang tercakup dalam ringkasan ini. Sangat dianjurkan +Anda membaca keseluruhan panduan definitif untuk mempelajari hal tersebut. Kemungkinannya adalah bahwa +beberapa fitur yang sebelumnya harus anda kembangkan sendiri kini menjadi bagian dari kode inti. Instalasi ------------ -Yii 2.0 sepenuhnya merangkul [composer](https://getcomposer.org/), yang adalah paket manager PHP. Instalasi +Yii 2.0 sepenuhnya menggunakan [composer](https://getcomposer.org/), yaitu dependency manager yang sudah diakui oleh PHP. Instalasi dari kerangka inti serta ekstensi, ditangani melalui Composer. Silakan merujuk ke bagian [Instalasi Yii](start-installation.md) untuk belajar cara menginstal Yii 2.0. Jika Anda menghendaki membuat ekstensi baru, atau mengubah ekstensi 1.1 yang sudah ke ekstensi 2.0 yang kompatibel, silakan @@ -25,7 +25,7 @@ merujuk panduan [Membuat Ekstensi](structure-extensions.md#menciptakan-ekstensi) Persyaratan PHP ---------------- -Yii 2.0 membutuhkan PHP 5.4 atau di atas, yang merupakan perbaikan besar atas PHP versi 5.2 yang dibutuhkan oleh Yii 1.1. +Yii 2.0 membutuhkan PHP 5.4 atau versi lebih tinggi, yang merupakan perbaikan besar atas PHP versi 5.2 yang dibutuhkan oleh Yii 1.1. Akibatnya, ada banyak perbedaan pada tingkat bahasa yang harus Anda perhatikan. Di bawah ini adalah ringkasan perubahan utama mengenai PHP: @@ -41,7 +41,7 @@ Di bawah ini adalah ringkasan perubahan utama mengenai PHP:   untuk mendukung fitur internasionalisasi. -namespace +Namespace --------- Perubahan yang paling jelas dalam Yii 2.0 adalah penggunaan namespace. Hampir setiap kelas inti @@ -49,8 +49,7 @@ menggunakan namespace, misalnya, `yii\web\Request`. Awalan "C" tidak lagi diguna Skema penamaan sekarang mengikuti struktur direktori. Misalnya, `yii\web\Request` menunjukkan bahwa file kelas yang sesuai adalah `web/Request.php` bawah folder framework Yii. -(Anda dapat menggunakan setiap kelas inti tanpa menyertakannya secara eksplisit berkat Yii -class loader.) +(Anda dapat menggunakan setiap kelas inti tanpa menyertakannya secara eksplisit berkat Yiiclass loader.) Komponen dan Object @@ -97,8 +96,7 @@ yang berisi pasangan nama-nilai untuk menginisialisasi properti pada akhir konst Anda dapat menimpa method [[yii\base\Object::init()|init()]] untuk melakukan pekerjaan inisialisasi yang harus dilakukan setelah konfigurasi telah diterapkan. -Dengan mengikuti konvensi ini, Anda akan dapat membuat dan mengkonfigurasi objek baru -menggunakan array konfigurasi: +Dengan mengikuti konvensi ini, Anda akan dapat membuat dan mengkonfigurasi objek baru menggunakan array konfigurasi: ```php $object = Yii::createObject([ @@ -138,14 +136,14 @@ Path Alias Yii 2.0 memperluas penggunaan alias path baik untuk file/direktori maupun URL. Yii 2.0 juga sekarang mensyaratkan nama alias dimulai dengan karakter `@`. -Misalnya, alias `@yii` mengacu pada direktori instalasi Yii. Alias path +Misalnya, alias `@yii` mengacu pada direktori instalasi Yii. Alias path didukung di sebagian besar tempat di kode inti Yii. Misalnya, [[yii\caching\FileCache::cachePath]] dapat mengambil baik alias path maupun direktori normal. Sebuah alias juga terkait erat dengan namespace kelas. Disarankan alias didefinisikan untuk setiap akar namespace, -sehingga memungkinkan Anda untuk menggunakan autoloader class Yii tanpa konfigurasi lebih lanjut. -Misalnya, karena `@yii` mengacu pada direktori instalasi Yii, class seperti `yii\web\Request` dapat otomatis diambil. -Jika Anda menggunakan librari pihak ketiga seperti Zend Framework. Anda dapat menentukan alias path `@Zend` yang mengacu pada +sehingga memungkinkan Anda untuk menggunakan autoloader class Yii tanpa konfigurasi lebih lanjut. +Misalnya, karena `@yii` mengacu pada direktori instalasi Yii, class seperti `yii\web\Request` dapat otomatis diambil. +Jika Anda menggunakan librari pihak ketiga seperti Zend Framework. Anda dapat menentukan alias path `@Zend` yang mengacu pada direktori instalasi framework direktori. Setelah Anda selesai melakukannya, Yii akan dapat menload setiap class dalam librari Zend Framework. Lebih jauh tentang alias path dapat ditemukan di bagian [Alias](concept-aliases.md). @@ -217,7 +215,7 @@ Yii 2.0 menggunakan [[yii\web\Controller]] sebagai kelas dasar controller, yang Dampak paling nyata dari perubahan ini pada kode Anda adalah bahwa aksi kontroler harus mengembalikan nilai konten alih-alih menampilkannya: -````php +```php public function actionView($id) { $model = \app\models\Post::findOne($id); @@ -303,10 +301,10 @@ sumber berdasarkan kategori pesan. Silakan merujuk ke bagian [Internasionalisasi](tutorial-i18n.md) untuk rincian lebih lanjut. -Filter Action +Action Filter -------------- -Filter Action sekarang diimplementasikan melalui behavior. Untuk membuat baru, filter diperluas dari [[yii\base\ActionFilter]]. +Action Filter sekarang diimplementasikan melalui behavior. Untuk membuat baru, filter diperluas dari [[yii\base\ActionFilter]]. Untuk menggunakan filter, pasang Kelas filter untuk controller sebagai behavior. Misalnya, untuk menggunakan filter [[yii\filters\AccessControl]], Anda harus mengikuti kode berikut di kontroler: @@ -539,4 +537,4 @@ Menggunakan Yii 1.1 dan 2.x bersama-sama ------------------------------ Jika Anda memiliki warisan kode Yii 1.1 yang ingin Anda gunakan bersama-sama dengan Yii 2.0, silakan lihat -bagian [Menggunakan Yii 1.1 dan 2.0 Bersama](tutorial-yii-integration.md). \ No newline at end of file +bagian [Menggunakan Yii 1.1 dan 2.0 Bersama](tutorial-yii-integration.md). diff --git a/docs/guide-id/intro-yii.md b/docs/guide-id/intro-yii.md index b822c18..f1dd73e 100644 --- a/docs/guide-id/intro-yii.md +++ b/docs/guide-id/intro-yii.md @@ -2,7 +2,7 @@ Apa Itu Yii =========== Yii adalah kerangka kerja PHP berkinerja tinggi, berbasis komponen yang digunakan untuk mengembangkan aplikasi web modern dengan cepat. -Nama Yii (diucapkan `Yee` atau` [ji:] `) berarti" sederhana dan evolusi "dalam bahasa Cina. Hal ini dapat juga +Nama Yii (diucapkan `Yee` atau `[ji:]`) yang berarti "sederhana dan berevolusi" dalam bahasa Cina. Hal ini dapat juga dianggap sebagai singkatan **Yes It Is (Ya, Itu Dia)**! @@ -10,31 +10,24 @@ Yii Terbaik untuk Apa? --------------------- Yii adalah kerangka kerja pemrograman web umum, yang berarti bahwa hal itu dapat digunakan untuk mengembangkan semua jenis -aplikasi Web yang menggunakan PHP. Karena arsitektur berbasis komponen dan dukungan caching yang canggih, - Yii sangat cocok untuk mengembangkan aplikasi skala besar seperti portal, forum, konten -sistem manajemen (CMS), proyek e-commerce, layanan web REST, dan sebagainya. +aplikasi Web yang menggunakan PHP. Karena arsitektur berbasis komponen dan dukungan caching yang canggih, Yii sangat cocok untuk mengembangkan aplikasi skala besar seperti portal, forum, sistem manajemen konten (CMS), proyek e-commerce, layanan web REST, dan sebagainya. -Bagaimana Yii Dibandingkan dengan Frameworks lain? +Bagaimana jika Yii Dibandingkan dengan Frameworks lain? ------------------------------------------- Jika Anda sudah akrab dengan framework lain, Anda mungkin menghargai pengetahuan bagaimana Yii dibandingkan: -- Seperti kebanyakan PHP framework, Yii mengimplementasikan MVC (Model-View-Controller) pola arsitektur dan mempromosikan kode -  organisasi berdasarkan pola itu. -- Yii mengambil filosofi kode yang harus ditulis dengan cara sederhana namun elegan. Yii tidak akan pernah mencoba untuk -  mendesain berlebihan terutama untuk secara ketat mengikuti beberapa pola desain. -- Yii adalah framework penuh yang menyediakan banyak fitur teruji dan siap pakai seperti: query builder -  dan ActiveRecord baik untuk relasional maupun NoSQL database; dukungan pengembangan API REST; dukungan -  caching banyak lapis dan masih banyak lagi. -- Yii sangat extensible. Anda dapat menyesuaikan atau mengganti hampir setiap bagian dari kode inti Yii. Anda juga bisa -  mengambil keuntungan dari arsitektur ekstensi Yii yang padat untuk menggunakan atau mengembangkan ekstensi untuk disebarkan kembali. -- Kinerja tinggi selalu tujuan utama dari Yii. - -Yii bukan one-man show, Yii didukung oleh [tim pengembang inti yang kuat][about_yii], serta komunitas besar +- Seperti kebanyakan PHP framework, Yii mengimplementasikan pola arsitektur MVC (Model-View-Controller) dan mempromosikan kode organisasi berdasarkan pola itu. +- Yii mengambil filosofi bahwa kode harus ditulis dengan cara sederhana namun elegan. Yii tidak akan pernah mencoba untuk mendesain berlebihan terutama untuk mengikuti beberapa pola desain secara ketat. +- Yii adalah framework penuh yang menyediakan banyak fitur teruji dan siap pakai seperti: query builder dan ActiveRecord baik untuk relasional maupun NoSQL database; dukungan pengembangan API REST; dukungan caching banyak lapis dan masih banyak lagi. +- Yii sangat extensible. Anda dapat menyesuaikan atau mengganti hampir setiap bagian dari kode inti Yii. Anda juga bisa mengambil keuntungan dari arsitektur ekstensi Yii yang solid untuk menggunakan atau mengembangkan ekstensi untuk disebarkan kembali. +- Kinerja tinggi selalu menjadi tujuan utama dari Yii. + +Yii tidak dikerjakan oleh satu orang, Yii didukung oleh [tim pengembang inti yang kuat][about_yii], serta komunitas besar profesional yang terus memberikan kontribusi bagi pengembangan Yii. Tim pengembang Yii -terus mengamati perkembangan tren terbaru Web, pada praktik terbaik serta fitur yang -ditemukan dalam framework dan proyek lain. Praktik terbaik yang paling relevan dan fitur yang ditemukan di tempat lain secara teratur +terus mengamati perkembangan tren terbaru Web, pada penerapan terbaik serta fitur yang +ditemukan dalam framework dan proyek lain. Penerapan terbaik yang paling relevan dan fitur yang ditemukan di tempat lain secara teratur dimasukkan ke dalam kerangka inti dan menampakkannya melalui antarmuka yang sederhana dan elegan. [about_yii]: http://www.yiiframework.com/about/ @@ -42,8 +35,8 @@ dimasukkan ke dalam kerangka inti dan menampakkannya melalui antarmuka yang sede Versi Yii ---------- -Yii saat ini memiliki dua versi utama yang tersedia: 1.1 dan 2.0. Versi 1.1 adalah generasi tua dan sekarang dalam modus pemeliharaan. -Versi 2.0 adalah penulisan ulang lengkap dari Yii, mengadopsi teknologi dan protokol terbaru, termasuk composer, PSR, namespace, trait, dan sebagainya. +Yii saat ini memiliki dua versi utama yang tersedia: 1.1 dan 2.0. Versi 1.1 adalah generasi lama dan sekarang dalam mode pemeliharaan. +Versi 2.0 adalah penulisan ulang lengkap dari Yii, mengadopsi teknologi dan protokol terbaru, termasuk composer, PSR, namespace, trait, dan sebagainya. Versi 2.0 merupakan generasi framework yang sekarang dan terus menerima upaya pengembangan selama beberapa tahun ke depan. Panduan ini terutama tentang versi 2.0. @@ -51,9 +44,9 @@ Panduan ini terutama tentang versi 2.0. Persyaratan dan Prasyarat -------------------------- -Yii 2.0 memerlukan PHP 5.4.0 atau lebih. Anda dapat menemukan persyaratan yang lebih rinci untuk setiap fitur +Yii 2.0 memerlukan PHP 5.4.0 atau versi lebih tinggi. Anda dapat menemukan persyaratan yang lebih rinci untuk setiap fitur dengan menjalankan pengecek persyaratan yang diikutsertakan dalam setiap rilis Yii. Menggunakan Yii memerlukan pengetahuan dasar tentang pemrograman berorientasi objek (OOP), mengingat Yii adalah framework berbasis OOP murni. Yii 2.0 juga memanfaatkan fitur terbaru dari PHP, seperti [namespace](http://www.php.net/manual/en/language.namespaces.php) dan [traits](http://www.php.net/manual/en/language.oop5.traits.php). -Memahami konsep-konsep ini akan membantu Anda lebih mudah memahami Yii 2.0. \ No newline at end of file +Memahami konsep-konsep ini akan membantu Anda lebih mudah memahami Yii 2.0. diff --git a/docs/guide-ja/db-active-record.md b/docs/guide-ja/db-active-record.md index 1f871b6..9c746ec 100644 --- a/docs/guide-ja/db-active-record.md +++ b/docs/guide-ja/db-active-record.md @@ -527,6 +527,11 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); > - [[yii\db\ActiveRecord::updateCounters()]] > - [[yii\db\ActiveRecord::updateAllCounters()]] +### データをリフレッシュする際のライフサイクル + +[[yii\db\ActiveRecord::refresh()|refresh()]] を呼んでアクティブレコードインスタンスをリフレッシュする際は、リフレッシュが成功してメソッドが `true` を返すと +[[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] イベントがトリガされます。 + ## トランザクションを扱う diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md index 63365bc..fcf10c5 100644 --- a/docs/guide-ja/db-migrations.md +++ b/docs/guide-ja/db-migrations.md @@ -407,7 +407,7 @@ class m160328_040430_create_post extends Migration パラメータが渡されなかった場合は、テーブル名はカラム名から推測されます。 上記の例で `author_id:integer:notNull:foreignKey(user)` は、`user` テーブルへの外部キーを持つ `author_id` という名前のカラムを生成します。 -一方、`category_id:integer:default(1):foreignKey` は、`category` テーブルへの外部キーを持つ `category_id` というカラムを生成します。 +一方、`category_id:integer:defaultValue(1):foreignKey` は、`category` テーブルへの外部キーを持つ `category_id` というカラムを生成します。 ### テーブルを削除する diff --git a/docs/guide-ja/images/tutorial-console-help.png b/docs/guide-ja/images/tutorial-console-help.png index 6813a96..15b8b66 100644 Binary files a/docs/guide-ja/images/tutorial-console-help.png and b/docs/guide-ja/images/tutorial-console-help.png differ diff --git a/docs/guide-ja/rest-authentication.md b/docs/guide-ja/rest-authentication.md index 503e41f..e2f7caa 100644 --- a/docs/guide-ja/rest-authentication.md +++ b/docs/guide-ja/rest-authentication.md @@ -110,5 +110,5 @@ class User extends ActiveRecord implements IdentityInterface ユーザが認証された後、おそらくは、リクエストされたリソースに対してリクエストされたアクションを実行する許可を彼または彼女が持っているかどうかをチェックしたいでしょう。 *権限付与* と呼ばれるこのプロセスについては、[権限付与](security-authorization.md) のセクションで詳細に説明されています。 -あなたのコントローラが [[yii\rest\ActiveController]] から拡張したものである場合は、[[yii\rest\Controller::checkAccess()|checkAccess()]] メソッドをオーバーライドして権限付与のチェックを実行することが出来ます。 +あなたのコントローラが [[yii\rest\ActiveController]] から拡張したものである場合は、[[yii\rest\ActiveController::checkAccess()|checkAccess()]] メソッドをオーバーライドして権限付与のチェックを実行することが出来ます。 このメソッドが [[yii\rest\ActiveController]] によって提供されている内蔵のアクションから呼び出されます。 diff --git a/docs/guide-ja/runtime-routing.md b/docs/guide-ja/runtime-routing.md index 550566a..432aa2f 100644 --- a/docs/guide-ja/runtime-routing.md +++ b/docs/guide-ja/runtime-routing.md @@ -109,6 +109,8 @@ $url = Url::to(['post/view', 'id' => 100]); `catchAll` プロパティは配列を取り、最初の要素はルートを指定し、残りの要素 (「名前-値」のペア) は [アクションのパラメータ](structure-controllers.md#action-parameters) を指定するものでなければなりません。 +> Info: このプロパティを有効にすると、開発環境でデバッグパネルが動作しなくなります。 + ## URL を生成する diff --git a/docs/guide-ja/security-authorization.md b/docs/guide-ja/security-authorization.md index 74b16cd..3f8e9de 100644 --- a/docs/guide-ja/security-authorization.md +++ b/docs/guide-ja/security-authorization.md @@ -305,6 +305,9 @@ class RbacController extends Controller } ``` +> Note: アドバンストテンプレートを使おうとするときは、`RbacController` を `console/controllers` +ディレクトリの中に置いて、名前空間を `console/controllers` に変更する必要があります。 + `yii rbac/init` によってコマンドを実行した後には、次の権限階層が得られます。 ![単純な RBAC 階層](images/rbac-hierarchy-1.png "単純な RBAC 階層") diff --git a/docs/guide-ja/structure-applications.md b/docs/guide-ja/structure-applications.md index 3c7442e..2c9721b 100644 --- a/docs/guide-ja/structure-applications.md +++ b/docs/guide-ja/structure-applications.md @@ -182,6 +182,8 @@ if (YII_ENV_DEV) { ] ``` +> Info: このプロパティを有効にすると、開発環境でデバッグパネルが動作しなくなります。 + #### [[yii\base\Application::components|components]] diff --git a/docs/guide-ja/tutorial-console.md b/docs/guide-ja/tutorial-console.md index e4709dc..5f907b2 100644 --- a/docs/guide-ja/tutorial-console.md +++ b/docs/guide-ja/tutorial-console.md @@ -160,7 +160,7 @@ class HelloController extends Controller これで、次の構文を使ってコマンドを走らせることが出来るようになります。 ``` -./yii hello -m=hola +./yii hello -m=hello ``` ### 引数 diff --git a/docs/guide-ru/images/tutorial-console-help.png b/docs/guide-ru/images/tutorial-console-help.png index 6813a96..15b8b66 100644 Binary files a/docs/guide-ru/images/tutorial-console-help.png and b/docs/guide-ru/images/tutorial-console-help.png differ diff --git a/docs/guide-ru/structure-modules.md b/docs/guide-ru/structure-modules.md index a00be9e..a24e418 100644 --- a/docs/guide-ru/structure-modules.md +++ b/docs/guide-ru/structure-modules.md @@ -272,6 +272,6 @@ class Module extends \yii\base\Module которых функции тесно связаны между собой. Каждая группа функций может разрабатываться в виде модуля, над которым работает один разработчик или одна команда. -Модули - это хороший способ повторно использовать код на уровне групп функций. В виде модулей можно реализовать такая -функциональность как управление пользователями или управление комментариями, а затем использовать эти модули в будущих +Модули - это хороший способ повторно использовать код на уровне групп функций. В виде модулей можно реализовать такую +функциональность, как управление пользователями или управление комментариями, а затем использовать эти модули в будущих разработках. diff --git a/docs/guide-ru/tutorial-mailing.md b/docs/guide-ru/tutorial-mailing.md index 31d71fc..bcc4d66 100644 --- a/docs/guide-ru/tutorial-mailing.md +++ b/docs/guide-ru/tutorial-mailing.md @@ -166,7 +166,7 @@ use yii\helpers\Html; Прикрепление файлов --------------- -Вы можете прикрепить вложения в сообщению с помощью методов `attach()` и `attachContent()`: +Вы можете прикрепить вложения к сообщению с помощью методов `attach()` и `attachContent()`: ```php $message = Yii::$app->mailer->compose(); @@ -183,7 +183,7 @@ $message->attachContent('Attachment content', ['fileName' => 'attach.txt', 'cont ---------------- Вы можете вставить изображения в содержание сообщения через `embed()` метод. Этот метод возвращает id прикрепленной картинки, -которые должны быть дыть доступны в 'img' тегах. +которые должны быть доступны в 'img' тегах. Этот метод легко использовать, когда сообщение составляется через файлы представления: ```php diff --git a/docs/guide-uk/images/tutorial-console-help.png b/docs/guide-uk/images/tutorial-console-help.png index 6813a96..15b8b66 100644 Binary files a/docs/guide-uk/images/tutorial-console-help.png and b/docs/guide-uk/images/tutorial-console-help.png differ diff --git a/docs/guide/db-active-record.md b/docs/guide/db-active-record.md index 8f52841..538b40d 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -570,6 +570,11 @@ life cycle will happen: > - [[yii\db\ActiveRecord::updateCounters()]] > - [[yii\db\ActiveRecord::updateAllCounters()]] +### Refreshing Data Life Cycle + +When calling [[yii\db\ActiveRecord::refresh()|refresh()]] to refresh an Active Record instance, the +[[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] event is triggered if refresh is successful and the method returns `true`. + ## Working with Transactions diff --git a/docs/guide/db-migrations.md b/docs/guide/db-migrations.md index 0027434..2933f72 100644 --- a/docs/guide/db-migrations.md +++ b/docs/guide/db-migrations.md @@ -417,8 +417,8 @@ is passed then the table name will be deduced from the column name. In the example above `author_id:integer:notNull:foreignKey(user)` will generate a column named `author_id` with a foreign key to the `user` table while -`category_id:integer:default(1):foreignKey` will generate a column `category_id` -with a foreign key to the `category` table. +`category_id:integer:defaultValue(1):foreignKey` will generate a column +`category_id` with a foreign key to the `category` table. ### Drop Table @@ -685,6 +685,10 @@ Below is the list of all these database accessing methods: * [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: removing a foreign key * [[yii\db\Migration::createIndex()|createIndex()]]: creating an index * [[yii\db\Migration::dropIndex()|dropIndex()]]: removing an index +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]: adding comment to column +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]: dropping comment from column +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]: adding comment to table +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]: dropping comment from table > Info: [[yii\db\Migration]] does not provide a database query method. This is because you normally do not need to display extra message about retrieving data from a database. It is also because you can use the powerful diff --git a/docs/guide/images/tutorial-console-help.png b/docs/guide/images/tutorial-console-help.png index 6813a96..15b8b66 100644 Binary files a/docs/guide/images/tutorial-console-help.png and b/docs/guide/images/tutorial-console-help.png differ diff --git a/docs/guide/input-forms.md b/docs/guide/input-forms.md index 4ee812f..4557a61 100644 --- a/docs/guide/input-forms.md +++ b/docs/guide/input-forms.md @@ -78,6 +78,10 @@ The name of the input field is determined automatically from the model's [[yii\b For example, the name for the input field for the `username` attribute in the above example will be `LoginForm[username]`. This naming rule will result in an array of all attributes for the login form to be available in `$_POST['LoginForm']` on the server side. +> Tip: If you have only one model in a form and want to simplify the input names you may skip the array part by +> overriding the [[yii\base\Model::formName()|formName()]] method of the model to return an empty string. +> This can be useful for filter models used in the [GridView](output-data-widgets.md#grid-view) to create nicer URLs. + Specifying the attribute of the model can be done in more sophisticated ways. For example when an attribute may take an array value when uploading multiple files or selecting multiple items you may specify it by appending `[]` to the attribute name: diff --git a/docs/guide/rest-authentication.md b/docs/guide/rest-authentication.md index 75dd0e7..0276b60 100644 --- a/docs/guide/rest-authentication.md +++ b/docs/guide/rest-authentication.md @@ -123,5 +123,5 @@ action for the requested resource. This process is called *authorization* which the [Authorization section](security-authorization.md). If your controllers extend from [[yii\rest\ActiveController]], you may override -the [[yii\rest\Controller::checkAccess()|checkAccess()]] method to perform authorization check. The method +the [[yii\rest\ActiveController::checkAccess()|checkAccess()]] method to perform authorization check. The method will be called by the built-in actions provided by [[yii\rest\ActiveController]]. diff --git a/docs/guide/runtime-routing.md b/docs/guide/runtime-routing.md index dd48fdc..99f6444 100644 --- a/docs/guide/runtime-routing.md +++ b/docs/guide/runtime-routing.md @@ -124,6 +124,8 @@ With the above configuration, the `site/offline` action will be used to handle a The `catchAll` property should take an array whose first element specifies a route, and the rest of the elements (name-value pairs) specify the parameters to be [bound to the action](structure-controllers.md#action-parameters). +> Info: Debug panel on development environment will not work when this property is enabled + ## Creating URLs diff --git a/docs/guide/structure-applications.md b/docs/guide/structure-applications.md index 0e96dad..80dd6ab 100644 --- a/docs/guide/structure-applications.md +++ b/docs/guide/structure-applications.md @@ -192,6 +192,7 @@ The rest of the array elements (key-value pairs) specify the parameters to be bo ] ``` +> Info: Debug panel on development environment will not work when this property is enabled #### [[yii\base\Application::components|components]] diff --git a/docs/guide/tutorial-console.md b/docs/guide/tutorial-console.md index a8daa89..13e7974 100644 --- a/docs/guide/tutorial-console.md +++ b/docs/guide/tutorial-console.md @@ -165,7 +165,7 @@ class HelloController extends Controller Now, you can use the following syntax to run the command: ``` -./yii hello -m=hola +./yii hello -m=hello ``` ### Arguments diff --git a/docs/internals-ja/core-code-style.md b/docs/internals-ja/core-code-style.md index 943c6a5..2f4fc6c 100644 --- a/docs/internals-ja/core-code-style.md +++ b/docs/internals-ja/core-code-style.md @@ -126,7 +126,7 @@ class Foo - クラスのメソッドは常に修飾子 `private`、`protected` または `public` を使って、可視性を宣言すべきです。`var` は許可されません。 - 関数の開始の中括弧は関数宣言の次の行に置くべきです。 -``` +```php /** * ドキュメント */ @@ -273,9 +273,9 @@ if (!$model && null === $event) ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } else { - // $result を処理 + // $result を処理 } ``` @@ -284,7 +284,7 @@ if (empty($result)) { ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } // $result を処理 diff --git a/docs/internals-ru/core-code-style.md b/docs/internals-ru/core-code-style.md new file mode 100644 index 0000000..c8b7374 --- /dev/null +++ b/docs/internals-ru/core-code-style.md @@ -0,0 +1,477 @@ +Стиль кодирования Yii2 framework +============================== + +Описанный ниже стиль кодирования используется при разработке ядра Yii 2.x и его официальных расширений. Если вы хотите участвовать в разработке фреймворка, постарайтесь придерживаться данного стиля. Мы не принуждаем вас использовать этот стиль при разработке ваших приложений с использованием Yii 2. В данном случае можете использовать тот стиль, который вам больше подходит. + +Пример конфигурационного файла для CodeSniffer вы можете найти здесь: https://github.com/yiisoft/yii2-coding-standards + +1. Обзор +----------- + +В общем, мы используем совместимый со стандартом [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) стиль, так что все положения [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) вполне применимы к нашему стилю кодирования. + +- ДОЛЖНЫ использоваться только открывающие теги `` или `` не нужен; +- Не добавляйте лишние пробелы в конец строки; +- Все файлы, содержащие PHP код, должны иметь расширение `.php`. + +### 2.2. Кодировка символов + +PHP код должен содержать только символы в кодировке UTF-8 без BOM. + +3. Имена Классов +-------------- + +Имена классов ДОЛЖНЫ быть определены используя `StudlyCaps`. Например, `Controller`, `Model`. + +4. Классы +---------- + +В данном случае, под классом подразумеваются все классы и интерфейсы. + +- При именовании классов следует использовать `CamelCase`; +- Открывающая фигурная скобка всегда должна быть на следующей строке после имена класса; +- Все классы должны быть документированы в соответствии с PHPDoc; +- Весь код класса должен быть выровнен с использованием 4 пробелов; +- В одном PHP файле должен быть только один класс; +- Все классы должны использовать пространства имен; +- Имя класса должно совпадать с именем файла. Пространство имен класса должно соответствовать структуре каталогов. + +```php +/** + * Документация + */ +class MyClass extends \yii\Object implements MyInterface +{ + // код +} +``` + +### 4.1. Константы + +Константы класса ДОЛЖНЫ быть объявлены в верхнем регистре с подчеркиванием в качестве разделителей. +Пример: + +```php + 'Yii', + 'options' => ['usePHP' => true], +]; +``` + +### 5.4 Управляющие конструкции + +- Оставляйте один пробел перед открывающей круглой скобкой и после закрывающей круглой скобки в управляющих конструкциях; +- Операторы внутри круглых скобок должны разделяться пробелами; +- Открывающая фигурная скобка должна быть на той же строке; +- Закрывающая фигурная скобка должна быть на новой строке; +- Всегда используйте фигурные скобки для однострочных выражений. + +```php +if ($event === null) { + return new Event(); +} +if ($event instanceof CoolEvent) { + return $event->instance(); +} +return null; + + +// такой код недопустим: +if (!$model && null === $event) + throw new Exception('test'); +``` + +Старайтесь избегать использования `else` после `return` там, где это возможно. +Используйте [граничные операторы](https://refactoring.guru/ru/replace-nested-conditional-with-guard-clauses). + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} else { + // дальнейшие вычисления +} +``` + +лучше переписать так: + +```php +$result = $this->getResult(); +if (empty($result)) { + return true; +} + +// дальнейшие вычисления +``` + +#### switch + +Используйте следующий стиль для switch: + +```php +switch ($this->phpType) { + case 'string': + $a = (string) $value; + break; + case 'integer': + case 'int': + $a = (int) $value; + break; + case 'boolean': + $a = (bool) $value; + break; + default: + $a = null; +} +``` + +### 5.5 Вызовы функций + +```php +doIt(2, 3); + +doIt(['a' => 'b']); + +doIt('a', [ + 'a' => 'b', + 'c' => 'd', +]); +``` + +### 5.6 Анонимные (lambda) функции + +Не забывайте про пробелы между ключевыми словами `function`/`use` и открывающими круглыми скобками: + +```php +// правильно +$n = 100; +$sum = array_reduce($numbers, function ($r, $x) use ($n) { + $this->doMagic(); + $r += $x * $n; + return $r; +}); + +// неправильно +$n = 100; +$mul = array_reduce($numbers, function($r, $x) use($n) { + $this->doMagic(); + $r *= $x * $n; + return $r; +}); +``` + +Документация +------------- + +- Для получения информации по синтаксису документации обратитесь к первоисточнику [PHPDoc](http://phpdoc.org/); +- Код без документации недопустим; +- Все файлы классов должны содержать блок документации в начале файла и блок документации непосредственно перед каждым классом; +- Нет необходимости использовать тег `@return` если метод не возвращает значение; +- Все виртуальные свойства классов, наследованных от `yii\base\Object`, документируются тегом `@property` в блоке документации класса; + Аннотации геттеров и сеттеров автоматически генерируются из соответствующих тегов `@return` or `@param` + посредством выполнения команды `./build php-doc` в соответствующем каталоге; + Вы можете добавить дополнительный тег `@property` для геттера или сеттера для пояснения назначения переменной метода, если это необходимо. + Например: + +```php + + * @since 2.0 + */ +class Component extends \yii\base\Object +``` + + +#### Функция / метод + +```php +/** + * Returns the list of attached event handlers for an event. + * You may manipulate the returned [[Vector]] object by adding or removing handlers. + * For example, + * + * ``` + * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); + * ``` + * + * @param string $name the event name + * @return Vector list of attached event handlers for the event + * @throws Exception if the event is not defined + */ +public function getEventHandlers($name) +{ + if (!isset($this->_e[$name])) { + $this->_e[$name] = new Vector; + } + $this->ensureBehaviors(); + return $this->_e[$name]; +} +``` + +#### Разметка + +Как вы можете видеть в примерах выше, мы используем специальную разметку для форматирования комментариев PHPDoc. + +Ниже описан дополнительный синтаксис для описания связей между классами, методами и свойствами в документации: + +- `'[[canSetProperty]] ` создаст ссылку на метод или свойство `canSetProperty` этого класса; +- `'[[Component::canSetProperty]]` создаст ссылку на метод `canSetProperty` класса `Component` того же пространства имен; +- `'[[yii\base\Component::canSetProperty]]` создаст ссылку на метод `canSetProperty` класса `Component` в пространстве имен `yii\base`; +- `'[[Component]]` создаст ссылку на класс `Component` в том же пространстве имен. Здесь так же возможно явное указание пространства имен. + +Для явного указания текста ссылки возможно использование следующего синтаксиса: + +``` +... as displayed in the [[header|header cell]]. +``` + +Часть до | это имя свойства, метода или класса для ссылки, а часто поле | это текст ссылки. + +Так же, возможно создание ссылок на Руководство: + +```markdown +[Руководство](guide:file-name.md) +[Раздел руководства](guide:file-name.md#subsection) +``` + + +#### Комментарии + +- Однострочные комментарии должны начинаться с `//`, а не с `#`; +- Однострочные комментарии должны располагаться на отдельной строке. + +Дополнительные правила +---------------- + +### `=== []` или `empty()` + +Используйте `empty()`, где это возможно. + +### Несколько точек возврата + +Не допускайте запутанных вложенных условных конструкций, используйте return. Для коротких методов это не актуально. + +### `self` или `static` + +Всегда используйте `static`, за исключением следующих случаев: + +- доступ к константам ДОЛЖЕН осуществляться через `self`: `self::MY_CONSTANT`; +- доступ к защищенным статическим свойствам ДОЛЖЕН осуществляться через `self`: `self::$_events`; +- допустимо использовать `self` для рекурсивного обращения к текущему классу, вместо класса наследника. + +### Значение "ничего не делать" + +Свойства указывающее компоненту на отсутствие необходимости что-либо делать, должны принимать значение `false`. `null`, `''`, or `[]` не должны использоваться в таких случаях. + +### Каталоги/пространства имен + +- Используйте нижний регистр; +- используйте множественную форму для существительных, представляющих объекты (например валидаторы); +- используйте единичную форму для имен, представляющих соответствующий функционал (например web). diff --git a/docs/internals/core-code-style.md b/docs/internals/core-code-style.md index 2a3eef4..4cb1c45 100644 --- a/docs/internals/core-code-style.md +++ b/docs/internals/core-code-style.md @@ -125,7 +125,7 @@ class Foo `public` modifiers. `var` is not allowed. - Opening brace of a function should be on the line after the function declaration. -``` +```php /** * Documentation */ @@ -270,9 +270,9 @@ Use [guard conditions](http://refactoring.com/catalog/replaceNestedConditionalWi ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } else { - // process result + // process result } ``` @@ -281,7 +281,7 @@ is better as ```php $result = $this->getResult(); if (empty($result)) { - return true; + return true; } // process result diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index cc2f0f3..d6c9279 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -130,7 +130,7 @@ Failing unit tests as issue description are also accepted. ### 5. Update the CHANGELOG Edit the CHANGELOG file to include your change, you should insert this at the top of the file under the -"Work in progress" heading, the line in the change log should look like one of the following: +first heading (the version that is currently under development), the line in the change log should look like one of the following: ``` Bug #999: a description of the bug fix (Your Name) diff --git a/framework/BaseYii.php b/framework/BaseYii.php index 3e396a7..0f99597 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -93,7 +93,7 @@ class BaseYii */ public static function getVersion() { - return '2.0.8-dev'; + return '2.0.9-dev'; } /** @@ -343,7 +343,7 @@ class BaseYii unset($type['class']); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { - return call_user_func($type, $params); + return static::$container->invoke($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } else { diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4a0b810..ca007d6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -7,79 +7,112 @@ Yii Framework 2 Change Log - Removed methods marked as deprected in 2.0.x (samdark) 2.0.8 under development +2.0.9 under development ----------------------- -- Bug #9935: Fixed `yii\validators\EachValidator` does not invoke `validateAttribute()` method of the embedded validator (klimov-paul) -- Bug #11270: Fixed `BaseActiveRecord::link()` method in order to support closure in `indexBy` for relations declaration (iushev) -- Bug #11333: Avoid serializing PHP 7 errors (zuozp8) -- Bug #11262: Enabled use of yii2 inside of PHAR packaged console applications (hiqsol) -- Bug #11196: Fixed VarDumper throws PHP Fatal when dumping `__PHP_Incomplete_Class` (DamianZ) +- Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) + + +2.0.8 April 28, 2016 +-------------------- + - Bug #7627: Fixed `yii\widgets\ActiveField` to handle inputs AJAX validation with changed ID properly (dizeee) +- Bug #7717: Fixed the bug that `$properties` parameter in `ArrayHelper::toArray()` was not passed to recursive calls (quantum13) +- Bug #9074: Fixed JS call `$('#grid').yiiGridView('getSelectedRows')` when `GridView::$showHeader` is set to false (NekitoSP, silverfire) - Bug #9851: Fixed partial commit / rollback in nested transactions (sammousa) +- Bug #9935: Fixed `yii\validators\EachValidator` does not invoke `validateAttribute()` method of the embedded validator (klimov-paul) +- Bug #10201: Fixed bug in ActiveRecord, where relational data could not be fetched in case with composite key and join with IN condition (PaulVanSchayck, airmoi, joe-meyer, cebe) +- Bug #10235: Fixed `yii\console\Application::runAction` to not to corrupt response object (hiqsol) - Bug #10480: Fixed removing old identity cookie when loggin in as another user without logging out first (maine-mike) - Bug #10617: Fixed `yii\web\Request::getBodyParams()` returned `null` instead of empty array if request body is empty and content type is application/json (samdark) - Bug #10784: Fixed `yii\grid\CheckboxColumn` to set correct value when `yii\grid\CheckboxColumn::$checkboxOptions` closure is used (nukkumatti) - Bug #10850: Fixed unable to use 'definitions' and 'aliases' at `yii\widgets\MaskedInput` (rahimov, klimov-paul) - Bug #10884: Fixed MessageFormatter for formatting messages when not all parameters are given (laxity7, cebe) -- Enh #10910: Fixed Captcha client side validation after image refresh, when controller is under module (silverfire) - Bug #10935: Fixed cache key collision in `yii\web\UrlManager::createUrl()` (sammousa) - Bug #10946: Fixed parameters binding to the SQL query in `yii\db\mysqlSchema::findConstraints()` (silverfire) - Bug #10969: Fixed generator migration tool with decimal params in column (pana1990) - Bug #10974: `yii.js` - fixed error in ajaxPrefilter event handler, caused by blocked frame (maximal) +- Bug #11012: Fixed `yii\web\UploadedFile::getBaseName()` to work with UTF-8 file names (hiscaler, silverfire) +- Bug #11026: Fixed `StringHelper::truncateWords()` to count words properly for non-English text (samdark, tol17) - Bug #11038: Fixed handling of intervals of 0 seconds in `yii\i18n\Formatter::asDuration()` (VirtualRJ) +- Bug #11040: Check parameter 'recursive' and disable recursive copying with option 'recursive' => false in method BaseFileHelper::copyDirectory (Ni-san) - Bug #11052: Fixed `HtmlPurifier` configuration sequence (samdark) - Bug #11066: `yii.js` - fixed `getQueryParams()` function to handle URLs with anchors correctly (DrDeath72) +- Bug #11088: Fixed bug with column name not being quoted correctly, when a quoted table name or a table name in prefix syntax was used (cebe, edgardmessias, Ni-san) - Bug #11093: Fixed `yii\db\QueryBuilder::buildAndCondition()` to add query params passed directly by `yii\db\Expression` (CedricYii, silverfire) -- Bug #11012: Fixed `yii\web\UploadedFile::getBaseName()` to work with UTF-8 file names (hiscaler, silverfire) -- Bug #11026: Fixed `StringHelper::truncateWords()` to count words properly for non-English text (samdark, tol17) -- Bug #11040: Check parameter 'recursive' and disable recursive copying with option 'recursive' => false in method BaseFileHelper::copyDirectory (Ni-san) - Bug #11125: Fixed `JSON_ERROR_SYNTAX` for `json_decode(null)` in PHP 7 (fps01) - Bug #11132: Fixed `yii\widgets\FragmentCache` not handling empty content correctly in all cases (kidol) - Bug #11188: Fixed wrong index usage in `CaptchaAction` when calling `imagefilledrectangle` (alsopub) +- Bug #11196: Fixed VarDumper throws PHP Fatal when dumping `__PHP_Incomplete_Class` (DamianZ) - Bug #11220: NumberValidator now handles objects properly (samdark) - Bug #11221: Boolean validator generates incorrect error message (azaikin, githubjeka) - Bug #11223: Fixed returning an empty array when DbManager::getRolesByUser() was called on a user with user id 0 (VirtualRJ) - Bug #11228: `yii.activeForm.js` - AJAX validation will not be triggered if client side validation failed (silverfire) +- Bug #11262: Enabled use of yii2 inside of PHAR packaged console applications (hiqsol) +- Bug #11270: Fixed `BaseActiveRecord::link()` method in order to support closure in `indexBy` for relations declaration (iushev) - Bug #11280: Descendants of `yii\console\controllers\BaseMigrateController`, like the one for MongoDB, unable to create new migration (klimov-paul) +- Bug #11333: Avoid serializing PHP 7 errors (zuozp8) +- Bug #11425: Fixed namespace conflict with Markdown helper and Console helper (cebe, mdmunir) - Bug: SQlite querybuilder did not create primary key with bigint for `TYPE_BIGPK` (cebe) - Enh #5469: Add mimetype validation by mask in FileValidator (kirsenn, samdark, silverfire) +- Enh #7177, #10165: Added support for validating datetime and time values using intl short format to `DateValidator` (VirtualRJ, cebe) - Enh #8145, #8139, #10234 #11153: `yii\validators\Validator::$attributes` property now supports `!attribute` notation to validate attribute, but do not mark it as safe (mdmunir) +- Enh #8148: Implemented ability to add comment on table and column in migration (vaseninm, silverfire) +- Enh #8505: `yii\db\Query` now contains a andFilterCompare() method that allows filtering using operators in the query value (lennartvdd) - Enh #8602: `yii\validators\DateValidator` skip validation for `timestampAttribute`, if it is already in correct format (klimov-paul) - Enh #8639: Improve ActiveRecord to not create new instances of classes when objects are available (cebe) - Enh #8779: Automatically set enctype form option when using file input field (pana1990, arogachev) - Enh #9340: Adds `after()` and `first()` column schema builder modifiers (df2) +- Enh #9425: `yii\db\Query::exists()` now uses SQL standard `EXISTS()` query via new `yii\db\QueryBuilder::selectExists()` method to improving performance in some cases (PowerGamer1) - Enh #9562: Adds `char` datatype to framework (df2) +- Enh #9604: `yii\db\BaseActiveRecord` now triggers event `EVENT_AFTER_REFRESH` after a record is refreshed (raoul2000) - Enh #9893: `yii.js` handleAction enhanced to support for data-form attribute, so links can trigger specific forms (SamMousa) +- Enh #10309: Extracted `yii\web\UrlManager` rule cache key into `$cacheKey` protected property (lordthorzonus) +- Enh #10322: ActiveForm now respects formtarget attribute of submit button (AnatolyRugalev) - Enh #10451: Check of existence of `$_SERVER` in `\yii\web\Request` before using it (quantum13) +- Enh #10475: Extracted `getUrlFromCache()` and `setRuleToCache()` protected methods from `yii\web\UrlManager::createUrl()` (dmdark) - Enh #10487: `yii\helpers\BaseArrayHelper::index()` got a third parameter `$groupBy` to group the input array by the key in one or more dimensions (quantum13, silverfire, samdark) - Enh #10610: Added `BaseUrl::$urlManager` to be able to set URL manager used for creating URLs (samdark) +- Enh #10631: Splitted gettng label and rendering cell in `yii\grid\DataColumn::renderHeaderCellContent()` to make code simpler (t-kanstantsin, samdark) +- Enh #10710: `yii\helpers\FileHelper::copyDirectory()` is now throwing exception when trying to copy a directory to itself or a subdirectory (wallysalami, cebe, samdark) - Enh #10764: `yii\helpers\Html::tag()` and `::beginTag()` return content without any HTML when the `$tag` attribute is `false` or `null` (pana1990) - Enh #10840: Added `yii\console\Controller::optionAliases()` method to support aliases for commands (pana1990) - Enh #10889: Allows unsigned primary key column definitions (df2) +- Enh #10908: Added Dependency Injection for Closure configuration (SamMousa) +- Enh #10910: Fixed Captcha client side validation after image refresh, when controller is under module (silverfire) - Enh #10921: `__toString()` of column schema builder now adapts to column types (df2) +- Enh #10931: Removed hard dependency of `yii\di\Container` on `Yii::$app` (SamMousa) - Enh #10937: `yii\web\User` will now confirm the request accepts an HTML response before redirecting to the login page. Added optional `$checkAcceptHeader` to `yii\web\User::loginRequired()` (sammousa) - Enh #10941: Added `yii\helpers\ArrayHelper::isTraversable`, added support for traversable selections for dropdownList, radioList and checkboxList in `yii\helpers\Html` (sammousa) +- Enh #10941: Added `yii\helpers\ArrayHelper::isTraversable`, added support for traversable selections for dropdownList, radioList and checkboxList in `yii\helpers\Html`. +- Enh #10954: `yii\db\QueryBuilder` now accepts `\Traversable` objects for `in` condition (SamMousa, silverfire) +- Enh #10967: Simplified Javascript on the exception debug page (SamMousa) +- Enh #10976: `Inflector::transliterate()` now uses `strtr` instead of `str_replace` (DrDeath72) +- Enh #11002: `AttributeBehavior::$skipUpdateOnClean` which determines whether to skip a behavior when the behavior owner has not been modified (Faryshta) +- Enh #11056: Allow setting a custom logger configuration for `yii\log\Dispatcher` in configuration (bionoren, cebe) +- Enh #11058: `yii\web\User::loginRequired()` now does not set return URL when request method is not GET (dawei101, silverfire) +- Enh #11110: Added migrations for DB session (mdmunir) - Enh #11137: Added weak ETag support to `yii\filters\HttpCache`. It could be turned on via setting `$weakEtag` to `true` (particleflux) - Enh #11139: `yii\validators\EachValidator` injects specific attribute value in error message parameters (silverfire) +- Enh #11166: migrate command new option `useTablePrefix` (Faryshta) - Enh #11187: migrate command now generates phpdoc for table migrations (Faryshta) +- Enh #11207: migrate command can create foreign keys. (Faryshta) - Enh #11254: Added ability to attach RBAC rule using class name (mdmunir) - Enh #11285: `yii\base\Security` enhancements (tom--, samdark) - Avoid reading more bytes than needed from `/dev/urandom` and `/dev/random`. - Pefer `/dev/random` to `/dev/urandom` when running on FreeBSD. - Better RNG performance. +- Enh #11336: Allow resettting `$hostInfo`, `$scriptUrl`, and `$pathInfo` in `yii\web\Request` and `$baseUrl`, and `$hostInfo` in `yii\web\UrlManager` to `null`, to make Yii determine the value again (cebe) - Enh: Added `StringHelper::countWords()` that given a string returns number of words in it (samdark) -- Enh #11207: migrate command can create foreign keys. (Faryshta) -- Enh #11166: migrate command new option `useTablePrefix` (Faryshta) -- Enh #11002: `AttributeBehavior::$skipUpdateOnClean` which determines whether to skip a behavior when the behavior owner has not been modified (Faryshta) -- Chg #11283: `ActiveRecord::unlink()` is not setting FK to `null` before deleting itself anymore (samdark) -- Eng #10976: `Inflector::transliterate()` now uses `strtr` instead of `str_replace` (DrDeath72) -- Enh #11110: Added migrations for DB session (mdmunir) -- Chg: HTMLPurifier dependency updated to `~4.6` (samdark) +- Chg #9854: Added `ActiveRecordInterface::populateRelation()` to respect the methods called by the implementation (SamMousa) - Chg #10726: Added `yii\rbac\ManagerInterface::canAddChild()` (dkhlystov, samdark) - Chg #10921: Inverts responsibility of database specific column schema builder classes (df2) - Chg #11071: `yii\helpers\BaseArrayHelper::isIn()` and `isTraversable()` since now throw `\yii\base\InvalidParamException` instead of `\InvalidArgumentException` (nukkumatti) +- Chg #11283: `ActiveRecord::unlink()` is not setting FK to `null` before deleting itself anymore (samdark) +- Chg: HTMLPurifier dependency updated to `~4.6` (samdark) - New #8920: Added `yii\mutex\PgsqlMutex` which implements mutex "lock" mechanism via PgSQL locks (nineinchnick, CSharpRU) + 2.0.7 February 14, 2016 ----------------------- diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 0a23246..188341f 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -8,30 +8,40 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to follow the instructions for both A and B. -Make sure you have global install of latest version of composer asset plugin: +Make sure you have global install of latest version of composer asset plugin as well as a stable version of composer: ``` +php composer.phar self-update php composer.phar global require "fxp/composer-asset-plugin:~1.1.1" ``` Upgrade from Yii 2.0.7 -______________________ +---------------------- * The signature of `yii\helpers\BaseArrayHelper::index()` was changed. The method has got an extra optional parameter `$groups`. + * `yii\helpers\BaseArrayHelper` methods `isIn()` and `isSubset()` throw `\yii\base\InvalidParamException` instead of `\InvalidArgumentException`. If you wrap calls of these methods in try/catch block, change expected exception class. + * `yii\rbac\ManagerInterface::canAddChild()` method was added. If you have custom backend for RBAC you need to implement it. + * The signature of `yii\web\User::loginRequired()` was changed. The method has got an extra optional parameter `$checkAcceptHeader`. + * The signature of `yii\db\ColumnSchemaBuilder::__construct()` was changed. The method has got an extra optional parameter `$db`. In case you are instantiating this class yourself and using the `$config` parameter, you will need to move it to the right by one. + * String types in the MSSQL column schema map were upgraded to Unicode storage types. This will have no effect on existing columns, but any new columns you generate via the migrations engine will now store data as Unicode. +* Output buffering was introduced in the pair of `yii\widgets\ActiveForm::init()` and `::run()`. If you override any of + these methods, make sure that output buffer handling is not corrupted. If you call the parent implementation, when + overriding, everything should work fine. You should be doing that anyway. + Upgrade from Yii 2.0.6 ---------------------- @@ -80,7 +90,6 @@ Upgrade from Yii 2.0.6 setting a lot of configuration options via command line. If you extend from this class, make sure it works as expected after these changes. - Upgrade from Yii 2.0.5 ---------------------- @@ -161,7 +170,6 @@ Upgrade from Yii 2.0 RC Quoting of values is broken in prior versions and Yii has no reliable way to work around this issue. A workaround that may have worked before has been removed in this release because it was not reliable. - Upgrade from Yii 2.0 Beta ------------------------- diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 7b73763..4ce37e7 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -201,7 +201,8 @@ settings: settings, attributes: attributes, submitting: false, - validated: false + validated: false, + target: $form.attr('target') }); /** @@ -575,7 +576,14 @@ data.submitting = false; } else { data.validated = true; + var buttonTarget = data.submitObject ? data.submitObject.attr('formtarget') : null; + if (buttonTarget) { + // set target attribute to form tag before submit + $form.attr('target', buttonTarget); + } $form.submit(); + // restore original target attribute value + $form.attr('target', data.target); } } else { $.each(data.attributes, function () { diff --git a/framework/assets/yii.gridView.js b/framework/assets/yii.gridView.js index 38964a6..8306666 100644 --- a/framework/assets/yii.gridView.js +++ b/framework/assets/yii.gridView.js @@ -55,7 +55,12 @@ return this.each(function () { var $e = $(this); var settings = $.extend({}, defaults, options || {}); - gridData[$e.attr('id')] = {settings: settings}; + var id = $e.attr('id'); + if (gridData[id] === undefined) { + gridData[id] = {}; + } + + gridData[id] = $.extend(gridData[id], {settings: settings}); var enterPressed = false; $(document).off('change.yiiGridView keydown.yiiGridView', settings.filterSelector) @@ -142,8 +147,11 @@ setSelectionColumn: function (options) { var $grid = $(this); var id = $(this).attr('id'); + if (gridData.id === undefined) { + gridData[id] = {}; + } gridData[id].selectionColumn = options.name; - if (!options.multiple) { + if (!options.multiple || !options.checkAll) { return; } var checkAll = "#" + id + " input[name='" + options.checkAll + "']"; diff --git a/framework/base/Security.php b/framework/base/Security.php index 25fd7b2..91bd8d5 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -428,10 +428,6 @@ class Security extends Component */ public function generateRandomKey($length = 32) { - if (function_exists('random_bytes')) { - return random_bytes($length); - } - if (!is_int($length)) { throw new InvalidParamException('First parameter ($length) must be an integer'); } @@ -440,6 +436,11 @@ class Security extends Component throw new InvalidParamException('First parameter ($length) must be greater than 0'); } + // always use random_bytes() if it is available + if (function_exists('random_bytes')) { + return random_bytes($length); + } + // The recent LibreSSL RNGs are faster and likely better than /dev/urandom. // Parse OPENSSL_VERSION_TEXT because OPENSSL_VERSION_NUMBER is no use for LibreSSL. // https://bugs.php.net/bug.php?id=71143 @@ -489,9 +490,9 @@ class Security extends Component $this->_randomFile = fopen($device, 'rb') ?: null; if (is_resource($this->_randomFile)) { - // By default PHP buffer size is 8192 bytes which causes wasting - // more entropy that we're actually using. Therefore setting it to - // lower value. + // Reduce PHP stream buffer from default 8192 bytes to optimize data + // transfer from the random device for smaller values of $length. + // This also helps to keep future randoms out of user memory space. $bufferSize = 8; if (function_exists('stream_set_read_buffer')) { @@ -514,7 +515,7 @@ class Security extends Component break; } $buffer .= $someBytes; - $stillNeed -= StringHelper::byteLength($buffer); + $stillNeed -= StringHelper::byteLength($someBytes); if ($stillNeed === 0) { // Leaving file pointer open in order to make next generation faster by reusing it. return $buffer; diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php index dfabe03..f24d306 100644 --- a/framework/behaviors/AttributeBehavior.php +++ b/framework/behaviors/AttributeBehavior.php @@ -62,7 +62,6 @@ class AttributeBehavior extends Behavior * ``` */ public $attributes = []; - /** * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function, * callable in array format (e.g. `[$this, 'methodName']`), an [[Expression]] object representing a DB expression @@ -78,7 +77,6 @@ class AttributeBehavior extends Behavior * ``` */ public $value; - /** * @var boolean whether to skip this behavior when the `$owner` has not been * modified @@ -86,6 +84,7 @@ class AttributeBehavior extends Behavior */ public $skipUpdateOnClean = true; + /** * @inheritdoc */ diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php index 3b595d2..e8d7817 100644 --- a/framework/behaviors/TimestampBehavior.php +++ b/framework/behaviors/TimestampBehavior.php @@ -29,7 +29,7 @@ use yii\db\BaseActiveRecord; * By default, TimestampBehavior will fill the `created_at` and `updated_at` attributes with the current timestamp * when the associated AR object is being inserted; it will fill the `updated_at` attribute * with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`. - * + * * For the above implementation to work with MySQL database, please declare the columns(`created_at`, `updated_at`) as int(11) for being UNIX timestamp. * * If your attribute names are different or you want to use a different way of calculating the timestamp, diff --git a/framework/classes.php b/framework/classes.php index d11666e..af49488 100644 --- a/framework/classes.php +++ b/framework/classes.php @@ -113,22 +113,23 @@ return [ 'yii\db\StaleObjectException' => YII2_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII2_PATH . '/db/TableSchema.php', 'yii\db\Transaction' => YII2_PATH . '/db/Transaction.php', + 'yii\db\cubrid\ColumnSchemaBuilder' => YII2_PATH . '/db/cubrid/ColumnSchemaBuilder.php', 'yii\db\cubrid\QueryBuilder' => YII2_PATH . '/db/cubrid/QueryBuilder.php', 'yii\db\cubrid\Schema' => YII2_PATH . '/db/cubrid/Schema.php', - 'yii\db\mssql\ColumnSchemaBuilder' => YII2_PATH . '/db/mssql/ColumnSchemaBuilder.php', 'yii\db\mssql\PDO' => YII2_PATH . '/db/mssql/PDO.php', 'yii\db\mssql\QueryBuilder' => YII2_PATH . '/db/mssql/QueryBuilder.php', 'yii\db\mssql\Schema' => YII2_PATH . '/db/mssql/Schema.php', 'yii\db\mssql\SqlsrvPDO' => YII2_PATH . '/db/mssql/SqlsrvPDO.php', 'yii\db\mssql\TableSchema' => YII2_PATH . '/db/mssql/TableSchema.php', + 'yii\db\mysql\ColumnSchemaBuilder' => YII2_PATH . '/db/mysql/ColumnSchemaBuilder.php', 'yii\db\mysql\QueryBuilder' => YII2_PATH . '/db/mysql/QueryBuilder.php', 'yii\db\mysql\Schema' => YII2_PATH . '/db/mysql/Schema.php', 'yii\db\oci\ColumnSchemaBuilder' => YII2_PATH . '/db/oci/ColumnSchemaBuilder.php', 'yii\db\oci\QueryBuilder' => YII2_PATH . '/db/oci/QueryBuilder.php', 'yii\db\oci\Schema' => YII2_PATH . '/db/oci/Schema.php', - 'yii\db\pgsql\ColumnSchemaBuilder' => YII2_PATH . '/db/pgsql/ColumnSchemaBuilder.php', 'yii\db\pgsql\QueryBuilder' => YII2_PATH . '/db/pgsql/QueryBuilder.php', 'yii\db\pgsql\Schema' => YII2_PATH . '/db/pgsql/Schema.php', + 'yii\db\sqlite\ColumnSchemaBuilder' => YII2_PATH . '/db/sqlite/ColumnSchemaBuilder.php', 'yii\db\sqlite\QueryBuilder' => YII2_PATH . '/db/sqlite/QueryBuilder.php', 'yii\db\sqlite\Schema' => YII2_PATH . '/db/sqlite/Schema.php', 'yii\di\Container' => YII2_PATH . '/di/Container.php', @@ -207,6 +208,7 @@ return [ 'yii\mutex\FileMutex' => YII2_PATH . '/mutex/FileMutex.php', 'yii\mutex\Mutex' => YII2_PATH . '/mutex/Mutex.php', 'yii\mutex\MysqlMutex' => YII2_PATH . '/mutex/MysqlMutex.php', + 'yii\mutex\PgsqlMutex' => YII2_PATH . '/mutex/PgsqlMutex.php', 'yii\rbac\Assignment' => YII2_PATH . '/rbac/Assignment.php', 'yii\rbac\BaseManager' => YII2_PATH . '/rbac/BaseManager.php', 'yii\rbac\DbManager' => YII2_PATH . '/rbac/DbManager.php', diff --git a/framework/console/Application.php b/framework/console/Application.php index 3769fe6..496b1e5 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -166,13 +166,15 @@ class Application extends \yii\base\Application * * @param string $route the route that specifies the action. * @param array $params the parameters to be passed to the action - * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. + * @return integer|Response the result of the action. This can be either an exit code or Response object. + * Exit code 0 means normal, and other values mean abnormal. Exit code of `null` is treaded as `0` as well. * @throws Exception if the route is invalid */ public function runAction($route, $params = []) { try { - return (int)parent::runAction($route, $params); + $res = parent::runAction($route, $params); + return is_object($res) ? $res : (int)$res; } catch (InvalidRouteException $e) { throw new Exception("Unknown command \"$route\".", 0, $e); } diff --git a/framework/console/controllers/BaseMigrateController.php b/framework/console/controllers/BaseMigrateController.php index 36eda05..adad1dd 100644 --- a/framework/console/controllers/BaseMigrateController.php +++ b/framework/console/controllers/BaseMigrateController.php @@ -467,6 +467,11 @@ abstract class BaseMigrateController extends Controller * * @param string $name the name of the new migration. This should only contain * letters, digits and/or underscores. + * + * Note: If the migration name is of a special form, for example create_xxx or + * drop_xxx then the generated migration file will contain extra code, + * in this case for creating/dropping tables. + * * @throws Exception if the name argument is invalid. */ public function actionCreate($name) diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php index df39885..99ec476 100644 --- a/framework/console/controllers/FixtureController.php +++ b/framework/console/controllers/FixtureController.php @@ -72,6 +72,7 @@ class FixtureController extends Controller /** * @inheritdoc + * @since 2.0.8 */ public function optionAliases() { @@ -333,6 +334,9 @@ class FixtureController extends Controller $this->outputList($except); } + $this->stdout("\nBe aware that:\n", Console::BOLD); + $this->stdout("Applying leads to purging of certain data in the database!\n", Console::FG_RED); + return $this->confirm("\nLoad above fixtures?"); } diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index fbe4f2a..29f30be 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -162,6 +162,7 @@ class MessageController extends Controller /** * @inheritdoc + * @since 2.0.8 */ public function optionAliases() { diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index 8c1c363..11f5960 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -89,11 +89,16 @@ class MigrateController extends BaseMigrateController * @since 2.0.8 */ public $useTablePrefix = false; - /** * @var array column definition strings used for creating migration code. - * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. - * For example, `--fields=name:string(12):notNull` produces a string column of size 12 which is not null. + * + * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`. + * For example, `--fields="name:string(12):notNull:unique"` + * produces a string column of size 12 which is not null and unique values. + * + * Note: primary key is added automatically and is named id by default. + * If you want to use another name you may specify it explicitly like + * `--fields="id_key:primaryKey,name:string(12):notNull:unique"` * @since 2.0.7 */ public $fields = []; @@ -121,6 +126,7 @@ class MigrateController extends BaseMigrateController /** * @inheritdoc + * @since 2.0.8 */ public function optionAliases() { @@ -228,6 +234,7 @@ class MigrateController extends BaseMigrateController /** * @inheritdoc + * @since 2.0.8 */ protected function generateMigrationSourceCode($params) { @@ -305,6 +312,7 @@ class MigrateController extends BaseMigrateController * * @param string $tableName the table name to generate. * @return string + * @since 2.0.8 */ protected function generateTableName($tableName) { diff --git a/framework/console/controllers/ServeController.php b/framework/console/controllers/ServeController.php index b1cc80f..daee73b 100644 --- a/framework/console/controllers/ServeController.php +++ b/framework/console/controllers/ServeController.php @@ -96,6 +96,7 @@ class ServeController extends Controller /** * @inheritdoc + * @since 2.0.8 */ public function optionAliases() { diff --git a/framework/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php index e69a7cd..01ea42b 100644 --- a/framework/data/BaseDataProvider.php +++ b/framework/data/BaseDataProvider.php @@ -18,10 +18,10 @@ use yii\base\InvalidParamException; * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is * uniquely identified by the corresponding key value in this array. * @property array $models The list of data models in the current page. - * @property Pagination|false $pagination The pagination object. If this is false, it means the pagination + * @property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination * is disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and * [[setPagination()]] for details. - * @property Sort|false $sort The sorting object. If this is false, it means the sorting is disabled. Note + * @property Sort|boolean $sort The sorting object. If this is false, it means the sorting is disabled. Note * that the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details. * @property integer $totalCount Total number of possible data models. * diff --git a/framework/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php index 6c0eba2..6276c3a 100644 --- a/framework/db/ActiveRecordInterface.php +++ b/framework/db/ActiveRecordInterface.php @@ -364,6 +364,15 @@ interface ActiveRecordInterface public function getRelation($name, $throwException = true); /** + * Populates the named relation with the related records. + * Note that this method does not check if the relation exists or not. + * @param string $name the relation name (case-sensitive) + * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation. + * @since 2.0.8 + */ + public function populateRelation($name, $records); + + /** * Establishes the relationship between two records. * * The relationship is established by setting the foreign key value(s) in one record diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php index 22f971f..0a31031 100644 --- a/framework/db/ActiveRelationTrait.php +++ b/framework/db/ActiveRelationTrait.php @@ -469,9 +469,15 @@ trait ActiveRelationTrait } } else { // composite keys + + // ensure keys of $this->link are prefixed the same way as $attributes + $prefixedLink = array_combine( + $attributes, + array_values($this->link) + ); foreach ($models as $model) { $v = []; - foreach ($this->link as $attribute => $link) { + foreach ($prefixedLink as $attribute => $link) { $v[$attribute] = $model[$link]; } $values[] = $v; diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index 31661a5..75272f1 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -77,6 +77,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * @event Event an event that is triggered after a record is deleted. */ const EVENT_AFTER_DELETE = 'afterDelete'; + /** + * @event Event an event that is triggered after a record is refreshed. + * @since 2.0.8 + */ + const EVENT_AFTER_REFRESH = 'afterRefresh'; /** * @var array attribute values indexed by attribute names @@ -951,6 +956,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * Repopulates this active record with the latest data. + * + * If the refresh is successful, an [[EVENT_AFTER_REFRESH]] event will be triggered. + * This event is available since version 2.0.8. + * * @return boolean whether the row still exists in the database. If true, the latest data * will be populated to this active record. Otherwise, this record will remain unchanged. */ @@ -966,11 +975,24 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface } $this->_oldAttributes = $this->_attributes; $this->_related = []; + $this->afterRefresh(); return true; } /** + * This method is called when the AR object is refreshed. + * The default implementation will trigger an [[EVENT_AFTER_REFRESH]] event. + * When overriding this method, make sure you call the parent implementation to ensure the + * event is triggered. + * @since 2.0.8 + */ + public function afterRefresh() + { + $this->trigger(self::EVENT_AFTER_REFRESH); + } + + /** * Returns a value indicating whether the given active record is the same as the current one. * The comparison is made by comparing the table names and the primary key values of the two active records. * If one of the records [[isNewRecord|is new]] they are also considered not equal. diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index bbf5f14..e50b6c8 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -70,6 +70,8 @@ class ColumnSchemaBuilder extends Object * @since 2.0.8 */ protected $isFirst; + + /** * @var array mapping of abstract column types (keys) to type categories (values). * @since 2.0.8 @@ -102,6 +104,11 @@ class ColumnSchemaBuilder extends Object * @since 2.0.8 */ public $db; + /** + * @var string comment value of the column. + * @since 2.0.8 + */ + public $comment; /** * Create a column schema builder instance giving the type and value precision. @@ -162,6 +169,18 @@ class ColumnSchemaBuilder extends Object } /** + * Specifies the comment for column. + * @param string $comment the comment + * @return $this + * @since 2.0.8 + */ + public function comment($comment) + { + $this->comment = $comment; + return $this; + } + + /** * Marks column as unsigned. * @return $this * @since 2.0.7 @@ -225,10 +244,10 @@ class ColumnSchemaBuilder extends Object { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}'; + $format = '{type}{check}{comment}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}'; } return $this->buildCompleteString($format); } @@ -348,6 +367,16 @@ class ColumnSchemaBuilder extends Object } /** + * Builds the comment specification for the column. + * @return string a string containing the COMMENT keyword and the comment itself + * @since 2.0.8 + */ + protected function buildCommentString() + { + return ''; + } + + /** * Returns the complete column definition from input format * @param string $format the format of the definition. * @return string a string containing the complete column definition. @@ -363,6 +392,7 @@ class ColumnSchemaBuilder extends Object '{unique}' => $this->buildUniqueString(), '{default}' => $this->buildDefaultString(), '{check}' => $this->buildCheckString(), + '{comment}' => $this->buildCommentString(), '{pos}' => ($this->isFirst) ? $this->buildFirstString() : $this->buildAfterString(), diff --git a/framework/db/Command.php b/framework/db/Command.php index 0e398fe..31a0dbd 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -757,6 +757,66 @@ class Command extends Component } /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for dropping comment from column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for dropping comment from table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table); + + return $this->setSql($sql); + } + + /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * No result set will be returned. diff --git a/framework/db/Migration.php b/framework/db/Migration.php index d118ce2..58bf2ca 100644 --- a/framework/db/Migration.php +++ b/framework/db/Migration.php @@ -258,7 +258,12 @@ class Migration extends Component implements MigrationInterface echo " > create table $table ..."; $time = microtime(true); $this->db->createCommand()->createTable($table, $columns, $options)->execute(); - echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + foreach ($columns as $column => $type) { + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } + } + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } /** @@ -311,6 +316,9 @@ class Migration extends Component implements MigrationInterface echo " > add column $column $type to table $table ..."; $time = microtime(true); $this->db->createCommand()->addColumn($table, $column, $type)->execute(); + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } @@ -354,6 +362,9 @@ class Migration extends Component implements MigrationInterface echo " > alter column $column in table $table to $type ..."; $time = microtime(true); $this->db->createCommand()->alterColumn($table, $column, $type)->execute(); + if ($type instanceof ColumnSchemaBuilder && $type->comment !== null) { + $this->db->createCommand()->addCommentOnColumn($table, $column, $type->comment)->execute(); + } echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } @@ -446,4 +457,68 @@ class Migration extends Component implements MigrationInterface $this->db->createCommand()->dropIndex($name, $table)->execute(); echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; } + + /** + * Builds and execute a SQL statement for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + echo " > add comment on column $column ..."; + $time = microtime(true); + $this->db->createCommand()->addCommentOnColumn($table, $column, $comment)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + echo " > add comment on table $table ..."; + $time = microtime(true); + $this->db->createCommand()->addCommentOnTable($table, $comment)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and execute a SQL statement for dropping comment from column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + echo " > drop comment from column $column ..."; + $time = microtime(true); + $this->db->createCommand()->dropCommentFromColumn($table, $column)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for dropping comment from table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return $this the command object itself + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + echo " > drop comment from table $table ..."; + $time = microtime(true); + $this->db->createCommand()->dropCommentFromTable($table)->execute(); + echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } } diff --git a/framework/db/Query.php b/framework/db/Query.php index d706f1d..2c099c0 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -360,11 +360,11 @@ class Query extends Component implements QueryInterface */ public function exists($db = null) { - $select = $this->select; - $this->select = [new Expression('1')]; $command = $this->createCommand($db); - $this->select = $select; - return $command->queryScalar() !== false; + $params = $command->params; + $command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql())); + $command->bindValues($params); + return (boolean)$command->queryScalar(); } /** @@ -582,6 +582,43 @@ class Query extends Component implements QueryInterface } /** + * Adds a filtering condition for a specific column and allow the user to choose a filter operator. + * + * It adds an additional WHERE condition for the given field and determines the comparison operator + * based on the first few characters of the given value. + * The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * The comparison operator is intelligently determined based on the first few characters in the given value. + * In particular, it recognizes the following operators if they appear as the leading characters in the given value: + * + * - `<`: the column must be less than the given value. + * - `>`: the column must be greater than the given value. + * - `<=`: the column must be less than or equal to the given value. + * - `>=`: the column must be greater than or equal to the given value. + * - `<>`: the column must not be the same as the given value. + * - `=`: the column must be equal to the given value. + * - If none of the above operators is detected, the `$defaultOperator` will be used. + * + * @param string $name the column name. + * @param string $value the column value optionally prepended with the comparison operator. + * @param string $defaultOperator The operator to use, when no operator is given in `$value`. + * Defaults to `=`, performing an exact match. + * @return $this The query object itself + * @since 2.0.8 + */ + public function andFilterCompare($name, $value, $defaultOperator = '=') + { + if (preg_match("/^(<>|>=|>|<=|<|=)/", $value, $matches)) { + $operator = $matches[1]; + $value = substr($value, strlen($operator)); + } else { + $operator = $defaultOperator; + } + return $this->andFilterWhere([$operator, $name, $value]); + } + + /** * Appends a JOIN part to the query. * The first parameter specifies what type of join it is. * @param string $type the type of join, such as INNER JOIN, LEFT JOIN. diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 3b89742..b069505 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -9,6 +9,7 @@ namespace yii\db; use yii\base\InvalidParamException; use yii\base\NotSupportedException; +use yii\helpers\ArrayHelper; /** * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object. @@ -569,6 +570,59 @@ class QueryBuilder extends \yii\base\Object } /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment); + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method. + * @return string the SQL statement for adding comment on table + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment); + } + + /** + * Builds a SQL command for adding comment to column + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @param string $column the name of the column to be commented. The column name will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL'; + } + + /** + * Builds a SQL command for adding comment to table + * + * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method. + * @return string the SQL statement for adding comment on column + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL'; + } + + /** * Converts an abstract column type into a physical column type. * The conversion is done using the type map specified in [[typeMap]]. * The following abstract column types are supported (using MySQL as an example to explain the corresponding @@ -970,7 +1024,7 @@ class QueryBuilder extends \yii\base\Object { $parts = []; foreach ($condition as $column => $value) { - if (is_array($value) || $value instanceof Query) { + if (ArrayHelper::isTraversable($value) || $value instanceof Query) { // IN condition $parts[] = $this->buildInCondition('IN', [$column, $value], $params); } else { @@ -1112,7 +1166,7 @@ class QueryBuilder extends \yii\base\Object list($column, $values) = $operands; - if ($values === [] || $column === []) { + if ($column === []) { return $operator === 'IN' ? '0=1' : ''; } @@ -1120,41 +1174,46 @@ class QueryBuilder extends \yii\base\Object return $this->buildSubqueryInCondition($operator, $column, $values, $params); } - $values = (array) $values; - - if (count($column) > 1) { + if ($column instanceof \Traversable || count($column) > 1) { return $this->buildCompositeInCondition($operator, $column, $values, $params); } if (is_array($column)) { $column = reset($column); } + + $sqlValues = []; foreach ($values as $i => $value) { - if (is_array($value)) { + if (is_array($value) || $value instanceof \ArrayAccess) { $value = isset($value[$column]) ? $value[$column] : null; } if ($value === null) { - $values[$i] = 'NULL'; + $sqlValues[$i] = 'NULL'; } elseif ($value instanceof Expression) { - $values[$i] = $value->expression; + $sqlValues[$i] = $value->expression; foreach ($value->params as $n => $v) { $params[$n] = $v; } } else { $phName = self::PARAM_PREFIX . count($params); $params[$phName] = $value; - $values[$i] = $phName; + $sqlValues[$i] = $phName; } } + + if (empty($sqlValues)) { + return $operator === 'IN' ? '0=1' : ''; + } + if (strpos($column, '(') === false) { $column = $this->db->quoteColumnName($column); } - if (count($values) > 1) { - return "$column $operator (" . implode(', ', $values) . ')'; + if (count($sqlValues) > 1) { + return "$column $operator (" . implode(', ', $sqlValues) . ')'; } else { $operator = $operator === 'IN' ? '=' : '<>'; - return $column . $operator . reset($values); + return $column . $operator . reset($sqlValues); } } @@ -1189,7 +1248,7 @@ class QueryBuilder extends \yii\base\Object * Builds SQL for IN condition * * @param string $operator - * @param array $columns + * @param array|\Traversable $columns * @param array $values * @param array $params * @return string SQL @@ -1210,13 +1269,17 @@ class QueryBuilder extends \yii\base\Object } $vss[] = '(' . implode(', ', $vs) . ')'; } + + if (empty($vss)) { + return $operator === 'IN' ? '0=1' : ''; + }; + + $sqlColumns = []; foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } + $sqlColumns[] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column; } - return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; + return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')'; } /** @@ -1340,4 +1403,15 @@ class QueryBuilder extends \yii\base\Object return "$column $operator $phName"; } } + + /** + * Creates a SELECT EXISTS() SQL statement. + * @param string $rawSql the subquery in a raw form to select from. + * @return string the SELECT EXISTS() SQL statement. + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT EXISTS(' . $rawSql . ')'; + } } diff --git a/framework/db/Schema.php b/framework/db/Schema.php index b9224ce..bea7d2f 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -522,7 +522,7 @@ abstract class Schema extends Object */ public function quoteColumnName($name) { - if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { + if (strpos($name, '(') !== false || strpos($name, '[[') !== false) { return $name; } if (($pos = strrpos($name, '.')) !== false) { @@ -531,7 +531,9 @@ abstract class Schema extends Object } else { $prefix = ''; } - + if (strpos($name, '{{') !== false) { + return $name; + } return $prefix . $this->quoteSimpleColumnName($name); } diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php index 49e431e..63595ed 100644 --- a/framework/db/Transaction.php +++ b/framework/db/Transaction.php @@ -37,6 +37,7 @@ use yii\base\InvalidConfigException; * one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a string * containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is * write-only. + * @property integer $level The current nesting level of the transaction. This property is read-only. * * @author Qiang Xue * @since 2.0 diff --git a/framework/db/cubrid/ColumnSchemaBuilder.php b/framework/db/cubrid/ColumnSchemaBuilder.php index 2efeaed..c39e4b7 100644 --- a/framework/db/cubrid/ColumnSchemaBuilder.php +++ b/framework/db/cubrid/ColumnSchemaBuilder.php @@ -31,7 +31,7 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder protected function buildAfterString() { return $this->after !== null ? - ' AFTER (' . $this->db->quoteColumnName($this->after) . ')' : + ' AFTER ' . $this->db->quoteColumnName($this->after) : ''; } @@ -46,17 +46,25 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder /** * @inheritdoc */ + protected function buildCommentString() + { + return $this->comment !== null ? " COMMENT " . $this->db->quoteValue($this->comment) : ''; + } + + /** + * @inheritdoc + */ public function __toString() { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{pos}'; + $format = '{type}{check}{pos}{comment}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{pos}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{pos}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php index 3b1e4c0..3ccee24 100644 --- a/framework/db/cubrid/QueryBuilder.php +++ b/framework/db/cubrid/QueryBuilder.php @@ -8,6 +8,7 @@ namespace yii\db\cubrid; use yii\base\InvalidParamException; +use yii\db\Exception; /** * QueryBuilder is the query builder for CUBRID databases (version 9.3.x and higher). @@ -94,4 +95,90 @@ class QueryBuilder extends \yii\db\QueryBuilder return $sql; } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END'; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $definition = $this->getColumnDefinition($table, $column); + $definition = trim(preg_replace("/COMMENT '(.*?)'/i", '', $definition)); + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' CHANGE ' . $this->db->quoteColumnName($column) + . ' ' . $this->db->quoteColumnName($column) + . (empty($definition) ? '' : ' ' . $definition) + . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return $this->addCommentOnColumn($table, $column, ''); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return $this->addCommentOnTable($table, ''); + } + + + /** + * Gets column definition. + * + * @param string $table table name + * @param string $column column name + * @return null|string the column definition + * @throws Exception in case when table does not contain column + * @since 2.0.8 + */ + private function getColumnDefinition($table, $column) + { + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->db->quoteTableName($table))->queryOne(); + if ($row === false) { + throw new Exception("Unable to find column '$column' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + $sql = preg_replace('/^[^(]+\((.*)\).*$/', '\1', $sql); + $sql = str_replace(', [', ",\n[", $sql); + if (preg_match_all('/^\s*\[(.*?)\]\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $column) { + return $matches[2][$i]; + } + } + } + return null; + } } diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 6a77a85..d6332fa 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -187,6 +187,42 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; + } + + /** * Returns an array of column names given model name * * @param string $modelClass name of the model class @@ -268,4 +304,13 @@ class QueryBuilder extends \yii\db\QueryBuilder return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')'; } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END'; + } } diff --git a/framework/db/mysql/ColumnSchemaBuilder.php b/framework/db/mysql/ColumnSchemaBuilder.php index ec484dc..8c338ec 100644 --- a/framework/db/mysql/ColumnSchemaBuilder.php +++ b/framework/db/mysql/ColumnSchemaBuilder.php @@ -31,7 +31,7 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder protected function buildAfterString() { return $this->after !== null ? - ' AFTER (' . $this->db->quoteColumnName($this->after) . ')' : + ' AFTER ' . $this->db->quoteColumnName($this->after) : ''; } @@ -46,17 +46,25 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder /** * @inheritdoc */ + protected function buildCommentString() + { + return $this->comment !== null ? " COMMENT " . $this->db->quoteValue($this->comment) : ''; + } + + /** + * @inheritdoc + */ public function __toString() { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{length}{check}{pos}'; + $format = '{type}{length}{check}{comment}{pos}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{pos}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{pos}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index e1b8de2..065c461 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -45,6 +45,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_MONEY => 'decimal(19,4)', ]; + /** * Builds a SQL statement for renaming a column. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. @@ -219,4 +220,79 @@ class QueryBuilder extends \yii\db\QueryBuilder . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + $definition = $this->getColumnDefinition($table, $column); + $definition = trim(preg_replace("/COMMENT '(.*?)'/i", '', $definition)); + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' CHANGE ' . $this->db->quoteColumnName($column) + . ' ' . $this->db->quoteColumnName($column) + . (empty($definition) ? '' : ' ' . $definition) + . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' COMMENT ' . $this->db->quoteValue($comment); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return $this->addCommentOnColumn($table, $column, ''); + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return $this->addCommentOnTable($table, ''); + } + + + /** + * Gets column definition. + * + * @param string $table table name + * @param string $column column name + * @return null|string the column definition + * @throws Exception in case when table does not contain column + */ + private function getColumnDefinition($table, $column) + { + $quotedTable = $this->db->quoteTableName($table); + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne(); + if ($row === false) { + throw new Exception("Unable to find column '$column' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $column) { + return $matches[2][$i]; + } + } + } + return null; + } } diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index fec034f..1b67be2 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -52,6 +52,7 @@ class Schema extends \yii\db\Schema 'enum' => self::TYPE_STRING, ]; + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. diff --git a/framework/db/oci/ColumnSchemaBuilder.php b/framework/db/oci/ColumnSchemaBuilder.php index 8a54384..b44665b 100644 --- a/framework/db/oci/ColumnSchemaBuilder.php +++ b/framework/db/oci/ColumnSchemaBuilder.php @@ -13,6 +13,7 @@ use yii\db\ColumnSchemaBuilder as AbstractColumnSchemaBuilder; * ColumnSchemaBuilder is the schema builder for Oracle databases. * * @author Vasenin Matvey + * @author Chris Harris * @since 2.0.6 */ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder @@ -31,7 +32,7 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder protected function buildAfterString() { return $this->after !== null ? - ' AFTER (' . $this->db->quoteColumnName($this->after) . ')' : + ' AFTER ' . $this->db->quoteColumnName($this->after) : ''; } diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index bc9af85..2d43483 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -46,6 +46,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_MONEY => 'NUMBER(19,4)', ]; + /** * @inheritdoc */ @@ -259,4 +260,31 @@ EOD; return 'INSERT ALL ' . $tableAndColumns . implode($tableAndColumns, $values) . ' SELECT 1 FROM SYS.DUAL'; } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function selectExists($rawSql) + { + return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM DUAL'; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . " IS ''"; + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . " IS ''"; + } } diff --git a/framework/db/sqlite/ColumnSchemaBuilder.php b/framework/db/sqlite/ColumnSchemaBuilder.php index 15396ba..956170a 100644 --- a/framework/db/sqlite/ColumnSchemaBuilder.php +++ b/framework/db/sqlite/ColumnSchemaBuilder.php @@ -41,6 +41,7 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder default: $format = '{type}{length}{notnull}{unique}{check}{default}'; } + return $this->buildCompleteString($format); } } diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 7c6ad98..1847b30 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -48,6 +48,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_MONEY => 'decimal(19,4)', ]; + /** * Generates a batch INSERT SQL statement. * For example, @@ -292,6 +293,46 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function addCommentOnColumn($table, $column, $comment) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function addCommentOnTable($table, $comment) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function dropCommentFromColumn($table, $column) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + * @throws NotSupportedException + * @since 2.0.8 + */ + public function dropCommentFromTable($table) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc */ public function buildLimit($limit, $offset) { diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php index e2cdb40..21af58d 100644 --- a/framework/db/sqlite/Schema.php +++ b/framework/db/sqlite/Schema.php @@ -92,6 +92,15 @@ class Schema extends \yii\db\Schema } /** + * @inheritdoc + * @return ColumnSchemaBuilder column schema builder instance + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length); + } + + /** * Returns all table names in the database. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. * @return array all table names in the database. The names have NO schema name prefix. @@ -281,12 +290,4 @@ class Schema extends \yii\db\Schema throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.'); } } - - /** - * @inheritdoc - */ - public function createColumnSchemaBuilder($type, $length = null) - { - return new ColumnSchemaBuilder($type, $length); - } } diff --git a/framework/di/Container.php b/framework/di/Container.php index d0c0a1c..c312937 100644 --- a/framework/di/Container.php +++ b/framework/di/Container.php @@ -524,7 +524,7 @@ class Container extends Component unset($params[$name]); } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) { $args[] = array_shift($params); - } elseif (Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { + } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { $args[] = $obj; } else { $args[] = $this->get($className); diff --git a/framework/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php index 3b66478..9b5a17d 100644 --- a/framework/grid/CheckboxColumn.php +++ b/framework/grid/CheckboxColumn.php @@ -10,6 +10,7 @@ namespace yii\grid; use Closure; use yii\base\InvalidConfigException; use yii\helpers\Html; +use yii\helpers\Json; /** * CheckboxColumn displays a column of checkboxes in a grid view. @@ -81,6 +82,8 @@ class CheckboxColumn extends Column if (substr_compare($this->name, '[]', -2, 2)) { $this->name .= '[]'; } + + $this->registerClientScript(); } /** @@ -91,28 +94,10 @@ class CheckboxColumn extends Column */ protected function renderHeaderCellContent() { - $name = $this->name; - if (substr_compare($name, '[]', -2, 2) === 0) { - $name = substr($name, 0, -2); - } - if (substr_compare($name, ']', -1, 1) === 0) { - $name = substr($name, 0, -1) . '_all]'; - } else { - $name .= '_all'; - } - - $id = $this->grid->options['id']; - $options = json_encode([ - 'name' => $this->name, - 'multiple' => $this->multiple, - 'checkAll' => $name, - ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); - if ($this->header !== null || !$this->multiple) { return parent::renderHeaderCellContent(); } else { - return Html::checkbox($name, false, ['class' => 'select-on-check-all']); + return Html::checkbox($this->getHeaderCheckBoxName(), false, ['class' => 'select-on-check-all']); } } @@ -128,9 +113,44 @@ class CheckboxColumn extends Column } if (!isset($options['value'])) { - $options['value'] = is_array($key) ? json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $key; + $options['value'] = is_array($key) ? Json::encode($key) : $key; } return Html::checkbox($this->name, !empty($options['checked']), $options); } + + /** + * Returns header checkbox name + * @return string header checkbox name + * @since 2.0.8 + */ + protected function getHeaderCheckBoxName() + { + $name = $this->name; + if (substr_compare($name, '[]', -2, 2) === 0) { + $name = substr($name, 0, -2); + } + if (substr_compare($name, ']', -1, 1) === 0) { + $name = substr($name, 0, -1) . '_all]'; + } else { + $name .= '_all'; + } + + return $name; + } + + /** + * Registers the needed JavaScript + * @since 2.0.8 + */ + public function registerClientScript() + { + $id = $this->grid->options['id']; + $options = Json::encode([ + 'name' => $this->name, + 'multiple' => $this->multiple, + 'checkAll' => $this->grid->showHeader ? $this->getHeaderCheckBoxName() : null, + ]); + $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); + } } diff --git a/framework/grid/Column.php b/framework/grid/Column.php index 8978194..2e9f0e2 100644 --- a/framework/grid/Column.php +++ b/framework/grid/Column.php @@ -124,7 +124,18 @@ class Column extends Object */ protected function renderHeaderCellContent() { - return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell; + return trim($this->header) !== '' ? $this->header : $this->getHeaderCellLabel(); + } + + /** + * Returns header cell label. + * This method may be overridden to customize the label of the header cell. + * @return string label + * @since 2.0.8 + */ + protected function getHeaderCellLabel() + { + return $this->grid->emptyCell; } /** diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index 3b6b01b..e10234c 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -117,6 +117,25 @@ class DataColumn extends Column return parent::renderHeaderCellContent(); } + $label = $this->getHeaderCellLabel(); + if ($this->encodeLabel) { + $label = Html::encode($label); + } + + if ($this->attribute !== null && $this->enableSorting && + ($sort = $this->grid->dataProvider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { + return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => $label])); + } else { + return $label; + } + } + + /** + * @inheritdoc + * @since 2.0.8 + */ + protected function getHeaderCellLabel() + { $provider = $this->grid->dataProvider; if ($this->label === null) { @@ -137,12 +156,7 @@ class DataColumn extends Column $label = $this->label; } - if ($this->attribute !== null && $this->enableSorting && - ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { - return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => $this->encodeLabel ? Html::encode($label) : $label])); - } else { - return $this->encodeLabel ? Html::encode($label) : $label; - } + return $label; } /** diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index ebe61fe..ba269a5 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -93,7 +93,7 @@ class BaseArrayHelper } } - return $recursive ? static::toArray($result) : $result; + return $recursive ? static::toArray($result, $properties) : $result; } else { return [$object]; } diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php index d74f890..9ed980e 100644 --- a/framework/helpers/BaseConsole.php +++ b/framework/helpers/BaseConsole.php @@ -7,7 +7,7 @@ namespace yii\helpers; -use yii\console\Markdown; +use yii\console\Markdown as ConsoleMarkdown; /** * BaseConsole provides concrete implementation for [[Console]]. @@ -455,12 +455,12 @@ class BaseConsole /** * Converts Markdown to be better readable in console environments by applying some ANSI format - * @param string $markdown - * @return string + * @param string $markdown the markdown string. + * @return string the parsed result as ANSI formatted string. */ public static function markdownToAnsi($markdown) { - $parser = new Markdown(); + $parser = new ConsoleMarkdown(); return $parser->parse($markdown); } diff --git a/framework/helpers/BaseFileHelper.php b/framework/helpers/BaseFileHelper.php index f47e3d1..f196871 100644 --- a/framework/helpers/BaseFileHelper.php +++ b/framework/helpers/BaseFileHelper.php @@ -253,6 +253,9 @@ class BaseFileHelper */ public static function copyDirectory($src, $dst, $options = []) { + if ($src === $dst || strpos($dst, $src) === 0) { + throw new InvalidParamException('Trying to copy a directory to itself or a subdirectory.'); + } if (!is_dir($dst)) { static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); } diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index 6f3fa6c..07ed299 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -93,13 +93,7 @@ class BaseMarkdown if (!isset(static::$flavors[$flavor])) { throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'"); } elseif (!is_object($config = static::$flavors[$flavor])) { - $parser = Yii::createObject($config); - if (is_array($config)) { - foreach ($config as $name => $value) { - $parser->{$name} = $value; - } - } - static::$flavors[$flavor] = $parser; + static::$flavors[$flavor] = Yii::createObject($config); } return static::$flavors[$flavor]; diff --git a/framework/log/Dispatcher.php b/framework/log/Dispatcher.php index 8175253..3caf8f5 100644 --- a/framework/log/Dispatcher.php +++ b/framework/log/Dispatcher.php @@ -52,7 +52,8 @@ use yii\base\ErrorHandler; * * @property integer $flushInterval How many messages should be logged before they are sent to targets. This * method returns the value of [[Logger::flushInterval]]. - * @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. + * @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. Note that the type of + * this property differs in getter and setter. See [[getLogger()]] and [[setLogger()]] for details. * @property integer $traceLevel How many application call stacks should be logged together with each message. * This method returns the value of [[Logger::traceLevel]]. Defaults to 0. * @@ -119,10 +120,14 @@ class Dispatcher extends Component /** * Sets the connected logger. - * @param Logger $value the logger. + * @param Logger|string|array $value the logger to be used. This can either be a logger instance + * or a configuration that will be used to create one using [[Yii::createObject()]]. */ public function setLogger($value) { + if (is_string($value) || is_array($value)) { + $value = Yii::createObject($value); + } $this->_logger = $value; $this->_logger->dispatcher = $this; } diff --git a/framework/messages/de/yii.php b/framework/messages/de/yii.php index ea23b80..1d928c0 100644 --- a/framework/messages/de/yii.php +++ b/framework/messages/de/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,6 +17,7 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ + 'Unknown alias: -{name}' => 'Unbekannter Alias: -{name}', '(not set)' => '(nicht gesetzt)', 'An internal server error occurred.' => 'Es ist ein interner Serverfehler aufgetreten.', 'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?', diff --git a/framework/messages/fa/yii.php b/framework/messages/fa/yii.php index ebde81f..841db00 100644 --- a/framework/messages/fa/yii.php +++ b/framework/messages/fa/yii.php @@ -95,12 +95,12 @@ return [ '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} حداقل باید شامل {min, number} کارکتر باشد.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} حداکثر باید شامل {max, number} کارکتر باشد.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} باید شامل {length, number} کارکتر باشد.', - '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 روز} other{# چندین روز}}', - '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 ساعت} other{# ساعات}}', - '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 دقیقه} other{# دقایق}}', - '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 ماه} other{# ماه ها}}', - '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 ثانیه} other{# ثانیه ها}}', - '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 سال} other{# سالیان}}', + '{delta, plural, =1{1 day} other{# days}}' => '{delta} روز', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta} ساعت', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta} دقیقه', + '{delta, plural, =1{1 month} other{# months}}' => '{delta} ماه', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta} ثانیه', + '{delta, plural, =1{1 year} other{# years}}' => '{delta} سال', '{delta, plural, =1{a day} other{# days}} ago' => '{delta} روز قبل', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} دقیقه قبل', '{delta, plural, =1{a month} other{# months}} ago' => '{delta} ماه قبل', diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index fc94f82..cc80e8a 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -678,6 +678,7 @@ class DbManager extends BaseManager /** * @inheritdoc + * @since 2.0.8 */ public function canAddChild($parent, $child) { diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 2ec3412..62e9dec 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -150,6 +150,7 @@ class PhpManager extends BaseManager /** * @inheritdoc + * @since 2.0.8 */ public function canAddChild($parent, $child) { diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php index 5ea3f41..d9de805 100644 --- a/framework/validators/DateValidator.php +++ b/framework/validators/DateValidator.php @@ -31,6 +31,41 @@ use yii\helpers\FormatConverter; class DateValidator extends Validator { /** + * Constant for specifying the validation [[type]] as a date value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_DATE = 'date'; + /** + * Constant for specifying the validation [[type]] as a datetime value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_DATETIME = 'datetime'; + /** + * Constant for specifying the validation [[type]] as a time value, used for validation with intl short format. + * @since 2.0.8 + * @see type + */ + const TYPE_TIME = 'time'; + + /** + * @var string the type of the validator. Indicates, whether a date, time or datetime value should be validated. + * This property influences the default value of [[format]] and also sets the correct behavior when [[format]] is one of the intl + * short formats, `short`, `medium`, `long`, or `full`. + * + * This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * + * This property can be set to the following values: + * + * - [[TYPE_DATE]] - (default) for validating date values only, that means only values that do not include a time range are valid. + * - [[TYPE_DATETIME]] - for validating datetime values, that contain a date part as well as a time part. + * - [[TYPE_TIME]] - for validating time values, that contain no date information. + * + * @since 2.0.8 + */ + public $type = self::TYPE_DATE; + /** * @var string the date format that the value being validated should follow. * This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). * @@ -38,6 +73,12 @@ class DateValidator extends Validator * Please refer to on supported formats. * * If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details. + * Since version 2.0.8 the default value will be determined from different formats of the formatter class, + * dependent on the value of [[type]]: + * + * - if type is [[TYPE_DATE]], the default value will be taken from [[\yii\i18n\Formatter::dateFormat]], + * - if type is [[TYPE_DATETIME]], it will be taken from [[\yii\i18n\Formatter::datetimeFormat]], + * - and if type is [[TYPE_TIME]], it will be [[\yii\i18n\Formatter::timeFormat]]. * * Here are some example values: * @@ -165,7 +206,15 @@ class DateValidator extends Validator $this->message = Yii::t('yii', 'The format of {attribute} is invalid.'); } if ($this->format === null) { - $this->format = Yii::$app->formatter->dateFormat; + if ($this->type === self::TYPE_DATE) { + $this->format = Yii::$app->formatter->dateFormat; + } elseif ($this->type === self::TYPE_DATETIME) { + $this->format = Yii::$app->formatter->datetimeFormat; + } elseif ($this->type === self::TYPE_TIME) { + $this->format = Yii::$app->formatter->timeFormat; + } else { + throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type); + } } if ($this->locale === null) { $this->locale = Yii::$app->language; @@ -297,7 +346,15 @@ class DateValidator extends Validator private function parseDateValueIntl($value, $format) { if (isset($this->_dateFormats[$format])) { - $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, 'UTC'); + if ($this->type === self::TYPE_DATE) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, 'UTC'); + } elseif ($this->type === self::TYPE_DATETIME) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone); + } elseif ($this->type === self::TYPE_TIME) { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone); + } else { + throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type); + } } else { // if no time was provided in the format string set time to 0 to get a simple date timestamp $hasTimeInfo = (strpbrk($format, 'ahHkKmsSA') !== false); diff --git a/framework/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php index 202ef0c..03a384c 100644 --- a/framework/views/errorHandler/exception.php +++ b/framework/views/errorHandler/exception.php @@ -333,6 +333,13 @@ h1,h2,h3,p,img,ul li{ .variable{ color: #a00; } + +body pre { + pointer-events: none; +} +body.mousedown pre { + pointer-events: auto; +} @@ -398,60 +405,24 @@ var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(/< - - endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 6a40a68..2962b0e 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -153,7 +153,9 @@ class AssetBundle extends Object $options = ArrayHelper::merge($this->jsOptions, $js); $view->registerJsFile($manager->getAssetUrl($this, $file), $options); } else { - $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions); + if ($js !== null) { + $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions); + } } } foreach ($this->css as $css) { @@ -162,7 +164,9 @@ class AssetBundle extends Object $options = ArrayHelper::merge($this->cssOptions, $css); $view->registerCssFile($manager->getAssetUrl($this, $file), $options); } else { - $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions); + if ($css !== null) { + $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions); + } } } } diff --git a/framework/web/ErrorHandler.php b/framework/web/ErrorHandler.php index f69e6d4..7098fcc 100644 --- a/framework/web/ErrorHandler.php +++ b/framework/web/ErrorHandler.php @@ -134,7 +134,7 @@ class ErrorHandler extends \yii\base\ErrorHandler protected function convertExceptionToArray($exception) { if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) { - $exception = new HttpException(500, Yii::t('yii', 'There was an error at the server.')); + $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.')); } $array = [ diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 45b1c0a..bb80e0e 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -48,7 +48,8 @@ class JsonParser implements RequestParserInterface public function parse($rawBody, $contentType) { try { - return Json::decode($rawBody, $this->asArray); + $parameters = Json::decode($rawBody, $this->asArray); + return $parameters === null ? [] : $parameters; } catch (InvalidParamException $e) { if ($this->throwException) { throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage()); diff --git a/framework/web/Request.php b/framework/web/Request.php index e6387e3..de81447 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -27,10 +27,10 @@ use yii\helpers\StringHelper; * corresponding quality score and other parameters as given in the header. * @property array $acceptableLanguages The languages ordered by the preference level. The first element * represents the most preferred language. - * @property string $authPassword The password sent via HTTP authentication, null if the password is not + * @property string|null $authPassword The password sent via HTTP authentication, null if the password is not + * given. This property is read-only. + * @property string|null $authUser The username sent via HTTP authentication, null if the username is not * given. This property is read-only. - * @property string $authUser The username sent via HTTP authentication, null if the username is not given. - * This property is read-only. * @property string $baseUrl The relative URL for the application. * @property array $bodyParams The request parameters given in the request body. * @property string $contentType Request content-type. Null is returned if this information is not available. @@ -42,7 +42,7 @@ use yii\helpers\StringHelper; * @property array $eTags The entity tags. This property is read-only. * @property HeaderCollection $headers The header collection. This property is read-only. * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. - * `http://www.yiiframework.com`), null in case it can't be obtained from `$_SERVER` and wasn't set. + * `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. * @property boolean $isDelete Whether this is a DELETE request. This property is read-only. * @property boolean $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is @@ -65,17 +65,16 @@ use yii\helpers\StringHelper; * @property string $queryString Part of the request URL that is after the question mark. This property is * read-only. * @property string $rawBody The request body. - * @property string $referrer URL referrer, null if not present. This property is read-only. + * @property string|null $referrer URL referrer, null if not available. This property is read-only. * @property string $scriptFile The entry script file path. * @property string $scriptUrl The relative URL of the entry script. * @property integer $securePort Port number for secure requests. * @property string $serverName Server name, null if not available. This property is read-only. - * @property integer $serverPort Server port number, null if not available. This property is read-only. + * @property integer|null $serverPort Server port number, null if not available. This property is read-only. * @property string $url The currently requested relative URL. Note that the URI returned is URL-encoded. - * @property string $userAgent User agent, null if not present. This property is read-only. - * @property string $userHost User host name, null if cannot be determined. This property is read-only. - * @property string $userIP User IP address. Null is returned if the user IP address cannot be detected. This - * property is read-only. + * @property string|null $userAgent User agent, null if not available. This property is read-only. + * @property string|null $userHost User host name, null if not available. This property is read-only. + * @property string|null $userIP User IP address, null if not available. This property is read-only. * * @author Qiang Xue * @since 2.0 @@ -557,7 +556,7 @@ class Request extends \yii\base\Request */ public function setHostInfo($value) { - $this->_hostInfo = rtrim($value, '/'); + $this->_hostInfo = $value === null ? null : rtrim($value, '/'); } private $_baseUrl; @@ -628,7 +627,7 @@ class Request extends \yii\base\Request */ public function setScriptUrl($value) { - $this->_scriptUrl = '/' . trim($value, '/'); + $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/'); } private $_scriptFile; @@ -688,7 +687,7 @@ class Request extends \yii\base\Request */ public function setPathInfo($value) { - $this->_pathInfo = ltrim($value, '/'); + $this->_pathInfo = $value === null ? null : ltrim($value, '/'); } /** diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index cca4bf2..bfdd944 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -126,6 +126,12 @@ class UrlManager extends Component */ public $ruleConfig = ['class' => 'yii\web\UrlRule']; + /** + * @var string the cache key for cached rules + * @since 2.0.8 + */ + protected $cacheKey = __CLASS__; + private $_baseUrl; private $_scriptUrl; private $_hostInfo; @@ -146,7 +152,7 @@ class UrlManager extends Component $this->cache = Yii::$app->get($this->cache, false); } if ($this->cache instanceof Cache) { - $cacheKey = __CLASS__; + $cacheKey = $this->cacheKey; $hash = md5(json_encode($this->rules)); if (($data = $this->cache->get($cacheKey)) !== false && isset($data[1]) && $data[1] === $hash) { $this->rules = $data[0]; @@ -322,28 +328,19 @@ class UrlManager extends Component } } - /* @var $rule UrlRule */ - $url = false; - if (isset($this->_ruleCache[$cacheKey])) { - foreach ($this->_ruleCache[$cacheKey] as $rule) { - if (($url = $rule->createUrl($this, $route, $params)) !== false) { - break; - } - } - } else { - $this->_ruleCache[$cacheKey] = []; - } + $url = $this->getUrlFromCache($cacheKey, $route, $params); if ($url === false) { $cacheable = true; foreach ($this->rules as $rule) { + /* @var $rule UrlRule */ if (!empty($rule->defaults) && $rule->mode !== UrlRule::PARSING_ONLY) { // if there is a rule with default values involved, the matching result may not be cached $cacheable = false; } if (($url = $rule->createUrl($this, $route, $params)) !== false) { if ($cacheable) { - $this->_ruleCache[$cacheKey][] = $rule; + $this->setRuleToCache($cacheKey, $rule); } break; } @@ -381,6 +378,41 @@ class UrlManager extends Component } /** + * Get URL from internal cache if exists + * @param string $cacheKey generated cache key to store data. + * @param string $route the route (e.g. `site/index`). + * @param array $params rule params. + * @return boolean|string the created URL + * @see createUrl() + * @since 2.0.8 + */ + protected function getUrlFromCache($cacheKey, $route, $params) + { + if (!empty($this->_ruleCache[$cacheKey])) { + foreach ($this->_ruleCache[$cacheKey] as $rule) { + /* @var $rule UrlRule */ + if (($url = $rule->createUrl($this, $route, $params)) !== false) { + return $url; + } + } + } else { + $this->_ruleCache[$cacheKey] = []; + } + return false; + } + + /** + * Store rule (e.g. [[UrlRule]]) to internal cache + * @param $cacheKey + * @param UrlRuleInterface $rule + * @since 2.0.8 + */ + protected function setRuleToCache($cacheKey, UrlRuleInterface $rule) + { + $this->_ruleCache[$cacheKey][] = $rule; + } + + /** * Creates an absolute URL using the given route and query parameters. * * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]]. @@ -437,7 +469,7 @@ class UrlManager extends Component */ public function setBaseUrl($value) { - $this->_baseUrl = rtrim($value, '/'); + $this->_baseUrl = $value === null ? null : rtrim($value, '/'); } /** @@ -496,6 +528,6 @@ class UrlManager extends Component */ public function setHostInfo($value) { - $this->_hostInfo = rtrim($value, '/'); + $this->_hostInfo = $value === null ? null : rtrim($value, '/'); } } diff --git a/framework/web/User.php b/framework/web/User.php index b330857..0418ac5 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -431,6 +431,7 @@ class User extends Component $request = Yii::$app->getRequest(); $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable(); if ($this->enableSession + && $request->getIsGet() && (!$checkAjax || !$request->getIsAjax()) && $canRedirect ) { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6debe5e..eb6f339 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,9 @@ + + framework/ + framework/helpers/Json.php framework/helpers/StringHelper.php diff --git a/tests/data/ar/Order.php b/tests/data/ar/Order.php index 3426787..29e1167 100644 --- a/tests/data/ar/Order.php +++ b/tests/data/ar/Order.php @@ -12,9 +12,11 @@ namespace yiiunit\data\ar; */ class Order extends ActiveRecord { + public static $tableName; + public static function tableName() { - return 'order'; + return static::$tableName ?: 'order'; } public function getCustomer() @@ -22,6 +24,18 @@ class Order extends ActiveRecord return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } + public function getCustomerJoinedWithProfile() + { + return $this->hasOne(Customer::className(), ['id' => 'customer_id']) + ->joinWith('profile'); + } + + public function getCustomerJoinedWithProfileIndexOrdered() + { + return $this->hasMany(Customer::className(), ['id' => 'customer_id']) + ->joinWith('profile')->orderBy(['profile.description' => SORT_ASC])->indexBy('name'); + } + public function getCustomer2() { return $this->hasOne(Customer::className(), ['id' => 'customer_id'])->inverseOf('orders2'); diff --git a/tests/data/ar/OrderItem.php b/tests/data/ar/OrderItem.php index 4c45f88..b1dbe78 100644 --- a/tests/data/ar/OrderItem.php +++ b/tests/data/ar/OrderItem.php @@ -12,9 +12,11 @@ namespace yiiunit\data\ar; */ class OrderItem extends ActiveRecord { + public static $tableName; + public static function tableName() { - return 'order_item'; + return static::$tableName ?: 'order_item'; } public function getOrder() @@ -26,4 +28,15 @@ class OrderItem extends ActiveRecord { return $this->hasOne(Item::className(), ['id' => 'item_id']); } + + // relations used by ::testFindCompositeWithJoin() + public function getOrderItemCompositeWithJoin() + { + return $this->hasOne(OrderItem::className(), ['item_id' => 'item_id', 'order_id' => 'order_id' ]) + ->joinWith('item'); + } + public function getOrderItemCompositeNoJoin() + { + return $this->hasOne(OrderItem::className(), ['item_id' => 'item_id', 'order_id' => 'order_id' ]); + } } diff --git a/tests/data/base/TraversableObject.php b/tests/data/base/TraversableObject.php new file mode 100644 index 0000000..bf4b58a --- /dev/null +++ b/tests/data/base/TraversableObject.php @@ -0,0 +1,75 @@ + + * @since 2.0.8 + */ +class TraversableObject implements \Iterator, \Countable +{ + private $data; + private $position = 0; + + public function __construct(array $array) + { + $this->data = $array; + } + + /** + * @throws \Exception + * @since 5.1.0 + */ + public function count() + { + throw new \Exception('Count called on object that should only be traversed.'); + } + + /** + * @inheritdoc + */ + public function current() + { + return $this->data[$this->position]; + } + + /** + * @inheritdoc + */ + public function next() + { + $this->position++; + } + + /** + * @inheritdoc + */ + public function key() + { + return $this->position; + } + + /** + * @inheritdoc + */ + public function valid() + { + return array_key_exists($this->position, $this->data); + } + + /** + * @inheritdoc + */ + public function rewind() + { + $this->position = 0; + } +} diff --git a/tests/data/mysql.sql b/tests/data/mysql.sql index 7c1e40b..8bdc46d 100644 --- a/tests/data/mysql.sql +++ b/tests/data/mysql.sql @@ -18,6 +18,7 @@ DROP TABLE IF EXISTS `constraints` CASCADE; DROP TABLE IF EXISTS `animal` CASCADE; DROP TABLE IF EXISTS `default_pk` CASCADE; DROP TABLE IF EXISTS `document` CASCADE; +DROP TABLE IF EXISTS `comment` CASCADE; DROP VIEW IF EXISTS `animal_view`; CREATE TABLE `constraints` @@ -150,6 +151,14 @@ CREATE TABLE `document` ( PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `comment` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `add_comment` VARCHAR(255) NOT NULL, + `replace_comment` VARCHAR(255) COMMENT 'comment', + `delete_comment` VARCHAR(128) NOT NULL COMMENT 'comment', + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE VIEW `animal_view` AS SELECT * FROM `animal`; INSERT INTO `animal` (`type`) VALUES ('yiiunit\data\ar\Cat'); diff --git a/tests/data/postgres.sql b/tests/data/postgres.sql index e37d735..a35c188 100644 --- a/tests/data/postgres.sql +++ b/tests/data/postgres.sql @@ -20,6 +20,7 @@ DROP TABLE IF EXISTS "bool_values" CASCADE; DROP TABLE IF EXISTS "animal" CASCADE; DROP TABLE IF EXISTS "default_pk" CASCADE; DROP TABLE IF EXISTS "document" CASCADE; +DROP TABLE IF EXISTS "comment" CASCADE; DROP VIEW IF EXISTS "animal_view"; DROP SCHEMA IF EXISTS "schema1" CASCADE; DROP SCHEMA IF EXISTS "schema2" CASCADE; @@ -154,6 +155,12 @@ CREATE TABLE "document" ( version integer not null default 0 ); +CREATE TABLE "comment" ( + id serial primary key, + name varchar(255) not null, + message text not null +); + CREATE VIEW "animal_view" AS SELECT * FROM "animal"; INSERT INTO "animal" (type) VALUES ('yiiunit\data\ar\Cat'); diff --git a/tests/framework/BaseYiiTest.php b/tests/framework/BaseYiiTest.php index 4168382..52c7486 100644 --- a/tests/framework/BaseYiiTest.php +++ b/tests/framework/BaseYiiTest.php @@ -2,6 +2,8 @@ namespace yiiunit\framework; use Yii; +use yii\di\Container; +use yiiunit\data\base\Singer; use yiiunit\TestCase; /** @@ -60,4 +62,26 @@ class BaseYiiTest extends TestCase { $this->assertTrue(is_string(Yii::powered())); } + + public function testCreateObjectCallable() + { + Yii::$container = new Container(); + + // Test passing in of normal params combined with DI params. + $this->assertTrue(Yii::createObject(function(Singer $singer, $a) { + return $a === 'a'; + }, ['a'])); + + + $singer = new Singer(); + $singer->firstName = 'Bob'; + $this->assertTrue(Yii::createObject(function(Singer $singer, $a) { + return $singer->firstName === 'Bob'; + }, [$singer, 'a'])); + + + $this->assertTrue(Yii::createObject(function(Singer $singer, $a = 3) { + return true; + })); + } } diff --git a/tests/framework/ar/ActiveRecordTestTrait.php b/tests/framework/ar/ActiveRecordTestTrait.php index 1587061..9334838 100644 --- a/tests/framework/ar/ActiveRecordTestTrait.php +++ b/tests/framework/ar/ActiveRecordTestTrait.php @@ -1126,6 +1126,27 @@ trait ActiveRecordTestTrait Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND); } + public function testAfterRefresh() + { + /* @var $customerClass \yii\db\ActiveRecordInterface */ + $customerClass = $this->getCustomerClass(); + /* @var $this TestCase|ActiveRecordTestTrait */ + + $afterRefreshCalls = []; + Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_REFRESH, function ($event) use (&$afterRefreshCalls) { + /* @var $ar BaseActiveRecord */ + $ar = $event->sender; + $afterRefreshCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')]; + }); + + $customer = $customerClass::findOne(1); + $this->assertNotNull($customer); + $customer->refresh(); + $this->assertEquals([[$customerClass, false, 1, false]], $afterRefreshCalls); + $afterRefreshCalls = []; + Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_REFRESH); + } + public function testFindEmptyInCondition() { /* @var $customerClass \yii\db\ActiveRecordInterface */ diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php index 762abec..f20ffc9 100644 --- a/tests/framework/base/SecurityTest.php +++ b/tests/framework/base/SecurityTest.php @@ -5,7 +5,46 @@ * @license http://www.yiiframework.com/license/ */ -namespace yiiunit\framework\base; +namespace yii\base { + + /** + * emulate availability of functions, to test different branches of Security class + * where different execution paths are chosen based on calling function_exists. + * + * This function overrides function_exists from the root namespace in yii\base. + */ + function function_exists($name) { + + if (isset(\yiiunit\framework\base\SecurityTest::$functions[$name])) { + return \yiiunit\framework\base\SecurityTest::$functions[$name]; + } + return \function_exists($name); + } + /** + * emulate chunked reading of fread(), to test different branches of Security class + * where different execution paths are chosen based on the return value of fopen/fread + * + * This function overrides fopen and fread from the root namespace in yii\base. + */ + function fopen($filename, $mode) { + if (\yiiunit\framework\base\SecurityTest::$fopen !== null) { + return \yiiunit\framework\base\SecurityTest::$fopen; + } + return \fopen($filename, $mode); + } + function fread($handle, $length) { + if (\yiiunit\framework\base\SecurityTest::$fread !== null) { + return \yiiunit\framework\base\SecurityTest::$fread; + } + if (\yiiunit\framework\base\SecurityTest::$fopen !== null) { + return $length < 8 ? \str_repeat('s', $length) : 'test1234'; + } + return \fread($handle, $length); + } + +} // closing namespace yii\base; + +namespace yiiunit\framework\base { use yii\base\Security; use yiiunit\TestCase; @@ -18,17 +57,38 @@ class SecurityTest extends TestCase const CRYPT_VECTORS = 'old'; /** + * @var array set of functions for which a fake return value for `function_exists()` is provided. + */ + public static $functions = []; + /** + * @var resource|false|null fake return value for fopen() in \yii\base namespace. Normal behavior if this is null. + */ + public static $fopen; + public static $fread; + + /** * @var ExposedSecurity */ protected $security; protected function setUp() { + static::$functions = []; + static::$fopen = null; + static::$fread = null; parent::setUp(); $this->security = new ExposedSecurity(); $this->security->derivationIterations = 1000; // speed up test running } + protected function tearDown() + { + static::$functions = []; + static::$fopen = null; + static::$fread = null; + parent::tearDown(); + } + // Tests : public function testHashData() @@ -799,9 +859,99 @@ TEXT; $this->assertEquals($data, $this->security->decryptByPassword($encrypted, $password)); } - public function testGenerateRandomKey() + + public function randomKeyInvalidInputs() { - $length = 21; + return [ + [ 0 ], + [ -1 ], + [ '0' ], + [ '34' ], + [ [] ], + ]; + } + + /** + * @dataProvider randomKeyInvalidInputs + * @expectedException \yii\base\InvalidParamException + */ + public function testRandomKeyInvalidInput($input) + { + $key1 = $this->security->generateRandomKey($input); + } + + /** + * Test the case where opening /dev/urandom fails + */ + public function testRandomKeyNoOptions() + { + static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ]; + static::$fopen = false; + $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + + $this->security->generateRandomKey(42); + } + + /** + * Test the case where reading from /dev/urandom fails + */ + public function testRandomKeyFreadFailure() + { + static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ]; + static::$fread = false; + $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + + $this->security->generateRandomKey(42); + } + + /** + * returns a set of different combinations of functions available. + */ + public function randomKeyVariants() + { + return [ + [ ['random_bytes' => true, 'openssl_random_pseudo_bytes' => true, 'mcrypt_create_iv' => true ] ], + [ ['random_bytes' => true, 'openssl_random_pseudo_bytes' => true, 'mcrypt_create_iv' => false ] ], + [ ['random_bytes' => true, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true ] ], + [ ['random_bytes' => true, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ] ], + [ ['random_bytes' => false, 'openssl_random_pseudo_bytes' => true, 'mcrypt_create_iv' => true ] ], + [ ['random_bytes' => false, 'openssl_random_pseudo_bytes' => true, 'mcrypt_create_iv' => false ] ], + [ ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true ] ], + [ ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false ] ], + ]; + } + + /** + * @dataProvider randomKeyVariants + */ + public function testGenerateRandomKey($functions) + { + foreach ($functions as $fun => $available) { + if ($available && !\function_exists($fun)) { + $this->markTestSkipped("Can not test generateRandomKey() branch that includes $fun, because it is not available on your system."); + } + } + // there is no /dev/urandom on windows so we expect this to fail + if (DIRECTORY_SEPARATOR === '\\' && $functions['random_bytes'] === false && $functions['openssl_random_pseudo_bytes'] === false && $functions['mcrypt_create_iv'] === false ) { + $this->setExpectedException('yii\base\Exception', 'Unable to generate a random key'); + } + static::$functions = $functions; + + // test various string lengths + for ($length = 1; $length < 64; $length++) { + $key1 = $this->security->generateRandomKey($length); + $this->assertInternalType('string', $key1); + $this->assertEquals($length, strlen($key1)); + $key2 = $this->security->generateRandomKey($length); + $this->assertInternalType('string', $key2); + $this->assertEquals($length, strlen($key2)); + if ($length >= 7) { // avoid random test failure, short strings are likely to collide + $this->assertTrue($key1 != $key2); + } + } + + // test for /dev/urandom, reading larger data to see if loop works properly + $length = 1024 * 1024; $key1 = $this->security->generateRandomKey($length); $this->assertInternalType('string', $key1); $this->assertEquals($length, strlen($key1)); @@ -809,6 +959,16 @@ TEXT; $this->assertInternalType('string', $key2); $this->assertEquals($length, strlen($key2)); $this->assertTrue($key1 != $key2); + + // force /dev/urandom reading loop to deal with chunked data + // the above test may have read everything in one run. + // not sure if this can happen in real life but if it does + // we should be prepared + static::$fopen = fopen('php://memory', 'rwb'); + $length = 1024 * 1024; + $key1 = $this->security->generateRandomKey($length); + $this->assertInternalType('string', $key1); + $this->assertEquals($length, strlen($key1)); } protected function randTime(Security $security, $count, $length, $message) @@ -860,29 +1020,6 @@ TEXT; $this->randTime($security, 10000, 5000, 'Rate test'); } - - public function testGenerateRandomKeyURandom() - { - if (function_exists('random_bytes')) { - $this->markTestSkipped('This test can only work on platforms where random_bytes() function does not exist. You may disable it in php.ini for testing. http://php.net/manual/en/ini.core.php#ini.disable-functions'); - } - if (PHP_VERSION_ID >= 50307 && function_exists('mcrypt_create_iv')) { - $this->markTestSkipped('This test can only work on platforms where mcrypt_create_iv() function does not exist. You may disable it in php.ini for testing. http://php.net/manual/en/ini.core.php#ini.disable-functions'); - } - if (!@is_readable('/dev/urandom')) { - $this->markTestSkipped('/dev/urandom does not seem to exist on your system.'); - } - - $length = 1024 * 1024; - $key1 = $this->security->generateRandomKey($length); - $this->assertInternalType('string', $key1); - $this->assertEquals($length, strlen($key1)); - $key2 = $this->security->generateRandomKey($length); - $this->assertInternalType('string', $key2); - $this->assertEquals($length, strlen($key2)); - $this->assertTrue($key1 != $key2); - } - public function testGenerateRandomString() { $length = 21; @@ -1110,3 +1247,6 @@ TEXT; $this->assertEquals(strcmp($expected, $actual) === 0, $this->security->compareString($expected, $actual)); } } + +} // closing namespace yiiunit\framework\base; + diff --git a/tests/framework/console/ControllerTest.php b/tests/framework/console/ControllerTest.php index 2d0b5dd..95adff6 100644 --- a/tests/framework/console/ControllerTest.php +++ b/tests/framework/console/ControllerTest.php @@ -8,6 +8,7 @@ namespace yiiunit\framework\console; use Yii; +use yii\console\Request; use yiiunit\TestCase; /** @@ -53,4 +54,39 @@ class ControllerTest extends TestCase $this->setExpectedException('yii\console\Exception', $message); $result = $controller->runAction('aksi3', $params); } + + public function assertResponseStatus($status, $response) + { + $this->assertInstanceOf('yii\console\Response', $response); + $this->assertSame($status, $response->exitStatus); + } + + public function runRequest($route, $args = 0) + { + $request = new Request(); + $request->setParams(func_get_args()); + return Yii::$app->handleRequest($request); + } + + public function testResponse() + { + $this->mockApplication(); + Yii::$app->controllerMap = [ + 'fake' => 'yiiunit\framework\console\FakeController', + ]; + $status = 123; + + $response = $this->runRequest('fake/status'); + $this->assertResponseStatus(0, $response); + + $response = $this->runRequest('fake/status', (string)$status); + $this->assertResponseStatus($status, $response); + + $response = $this->runRequest('fake/response'); + $this->assertResponseStatus(0, $response); + + $response = $this->runRequest('fake/response', (string)$status); + $this->assertResponseStatus($status, $response); + } + } diff --git a/tests/framework/console/FakeController.php b/tests/framework/console/FakeController.php index e515f80..6d15d4ef0 100644 --- a/tests/framework/console/FakeController.php +++ b/tests/framework/console/FakeController.php @@ -8,6 +8,7 @@ namespace yiiunit\framework\console; use yii\console\Controller; +use yii\console\Response; /** * @author Misbahul D Munir @@ -67,4 +68,16 @@ class FakeController extends Controller { return $this->testArray; } + + public function actionStatus($status = 0) + { + return $status; + } + + public function actionResponse($status = 0) + { + $response = new Response(); + $response->exitStatus = (int)$status; + return $response; + } } diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index a29db1c..3c3ce4c 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -97,6 +97,14 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals('user2', $customerName); } + public function testFindExists() + { + $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->exists()); + $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->exists()); + $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->select('[[name]]')->exists()); + $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->select('[[name]]')->exists()); + } + public function testFindColumn() { /* @var $this TestCase|ActiveRecordTestTrait */ @@ -815,6 +823,74 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals(0, count($orders[0]->itemsIndexed)); } + /** + * https://github.com/yiisoft/yii2/issues/10201 + * https://github.com/yiisoft/yii2/issues/9047 + */ + public function testFindCompositeRelationWithJoin() + { + /* @var $orderItem OrderItem */ + $orderItem = OrderItem::findOne([1, 1]); + + $orderItemNoJoin = $orderItem->orderItemCompositeNoJoin; + $this->assertInstanceOf('yiiunit\data\ar\OrderItem', $orderItemNoJoin); + + $orderItemWithJoin = $orderItem->orderItemCompositeWithJoin; + $this->assertInstanceOf('yiiunit\data\ar\OrderItem', $orderItemWithJoin); + } + + public function testFindSimpleRelationWithJoin() + { + /* @var $order Order */ + $order = Order::findOne(1); + + $customerNoJoin = $order->customer; + $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerNoJoin); + + $customerWithJoin = $order->customerJoinedWithProfile; + $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerWithJoin); + + $customerWithJoinIndexOrdered = $order->customerJoinedWithProfileIndexOrdered; + $this->assertTrue(is_array($customerWithJoinIndexOrdered)); + $this->assertArrayHasKey('user1', $customerWithJoinIndexOrdered); + $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerWithJoinIndexOrdered['user1']); + } + + public function tableNameProvider() + { + return [ + ['order', 'order_item'], + ['order', '{{%order_item}}'], + ['{{%order}}', 'order_item'], + ['{{%order}}', '{{%order_item}}'], + ]; + } + + /** + * Test whether conditions are quoted correctly in conditions where joinWith is used. + * @see https://github.com/yiisoft/yii2/issues/11088 + * @dataProvider tableNameProvider + */ + public function testRelationWhereParams($orderTableName, $orderItemTableName) + { + Order::$tableName = $orderTableName; + OrderItem::$tableName = $orderItemTableName; + + /** @var $order Order */ + $order = Order::findOne(1); + $itemsSQL = $order->getOrderitems()->createCommand()->rawSql; + $expectedSQL = $this->replaceQuotes("SELECT * FROM [[order_item]] WHERE [[order_id]]=1"); + $this->assertEquals($expectedSQL, $itemsSQL); + + $order = Order::findOne(1); + $itemsSQL = $order->getOrderItems()->joinWith('item')->createCommand()->rawSql; + $expectedSQL = $this->replaceQuotes("SELECT [[order_item]].* FROM [[order_item]] LEFT JOIN [[item]] ON [[order_item]].[[item_id]] = [[item]].[[id]] WHERE [[order_item]].[[order_id]]=1"); + $this->assertEquals($expectedSQL, $itemsSQL); + + Order::$tableName = null; + OrderItem::$tableName = null; + } + public function testAlias() { $query = Order::find(); diff --git a/tests/framework/db/ConnectionTest.php b/tests/framework/db/ConnectionTest.php index 8a7ac47..444fbd8 100644 --- a/tests/framework/db/ConnectionTest.php +++ b/tests/framework/db/ConnectionTest.php @@ -70,25 +70,49 @@ class ConnectionTest extends DatabaseTestCase public function testQuoteTableName() { - $connection = $this->getConnection(false); + $connection = $this->getConnection(false, false); $this->assertEquals('`table`', $connection->quoteTableName('table')); $this->assertEquals('`table`', $connection->quoteTableName('`table`')); $this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.table')); $this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.`table`')); + $this->assertEquals('`schema`.`table`', $connection->quoteTableName('`schema`.`table`')); $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); $this->assertEquals('(table)', $connection->quoteTableName('(table)')); } public function testQuoteColumnName() { - $connection = $this->getConnection(false); + $connection = $this->getConnection(false, false); $this->assertEquals('`column`', $connection->quoteColumnName('column')); $this->assertEquals('`column`', $connection->quoteColumnName('`column`')); - $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.column')); - $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.`column`')); $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + + $this->assertEquals('`column`', $connection->quoteSql('[[column]]')); + $this->assertEquals('`column`', $connection->quoteSql('{{column}}')); + } + + public function testQuoteFullColumnName() + { + $connection = $this->getConnection(false, false); + $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.column')); + $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.`column`')); + $this->assertEquals('`table`.`column`', $connection->quoteColumnName('`table`.column')); + $this->assertEquals('`table`.`column`', $connection->quoteColumnName('`table`.`column`')); + + $this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); + $this->assertEquals('{{table}}.`column`', $connection->quoteColumnName('{{table}}.column')); + $this->assertEquals('{{table}}.`column`', $connection->quoteColumnName('{{table}}.`column`')); + $this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); + $this->assertEquals('{{%table}}.`column`', $connection->quoteColumnName('{{%table}}.column')); + $this->assertEquals('{{%table}}.`column`', $connection->quoteColumnName('{{%table}}.`column`')); + + $this->assertEquals('`table`.`column`', $connection->quoteSql('[[table.column]]')); + $this->assertEquals('`table`.`column`', $connection->quoteSql('{{table}}.[[column]]')); + $this->assertEquals('`table`.`column`', $connection->quoteSql('{{table}}.`column`')); + $this->assertEquals('`table`.`column`', $connection->quoteSql('{{%table}}.[[column]]')); + $this->assertEquals('`table`.`column`', $connection->quoteSql('{{%table}}.`column`')); } public function testTransaction() diff --git a/tests/framework/db/DatabaseTestCase.php b/tests/framework/db/DatabaseTestCase.php index e500e6c..ec80f95 100644 --- a/tests/framework/db/DatabaseTestCase.php +++ b/tests/framework/db/DatabaseTestCase.php @@ -89,4 +89,26 @@ abstract class DatabaseTestCase extends TestCase } return $db; } + + /** + * adjust dbms specific escaping + * @param $sql + * @return mixed + */ + protected function replaceQuotes($sql) + { + switch ($this->driverName) { + case 'mysql': + case 'sqlite': + return str_replace(['[[', ']]'], '`', $sql); + case 'cubrid': + case 'pgsql': + case 'oci': + return str_replace(['[[', ']]'], '"', $sql); + case 'sqlsrv': + return str_replace(['[[', ']]'], ['[', ']'], $sql); + default: + return $sql; + } + } } diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 8ef23de..93ce8f2 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -13,6 +13,7 @@ use yii\db\mssql\QueryBuilder as MssqlQueryBuilder; use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder; use yii\db\cubrid\QueryBuilder as CubridQueryBuilder; use yii\db\oci\QueryBuilder as OracleQueryBuilder; +use yiiunit\data\base\TraversableObject; abstract class QueryBuilderTest extends DatabaseTestCase { @@ -51,28 +52,6 @@ abstract class QueryBuilderTest extends DatabaseTestCase } /** - * adjust dbms specific escaping - * @param $sql - * @return mixed - */ - protected function replaceQuotes($sql) - { - switch ($this->driverName) { - case 'mysql': - case 'sqlite': - return str_replace(['[[', ']]'], '`', $sql); - case 'cubrid': - case 'pgsql': - case 'oci': - return str_replace(['[[', ']]'], '"', $sql); - case 'sqlsrv': - return str_replace(['[[', ']]'], ['[', ']'], $sql); - default: - return $sql; - } - } - - /** * this is not used as a dataprovider for testGetColumnType to speed up the test * when used as dataprovider every single line will cause a reconnect with the database which is not needed here */ @@ -80,15 +59,15 @@ abstract class QueryBuilderTest extends DatabaseTestCase { $items = [ [ - Schema::TYPE_BIGINT . ' CHECK (value > 5)', - $this->bigInteger()->check('value > 5'), + Schema::TYPE_BIGINT, + $this->bigInteger(), [ - 'mysql' => 'bigint(20) CHECK (value > 5)', - 'postgres' => 'bigint CHECK (value > 5)', - 'sqlite' => 'bigint CHECK (value > 5)', - 'oci' => 'NUMBER(20) CHECK (value > 5)', - 'sqlsrv' => 'bigint CHECK (value > 5)', - 'cubrid' => 'bigint CHECK (value > 5)', + 'mysql' => 'bigint(20)', + 'postgres' => 'bigint', + 'sqlite' => 'bigint', + 'oci' => 'NUMBER(20)', + 'sqlsrv' => 'bigint', + 'cubrid' => 'bigint', ], ], [ @@ -104,13 +83,13 @@ abstract class QueryBuilderTest extends DatabaseTestCase ], ], [ - Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', - $this->bigInteger(8)->check('value > 5'), + Schema::TYPE_BIGINT . ' CHECK (value > 5)', + $this->bigInteger()->check('value > 5'), [ - 'mysql' => 'bigint(8) CHECK (value > 5)', + 'mysql' => 'bigint(20) CHECK (value > 5)', 'postgres' => 'bigint CHECK (value > 5)', 'sqlite' => 'bigint CHECK (value > 5)', - 'oci' => 'NUMBER(8) CHECK (value > 5)', + 'oci' => 'NUMBER(20) CHECK (value > 5)', 'sqlsrv' => 'bigint CHECK (value > 5)', 'cubrid' => 'bigint CHECK (value > 5)', ], @@ -128,15 +107,15 @@ abstract class QueryBuilderTest extends DatabaseTestCase ], ], [ - Schema::TYPE_BIGINT, - $this->bigInteger(), + Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', + $this->bigInteger(8)->check('value > 5'), [ - 'mysql' => 'bigint(20)', - 'postgres' => 'bigint', - 'sqlite' => 'bigint', - 'oci' => 'NUMBER(20)', - 'sqlsrv' => 'bigint', - 'cubrid' => 'bigint', + 'mysql' => 'bigint(8) CHECK (value > 5)', + 'postgres' => 'bigint CHECK (value > 5)', + 'sqlite' => 'bigint CHECK (value > 5)', + 'oci' => 'NUMBER(8) CHECK (value > 5)', + 'sqlsrv' => 'bigint CHECK (value > 5)', + 'cubrid' => 'bigint CHECK (value > 5)', ], ], [ @@ -915,6 +894,28 @@ abstract class QueryBuilderTest extends DatabaseTestCase 'sqlite' => 'bigint UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', ], ], + [ + Schema::TYPE_INTEGER . " COMMENT 'test comment'", + $this->integer()->comment('test comment'), + [ + 'mysql' => "int(11) COMMENT 'test comment'", + 'postgres' => 'integer', + 'oci' => "NUMBER(10)", + 'sqlsrv' => 'int', + 'cubrid' => "int COMMENT 'test comment'", + ], + ], + [ + Schema::TYPE_PK . " COMMENT 'test comment'", + $this->primaryKey()->comment('test comment'), + [ + 'mysql' => "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test comment'", + 'postgres' => 'serial NOT NULL PRIMARY KEY', + 'oci' => 'NUMBER(10) NOT NULL PRIMARY KEY', + 'sqlsrv' => 'int IDENTITY PRIMARY KEY', + 'cubrid' => "int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test comment'", + ], + ], ]; foreach ($items as $i => $item) { @@ -1023,6 +1024,27 @@ abstract class QueryBuilderTest extends DatabaseTestCase [ ['in', 'id', (new Query())->select('id')->from('users')->where(['active' => 1])], '[[id]] IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1] ], [ ['not in', 'id', (new Query())->select('id')->from('users')->where(['active' => 1])], '[[id]] NOT IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1] ], + [ ['in', 'id', [1]], '[[id]]=:qp0', [':qp0' => 1] ], + [ ['in', 'id', new TraversableObject([1])], '[[id]]=:qp0', [':qp0' => 1] ], + 'composite in' => [ + ['in', ['id', 'name'], [['id' =>1, 'name' => 'oy']]], + '([[id]], [[name]]) IN ((:qp0, :qp1))', + [':qp0' => 1, ':qp1' => 'oy'] + ], + + // in using array objects. + [ ['id' => new TraversableObject([1, 2])], '[[id]] IN (:qp0, :qp1)', [':qp0' => 1, ':qp1' => 2] ], + + [ ['in', 'id', new TraversableObject([1, 2, 3])], '[[id]] IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ], + + 'composite in using array objects' => [ + ['in', new TraversableObject(['id', 'name']), new TraversableObject([ + ['id' => 1, 'name' => 'oy'], + ['id' => 2, 'name' => 'yo'], + ])], + '([[id]], [[name]]) IN ((:qp0, :qp1), (:qp2, :qp3))', + [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'] + ], // exists [ ['exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1] ], [ ['not exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1] ], @@ -1046,8 +1068,10 @@ abstract class QueryBuilderTest extends DatabaseTestCase // direct conditions [ 'a = CONCAT(col1, col2)', 'a = CONCAT(col1, col2)', [] ], [ new Expression('a = CONCAT(col1, :param1)', ['param1' => 'value1']), 'a = CONCAT(col1, :param1)', ['param1' => 'value1'] ], - ]; + + + ]; switch ($this->driverName) { case 'sqlsrv': case 'sqlite': @@ -1347,19 +1371,6 @@ abstract class QueryBuilderTest extends DatabaseTestCase } - public function testCompositeInCondition() - { - $condition = [ - 'in', - ['id', 'name'], - [ - ['id' => 1, 'name' => 'foo'], - ['id' => 2, 'name' => 'bar'], - ], - ]; - (new Query())->from('customer')->where($condition)->all($this->getConnection()); - } - /** * https://github.com/yiisoft/yii2/issues/10869 */ @@ -1521,4 +1532,34 @@ abstract class QueryBuilderTest extends DatabaseTestCase // // TODO implement // } + + public function testCommentColumn() + { + $qb = $this->getQueryBuilder(); + + $expected = "ALTER TABLE [[comment]] CHANGE [[add_comment]] [[add_comment]] varchar(255) NOT NULL COMMENT 'This is my column.'"; + $sql = $qb->addCommentOnColumn('comment', 'add_comment', 'This is my column.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "ALTER TABLE [[comment]] CHANGE [[replace_comment]] [[replace_comment]] varchar(255) DEFAULT NULL COMMENT 'This is my column.'"; + $sql = $qb->addCommentOnColumn('comment', 'replace_comment', 'This is my column.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "ALTER TABLE [[comment]] CHANGE [[delete_comment]] [[delete_comment]] varchar(128) NOT NULL COMMENT ''"; + $sql = $qb->dropCommentFromColumn('comment', 'delete_comment'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } + + public function testCommentTable() + { + $qb = $this->getQueryBuilder(); + + $expected = "ALTER TABLE [[comment]] COMMENT 'This is my table.'"; + $sql = $qb->addCommentOnTable('comment', 'This is my table.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "ALTER TABLE [[comment]] COMMENT ''"; + $sql = $qb->dropCommentFromTable('comment'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } } diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index a9548f3..660ce09 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -214,6 +214,17 @@ class QueryTest extends DatabaseTestCase $this->assertFalse($result); } + public function testExists() + { + $db = $this->getConnection(); + + $result = (new Query)->from('customer')->where(['status' => 2])->exists($db); + $this->assertTrue($result); + + $result = (new Query)->from('customer')->where(['status' => 3])->exists($db); + $this->assertFalse($result); + } + public function testColumn() { $db = $this->getConnection(); @@ -244,6 +255,37 @@ class QueryTest extends DatabaseTestCase } /** + * @depends testFilterWhere + */ + public function testAndFilterCompare() + { + $query = new Query; + + $result = $query->andFilterCompare('name', null); + $this->assertInstanceOf('yii\db\Query', $result); + $this->assertNull($query->where); + + $query->andFilterCompare('name', ''); + $this->assertNull($query->where); + + $query->andFilterCompare('name', 'John Doe'); + $condition = ['=', 'name', 'John Doe']; + $this->assertEquals($condition, $query->where); + + $condition = ['and', $condition, ['like', 'name', 'Doe']]; + $query->andFilterCompare('name', 'Doe', 'like'); + $this->assertEquals($condition, $query->where); + + $condition = ['and', $condition, ['>', 'rating', '9']]; + $query->andFilterCompare('rating', '>9'); + $this->assertEquals($condition, $query->where); + + $condition = ['and', $condition, ['<=', 'value', '100']]; + $query->andFilterCompare('value', '<=100'); + $this->assertEquals($condition, $query->where); + } + + /** * @see https://github.com/yiisoft/yii2/issues/8068 * * @depends testCount diff --git a/tests/framework/db/cubrid/CubridConnectionTest.php b/tests/framework/db/cubrid/CubridConnectionTest.php index 9b432d0..7dbd0f3 100644 --- a/tests/framework/db/cubrid/CubridConnectionTest.php +++ b/tests/framework/db/cubrid/CubridConnectionTest.php @@ -26,6 +26,7 @@ class CubridConnectionTest extends ConnectionTest $this->assertEquals('"table"', $connection->quoteTableName('"table"')); $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('"schema"."table"')); $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); $this->assertEquals('(table)', $connection->quoteTableName('(table)')); } @@ -35,10 +36,33 @@ class CubridConnectionTest extends ConnectionTest $connection = $this->getConnection(false); $this->assertEquals('"column"', $connection->quoteColumnName('column')); $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); - $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); - $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + + $this->assertEquals('"column"', $connection->quoteSql('[[column]]')); + $this->assertEquals('"column"', $connection->quoteSql('{{column}}')); + } + + public function testQuoteFullColumnName() + { + $connection = $this->getConnection(false, false); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table".column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); + + $this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); + $this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}.column')); + $this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}."column"')); + $this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); + $this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}.column')); + $this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}."column"')); + + $this->assertEquals('"table"."column"', $connection->quoteSql('[[table.column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}.[[column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}."column"')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}.[[column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}."column"')); } } diff --git a/tests/framework/db/mssql/MssqlConnectionTest.php b/tests/framework/db/mssql/MssqlConnectionTest.php index 02f7bfd..8a3a214 100644 --- a/tests/framework/db/mssql/MssqlConnectionTest.php +++ b/tests/framework/db/mssql/MssqlConnectionTest.php @@ -27,6 +27,7 @@ class MssqlConnectionTest extends ConnectionTest $this->assertEquals('[table]', $connection->quoteTableName('[table]')); $this->assertEquals('[schema].[table]', $connection->quoteTableName('schema.table')); $this->assertEquals('[schema].[table]', $connection->quoteTableName('schema.[table]')); + $this->assertEquals('[schema].[table]', $connection->quoteTableName('[schema].[table]')); $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); $this->assertEquals('(table)', $connection->quoteTableName('(table)')); } @@ -36,10 +37,33 @@ class MssqlConnectionTest extends ConnectionTest $connection = $this->getConnection(false); $this->assertEquals('[column]', $connection->quoteColumnName('column')); $this->assertEquals('[column]', $connection->quoteColumnName('[column]')); - $this->assertEquals('[table].[column]', $connection->quoteColumnName('table.column')); - $this->assertEquals('[table].[column]', $connection->quoteColumnName('table.[column]')); $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + + $this->assertEquals('[column]', $connection->quoteSql('[[column]]')); + $this->assertEquals('[column]', $connection->quoteSql('{{column}}')); + } + + public function testQuoteFullColumnName() + { + $connection = $this->getConnection(false, false); + $this->assertEquals('[table].[column]', $connection->quoteColumnName('table.column')); + $this->assertEquals('[table].[column]', $connection->quoteColumnName('table.[column]')); + $this->assertEquals('[table].[column]', $connection->quoteColumnName('[table].column')); + $this->assertEquals('[table].[column]', $connection->quoteColumnName('[table].[column]')); + + $this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); + $this->assertEquals('{{table}}.[column]', $connection->quoteColumnName('{{table}}.column')); + $this->assertEquals('{{table}}.[column]', $connection->quoteColumnName('{{table}}.[column]')); + $this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); + $this->assertEquals('{{%table}}.[column]', $connection->quoteColumnName('{{%table}}.column')); + $this->assertEquals('{{%table}}.[column]', $connection->quoteColumnName('{{%table}}.[column]')); + + $this->assertEquals('[table].[column]', $connection->quoteSql('[[table.column]]')); + $this->assertEquals('[table].[column]', $connection->quoteSql('{{table}}.[[column]]')); + $this->assertEquals('[table].[column]', $connection->quoteSql('{{table}}.[column]')); + $this->assertEquals('[table].[column]', $connection->quoteSql('{{%table}}.[[column]]')); + $this->assertEquals('[table].[column]', $connection->quoteSql('{{%table}}.[column]')); } } diff --git a/tests/framework/db/mssql/MssqlQueryBuilderTest.php b/tests/framework/db/mssql/MssqlQueryBuilderTest.php index c821cea..404079e 100644 --- a/tests/framework/db/mssql/MssqlQueryBuilderTest.php +++ b/tests/framework/db/mssql/MssqlQueryBuilderTest.php @@ -56,6 +56,32 @@ class MssqlQueryBuilderTest extends QueryBuilderTest $this->assertEquals($expectedQueryParams, $actualQueryParams); } + public function testCommentColumn() + { + $qb = $this->getQueryBuilder(); + + $expected = "sp_updateextendedproperty @name = N'MS_Description', @value = 'This is my column.', @level1type = N'Table', @level1name = comment, @level2type = N'Column', @level2name = text"; + $sql = $qb->addCommentOnColumn('comment', 'text', 'This is my column.'); + $this->assertEquals($expected, $sql); + + $expected = "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = comment, @level2type = N'Column', @level2name = text"; + $sql = $qb->dropCommentFromColumn('comment', 'text'); + $this->assertEquals($expected, $sql); + } + + public function testCommentTable() + { + $qb = $this->getQueryBuilder(); + + $expected = "sp_updateextendedproperty @name = N'MS_Description', @value = 'This is my table.', @level1type = N'Table', @level1name = comment"; + $sql = $qb->addCommentOnTable('comment', 'This is my table.'); + $this->assertEquals($expected, $sql); + + $expected = "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = comment"; + $sql = $qb->dropCommentFromTable('comment'); + $this->assertEquals($expected, $sql); + } + /** * this is not used as a dataprovider for testGetColumnType to speed up the test * when used as dataprovider every single line will cause a reconnect with the database which is not needed here diff --git a/tests/framework/db/mysql/MysqlQueryBuilderTest.php b/tests/framework/db/mysql/MysqlQueryBuilderTest.php index 7b57cc4..e0ea3fe 100644 --- a/tests/framework/db/mysql/MysqlQueryBuilderTest.php +++ b/tests/framework/db/mysql/MysqlQueryBuilderTest.php @@ -19,9 +19,9 @@ class MysqlQueryBuilderTest extends QueryBuilderTest { return array_merge(parent::columnTypes(), [ [ - Schema::TYPE_PK . ' AFTER (`col_before`)', + Schema::TYPE_PK . ' AFTER `col_before`', $this->primaryKey()->after('col_before'), - 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER (`col_before`)' + 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`' ], [ Schema::TYPE_PK . ' FIRST', @@ -34,9 +34,9 @@ class MysqlQueryBuilderTest extends QueryBuilderTest 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST' ], [ - Schema::TYPE_PK . '(8) AFTER (`col_before`)', + Schema::TYPE_PK . '(8) AFTER `col_before`', $this->primaryKey(8)->after('col_before'), - 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER (`col_before`)' + 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`' ], [ Schema::TYPE_PK . '(8) FIRST', @@ -48,6 +48,11 @@ class MysqlQueryBuilderTest extends QueryBuilderTest $this->primaryKey(8)->first()->after('col_before'), 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST' ], + [ + Schema::TYPE_PK . " COMMENT 'test' AFTER `col_before`", + $this->primaryKey()->comment('test')->after('col_before'), + "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test' AFTER `col_before`" + ], ]); } } diff --git a/tests/framework/db/oci/OracleQueryBuilderTest.php b/tests/framework/db/oci/OracleQueryBuilderTest.php index c082f89..00f200d 100644 --- a/tests/framework/db/oci/OracleQueryBuilderTest.php +++ b/tests/framework/db/oci/OracleQueryBuilderTest.php @@ -27,4 +27,31 @@ class OracleQueryBuilderTest extends QueryBuilderTest ], ]); } + + + public function testCommentColumn() + { + $qb = $this->getQueryBuilder(); + + $expected = "COMMENT ON COLUMN [[comment]].[[text]] IS 'This is my column.'"; + $sql = $qb->addCommentOnColumn('comment', 'text', 'This is my column.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "COMMENT ON COLUMN [[comment]].[[text]] IS ''"; + $sql = $qb->dropCommentFromColumn('comment', 'text'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } + + public function testCommentTable() + { + $qb = $this->getQueryBuilder(); + + $expected = "COMMENT ON TABLE [[comment]] IS 'This is my table.'"; + $sql = $qb->addCommentOnTable('comment', 'This is my table.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "COMMENT ON TABLE [[comment]] IS ''"; + $sql = $qb->dropCommentFromTable('comment'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } } diff --git a/tests/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/framework/db/pgsql/PostgreSQLConnectionTest.php index c569a44..2206b75 100644 --- a/tests/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -42,12 +42,34 @@ class PostgreSQLConnectionTest extends ConnectionTest $connection = $this->getConnection(false); $this->assertEquals('"column"', $connection->quoteColumnName('column')); $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); - $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); - $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); - $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + + $this->assertEquals('"column"', $connection->quoteSql('[[column]]')); + $this->assertEquals('"column"', $connection->quoteSql('{{column}}')); + } + + public function testQuoteFullColumnName() + { + $connection = $this->getConnection(false, false); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table".column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); + + $this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); + $this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}.column')); + $this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}."column"')); + $this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); + $this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}.column')); + $this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}."column"')); + + $this->assertEquals('"table"."column"', $connection->quoteSql('[[table.column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}.[[column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}."column"')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}.[[column]]')); + $this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}."column"')); } public function testTransactionIsolation() diff --git a/tests/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index b2aa8ab..775f1b5 100644 --- a/tests/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -95,4 +95,30 @@ class PostgreSQLQueryBuilderTest extends QueryBuilderTest $sql = $qb->alterColumn('foo1', 'bar', 'reset xyz'); $this->assertEquals($expected, $sql); } + + public function testCommentColumn() + { + $qb = $this->getQueryBuilder(); + + $expected = "COMMENT ON COLUMN [[comment]].[[text]] IS 'This is my column.'"; + $sql = $qb->addCommentOnColumn('comment', 'text', 'This is my column.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "COMMENT ON COLUMN [[comment]].[[text]] IS NULL"; + $sql = $qb->dropCommentFromColumn('comment', 'text'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } + + public function testCommentTable() + { + $qb = $this->getQueryBuilder(); + + $expected = "COMMENT ON TABLE [[comment]] IS 'This is my table.'"; + $sql = $qb->addCommentOnTable('comment', 'This is my table.'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + + $expected = "COMMENT ON TABLE [[comment]] IS NULL"; + $sql = $qb->dropCommentFromTable('comment'); + $this->assertEquals($this->replaceQuotes($expected), $sql); + } } diff --git a/tests/framework/db/sqlite/SqliteConnectionTest.php b/tests/framework/db/sqlite/SqliteConnectionTest.php index 0978158..e7868df 100644 --- a/tests/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/framework/db/sqlite/SqliteConnectionTest.php @@ -31,25 +31,6 @@ class SqliteConnectionTest extends ConnectionTest $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); } - public function testQuoteTableName() - { - $connection = $this->getConnection(false); - $this->assertEquals("`table`", $connection->quoteTableName('table')); - $this->assertEquals("`schema`.`table`", $connection->quoteTableName('schema.table')); - $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); - $this->assertEquals('(table)', $connection->quoteTableName('(table)')); - } - - public function testQuoteColumnName() - { - $connection = $this->getConnection(false); - $this->assertEquals('`column`', $connection->quoteColumnName('column')); - $this->assertEquals("`table`.`column`", $connection->quoteColumnName('table.column')); - $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); - $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); - $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); - } - public function testTransactionIsolation() { $connection = $this->getConnection(true); diff --git a/tests/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/framework/db/sqlite/SqliteQueryBuilderTest.php index f94885d..c4834a1 100644 --- a/tests/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -4,6 +4,7 @@ namespace yiiunit\framework\db\sqlite; use yii\db\Query; use yii\db\Schema; +use yiiunit\data\base\TraversableObject; use yiiunit\framework\db\QueryBuilderTest; /** @@ -25,10 +26,38 @@ class SqliteQueryBuilderTest extends QueryBuilderTest ]); } + public function conditionProvider() + { + return array_merge(parent::conditionProvider(), [ + 'composite in using array objects' => [ + ['in', new TraversableObject(['id', 'name']), new TraversableObject([ + ['id' => 1, 'name' => 'oy'], + ['id' => 2, 'name' => 'yo'], + ])], + '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))', + [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'] + ], + 'composite in' => [ + ['in', ['id', 'name'], [['id' =>1, 'name' => 'oy']]], + '(([[id]] = :qp0 AND [[name]] = :qp1))', + [':qp0' => 1, ':qp1' => 'oy'] + ], + ]); + } + public function testAddDropPrimaryKey() { - $this->setExpectedException('yii\base\NotSupportedException'); - parent::testAddDropPrimaryKey(); + $this->markTestSkipped('Comments are not supported in SQLite'); + } + + public function testCommentColumn() + { + $this->markTestSkipped('Comments are not supported in SQLite'); + } + + public function testCommentTable() + { + $this->markTestSkipped('Comments are not supported in SQLite'); } public function testBatchInsert() diff --git a/tests/framework/grid/CheckboxColumnTest.php b/tests/framework/grid/CheckboxColumnTest.php index a97d0e3..b69b795 100644 --- a/tests/framework/grid/CheckboxColumnTest.php +++ b/tests/framework/grid/CheckboxColumnTest.php @@ -56,19 +56,20 @@ class CheckboxColumnTest extends TestCase public function testInputValue() { - $column = new CheckboxColumn(); + $column = new CheckboxColumn(['grid' => $this->getGrid()]); $this->assertContains('value="1"', $column->renderDataCell([], 1, 0)); $this->assertContains('value="42"', $column->renderDataCell([], 42, 0)); $this->assertContains('value="[1,42]"', $column->renderDataCell([], [1, 42], 0)); - $column = new CheckboxColumn(['checkboxOptions' => ['value' => 42]]); + $column = new CheckboxColumn(['checkboxOptions' => ['value' => 42], 'grid' => $this->getGrid()]); $this->assertNotContains('value="1"', $column->renderDataCell([], 1, 0)); $this->assertContains('value="42"', $column->renderDataCell([], 1, 0)); $column = new CheckboxColumn([ 'checkboxOptions' => function ($model, $key, $index, $column) { return []; - } + }, + 'grid' => $this->getGrid() ]); $this->assertContains('value="1"', $column->renderDataCell([], 1, 0)); $this->assertContains('value="42"', $column->renderDataCell([], 42, 0)); @@ -77,7 +78,8 @@ class CheckboxColumnTest extends TestCase $column = new CheckboxColumn([ 'checkboxOptions' => function ($model, $key, $index, $column) { return ['value' => 42]; - } + }, + 'grid' => $this->getGrid() ]); $this->assertNotContains('value="1"', $column->renderDataCell([], 1, 0)); $this->assertContains('value="42"', $column->renderDataCell([], 1, 0)); diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index 0a100b3..8ef2e6c 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -27,6 +27,7 @@ class Post2 extends Object class Post3 extends Object { public $id = 33; + /** @var Object */ public $subObject; public function init() @@ -88,6 +89,45 @@ class ArrayHelperTest extends TestCase 'content' => 'test', ], ], ArrayHelper::toArray($object)); + + //recursive with attributes of object and subobject + $this->assertEquals([ + 'id' => 33, + 'id_plus_1' => 34, + 'subObject' => [ + 'id' => 123, + 'id_plus_1' => 124, + ], + ], ArrayHelper::toArray($object, [ + $object->className() => [ + 'id', 'subObject', + 'id_plus_1' => function ($post) { + return $post->id+1; + } + ], + $object->subObject->className() => [ + 'id', + 'id_plus_1' => function ($post) { + return $post->id+1; + } + ], + ])); + + //recursive with attributes of subobject only + $this->assertEquals([ + 'id' => 33, + 'subObject' => [ + 'id' => 123, + 'id_plus_1' => 124, + ], + ], ArrayHelper::toArray($object, [ + $object->subObject->className() => [ + 'id', + 'id_plus_1' => function ($post) { + return $post->id+1; + } + ], + ])); } public function testRemove() diff --git a/tests/framework/helpers/FileHelperTest.php b/tests/framework/helpers/FileHelperTest.php index c052715..f08ef27 100644 --- a/tests/framework/helpers/FileHelperTest.php +++ b/tests/framework/helpers/FileHelperTest.php @@ -253,6 +253,57 @@ class FileHelperTest extends TestCase $this->assertFileMode($fileMode, $dstDirName . DIRECTORY_SEPARATOR . $fileName, 'Copied file has wrong mode!'); } + /** + * @see https://github.com/yiisoft/yii2/issues/10710 + */ + public function testCopyDirectoryToItself() + { + $dirName = 'test_dir'; + + $this->createFileStructure([ + $dirName => [], + ]); + + $this->setExpectedException('yii\base\InvalidParamException'); + + $dirName = $this->testFilePath . DIRECTORY_SEPARATOR . 'test_dir'; + FileHelper::copyDirectory($dirName, $dirName); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/10710 + */ + public function testCopyDirToSubdirOfItself() + { + $this->createFileStructure([ + 'data' => [], + 'backup' => ['data' => []] + ]); + + $this->setExpectedException('yii\base\InvalidParamException'); + + FileHelper::copyDirectory( + $this->testFilePath . DIRECTORY_SEPARATOR . 'backup', + $this->testFilePath . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'data' + ); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/10710 + */ + public function testCopyDirToAnotherWithSameName() + { + $this->createFileStructure([ + 'data' => [], + 'backup' => ['data' => []] + ]); + + FileHelper::copyDirectory( + $this->testFilePath . DIRECTORY_SEPARATOR . 'data', + $this->testFilePath . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'data' + ); + } + public function testRemoveDirectory() { $dirName = 'test_dir_for_remove'; diff --git a/tests/framework/log/DispatcherTest.php b/tests/framework/log/DispatcherTest.php new file mode 100644 index 0000000..a16ede9 --- /dev/null +++ b/tests/framework/log/DispatcherTest.php @@ -0,0 +1,71 @@ + + */ + +namespace yiiunit\framework\log; + +use yii\log\Dispatcher; +use yii\log\Logger; +use Yii; +use yiiunit\TestCase; + +/** + * @group log + */ +class DispatcherTest extends TestCase +{ + + public function testConfigureLogger() + { + $dispatcher = new Dispatcher(); + $this->assertSame(Yii::getLogger(), $dispatcher->getLogger()); + + + $logger = new Logger(); + $dispatcher = new Dispatcher([ + 'logger' => $logger, + ]); + $this->assertSame($logger, $dispatcher->getLogger()); + + + $dispatcher = new Dispatcher([ + 'logger' => 'yii\log\Logger', + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(0, $dispatcher->getLogger()->traceLevel); + + + $dispatcher = new Dispatcher([ + 'logger' => [ + 'class' => 'yii\log\Logger', + 'traceLevel' => 42, + ], + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(42, $dispatcher->getLogger()->traceLevel); + } + + public function testSetLogger() + { + $dispatcher = new Dispatcher(); + $this->assertSame(Yii::getLogger(), $dispatcher->getLogger()); + + $logger = new Logger(); + $dispatcher->setLogger($logger); + $this->assertSame($logger, $dispatcher->getLogger()); + + $dispatcher->setLogger('yii\log\Logger'); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(0, $dispatcher->getLogger()->traceLevel); + + + $dispatcher->setLogger([ + 'class' => 'yii\log\Logger', + 'traceLevel' => 42, + ]); + $this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger()); + $this->assertEquals(42, $dispatcher->getLogger()->traceLevel); + } + +} diff --git a/tests/framework/mutex/FileMutexTest.php b/tests/framework/mutex/FileMutexTest.php new file mode 100644 index 0000000..dd4d982 --- /dev/null +++ b/tests/framework/mutex/FileMutexTest.php @@ -0,0 +1,37 @@ +markTestSkipped('FileMutex does not have MS Windows operating system support.'); + } + } + + /** + * @return FileMutex + * @throws \yii\base\InvalidConfigException + */ + protected function createMutex() + { + return \Yii::createObject([ + 'class' => FileMutex::className(), + ]); + } + +} diff --git a/tests/framework/mutex/MutexTestTrait.php b/tests/framework/mutex/MutexTestTrait.php new file mode 100644 index 0000000..2c1df6c --- /dev/null +++ b/tests/framework/mutex/MutexTestTrait.php @@ -0,0 +1,39 @@ +createMutex(); + + $this->assertTrue($mutex->acquire(self::$mutexName)); + } + + public function testThatMutexLockIsWorking() + { + $mutexOne = $this->createMutex(); + $mutexTwo = $this->createMutex(); + + $this->assertTrue($mutexOne->acquire(self::$mutexName)); + $this->assertFalse($mutexTwo->acquire(self::$mutexName)); + + $mutexOne->release(self::$mutexName); + + $this->assertTrue($mutexTwo->acquire(self::$mutexName)); + } +} diff --git a/tests/framework/mutex/MysqlMutexTest.php b/tests/framework/mutex/MysqlMutexTest.php new file mode 100644 index 0000000..a339064 --- /dev/null +++ b/tests/framework/mutex/MysqlMutexTest.php @@ -0,0 +1,34 @@ + MysqlMutex::className(), + 'db' => $this->getConnection(), + ]); + } + +} diff --git a/tests/framework/mutex/PgsqlMutexTest.php b/tests/framework/mutex/PgsqlMutexTest.php index 60dea78..3eef405 100644 --- a/tests/framework/mutex/PgsqlMutexTest.php +++ b/tests/framework/mutex/PgsqlMutexTest.php @@ -1,6 +1,6 @@ createMutex(); - - $this->assertTrue($mutex->acquire(self::MUTEX_NAME)); - } - /** * @return PgsqlMutex * @throws \yii\base\InvalidConfigException */ - private function createMutex() + protected function createMutex() { return \Yii::createObject([ 'class' => PgsqlMutex::className(), @@ -35,16 +31,4 @@ class PgsqlMutexTest extends DatabaseTestCase ]); } - public function testThatMutexLockIsWorking() - { - $mutexOne = $this->createMutex(); - $mutexTwo = $this->createMutex(); - - $this->assertTrue($mutexOne->acquire(self::MUTEX_NAME)); - $this->assertFalse($mutexTwo->acquire(self::MUTEX_NAME)); - - $mutexOne->release(self::MUTEX_NAME); - - $this->assertTrue($mutexTwo->acquire(self::MUTEX_NAME)); - } } diff --git a/tests/framework/validators/DateValidatorTest.php b/tests/framework/validators/DateValidatorTest.php index 19d58db..b85fd48 100644 --- a/tests/framework/validators/DateValidatorTest.php +++ b/tests/framework/validators/DateValidatorTest.php @@ -280,6 +280,37 @@ class DateValidatorTest extends TestCase public function testIntlValidationWithTime($timezone) { $this->testValidationWithTime($timezone); + + $this->mockApplication([ + 'language' => 'en-GB', + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'long', + 'datetimeFormat' => 'short', // this is the format to be used by the validator by default + ] + ] + ]); + $val = new DateValidator(['type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31/5/2017 12:30')); + $this->assertFalse($val->validate('5/31/2017 12:30')); + $val = new DateValidator(['format' => 'short', 'locale' => 'en-GB', 'type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31/5/2017 12:30')); + $this->assertFalse($val->validate('5/31/2017 12:30')); + $this->mockApplication([ + 'language' => 'de-DE', + 'components' => [ + 'formatter' => [ + 'dateFormat' => 'long', + 'datetimeFormat' => 'short', // this is the format to be used by the validator by default + ] + ] + ]); + $val = new DateValidator(['type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31.5.2017 12:30')); + $this->assertFalse($val->validate('5.31.2017 12:30')); + $val = new DateValidator(['format' => 'short', 'locale' => 'de-DE', 'type' => DateValidator::TYPE_DATETIME]); + $this->assertTrue($val->validate('31.5.2017 12:30')); + $this->assertFalse($val->validate('5.31.2017 12:30')); } /** diff --git a/tests/framework/web/DbSessionTest.php b/tests/framework/web/DbSessionTest.php index 61a6b8f..94411fd 100644 --- a/tests/framework/web/DbSessionTest.php +++ b/tests/framework/web/DbSessionTest.php @@ -6,6 +6,7 @@ use Yii; use yii\db\Connection; use yii\db\Query; use yii\web\DbSession; +use yiiunit\framework\console\controllers\EchoMigrateController; use yiiunit\TestCase; /** @@ -95,7 +96,7 @@ class DbSessionTest extends TestCase protected function runMigrate($action, $params = []) { - $migrate = new \yii\console\controllers\MigrateController('migrate', Yii::$app, [ + $migrate = new EchoMigrateController('migrate', Yii::$app, [ 'migrationPath' => '@yii/web/migrations', 'interactive' => false, ]); @@ -120,7 +121,7 @@ class DbSessionTest extends TestCase ] ], ]); - + $history = $this->runMigrate('history'); $this->assertEquals(['base'], $history); diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 8c161e8..7e048d9 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -229,6 +229,20 @@ class UserTest extends TestCase $this->assertEquals('accept-html-json', $user->getReturnUrl()); $this->assertTrue(Yii::$app->response->getIsRedirection()); + $this->reset(); + $_SERVER['REQUEST_METHOD'] = 'POST'; + Yii::$app->request->setUrl('dont-set-return-url-on-post-request'); + Yii::$app->getSession()->set($user->returnUrlParam, null); + $user->loginRequired(); + $this->assertNull(Yii::$app->getSession()->get($user->returnUrlParam)); + + $this->reset(); + $_SERVER['REQUEST_METHOD'] = 'GET'; + Yii::$app->request->setUrl('set-return-url-on-get-request'); + Yii::$app->getSession()->set($user->returnUrlParam, null); + $user->loginRequired(); + $this->assertEquals('set-return-url-on-get-request', Yii::$app->getSession()->get($user->returnUrlParam)); + // Confirm that returnUrl is not set. $this->reset(); Yii::$app->request->setUrl('json-only'); @@ -238,7 +252,6 @@ class UserTest extends TestCase } catch (ForbiddenHttpException $e) {} $this->assertNotEquals('json-only', $user->getReturnUrl()); - $this->reset(); $_SERVER['HTTP_ACCEPT'] = 'text/json;q=0.1'; $this->setExpectedException('yii\\web\\ForbiddenHttpException');