./craft command-line/magic


@michaelrog

Howdy!


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

Deployment

./craft migrate/all --track=my-custom-track

./craft project-config/apply

./craft up --force

./craft clear-caches/all

Deployment

./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"
}

                    

Rescue

./craft users/set-password

./craft users/create --email --password --admin

./craft plugin/list

./craft plugin/disable

Development

./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()"

Production

./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

Thanks, yall!

// Go make magic!


rog.io/craft-command-line-magic