1

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
{...}
TechFanDan
  • 3,329
  • 6
  • 46
  • 89

1 Answers1

1

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.

ndm
  • 59,784
  • 9
  • 71
  • 110
  • Are you saying that I need to create `plugins/MyPlugin/src/Bake/Command` and move my class in there? This is what I did and the message goes away but it doesn't display my changes. I added an extra scaffoldActions and changed the output message `$io->out("\n" . sprintf('!!Modified!! Baking `%s` view template file...', $outputFile), 1, ConsoleIo::QUIET);`l – TechFanDan Feb 13 '22 at 19:00
  • @TechFanDan No, I'm saying that you need to remove `Bake` from the namespace. Also note that overriding on name basis only works when your plugin is being loaded after the bake plugin. – ndm Feb 13 '22 at 19:49
  • Added output of loaded plugins. It would seem the order is correct? – TechFanDan Feb 14 '22 at 01:07
  • I was sure my twig files were being picked up before asking this, but they don't seem to be. I no longer get errors, but it's not picking up my file as it's not showing the IO out messages nor does it list the extra scaffoldActions. – TechFanDan Feb 14 '22 at 20:20
  • In 4.0, are Commands replacements for Shell/Tasks? – TechFanDan Feb 16 '22 at 18:16
  • 1
    @TechFanDan Yes, they are. – ndm Feb 17 '22 at 08:45
  • Thanks. Can you comment on Edit #3? I don't get any errors during baking, but it's not outputting what I've added to my class (ie. ***MYPLUGIN***) – TechFanDan Feb 17 '22 at 13:20
  • @TechFanDan Probably your command isn't being used. You'll have to do some debugging to pin down the problem, like what the final list of commands look like (`\Cake\Console\CommandRunner::run()`), what exactly command scanning returns (`\Cake\Console\CommandCollection::discoverPlugin()/resolveNames()`), etc. Also note that the command override would happen regardless of what theme you are using. – ndm Feb 17 '22 at 14:55
  • See edit #4. I think I did this right. It shows that my commands are in the list. – TechFanDan Feb 17 '22 at 17:56
  • 1
    @TechFanDan If you look closely, something doesn't match up. Your command is being listed as `template`, and later on, there's `bake template`, which is what you actually wanted to override. This could mean that your plugin is loaded too early. Also your command's `defaultName()` method doesn't resolve to `bake template` (which is what you need and what should be the default), but to `template`. Also there seems to be a bug in how path's are picked up, but that should be unrelated. – ndm Feb 17 '22 at 19:25
  • See edit #5, I think we got it! – TechFanDan Feb 17 '22 at 20:07