Skip to content

Commit f211ff0

Browse files
vinceAmstoutzsoyuka
authored andcommitted
feat(laravel): add make:filter command to generate API Platform filters (#7364)
1 parent bcf7145 commit f211ff0

12 files changed

+377
-51
lines changed

ApiPlatformProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ public function register(): void
956956
Console\InstallCommand::class,
957957
Console\Maker\MakeStateProcessorCommand::class,
958958
Console\Maker\MakeStateProviderCommand::class,
959+
Console\Maker\MakeFilterCommand::class,
959960
OpenApiCommand::class,
960961
]);
961962
}

Console/Maker/AbstractMakeStateCommand.php

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace ApiPlatform\Laravel\Console\Maker;
1515

16-
use ApiPlatform\Laravel\Console\Maker\Utils\AppServiceProviderTagger;
16+
use ApiPlatform\Laravel\Console\Maker\Utils\StateAppServiceProviderTagger;
1717
use ApiPlatform\Laravel\Console\Maker\Utils\StateTemplateGenerator;
1818
use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;
1919
use ApiPlatform\Laravel\Console\Maker\Utils\SuccessMessageTrait;
@@ -28,7 +28,7 @@ abstract class AbstractMakeStateCommand extends Command
2828
public function __construct(
2929
private readonly Filesystem $filesystem,
3030
private readonly StateTemplateGenerator $stateTemplateGenerator,
31-
private readonly AppServiceProviderTagger $appServiceProviderTagger,
31+
private readonly StateAppServiceProviderTagger $stateAppServiceProviderTagger,
3232
) {
3333
parent::__construct();
3434
}
@@ -38,7 +38,13 @@ public function __construct(
3838
*/
3939
public function handle(): int
4040
{
41-
$stateName = $this->askForStateName();
41+
$stateType = $this->getStateType()->name;
42+
$stateName = $this->ask(\sprintf('Choose a class name for your state %s (e.g. <fg=yellow>AwesomeState%s</>)', strtolower($stateType), ucfirst($stateType)));
43+
if (null === $stateName || '' === $stateName) {
44+
$this->error('[ERROR] The name argument cannot be blank.');
45+
46+
return self::FAILURE;
47+
}
4248

4349
$directoryPath = base_path('app/State/');
4450
$this->filesystem->ensureDirectoryExists($directoryPath);
@@ -57,25 +63,12 @@ public function handle(): int
5763
return self::FAILURE;
5864
}
5965

60-
$this->appServiceProviderTagger->addTagToServiceProvider($stateName, $this->getStateType());
66+
$this->stateAppServiceProviderTagger->addTagToServiceProvider($stateName, $this->getStateType());
6167

62-
$this->writeSuccessMessage($filePath, $this->getStateType());
68+
$this->writeSuccessMessage($filePath, \sprintf('State %s', ucfirst($this->getStateType()->name)));
6369

6470
return self::SUCCESS;
6571
}
6672

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-
8073
abstract protected function getStateType(): StateTypeEnum;
8174
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\FilterAppServiceProviderTagger;
17+
use ApiPlatform\Laravel\Console\Maker\Utils\FilterTemplateGenerator;
18+
use ApiPlatform\Laravel\Console\Maker\Utils\SuccessMessageTrait;
19+
use Illuminate\Console\Command;
20+
use Illuminate\Contracts\Filesystem\FileNotFoundException;
21+
use Illuminate\Filesystem\Filesystem;
22+
23+
final class MakeFilterCommand extends Command
24+
{
25+
use SuccessMessageTrait;
26+
27+
protected $signature = 'make:filter';
28+
protected $description = 'Creates an API Platform filter';
29+
30+
public function __construct(
31+
private readonly Filesystem $filesystem,
32+
private readonly FilterTemplateGenerator $filterTemplateGenerator,
33+
private readonly FilterAppServiceProviderTagger $filterAppServiceProviderTagger,
34+
) {
35+
parent::__construct();
36+
}
37+
38+
/**
39+
* @throws FileNotFoundException
40+
*/
41+
public function handle(): int
42+
{
43+
$nameArgument = $this->ask('Choose a class name for your filter (e.g. <fg=yellow>AwesomeFilter</>)');
44+
if (null === $nameArgument || '' === $nameArgument) {
45+
$this->error('[ERROR] The name argument cannot be blank.');
46+
47+
return self::FAILURE;
48+
}
49+
50+
$directoryPath = base_path('app/Filter/');
51+
$this->filesystem->ensureDirectoryExists($directoryPath);
52+
53+
$filePath = $this->filterTemplateGenerator->getFilePath($directoryPath, $nameArgument);
54+
if ($this->filesystem->exists($filePath)) {
55+
$this->error(\sprintf('[ERROR] The file "%s" can\'t be generated because it already exists.', $filePath));
56+
57+
return self::FAILURE;
58+
}
59+
60+
$this->filterTemplateGenerator->generate($filePath, $nameArgument);
61+
if (!$this->filesystem->exists($filePath)) {
62+
$this->error(\sprintf('[ERROR] The file "%s" could not be created.', $filePath));
63+
64+
return self::FAILURE;
65+
}
66+
67+
$this->filterAppServiceProviderTagger->addTagToServiceProvider($nameArgument);
68+
69+
$this->writeSuccessMessage($filePath, 'Eloquent Filter');
70+
71+
return self::SUCCESS;
72+
}
73+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{ namespace }};
6+
7+
use ApiPlatform\Metadata\Parameter;
8+
use Illuminate\Database\Eloquent\Builder;
9+
use Illuminate\Database\Eloquent\Model;
10+
11+
final class {{ class_name }} implements FilterInterface
12+
{
13+
/**
14+
* @param Builder<Model> $builder
15+
* @param array<string, mixed> $context
16+
*/
17+
public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder
18+
{
19+
// TODO: make your awesome query using the $builder
20+
// return $builder->
21+
}
22+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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 FilterAppServiceProviderTagger
20+
{
21+
/** @var string */
22+
private const APP_SERVICE_PROVIDER_PATH = 'Providers/AppServiceProvider.php';
23+
24+
/** @var string */
25+
private const FILTER_INTERFACE_USE_STATEMENT = 'use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface;';
26+
27+
public function __construct(private Filesystem $filesystem)
28+
{
29+
}
30+
31+
/**
32+
* @throws FileNotFoundException
33+
*/
34+
public function addTagToServiceProvider(string $filterName): void
35+
{
36+
$appServiceProviderPath = app_path(self::APP_SERVICE_PROVIDER_PATH);
37+
if (!$this->filesystem->exists($appServiceProviderPath)) {
38+
throw new \RuntimeException('The AppServiceProvider is missing!');
39+
}
40+
41+
$serviceProviderContent = $this->filesystem->get($appServiceProviderPath);
42+
43+
$this->addUseStatements($serviceProviderContent, $filterName);
44+
$this->addTag($serviceProviderContent, $filterName, $appServiceProviderPath);
45+
}
46+
47+
private function addUseStatements(string &$content, string $filterName): void
48+
{
49+
$useStatements = [self::FILTER_INTERFACE_USE_STATEMENT, \sprintf('use App\\Filter\\%s;', $filterName)];
50+
$statementsString = implode("\n", $useStatements)."\n";
51+
52+
$content = preg_replace(
53+
'/^(namespace\s[^;]+;\s*)/m',
54+
"$1\n$statementsString",
55+
$content,
56+
1
57+
);
58+
}
59+
60+
private function addTag(string &$content, string $filterName, string $serviceProviderPath): void
61+
{
62+
$tagStatement = \sprintf("\n\n\t\t\$this->app->tag(%s::class, FilterInterface::class);", $filterName);
63+
64+
if (!str_contains($content, $tagStatement)) {
65+
$content = preg_replace(
66+
'/(public function register\(\)[^{]*{)(.*?)(\s*}\s*})/s',
67+
"$1$2$tagStatement$3",
68+
$content
69+
);
70+
71+
$this->filesystem->put($serviceProviderPath, $content);
72+
}
73+
}
74+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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 FilterTemplateGenerator
20+
{
21+
public function __construct(private Filesystem $filesystem)
22+
{
23+
}
24+
25+
public function getFilePath(string $directoryPath, string $filterFileName): string
26+
{
27+
return \sprintf('%s%s.php', $directoryPath, $filterFileName);
28+
}
29+
30+
/**
31+
* @throws FileNotFoundException
32+
*/
33+
public function generate(string $pathLink, string $filterName): void
34+
{
35+
$namespace = 'App\\Filter';
36+
$template = $this->filesystem->get(
37+
\sprintf(
38+
'%s/Resources/skeleton/EloquentFilter.php.tpl',
39+
\dirname(__DIR__),
40+
)
41+
);
42+
43+
$content = strtr($template, [
44+
'{{ namespace }}' => $namespace,
45+
'{{ class_name }}' => $filterName,
46+
]);
47+
48+
$this->filesystem->put($pathLink, $content);
49+
}
50+
}

Console/Maker/Utils/AppServiceProviderTagger.php renamed to Console/Maker/Utils/StateAppServiceProviderTagger.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use Illuminate\Contracts\Filesystem\FileNotFoundException;
1717
use Illuminate\Filesystem\Filesystem;
1818

19-
final readonly class AppServiceProviderTagger
19+
final readonly class StateAppServiceProviderTagger
2020
{
2121
/** @var string */
2222
private const APP_SERVICE_PROVIDER_PATH = 'Providers/AppServiceProvider.php';

Console/Maker/Utils/SuccessMessageTrait.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@
1515

1616
trait SuccessMessageTrait
1717
{
18-
private function writeSuccessMessage(string $filePath, StateTypeEnum $stateTypeEnum): void
18+
private function writeSuccessMessage(string $filePath, string $customText): void
1919
{
20-
$stateText = strtolower($stateTypeEnum->name);
21-
2220
$this->newLine();
2321
$this->line(' <bg=green;fg=white> </>');
2422
$this->line(' <bg=green;fg=white> Success! </>');
2523
$this->line(' <bg=green;fg=white> </>');
2624
$this->newLine();
2725
$this->line('<fg=blue>created</>: <fg=white;options=underscore>'.$filePath.'</>');
2826
$this->newLine();
29-
$this->line("Next: Open your new state $stateText class and start customizing it.");
27+
$this->line("Next: Open your new $customText class and start customizing it.");
3028
}
3129
}

0 commit comments

Comments
 (0)