diff --git a/docs/guide/structure-application-components.md b/docs/guide/structure-application-components.md index eeaac00baf..3f18c2b327 100644 --- a/docs/guide/structure-application-components.md +++ b/docs/guide/structure-application-components.md @@ -58,12 +58,22 @@ If it is not accessed at all during a request, it will not be instantiated. Some to instantiate an application component for every request, even if it is not explicitly accessed. To do so, you may list its ID in the [[yii\base\Application::bootstrap|bootstrap]] property of the application. +You can also use Closures to bootstrap customized components. Returning a instantiated component is not +required. A Closure can also be used simply for running code after [[yii\base\Application]] instantiation. + For example, the following application configuration makes sure the `log` component is always loaded: ```php [ 'bootstrap' => [ 'log', + function($app){ + return new ComponentX(); + }, + function($app){ + // some code + return; + } ], 'components' => [ 'log' => [ diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index cbe680c380..c6b7a1f9c0 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -3,7 +3,7 @@ Yii Framework 2 Change Log 2.0.13 under development ------------------------ - +- Bug #14449: Fix PHP 7.2 compatibility bugs and add explicit closure support in `yii\base\Application` (dynasource) - Bug #7890: Allow `migrate/mark` to mark history at the point of the base migration (cebe) - Bug #14206: `MySqlMutex`, `PgsqlMutex` and `OracleMutex` now use `useMaster()` to ensure lock is aquired on the same DB server (cebe, ryusoft) - Chg #14321: `yii\widgets\MaskedInput` is now registering its JavaScript `clientOptions` initialization code in head section (DaveFerger) diff --git a/framework/base/Application.php b/framework/base/Application.php index 2cb68cac9e..441f00cf03 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -168,6 +168,7 @@ abstract class Application extends Module * - a module ID as specified via [[modules]]. * - a class name. * - a configuration array. + * - a Closure * * During the bootstrapping process, each component will be instantiated. If the component class * implements [[BootstrapInterface]], its [[BootstrapInterface::bootstrap()|bootstrap()]] method @@ -300,19 +301,25 @@ abstract class Application extends Module } } - foreach ($this->bootstrap as $class) { + foreach ($this->bootstrap as $mixed) { $component = null; - if (is_string($class)) { - if ($this->has($class)) { - $component = $this->get($class); - } elseif ($this->hasModule($class)) { - $component = $this->getModule($class); - } elseif (strpos($class, '\\') === false) { - throw new InvalidConfigException("Unknown bootstrapping component ID: $class"); + if ($mixed instanceof \Closure) { + Yii::trace('Bootstrap with Closure', __METHOD__); + if (!$component = call_user_func($mixed, $this)) { + continue; + } + } elseif (is_string($mixed)) { + if ($this->has($mixed)) { + $component = $this->get($mixed); + } elseif ($this->hasModule($mixed)) { + $component = $this->getModule($mixed); + } elseif (strpos($mixed, '\\') === false) { + throw new InvalidConfigException("Unknown bootstrapping component ID: $mixed"); } } + if (!isset($component)) { - $component = Yii::createObject($class); + $component = Yii::createObject($mixed); } if ($component instanceof BootstrapInterface) { diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 20b81f4fde..5a4b1fc7ee 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -1350,7 +1350,7 @@ class QueryBuilder extends \yii\base\Object $values = (array) $values; } - if ($column instanceof \Traversable || count($column) > 1) { + if ($column instanceof \Traversable || ((is_array($column) || $column instanceof \Countable) && count($column) > 1)) { return $this->buildCompositeInCondition($operator, $column, $values, $params); } elseif (is_array($column)) { $column = reset($column); diff --git a/tests/framework/base/ApplicationTest.php b/tests/framework/base/ApplicationTest.php index f3ce06cabb..8834f9a499 100644 --- a/tests/framework/base/ApplicationTest.php +++ b/tests/framework/base/ApplicationTest.php @@ -8,6 +8,9 @@ namespace yiiunit\framework\base; use Yii; +use yii\base\BootstrapInterface; +use yii\base\Component; +use yii\base\Module; use yii\log\Dispatcher; use yiiunit\TestCase; @@ -29,8 +32,50 @@ class ApplicationTest extends TestCase $this->assertInstanceOf(DispatcherMock::className(), Yii::$app->log); } + + public function testBootstrap() + { + Yii::getLogger()->flush(); + + + $this->mockApplication([ + 'components' => [ + 'withoutBootstrapInterface' => [ + 'class' => Component::class + ], + 'withBootstrapInterface' => [ + 'class' => BootstrapComponentMock::class + ] + ], + 'modules' => [ + 'moduleX' => [ + 'class' => Module::class + ] + ], + 'bootstrap' => [ + 'withoutBootstrapInterface', + 'withBootstrapInterface', + 'moduleX', + function () { + } + + ], + ]); + $this->assertSame('Bootstrap with yii\base\Component', Yii::getLogger()->messages[0][0]); + $this->assertSame('Bootstrap with yiiunit\framework\base\BootstrapComponentMock::bootstrap()', Yii::getLogger()->messages[1][0]); + $this->assertSame('Loading module: moduleX', Yii::getLogger()->messages[2][0]); + $this->assertSame('Bootstrap with yii\base\Module', Yii::getLogger()->messages[3][0]); + $this->assertSame('Bootstrap with Closure', Yii::getLogger()->messages[4][0]); + } } class DispatcherMock extends Dispatcher { } + +class BootstrapComponentMock extends Component implements BootstrapInterface +{ + public function bootstrap($app) + { + } +} \ No newline at end of file