Skip to content

Commit 5aa7993

Browse files
feat(laravel): command to generate state providers/processors (#6708)
Co-authored-by: Antoine Bluchet <[email protected]>
1 parent 4bbfff3 commit 5aa7993

17 files changed

+711
-3
lines changed

.php-cs-fixer.dist.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
->in(__DIR__)
1616
->exclude([
1717
'src/Core/Bridge/Symfony/Maker/Resources/skeleton',
18+
'src/Laravel/Console/Maker/Resources/skeleton',
1819
'src/Laravel/config',
1920
'tests/Fixtures/app/var',
2021
'docs/guides',

src/Laravel/ApiPlatformProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,11 @@ function (Application $app) {
10581058
});
10591059

10601060
if ($this->app->runningInConsole()) {
1061-
$this->commands([Console\InstallCommand::class]);
1061+
$this->commands([
1062+
Console\InstallCommand::class,
1063+
Console\Maker\MakeStateProcessorCommand::class,
1064+
Console\Maker\MakeStateProviderCommand::class,
1065+
]);
10621066
}
10631067
}
10641068

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker;
15+
16+
use ApiPlatform\Laravel\Console\Maker\Utils\AppServiceProviderTagger;
17+
use ApiPlatform\Laravel\Console\Maker\Utils\StateTemplateGenerator;
18+
use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;
19+
use ApiPlatform\Laravel\Console\Maker\Utils\SuccessMessageTrait;
20+
use Illuminate\Console\Command;
21+
use Illuminate\Contracts\Filesystem\FileNotFoundException;
22+
use Illuminate\Filesystem\Filesystem;
23+
24+
abstract class AbstractMakeStateCommand extends Command
25+
{
26+
use SuccessMessageTrait;
27+
28+
public function __construct(
29+
private readonly Filesystem $filesystem,
30+
private readonly StateTemplateGenerator $stateTemplateGenerator,
31+
private readonly AppServiceProviderTagger $appServiceProviderTagger,
32+
) {
33+
parent::__construct();
34+
}
35+
36+
/**
37+
* @throws FileNotFoundException
38+
*/
39+
public function handle(): int
40+
{
41+
$stateName = $this->askForStateName();
42+
43+
$directoryPath = base_path('src/State/');
44+
$this->filesystem->ensureDirectoryExists($directoryPath);
45+
46+
$filePath = $this->stateTemplateGenerator->getFilePath($directoryPath, $stateName);
47+
if ($this->filesystem->exists($filePath)) {
48+
$this->error(\sprintf('[ERROR] The file "%s" can\'t be generated because it already exists.', $filePath));
49+
50+
return self::FAILURE;
51+
}
52+
53+
$this->stateTemplateGenerator->generate($filePath, $stateName, $this->getStateType());
54+
if (!$this->filesystem->exists($filePath)) {
55+
$this->error(\sprintf('[ERROR] The file "%s" could not be created.', $filePath));
56+
57+
return self::FAILURE;
58+
}
59+
60+
$this->appServiceProviderTagger->addTagToServiceProvider($stateName, $this->getStateType());
61+
62+
$this->writeSuccessMessage($filePath, $this->getStateType());
63+
64+
return self::SUCCESS;
65+
}
66+
67+
protected function askForStateName(): string
68+
{
69+
do {
70+
$stateType = $this->getStateType()->name;
71+
$stateName = $this->ask(\sprintf('Choose a class name for your state %s (e.g. <fg=yellow>AwesomeState%s</>)', strtolower($stateType), ucfirst($stateType)));
72+
if (empty($stateName)) {
73+
$this->error('[ERROR] This value cannot be blank.');
74+
}
75+
} while (empty($stateName));
76+
77+
return $stateName;
78+
}
79+
80+
abstract protected function getStateType(): StateTypeEnum;
81+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker;
15+
16+
use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;
17+
18+
final class MakeStateProcessorCommand extends AbstractMakeStateCommand
19+
{
20+
protected $signature = 'make:state-processor';
21+
protected $description = 'Creates an API Platform state processor';
22+
23+
protected function getStateType(): StateTypeEnum
24+
{
25+
return StateTypeEnum::Processor;
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker;
15+
16+
use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;
17+
18+
final class MakeStateProviderCommand extends AbstractMakeStateCommand
19+
{
20+
protected $signature = 'make:state-provider';
21+
protected $description = 'Creates an API Platform state provider';
22+
23+
protected function getStateType(): StateTypeEnum
24+
{
25+
return StateTypeEnum::Provider;
26+
}
27+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{ namespace }};
6+
7+
use ApiPlatform\Metadata\Operation;
8+
use ApiPlatform\State\ProcessorInterface;
9+
10+
final class {{ class_name }} implements ProcessorInterface
11+
{
12+
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
13+
{
14+
// Handle the state
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{ namespace }};
6+
7+
use ApiPlatform\Metadata\Operation;
8+
use ApiPlatform\State\ProviderInterface;
9+
10+
final class {{ class_name }} implements ProviderInterface
11+
{
12+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
13+
{
14+
// Retrieve the state from somewhere
15+
}
16+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker\Utils;
15+
16+
use Illuminate\Contracts\Filesystem\FileNotFoundException;
17+
use Illuminate\Filesystem\Filesystem;
18+
19+
final readonly class AppServiceProviderTagger
20+
{
21+
/** @var string */
22+
private const APP_SERVICE_PROVIDER_PATH = 'Providers/AppServiceProvider.php';
23+
24+
/** @var string */
25+
private const ITEM_PROVIDER_USE_STATEMENT = 'use ApiPlatform\State\ProviderInterface;';
26+
27+
/** @var string */
28+
private const ITEM_PROCESSOR_USE_STATEMENT = 'use ApiPlatform\State\ProcessorInterface;';
29+
30+
public function __construct(private Filesystem $filesystem)
31+
{
32+
}
33+
34+
/**
35+
* @throws FileNotFoundException
36+
*/
37+
public function addTagToServiceProvider(string $providerName, StateTypeEnum $stateTypeEnum): void
38+
{
39+
$appServiceProviderPath = app_path(self::APP_SERVICE_PROVIDER_PATH);
40+
if (!$this->filesystem->exists($appServiceProviderPath)) {
41+
throw new \RuntimeException('The AppServiceProvider is missing!');
42+
}
43+
44+
$serviceProviderContent = $this->filesystem->get($appServiceProviderPath);
45+
46+
$this->addUseStatement($serviceProviderContent, $this->getStateTypeStatement($stateTypeEnum));
47+
$this->addUseStatement($serviceProviderContent, \sprintf('use App\\State\\%s;', $providerName));
48+
$this->addTag($serviceProviderContent, $providerName, $appServiceProviderPath, $stateTypeEnum);
49+
}
50+
51+
private function addUseStatement(string &$content, string $useStatement): void
52+
{
53+
if (!str_contains($content, $useStatement)) {
54+
$content = preg_replace(
55+
'/^(namespace\s[^;]+;\s*)(\n)/m',
56+
"$1\n$useStatement$2",
57+
$content,
58+
1
59+
);
60+
}
61+
}
62+
63+
private function addTag(string &$content, string $stateName, string $serviceProviderPath, StateTypeEnum $stateTypeEnum): void
64+
{
65+
$tagStatement = \sprintf("\n\n\t\t\$this->app->tag(%s::class, %sInterface::class);", $stateName, $stateTypeEnum->name);
66+
67+
if (!str_contains($content, $tagStatement)) {
68+
$content = preg_replace(
69+
'/(public function register\(\)[^{]*{)(.*?)(\s*}\s*})/s',
70+
"$1$2$tagStatement$3",
71+
$content
72+
);
73+
74+
$this->filesystem->put($serviceProviderPath, $content);
75+
}
76+
}
77+
78+
private function getStateTypeStatement(StateTypeEnum $stateTypeEnum): string
79+
{
80+
return match ($stateTypeEnum) {
81+
StateTypeEnum::Provider => self::ITEM_PROVIDER_USE_STATEMENT,
82+
StateTypeEnum::Processor => self::ITEM_PROCESSOR_USE_STATEMENT,
83+
};
84+
}
85+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker\Utils;
15+
16+
use Illuminate\Contracts\Filesystem\FileNotFoundException;
17+
use Illuminate\Filesystem\Filesystem;
18+
19+
final readonly class StateTemplateGenerator
20+
{
21+
public function __construct(private Filesystem $filesystem)
22+
{
23+
}
24+
25+
public function getFilePath(string $directoryPath, string $stateFileName): string
26+
{
27+
return $directoryPath.$stateFileName.'.php';
28+
}
29+
30+
/**
31+
* @throws FileNotFoundException
32+
*/
33+
public function generate(string $pathLink, string $stateClassName, StateTypeEnum $stateTypeEnum): void
34+
{
35+
$namespace = 'App\\State';
36+
$template = $this->loadTemplate($stateTypeEnum);
37+
38+
$content = strtr($template, [
39+
'{{ namespace }}' => $namespace,
40+
'{{ class_name }}' => $stateClassName,
41+
]);
42+
43+
$this->filesystem->put($pathLink, $content);
44+
}
45+
46+
/**
47+
* @throws FileNotFoundException
48+
*/
49+
private function loadTemplate(StateTypeEnum $stateTypeEnum): string
50+
{
51+
$templateFile = match ($stateTypeEnum) {
52+
StateTypeEnum::Provider => 'StateProvider.tpl.php',
53+
StateTypeEnum::Processor => 'StateProcessor.tpl.php',
54+
};
55+
56+
$templatePath = \dirname(__DIR__).'/Resources/skeleton/'.$templateFile;
57+
58+
return $this->filesystem->get($templatePath);
59+
}
60+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Console\Maker\Utils;
15+
16+
enum StateTypeEnum
17+
{
18+
case Provider;
19+
case Processor;
20+
}

0 commit comments

Comments
 (0)