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

api-platform / core / 15927963363

27 Jun 2025 01:52PM UTC coverage: 22.489% (-0.01%) from 22.5%
15927963363

Pull #7247

github

web-flow
Merge 66de6d385 into d3e73f09a
Pull Request #7247: fix(laravel): decorate error handler

0 of 10 new or added lines in 2 files covered. (0.0%)

8 existing lines in 4 files now uncovered.

11063 of 49194 relevant lines covered (22.49%)

11.03 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\EndSearchFilter;
31
use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter;
32
use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface as EloquentFilterInterface;
33
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilter;
34
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilterParameterProvider;
35
use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
36
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
37
use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter;
38
use ApiPlatform\Laravel\Eloquent\Filter\StartSearchFilter;
39
use ApiPlatform\Laravel\Eloquent\Metadata\Factory\Resource\EloquentResourceCollectionMetadataFactory;
40
use ApiPlatform\Laravel\Eloquent\State\CollectionProvider;
41
use ApiPlatform\Laravel\Eloquent\State\ItemProvider;
42
use ApiPlatform\Laravel\Eloquent\State\LinksHandler;
43
use ApiPlatform\Laravel\Eloquent\State\LinksHandlerInterface;
44
use ApiPlatform\Laravel\Eloquent\State\PersistProcessor;
45
use ApiPlatform\Laravel\Eloquent\State\RemoveProcessor;
46
use ApiPlatform\Laravel\Exception\ErrorHandler;
47
use ApiPlatform\Laravel\Metadata\CacheResourceCollectionMetadataFactory;
48
use ApiPlatform\Laravel\Metadata\ParameterValidationResourceMetadataCollectionFactory;
49
use ApiPlatform\Laravel\State\ParameterValidatorProvider;
50
use ApiPlatform\Laravel\State\SwaggerUiProcessor;
51
use ApiPlatform\Laravel\State\ValidateProvider;
52
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
53
use ApiPlatform\Metadata\InflectorInterface;
54
use ApiPlatform\Metadata\Operation\PathSegmentNameGeneratorInterface;
55
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
56
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
57
use ApiPlatform\Metadata\Resource\Factory\AlternateUriResourceMetadataCollectionFactory;
58
use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
59
use ApiPlatform\Metadata\Resource\Factory\ConcernsResourceMetadataCollectionFactory;
60
use ApiPlatform\Metadata\Resource\Factory\FiltersResourceMetadataCollectionFactory;
61
use ApiPlatform\Metadata\Resource\Factory\FormatsResourceMetadataCollectionFactory;
62
use ApiPlatform\Metadata\Resource\Factory\InputOutputResourceMetadataCollectionFactory;
63
use ApiPlatform\Metadata\Resource\Factory\LinkFactoryInterface;
64
use ApiPlatform\Metadata\Resource\Factory\LinkResourceMetadataCollectionFactory;
65
use ApiPlatform\Metadata\Resource\Factory\NotExposedOperationResourceMetadataCollectionFactory;
66
use ApiPlatform\Metadata\Resource\Factory\OperationNameResourceMetadataCollectionFactory;
67
use ApiPlatform\Metadata\Resource\Factory\ParameterResourceMetadataCollectionFactory;
68
use ApiPlatform\Metadata\Resource\Factory\PhpDocResourceMetadataCollectionFactory;
69
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
70
use ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory;
71
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
72
use ApiPlatform\Metadata\ResourceClassResolverInterface;
73
use ApiPlatform\Metadata\Util\ReflectionClassRecursiveIterator;
74
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
75
use ApiPlatform\Serializer\Filter\PropertyFilter;
76
use ApiPlatform\Serializer\Parameter\SerializerFilterParameterProvider;
77
use ApiPlatform\State\CallableProcessor;
78
use ApiPlatform\State\CallableProvider;
79
use ApiPlatform\State\ErrorProvider;
80
use ApiPlatform\State\Pagination\Pagination;
81
use ApiPlatform\State\ParameterProviderInterface;
82
use ApiPlatform\State\ProcessorInterface;
83
use ApiPlatform\State\Provider\ParameterProvider;
84
use ApiPlatform\State\Provider\SecurityParameterProvider;
85
use ApiPlatform\State\ProviderInterface;
86
use Illuminate\Contracts\Debug\ExceptionHandler;
87
use Illuminate\Contracts\Foundation\Application;
88
use Illuminate\Contracts\Support\DeferrableProvider;
89
use Illuminate\Support\ServiceProvider;
90
use Negotiation\Negotiator;
91
use Psr\Log\LoggerInterface;
92
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
93
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
94

95
class ApiPlatformDeferredProvider extends ServiceProvider implements DeferrableProvider
96
{
97
    /**
98
     * Register any application services.
99
     */
100
    public function register(): void
101
    {
102
        $directory = app_path();
×
103
        $classes = ReflectionClassRecursiveIterator::getReflectionClassesFromDirectories([$directory], '(?!.*Test\.php$)');
×
104

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

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

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

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

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

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

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

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

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

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

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

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

154
            return new FilterQueryExtension(new ServiceLocator($tagged));
×
155
        });
×
156

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

170
        $this->app->singleton(CallableProcessor::class, function (Application $app) {
×
171
            /** @var ConfigRepository */
172
            $config = $app['config'];
×
173
            $tagged = iterator_to_array($app->tagged(ProcessorInterface::class));
×
174

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

180
            return new CallableProcessor(new ServiceLocator($tagged));
×
181
        });
×
182

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

185
        $this->app->singleton(CallableProvider::class, function (Application $app) {
×
186
            $tagged = iterator_to_array($app->tagged(ProviderInterface::class));
×
187

188
            return new CallableProvider(new ServiceLocator($tagged));
×
189
        });
×
190

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

193
        $this->app->singleton(ResourceMetadataCollectionFactoryInterface::class, function (Application $app) {
×
194
            /** @var ConfigRepository $config */
195
            $config = $app['config'];
×
196
            $formats = $config->get('api-platform.formats');
×
197

198
            if ($config->get('api-platform.swagger_ui.enabled', false) && !isset($formats['html'])) {
×
199
                $formats['html'] = ['text/html'];
×
200
            }
201

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

NEW
255
        $this->app->extend(
×
NEW
256
            ExceptionHandler::class,
×
NEW
257
            function (ExceptionHandler $decorated, Application $app) {
×
258
                /** @var ConfigRepository */
259
                $config = $app['config'];
×
260

261
                return new ErrorHandler(
×
262
                    $app,
×
263
                    $app->make(ResourceMetadataCollectionFactoryInterface::class),
×
264
                    $app->make(ApiPlatformController::class),
×
265
                    $app->make(IdentifiersExtractorInterface::class),
×
266
                    $app->make(ResourceClassResolverInterface::class),
×
267
                    $app->make(Negotiator::class),
×
268
                    $config->get('api-platform.exception_to_status'),
×
269
                    $config->get('app.debug'),
×
NEW
270
                    $config->get('api-platform.error_formats'),
×
NEW
271
                    $decorated
×
272
                );
×
273
            }
×
274
        );
×
275

276
        if (interface_exists(FieldsBuilderEnumInterface::class)) {
×
277
            $this->registerGraphQl();
×
278
        }
279
    }
280

281
    private function registerGraphQl(): void
282
    {
283
        $this->app->singleton('api_platform.graphql.state_provider.parameter', function (Application $app) {
×
284
            $tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
×
285
            $tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);
×
286

287
            return new ParameterProvider(
×
288
                new ParameterValidatorProvider(
×
289
                    new SecurityParameterProvider(
×
290
                        $app->make(GraphQlDenormalizeProvider::class),
×
291
                        $app->make(ResourceAccessCheckerInterface::class)
×
292
                    ),
×
293
                ),
×
294
                new ServiceLocator($tagged)
×
295
            );
×
296
        });
×
297

298
        $this->app->singleton(FieldsBuilderEnumInterface::class, function (Application $app) {
×
299
            /** @var ConfigRepository */
300
            $config = $app['config'];
×
301

302
            return new FieldsBuilder(
×
303
                $app->make(PropertyNameCollectionFactoryInterface::class),
×
304
                $app->make(PropertyMetadataFactoryInterface::class),
×
305
                $app->make(ResourceMetadataCollectionFactoryInterface::class),
×
306
                $app->make(ResourceClassResolverInterface::class),
×
307
                $app->make(TypesContainerInterface::class),
×
308
                $app->make(ContextAwareTypeBuilderInterface::class),
×
309
                $app->make(TypeConverterInterface::class),
×
310
                $app->make(ResolverFactoryInterface::class),
×
311
                $app->make('filters'),
×
312
                $app->make(Pagination::class),
×
313
                $app->make(NameConverterInterface::class),
×
314
                $config->get('api-platform.graphql.nesting_separator') ?? '__',
×
315
                $app->make(InflectorInterface::class)
×
316
            );
×
317
        });
×
318
    }
319

320
    /**
321
     * @param array<class-string, \ReflectionClass> $classes
322
     * @param class-string                          $interface
323
     * @param array<int, class-string>              $apiPlatformProviders
324
     */
325
    private function autoconfigure(array $classes, string $interface, array $apiPlatformProviders): void
326
    {
327
        $m = $apiPlatformProviders;
×
328
        foreach ($classes as $className => $refl) {
×
329
            if ($refl->implementsInterface($interface)) {
×
330
                $m[] = $className;
×
331
            }
332
        }
333

334
        if ($m) {
×
335
            $this->app->tag($m, $interface);
×
336
        }
337
    }
338

339
    /**
340
     * Get the services provided by the provider.
341
     *
342
     * @return array<int, string>
343
     */
344
    public function provides(): array
345
    {
346
        return [
×
347
            CallableProvider::class,
×
348
            CallableProcessor::class,
×
349
            ItemProvider::class,
×
350
            CollectionProvider::class,
×
351
            SerializerFilterParameterProvider::class,
×
352
            ParameterProvider::class,
×
353
            FilterQueryExtension::class,
×
354
            'filters',
×
355
            ResourceMetadataCollectionFactoryInterface::class,
×
356
            'api_platform.graphql.state_provider.parameter',
×
357
            FieldsBuilderEnumInterface::class,
×
358
            ExceptionHandlerInterface::class,
×
359
        ];
×
360
    }
361
}
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

© 2025 Coveralls, Inc