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(laravel) add commands to generate state providers and state processors #6708

Open
wants to merge 3 commits into
base: 4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
->in(__DIR__)
->exclude([
'src/Core/Bridge/Symfony/Maker/Resources/skeleton',
'src/Laravel/Console/Maker/Resources/skeleton',
'src/Laravel/config',
'tests/Fixtures/app/var',
'docs/guides',
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v4.0.4

### Features

* [5c2cecfa4](https://github.com/api-platform/core/pull/6708/commits/5c2cecfa40fdf7b3db8f77f0f389d2a78d71a917) feat(laravel): add make provider and processor commands

## v4.0.3

### Bug fixes
Expand Down
6 changes: 5 additions & 1 deletion src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,11 @@ function (Application $app) {
});

if ($this->app->runningInConsole()) {
$this->commands([Console\InstallCommand::class]);
$this->commands([
Console\InstallCommand::class,
Console\Maker\MakeStateProcessorCommand::class,
Console\Maker\MakeStateProviderCommand::class,
]);
}
}

Expand Down
81 changes: 81 additions & 0 deletions src/Laravel/Console/Maker/AbstractMakeStateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker;

use ApiPlatform\Laravel\Console\Maker\Utils\AppServiceProviderTagger;
use ApiPlatform\Laravel\Console\Maker\Utils\StateTemplateGenerator;
use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;
use ApiPlatform\Laravel\Console\Maker\Utils\SuccessMessageTrait;
use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Filesystem\Filesystem;

abstract class AbstractMakeStateCommand extends Command
{
use SuccessMessageTrait;

public function __construct(
private readonly Filesystem $filesystem,
private readonly StateTemplateGenerator $stateTemplateGenerator,
private readonly AppServiceProviderTagger $appServiceProviderTagger,
) {
parent::__construct();
}

/**
* @throws FileNotFoundException
*/
public function handle(): int
{
$stateName = $this->askForStateName();

$directoryPath = base_path('src/State/');
$this->filesystem->ensureDirectoryExists($directoryPath);

$filePath = $this->stateTemplateGenerator->getFilePath($directoryPath, $stateName);
if ($this->filesystem->exists($filePath)) {
$this->error(\sprintf('[ERROR] The file "%s" can\'t be generated because it already exists.', $filePath));

return self::FAILURE;
}

$this->stateTemplateGenerator->generate($filePath, $stateName, $this->getStateType());
if (!$this->filesystem->exists($filePath)) {
$this->error(\sprintf('[ERROR] The file "%s" could not be created.', $filePath));

return self::FAILURE;
}

$this->appServiceProviderTagger->addTagToServiceProvider($stateName, $this->getStateType());

$this->writeSuccessMessage($filePath, $this->getStateType());

return self::SUCCESS;
}

protected function askForStateName(): string
{
do {
$stateType = $this->getStateType()->name;
$stateName = $this->ask(\sprintf('Choose a class name for your state %s (e.g. <fg=yellow>AwesomeState%s</>)', mb_strtolower($stateType), mb_ucfirst($stateType)));
if (empty($stateName)) {
$this->error('[ERROR] This value cannot be blank.');
}
} while (empty($stateName));

return $stateName;
}

abstract protected function getStateType(): StateTypeEnum;
}
27 changes: 27 additions & 0 deletions src/Laravel/Console/Maker/MakeStateProcessorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker;

use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;

final class MakeStateProcessorCommand extends AbstractMakeStateCommand
{
protected $signature = 'make:state-processor';
protected $description = 'Creates an API Platform state processor';

protected function getStateType(): StateTypeEnum
{
return StateTypeEnum::Processor;
}
}
27 changes: 27 additions & 0 deletions src/Laravel/Console/Maker/MakeStateProviderCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker;

use ApiPlatform\Laravel\Console\Maker\Utils\StateTypeEnum;

final class MakeStateProviderCommand extends AbstractMakeStateCommand
{
protected $signature = 'make:state-provider';
protected $description = 'Creates an API Platform state provider';

protected function getStateType(): StateTypeEnum
{
return StateTypeEnum::Provider;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace {{ namespace }};

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;

final class {{ class_name }} implements ProcessorInterface
{
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
{
// Handle the state
}
}
16 changes: 16 additions & 0 deletions src/Laravel/Console/Maker/Resources/skeleton/StateProvider.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace {{ namespace }};

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;

final class {{ class_name }} implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
// Retrieve the state from somewhere
}
}
85 changes: 85 additions & 0 deletions src/Laravel/Console/Maker/Utils/AppServiceProviderTagger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker\Utils;

use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Filesystem\Filesystem;

final readonly class AppServiceProviderTagger
{
/** @var string */
private const APP_SERVICE_PROVIDER_PATH = 'Providers/AppServiceProvider.php';

/** @var string */
private const ITEM_PROVIDER_USE_STATEMENT = 'use ApiPlatform\State\ProviderInterface;';

/** @var string */
private const ITEM_PROCESSOR_USE_STATEMENT = 'use ApiPlatform\State\ProcessorInterface;';

public function __construct(private Filesystem $filesystem)
{
}

/**
* @throws FileNotFoundException
*/
public function addTagToServiceProvider(string $providerName, StateTypeEnum $stateTypeEnum): void
{
$appServiceProviderPath = app_path(self::APP_SERVICE_PROVIDER_PATH);
if (!$this->filesystem->exists($appServiceProviderPath)) {
throw new \RuntimeException('The AppServiceProvider is missing!');
}

$serviceProviderContent = $this->filesystem->get($appServiceProviderPath);

$this->addUseStatement($serviceProviderContent, $this->getStateTypeStatement($stateTypeEnum));
$this->addUseStatement($serviceProviderContent, \sprintf('use App\\State\\%s;', $providerName));
$this->addTag($serviceProviderContent, $providerName, $appServiceProviderPath, $stateTypeEnum);
}

private function addUseStatement(string &$content, string $useStatement): void
{
if (!str_contains($content, $useStatement)) {
$content = preg_replace(
'/^(namespace\s[^;]+;\s*)(\n)/m',
"$1\n$useStatement$2",
$content,
1
);
}
}

private function addTag(string &$content, string $stateName, string $serviceProviderPath, StateTypeEnum $stateTypeEnum): void
{
$tagStatement = \sprintf("\n\n\t\t\$this->app->tag(%s::class, %sInterface::class);", $stateName, $stateTypeEnum->name);

if (!str_contains($content, $tagStatement)) {
$content = preg_replace(
'/(public function register\(\)[^{]*{)(.*?)(\s*}\s*})/s',
"$1$2$tagStatement$3",
$content
);

$this->filesystem->put($serviceProviderPath, $content);
}
}

private function getStateTypeStatement(StateTypeEnum $stateTypeEnum): string
{
return match ($stateTypeEnum) {
StateTypeEnum::Provider => self::ITEM_PROVIDER_USE_STATEMENT,
StateTypeEnum::Processor => self::ITEM_PROCESSOR_USE_STATEMENT,
};
}
}
60 changes: 60 additions & 0 deletions src/Laravel/Console/Maker/Utils/StateTemplateGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker\Utils;

use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Filesystem\Filesystem;

final readonly class StateTemplateGenerator
{
public function __construct(private Filesystem $filesystem)
{
}

public function getFilePath(string $directoryPath, string $stateFileName): string
{
return $directoryPath.$stateFileName.'.php';
}

/**
* @throws FileNotFoundException
*/
public function generate(string $pathLink, string $stateClassName, StateTypeEnum $stateTypeEnum): void
{
$namespace = 'App\\State';
$template = $this->loadTemplate($stateTypeEnum);

$content = strtr($template, [
'{{ namespace }}' => $namespace,
'{{ class_name }}' => $stateClassName,
]);

$this->filesystem->put($pathLink, $content);
}

/**
* @throws FileNotFoundException
*/
private function loadTemplate(StateTypeEnum $stateTypeEnum): string
{
$templateFile = match ($stateTypeEnum) {
StateTypeEnum::Provider => 'StateProvider.tpl.php',
StateTypeEnum::Processor => 'StateProcessor.tpl.php',
};

$templatePath = \dirname(__DIR__).'/Resources/skeleton/'.$templateFile;

return $this->filesystem->get($templatePath);
}
}
20 changes: 20 additions & 0 deletions src/Laravel/Console/Maker/Utils/StateTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Console\Maker\Utils;

enum StateTypeEnum
{
case Provider;
case Processor;
}
Loading
Loading