Skip to content

Commit 8a22650

Browse files
authored
fix(laravel): defer "filters" dependent services (api-platform#7045)
1 parent 47a6dff commit 8a22650

File tree

3 files changed

+192
-143
lines changed

3 files changed

+192
-143
lines changed

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,3 +1283,32 @@ jobs:
12831283
composer run-script test
12841284
working-directory: 'src/Laravel'
12851285

1286+
laravel-e2e:
1287+
name: Laravel E2E installation (PHP 8.4)
1288+
runs-on: ubuntu-latest
1289+
timeout-minutes: 20
1290+
steps:
1291+
- name: Checkout
1292+
uses: actions/checkout@v4
1293+
- name: Setup PHP
1294+
uses: shivammathur/setup-php@v2
1295+
with:
1296+
php-version: 8.4
1297+
tools: pecl, composer
1298+
extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite, mongodb
1299+
ini-values: memory_limit=-1
1300+
- name: Update project dependencies
1301+
run: |
1302+
composer global require soyuka/pmu
1303+
composer global config allow-plugins.soyuka/pmu true --no-interaction
1304+
composer global link . --permanent
1305+
- name: Setup laravel project
1306+
run: |
1307+
composer global require laravel/installer
1308+
laravel new test-api-platform-install --pest --no-interaction
1309+
- name: Install api-platform/laravel
1310+
run: |
1311+
composer global link ../src/Laravel --permanent
1312+
composer require api-platform/laravel:"@dev"
1313+
php ./artisan api-platform:install
1314+
working-directory: 'test-api-platform-install'

src/Laravel/ApiPlatformDeferredProvider.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@
1313

1414
namespace ApiPlatform\Laravel;
1515

16+
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
17+
use ApiPlatform\GraphQl\State\Provider\DenormalizeProvider as GraphQlDenormalizeProvider;
18+
use ApiPlatform\GraphQl\Type\ContextAwareTypeBuilderInterface;
19+
use ApiPlatform\GraphQl\Type\FieldsBuilder;
20+
use ApiPlatform\GraphQl\Type\FieldsBuilderEnumInterface;
21+
use ApiPlatform\GraphQl\Type\TypeConverterInterface;
22+
use ApiPlatform\GraphQl\Type\TypesContainerInterface;
1623
use ApiPlatform\JsonApi\Filter\SparseFieldset;
1724
use ApiPlatform\JsonApi\Filter\SparseFieldsetParameterProvider;
25+
use ApiPlatform\Laravel\Controller\ApiPlatformController;
1826
use ApiPlatform\Laravel\Eloquent\Extension\FilterQueryExtension;
1927
use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface;
2028
use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter;
@@ -26,16 +34,39 @@
2634
use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
2735
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
2836
use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter;
37+
use ApiPlatform\Laravel\Eloquent\Metadata\Factory\Resource\EloquentResourceCollectionMetadataFactory;
2938
use ApiPlatform\Laravel\Eloquent\State\CollectionProvider;
3039
use ApiPlatform\Laravel\Eloquent\State\ItemProvider;
3140
use ApiPlatform\Laravel\Eloquent\State\LinksHandler;
3241
use ApiPlatform\Laravel\Eloquent\State\LinksHandlerInterface;
3342
use ApiPlatform\Laravel\Eloquent\State\PersistProcessor;
3443
use ApiPlatform\Laravel\Eloquent\State\RemoveProcessor;
44+
use ApiPlatform\Laravel\Exception\ErrorHandler;
45+
use ApiPlatform\Laravel\Metadata\CacheResourceCollectionMetadataFactory;
46+
use ApiPlatform\Laravel\Metadata\ParameterValidationResourceMetadataCollectionFactory;
3547
use ApiPlatform\Laravel\State\ParameterValidatorProvider;
3648
use ApiPlatform\Laravel\State\SwaggerUiProcessor;
49+
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
50+
use ApiPlatform\Metadata\InflectorInterface;
51+
use ApiPlatform\Metadata\Operation\PathSegmentNameGeneratorInterface;
52+
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
53+
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
54+
use ApiPlatform\Metadata\Resource\Factory\AlternateUriResourceMetadataCollectionFactory;
55+
use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
56+
use ApiPlatform\Metadata\Resource\Factory\ConcernsResourceMetadataCollectionFactory;
57+
use ApiPlatform\Metadata\Resource\Factory\FiltersResourceMetadataCollectionFactory;
58+
use ApiPlatform\Metadata\Resource\Factory\FormatsResourceMetadataCollectionFactory;
59+
use ApiPlatform\Metadata\Resource\Factory\InputOutputResourceMetadataCollectionFactory;
60+
use ApiPlatform\Metadata\Resource\Factory\LinkFactoryInterface;
61+
use ApiPlatform\Metadata\Resource\Factory\LinkResourceMetadataCollectionFactory;
62+
use ApiPlatform\Metadata\Resource\Factory\NotExposedOperationResourceMetadataCollectionFactory;
63+
use ApiPlatform\Metadata\Resource\Factory\OperationNameResourceMetadataCollectionFactory;
64+
use ApiPlatform\Metadata\Resource\Factory\ParameterResourceMetadataCollectionFactory;
65+
use ApiPlatform\Metadata\Resource\Factory\PhpDocResourceMetadataCollectionFactory;
3766
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
67+
use ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory;
3868
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
69+
use ApiPlatform\Metadata\ResourceClassResolverInterface;
3970
use ApiPlatform\Metadata\Util\ReflectionClassRecursiveIterator;
4071
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
4172
use ApiPlatform\Serializer\Filter\PropertyFilter;
@@ -50,9 +81,14 @@
5081
use ApiPlatform\State\Provider\ParameterProvider;
5182
use ApiPlatform\State\Provider\SecurityParameterProvider;
5283
use ApiPlatform\State\ProviderInterface;
84+
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerInterface;
5385
use Illuminate\Contracts\Foundation\Application;
5486
use Illuminate\Contracts\Support\DeferrableProvider;
5587
use Illuminate\Support\ServiceProvider;
88+
use Negotiation\Negotiator;
89+
use Psr\Log\LoggerInterface;
90+
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
91+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
5692

5793
class ApiPlatformDeferredProvider extends ServiceProvider implements DeferrableProvider
5894
{
@@ -149,6 +185,130 @@ public function register(): void
149185
});
150186

151187
$this->autoconfigure($classes, ProviderInterface::class, [ItemProvider::class, CollectionProvider::class, ErrorProvider::class]);
188+
189+
$this->app->singleton(ResourceMetadataCollectionFactoryInterface::class, function (Application $app) {
190+
/** @var ConfigRepository $config */
191+
$config = $app['config'];
192+
$formats = $config->get('api-platform.formats');
193+
194+
if ($config->get('api-platform.swagger_ui.enabled', false) && !isset($formats['html'])) {
195+
$formats['html'] = ['text/html'];
196+
}
197+
198+
return new CacheResourceCollectionMetadataFactory(
199+
new EloquentResourceCollectionMetadataFactory(
200+
new ParameterValidationResourceMetadataCollectionFactory(
201+
new ParameterResourceMetadataCollectionFactory(
202+
$this->app->make(PropertyNameCollectionFactoryInterface::class),
203+
$this->app->make(PropertyMetadataFactoryInterface::class),
204+
new AlternateUriResourceMetadataCollectionFactory(
205+
new FiltersResourceMetadataCollectionFactory(
206+
new FormatsResourceMetadataCollectionFactory(
207+
new InputOutputResourceMetadataCollectionFactory(
208+
new PhpDocResourceMetadataCollectionFactory(
209+
new OperationNameResourceMetadataCollectionFactory(
210+
new LinkResourceMetadataCollectionFactory(
211+
$app->make(LinkFactoryInterface::class),
212+
new UriTemplateResourceMetadataCollectionFactory(
213+
$app->make(LinkFactoryInterface::class),
214+
$app->make(PathSegmentNameGeneratorInterface::class),
215+
new NotExposedOperationResourceMetadataCollectionFactory(
216+
$app->make(LinkFactoryInterface::class),
217+
new AttributesResourceMetadataCollectionFactory(
218+
new ConcernsResourceMetadataCollectionFactory(
219+
null,
220+
$app->make(LoggerInterface::class),
221+
$config->get('api-platform.defaults', []),
222+
$config->get('api-platform.graphql.enabled'),
223+
),
224+
$app->make(LoggerInterface::class),
225+
$config->get('api-platform.defaults', []),
226+
$config->get('api-platform.graphql.enabled'),
227+
),
228+
)
229+
),
230+
$config->get('api-platform.graphql.enabled')
231+
)
232+
)
233+
)
234+
),
235+
$formats,
236+
$config->get('api-platform.patch_formats'),
237+
)
238+
)
239+
),
240+
$app->make('filters'),
241+
$app->make(CamelCaseToSnakeCaseNameConverter::class),
242+
$this->app->make(LoggerInterface::class)
243+
),
244+
$app->make('filters')
245+
)
246+
),
247+
true === $config->get('app.debug') ? 'array' : $config->get('api-platform.cache', 'file')
248+
);
249+
});
250+
251+
$this->app->singleton(
252+
ExceptionHandlerInterface::class,
253+
function (Application $app) {
254+
/** @var ConfigRepository */
255+
$config = $app['config'];
256+
257+
return new ErrorHandler(
258+
$app,
259+
$app->make(ResourceMetadataCollectionFactoryInterface::class),
260+
$app->make(ApiPlatformController::class),
261+
$app->make(IdentifiersExtractorInterface::class),
262+
$app->make(ResourceClassResolverInterface::class),
263+
$app->make(Negotiator::class),
264+
$config->get('api-platform.exception_to_status'),
265+
$config->get('app.debug')
266+
);
267+
}
268+
);
269+
270+
if (interface_exists(FieldsBuilderEnumInterface::class)) {
271+
$this->registerGraphQl();
272+
}
273+
}
274+
275+
private function registerGraphQl(): void
276+
{
277+
$this->app->singleton('api_platform.graphql.state_provider.parameter', function (Application $app) {
278+
$tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
279+
$tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);
280+
281+
return new ParameterProvider(
282+
new ParameterValidatorProvider(
283+
new SecurityParameterProvider(
284+
$app->make(GraphQlDenormalizeProvider::class),
285+
$app->make(ResourceAccessCheckerInterface::class)
286+
),
287+
),
288+
new ServiceLocator($tagged)
289+
);
290+
});
291+
292+
$this->app->singleton(FieldsBuilderEnumInterface::class, function (Application $app) {
293+
/** @var ConfigRepository */
294+
$config = $app['config'];
295+
296+
return new FieldsBuilder(
297+
$app->make(PropertyNameCollectionFactoryInterface::class),
298+
$app->make(PropertyMetadataFactoryInterface::class),
299+
$app->make(ResourceMetadataCollectionFactoryInterface::class),
300+
$app->make(ResourceClassResolverInterface::class),
301+
$app->make(TypesContainerInterface::class),
302+
$app->make(ContextAwareTypeBuilderInterface::class),
303+
$app->make(TypeConverterInterface::class),
304+
$app->make(ResolverFactoryInterface::class),
305+
$app->make('filters'),
306+
$app->make(Pagination::class),
307+
$app->make(NameConverterInterface::class),
308+
$config->get('api-platform.graphql.nesting_separator') ?? '__',
309+
$app->make(InflectorInterface::class)
310+
);
311+
});
152312
}
153313

154314
/**
@@ -186,6 +346,9 @@ public function provides(): array
186346
ParameterProvider::class,
187347
FilterQueryExtension::class,
188348
'filters',
349+
ResourceMetadataCollectionFactoryInterface::class,
350+
'api_platform.graphql.state_provider.parameter',
351+
FieldsBuilderEnumInterface::class,
189352
];
190353
}
191354
}

0 commit comments

Comments
 (0)