Version 6 pre-stable
This version of Silverstripe CMS has not yet been given a stable release. See the release roadmap for more information. Go to documentation for the most recent stable version.

PolyCommand

Some code needs to be accessible both via HTTP requests over the web and via CLI in your terminal. Common examples of this are building the database and flushing cache.

The PolyCommand class provides a consistent API for getting input and providing output regardless of the context where the code is run. The API for this class is intentionally similar to the API for the Command class provided by symfony/console.

See the symfony/console input documentation for more specific details about using InputInterface and InputOption. Note that we use the getOptions() method instead of configure() to return an array of InputOption objects. PolyCommand does not allow arguments.

See the symfony/console colouring documentation for information about styling output. Note that all output (including outputting for HTTP requests) as well as the command description and help info can use the symfony/console styling format.

namespace App\Cli\Command;

use SilverStripe\PolyExecution\PolyCommand;
use SilverStripe\PolyExecution\PolyOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;

class MyPolyCommand extends PolyCommand
{
    protected static string $commandName = 'app:my-command';

    protected string $title = 'My command';

    protected static string $description = 'A command that does something';

    public function run(InputInterface $input, PolyOutput $output): int
    {
        if ($input->getOption('do-action')) {
            $output->writeln('Doing something...');
        }
        return Command::SUCCESS;
    }

    public function getOptions(): array
    {
        return [
            new InputOption('do-action', null, InputOption::VALUE_NONE, 'do something specific'),
        ];
    }
}

You can also optionally implement the static getHelp() method to provide additional context about the command. This helps to keep the description short.

Once you've set up your command in PHP, you can add an HTTP route to is using the regular Director routing rules, and add it to Sake with the Sake.commands configuration property. Note that this isn't necessary for the special subclasses mentioned in the sections below.

---
Name: polycommands
---
SilverStripe\Control\Director:
  rules:
    my-command: 'App\Cli\Command\MyPolyCommand'

SilverStripe\Cli\Sake:
  commands:
    - 'App\Cli\Command\MyPolyCommand'

This command could then be used by visiting https://www.example.com/my-command in a browser or running sake app:my-command in CLI.

Configuration

For every PolyCommand subclass, including the special kinds listed below, you can set the following configuration:

These can espcially be useful for defining how commands provided by modules can be executed.

BuildTask

BuildTask is a PolyCommand subclass which is often used for one-off tasks. Common examples include:

  • Migrating data from an old database schema after deploying an updated codebase.
  • Migrating data from an old database schema after updating a module.
  • Updating many values in the database at once after a change in business logic.

You can see the list of available tasks by either navigating to /dev/tasks in your browser or running sake tasks in a terminal.

You can disable a task completely by setting the is_enabled configuration property to false. This is useful when third-party modules add tasks that you don't want to use in your project.

You can also set the can_run_in_cli and can_run_in_browser configuration properties as mentioned in PolyCommand above.

The API for a BuildTask is effectively identical to other PolyCommand subclasses. Note that instead of implementing run() directly, you should implement the execute() method. There is some logic in BuildTask::run() which will output the title of your command before calling execute(). Once your task has finished, it will also output whether it was successful or not (based on the return value) and how long it took.

Unlike other PolyCommand subclasses, you cannot include a namespace for tasks. This is because the command name is also used as the URL segment for HTTP execution of the task, and in the CLI the tasks: namespace is automatically applied.

namespace App\Tasks;

use SilverStripe\Dev\BuildTask;
use SilverStripe\PolyExecution\PolyOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;

class MyCustomTask extends BuildTask
{
    protected static string $commandName = 'my-custom-task';

    protected string $title = 'My custom task';

    protected static string $description = 'A task that does something custom';

    protected function execute(InputInterface $input, PolyOutput $output): int
    {
        if ($input->getOption('do-action')) {
            $output->writeln('Doing something...');
        }
        return Command::SUCCESS;
    }

    public function getOptions(): array
    {
        return [
            new InputOption('do-action', null, InputOption::VALUE_NONE, 'do something specific'),
        ];
    }
}

You don't need to do anything to register a BuildTask - it will be given an HTTP route and be registered with Sake simply by existing.

The above command would be accessible via the browser at /dev/tasks/my-custom-task and via CLI with sake tasks:my-custom-task.

Running regular tasks with cron

On a UNIX machine, you can typically run a scheduled task with a cron job. Run your task in as a cron job using sake.

For example, the following will run MyTask every minute.

* * * * * /your/site/folder/vendor/bin/sake tasks:my-task

DevCommand and /dev/*

There are a set of repeatable actions that developers often need to run which are accessible by browser and in CLI by default, so that developers aren't hindered by restrictions imposed by their hosting provider. These include building the database and viewing the values of all configuration properties.

These commands can be seen by visiting /dev in your browser. There is no specific namespace or command to list these in Sake, as their command names vary based on their purpose.

These commands are all subclasses of DevCommand, which is itself a subclass of PolyCommand. The DevCommand API is identical to BuildTask above, with these exceptions:

  • Unlike tasks you can include a namespace in the commandName if you want to.
  • A description, title, and command name are mandatory for dev commands but are given default values for tasks.
  • There is no single configuration property for disabling dev commands.

Like tasks, when a dev command is executed, the title of the command will be executed first. There is no default output for after a dev command has finished executing.

These are registered by adding them to the DevelopmentAdmin.commands configuration property. The key is the URL segment for the command over HTTP requests.

SilverStripe\Dev\DevelopmentAdmin:
  commands:
    my-dev-command: 'App\Dev\MyDevCommand'

For the above example, the command would be accessible over HTTP by visiting /dev/my-dev-command. The CLI command name is based on the commandName property for that class.