• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 14035635597

24 Mar 2025 12:46PM UTC coverage: 8.513% (-7.6%) from 16.086%
14035635597

push

github

web-flow
fix(laravel): defer "filters" dependent services (#7045)

0 of 109 new or added lines in 1 file covered. (0.0%)

3078 existing lines in 187 files now uncovered.

13386 of 157241 relevant lines covered (8.51%)

22.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Laravel/ApiPlatformDeferredProvider.php
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;
15

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;
23
use ApiPlatform\JsonApi\Filter\SparseFieldset;
24
use ApiPlatform\JsonApi\Filter\SparseFieldsetParameterProvider;
25
use ApiPlatform\Laravel\Controller\ApiPlatformController;
26
use ApiPlatform\Laravel\Eloquent\Extension\FilterQueryExtension;
27
use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface;
28
use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter;
29
use ApiPlatform\Laravel\Eloquent\Filter\DateFilter;
30
use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter;
31
use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface as EloquentFilterInterface;
32
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilter;
33
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilterParameterProvider;
34
use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
35
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
36
use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter;
37
use ApiPlatform\Laravel\Eloquent\Metadata\Factory\Resource\EloquentResourceCollectionMetadataFactory;
38
use ApiPlatform\Laravel\Eloquent\State\CollectionProvider;
39
use ApiPlatform\Laravel\Eloquent\State\ItemProvider;
40
use ApiPlatform\Laravel\Eloquent\State\LinksHandler;
41
use ApiPlatform\Laravel\Eloquent\State\LinksHandlerInterface;
42
use ApiPlatform\Laravel\Eloquent\State\PersistProcessor;
43
use ApiPlatform\Laravel\Eloquent\State\RemoveProcessor;
44
use ApiPlatform\Laravel\Exception\ErrorHandler;
45
use ApiPlatform\Laravel\Metadata\CacheResourceCollectionMetadataFactory;
46
use ApiPlatform\Laravel\Metadata\ParameterValidationResourceMetadataCollectionFactory;
47
use ApiPlatform\Laravel\State\ParameterValidatorProvider;
48
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;
66
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
67
use ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory;
68
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
69
use ApiPlatform\Metadata\ResourceClassResolverInterface;
70
use ApiPlatform\Metadata\Util\ReflectionClassRecursiveIterator;
71
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
72
use ApiPlatform\Serializer\Filter\PropertyFilter;
73
use ApiPlatform\Serializer\Parameter\SerializerFilterParameterProvider;
74
use ApiPlatform\State\CallableProcessor;
75
use ApiPlatform\State\CallableProvider;
76
use ApiPlatform\State\ErrorProvider;
77
use ApiPlatform\State\Pagination\Pagination;
78
use ApiPlatform\State\ParameterProviderInterface;
79
use ApiPlatform\State\ProcessorInterface;
80
use ApiPlatform\State\Provider\DeserializeProvider;
81
use ApiPlatform\State\Provider\ParameterProvider;
82
use ApiPlatform\State\Provider\SecurityParameterProvider;
83
use ApiPlatform\State\ProviderInterface;
84
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerInterface;
85
use Illuminate\Contracts\Foundation\Application;
86
use Illuminate\Contracts\Support\DeferrableProvider;
87
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;
92

93
class ApiPlatformDeferredProvider extends ServiceProvider implements DeferrableProvider
94
{
95
    /**
96
     * Register any application services.
97
     */
98
    public function register(): void
99
    {
100
        $directory = app_path();
×
101
        $classes = ReflectionClassRecursiveIterator::getReflectionClassesFromDirectories([$directory]);
×
102

103
        $this->autoconfigure($classes, QueryExtensionInterface::class, [FilterQueryExtension::class]);
×
104
        $this->app->singleton(ItemProvider::class, function (Application $app) {
×
105
            $tagged = iterator_to_array($app->tagged(LinksHandlerInterface::class));
×
106

107
            return new ItemProvider(new LinksHandler($app, $app->make(ResourceMetadataCollectionFactoryInterface::class)), new ServiceLocator($tagged), $app->tagged(QueryExtensionInterface::class));
×
108
        });
×
109

110
        $this->app->singleton(CollectionProvider::class, function (Application $app) {
×
111
            $tagged = iterator_to_array($app->tagged(LinksHandlerInterface::class));
×
112

113
            return new CollectionProvider($app->make(Pagination::class), new LinksHandler($app, $app->make(ResourceMetadataCollectionFactoryInterface::class)), $app->tagged(QueryExtensionInterface::class), new ServiceLocator($tagged));
×
114
        });
×
115

116
        $this->app->singleton(SerializerFilterParameterProvider::class, function (Application $app) {
×
117
            $tagged = iterator_to_array($app->tagged(SerializerFilterInterface::class));
×
118

119
            return new SerializerFilterParameterProvider(new ServiceLocator($tagged));
×
120
        });
×
121
        $this->app->alias(SerializerFilterParameterProvider::class, 'api_platform.serializer.filter_parameter_provider');
×
122

123
        $this->app->singleton('filters', function (Application $app) {
×
124
            return new ServiceLocator(array_merge(
×
125
                iterator_to_array($app->tagged(SerializerFilterInterface::class)),
×
126
                iterator_to_array($app->tagged(EloquentFilterInterface::class))
×
127
            ));
×
128
        });
×
129

130
        $this->autoconfigure($classes, SerializerFilterInterface::class, [PropertyFilter::class]);
×
131

132
        $this->app->singleton(ParameterProvider::class, function (Application $app) {
×
133
            $tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
×
134
            $tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);
×
135

136
            return new ParameterProvider(
×
137
                new ParameterValidatorProvider(
×
138
                    new SecurityParameterProvider(
×
139
                        $app->make(DeserializeProvider::class),
×
140
                        $app->make(ResourceAccessCheckerInterface::class)
×
141
                    ),
×
142
                ),
×
143
                new ServiceLocator($tagged)
×
144
            );
×
145
        });
×
146

147
        $this->autoconfigure($classes, ParameterProviderInterface::class, [SerializerFilterParameterProvider::class, SortFilterParameterProvider::class, SparseFieldsetParameterProvider::class]);
×
148

149
        $this->app->bind(FilterQueryExtension::class, function (Application $app) {
×
150
            $tagged = iterator_to_array($app->tagged(EloquentFilterInterface::class));
×
151

152
            return new FilterQueryExtension(new ServiceLocator($tagged));
×
153
        });
×
154

155
        $this->autoconfigure($classes, EloquentFilterInterface::class, [
×
156
            BooleanFilter::class,
×
157
            EqualsFilter::class,
×
158
            PartialSearchFilter::class,
×
159
            DateFilter::class,
×
160
            OrderFilter::class,
×
161
            RangeFilter::class,
×
162
            SortFilter::class,
×
163
            SparseFieldset::class,
×
164
        ]);
×
165

166
        $this->app->singleton(CallableProcessor::class, function (Application $app) {
×
167
            /** @var ConfigRepository */
168
            $config = $app['config'];
×
169
            $tagged = iterator_to_array($app->tagged(ProcessorInterface::class));
×
170

171
            if ($config->get('api-platform.swagger_ui.enabled', false)) {
×
172
                // TODO: tag SwaggerUiProcessor instead?
173
                $tagged['api_platform.swagger_ui.processor'] = $app->make(SwaggerUiProcessor::class);
×
174
            }
175

176
            return new CallableProcessor(new ServiceLocator($tagged));
×
177
        });
×
178

179
        $this->autoconfigure($classes, ProcessorInterface::class, [RemoveProcessor::class, PersistProcessor::class]);
×
180

181
        $this->app->singleton(CallableProvider::class, function (Application $app) {
×
182
            $tagged = iterator_to_array($app->tagged(ProviderInterface::class));
×
183

184
            return new CallableProvider(new ServiceLocator($tagged));
×
185
        });
×
186

187
        $this->autoconfigure($classes, ProviderInterface::class, [ItemProvider::class, CollectionProvider::class, ErrorProvider::class]);
×
188

NEW
189
        $this->app->singleton(ResourceMetadataCollectionFactoryInterface::class, function (Application $app) {
×
190
            /** @var ConfigRepository $config */
NEW
191
            $config = $app['config'];
×
NEW
192
            $formats = $config->get('api-platform.formats');
×
193

NEW
194
            if ($config->get('api-platform.swagger_ui.enabled', false) && !isset($formats['html'])) {
×
NEW
195
                $formats['html'] = ['text/html'];
×
196
            }
197

NEW
198
            return new CacheResourceCollectionMetadataFactory(
×
NEW
199
                new EloquentResourceCollectionMetadataFactory(
×
NEW
200
                    new ParameterValidationResourceMetadataCollectionFactory(
×
NEW
201
                        new ParameterResourceMetadataCollectionFactory(
×
NEW
202
                            $this->app->make(PropertyNameCollectionFactoryInterface::class),
×
NEW
203
                            $this->app->make(PropertyMetadataFactoryInterface::class),
×
NEW
204
                            new AlternateUriResourceMetadataCollectionFactory(
×
NEW
205
                                new FiltersResourceMetadataCollectionFactory(
×
NEW
206
                                    new FormatsResourceMetadataCollectionFactory(
×
NEW
207
                                        new InputOutputResourceMetadataCollectionFactory(
×
NEW
208
                                            new PhpDocResourceMetadataCollectionFactory(
×
NEW
209
                                                new OperationNameResourceMetadataCollectionFactory(
×
NEW
210
                                                    new LinkResourceMetadataCollectionFactory(
×
NEW
211
                                                        $app->make(LinkFactoryInterface::class),
×
NEW
212
                                                        new UriTemplateResourceMetadataCollectionFactory(
×
NEW
213
                                                            $app->make(LinkFactoryInterface::class),
×
NEW
214
                                                            $app->make(PathSegmentNameGeneratorInterface::class),
×
NEW
215
                                                            new NotExposedOperationResourceMetadataCollectionFactory(
×
NEW
216
                                                                $app->make(LinkFactoryInterface::class),
×
NEW
217
                                                                new AttributesResourceMetadataCollectionFactory(
×
NEW
218
                                                                    new ConcernsResourceMetadataCollectionFactory(
×
NEW
219
                                                                        null,
×
NEW
220
                                                                        $app->make(LoggerInterface::class),
×
NEW
221
                                                                        $config->get('api-platform.defaults', []),
×
NEW
222
                                                                        $config->get('api-platform.graphql.enabled'),
×
NEW
223
                                                                    ),
×
NEW
224
                                                                    $app->make(LoggerInterface::class),
×
NEW
225
                                                                    $config->get('api-platform.defaults', []),
×
NEW
226
                                                                    $config->get('api-platform.graphql.enabled'),
×
NEW
227
                                                                ),
×
NEW
228
                                                            )
×
NEW
229
                                                        ),
×
NEW
230
                                                        $config->get('api-platform.graphql.enabled')
×
NEW
231
                                                    )
×
NEW
232
                                                )
×
NEW
233
                                            )
×
NEW
234
                                        ),
×
NEW
235
                                        $formats,
×
NEW
236
                                        $config->get('api-platform.patch_formats'),
×
NEW
237
                                    )
×
NEW
238
                                )
×
NEW
239
                            ),
×
NEW
240
                            $app->make('filters'),
×
NEW
241
                            $app->make(CamelCaseToSnakeCaseNameConverter::class),
×
NEW
242
                            $this->app->make(LoggerInterface::class)
×
NEW
243
                        ),
×
NEW
244
                        $app->make('filters')
×
NEW
245
                    )
×
NEW
246
                ),
×
NEW
247
                true === $config->get('app.debug') ? 'array' : $config->get('api-platform.cache', 'file')
×
NEW
248
            );
×
NEW
249
        });
×
250

NEW
251
        $this->app->singleton(
×
NEW
252
            ExceptionHandlerInterface::class,
×
NEW
253
            function (Application $app) {
×
254
                /** @var ConfigRepository */
NEW
255
                $config = $app['config'];
×
256

NEW
257
                return new ErrorHandler(
×
NEW
258
                    $app,
×
NEW
259
                    $app->make(ResourceMetadataCollectionFactoryInterface::class),
×
NEW
260
                    $app->make(ApiPlatformController::class),
×
NEW
261
                    $app->make(IdentifiersExtractorInterface::class),
×
NEW
262
                    $app->make(ResourceClassResolverInterface::class),
×
NEW
263
                    $app->make(Negotiator::class),
×
NEW
264
                    $config->get('api-platform.exception_to_status'),
×
NEW
265
                    $config->get('app.debug')
×
NEW
266
                );
×
NEW
267
            }
×
NEW
268
        );
×
269

NEW
270
        if (interface_exists(FieldsBuilderEnumInterface::class)) {
×
NEW
271
            $this->registerGraphQl();
×
272
        }
273
    }
274

275
    private function registerGraphQl(): void
276
    {
NEW
277
        $this->app->singleton('api_platform.graphql.state_provider.parameter', function (Application $app) {
×
NEW
278
            $tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
×
NEW
279
            $tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);
×
280

NEW
281
            return new ParameterProvider(
×
NEW
282
                new ParameterValidatorProvider(
×
NEW
283
                    new SecurityParameterProvider(
×
NEW
284
                        $app->make(GraphQlDenormalizeProvider::class),
×
NEW
285
                        $app->make(ResourceAccessCheckerInterface::class)
×
NEW
286
                    ),
×
NEW
287
                ),
×
NEW
288
                new ServiceLocator($tagged)
×
NEW
289
            );
×
NEW
290
        });
×
291

NEW
292
        $this->app->singleton(FieldsBuilderEnumInterface::class, function (Application $app) {
×
293
            /** @var ConfigRepository */
NEW
294
            $config = $app['config'];
×
295

NEW
296
            return new FieldsBuilder(
×
NEW
297
                $app->make(PropertyNameCollectionFactoryInterface::class),
×
NEW
298
                $app->make(PropertyMetadataFactoryInterface::class),
×
NEW
299
                $app->make(ResourceMetadataCollectionFactoryInterface::class),
×
NEW
300
                $app->make(ResourceClassResolverInterface::class),
×
NEW
301
                $app->make(TypesContainerInterface::class),
×
NEW
302
                $app->make(ContextAwareTypeBuilderInterface::class),
×
NEW
303
                $app->make(TypeConverterInterface::class),
×
NEW
304
                $app->make(ResolverFactoryInterface::class),
×
NEW
305
                $app->make('filters'),
×
NEW
306
                $app->make(Pagination::class),
×
NEW
307
                $app->make(NameConverterInterface::class),
×
NEW
308
                $config->get('api-platform.graphql.nesting_separator') ?? '__',
×
NEW
309
                $app->make(InflectorInterface::class)
×
NEW
310
            );
×
NEW
311
        });
×
312
    }
313

314
    /**
315
     * @param array<class-string, \ReflectionClass> $classes
316
     * @param class-string                          $interface
317
     * @param array<int, class-string>              $apiPlatformProviders
318
     */
319
    private function autoconfigure(array $classes, string $interface, array $apiPlatformProviders): void
320
    {
321
        $m = $apiPlatformProviders;
×
322
        foreach ($classes as $className => $refl) {
×
323
            if ($refl->implementsInterface($interface)) {
×
324
                $m[] = $className;
×
325
            }
326
        }
327

328
        if ($m) {
×
329
            $this->app->tag($m, $interface);
×
330
        }
331
    }
332

333
    /**
334
     * Get the services provided by the provider.
335
     *
336
     * @return array<int, string>
337
     */
338
    public function provides(): array
339
    {
340
        return [
×
341
            CallableProvider::class,
×
342
            CallableProcessor::class,
×
343
            ItemProvider::class,
×
344
            CollectionProvider::class,
×
345
            SerializerFilterParameterProvider::class,
×
346
            ParameterProvider::class,
×
347
            FilterQueryExtension::class,
×
348
            'filters',
×
NEW
349
            ResourceMetadataCollectionFactoryInterface::class,
×
NEW
350
            'api_platform.graphql.state_provider.parameter',
×
NEW
351
            FieldsBuilderEnumInterface::class,
×
UNCOV
352
        ];
×
353
    }
354
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc