Bootstrapping the Core Library not up to date · Issue #2133 · api-platform/docs · GitHub
Skip to content

Bootstrapping the Core Library not up to date #2133

Description

@tsaquet

Hello !

I'm trying to bootstrap api platform on a legacy vanilla php project that we do not want to migrate to symfony or laravel.

I read the documentation available here : Bootstrapping the Core Library

If I understand well, the "minimal version of API Platform" seems outdated, many interfaces have changed, many classes are not located where they used to be.

I am trying to make it work, not knowing the code it is a bit complicated, could you update the doc ? Or, if so, tell me what I misunderstood ?

Thank you :)

Activity

  1. vinceAmstoutz commented on Jan 16, 2025

    @vinceAmstoutz
    Member

    @tsaquet Yes, you're right, this documentation is out of date. We plan to update it as soon as possible, but if you'd like us to investigate the code to find out how it works internally, we can help you and update the documentation simultaneously.

  2. tsaquet commented on Jan 16, 2025

    @tsaquet
    Author

    Thank you for this quick answer. There are 65 problems at the file opening, here is what I found (or not) for each line.
    In bold what I could not solve.

    :146 \ApiPlatform\Exception\InvalidArgumentException -> \ApiPlatform\Metadata\Exception\InvalidArgumentException
    :147 \ApiPlatform\Exception\FilterValidationException -> Did not find a solution
    :165 new AnnotationLoader() -> new AttributeLoader()
    :180 changed import from use ApiPlatform\Operation\UnderscorePathSegmentNameGenerator; to use ApiPlatform\Metadata\Operation\UnderscorePathSegmentNameGenerator;
    :183 changed import from use ApiPlatform\Api\ResourceClassResolver; to use ApiPlatform\Metadata\ResourceClassResolver;
    :274 Validator -> ObjectProvider
    :308 changed import from use ApiPlatform\Api\IdentifiersExtractor; to use ApiPlatform\Metadata\IdentifiersExtractor;
    :346 $this->container -> Did not find a solution
    :353 changed import from use ApiPlatform\Action\PlaceholderAction; to use ApiPlatform\Symfony\Action\PlaceholderAction;
    :398 added return type RouteCollection
    :406 added return type void
    :419 used MetadataUrlGeneratorInterface instead of ApiUrlGeneratorInterface
    :437 from use ApiPlatform\Api\UriVariableTransformer\IntegerUriVariableTransformer; to use ApiPlatform\Metadata\UriVariableTransformer\IntegerUriVariableTransformer;
    :438 from use ApiPlatform\Api\UriVariableTransformer\DateTimeUriVariableTransformer; to use ApiPlatform\Metadata\UriVariableTransformer\DateTimeUriVariableTransformer;
    :447 from use ApiPlatform\Api\UriVariablesConverter; to use ApiPlatform\Metadata\UriVariablesConverter;
    :453 $iriConverter not expected type -> Did not find a solution
    :454 $resourceClassResolver not expected type -> Did not find a solution
    :474 HydraErrorNormalizer -> Did not find a solution
    :477 HydraConstraintViolationListNormalizer signature not ok anymore -> Did not find a solution
    :479 ErrorNormalizer -> Did not find a solution
    :560 -> :568 -> Serializers & listeners -> Did not find a solution
    :602 return type not expected -> Did not find a solution
    :615 TypeFactory -> Tried SchemaFactory but not successful
    :616 from use ApiPlatform\JsonSchema\SchemaFactory; to use ApiPlatform\JsonApi\JsonSchema\SchemaFactory; -> not enough
    :622 EntrypointAction -> think I found a replacement but method signature has changed

    :628 use ApiPlatform\Action\NotExposedAction; to use ApiPlatform\Symfony\Action\NotExposedAction;

  3. tsaquet commented on Jan 16, 2025

    @tsaquet
    Author

    The file with the changes in the previous message.

    <?php
    
    require './vendor/autoload.php';
    
    use ApiPlatform\Action\EntrypointAction;
    use ApiPlatform\Action\ExceptionAction;
    use ApiPlatform\Symfony\Validator\EventListener\ValidationExceptionListener;
    use ApiPlatform\Documentation\DocumentationInterface;
    use ApiPlatform\Symfony\EventListener\AddFormatListener;
    use ApiPlatform\Symfony\EventListener\DeserializeListener;
    use ApiPlatform\Symfony\EventListener\ExceptionListener;
    use ApiPlatform\Symfony\EventListener\ReadListener;
    use ApiPlatform\Symfony\EventListener\WriteListener;
    use ApiPlatform\State\Pagination\PaginationOptions;
    use ApiPlatform\Symfony\EventListener\RespondListener;
    use ApiPlatform\Symfony\EventListener\SerializeListener;
    use ApiPlatform\Hal\Serializer\CollectionNormalizer as HalCollectionNormalizer;
    use ApiPlatform\Hal\Serializer\EntrypointNormalizer as HalEntrypointNormalizer;
    use ApiPlatform\Hal\Serializer\ItemNormalizer as HalItemNormalizer;
    use ApiPlatform\Hal\Serializer\ObjectNormalizer as HalObjectNormalizer;
    use ApiPlatform\Hydra\EventListener\AddLinkHeaderListener;
    use ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer;
    use ApiPlatform\Hydra\Serializer\CollectionNormalizer as HydraCollectionNormalizer;
    use ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer as HydraConstraintViolationListNormalizer;
    use ApiPlatform\Hydra\Serializer\DocumentationNormalizer as HydraDocumentationNormalizer;
    use ApiPlatform\Hydra\Serializer\EntrypointNormalizer as HydraEntrypointNormalizer;
    use ApiPlatform\Hydra\Serializer\ErrorNormalizer as HydraErrorNormalizer;
    use ApiPlatform\Hydra\Serializer\PartialCollectionViewNormalizer;
    use ApiPlatform\JsonApi\JsonSchema\SchemaFactory;
    use ApiPlatform\JsonLd\Action\ContextAction;
    use ApiPlatform\JsonLd\Serializer\ItemNormalizer as JsonLdItemNormalizer;
    use ApiPlatform\JsonLd\Serializer\ObjectNormalizer as JsonLdObjectNormalizer;
    use ApiPlatform\JsonLd\ContextBuilder as JsonLdContextBuilder;
    use ApiPlatform\JsonSchema\TypeFactory;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatform\Metadata\CollectionOperationInterface;
    use ApiPlatform\Metadata\HttpOperation;
    use ApiPlatform\Metadata\IdentifiersExtractor;
    use ApiPlatform\Metadata\Operation;
    use ApiPlatform\Metadata\Operation\UnderscorePathSegmentNameGenerator;
    use ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyMetadataFactory;
    use ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyNameCollectionFactory;
    use ApiPlatform\Metadata\Property\Factory\SerializerPropertyMetadataFactory;
    use ApiPlatform\Metadata\Resource\Factory\AlternateUriResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\AttributesResourceNameCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\FiltersResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\FormatsResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\InputOutputResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\LinkFactory;
    use ApiPlatform\Metadata\Resource\Factory\LinkResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\NotExposedOperationResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\OperationNameResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\PhpDocResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
    use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
    use ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory;
    use ApiPlatform\Metadata\ResourceClassResolver;
    use ApiPlatform\Metadata\UriVariablesConverter;
    use ApiPlatform\Metadata\UriVariableTransformer\DateTimeUriVariableTransformer;
    use ApiPlatform\Metadata\UriVariableTransformer\IntegerUriVariableTransformer;
    use ApiPlatform\Metadata\UrlGeneratorInterface as MetadataUrlGeneratorInterface;
    use ApiPlatform\OpenApi\Factory\OpenApiFactory;
    use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
    use ApiPlatform\OpenApi\Options as OpenApiOptions;
    use ApiPlatform\OpenApi\Serializer\OpenApiNormalizer;
    use ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer as ProblemConstraintViolationListNormalizer;
    use ApiPlatform\Problem\Serializer\ErrorNormalizer;
    use ApiPlatform\Serializer\ItemNormalizer;
    use ApiPlatform\Serializer\JsonEncoder as JsonLdEncoder;
    use ApiPlatform\Serializer\Mapping\Factory\ClassMetadataFactory as ApiClassMetadataFactory;
    use ApiPlatform\Serializer\SerializerContextBuilder;
    use ApiPlatform\State\CallableProcessor;
    use ApiPlatform\State\CallableProvider;
    use ApiPlatform\State\ObjectProvider;
    use ApiPlatform\State\ProcessorInterface;
    use ApiPlatform\State\ProviderInterface;
    use ApiPlatform\Symfony\Action\NotExposedAction;
    use ApiPlatform\Symfony\Action\PlaceholderAction;
    use ApiPlatform\Symfony\Routing\IriConverter;
    use ApiPlatform\Symfony\EventListener\ValidateListener;
    use ApiPlatform\Symfony\Messenger\Metadata\MessengerResourceMetadataCollectionFactory;
    use ApiPlatform\Symfony\Routing\SkolemIriConverter;
    use ApiPlatform\Validator\ValidatorInterface;
    use Negotiation\Negotiator;
    use Psr\Container\ContainerInterface;
    use Symfony\Component\EventDispatcher\EventDispatcher;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\RequestStack;
    use Symfony\Component\HttpKernel\Log\Logger;
    use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
    use Symfony\Component\HttpKernel\Controller\ControllerResolver;
    use Symfony\Component\HttpKernel\EventListener\ErrorListener;
    use Symfony\Component\HttpKernel\EventListener\RouterListener;
    use Symfony\Component\HttpKernel\HttpKernel;
    use Symfony\Component\PropertyAccess\PropertyAccess;
    use Symfony\Component\Routing\Matcher\UrlMatcher;
    use Symfony\Component\Routing\RequestContext;
    use Symfony\Component\Routing\Route;
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
    use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
    use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
    use Symfony\Component\Routing\Generator\UrlGenerator;
    use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
    use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
    use Symfony\Component\Routing\RouterInterface;
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
    use Symfony\Component\Validator\Validation;
    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
    use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
    use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
    use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
    use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
    use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer;
    use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    use Symfony\Component\Serializer\Normalizer\ProblemNormalizer;
    use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
    use Symfony\Component\Serializer\Serializer;
    
    $debug = true;
    $defaultContext = [];
    $patchFormats = ['json' => ['application/merge-patch+json'], 'jsonapi' => ['application/vnd.api+json']];
    $formats = ['jsonld' => ['application/ld+json']];
    $errorFormats = [
        'jsonproblem' => ['application/problem+json'],
        'jsonld' => ['application/ld+json'],
        'jsonapi' => ['application/vnd.api+json']
    ];
    
    $configuration = [
        'collection' => [
            'pagination' => [
                'page_parameter_name' => 'page',
                'enabled_parameter_name' => 'pagination'
            ]
        ]
    ];
    
    $exceptionToStatus = [
        # The 4 following handlers are registered by default, keep those lines to prevent unexpected side effects
        \Symfony\Component\Serializer\Exception\ExceptionInterface::class => 400,
        \ApiPlatform\Metadata\Exception\InvalidArgumentException::class => 400,
        \ApiPlatform\Exception\FilterValidationException::class => 400,
        \Doctrine\ORM\OptimisticLockException::class => 409,
    ];
    
    $logger = new Logger();
    
    $phpDocExtractor = new PhpDocExtractor();
    $reflectionExtractor = new ReflectionExtractor();
    
    $propertyInfo = new PropertyInfoExtractor(
        [$reflectionExtractor],
        [$phpDocExtractor, $reflectionExtractor],
        [$phpDocExtractor],
        [$reflectionExtractor],
        [$reflectionExtractor]
    );
    
    
    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
    
    final class FilterLocator implements ContainerInterface
    {
        private $filters = [];
        public function get(string $id) {
            return $this->filters[$id] ?? null;
        }
    
        public function has(string $id): bool {
            return isset($this->filter[$id]);
        }
    }
    
    $filterLocator = new FilterLocator();
    $pathSegmentNameGenerator = new UnderscorePathSegmentNameGenerator();
    
    $resourceNameCollectionFactory = new AttributesResourceNameCollectionFactory([__DIR__.'/../src/']);
    $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactory);
    $propertyMetadataFactory = new PropertyInfoPropertyMetadataFactory($propertyInfo);
    $propertyMetadataFactory = new SerializerPropertyMetadataFactory(new ApiClassMetadataFactory($classMetadataFactory), $propertyMetadataFactory, $resourceClassResolver);
    
    $propertyNameCollectionFactory = new PropertyInfoPropertyNameCollectionFactory($propertyInfo);
    $linkFactory = new LinkFactory(
        $propertyNameCollectionFactory,
        $propertyMetadataFactory,
        $resourceClassResolver
    );
    
    // CachedResourceMetadataCollectionFactory decoration-priority="-10"
    // MessengerResourceMetadataCollectionFactory decoration-priority="50"
    // AlternateUriResourceMetadataCollectionFactory decoration-priority="200"
    // FiltersResourceMetadataCollectionFactory decoration-priority="200"
    // FormatsResourceMetadataCollectionFactory decoration-priority="200"
    // InputOutputResourceMetadataCollectionFactory decoration-priority="200"
    // PhpDocResourceMetadataCollectionFactory decoration-priority="200"
    // OperationNameResourceMetadataCollectionFactory decoration-priority="200"
    // LinkResourceMetadataCollectionFactory decoration-priority="500"
    // UriTemplateResourceMetadataCollectionFactory decoration-priority="500"
    // NotExposedOperationResourceMetadataCollectionFactory decoration-priority="700"
    // ExtractorResourceMetadataCollectionFactory  decoration-priority="800"
    
    // AttributesResourceMetadataCollectionFactory decorated
    
    $resourceMetadataFactory = new MessengerResourceMetadataCollectionFactory(
        new AlternateUriResourceMetadataCollectionFactory(
            new FiltersResourceMetadataCollectionFactory(
                new FormatsResourceMetadataCollectionFactory(
                    new InputOutputResourceMetadataCollectionFactory(
                        new PhpDocResourceMetadataCollectionFactory(
                            new OperationNameResourceMetadataCollectionFactory(
                                new LinkResourceMetadataCollectionFactory(
                                    $linkFactory,
                                    new UriTemplateResourceMetadataCollectionFactory(
                                        $linkFactory,
                                        $pathSegmentNameGenerator,
                                        new NotExposedOperationResourceMetadataCollectionFactory(
                                            $linkFactory,
                                            new AttributesResourceMetadataCollectionFactory(null, $logger, [], false)
                                        )
                                    )
                                )
                            )
                        )
                    ),
                    $formats,
                    $patchFormats,
                )
            )
        )
    );
    
    $providerCollection = new class implements ContainerInterface {
        public array $providers = [];
        public function get($id) {
            return $this->providers[$id];
        }
    
        public function has($id): bool {
            return isset($this->providers[$id]);
        }
    };
    $stateProviders = new CallableProvider($providerCollection);
    
    $processorCollection = new class implements ContainerInterface {
        public array $processors = [];
        public function get($id) {
            return $this->processors[$id];
        }
    
        public function has($id): bool {
            return isset($this->processors['id']);
        }
    };
    $stateProcessors = new CallableProcessor($processorCollection);
    
    class Validator implements ValidatorInterface {
        private $validator;
        public function __construct($validator)
        {
            $this->validator = $validator;
        }
    
        public function validate(object $data, array $context = []): void {
            $this->validator->validate($data, $context);
        }
    }
    
    $validator = new ObjectProvider(Validation::createValidator());
    $validateListener = new ValidateListener($validator, $resourceMetadataFactory);
    
    #[ApiResource(provider: \BookProvider::class)]
    class Book
    {
        public int $id;
    }
    
    class BookProvider implements ProviderInterface
    {
        public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
        {
            $book = new Book();
            if ($operation instanceof CollectionOperationInterface) {
                return [$book];
            }
    
            $book->id = $uriVariables['id'];
            return $book;
        }
    }
    
    $dataProvider = new BookProvider();
    $providerCollection->providers[BookProvider::class] = $dataProvider;
    
    class BookProcessor implements ProcessorInterface {
        public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) {}
    }
    
    $bookProcessor = new BookProcessor();
    $processorCollection->processors[BookProcessor::class] = $bookProcessor;
    
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
    $identifiersExtractor = new IdentifiersExtractor($resourceMetadataFactory, $resourceClassResolver, $propertyNameCollectionFactory, $propertyMetadataFactory, $propertyAccessor);
    
    class ApiLoader {
        private $resourceNameCollectionFactory;
        private $resourceMetadataFactory;
    
        public function __construct(
            ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory,
            ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory
        ) {
            $this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
            $this->resourceMetadataFactory = $resourceMetadataFactory;
        }
    
        public function load(): RouteCollection
        {
            $routeCollection = new RouteCollection();
    
            foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
                foreach ($this->resourceMetadataFactory->create($resourceClass) as $resourceMetadata) {
                    foreach ($resourceMetadata->getOperations() as $operationName => $operation) {
                        if ($operation->getRouteName()) {
                            continue;
                        }
    
                        if (SkolemIriConverter::$skolemUriTemplate === $operation->getUriTemplate()) {
                            continue;
                        }
    
                        $path = ($operation->getRoutePrefix() ?? '').$operation->getUriTemplate();
                        foreach ($operation->getUriVariables() ?? [] as $parameterName => $link) {
                            if (!$expandedValue = $link->getExpandedValue()) {
                                continue;
                            }
    
                            $path = str_replace(sprintf('{%s}', $parameterName), $expandedValue, $path);
                        }
    
                        if (($controller = $operation->getController()) && !$this->container->has($controller)) {
                            throw new RuntimeException(sprintf('There is no builtin action for the "%s" operation. You need to define the controller yourself.', $operationName));
                        }
    
                        $route = new Route(
                            $path,
                            [
                                '_controller' => $controller ?? PlaceholderAction::class,
                                '_format' => null,
                                '_stateless' => $operation->getStateless(),
                                '_api_resource_class' => $resourceClass,
                                '_api_operation_name' => $operationName,
                            ] + ($operation->getDefaults() ?? []),
                            $operation->getRequirements() ?? [],
                            $operation->getOptions() ?? [],
                            $operation->getHost() ?? '',
                            $operation->getSchemes() ?? [],
                            [$operation->getMethod() ?? HttpOperation::METHOD_GET],
                            $operation->getCondition() ?? ''
                        );
    
                        $routeCollection->add($operationName, $route);
                    }
                }
            }
    
            return $routeCollection;
        }
    }
    
    $apiLoader = new ApiLoader($resourceNameCollectionFactory, $resourceMetadataFactory);
    $routes = $apiLoader->load();
    
    $requestContext = new RequestContext();
    $matcher = new UrlMatcher($routes, $requestContext);
    $generator = new UrlGenerator($routes, $requestContext);
    
    class Router implements RouterInterface
    {
        private $routes;
        private $context;
        private $matcher;
        private $generator;
    
        public function __construct(RouteCollection $routes, UrlMatcherInterface $matcher, UrlGeneratorInterface $generator, RequestContext $requestContext)
        {
            $this->routes = $routes;
            $this->matcher = $matcher;
            $this->generator = $generator;
            $this->context = $requestContext;
        }
    
        public function getRouteCollection(): RouteCollection {
            return $this->routes;
        }
    
        public function match(string $pathinfo): array {
            return $this->matcher->match($pathinfo);
        }
    
        public function setContext(RequestContext $context): void {
            $this->context = $context;
        }
    
        public function getContext(): RequestContext {
            return $this->context;
        }
    
        public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string {
            return $this->generator->generate($name, $parameters, $referenceType);
        }
    }
    
    class ApiUrlGenerator implements MetadataUrlGeneratorInterface {
        private $generator;
    
        public function __construct(UrlGeneratorInterface $generator)
        {
            $this->generator = $generator;
        }
    
        public function generate($name, $parameters = [], $referenceType = self::ABS_PATH): string {
            return $this->generator->generate($name, $parameters, $referenceType ?: self::ABS_PATH);
        }
    }
    
    $apiUrlGenerator = new ApiUrlGenerator($generator);
    
    $router = new Router($routes, $matcher, $generator, $requestContext);
    
    $uriVariableTransformers = [
        new IntegerUriVariableTransformer(),
        new DateTimeUriVariableTransformer(),
    ];
    
    $iriConverter = new IriConverter(
        $stateProviders,
        $router,
        $identifiersExtractor,
        $resourceClassResolver,
        $resourceMetadataFactory,
        new UriVariablesConverter($propertyMetadataFactory, $resourceMetadataFactory, $uriVariableTransformers),
        new SkolemIriConverter($router)
    );
    
    $writeListener = new WriteListener(
        $stateProcessors,
        $iriConverter,
        $resourceClassResolver,
        $resourceMetadataFactory,
        /**new UriVariablesConverter($propertyMetadataFactory, $resourceMetadataFactory, $uriVariableTransformers)*/ null,
    );
    
    $serializerContextBuilder = new SerializerContextBuilder($resourceMetadataFactory);
    
    $objectNormalizer = new ObjectNormalizer();
    
    $nameConverter = new MetadataAwareNameConverter($classMetadataFactory);
    $jsonLdContextBuilder = new JsonLdContextBuilder($resourceNameCollectionFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $apiUrlGenerator, $iriConverter, $nameConverter);
    $jsonLdItemNormalizer = new JsonLdItemNormalizer($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $jsonLdContextBuilder, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, /** resource access checker **/ null);
    $jsonLdObjectNormalizer = new JsonLdObjectNormalizer($objectNormalizer, $iriConverter, $jsonLdContextBuilder);
    $jsonLdEncoder = new JsonLdEncoder('jsonld', new JsonEncoder());
    
    $problemConstraintViolationListNormalizer = new ProblemConstraintViolationListNormalizer([], $nameConverter, $defaultContext);
    
    $hydraCollectionNormalizer = new HydraCollectionNormalizer($jsonLdContextBuilder, $resourceClassResolver, $iriConverter, $resourceMetadataFactory, $defaultContext);
    $hydraPartialCollectionNormalizer = new PartialCollectionViewNormalizer($hydraCollectionNormalizer, $configuration['collection']['pagination']['page_parameter_name'], $configuration['collection']['pagination']['enabled_parameter_name'], $resourceMetadataFactory, $propertyAccessor);
    $hydraCollectionFiltersNormalizer = new CollectionFiltersNormalizer($hydraPartialCollectionNormalizer, $resourceMetadataFactory, $resourceClassResolver, $filterLocator);
    $hydraErrorNormalizer = new HydraErrorNormalizer($apiUrlGenerator, $debug, $defaultContext);
    $hydraEntrypointNormalizer = new HydraEntrypointNormalizer($resourceMetadataFactory, $iriConverter, $apiUrlGenerator);
    $hydraDocumentationNormalizer = new HydraDocumentationNormalizer($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolver, $apiUrlGenerator, $nameConverter);
    $hydraConstraintViolationNormalizer = new HydraConstraintViolationListNormalizer($apiUrlGenerator, [], $nameConverter);
    
    $problemErrorNormalizer = new ErrorNormalizer($debug, $defaultContext);
    
    // $expressionLanguage = new ExpressionLanguage();
    // $resourceAccessChecker = new ResourceAccessChecker(
    //      $expressionLanguage,
    // );
    
    $itemNormalizer = new ItemNormalizer(
        $propertyNameCollectionFactory,
        $propertyMetadataFactory,
        $iriConverter,
        $resourceClassResolver,
        $propertyAccessor,
        $nameConverter,
        $classMetadataFactory,
        $logger,
        $resourceMetadataFactory,
        /**$resourceAccessChecker **/ null,
        $defaultContext
    );
    
    $arrayDenormalizer = new ArrayDenormalizer();
    $problemNormalizer = new ProblemNormalizer($debug, $defaultContext);
    $jsonserializableNormalizer = new JsonSerializableNormalizer($classMetadataFactory, $nameConverter, $defaultContext);
    $dateTimeNormalizer = new DateTimeNormalizer($defaultContext);
    $dataUriNormalizer = new DataUriNormalizer();
    $dateIntervalNormalizer = new DateIntervalNormalizer($defaultContext);
    $dateTimeZoneNormalizer = new DateTimeZoneNormalizer();
    $constraintViolationListNormalizer = new ConstraintViolationListNormalizer($defaultContext, $nameConverter);
    $unwrappingDenormalizer = new UnwrappingDenormalizer($propertyAccessor);
    
    $halItemNormalizer = new HalItemNormalizer($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, /** resourceAccessChecker **/ null);
    $halItemNormalizer = new HalItemNormalizer($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, /** resourceAccessChecker **/ null);
    
    $halEntrypointNormalizer = new HalEntrypointNormalizer($resourceMetadataFactory, $iriConverter, $apiUrlGenerator);
    $halCollectionNormalizer = new HalCollectionNormalizer($resourceClassResolver, $configuration['collection']['pagination']['page_parameter_name'], $resourceMetadataFactory);
    $halObjectNormalizer = new HalObjectNormalizer($objectNormalizer, $iriConverter);
    
    $openApiNormalizer = new OpenApiNormalizer($objectNormalizer);
    
    $list = new \SplPriorityQueue();
    $list->insert($unwrappingDenormalizer, 1000);
    $list->insert($halItemNormalizer, -890);
    $list->insert($hydraConstraintViolationNormalizer, -780);
    $list->insert($hydraEntrypointNormalizer, -800);
    $list->insert($hydraErrorNormalizer, -800);
    $list->insert($hydraCollectionFiltersNormalizer, -800);
    $list->insert($halEntrypointNormalizer, -800);
    $list->insert($halCollectionNormalizer, -985);
    $list->insert($halObjectNormalizer, -995);
    $list->insert($jsonLdItemNormalizer, -890);
    $list->insert($problemConstraintViolationListNormalizer, -780);
    $list->insert($problemErrorNormalizer, -810);
    $list->insert($jsonLdObjectNormalizer, -995);
    $list->insert($constraintViolationListNormalizer, -915);
    $list->insert($arrayDenormalizer, -990);
    $list->insert($dateTimeZoneNormalizer, -915);
    $list->insert($dateIntervalNormalizer, -915);
    $list->insert($dataUriNormalizer, -920);
    $list->insert($dateTimeNormalizer, -910);
    $list->insert($jsonserializableNormalizer, -900);
    $list->insert($problemNormalizer, -890);
    $list->insert($objectNormalizer, -1000);
    $list->insert($itemNormalizer, -895);
    // $list->insert($uuidDenormalizer, -895); //Todo ramsey uuid support ?
    $list->insert($openApiNormalizer, -780);
    
    // TODO: JSON-API support
    /**
     * api_platform.jsonapi.normalizer.error                       -790       ApiPlatform\JsonApi\Serializer\ErrorNormalizer
     * api_platform.jsonapi.normalizer.constraint_violation_list   -780       ApiPlatform\JsonApi\Serializer\ConstraintViolationListNormalizer
     * api_platform.openapi.normalizer.api_gateway                 -780       ApiPlatform\Swagger\Serializer\ApiGatewayNormalizer
     * api_platform.jsonapi.normalizer.entrypoint                  -800       ApiPlatform\JsonApi\Serializer\EntrypointNormalizer
     * api_platform.jsonapi.normalizer.collection                  -985       ApiPlatform\JsonApi\Serializer\CollectionNormalizer
     * api_platform.jsonapi.normalizer.item                        -890       ApiPlatform\JsonApi\Serializer\ItemNormalizer
     * api_platform.jsonapi.normalizer.object                      -995       ApiPlatform\JsonApi\Serializer\ObjectNormalizer
     */
    
    $encoders = [new JsonEncoder(), $jsonLdEncoder];
    $serializer = new Serializer(iterator_to_array($list), $encoders);
    
    $serializeListener = new SerializeListener($serializer, $serializerContextBuilder, $resourceMetadataFactory);
    $respondListener = new RespondListener($resourceMetadataFactory);
    $formatListener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory, $formats);
    $readListener = new ReadListener($stateProviders, $resourceMetadataFactory, $serializerContextBuilder);
    $deserializeListener = new DeserializeListener($serializer, $serializerContextBuilder, $resourceMetadataFactory);
    $addLinkHeaderListener = new AddLinkHeaderListener($apiUrlGenerator);
    $validationExceptionListener = new ValidationExceptionListener($serializer, $errorFormats, $exceptionToStatus);
    
    $controller = new ExceptionAction($serializer, $errorFormats, $exceptionToStatus);
    $errorListener = new ErrorListener($controller);
    $exceptionListener = new ExceptionListener($errorListener);
    
    $dispatcher = new EventDispatcher();
    $dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
    $dispatcher->addListener('kernel.view', [$validateListener, 'onKernelView'], 64);
    $dispatcher->addListener('kernel.view', [$writeListener, 'onKernelView'], 32);
    $dispatcher->addListener('kernel.view', [$serializeListener, 'onKernelView'], 16);
    // TODO: ApiPlatform\EventListener\QueryParameterValidateListener, prio 16
    $dispatcher->addListener('kernel.view', [$respondListener, 'onKernelView'], 8);
    $dispatcher->addListener('kernel.request', [$formatListener, 'onKernelRequest'], 28);
    $dispatcher->addListener('kernel.request', [$readListener, 'onKernelRequest'], 4);
    $dispatcher->addListener('kernel.request', [$deserializeListener, 'onKernelRequest'], 2);
    $dispatcher->addListener('kernel.exception', [$validationExceptionListener, 'onKernelException'], 2);
    // $dispatcher->addListener('kernel.exception', [$exceptionListener, 'onKernelException'], -96);
    $dispatcher->addListener('kernel.response', [$addLinkHeaderListener, 'onKernelResponse'], 2);
    
    /*
     * TODO:
     * api_platform.security.listener.request.deny_access     kernel.request      onSecurity                  3          ApiPlatform\Security\EventListener\DenyAccessListener
     *   "                                                    kernel.request      onSecurityPostDenormalize   1
     * api_platform.swagger.listener.ui                       kernel.request      onKernelRequest                        ApiPlatform\Bridge\Symfony\Bundle\EventListener\SwaggerUiListener
     * api_platform.http_cache.listener.response.configure    kernel.response     onKernelResponse            -1         ApiPlatform\HttpCache\EventListener\AddHeadersListener
    */
    
    final class DocumentationAction
    {
        private $openApiFactory;
        public function __construct(OpenApiFactoryInterface $openApiFactory)
        {
            $this->openApiFactory = $openApiFactory;
        }
    
        public function __invoke(Request $request): DocumentationInterface
        {
            $context = ['base_url' => $request->getBaseUrl(), 'spec_version' => 3];
            if ($request->query->getBoolean('api_gateway')) {
                $context['api_gateway'] = true;
            }
    
            return $this->openApiFactory->__invoke($context);
        }
    }
    
    $paginationOptions = new PaginationOptions();
    $openApiOptions = new OpenApiOptions('API Platform');
    $jsonSchemaTypeFactory = new TypeFactory($resourceClassResolver);
    $jsonSchemaFactory = new SchemaFactory($jsonSchemaTypeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter, $resourceClassResolver);
    
    $openApiFactory = new OpenApiFactory($resourceNameCollectionFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $jsonSchemaFactory, $jsonSchemaTypeFactory, $filterLocator);
    $documentationAction = new DocumentationAction($openApiFactory);
    $routes->add('api_doc', new Route('/docs.{_format}', ['_controller' => $documentationAction, '_format' => null, '_api_respond' => true]));
    
    $entryPointAction = new EntrypointAction($resourceNameCollectionFactory);
    $routes->add('api_entrypoint', new Route('/{index}.{_format}', ['_controller' => $entryPointAction, '_format' => null, '_api_respond' => true, 'index' => 'index'], ['index' => 'index']));
    
    $contextAction = new ContextAction($jsonLdContextBuilder, $resourceNameCollectionFactory, $resourceMetadataFactory);
    $routes->add('api_jsonld_context', new Route('/contexts/{shortName}.{_format}', ['_controller' => $contextAction, '_format' => 'jsonld', '_api_respond' => true], ['shortName' => '.+']));
    
    $notExposedAction = new NotExposedAction();
    $routes->add('api_genid', new Route('/.well-known/genid/{id}', ['_controller' => $notExposedAction, '_format' => 'text', '_api_respond' => true]));
    
    $controllerResolver = new ControllerResolver();
    $argumentResolver = new ArgumentResolver();
    
    $kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
    
    $request = Request::createFromGlobals();
    $response = $kernel->handle($request);
    $response->send();
    $kernel->terminate($request, $response);
  4. vinceAmstoutz commented on Jan 17, 2025

    @vinceAmstoutz
    Member

    @tsaquet Tanks for the details. I won't be able to get into this in the next few weeks, but in the meantime, I invite you to look at the git diffs between versions 3 and 4 of API Platform and the upgrade guide to identify new symbols.

  5. vinceAmstoutz commented on Jan 17, 2025

    @vinceAmstoutz
    Member

    If you're in a rush, don't hesitate to ask Les-Tilleuls.coop for an estimate, who is the company that created and maintain API Platform.

  6. tsaquet commented on Jan 17, 2025

    @tsaquet
    Author

    We're in a bit of a hurry, but this is a volunteer open source project with no funds, so we're in no position to ask for an estimate :)

    I will either make it work or postpone this part of the code refactoring :)

  7. soyuka commented on Feb 12, 2025

    @soyuka
    Member

    It's up to date in the ApiPlatformLaravelProvider :D

  8. dunglas commented on Feb 12, 2025

    @dunglas
    Member

    If it's too hard to maintain, maybe should we just link the Laravel provider?

    Better no docs than outdated docs 😅

  9. removed their assignment
    on Aug 8, 2025
  10. reopened this on Dec 5, 2025
  11. soyuka commented on Dec 5, 2025

    @soyuka
    Member

    I've added a note to the docs and alink to the provider, lets keep that open if someone wants it needs quite an update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions