Task Input and Output

One of the great features about tasks that sets them apart from previous attempts at solving the command execution problem is the ability for tasks to prompt users for input, and to echo output at various times.

Task Input

Defining Task Input

Getting Task Input can be done by implementing the getInputDefinition method in your task controller. This method must return null or an object of the type Concrete\Core\Command\Task\Input\Definition\Definition. The definition object defines a list of basic inputs for different types of use cases. These input types will display nicely in the Dashboard and work well within the console. Here’s an example of the getInputDefinition method within the ReindexContentController, which powers the “Reindex Content” task.

public function getInputDefinition(): ?Definition
{
    $definition = new Definition();
    $definition->addField(new BooleanField('clear', t('Clear Index'), t('Clear index before reindexing.')));
    $definition->addField(new BooleanField('rebuild', t('Rebuild Index'), t('Rebuild index attributes table by rescanning all keys.')));
    $definition->addField(new Field('after', t('After ID'), t('Reindex objects after a particular ID.')));
    $definition->addField(
        new SelectField(
            'object',
            t('Object to reindex.'),
            t('You must provide what type of object you want to reindex.'),
            [
                'pages' => t('Pages'),
                'files' => t('Files'),
                'users' => t('Users'),
                'express' => t('Express'),
            ],
            true
        )
    );
    return $definition;
}

This should do a decent job at showing off the various Task input fields that are available. We have

Once you provide the input definition, the console arguments and options and the input fields are automatically available to your command. Here’s what the task options look like for Reindex Content in the Dashboard:

and on the console:

Getting Data from Task Input

Showing fields in the Dashboard and on the command line is only half of the problem; we also need to retrieve data for these task options when starting our task. Fortunately, that’s pretty easy to do from within the getTaskRunner command. The getTaskRunner command always has access to the Concrete\Core\Command\Task\Input\Input object, from which you can retrieve fields and their data. Here’s how the Reindex Content task checks the clear, rebuild, and after definition fields.

The --clear option can be checked through the use of $input->hasField():

if ($input->hasField('clear')) {
    $batch->add(new ClearPageIndexCommand());
}

Same with the --rebuild command, which is also a BooleanField:

if ($input->hasField('rebuild')) {
    foreach($entities as $entity) {
        $batch->add(new RebuildEntityIndexCommand($entity->getId()));
    }
}

The Field and SelectField objects can be checked with hasField and with getField:

$object = (string) $input->getField('object')->getValue();
$after = null;
if ($input->hasField('after')) {
    $after = $input->getField('after')->getValue();
}

Once you have the value of the specified inputs, you can craft your batches, process or command task runners based on what the administrators have specified.

Task Output

Tasks also do a good job of reporting their activity, provided task logging is enabled. Tasks accomplish this through the of the Concrete\Core\Command\Task\Output\OutputAwareInterface. Any command handler used by a task that implements this interface and its corresponding trait (Concrete\Core\Command\Task\Output\OutputAwareTrait) will be able to echo out various things that occur during the running of a task. Here’s how the rescan file task command reports on how it’s operating:

<?php

namespace Concrete\Core\File\Command;

use Concrete\Core\Command\Task\Output\OutputAwareInterface;
use Concrete\Core\Command\Task\Output\OutputAwareTrait;

class RescanFileTaskCommandHandler extends RescanFileCommandHandler implements OutputAwareInterface
{

    use OutputAwareTrait;

    /**
     * @param RescanFileTaskCommand $command
     */
    public function __invoke(RescanFileCommand $command)
    {
        $this->output->write(t('Rescanning file ID: %s', $command->getFileID()));
        parent::__invoke($command);
    }


}

(The RescanFileTaskCommandHandler differs from the core RescanFileCommandHandler because it reports on its operation within a task context, using $this->output.)

Once you write your task handlers using $this->output, you can rely on their output showing in real-time in the Dashboard, within historical task logs, and on the console if the task is run from the command line.


A Note on Task Output and Custom Tasks

There is more in tasks than just the definitions of the input, the command and the handlers, etc. The support classes for the tasks themselves take care of injecting that output object into the command handlers, so if they are not run using the support classes (and instead just reference the command directly) it will not have that output.

There are two options for this - the first option is you can run the command directly and have no access to output. Since most commands in a command bus won’t generate output, this may be fine for your use case. Alternatively, you can use the support classes to instantiate the tasks with output and run them that way.

This second option is actually not quite as difficult as it may sound. Here is an example of the a new Dashboard page introduced in Concrete 9.2, the Site Health page. It presents a number of tasks in the special site health category.

These reports are just tasks. When you click on the Run report button, behind the scenes from this custom dashboard page a task is run.

The sub-tasks/commands that make up this report are even displayed in the page. You can see the code that runs the task at:

concrete/controllers/single_page/dashboard/welcome/health.php.

If you were to adapt it for a hard-coded custom task, here is an example of how you might do that:

use Concrete\Core\Command\Task\TaskService;
use Concrete\Core\Command\Task\Traits\DashboardTaskRunnerTrait;

// snip...

use DashboardTaskRunnerTrait; // This adds the `executeTask` method into the controller.

$task = $this->app->make(TaskService::class)->getByHandle('my_custom_task');
$controller = $task->getController();
$this->executeTask($task);

This will execute the task with the Dashboard task output present.

For those using versions of Concrete earlier than 9.2 and wondering what that DashboardTaskRunnerTrait is, the contents of it are included below. This code can be used directly in lieu of the trait - the trait was added to make it easier for developers going forward:

$controller = $task->getController();
$runner = $controller->getTaskRunner($task, new Input());
$handler = $this->app->make($runner->getTaskRunnerHandler());
$handler->boot($runner);

$contextFactory = $this->app->make(ContextFactory::class);
$context = $contextFactory->createDashboardContext($runner);

$handler->start($runner, $context);
$handler->run($runner, $context);