Search code examples
phpcakephpcakephp-4.xcakephp-bake

CakePHP 4 Bake Custom TemplateCommand in Plugin


I cannot seem to override TemplateCommand from my plugin.

Original TemplateCommand located here: vendor/cakephp/bake/src/Command/TemplateCommand.php

namespace Bake\Command;

...

class TemplateCommand extends BakeCommand
{...}

I stored mine here: plugins/MyPlugin/src/Command/TemplateCommand:

namespace MyPlugin\Bake\Command;

...

use Bake\Command\TemplateCommand as MyTemplateCommand;

class TemplateCommand extends MyTemplateCommand
{...}

When I run bin/cake bake template books -t "MyPlugin", I get:

Fatal error: Cannot declare class MyPlugin\Bake\Command\TemplateCommand, because the name is already in use in C:\path\to\app\plugins\MyPlugin\src\Command\TemplateCommand.php on line 39

Edit #2

In Plugin.php, in bootstrap(), I was able to debug loaded plugins.

[
        (int) 0 => 'AuditStash',
        (int) 1 => 'Bake',
        (int) 2 => 'DebugKit',
        (int) 3 => 'Migrations',
        (int) 4 => 'MyPlugin'
]

Edit #3

In plugins/MyPlugin/src/Command/TemplateCommand.php, I added ***MYPLUGIN*** to the output.

namespace MyPlugin\Command;

...

use Bake\Command\TemplateCommand as MyPluginTemplateCommand;

class TemplateCommand extends MyPluginTemplateCommand
{
    ...
    public function bake(
        Arguments $args,
        ConsoleIo $io,
        string $template,
        $content = '',
        ?string $outputFile = null
    ): void {
        if ($outputFile === null) {
            $outputFile = $template;
        }
        if ($content === true) {
            $content = $this->getContent($args, $io, $template);
        }
        if (empty($content)) {
            $io->err("<warning>No generated content for '{$template}.php', not generating template.</warning>");

            return;
        }
        $path = $this->getTemplatePath($args);
        $filename = $path . Inflector::underscore($outputFile) . '.php';

        $io->out("\n" . sprintf('***MYPLUGIN*** Baking `%s` view template file...', $outputFile), 1, ConsoleIo::QUIET);
        $io->createFile($filename, $content, $args->getOption('force'));
    }
    ...
}

When I run bin/bake template books -t MyPlugin, I see this message without my added prefix:

Baking index view template file...

It doesn't seem to pickup my class.

Edit #4

In vendor/cakephp/cakephp/src/Console/CommandRunner.php, function run, I added var_dump:

...
$shell = $this->getCommand($io, $commands, $name);
var_dump($commands);
...

Output:

object(Cake\Console\CommandCollection)#33 (1) {
  ["commands":protected]=>
  array(87) {
    ["help"]=>
    string(32) "Cake\Console\Command\HelpCommand"
    ["version"]=>
    string(27) "Cake\Command\VersionCommand"
    ["cache clear"]=>
    string(30) "Cake\Command\CacheClearCommand"
    ["cache clear_all"]=>
    string(33) "Cake\Command\CacheClearallCommand"
    ["cache list"]=>
    string(29) "Cake\Command\CacheListCommand"
    ["completion"]=>
    string(30) "Cake\Command\CompletionCommand"
    ["i18n"]=>
    string(24) "Cake\Command\I18nCommand"
    ["i18n extract"]=>
    string(31) "Cake\Command\I18nExtractCommand"
    ["i18n init"]=>
    string(28) "Cake\Command\I18nInitCommand"
    ["plugin assets copy"]=>
    string(36) "Cake\Command\PluginAssetsCopyCommand"
    ["plugin assets remove"]=>
    string(38) "Cake\Command\PluginAssetsRemoveCommand"
    ["plugin assets symlink"]=>
    string(39) "Cake\Command\PluginAssetsSymlinkCommand"
    ["plugin load"]=>
    string(30) "Cake\Command\PluginLoadCommand"
    ["plugin loaded"]=>
    string(32) "Cake\Command\PluginLoadedCommand"
    ["plugin unload"]=>
    string(32) "Cake\Command\PluginUnloadCommand"
    ["routes check"]=>
    string(31) "Cake\Command\RoutesCheckCommand"
    ["routes"]=>
    string(26) "Cake\Command\RoutesCommand"
    ["routes generate"]=>
    string(34) "Cake\Command\RoutesGenerateCommand"
    ["schema_cache build"]=>
    string(36) "Cake\Command\SchemacacheBuildCommand"
    ["schema_cache clear"]=>
    string(36) "Cake\Command\SchemacacheClearCommand"
    ["server"]=>
    string(26) "Cake\Command\ServerCommand"
    ["console"]=>
    string(22) "App\Shell\ConsoleShell"
    ["model"]=>
    string(29) "MyPlugin\Command\ModelCommand"
    ["my_plugin.model"]=>
    string(29) "MyPlugin\Command\ModelCommand"
    ["template"]=>
    string(32) "MyPlugin\Command\TemplateCommand"
    ["my_plugin.template"]=>
    string(32) "MyPlugin\Command\TemplateCommand"
    ...
    string(25) "Bake\Command\ModelCommand"
    ["bake template"]=>
    string(28) "Bake\Command\TemplateCommand"
    ...
  }
}

In vendor/cakephp/cakephp/src/Console/CommandScanner.php, added var_dump:

public function scanPlugin(string $plugin): array
{
    if (!Plugin::isLoaded($plugin)) {
        return [];
    }
    $path = Plugin::classPath($plugin);
    $namespace = str_replace('/', '\\', $plugin);
    $prefix = Inflector::underscore($plugin) . '.';

    $commands = $this->scanDir($path . 'Command', $namespace . '\Command\\', $prefix, []);
    $shells = $this->scanDir($path . 'Shell', $namespace . '\Shell\\', $prefix, []);
    var_dump($commands);

    return array_merge($shells, $commands);
}

Output:

array(0) {
}
array(2) {
  [0]=>
  array(4) {
    ["file"]=>
    string(94) "C:\path\to\MyPlugin\src\CommandModelCommand.php"
    ["fullName"]=>
    string(15) "my_plugin.model"
    ["name"]=>
    string(5) "model"
    ["class"]=>
    string(29) "MyPlugin\Command\ModelCommand"
  }
  [1]=>
  array(4) {
    ["file"]=>
    string(97) "C:\path\to\MyPlugin\src\CommandTemplateCommand.php"
    ["fullName"]=>
    string(18) "my_plugin.template"
    ["name"]=>
    string(8) "template"
    ["class"]=>
    string(32) "MyPlugin\Command\TemplateCommand"
  }
}
...

Edit #5

I think I got it!

namespace MyPlugin\Command;

use Bake\Command\TemplateCommand as MyPluginTemplateCommand;

class TemplateCommand extends MyPluginTemplateCommand
{...}

I also moved the addition of my plugin on the last line of the bootstrap() function.

The output from the CLI now reflects the changes I made.

Edit #6

Need to figure out why it's not writing the views to the appropriate folder. It doubles up on books: src/templates/Books/Books

Edit #7

To get rid of the doubling in the path, commented out the function getTemplatePath.

Edit #8

Final solution.

The class definition used, updated Edit #5 too.

namespace MyPlugin\Command;

use Bake\Command\TemplateCommand as MyPluginTemplateCommand;

class TemplateCommand extends MyPluginTemplateCommand
{...}

Solution

  • Your namespace is wrong, there is no Bake folder in the path to your class file. This mismatch will cause checks for the class' existence to fail, and subsequently cause the autoloader to load the file multiple times, resulting in the error that you're seeing, as a class can only be declared once.