./craft command-line/magic
@michaelrog
rog.io/craft-command-line-magic
Craft's native CLI tool
Building your own custom CLI commands
Use case examples
php craft ...
chmod +x ./craft
./craft ...
./craft help
cache
clear-caches
clear-deprecations
db
elements
fixture
gc
graphql
help
index-assets
install
invalidate-tags
mailer
migrate
off
on
plugin
project-config
queue
resave
serve
setup
tests
up
update
users
utils
./craft migrate/all --track=my-custom-track
./craft project-config/apply
./craft up --force
./craft clear-caches/all
./craft install/check
./craft install/check && ./craft up || exit 0
"scripts": {
"craft-update": [
"@php craft install/check && php craft migrate/all --interactive=0 || exit 0",
"@php craft install/check && php craft project-config/apply --force --interactive=0 || exit 0",
"@php craft install/check && php craft clear-caches/all --interactive=0 || exit 0"
],
"post-update-cmd": "@craft-update",
"post-install-cmd": "@craft-update"
}
./craft users/set-password
./craft users/create --email --password --admin
./craft plugin/list
./craft plugin/disable
./craft resave/entries --set=myField --if-empty --to ...
...an attribute name:
--to myCustomField
...an object template:
--to "={myCustomField|lower}"
...a raw value:
--to "=42"
...an empty value:
--to :empty:
...a PHP arrow function:
--to "fn(\$element) => \$element->callSomething()"
./craft mailer/test
// and more ...
craftcms.com/docs/4.x/console-commands.html
Craft's native CLI tool ✓
Building your own custom CLI commands
Use case examples
// Key concepts
controllers: Request → Response
routing: namespaces, controller maps
plugin vs module
actions, arguments, options
output, exit codes
// controller > action
class MagicController extends craft\console\Controller
{
public function actionAbracadabra($foo, $bar): int
{
return ExitCode::OK;
}
}
./craft dot-all/magic/abracadabra myFoo myBar
// routing, for plugins
DotAllPlugin.php
console /
↳ controllers /
↳ MagicController.php
// routing, for modules
DotAllModule.php
controllers /
↳ console /
↳ MagicController.php
↳ web /
↳ SomeWebController.php
// DotAllModule.php
<?php
namespace modules\dotall;
class DotAllModule extends yii\base\Module
{
public function init()
{
Craft::setAlias('@modules/dotall', __DIR__);
}
}
// app.php
<?php
return [
'modules' => [
'dotall' => \modules\dotall\DotAllModule::class,
],
'bootstrap' => [
'dotall',
],
];
// DotAllModule.php
...
public function init()
{
Craft::setAlias('@modules/dotall', __DIR__);
if (Craft::$app instanceof craft\console\Application) {
$this->controllerNamespace = 'modules\\dotall\\controllers\\console';
}
}
...
// routing, for modules
$this->controllerNamespace = 'modules\\dotall\\controllers\\console';
DotAllModule.php
controllers /
↳ console /
↳ MagicController.php
↳ FooController.php
↳ web /
↳ SomeWebController.php
./craft dot-all/magic/...
./craft dot-all/foo/...
use craft\console\Controller;
use yii\console\ExitCode;
class MagicController extends Controller
{
public function actionAbracadabra($foo, $bar): int
{
return ExitCode::OK;
}
}
./craft dot-all/magic/abracadabra myFoo myBar
class MagicController extends Controller
{
public $foo;
public function options($actionID)
{
return ['foo'];
}
public function actionAbracadabra(): int
{
$this->foo ...
return ExitCode::OK;
}
}
./craft dot-all/magic/abracadabra --foo=bar
class MagicController extends Controller
{
public function beforeAction($action)
{
return parent::beforeAction($action);
}
public function afterAction($action)
{
return parent::afterAction($action);
}
}
class MagicController extends Controller
{
/*
* Does magic with the command line!
*/
public function actionAbracadabra($foo, $bar): int
{
return ExitCode::OK;
}
}
public function actionAbracadabra(): int
{
if ($this->prompt("Say the magic word...") === 'abracadabra')
{
$this->stdout("You did it!");
return ExitCode::OK;
}
$this->stderr("That didn't work.");
return ExitCode::UNSPECIFIED_ERROR;
}
use craft\helpers\App;
...
public function actionAbracadabra(): int
{
App::maxPowerCaptian()
// ...
}
abstract class BaseConsoleController extends Controller
{
/**
* Writes a line to the console
*/
protected function _writeLine($msg = null)
{
$this->stdout(print_r($msg, true) . PHP_EOL);
}
}
abstract class BaseConsoleController extends Controller
{
/**
* Writes an error to the console
*/
protected function _writeError($msg)
{
$this->stderr('Error: ', Console::BOLD, Console::FG_RED);
$this->stderr(print_r($msg, true) . PHP_EOL);
}
}
protected function runAndExit(callable $function): int
{
try {
$function();
return ExitCode::OK;
}
catch (\Exception $e) {
$this->_writeErr($e->getMessage());
return ExitCode::UNSPECIFIED_ERROR;
}
}
return $this->runAndExit(function() {
// Do magic...
});
use craft\console\Controller;
use yii\console\ExitCode;
class MagicController extends Controller
{
public function actionAbracadabra($foo, $bar): int
{
return ExitCode::OK;
}
}
./craft dot-all/magic/abracadabra myFoo myBar
Craft's native CLI tool ✓
Building your own custom CLI commands ✓
Use case examples
// Plugin use cases
Walk
easy CLI tools for processing or counting elements
Recurring Orders
process subscription renewals nightly on cron
Campaigns
send scheduled emails
// Project use cases
Import content (JSON, CSV, EE...)
class ImportController extends BaseConsoleController
{
public function actionCsvRecords($file): int
{
return $this->runAndExit(function() use $file {
foreach(file($file) as $row) {
$data = str_getcsv($row);
$entry = new Entry([
'sectionId' => 4,
'typeId` => 2,
]);
$entry->title = $data[0];
$entry->setFieldValue('foo', $data[1]);
if (!Craft::$app->elements->saveElement($entry))
{
throw new Exception("Yikes!");
}
}
});
}
}
Replay event actions
class AdminController extends BaseConsoleController
{
public function actionSendEntryNotification(int $entryId): int
{
$entry = Craft::$app->entries->getEntryById($entryId);
if (!$entry)
{
throw new Exception("Entry not found.");
}
MyModule::getInstance()->notifications->sendEntryNotification($entry);
}
}
Purge test content during dev
class DevController extends BaseConsoleController
{
public function actionEraseCommerceContent(): int
{
$forReal = $this->confirm('Delete ALL Orders, Customers, and Addresses?');
if (!$forReal)
return ExitCode::OK;
foreach (Order::findAll() as $order)
{
Craft::$app->elements->deleteElement($order);
}
Customer::deleteAll();
Address::deleteAll();
// ... Run garbage collection ...
}
}
Dev playground
class DevController extends BaseConsoleController
{
public function actionTestAThing($foo, $bar): int
{
Craft::$app->someService->someMethod($foo, $bar);
}
}
Craft's native CLI tool ✓
Building your own custom CLI commands ✓
Use case examples ✓
// instruction & inspiration
craftcms.com/docs/4.x/console-commands.html
craftcms.com/docs/4.x/extend/commands.html
yiiframework.com/doc/guide/2.0/en/tutorial-console
github.com/TopShelfCraft/Walk
// Go make magic!
rog.io/craft-command-line-magic