Skip to content

Commit f162173

Browse files
mattwellssihor-sviziev
authored andcommitted
Implement command loader
1 parent 2b3ad2e commit f162173

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+644
-176
lines changed

app/code/Magento/Backend/Console/Command/MaintenanceAllowIpsCommand.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class MaintenanceAllowIpsCommand extends AbstractSetupCommand
2525
const INPUT_KEY_IP = 'ip';
2626
const INPUT_KEY_NONE = 'none';
2727
const INPUT_KEY_ADD = 'add';
28+
const NAME = 'maintenance:allow-ips';
2829

2930
/**
3031
* @var MaintenanceMode
@@ -76,7 +77,7 @@ protected function configure(): void
7677
'Add the IP address to existing list'
7778
),
7879
];
79-
$this->setName('maintenance:allow-ips')
80+
$this->setName(self::NAME)
8081
->setDescription('Sets maintenance mode exempt IPs')
8182
->setDefinition(array_merge($arguments, $options));
8283

app/code/Magento/Backend/Console/Command/MaintenanceDisableCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010
*/
1111
class MaintenanceDisableCommand extends AbstractMaintenanceCommand
1212
{
13+
const NAME = 'maintenance:disable';
14+
1315
/**
1416
* Initialization of the command
1517
*
1618
* @return void
1719
*/
1820
protected function configure()
1921
{
20-
$this->setName('maintenance:disable')->setDescription('Disables maintenance mode');
22+
$this->setName(self::NAME)->setDescription('Disables maintenance mode');
2123

2224
parent::configure();
2325
}

app/code/Magento/Backend/Console/Command/MaintenanceEnableCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010
*/
1111
class MaintenanceEnableCommand extends AbstractMaintenanceCommand
1212
{
13+
const NAME = 'maintenance:enable';
14+
1315
/**
1416
* Initialization of the command
1517
*
1618
* @return void
1719
*/
1820
protected function configure(): void
1921
{
20-
$this->setName('maintenance:enable')->setDescription('Enables maintenance mode');
22+
$this->setName(self::NAME)->setDescription('Enables maintenance mode');
2123

2224
parent::configure();
2325
}

app/code/Magento/Backend/Console/Command/MaintenanceStatusCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
class MaintenanceStatusCommand extends AbstractSetupCommand
1818
{
19+
const NAME = 'maintenance:status';
20+
1921
/**
2022
* @var MaintenanceMode $maintenanceMode
2123
*/
@@ -40,7 +42,7 @@ public function __construct(MaintenanceMode $maintenanceMode)
4042
*/
4143
protected function configure(): void
4244
{
43-
$this->setName('maintenance:status')
45+
$this->setName(self::NAME)
4446
->setDescription('Displays maintenance mode status');
4547

4648
parent::configure();

dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected function getAllowedNamespaces()
3939
'Framework',
4040
'SomeModule',
4141
'ModuleName',
42-
'Setup\Console\CommandList',
42+
'Setup\Console\CommandLoader',
4343
'Setup\Console\CompilerPreparation',
4444
'Setup\Model\ObjectManagerProvider',
4545
'Setup\Mvc\Bootstrap\InitParamListener',
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\App\Test\Unit\Console\CommandLoader;
7+
8+
use Magento\Framework\Console\CommandLoader\Aggregate;
9+
use PHPUnit\Framework\MockObject\MockObject;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Component\Console\Command\Command;
12+
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
13+
use Symfony\Component\Console\Exception\CommandNotFoundException;
14+
15+
/**
16+
* Tests the "aggregate" command loader
17+
* @see Aggregate
18+
*/
19+
class AggregateTest extends TestCase
20+
{
21+
/** @var MockObject */
22+
private $firstMockCommandLoader;
23+
24+
/** @var MockObject */
25+
private $secondMockCommandLoader;
26+
27+
/** @var Aggregate */
28+
private $aggregateCommandLoader;
29+
30+
protected function setUp(): void
31+
{
32+
$this->firstMockCommandLoader = $this->getMockBuilder(CommandLoaderInterface::class)->getMock();
33+
$this->secondMockCommandLoader = $this->getMockBuilder(CommandLoaderInterface::class)->getMock();
34+
$this->aggregateCommandLoader = new Aggregate([$this->firstMockCommandLoader, $this->secondMockCommandLoader]);
35+
}
36+
37+
/**
38+
* Test the various cases of `has` for the aggregate command loader:
39+
* - When at least one "internal" command loader has a command, the aggregate does as well
40+
* - When none of the "internal" command loaders has a command, neither does the aggregate
41+
*
42+
* @dataProvider provideTestCasesForHas
43+
*/
44+
public function testHas(bool $firstResult, bool $secondResult, bool $overallResult)
45+
{
46+
$this->firstMockCommandLoader->method('has')->with('foo')->willReturn($firstResult);
47+
$this->secondMockCommandLoader->method('has')->with('foo')->willReturn($secondResult);
48+
49+
$this->assertEquals($overallResult, $this->aggregateCommandLoader->has('foo'));
50+
}
51+
52+
public function provideTestCasesForHas()
53+
{
54+
return [
55+
[true, false, true],
56+
[false, true, true],
57+
[false, false, false]
58+
];
59+
}
60+
61+
/**
62+
* Test the various cases of `get` for the aggregate command loader. Similar to `has`,
63+
* the return value of `Aggregate::get` mirrors its internal command loaders.
64+
*
65+
* For simplicity, this test does not cover the "no results" case. @see testGetThrow
66+
*
67+
* @dataProvider provideTestCasesForGet
68+
*/
69+
public function testGet(?Command $firstCmd, ?Command $secondCmd)
70+
{
71+
$firstHas = (bool)$firstCmd;
72+
$secondHas = (bool)$secondCmd;
73+
$this->firstMockCommandLoader->method('has')->with('foo')->willReturn($firstHas);
74+
$this->firstMockCommandLoader->method('get')->with('foo')->willReturn($firstCmd);
75+
$this->secondMockCommandLoader->method('has')->with('foo')->willReturn($secondHas);
76+
$this->secondMockCommandLoader->method('get')->with('foo')->willReturn($secondCmd);
77+
78+
$this->assertInstanceOf(Command::class, $this->aggregateCommandLoader->get('foo'));
79+
}
80+
81+
public function provideTestCasesForGet()
82+
{
83+
return [
84+
[
85+
new Command(),
86+
null
87+
],
88+
[
89+
null,
90+
new Command()
91+
]
92+
];
93+
}
94+
95+
/**
96+
* When none of the internal command loaders have matching commands, the aggregate command loader
97+
* will throw an exception. @see CommandNotFoundException
98+
*/
99+
public function testGetThrow()
100+
{
101+
$this->firstMockCommandLoader->method('has')->with('foo')->willReturn(false);
102+
$this->secondMockCommandLoader->method('has')->with('foo')->willReturn(false);
103+
104+
$this->expectException(CommandNotFoundException::class);
105+
$this->aggregateCommandLoader->get('foo');
106+
}
107+
108+
/**
109+
* An aggregate command loader's `getNames` method returns the merged array of the `getNames`
110+
* return values of all its internal command loaders
111+
*/
112+
public function testGetNames()
113+
{
114+
$this->firstMockCommandLoader->method('getNames')->willReturn(['foo', 'bar']);
115+
$this->secondMockCommandLoader->method('getNames')->willReturn(['baz', 'qux']);
116+
117+
$this->assertEquals(['foo', 'bar', 'baz', 'qux'], $this->aggregateCommandLoader->getNames());
118+
}
119+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\App\Test\Unit\Console;
7+
8+
use Magento\Framework\Console\CommandLoader;
9+
use Magento\Framework\ObjectManagerInterface;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Component\Console\Command\Command;
12+
use Symfony\Component\Console\Exception\CommandNotFoundException;
13+
14+
class CommandLoaderTest extends TestCase
15+
{
16+
/** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManagerInterface */
17+
private $objectManagerMock;
18+
19+
protected function setUp(): void
20+
{
21+
$this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)->getMock();
22+
}
23+
24+
/**
25+
* Test that the command loader, when provided zero commands, does not have a command named "foo"
26+
*/
27+
public function testHasWithZeroCommands()
28+
{
29+
$subj = new CommandLoader($this->objectManagerMock, []);
30+
31+
$this->assertFalse($subj->has('foo'));
32+
}
33+
34+
/**
35+
* Test that the command loader will return true when provided with a command "foo"
36+
*/
37+
public function testHasWithAtLeastOneCommand()
38+
{
39+
$subj = new CommandLoader($this->objectManagerMock, [
40+
[
41+
'name' => 'foo',
42+
'class' => FooCommand::class
43+
]
44+
]);
45+
46+
$this->assertTrue($subj->has('foo'));
47+
}
48+
49+
/**
50+
* Test that the command loader will throw a CommandNotFoundException when it does not have the requested command
51+
*/
52+
public function testGetWithZeroCommands()
53+
{
54+
$subj = new CommandLoader($this->objectManagerMock, []);
55+
56+
$this->expectException(CommandNotFoundException::class);
57+
58+
$subj->get('foo');
59+
}
60+
61+
/**
62+
* Test that the command loader returns a command when one it has is requested
63+
*/
64+
public function testGetWithAtLeastOneCommand()
65+
{
66+
$this->objectManagerMock
67+
->method('create')
68+
->with(FooCommand::class)
69+
->willReturn(new FooCommand());
70+
71+
$subj = new CommandLoader($this->objectManagerMock, [
72+
[
73+
'name' => 'foo',
74+
'class' => FooCommand::class
75+
]
76+
]);
77+
78+
$this->assertInstanceOf(FooCommand::class, $subj->get('foo'));
79+
}
80+
81+
/**
82+
* Test that the command loader will return an empty "names" array when it has none
83+
*/
84+
public function testGetNamesWithZeroCommands()
85+
{
86+
$subj = new CommandLoader($this->objectManagerMock, []);
87+
88+
$this->assertEquals([], $subj->getNames());
89+
}
90+
91+
/**
92+
* Test that the command loader returns an array of its command names when `getNames` is called
93+
*/
94+
public function testGetNames()
95+
{
96+
$subj = new CommandLoader($this->objectManagerMock, [
97+
[
98+
'name' => 'foo',
99+
'class' => FooCommand::class
100+
],
101+
[
102+
'name' => 'bar',
103+
'class' => 'BarCommand'
104+
]
105+
]);
106+
107+
$this->assertEquals(['foo', 'bar'], $subj->getNames());
108+
}
109+
}
110+
111+
// phpcs:ignore PSR1.Classes.ClassDeclaration
112+
class FooCommand extends Command
113+
{
114+
}

lib/internal/Magento/Framework/Console/Cli.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212
use Magento\Framework\App\Filesystem\DirectoryList;
1313
use Magento\Framework\App\ProductMetadata;
1414
use Magento\Framework\Composer\ComposerJsonFinder;
15+
use Magento\Framework\Config\ConfigOptionsListConstants;
16+
use Magento\Framework\Console\CommandLoader\Aggregate;
1517
use Magento\Framework\Console\Exception\GenerationDirectoryAccessException;
1618
use Magento\Framework\Filesystem\Driver\File;
1719
use Magento\Framework\ObjectManagerInterface;
1820
use Magento\Framework\Shell\ComplexParameter;
1921
use Magento\Setup\Application;
22+
use Magento\Setup\Console\CommandLoader as SetupCommandLoader;
2023
use Magento\Setup\Console\CompilerPreparation;
2124
use Magento\Setup\Model\ObjectManagerProvider;
2225
use Psr\Log\LoggerInterface;
2326
use Symfony\Component\Console;
24-
use Magento\Framework\Config\ConfigOptionsListConstants;
2527

2628
/**
2729
* Magento 2 CLI Application.
@@ -102,6 +104,7 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
102104
parent::__construct($name, $version);
103105
$this->serviceManager->setService(\Symfony\Component\Console\Application::class, $this);
104106
$this->logger = $this->objectManager->get(LoggerInterface::class);
107+
$this->setCommandLoader($this->getCommandLoader());
105108
}
106109

107110
/**
@@ -144,11 +147,6 @@ protected function getApplicationCommands()
144147
{
145148
$commands = [];
146149
try {
147-
if (class_exists(\Magento\Setup\Console\CommandList::class)) {
148-
$setupCommandList = new \Magento\Setup\Console\CommandList($this->serviceManager);
149-
$commands = array_merge($commands, $setupCommandList->getCommands());
150-
}
151-
152150
if ($this->objectManager->get(DeploymentConfig::class)->isAvailable()) {
153151
/** @var CommandListInterface */
154152
$commandList = $this->objectManager->create(CommandListInterface::class);
@@ -230,4 +228,23 @@ protected function getVendorCommands($objectManager)
230228

231229
return array_merge([], ...$commands);
232230
}
231+
232+
/**
233+
* Generate and return the Command Loader
234+
*
235+
* @throws \LogicException
236+
* @throws \BadMethodCallException
237+
*/
238+
private function getCommandLoader(): Console\CommandLoader\CommandLoaderInterface
239+
{
240+
$commandLoaders = [];
241+
if (class_exists(SetupCommandLoader::class)) {
242+
$commandLoaders[] = new SetupCommandLoader($this->serviceManager);
243+
}
244+
$commandLoaders[] = $this->objectManager->create(CommandLoader::class);
245+
246+
return $this->objectManager->create(Aggregate::class, [
247+
'commandLoaders' => $commandLoaders
248+
]);
249+
}
233250
}

0 commit comments

Comments
 (0)