I find having to edit metadata during development a bit annoying, so I do this to avoid that. Since the loading logic is not set in stone (if espo used separate functions in the command manager it would've been easier and more "upgrade-safer" to extend) I would avoid this in production by actually editing all the required metadata when done. Maybe an update to this could be a config.php variable to enable or disable dynamic command loading.
For this we override the loading of the console command manager class in container using a custom loader:
custom/Espo/Custom/Core/Loaders/ConsoleCommandManager.php
Now create a new class for the command:
custom/Espo/Custom/Core/Console/Commands/MyCommand.php
Afterwards, to run execute the command normally with the new command name: php command.php my-command
Enjoy!
For this we override the loading of the console command manager class in container using a custom loader:
custom/Espo/Custom/Core/Loaders/ConsoleCommandManager.php
Code:
<?php /** * Description: A custom loader to fallback to filesystem on CLI command loading. * Author: tothewine * Version: 1.00 * @see: https://forum.espocrm.com/forum/developer-help/59557-tutorial-custom-cli-commands-without-editing-metadata * License: Please keep this notice & post improvements to the forum thread so everyone benefits. * Date: 18/08/2020 */ namespace Espo\Custom\Core\Loaders; use Espo\Core\Console\CommandManager; use Espo\Core\Loaders\Base; class ConsoleCommandManager extends Base { public function load() { return new class ($this->getContainer()) extends CommandManager { public function run(string $command) { return $this->customRun($command); } private $cRef; public function __construct(\Espo\Core\Container $container) { parent::__construct($container); $this->cRef = static function () use ($container) { return $container; }; } private function customRun(string $command) { // echo "\n[Notice] Using modded Command Manager for dynamic class loading.\n"; $command = ucfirst(\Espo\Core\Utils\Util::hyphenToCamelCase($command)); $argumentList = []; $options = []; $flagList = []; $skipIndex = 1; if (isset($_SERVER['argv'][0]) && $_SERVER['argv'][0] === 'command.php') { $skipIndex = 2; } foreach ($_SERVER['argv'] as $i => $item) { if ($i < $skipIndex) continue; if (strpos($item, '--') === 0 && strpos($item, '=') > 2) { [$name, $value] = explode('=', substr($item, 2)); $name = \Espo\Core\Utils\Util::hyphenToCamelCase($name); $options[$name] = $value; } else if (strpos($item, '-') === 0) { $flagList[] = substr($item, 1); } else { $argumentList[] = $item; } } $className = '\\Espo\\Core\\Console\\Commands\\' . $command; $className = ($this->cRef)()->get('metadata')->get("app.consoleCommands.$command.className", $className); if (!(class_exists($className))) $className = ('\\Espo\\Custom\\Core\\Console\\Commands\\' . $command); if (!class_exists($className)) { $msg = "Command '{$command}' does not exist. @ $className"; echo $msg . "\n"; throw new \Espo\Core\Exceptions\Error($msg); } $impl = new $className(($this->cRef)()); return $impl->run($options, $flagList, $argumentList); } }; } }
custom/Espo/Custom/Core/Console/Commands/MyCommand.php
Code:
<?php namespace Espo\Custom\Core\Console\Commands; class MyCommand extends \Espo\Core\Console\Commands\Base{ public function run($options, $flags, $arguments) { echo 'HELLO FROM ESPOCRM CLI !'.PHP_EOL; }}
Enjoy!