├── .github
└── workflows
│ └── close_pr.yml
├── ApiPlatformDeferredProvider.php
├── ApiPlatformMiddleware.php
├── ApiPlatformProvider.php
├── ApiResource
├── Error.php
└── ValidationError.php
├── CONTRIBUTING.md
├── Console
├── InstallCommand.php
└── Maker
│ ├── AbstractMakeStateCommand.php
│ ├── MakeStateProcessorCommand.php
│ ├── MakeStateProviderCommand.php
│ ├── Resources
│ └── skeleton
│ │ ├── StateProcessor.php.tpl
│ │ └── StateProvider.php.tpl
│ └── Utils
│ ├── AppServiceProviderTagger.php
│ ├── StateTemplateGenerator.php
│ ├── StateTypeEnum.php
│ └── SuccessMessageTrait.php
├── Controller
├── ApiPlatformController.php
├── DocumentationController.php
└── EntrypointController.php
├── Eloquent
├── Extension
│ ├── FilterQueryExtension.php
│ └── QueryExtensionInterface.php
├── Filter
│ ├── BooleanFilter.php
│ ├── DateFilter.php
│ ├── EndSearchFilter.php
│ ├── EqualsFilter.php
│ ├── FilterInterface.php
│ ├── JsonApi
│ │ ├── SortFilter.php
│ │ └── SortFilterParameterProvider.php
│ ├── OrFilter.php
│ ├── OrderFilter.php
│ ├── PartialSearchFilter.php
│ ├── QueryPropertyTrait.php
│ ├── RangeFilter.php
│ └── StartSearchFilter.php
├── Metadata
│ ├── Factory
│ │ ├── Property
│ │ │ ├── EloquentAttributePropertyMetadataFactory.php
│ │ │ ├── EloquentPropertyMetadataFactory.php
│ │ │ └── EloquentPropertyNameCollectionMetadataFactory.php
│ │ └── Resource
│ │ │ └── EloquentResourceCollectionMetadataFactory.php
│ ├── IdentifiersExtractor.php
│ ├── ModelMetadata.php
│ └── ResourceClassResolver.php
├── Paginator.php
├── PartialPaginator.php
├── PropertyAccess
│ └── PropertyAccessor.php
├── Serializer
│ └── SerializerContextBuilder.php
└── State
│ ├── CollectionProvider.php
│ ├── ItemProvider.php
│ ├── LinksHandler.php
│ ├── LinksHandlerInterface.php
│ ├── LinksHandlerLocatorTrait.php
│ ├── Options.php
│ ├── PersistProcessor.php
│ └── RemoveProcessor.php
├── Exception
└── ErrorHandler.php
├── GraphQl
└── Controller
│ ├── EntrypointController.php
│ └── GraphiQlController.php
├── JsonApi
└── State
│ └── JsonApiProvider.php
├── LICENSE
├── Metadata
├── CachePropertyMetadataFactory.php
├── CachePropertyNameCollectionMetadataFactory.php
├── CacheResourceCollectionMetadataFactory.php
└── ParameterValidationResourceMetadataCollectionFactory.php
├── README.md
├── Routing
├── IriConverter.php
├── Router.php
└── SkolemIriConverter.php
├── Security
└── ResourceAccessChecker.php
├── ServiceLocator.php
├── State
├── AccessCheckerProvider.php
├── ParameterValidatorProvider.php
├── SwaggerUiProcessor.php
├── SwaggerUiProvider.php
├── ValidateProvider.php
└── ValidationErrorTrait.php
├── Test
├── ApiTestAssertionsTrait.php
└── Constraint
│ ├── ArraySubset.php
│ └── ArraySubsetTrait.php
├── composer.json
├── config
└── api-platform.php
├── public
├── 400.css
├── 700.css
├── car.svg
├── es6-promise
│ └── es6-promise.auto.min.js
├── fetch
│ └── fetch.js
├── fonts
│ └── open-sans
│ │ ├── 400.css
│ │ ├── 700.css
│ │ └── files
│ │ ├── open-sans-cyrillic-400-normal.woff
│ │ ├── open-sans-cyrillic-400-normal.woff2
│ │ ├── open-sans-cyrillic-700-normal.woff
│ │ ├── open-sans-cyrillic-700-normal.woff2
│ │ ├── open-sans-cyrillic-ext-400-normal.woff
│ │ ├── open-sans-cyrillic-ext-400-normal.woff2
│ │ ├── open-sans-cyrillic-ext-700-normal.woff
│ │ ├── open-sans-cyrillic-ext-700-normal.woff2
│ │ ├── open-sans-greek-400-normal.woff
│ │ ├── open-sans-greek-400-normal.woff2
│ │ ├── open-sans-greek-700-normal.woff
│ │ ├── open-sans-greek-700-normal.woff2
│ │ ├── open-sans-greek-ext-400-normal.woff
│ │ ├── open-sans-greek-ext-400-normal.woff2
│ │ ├── open-sans-greek-ext-700-normal.woff
│ │ ├── open-sans-greek-ext-700-normal.woff2
│ │ ├── open-sans-hebrew-400-normal.woff
│ │ ├── open-sans-hebrew-400-normal.woff2
│ │ ├── open-sans-hebrew-700-normal.woff
│ │ ├── open-sans-hebrew-700-normal.woff2
│ │ ├── open-sans-latin-400-normal.woff
│ │ ├── open-sans-latin-400-normal.woff2
│ │ ├── open-sans-latin-700-normal.woff
│ │ ├── open-sans-latin-700-normal.woff2
│ │ ├── open-sans-latin-ext-400-normal.woff
│ │ ├── open-sans-latin-ext-400-normal.woff2
│ │ ├── open-sans-latin-ext-700-normal.woff
│ │ ├── open-sans-latin-ext-700-normal.woff2
│ │ ├── open-sans-math-400-normal.woff
│ │ ├── open-sans-math-400-normal.woff2
│ │ ├── open-sans-math-700-normal.woff
│ │ ├── open-sans-math-700-normal.woff2
│ │ ├── open-sans-symbols-400-normal.woff
│ │ ├── open-sans-symbols-400-normal.woff2
│ │ ├── open-sans-symbols-700-normal.woff
│ │ ├── open-sans-symbols-700-normal.woff2
│ │ ├── open-sans-vietnamese-400-normal.woff
│ │ ├── open-sans-vietnamese-400-normal.woff2
│ │ ├── open-sans-vietnamese-700-normal.woff
│ │ └── open-sans-vietnamese-700-normal.woff2
├── graphiql-style.css
├── graphiql
│ ├── graphiql.css
│ └── graphiql.min.js
├── graphql-playground-style.css
├── graphql-playground
│ ├── index.css
│ └── middleware.js
├── init-common-ui.js
├── init-graphiql.js
├── init-graphql-playground.js
├── init-redoc-ui.js
├── init-swagger-ui.js
├── logo-header.svg
├── react
│ ├── react-dom.production.min.js
│ └── react.production.min.js
├── redoc
│ └── redoc.standalone.js
├── style.css
├── swagger-ui-bundle.js
├── swagger-ui-standalone-preset.js
├── swagger-ui.css
├── swagger-ui
│ ├── oauth2-redirect.html
│ ├── swagger-ui-bundle.js
│ ├── swagger-ui-standalone-preset.js
│ ├── swagger-ui.css
│ └── swagger-ui.css.map
├── web.png
└── webby.png
├── resources
└── views
│ ├── graphiql.blade.php
│ └── swagger-ui.blade.php
├── routes
└── api.php
└── testbench.yaml
/.github/workflows/close_pr.yml:
--------------------------------------------------------------------------------
1 | name: Close Pull Request
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened]
6 |
7 | jobs:
8 | run:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: superbrothers/close-pull-request@v3
12 | with:
13 | comment: "Thank you for your pull request. However, you have submitted this PR on a read-only sub split of `api-platform/core`. Please submit your PR on the https://github.com/api-platform/core repository.
Thanks!"
14 |
--------------------------------------------------------------------------------
/ApiPlatformMiddleware.php:
--------------------------------------------------------------------------------
1 |
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;
15 |
16 | use ApiPlatform\Metadata\HttpOperation;
17 | use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactory;
18 | use Illuminate\Http\Request;
19 | use Symfony\Component\HttpFoundation\Response;
20 |
21 | class ApiPlatformMiddleware
22 | {
23 | public function __construct(
24 | protected OperationMetadataFactory $operationMetadataFactory,
25 | ) {
26 | }
27 |
28 | /**
29 | * @param \Closure(Request): (Response) $next
30 | */
31 | public function handle(Request $request, \Closure $next, ?string $operationName = null): Response
32 | {
33 | $operation = null;
34 | if ($operationName) {
35 | $request->attributes->set('_api_operation', $operation = $this->operationMetadataFactory->create($operationName));
36 | }
37 |
38 | if (!($format = $request->route('_format')) && $operation instanceof HttpOperation && str_ends_with($operation->getUriTemplate(), '{._format}')) {
39 | $matches = [];
40 | if (preg_match('/\.[a-zA-Z]+$/', $request->getPathInfo(), $matches)) {
41 | $format = $matches[0];
42 | }
43 | }
44 |
45 | $request->attributes->set('_format', $format ? substr($format, 1, \strlen($format) - 1) : '');
46 |
47 | return $next($request);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the Laravel Integration of API Platform
2 |
3 | Pull requests should be made at https://github.com/api-plaform/core
4 |
5 | ## Tests
6 |
7 | cd src/Laravel
8 | composer global require soyuka/pmu
9 | composer global link ../../
10 | vendor/bin/testbench workbench:build
11 | vendor/bin/testbench api-platform:install
12 | vendor/bin/testbench package:test
13 | # or
14 | vendor/bin/phpunit
15 |
16 | A command is available to remove the database:
17 |
18 | vendor/bin/testbench workbench:drop-sqlite-db
19 |
20 | ## Starting the Test App
21 |
22 | The test server is also available through:
23 |
24 | vendor/bin/testbench serve
25 |
--------------------------------------------------------------------------------
/Console/InstallCommand.php:
--------------------------------------------------------------------------------
1 |
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;
15 |
16 | use Illuminate\Console\Command;
17 | use Symfony\Component\Console\Attribute\AsCommand;
18 |
19 | #[AsCommand(name: 'api-platform:install')]
20 | class InstallCommand extends Command
21 | {
22 | /**
23 | * @var string
24 | */
25 | protected $signature = 'api-platform:install';
26 |
27 | /**
28 | * @var string
29 | */
30 | protected $description = 'Install all of the API Platform resources';
31 |
32 | /**
33 | * Execute the console command.
34 | */
35 | public function handle(): void
36 | {
37 | $this->comment('Publishing API Platform Assets...');
38 | $this->callSilent('vendor:publish', ['--tag' => 'api-platform-assets']);
39 |
40 | $this->comment('Publishing API Platform Configuration...');
41 | $this->callSilent('vendor:publish', ['--tag' => 'api-platform-config']);
42 |
43 | $this->info('API Platform installed successfully.');
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Console/Maker/AbstractMakeStateCommand.php:
--------------------------------------------------------------------------------
1 |
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('app/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. 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 | }
82 |
--------------------------------------------------------------------------------
/Console/Maker/MakeStateProcessorCommand.php:
--------------------------------------------------------------------------------
1 |
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 | }
28 |
--------------------------------------------------------------------------------
/Console/Maker/MakeStateProviderCommand.php:
--------------------------------------------------------------------------------
1 |
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 | }
28 |
--------------------------------------------------------------------------------
/Console/Maker/Resources/skeleton/StateProcessor.php.tpl:
--------------------------------------------------------------------------------
1 |
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 | }
86 |
--------------------------------------------------------------------------------
/Console/Maker/Utils/StateTemplateGenerator.php:
--------------------------------------------------------------------------------
1 |
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.php.tpl',
53 | StateTypeEnum::Processor => 'StateProcessor.php.tpl',
54 | };
55 |
56 | $templatePath = \dirname(__DIR__).'/Resources/skeleton/'.$templateFile;
57 |
58 | return $this->filesystem->get($templatePath);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Console/Maker/Utils/StateTypeEnum.php:
--------------------------------------------------------------------------------
1 |
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 | }
21 |
--------------------------------------------------------------------------------
/Console/Maker/Utils/SuccessMessageTrait.php:
--------------------------------------------------------------------------------
1 |
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 | trait SuccessMessageTrait
17 | {
18 | private function writeSuccessMessage(string $filePath, StateTypeEnum $stateTypeEnum): void
19 | {
20 | $stateText = strtolower($stateTypeEnum->name);
21 |
22 | $this->newLine();
23 | $this->line(' >');
24 | $this->line(' Success! >');
25 | $this->line(' >');
26 | $this->newLine();
27 | $this->line('created>: '.$filePath.'>');
28 | $this->newLine();
29 | $this->line("Next: Open your new state $stateText class and start customizing it.");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Controller/ApiPlatformController.php:
--------------------------------------------------------------------------------
1 |
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\Controller;
15 |
16 | use ApiPlatform\Metadata\HttpOperation;
17 | use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
18 | use ApiPlatform\State\ProcessorInterface;
19 | use ApiPlatform\State\ProviderInterface;
20 | use Illuminate\Http\Request;
21 | use Illuminate\Routing\Controller;
22 | use Symfony\Component\HttpFoundation\Response;
23 |
24 | class ApiPlatformController extends Controller
25 | {
26 | /**
27 | * @param ProviderInterface