# Bootstraping the core library You may want to run a minimal version of API Platform. This one file runs API Platform (without graphql, doctrine and mongodb). It requires the following composer packages: ```console composer require \ api-platform/core \ phpdocumentor/reflection-docblock \ symfony/property-info \ symfony/routing \ symfony/validator ``` The minimal version of API Platform: ```php ['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\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 AnnotationLoader()); 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 Validator(Validation::createValidator()); $validateListener = new ValidateListener($validator, $resourceMetadataFactory); 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() { return $this->routes; } public function match(string $pathinfo): array { return $this->matcher->match($pathinfo); } public function setContext(RequestContext $context) { $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 ApiUrlGeneratorInterface { 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); ```