Skip to content

Commit

Permalink
feat: Introduce a command loader factory and improve laziness
Browse files Browse the repository at this point in the history
  • Loading branch information
theofidry committed Jul 30, 2023
1 parent f562f1b commit aa119b1
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 53 deletions.
3 changes: 2 additions & 1 deletion src/Application/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Fidry\Console\Application;

use Fidry\Console\Command\Command;
use Fidry\Console\Command\LazyCommandEnvelope;

interface Application
{
Expand Down Expand Up @@ -43,7 +44,7 @@ public function getHelp(): string;
* Exhaustive list of the custom commands. A few more commands such as
* the HelpCommand or ListCommand are also included besides those.
*
* @return Command[]
* @return array<LazyCommandEnvelope|Command>
*/
public function getCommands(): array;

Expand Down
13 changes: 8 additions & 5 deletions src/Application/ApplicationRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

use Fidry\Console\Bridge\Application\SymfonyApplication;
use Fidry\Console\Bridge\Command\BasicSymfonyCommandFactory;
use Fidry\Console\Bridge\Command\SymfonyCommandFactory;
use Fidry\Console\Bridge\CommandLoader\CommandLoaderFactory;
use Fidry\Console\Bridge\CommandLoader\SymfonyFactoryCommandLoaderFactory;
use Fidry\Console\IO;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -28,11 +29,13 @@ final class ApplicationRunner

public function __construct(
Application $application,
?SymfonyCommandFactory $commandFactory = null,
?CommandLoaderFactory $commandLoaderFactory = null,
) {
$this->application = new SymfonyApplication(
$application,
$commandFactory ?? new BasicSymfonyCommandFactory(),
$commandLoaderFactory ?? new SymfonyFactoryCommandLoaderFactory(
new BasicSymfonyCommandFactory(),
),
);
}

Expand All @@ -49,11 +52,11 @@ public static function runApplication(
Application $application,
?InputInterface $input = null,
?OutputInterface $output = null,
?SymfonyCommandFactory $commandFactory = null,
?CommandLoaderFactory $commandLoaderFactory = null,
): int {
$runner = new self(
$application,
$commandFactory,
$commandLoaderFactory,
);

return $runner->run(
Expand Down
37 changes: 5 additions & 32 deletions src/Bridge/Application/SymfonyApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@

use Fidry\Console\Application\Application;
use Fidry\Console\Application\ConfigurableIO;
use Fidry\Console\Bridge\Command\SymfonyCommandFactory;
use Fidry\Console\Bridge\CommandLoader\CommandLoaderFactory;
use Fidry\Console\IO;
use LogicException;
use Symfony\Component\Console\Application as BaseSymfonyApplication;
use Symfony\Component\Console\Command\Command as BaseSymfonyCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\Service\ResetInterface;
use function array_map;
use function array_values;

/**
* Bridge to create a traditional Symfony application from the new Application
Expand All @@ -37,7 +33,7 @@ final class SymfonyApplication extends BaseSymfonyApplication
{
public function __construct(
private readonly Application $application,
private readonly SymfonyCommandFactory $commandFactory,
CommandLoaderFactory $commandLoaderFactory,
) {
parent::__construct(
$application->getName(),
Expand All @@ -47,6 +43,9 @@ public function __construct(
$this->setDefaultCommand($application->getDefaultCommand());
$this->setAutoExit($application->isAutoExitEnabled());
$this->setCatchExceptions($application->areExceptionsCaught());
$this->setCommandLoader(
$commandLoaderFactory->createCommandLoader($application->getCommands()),
);
}

public function reset(): void
Expand Down Expand Up @@ -76,11 +75,6 @@ public function getLongVersion(): string
return $this->application->getLongVersion();
}

public function setCommandLoader(CommandLoaderInterface $commandLoader): void
{
throw new LogicException('Not supported');
}

public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void
{
throw new LogicException('Not supported');
Expand All @@ -106,25 +100,4 @@ protected function configureIO(InputInterface $input, OutputInterface $output):
);
}
}

protected function getDefaultCommands(): array
{
return [
...parent::getDefaultCommands(),
...$this->getSymfonyCommands(),
];
}

/**
* @return list<BaseSymfonyCommand>
*/
private function getSymfonyCommands(): array
{
return array_values(
array_map(
$this->commandFactory->crateSymfonyCommand(...),
$this->application->getCommands(),
),
);
}
}
16 changes: 16 additions & 0 deletions src/Bridge/Command/BasicSymfonyCommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Fidry\Console\Bridge\Command;

use Closure;
use Fidry\Console\Command\Command as FidryCommand;
use Fidry\Console\Command\LazyCommand as FidryLazyCommand;
use Symfony\Component\Console\Command\Command as BaseSymfonyCommand;
Expand All @@ -33,4 +34,19 @@ public function crateSymfonyCommand(FidryCommand $command): BaseSymfonyCommand
)
: new SymfonyCommand($command);
}

public function crateSymfonyLazyCommand(
string $name,
string $description,
Closure $factory,
): BaseSymfonyCommand {
return new SymfonyLazyCommand(
$name,
[],
$description,
false,
static fn () => new SymfonyCommand($factory()),
true,
);
}
}
10 changes: 10 additions & 0 deletions src/Bridge/Command/SymfonyCommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@

namespace Fidry\Console\Bridge\Command;

use Closure;
use Fidry\Console\Command\Command as FidryCommand;
use Symfony\Component\Console\Command\Command as BaseSymfonyCommand;

interface SymfonyCommandFactory
{
public function crateSymfonyCommand(FidryCommand $command): BaseSymfonyCommand;

/**
* @param Closure(): FidryCommand $factory
*/
public function crateSymfonyLazyCommand(
string $name,
string $description,
Closure $factory,
): BaseSymfonyCommand;
}
26 changes: 26 additions & 0 deletions src/Bridge/CommandLoader/CommandLoaderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Fidry\Console package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\Console\Bridge\CommandLoader;

use Fidry\Console\Command\Command as FidryCommand;
use Fidry\Console\Command\LazyCommandEnvelope;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;

interface CommandLoaderFactory
{
/**
* @param array<LazyCommandEnvelope|FidryCommand> $commands
*/
public function createCommandLoader(array $commands): CommandLoaderInterface;
}
55 changes: 55 additions & 0 deletions src/Bridge/CommandLoader/SymfonyFactoryCommandLoaderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

/*
* This file is part of the Fidry\Console package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\Console\Bridge\CommandLoader;

use Fidry\Console\Bridge\Command\SymfonyCommandFactory;
use Fidry\Console\Command\Command as FidryCommand;
use Fidry\Console\Command\LazyCommandEnvelope;
use Symfony\Component\Console\Command\Command as SymfonyNativeCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;

final class SymfonyFactoryCommandLoaderFactory implements CommandLoaderFactory
{
public function __construct(
private readonly SymfonyCommandFactory $commandFactory,
) {
}

public function createCommandLoader(array $commands): CommandLoaderInterface
{
$factories = [];

foreach ($commands as $commandOrEnvelope) {
$command = $this->createCommand($commandOrEnvelope);
/** @var string $name */
$name = $command->getName();

$factories[$name] = static fn (): SymfonyNativeCommand => $command;
}

return new FactoryCommandLoader($factories);
}

private function createCommand(LazyCommandEnvelope|FidryCommand $commandOrCommandFactory): SymfonyNativeCommand
{
return $commandOrCommandFactory instanceof FidryCommand
? $this->commandFactory->crateSymfonyCommand($commandOrCommandFactory)
: $this->commandFactory->crateSymfonyLazyCommand(
$commandOrCommandFactory->name,
$commandOrCommandFactory->description,
$commandOrCommandFactory->factory,
);
}
}
43 changes: 43 additions & 0 deletions src/Command/LazyCommandEnvelope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the Fidry\Console package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\Console\Command;

// TODO: description & doc
use Closure;

final class LazyCommandEnvelope
{
/**
* @param Closure(): Command $factory
*/
public function __construct(
public readonly string $name,
public readonly string $description,
public readonly Closure $factory,
) {
}

/**
* @param class-string<LazyCommand> $commandClassName
* @param Closure():LazyCommand $factory
*/
public static function wrap(string $commandClassName, Closure $factory): self
{
return new self(
$commandClassName::getName(),
$commandClassName::getDescription(),
$factory,
);
}
}
7 changes: 5 additions & 2 deletions src/Test/AppTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
use Fidry\Console\Application\Application as ConsoleApplication;
use Fidry\Console\Bridge\Application\SymfonyApplication;
use Fidry\Console\Bridge\Command\BasicSymfonyCommandFactory;
use Fidry\Console\Bridge\Command\SymfonyCommandFactory;
use Fidry\Console\Bridge\CommandLoader\CommandLoaderFactory;
use Fidry\Console\Bridge\CommandLoader\SymfonyFactoryCommandLoaderFactory;
use Fidry\Console\DisplayNormalizer;
use Symfony\Component\Console\Tester\ApplicationTester;

Expand All @@ -27,7 +28,9 @@ final class AppTester extends ApplicationTester
{
public static function fromConsoleApp(
ConsoleApplication $application,
SymfonyCommandFactory $commandFactory = new BasicSymfonyCommandFactory(),
CommandLoaderFactory $commandFactory = new SymfonyFactoryCommandLoaderFactory(
new BasicSymfonyCommandFactory(),
),
): self {
return new self(
new SymfonyApplication(
Expand Down
4 changes: 2 additions & 2 deletions tests/Application/ApplicationRunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

use DomainException;
use Fidry\Console\Application\ApplicationRunner;
use Fidry\Console\Tests\Bridge\Command\FakeSymfonyCommandFactory;
use Fidry\Console\Tests\Bridge\Command\FakeSymfonyCommandLoaderFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

Expand All @@ -26,7 +26,7 @@ public function test_it_uses_the_command_factory_given(): void
{
$runner = new ApplicationRunner(
new DummyApplication(),
new FakeSymfonyCommandFactory(),
new FakeSymfonyCommandLoaderFactory(),
);

$this->expectException(DomainException::class);
Expand Down
9 changes: 7 additions & 2 deletions tests/Application/Feature/BaseApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Fidry\Console\Application\ApplicationRunner;
use Fidry\Console\Bridge\Application\SymfonyApplication;
use Fidry\Console\Bridge\Command\BasicSymfonyCommandFactory;
use Fidry\Console\Bridge\CommandLoader\SymfonyFactoryCommandLoaderFactory;
use Fidry\Console\IO;
use Fidry\Console\Tests\Application\Fixture\SimpleApplicationUsingBaseApplication;
use Fidry\Console\Tests\Application\OutputAssertions;
Expand Down Expand Up @@ -77,7 +78,9 @@ public function test_it_can_be_run_without_the_static_helper(): void

$runner = new ApplicationRunner(
new SimpleApplicationUsingBaseApplication(),
new BasicSymfonyCommandFactory(),
new SymfonyFactoryCommandLoaderFactory(
new BasicSymfonyCommandFactory(),
),
);

$runner->run(
Expand All @@ -97,7 +100,9 @@ public function test_it_can_display_the_version_used(): void

$runner = new ApplicationRunner(
new SimpleApplicationUsingBaseApplication(),
new BasicSymfonyCommandFactory(),
new SymfonyFactoryCommandLoaderFactory(
new BasicSymfonyCommandFactory(),
),
);

$runner->run(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@
namespace Fidry\Console\Tests\Bridge\Command;

use DomainException;
use Fidry\Console\Bridge\Command\SymfonyCommandFactory;
use Fidry\Console\Command\Command as FidryCommand;
use Symfony\Component\Console\Command\Command as BaseSymfonyCommand;
use Fidry\Console\Bridge\CommandLoader\CommandLoaderFactory;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;

final class FakeSymfonyCommandFactory implements SymfonyCommandFactory
final class FakeSymfonyCommandLoaderFactory implements CommandLoaderFactory
{
public function crateSymfonyCommand(FidryCommand $command): BaseSymfonyCommand
public function createCommandLoader(array $commands): CommandLoaderInterface
{
throw new DomainException('Should not be called.');
}
Expand Down
Loading

0 comments on commit aa119b1

Please sign in to comment.