Browse Source

Update PluginLoadCommand to modify new config file.

Also add new options for plugin loading.
ADmad 3 years ago
parent
commit
1a6181f577
2 changed files with 114 additions and 66 deletions
  1. 78 55
      src/Command/PluginLoadCommand.php
  2. 36 11
      tests/TestCase/Command/PluginLoadCommandTest.php

+ 78 - 55
src/Command/PluginLoadCommand.php

@@ -21,6 +21,8 @@ use Cake\Console\ConsoleIo;
 use Cake\Console\ConsoleOptionParser;
 use Cake\Core\Exception\MissingPluginException;
 use Cake\Core\Plugin;
+use Cake\Core\PluginInterface;
+use Cake\Utility\Hash;
 
 /**
  * Command for loading plugins.
@@ -38,18 +40,11 @@ class PluginLoadCommand extends Command
     }
 
     /**
-     * Arguments
+     * Config file
      *
-     * @var \Cake\Console\Arguments
+     * @var string
      */
-    protected Arguments $args;
-
-    /**
-     * Console IO
-     *
-     * @var \Cake\Console\ConsoleIo
-     */
-    protected ConsoleIo $io;
+    protected string $configFile = CONFIG . 'plugins.php';
 
     /**
      * Execute the command
@@ -60,65 +55,70 @@ class PluginLoadCommand extends Command
      */
     public function execute(Arguments $args, ConsoleIo $io): ?int
     {
-        $this->io = $io;
-        $this->args = $args;
+        $plugin = (string)$args->getArgument('plugin');
+        $options = [];
+        if ($args->getOption('only-debug')) {
+            $options['onlyDebug'] = true;
+        }
+        if ($args->getOption('only-cli')) {
+            $options['onlyCli'] = true;
+        }
+        if ($args->getOption('optional')) {
+            $options['optional'] = true;
+        }
+
+        foreach (PluginInterface::VALID_HOOKS as $hook) {
+            if ($args->getOption('no-' . $hook)) {
+                $options[$hook] = false;
+            }
+        }
 
-        $plugin = $args->getArgument('plugin') ?? '';
         try {
             Plugin::getCollection()->findPath($plugin);
         } catch (MissingPluginException $e) {
-            $this->io->err($e->getMessage());
-            $this->io->err('Ensure you have the correct spelling and casing.');
+            /** @psalm-suppress InvalidArgument */
+            if (empty($options['optional'])) {
+                $io->err($e->getMessage());
+                $io->err('Ensure you have the correct spelling and casing.');
 
-            return static::CODE_ERROR;
+                return static::CODE_ERROR;
+            }
         }
 
-        $app = APP . 'Application.php';
-        if (file_exists($app)) {
-            $this->modifyApplication($app, $plugin);
-
-            return static::CODE_SUCCESS;
+        $result = $this->modifyConfigFile($plugin, $options);
+        if ($result === static::CODE_ERROR) {
+            $io->err('Failed to update `CONFIG/plugins.php`');
         }
 
-        return static::CODE_ERROR;
+        return $result;
     }
 
     /**
-     * Modify the application class
+     * Modify the plugins config file.
      *
-     * @param string $app The Application file to modify.
-     * @param string $plugin The plugin name to add.
-     * @return void
+     * @param string $plugin Plugin name.
+     * @param array $options Plugin options.
+     * @return int
      */
-    protected function modifyApplication(string $app, string $plugin): void
+    protected function modifyConfigFile(string $plugin, array $options): int
     {
-        $contents = file_get_contents($app);
-
-        // Find start of bootstrap
-        if (!preg_match('/^(\s+)public function bootstrap(?:\s*)\(\)/mu', $contents, $matches, PREG_OFFSET_CAPTURE)) {
-            $this->io->err('Your Application class does not have a bootstrap() method. Please add one.');
-            $this->abort();
+        // phpcs:ignore
+        $config = @include $this->configFile;
+        if (!is_array($config)) {
+            $config = [];
+        } else {
+            $config = Hash::normalize($config);
         }
 
-        $offset = $matches[0][1];
-        $indent = $matches[1][0];
-
-        // Find closing function bracket
-        if (!preg_match("/^$indent\}\n$/mu", $contents, $matches, PREG_OFFSET_CAPTURE, $offset)) {
-            $this->io->err('Your Application class does not have a bootstrap() method. Please add one.');
-            $this->abort();
-        }
+        $config[$plugin] = $options;
 
-        $append = "$indent    \$this->addPlugin('%s');\n";
-        $insert = str_replace(', []', '', sprintf($append, $plugin));
+        $contents = '<?php' . "\n" . 'return ' . var_export($config, true) . ';';
 
-        $offset = $matches[0][1];
-        $contents = substr_replace($contents, $insert, $offset, 0);
-
-        file_put_contents($app, $contents);
+        if (file_put_contents($this->configFile, $contents)) {
+            return static::CODE_SUCCESS;
+        }
 
-        $this->io->out('');
-        $this->io->out(sprintf('%s modified', $app));
+        return static::CODE_ERROR;
     }
 
     /**
@@ -130,12 +130,35 @@ class PluginLoadCommand extends Command
     public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
     {
         $parser->setDescription([
-            'Command for loading plugins.',
-        ])
-        ->addArgument('plugin', [
-            'help' => 'Name of the plugin to load. Must be in CamelCase format. Example: cake plugin load Example',
-            'required' => true,
-        ]);
+                'Command for loading plugins.',
+            ])->addArgument('plugin', [
+                'help' => 'Name of the plugin to load. Must be in CamelCase format. Example: cake plugin load Example',
+                'required' => true,
+            ])->addOption('only-debug', [
+                'boolean' => true,
+                'help' => 'Load the plugin only when `debug` is enabled.',
+            ])->addOption('only-cli', [
+                'boolean' => true,
+                'help' => 'Load the plugin only for CLI.',
+            ])->addOption('optional', [
+                'boolean' => true,
+                'help' => 'Do not throw an error if the plugin is not available.',
+            ])->addOption('no-bootstrap', [
+                'boolean' => true,
+                'help' => 'Do not run the `bootstrap()` hook.',
+            ])->addOption('no-console', [
+                'boolean' => true,
+                'help' => 'Do not run the `console()` hook.',
+            ])->addOption('no-middleware', [
+                'boolean' => true,
+                'help' => 'Do not run the `middleware()` hook..',
+            ])->addOption('no-routes', [
+                'boolean' => true,
+                'help' => 'Do not run the `routes()` hook.',
+            ])->addOption('no-services', [
+                'boolean' => true,
+                'help' => 'Do not run the `services()` hook.',
+            ]);
 
         return $parser;
     }

+ 36 - 11
tests/TestCase/Command/PluginLoadCommandTest.php

@@ -29,12 +29,12 @@ class PluginLoadCommandTest extends TestCase
     /**
      * @var string
      */
-    protected $app;
+    protected $configFile;
 
     /**
      * @var string
      */
-    protected $originalAppContent;
+    protected $originalContent;
 
     /**
      * setUp method
@@ -43,8 +43,8 @@ class PluginLoadCommandTest extends TestCase
     {
         parent::setUp();
 
-        $this->app = APP . DS . 'Application.php';
-        $this->originalAppContent = file_get_contents($this->app);
+        $this->configFile = CONFIG . 'plugins.php';
+        $this->originalContent = file_get_contents($this->configFile);
 
         $this->setAppNamespace();
     }
@@ -56,7 +56,7 @@ class PluginLoadCommandTest extends TestCase
     {
         parent::tearDown();
 
-        file_put_contents($this->app, $this->originalAppContent);
+        file_put_contents($this->configFile, $this->originalContent);
     }
 
     /**
@@ -70,15 +70,28 @@ class PluginLoadCommandTest extends TestCase
     }
 
     /**
-     * Test loading a plugin modifies the app
+     * Test loading a plugin modifies the config file
      */
-    public function testLoadModifiesApplication(): void
+    public function testLoad(): void
     {
         $this->exec('plugin load TestPlugin');
         $this->assertExitCode(CommandInterface::CODE_SUCCESS);
 
-        $contents = file_get_contents($this->app);
-        $this->assertMatchesRegularExpression('/Check plugins added here\n {8}\$this->addPlugin\(\'TestPlugin\'\);\n {4}\}\n/u', $contents);
+        $this->exec('plugin load TestPluginTwo --no-bootstrap --no-console --no-middleware --no-routes --no-services');
+        $this->assertExitCode(CommandInterface::CODE_SUCCESS);
+
+        $this->exec('plugin load Company/TestPluginThree --only-debug --only-cli');
+        $this->assertExitCode(CommandInterface::CODE_SUCCESS);
+
+        $config = include $this->configFile;
+        $this->assertTrue(isset($config['TestPlugin']));
+        $this->assertTrue(isset($config['TestPluginTwo']));
+        $this->assertTrue(isset($config['Company/TestPluginThree']));
+        $this->assertSame(['onlyDebug' => true, 'onlyCli' => true], $config['Company/TestPluginThree']);
+        $this->assertSame(
+            ['bootstrap' => false, 'console' => false, 'middleware' => false, 'routes' => false, 'services' => false],
+            $config['TestPluginTwo']
+        );
     }
 
     /**
@@ -90,7 +103,19 @@ class PluginLoadCommandTest extends TestCase
         $this->assertExitCode(CommandInterface::CODE_ERROR);
         $this->assertErrorContains('Plugin NopeNotThere could not be found');
 
-        $contents = file_get_contents($this->app);
-        $this->assertStringNotContainsString("\$this->addPlugin('NopeNotThere');", $contents);
+        $config = include $this->configFile;
+        $this->assertFalse(isset($config['NopeNotThere']));
+    }
+
+    /**
+     * Test loading optional plugin
+     */
+    public function testLoadOptionalPlugin(): void
+    {
+        $this->exec('plugin load NopeNotThere --optional');
+
+        $config = include $this->configFile;
+        $this->assertTrue(isset($config['NopeNotThere']));
+        $this->assertSame(['optional' => true], $config['NopeNotThere']);
     }
 }