Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): add make:middleware command #804

Merged
68 changes: 68 additions & 0 deletions src/Tempest/Console/src/Commands/MakeMiddlewareCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Commands;

use InvalidArgumentException;
use Tempest\Console\ConsoleArgument;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\Enums\MiddlewareType;
use Tempest\Console\Stubs\CommandBusMiddlewareStub;
use Tempest\Console\Stubs\ConsoleMiddlewareStub;
use Tempest\Console\Stubs\EventBusMiddlewareStub;
use Tempest\Console\Stubs\HttpMiddlewareStub;
use Tempest\Core\PublishesFiles;
use Tempest\Generation\DataObjects\StubFile;
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
use Tempest\Generation\Exceptions\FileGenerationFailedException;

final class MakeMiddlewareCommand
{
use PublishesFiles;

#[ConsoleCommand(
name: 'make:middleware',
description: 'Creates a new middleware class',
aliases: ['middleware:make', 'middleware:create', 'create:middleware'],
)]
public function __invoke(
#[ConsoleArgument(
help: 'The name of the middleware class to create',
)]
string $className,
#[ConsoleArgument(
name: 'type',
help: 'The type of the middleware to create',
)]
MiddlewareType $middlewareType,
): void {
try {
$stubFile = $this->getStubFileFromMiddlewareType($middlewareType);
$suggestedPath = $this->getSuggestedPath($className);
$targetPath = $this->promptTargetPath($suggestedPath);
$shouldOverride = $this->askForOverride($targetPath);

$this->stubFileGenerator->generateClassFile(
stubFile: $stubFile,
targetPath: $targetPath,
shouldOverride: $shouldOverride,
);

$this->success(sprintf('Middleware successfully created at "%s".', $targetPath));
} catch (FileGenerationAbortedException|FileGenerationFailedException|InvalidArgumentException $e) {
$this->error($e->getMessage());
}
}

private function getStubFileFromMiddlewareType(MiddlewareType $middlewareType): StubFile
{
return match ($middlewareType) {
MiddlewareType::CONSOLE => StubFile::from(ConsoleMiddlewareStub::class),
MiddlewareType::HTTP => StubFile::from(HttpMiddlewareStub::class),
MiddlewareType::EVENT_BUS => StubFile::from(EventBusMiddlewareStub::class),
MiddlewareType::COMMAND_BUS => StubFile::from(CommandBusMiddlewareStub::class), // @phpstan-ignore match.alwaysTrue (Because this is a guardrail for the future implementations)
default => throw new InvalidArgumentException(sprintf('The "%s" middleware type has no supported stub file.', $middlewareType->value)),
};
}
}
36 changes: 36 additions & 0 deletions src/Tempest/Console/src/Enums/MiddlewareType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Enums;

use Tempest\CommandBus\CommandBusMiddleware;
use Tempest\Console\ConsoleMiddleware;
use Tempest\EventBus\EventBusMiddleware;
use Tempest\Router\HttpMiddleware;

/**
* Represents available middleware types in Tempest.
*/
enum MiddlewareType: string
{
case CONSOLE = 'console';
case HTTP = 'http';
case EVENT_BUS = 'event-bus';
case COMMAND_BUS = 'command-bus';

/**
* Get the related interface for the middleware type.
*
* @return class-string
*/
public function relatedInterface(): string
{
return match ($this) {
self::CONSOLE => ConsoleMiddleware::class,
self::HTTP => HttpMiddleware::class,
self::EVENT_BUS => EventBusMiddleware::class,
self::COMMAND_BUS => CommandBusMiddleware::class,
};
}
}
16 changes: 16 additions & 0 deletions src/Tempest/Console/src/Stubs/CommandBusMiddlewareStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Stubs;

use Tempest\CommandBus\CommandBusMiddleware;
use Tempest\CommandBus\CommandBusMiddlewareCallable;

final class CommandBusMiddlewareStub implements CommandBusMiddleware
{
public function __invoke(object $command, CommandBusMiddlewareCallable $next): void
{
$next($command);
}
}
21 changes: 21 additions & 0 deletions src/Tempest/Console/src/Stubs/ConsoleMiddlewareStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Stubs;

use Tempest\Console\ConsoleMiddleware;
use Tempest\Console\ConsoleMiddlewareCallable;
use Tempest\Console\ExitCode;
use Tempest\Console\HasConsole;
use Tempest\Console\Initializers\Invocation;

final class ConsoleMiddlewareStub implements ConsoleMiddleware
{
use HasConsole;

public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int
{
return $next($invocation);
}
}
16 changes: 16 additions & 0 deletions src/Tempest/Console/src/Stubs/EventBusMiddlewareStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Stubs;

use Tempest\EventBus\EventBusMiddleware;
use Tempest\EventBus\EventBusMiddlewareCallable;

final class EventBusMiddlewareStub implements EventBusMiddleware
{
public function __invoke(object $event, EventBusMiddlewareCallable $next): void
{
$next($event);
}
}
18 changes: 18 additions & 0 deletions src/Tempest/Console/src/Stubs/HttpMiddlewareStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Stubs;

use Tempest\Router\HttpMiddleware;
use Tempest\Router\HttpMiddlewareCallable;
use Tempest\Router\Request;
use Tempest\Router\Response;

final class HttpMiddlewareStub implements HttpMiddleware
{
public function __invoke(Request $request, HttpMiddlewareCallable $next): Response
{
return $next($request);
}
}
102 changes: 102 additions & 0 deletions tests/Integration/Console/Commands/MakeMiddlewareCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Console\Commands;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tempest\Console\Enums\MiddlewareType;
use Tempest\Core\ComposerNamespace;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
use function Tempest\Support\str;

/**
* @internal
*/
final class MakeMiddlewareCommandTest extends FrameworkIntegrationTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->installer->configure(
__DIR__ . '/install',
new ComposerNamespace('App\\', __DIR__ . '/install/App'),
);
}

protected function tearDown(): void
{
$this->installer->clean();

parent::tearDown();
}

#[DataProvider('command_input_provider')]
#[Test]
public function make_command(
string $commandArgs,
string $expectedPath,
string $expectedNamespace,
): void {
$this->console
->call("make:middleware {$commandArgs}")
->submit();

$this->installer
->assertFileExists($expectedPath)
->assertFileContains($expectedPath, 'namespace ' . $expectedNamespace . ';');
}

public static function command_input_provider(): array
{
return [
'make_with_defaults' => [
'commandArgs' => 'BookMiddleware http',
'expectedPath' => 'App/BookMiddleware.php',
'expectedNamespace' => 'App',
],
'make_with_other_namespace' => [
'commandArgs' => 'Middlewares\\BookMiddleware http',
'expectedPath' => 'App/Middlewares/BookMiddleware.php',
'expectedNamespace' => 'App\\Middlewares',
],
'make_with_input_path' => [
'commandArgs' => 'Middlewares/BookMiddleware http',
'expectedPath' => 'App/Middlewares/BookMiddleware.php',
'expectedNamespace' => 'App\\Middlewares',
],
];
}

#[DataProvider('middleware_type_provider')]
#[Test]
public function make_command_with_each_type(
MiddlewareType $middlewareType,
string $middlewareInterface,
): void {
$this->console
->call("make:middleware TestMiddleware {$middlewareType->value}")
->submit();

$filepath = 'App/TestMiddleware.php';
$middlewareInterface = str($middlewareInterface)->classBasename()->toString();
$this->installer
->assertFileExists($filepath)
->assertFileContains($filepath, 'implements ' . $middlewareInterface);
}

public static function middleware_type_provider(): array
{
$cases = MiddlewareType::cases();

return array_combine(
keys: array_map(fn (MiddlewareType $case) => $case->value, $cases),
values: array_map(fn (MiddlewareType $case) => [
'middlewareType' => $case,
'middlewareInterface' => $case->relatedInterface(),
], $cases),
);
}
}
Loading