J4.x

Writing A CLI Application

From Joomla! Documentation

This page contains changes which are not marked for translation.
Joomla! 
4.x
>Tutorial
Other languages:
Deutsch • ‎English • ‎中文(台灣)‎

Joomla 4 comes with an updated way of writing CLI Commands. We'll try and cover the most important changes here and teach you how to write your own CLI commands in Joomla 4.

Brief Overview of changes in Joomla 4[edit]

When you built a CLI application in Joomla 3 you'd create a full Application instance by extending JApplicationCli. In Joomla 4 instead of building up a full new application instance we've built a new Joomla Application that uses the Symfony Console component to make the process of integrating your components much easier than it was previously. The Joomla 3 way of building an application through JApplication will work in Joomla 4, however is deprecated and will be removed in Joomla 5.

The new Console Package (a new package in version 2 of the Joomla Framework) contains the base code and it's extended in the CMS to add support for things like using JInput.

The advantage of the new way is that we have a single application that means using JApplication::isClient('cli') will work. The introduction of \\Joomla\\CMS\\Application\\CMSApplicationInterface means that all applications within Joomla should have a consistent set of methods available for extensions to use.

Files in this Tutorial[edit]

We'll develop a Console / CLI application to output Hello World. The files you'll need are shown in the diagram.

Console Plugin files

First create a plg_helloworld_cli folder. Into this folder you're going to put 4 files:

  • helloworld_cli.xml - a manifest file for the plugin
  • services/provider.php - some boilerplate code to create your plugin instance via the Dependency Injection container
  • src/Extension/HelloworldConsolePlugin.php - the code which interacts with the Joomla plugin framework
  • src/CliCommand/RunHelloCommand.php - the code which performs the hello:world command

We'll use the namespace prefix Mycompany\Plugin\Console\Helloworld.

Creating a Command[edit]

The most rudimentary command just prints a title and some text into the terminal. Put this code into src/CliCommand/RunHelloCommand.php

<?php
namespace Mycompany\Plugin\Console\Helloworld\CliCommand;

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Joomla\Console\Command\AbstractCommand;

class RunHelloCommand extends AbstractCommand
{
	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  4.0.0
	 */
	protected static $defaultName = 'hello:world';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   4.0.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$symfonyStyle = new SymfonyStyle($input, $output);

		$symfonyStyle->title('Hello World Command Title');

		// You might want to do some stuff here in Joomla

		$symfonyStyle->success('Hello World!');

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   4.0.0
	 */
	protected function configure(): void
	{
		$this->setDescription('This command prints hello world to whoever calls it');
		$this->setHelp(
			<<<EOF
The <info>%command.name%</info> command prints hello world to whoever calls it
<info>php %command.full_name%</info>
EOF
		);
	}
}

Let's cover the 3 parts of this code:

  • configure function: Configures information about the command setting up information in the class which is shown when a user lists help information for the command
  • doExecute function: This method does the work of the command - you'd normally go and execute a Joomla Component here (i.e. Dispatch a components dispatcher)
  • defaultName property: This is the command name that is run when executed (remember this we'll come back to it in a second!).

If you need more help there's an excellent Video produced as part of our GSOC 2018 program to help you out, but bear in mind that this video shows changing a library file to register the command - in our case we must use a plugin instead.

Adding the command to the Application by Plugin[edit]

Inside your plugin you're going to register to the event \Joomla\Application\ApplicationEvents::BEFORE_EXECUTE using the method getSubscribedEvents. When Joomla triggers that BEFORE_EXECUTE event you'll register the class which contains the helloworld command. Put this code into src/Extension/HelloworldConsolePlugin.php

<?php
namespace Mycompany\Plugin\Console\Helloworld\Extension;

\defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\Application\ApplicationEvents;
use Joomla\CMS\Factory;
use Mycompany\Plugin\Console\Helloworld\CliCommand\RunHelloCommand;

class HelloworldConsolePlugin extends CMSPlugin implements SubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            \Joomla\Application\ApplicationEvents::BEFORE_EXECUTE => 'registerCommands',
        ];
    }

    public function registerCommands(): void
    {
        $app = Factory::getApplication();
        $app->addCommand(new RunHelloCommand());
    }
}

services/provider.php file[edit]

This is standard code which will create your plugin instance via the Dependency Injection container. Put this code into services/provider.php

<?php
defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Mycompany\Plugin\Console\Helloworld\Extension\HelloworldConsolePlugin;

return new class implements ServiceProviderInterface
{
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.2.0
     */
    public function register(Container $container)
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $dispatcher = $container->get(DispatcherInterface::class);
                $plugin     = new HelloworldConsolePlugin(
                    $dispatcher,
                    (array) PluginHelper::getPlugin('console', 'helloworld_cli')
                );

                return $plugin;
            }
        );
    }
};

Plugin Manifest File[edit]

Put this code into helloworld_cli.xml. The name of our plugin extension is "helloworld_cli" and this must match the filename of the manifest file, the plugin="helloworld_cli" attribute in the manifest <folder> element below, and the parameter passed to PluginHelper::getPlugin in the services/provider.php file. The manifest file is also where the namespace prefix is defined.

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="console" method="upgrade">
	<name>Helloworld Console Plugin</name>
	<version>1.0.0</version>
	<creationDate>today</creationDate>
	<author>Mycompany</author>
	<description>Plugin to demonstrate a basic console application</description>
	<namespace path="src">Mycompany\Plugin\Console\Helloworld</namespace>
	<files>
		<folder plugin="helloworld_cli">services</folder>
		<folder>src</folder>
	</files>
</extension>

Once you have written the source code files zip up the folder and install your plugin extension. Remember that you have to then enable your plugin.

Calling the Command[edit]

Once your plugin is installed and enabled you should be able to see that the hello:world command is now registered

php cli/joomla.php

To call the command you just created you need to run it through the Joomla CLI Command:

php cli/joomla.php hello:world