Skip to content

Commit 4497b50

Browse files
committed
Added new command "make:command"
1 parent 4fe6dc4 commit 4497b50

File tree

8 files changed

+387
-1
lines changed

8 files changed

+387
-1
lines changed

Console/Command/MakeCommand.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// phpcs:disable Generic.Files.LineLength.TooLong
6+
// phpcs:disable Magento2.Annotation.MethodAnnotationStructure.MethodAnnotation
7+
8+
namespace MasterZydra\GenCli\Console\Command;
9+
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Output\OutputInterface;
13+
14+
class MakeCommand extends \Symfony\Component\Console\Command\Command
15+
{
16+
private const VENDOR = 'vendor';
17+
private const MODULE = 'module';
18+
private const NAME = 'name';
19+
private const COMMAND = 'cmd';
20+
private const DESCRIPTION = 'description';
21+
22+
/** @inheritdoc */
23+
public function __construct(
24+
private \MasterZydra\GenCli\Helper\Dir $dir,
25+
private \MasterZydra\GenCli\Helper\File $file,
26+
private \MasterZydra\GenCli\Helper\Question $question,
27+
private \MasterZydra\GenCli\Model\Module $module,
28+
private \MasterZydra\GenCli\Model\Command $command,
29+
?string $name = null
30+
) {
31+
parent::__construct($name);
32+
}
33+
34+
/** @inheritdoc */
35+
protected function configure(): void
36+
{
37+
$this->setName('make:command');
38+
$this->setDescription('Create a new command');
39+
$this->addArgument(self::VENDOR, InputArgument::OPTIONAL, 'Vendor name (e.g. \'Magento\')');
40+
$this->addArgument(self::MODULE, InputArgument::OPTIONAL, 'Module name (e.g. \'Sales\')');
41+
$this->addArgument(self::NAME, InputArgument::OPTIONAL, 'Command name (e.g. \'SyncSales\')');
42+
$this->addArgument(self::COMMAND, InputArgument::OPTIONAL, 'Command (e.g. \'sales:sync\')');
43+
$this->addArgument(self::DESCRIPTION, InputArgument::OPTIONAL, 'Command description (e.g. \'Sync sales with Cloud\')');
44+
parent::configure();
45+
}
46+
47+
/** @inheritdoc */
48+
protected function execute(InputInterface $input, OutputInterface $output): int
49+
{
50+
// User inputs
51+
$vendor = $input->getArgument(self::VENDOR);
52+
if (empty($vendor)) {
53+
$vendor = $this->question->ask($input, $output, 'Vendor name (e.g. \'Magento\'): ', false);
54+
if (empty($vendor)) {
55+
$output->writeln('Vendor name is required!');
56+
return 1;
57+
}
58+
}
59+
60+
$module = $input->getArgument(self::MODULE);
61+
if (empty($module)) {
62+
$module = $this->question->ask($input, $output, 'Module name (e.g. \'Sales\'): ', null);
63+
if (empty($module)) {
64+
$output->writeln('Module name is required!');
65+
return 1;
66+
}
67+
}
68+
69+
$this->module->init($vendor, $module, $output);
70+
71+
// Check if module exists
72+
if (!$this->module->exists(false)) {
73+
$output->writeln("Module '$vendor/$module' does not exist!");
74+
return 1;
75+
}
76+
77+
// User inputs
78+
$name = $input->getArgument(self::NAME);
79+
if (empty($name)) {
80+
$name = $this->question->ask($input, $output, 'Command name (e.g. \'SyncSales\'): ', null);
81+
if (empty($name)) {
82+
$output->writeln('Command name is required!');
83+
return 1;
84+
}
85+
}
86+
87+
$command = $input->getArgument(self::COMMAND);
88+
if (empty($command)) {
89+
$command = $this->question->ask($input, $output, 'Command (e.g. \'sales:sync\'): ', null);
90+
if (empty($command)) {
91+
$output->writeln('Command is required!');
92+
return 1;
93+
}
94+
}
95+
96+
$description = $input->getArgument(self::DESCRIPTION);
97+
if (empty($description)) {
98+
$description = $this->question->ask($input, $output, 'Command description (e.g. \'Sync sales with Cloud\'): ', null);
99+
if (empty($description)) {
100+
$output->writeln('Command description is required!');
101+
return 1;
102+
}
103+
}
104+
105+
$output->writeln('');
106+
107+
$this->command->init($this->module, $name, $command, $description);
108+
109+
// Check if command already exists
110+
if ($this->command->exists()) {
111+
return 1;
112+
}
113+
114+
$output->writeln('Generating command...');
115+
116+
// Generate file
117+
if (!$this->command->copy()) {
118+
return 1;
119+
}
120+
121+
$output->writeln('');
122+
$output->writeln("Command '$name' was created.");
123+
return 0;
124+
}
125+
}

Helper/Xml.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// phpcs:disable Magento2.Annotation.MethodArguments.ParamMissing
6+
// phpcs:disable Magento2.Annotation.MethodAnnotationStructure.MethodAnnotation
7+
8+
namespace MasterZydra\GenCli\Helper;
9+
10+
class Xml extends \Magento\Framework\App\Helper\AbstractHelper
11+
{
12+
/** @inheritdoc */
13+
public function __construct(
14+
\Magento\Framework\App\Helper\Context $context,
15+
private \MasterZydra\GenCli\Helper\File $file,
16+
) {
17+
parent::__construct($context);
18+
}
19+
20+
/** Read given XML file */
21+
public function read(string $path): ?\DOMDocument
22+
{
23+
$document = new \DomDocument();
24+
$document->formatOutput = true;
25+
$document->preserveWhiteSpace = false;
26+
27+
if ($document->load($path)) {
28+
return $document;
29+
}
30+
return null;
31+
}
32+
33+
/** Create a new DOM element */
34+
public function createElement(
35+
\DOMDocument $document,
36+
string $localName,
37+
string $value = '',
38+
array $attributes = [],
39+
): ?\DOMElement {
40+
$element = $document->createElement($localName, $value);
41+
if ($element === false) {
42+
return null;
43+
}
44+
foreach ($attributes as $qualifiedName => $value) {
45+
if ($element->setAttribute($qualifiedName, $value) === false) {
46+
return null;
47+
}
48+
}
49+
return $element;
50+
}
51+
52+
/** Write XML to given file */
53+
public function save(\DOMDocument $document, string $path): bool
54+
{
55+
$xml = $document->saveXML();
56+
if ($xml === false) {
57+
return false;
58+
}
59+
return $this->file->write($path, $xml) > 0;
60+
}
61+
}

Model/Command.php

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// phpcs:disable Generic.Files.LineLength.TooLong
6+
// phpcs:disable Magento2.Annotation.MethodAnnotationStructure.MethodAnnotation
7+
// phpcs:disable Magento2.Annotation.MethodArguments.ParamMissing
8+
// phpcs:disable Magento2.Commenting.ClassPropertyPHPDocFormatting.Missing
9+
10+
namespace MasterZydra\GenCli\Model;
11+
12+
class Command
13+
{
14+
private ?Module $module;
15+
private string $name;
16+
private string $command;
17+
private string $description;
18+
private ?\Symfony\Component\Console\Output\OutputInterface $output;
19+
20+
private string $commandPath;
21+
private string $diXmlPath;
22+
23+
/** @inheritdoc */
24+
public function __construct(
25+
private \MasterZydra\GenCli\Helper\Dir $dir,
26+
private \MasterZydra\GenCli\Helper\File $file,
27+
private \MasterZydra\GenCli\Helper\Xml $xml,
28+
) {
29+
}
30+
31+
/** Init controller model */
32+
public function init(
33+
Module $module,
34+
string $name,
35+
string $command,
36+
string $description
37+
): void {
38+
$this->module = $module;
39+
$this->name = $name;
40+
$this->command = $command;
41+
$this->description = $description;
42+
$this->output = $module->output();
43+
44+
$this->commandPath = $this->file->join($this->module->path(), 'Console', 'Command', $this->name . '.php');
45+
$this->diXmlPath = $this->file->join($this->module->path(), 'etc', 'di.xml');
46+
}
47+
48+
/** Check if controller alread exists */
49+
public function exists(): bool
50+
{
51+
if ($this->file->exists($this->commandPath)) {
52+
$this->output->writeln('Command \'' . $this->name . '.php\' already exists!');
53+
return true;
54+
}
55+
return false;
56+
}
57+
58+
/** Copy templates */
59+
public function copy(): bool
60+
{
61+
if (!$this->file->copyTemplate(
62+
$this->file->join($this->dir->template(), 'Console', 'Command', 'Command.php.template'),
63+
$this->commandPath,
64+
[
65+
'{{ vendor }}' => $this->module->vendor(),
66+
'{{ module }}' => $this->module->module(),
67+
'{{ name }}' => $this->name,
68+
'{{ command }}' => $this->command,
69+
'{{ description }}' => $this->description,
70+
],
71+
)) {
72+
$this->output->writeln('An error occured while creating \'' . $this->file->join('Console', 'Command', $this->name . '.php') . '\'');
73+
return false;
74+
}
75+
76+
if (!$this->file->exists($this->diXmlPath)) {
77+
if (!$this->file->copyTemplate(
78+
$this->file->join($this->dir->template(), 'etc', 'di.xml.template'),
79+
$this->diXmlPath,
80+
[],
81+
)) {
82+
$this->output->writeln('An error occured while creating \'' . $this->file->join('etc', 'di.xml') . '\'');
83+
return false;
84+
}
85+
}
86+
87+
return $this->addItemToDiXml();
88+
}
89+
90+
private function addItemToDiXml(): bool
91+
{
92+
$dom = $this->xml->read($this->diXmlPath);
93+
if ($dom === null) {
94+
$this->output->writeln('An error occured while reading \'' . $this->file->join('etc', 'di.xml') . '\'');
95+
return false;
96+
}
97+
98+
// <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
99+
100+
/** @var \DOMElement $configElement */
101+
$configElement = $dom->getElementsByTagName('config')->item(0);
102+
if (!$configElement) {
103+
$configElement = $this->xml->createElement($dom, 'config', attributes: ['xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'urn:magento:framework:ObjectManager/etc/config.xsd']);
104+
if ($configElement === null) return false;
105+
$dom->appendChild($configElement);
106+
}
107+
108+
// <type name="Magento\Framework\Console\CommandListInterface">
109+
110+
/** @var \DOMElement $typeElement */
111+
$typeElement = $configElement->getElementsByTagName('type')->item(0);
112+
if (!$typeElement) {
113+
$typeElement = $this->xml->createElement($dom, 'type', attributes: ['name' => 'Magento\Framework\Console\CommandListInterface']); // phpcs:ignore Magento2.PHP.LiteralNamespaces.LiteralClassUsage
114+
if ($typeElement === null) return false;
115+
$configElement->appendChild($typeElement);
116+
}
117+
118+
// <arguments>
119+
120+
/** @var \DOMElement $argumentsElement */
121+
$argumentsElement = $typeElement->getElementsByTagName('arguments')->item(0);
122+
if (!$argumentsElement) {
123+
$argumentsElement = $this->xml->createElement($dom, 'arguments');
124+
if ($argumentsElement === null) return false;
125+
$typeElement->appendChild($argumentsElement);
126+
}
127+
128+
// <argument name="commands" xsi:type="array">
129+
130+
$argumentElements = $argumentsElement->getElementsByTagName('argument');
131+
$argumentElement = null;
132+
/** @var \DOMElement $element */
133+
foreach ($argumentElements as $element) {
134+
if ($element->getAttribute('name') === 'commands') {
135+
$argumentElement = $element;
136+
}
137+
}
138+
139+
/** @var \DOMElement $argumentElement */
140+
if (!$argumentElement) {
141+
$argumentElement = $this->xml->createElement($dom, 'argument', attributes: ['name' => 'commands', 'xsi:type' => 'array']);
142+
if ($argumentElement === null) return false;
143+
$argumentsElement->appendChild($argumentElement);
144+
}
145+
146+
// <item name="MakeBlock" xsi:type="object">MasterZydra\GenCli\Console\Command\MakeBlock</item>
147+
148+
$namespacePath = $this->module->vendor() . '\\' . $this->module->module() . '\\Console\\Command\\' . $this->name;
149+
$itemElement = $this->xml->createElement($dom, 'item', $namespacePath, ['name' => $this->name,'xsi:type' => 'object']);
150+
if ($itemElement === null) return false;
151+
$argumentElement->appendChild($itemElement);
152+
153+
return $this->xml->save($dom, $this->diXmlPath);
154+
}
155+
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This extension adds Laravel-like CLI commands to simplify these steps.
1414
| Command | Description |
1515
|---------|-------------|
1616
| `make:block` | Create a new block |
17+
| `make:command` | Create a new command |
1718
| `make:controller` | Create a new controller |
1819
| `make:helper` | Create a new helper |
1920
| `make:module` | Create a new module |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{ vendor }}\{{ module }}\Console\Command;
6+
7+
use Symfony\Component\Console\Input\InputArgument;
8+
use Symfony\Component\Console\Input\InputInterface;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
11+
class {{ name }} extends \Symfony\Component\Console\Command\Command
12+
{
13+
private const ARGUMENT = 'arg';
14+
15+
/** @inheritdoc */
16+
public function __construct(
17+
?string $name = null
18+
) {
19+
parent::__construct($name);
20+
}
21+
22+
/** @inheritdoc */
23+
protected function configure(): void
24+
{
25+
$this->setName('{{ command }}');
26+
$this->setDescription('{{ description }}');
27+
$this->addArgument(self::ARGUMENT, InputArgument::OPTIONAL, 'Optional argument');
28+
parent::configure();
29+
}
30+
31+
/** Executes the current command */
32+
protected function execute(InputInterface $input, OutputInterface $output): int
33+
{
34+
$argument = $input->getArgument(self::ARGUMENT);
35+
36+
$output->writeln('Hello world');
37+
38+
return 0;
39+
}
40+
}

Template/etc/di.xml.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
3+
</config>

0 commit comments

Comments
 (0)