Browse Source

Merge pull request #11832 from cakephp/issue-11787

Fix recursive plugin loading
Mark Story 8 years ago
parent
commit
a9a01992eb

+ 1 - 0
composer.json

@@ -65,6 +65,7 @@
             "TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src/",
             "Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src/",
             "Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests/",
+            "ParentPlugin\\": "tests/test_app/Plugin/ParentPlugin/src/",
             "PluginJs\\": "tests/test_app/Plugin/PluginJs/src/"
         }
     },

+ 1 - 0
src/Core/Plugin.php

@@ -409,6 +409,7 @@ class Plugin
     /**
      * Get the shared plugin collection.
      *
+     * @internal
      * @return \Cake\Core\PluginCollection
      */
     public static function getCollection()

+ 66 - 6
src/Core/PluginCollection.php

@@ -17,7 +17,7 @@ use ArrayIterator;
 use Cake\Core\Exception\MissingPluginException;
 use Countable;
 use InvalidArgumentException;
-use IteratorAggregate;
+use Iterator;
 use RuntimeException;
 
 /**
@@ -26,8 +26,12 @@ use RuntimeException;
  * Holds onto plugin objects loaded into an application, and
  * provides methods for iterating, and finding plugins based
  * on criteria.
+ *
+ * This class implements the Iterator interface to allow plugins
+ * to be iterated, handling the situation where a plugin's hook
+ * method (usually bootstrap) loads another plugin during iteration.
  */
-class PluginCollection implements IteratorAggregate, Countable
+class PluginCollection implements Iterator, Countable
 {
     /**
      * Plugin list
@@ -37,6 +41,18 @@ class PluginCollection implements IteratorAggregate, Countable
     protected $plugins = [];
 
     /**
+     * Names of plugins
+     *
+     * @var array
+     */
+    protected $names = [];
+
+    /**
+     * @var null|string
+     */
+    protected $position = 0;
+
+    /**
      * Constructor
      *
      * @param array $plugins The map of plugins to add to the collection.
@@ -60,6 +76,7 @@ class PluginCollection implements IteratorAggregate, Countable
     {
         $name = $plugin->getName();
         $this->plugins[$name] = $plugin;
+        $this->names = array_keys($this->plugins);
 
         return $this;
     }
@@ -73,6 +90,7 @@ class PluginCollection implements IteratorAggregate, Countable
     public function remove($name)
     {
         unset($this->plugins[$name]);
+        $this->names = array_keys($this->plugins);
 
         return $this;
     }
@@ -105,13 +123,55 @@ class PluginCollection implements IteratorAggregate, Countable
     }
 
     /**
-     * Implementation of IteratorAggregate.
+     * Part of Iterator Interface
+     *
+     * @return void
+     */
+    public function next()
+    {
+        $this->position += 1;
+    }
+
+    /**
+     * Part of Iterator Interface
+     *
+     * @return string
+     */
+    public function key()
+    {
+        return $this->names[$this->position];
+    }
+
+    /**
+     * Part of Iterator Interface
+     *
+     * @return \Cake\Core\PluginInterface
+     */
+    public function current()
+    {
+        $name = $this->names[$this->position];
+
+        return $this->plugins[$name];
+    }
+
+    /**
+     * Part of Iterator Interface
      *
-     * @return \ArrayIterator
+     * @return void
+     */
+    public function rewind()
+    {
+        $this->position = 0;
+    }
+
+    /**
+     * Part of Iterator Interface
+     *
+     * @return bool
      */
-    public function getIterator()
+    public function valid()
     {
-        return new ArrayIterator($this->plugins);
+        return $this->position < count($this->plugins);
     }
 
     /**

+ 12 - 3
tests/TestCase/Core/PluginTest.php

@@ -301,7 +301,10 @@ class PluginTest extends TestCase
     public function testLoadAll()
     {
         Plugin::loadAll();
-        $expected = ['Company', 'PluginJs', 'TestPlugin', 'TestPluginFour', 'TestPluginTwo', 'TestTheme'];
+        $expected = [
+            'Company', 'ParentPlugin', 'PluginJs', 'TestPlugin',
+            'TestPluginFour', 'TestPluginTwo', 'TestTheme'
+        ];
         $this->assertEquals($expected, Plugin::loaded());
     }
 
@@ -338,7 +341,10 @@ class PluginTest extends TestCase
     {
         $defaults = ['bootstrap' => true, 'ignoreMissing' => true];
         Plugin::loadAll([$defaults]);
-        $expected = ['Company', 'PluginJs', 'TestPlugin', 'TestPluginFour', 'TestPluginTwo', 'TestTheme'];
+        $expected = [
+            'Company', 'ParentPlugin', 'PluginJs', 'TestPlugin',
+            'TestPluginFour', 'TestPluginTwo', 'TestTheme'
+        ];
         $this->assertEquals($expected, Plugin::loaded());
         $this->assertEquals('loaded js plugin bootstrap', Configure::read('PluginTest.js_plugin.bootstrap'));
         $this->assertEquals('loaded plugin bootstrap', Configure::read('PluginTest.test_plugin.bootstrap'));
@@ -360,7 +366,10 @@ class PluginTest extends TestCase
         ]);
         Plugin::routes();
 
-        $expected = ['Company', 'PluginJs', 'TestPlugin', 'TestPluginFour', 'TestPluginTwo', 'TestTheme'];
+        $expected = [
+            'Company', 'ParentPlugin', 'PluginJs', 'TestPlugin',
+            'TestPluginFour', 'TestPluginTwo', 'TestTheme'
+        ];
         $this->assertEquals($expected, Plugin::loaded());
         $this->assertEquals('loaded js plugin bootstrap', Configure::read('PluginTest.js_plugin.bootstrap'));
         $this->assertEquals('loaded plugin routes', Configure::read('PluginTest.test_plugin.routes'));

+ 29 - 0
tests/TestCase/Http/BaseApplicationTest.php

@@ -182,4 +182,33 @@ class BaseApplicationTest extends TestCase
             'Key should not be set, as plugin has already had bootstrap run'
         );
     }
+
+    /**
+     * Test that plugins loaded with addPlugin() can load additional
+     * plugins.
+     *
+     * @return void
+     */
+    public function testPluginBootstrapRecursivePlugins()
+    {
+        $app = $this->getMockForAbstractClass(
+            BaseApplication::class,
+            [$this->path]
+        );
+        $app->addPlugin('ParentPlugin');
+        $app->pluginBootstrap();
+
+        $this->assertTrue(
+            Configure::check('ParentPlugin.bootstrap'),
+            'Plugin bootstrap should be run'
+        );
+        $this->assertTrue(
+            Configure::check('PluginTest.test_plugin.bootstrap'),
+            'Nested plugin should have bootstrap run'
+        );
+        $this->assertTrue(
+            Configure::check('PluginTest.test_plugin_two.bootstrap'),
+            'Nested plugin should have bootstrap run'
+        );
+    }
 }

+ 18 - 0
tests/test_app/Plugin/ParentPlugin/src/Plugin.php

@@ -0,0 +1,18 @@
+<?php
+namespace ParentPlugin;
+
+use Cake\Core\BasePlugin;
+use Cake\Core\Configure;
+use Cake\Core\PluginApplicationInterface;
+use Cake\Core\Plugin as CorePlugin;
+
+class Plugin extends BasePlugin
+{
+    public function bootstrap(PluginApplicationInterface $app)
+    {
+        Configure::write('ParentPlugin.bootstrap', true);
+
+        CorePlugin::load('TestPluginTwo', ['bootstrap' => true]);
+        $app->addPlugin('TestPlugin', ['bootstrap' => true]);
+    }
+}