In this blog post, you will learn how to create a custom command in a Shopware 6 plugin and use it to generate the demo data.
Because Shopware 6 is based on the Symfony framework, it also has its Console available. So you can do most of the stuff you need to do while developing Shopware 6 plugins.
Folder structure
<pluginRoot>
└── src
├── Command
│ └── DemodataCommand.php
│
├── Generator
│ └── ArticleGenerator.php
│
└── Resources
└── config
└── services.xml
Registering the command
To register a new command, just add it to your plugin's services.xml
and specify the console.command
tag:
<service id="Sas\BlogModule\Command\DemodataCommand">
<argument type="service" id="Shopware\Core\Framework\Demodata\DemodataService"/>
<tag name="console.command"/>
</service>
Configuring the command
Your command's class should extend from the Symfony\Component\Console\Command\Command
class:
class DemodataCommand extends Command
{
protected static $defaultName = '';
protected function configure(): void
{
//
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
//
}
}
Let’s start with the name of the command (the part after "bin/console"). It's the description shown when running "php bin/console list":
protected static $defaultName = 'article:demodata';
You can optionally define a description, help message and the input options and arguments
by overriding the configure()
method:
protected function configure(): void
{
$this->addArgument('count', InputArgument::REQUIRED, 'The number of the articles.');
}
Put in the execute()
method the code to create demo data:
This method must return an integer number with the "exit status code" of the command. You can also use these constants to make code more readable:
Command::SUCCESS
if there was no problem running the command.Command::FAILURE
if some error happened during the execution.Command::INVALID
indicates incorrect command usage: invalid options or missing arguments.
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... put here the code to create articles
return Command::SUCCESS;
}
Styling the command
Title
It displays the given string as the command title. This method is meant to be used only once in a given command, but nothing prevents you from using it repeatedly:
$io->title('Article Data Generator');
The console output should be:
Article Data Generator
======================
Table
It displays the given array of headers and rows as a compact table:
$io->table(
['Entity', 'Items', 'Time'],
$demoContext->getTimings()
);
The console output should be:
---------------------- ------- --------------------
Entity Items Time
---------------------- ------- --------------------
media 1000 3.7
article 1000 6.1
---------------------- ------- --------------------
Progress bar
When executing longer-running commands, it may be helpful to show progress information, which updates as your command runs:
Generating 1000 items for article
---------------------------------
1000/1000 [============================] 100%
! [NOTE] Took 6.1 seconds
Start progress bar: It displays a progress bar with several steps equal to the argument passed to the method (don't progress bar's length of the progress bar is unknown).
// displays a progress bar of unknown length
$context->getConsole()->progressStart();
// displays a 100-step length progress bar
$context->getConsole()->progressStart(100);
Advance progress bar: It makes the progress bar advance the given number of steps.
// advances the progress bar 1 step
$context->getConsole()->progressAdvance();
// advances the progress bar 10 steps
$context->getConsole()->progressAdvance(10);
Finish progress bar: It finishes the progress bar (filling up all the remaining steps when its length is known).
$context->getConsole()->progressFinish();
Creating the generator
To register a new generator, add it to your plugin's services.xml
and specify the shopware.demodata_generator
tag:
<service id="Sas\BlogModule\Generator\ArticleGenerator">
<argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Write\EntityWriter" />
<argument type="service" id="Doctrine\DBAL\Connection" />
<argument type="service" id="Sas\BlogModule\Content\Article\ArticleDefinition"/>
<tag name="shopware.demodata_generator"/>
</service>
Now, we need our generator to know its definition class. This is done by overriding the method getDefinition()
public function getDefinition(): string
{
return ArticleDefinition::class;
}
Put in the generate()
method the code to generate demo data:
public function generate(int $numberOfItems, DemodataContext $context, array $options = []): void
{
$writeContext = WriteContext::createFromContext($context->getContext());
$payload = [
// an array of articles
];
$this->writer->upsert($this->articleDefinition, $payload, $writeContext);
}
Running the command
bin/console article:demodata 1000
Complete classes and XML file
DemodataCommand.php
use Sas\BlogModule\Content\Article\ArticleDefinition;
use Shopware\Core\Framework\Adapter\Console\ShopwareStyle;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Demodata\DemodataRequest;
use Shopware\Core\Framework\Demodata\DemodataService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DemodataCommand extends Command
{
protected static $defaultName = 'article:demodata';
private DemodataService $demodataService;
public function __construct(DemodataService $demodataService)
{
parent::__construct();
$this->demodataService = $demodataService;
}
protected function configure(): void
{
$this->addArgument('count', InputArgument::REQUIRED, 'The number of the articles.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new ShopwareStyle($input, $output);
$io->title('Article Data Generator');
$context = Context::createDefaultContext();
$request = new DemodataRequest();
$request->add(ArticleDefinition::class, (int)$input->getArgument('count'));
$demoContext = $this->demodataService->generate($request, $context, $io);
$io->table(
['Entity', 'Items', 'Time'],
$demoContext->getTimings()
);
return self::SUCCESS;
}
}
ArticleGenerator.php
use Doctrine\DBAL\Connection;
use Sas\BlogModule\Content\Article\ArticleDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Write\EntityWriterInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteContext;
use Shopware\Core\Framework\Demodata\DemodataContext;
use Shopware\Core\Framework\Demodata\DemodataGeneratorInterface;
use Shopware\Core\Framework\Uuid\Uuid;
class ArticleGenerator implements DemodataGeneratorInterface
{
private EntityWriterInterface $writer;
private Connection $connection;
private ArticleDefinition $articleDefinition;
public function __construct(
EntityWriterInterface $writer,
Connection $connection,
ArticleDefinition $articleDefinition
) {
$this->writer = $writer;
$this->connection = $connection;
$this->articleDefinition = $articleDefinition;
}
public function getDefinition(): string
{
return ArticleDefinition::class;
}
public function generate(int $numberOfItems, DemodataContext $context, array $options = []): void
{
$authorId = $this->connection->fetchOne('SELECT LOWER(HEX(id)) FROM author');
$writeContext = WriteContext::createFromContext($context->getContext());
$context->getConsole()->progressStart($numberOfItems);
$payload = [];
for ($i = 0; $i < $numberOfItems; ++$i) {
$translations = [
'en-GB' => [
'title' => $title,
'teaser' => $context->getFaker()->text(200),
'content' => $context->getFaker()->text(50),
],
];
$article = [
'id' => Uuid::randomHex(),
'active' => true,
'authorId' => $authorId,
'translations' => $translations
];
$payload[] = $article;
if (\count($payload) >= 100) {
$this->writer->upsert($this->articleDefinition, $payload, $writeContext);
$context->getConsole()->progressAdvance(\count($payload));
$payload = [];
}
}
if (!empty($payload)) {
$this->writer->upsert($this->articleDefinition, $payload, $writeContext);
$context->getConsole()->progressAdvance(\count($payload));
}
$context->getConsole()->progressFinish();
}
}
services.xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Sas\BlogModule\Command\DemodataCommand">
<argument type="service" id="Shopware\Core\Framework\Demodata\DemodataService"/>
<tag name="console.command"/>
</service>
<service id="Sas\BlogModule\Generator\ArticleGenerator">
<argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Write\EntityWriter" />
<argument type="service" id="Doctrine\DBAL\Connection" />
<argument type="service" id="Sas\BlogModule\Content\Article\ArticleDefinition"/>
<tag name="shopware.demodata_generator"/>
</service>
</services>
</container>
I am really happy to receive your feedback on this article. Thanks for your precious time reading this.
References: