Skip to content

Instantly share code, notes, and snippets.

@soyuka
Created December 14, 2020 08:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save soyuka/38863de8374eabb9bf0a489ca10a797b to your computer and use it in GitHub Desktop.
Save soyuka/38863de8374eabb9bf0a489ca10a797b to your computer and use it in GitHub Desktop.
API Platform bootstrap
<?php
require './vendor/autoload.php';
use ApiPlatform\Core\Action\EntrypointAction;
use ApiPlatform\Core\Action\ExceptionAction;
use ApiPlatform\Core\Action\PlaceholderAction;
use ApiPlatform\Core\Api\IdentifiersExtractor;
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
use ApiPlatform\Core\Api\OperationType;
use ApiPlatform\Core\Api\ResourceClassResolver;
use ApiPlatform\Core\Api\UrlGeneratorInterface as ApiUrlGeneratorInterface;
use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator;
use ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener;
use ApiPlatform\Core\Documentation\DocumentationInterface;
use ApiPlatform\Core\EventListener\AddFormatListener;
use ApiPlatform\Core\EventListener\DeserializeListener;
use ApiPlatform\Core\EventListener\ExceptionListener;
use ApiPlatform\Core\EventListener\ReadListener;
use ApiPlatform\Core\EventListener\WriteListener;
use ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyMetadataFactory;
use ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyNameCollectionFactory;
use ApiPlatform\Core\Bridge\Symfony\Routing\IriConverter;
use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\PaginationOptions;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use ApiPlatform\Core\EventListener\RespondListener;
use ApiPlatform\Core\EventListener\SerializeListener;
use ApiPlatform\Core\Hal\Serializer\CollectionNormalizer as HalCollectionNormalizer;
use ApiPlatform\Core\Hal\Serializer\EntrypointNormalizer as HalEntrypointNormalizer;
use ApiPlatform\Core\Hal\Serializer\ItemNormalizer as HalItemNormalizer;
use ApiPlatform\Core\Hal\Serializer\ObjectNormalizer as HalObjectNormalizer;
use ApiPlatform\Core\Hydra\EventListener\AddLinkHeaderListener;
use ApiPlatform\Core\Hydra\Serializer\CollectionFiltersNormalizer;
use ApiPlatform\Core\Hydra\Serializer\CollectionNormalizer as HydraCollectionNormalizer;
use ApiPlatform\Core\Hydra\Serializer\ConstraintViolationListNormalizer as HydraConstraintViolationListNormalizer;
use ApiPlatform\Core\Hydra\Serializer\DocumentationNormalizer as HydraDocumentationNormalizer;
use ApiPlatform\Core\Hydra\Serializer\EntrypointNormalizer as HydraEntrypointNormalizer;
use ApiPlatform\Core\Hydra\Serializer\ErrorNormalizer as HydraErrorNormalizer;
use ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer;
use ApiPlatform\Core\Identifier\IdentifierConverter;
use ApiPlatform\Core\Identifier\Normalizer\IntegerDenormalizer;
use ApiPlatform\Core\JsonLd\Action\ContextAction;
use ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer as JsonLdItemNormalizer;
use ApiPlatform\Core\JsonLd\Serializer\ObjectNormalizer as JsonLdObjectNormalizer;
use ApiPlatform\Core\JsonLd\ContextBuilder as JsonLdContextBuilder;
use ApiPlatform\Core\JsonSchema\SchemaFactory;
use ApiPlatform\Core\JsonSchema\TypeFactory;
use ApiPlatform\Core\Metadata\Property\Factory\AnnotationPropertyMetadataFactory;
use ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyMetadataFactory;
use ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyNameCollectionFactory;
use ApiPlatform\Core\Metadata\Property\Factory\SerializerPropertyMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceFilterMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceNameCollectionFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\FormatsResourceMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\InputOutputResourceMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\OperationResourceMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ShortNameResourceMetadataFactory;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactory;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\Options as OpenApiOptions;
use ApiPlatform\Core\OpenApi\Serializer\OpenApiNormalizer;
use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory;
use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator;
use ApiPlatform\Core\PathResolver\OperationPathResolver;
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
use ApiPlatform\Core\Problem\Serializer\ConstraintViolationListNormalizer as ProblemConstraintViolationListNormalizer;
use ApiPlatform\Core\Problem\Serializer\ErrorNormalizer;
use ApiPlatform\Core\Serializer\ItemNormalizer;
use ApiPlatform\Core\Serializer\JsonEncoder as JsonLdEncoder;
use ApiPlatform\Core\Serializer\SerializerContextBuilder;
use ApiPlatform\Core\Validator\EventListener\ValidateListener;
use ApiPlatform\Core\Validator\ValidatorInterface;
use Doctrine\Common\Annotations\AnnotationReader;
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\HttpFoundation\Response;
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\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
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;
$allowPlainIdentifiers = false;
$debug = true;
$defaultContext = [];
$dataTransformers = [];
$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\Core\Exception\InvalidArgumentException::class => 400,
\ApiPlatform\Core\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]
);
$doctrineAnnotationReader = new AnnotationReader();
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader($doctrineAnnotationReader));
final class FilterLocator implements ContainerInterface
{
private $filters = [];
public function get($id) {
return $this->filters[$id] ?? null;
}
public function has($id) {
return isset($this->filter[$id]);
}
}
$apiPlatformAnnotationReader = $doctrineAnnotationReader;
if (\PHP_VERSION_ID >= 80000) {
$apiPlatformAnnotationReader = null;
}
$filterLocator = new FilterLocator();
$resourceNameCollectionFactory = new AnnotationResourceNameCollectionFactory($apiPlatformAnnotationReader, ['./src/Entity']);
$resourceMetadataFactory = new FormatsResourceMetadataFactory(new OperationResourceMetadataFactory(new ShortNameResourceMetadataFactory(new InputOutputResourceMetadataFactory(new AnnotationResourceFilterMetadataFactory($apiPlatformAnnotationReader, new AnnotationResourceMetadataFactory($apiPlatformAnnotationReader, null)))), $patchFormats), $formats, $patchFormats);;
$propertyNameCollectionFactory = new InheritedPropertyNameCollectionFactory($resourceNameCollectionFactory, new PropertyInfoPropertyNameCollectionFactory($propertyInfo));
$resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactory);
$propertyMetadataFactory = new SerializerPropertyMetadataFactory($resourceMetadataFactory, $classMetadataFactory, new InheritedPropertyMetadataFactory($resourceNameCollectionFactory, new PropertyInfoPropertyMetadataFactory($propertyInfo, new AnnotationPropertyMetadataFactory($apiPlatformAnnotationReader))), $resourceClassResolver);
class Validator implements ValidatorInterface {
private $validator;
public function __construct($validator)
{
$this->validator = $validator;
}
public function validate($data, array $context = []) {
return $this->validator->validate($data, $context);
}
}
$validator = new Validator(Validation::createValidator());
$validateListener = new ValidateListener($validator, $resourceMetadataFactory);
class DataProvider implements DenormalizedIdentifiersAwareItemDataProviderInterface, RestrictedDataProviderInterface, ContextAwareCollectionDataProviderInterface
{
public function getCollection(string $resourceClass, string $operationName = null, array $context = [])
{
$book = new Book();
$book->id = '1';
return [$book];
}
public function getItem(string $resourceClass, $identifiers, string $operationName = null, array $context = []) {
$book = new Book();
$book->id = $identifiers['id'];
return $book;
}
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool {
return true;
}
}
$dataProvider = new DataProvider();
class DataPersister implements DataPersisterInterface {
public function supports($data): bool
{
return true;
}
public function persist($data) {}
public function remove($data) {}
}
$dataPersister = new DataPersister();
$propertyAccessor = PropertyAccess::createPropertyAccessor();
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, $propertyAccessor);
$pathSegmentNameGenerator = new UnderscorePathSegmentNameGenerator();
$operationPathResolver = new OperationPathResolver($pathSegmentNameGenerator);
$subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $pathSegmentNameGenerator);
class ApiLoader {
private $resourceNameCollectionFactory;
private $resourceMetadataFactory;
private $identifiersExtractor;
private $operationPathResolver;
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, IdentifiersExtractorInterface $identifiersExtractor, OperationPathResolverInterface $operationPathResolver)
{
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->identifiersExtractor = $identifiersExtractor;
$this->operationPathResolver = $operationPathResolver;
}
public function load(): RouteCollection
{
$routeCollection = new RouteCollection();
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$resourceShortName = $resourceMetadata->getShortName();
if (null === $resourceShortName) {
throw new InvalidResourceException(sprintf('Resource %s has no short name defined.', $resourceClass));
}
if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) {
foreach ($collectionOperations as $operationName => $operation) {
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::COLLECTION);
}
}
if (null !== $itemOperations = $resourceMetadata->getItemOperations()) {
foreach ($itemOperations as $operationName => $operation) {
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::ITEM);
}
}
}
return $routeCollection;
}
private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, ResourceMetadata $resourceMetadata, string $operationType): void
{
$resourceShortName = $resourceMetadata->getShortName();
if (isset($operation['route_name'])) {
if (!isset($operation['method'])) {
@trigger_error(sprintf('Not setting the "method" attribute is deprecated and will not be supported anymore in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', OperationType::COLLECTION === $operationType ? 'collection' : 'item', $operationName, $resourceClass), E_USER_DEPRECATED);
}
return;
}
if (!isset($operation['method'])) {
throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass));
}
if (null === $controller = $operation['controller'] ?? null) {
$controller = PlaceholderAction::class;
}
$operation['identified_by'] = (array) ($operation['identified_by'] ?? $resourceMetadata->getAttribute('identified_by', $this->identifiersExtractor ? $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass) : ['id']));
$operation['has_composite_identifier'] = \count($operation['identified_by']) > 1 ? $resourceMetadata->getAttribute('composite_identifier', true) : false;
$path = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/');
$path .= $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName);
$route = new Route(
$path,
[
'_controller' => $controller,
'_format' => null,
'_stateless' => $operation['stateless'],
'_api_resource_class' => $resourceClass,
'_api_identified_by' => $operation['identified_by'],
'_api_has_composite_identifier' => $operation['has_composite_identifier'],
sprintf('_api_%s_operation_name', $operationType) => $operationName,
] + ($operation['defaults'] ?? []),
$operation['requirements'] ?? [],
$operation['options'] ?? [],
$operation['host'] ?? '',
$operation['schemes'] ?? [],
[$operation['method']],
$operation['condition'] ?? ''
);
$routeCollection->add(RouteNameGenerator::generate($operationName, $resourceShortName, $operationType), $route);
}
}
$apiLoader = new ApiLoader($resourceNameCollectionFactory, $resourceMetadataFactory, $identifiersExtractor, $operationPathResolver);
$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) {
return $this->matcher->match($pathinfo);
}
public function setContext(RequestContext $context) {
$this->context = $context;
}
public function getContext() {
return $this->context;
}
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) {
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) {
return $this->generator->generate($name, $parameters, $referenceType ?: self::ABS_PATH);
}
}
$apiUrlGenerator = new ApiUrlGenerator($generator);
$router = new Router($routes, $matcher, $generator, $requestContext);
$routeNameResolver = new RouteNameResolver($router);
$identifierDenormalizers = [new IntegerDenormalizer()];
$identifierConverter = new IdentifierConverter($identifiersExtractor, $propertyMetadataFactory, $identifierDenormalizers, $resourceMetadataFactory);
$iriConverter = new IriConverter($propertyNameCollectionFactory, $propertyMetadataFactory, $dataProvider, $routeNameResolver, $router, $propertyAccessor, $identifiersExtractor, /** SubresourceDataProviderInterface */ null, $identifierConverter, $resourceClassResolver, $resourceMetadataFactory);
$writeListener = new WriteListener($dataPersister, $iriConverter, $resourceMetadataFactory, $resourceClassResolver);
$serializerContextBuilder = new SerializerContextBuilder($resourceMetadataFactory);
$objectNormalizer = new ObjectNormalizer();
$nameConverter = new MetadataAwareNameConverter($classMetadataFactory);
$jsonLdContextBuilder = new JsonLdContextBuilder($resourceNameCollectionFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $apiUrlGenerator, $nameConverter);
$jsonLdItemNormalizer = new JsonLdItemNormalizer($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $jsonLdContextBuilder, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $dataTransformers, /** 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, $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, null, $apiUrlGenerator, /* SubresourceOperationFactoryInterface */ null, $nameConverter);
$hydraConstraintViolationNormalizer = new HydraConstraintViolationListNormalizer($apiUrlGenerator, [], $nameConverter);
$problemErrorNormalizer = new ErrorNormalizer($debug, $defaultContext);
$itemNormalizer = new ItemNormalizer($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $dataProvider, $allowPlainIdentifiers, $logger, $dataTransformers, $resourceMetadataFactory, /** resourceAccessChecker **/ null);
$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, $dataProvider, $allowPlainIdentifiers, $defaultContext, $dataTransformers, $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\Core\JsonApi\Serializer\ErrorNormalizer
* api_platform.jsonapi.normalizer.constraint_violation_list -780 ApiPlatform\Core\JsonApi\Serializer\ConstraintViolationListNormalizer
* api_platform.openapi.normalizer.api_gateway -780 ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer
* api_platform.jsonapi.normalizer.entrypoint -800 ApiPlatform\Core\JsonApi\Serializer\EntrypointNormalizer
* api_platform.jsonapi.normalizer.collection -985 ApiPlatform\Core\JsonApi\Serializer\CollectionNormalizer
* api_platform.jsonapi.normalizer.item -890 ApiPlatform\Core\JsonApi\Serializer\ItemNormalizer
* api_platform.jsonapi.normalizer.object -995 ApiPlatform\Core\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($dataProvider, $dataProvider, /** SubresourceDataProvider **/ null, $serializerContextBuilder, $identifierConverter, $resourceMetadataFactory);
$deserializeListener = new DeserializeListener($serializer, $serializerContextBuilder, $formats, $resourceMetadataFactory);
$addLinkHeaderListener = new AddLinkHeaderListener($apiUrlGenerator);
$validationExceptionListener = new ValidationExceptionListener($serializer, $errorFormats, $exceptionToStatus);
$controller = new ExceptionAction($serializer, $errorFormats, $exceptionToStatus);
$errorListener = new ErrorListener($controller);
$exceptionListener = new ExceptionListener($controller, null, $debug, $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\Core\EventListener\QueryParameterValidateListener, prio 16
$dispatcher->addListener('kernel.view', [$respondListener, 'onKernelView'], 8);
$dispatcher->addListener('kernel.request', [$formatListener, 'onKernelRequest'], 7);
$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\Core\Security\EventListener\DenyAccessListener
* " kernel.request onSecurityPostDenormalize 1
* api_platform.swagger.listener.ui kernel.request onKernelRequest ApiPlatform\Core\Bridge\Symfony\Bundle\EventListener\SwaggerUiListener
* api_platform.http_cache.listener.response.configure kernel.response onKernelResponse -1 ApiPlatform\Core\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, $operationPathResolver, $filterLocator, $subresourceOperationFactory, $identifiersExtractor, $formats, $openApiOptions, $paginationOptions);
$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' => '.+']));
$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);
@rastapopougros
Copy link

Hello @soyuka, j'ai quelques questions pour comprendre comment utiliser cette merveilleuse api-platform depuis n'importe quel autre logiciel (php), autre qu'une app symfony complète. Pour l'instant on navigue un peu à l'aveugle en tentant de lancer ça tel quel, ou en tentant de modifier un peu au hasard des lignes dedans… :p

  • Est-ce fait exprès que ça alterne variable racine, puis définition de classe, puis re variables ou appel de méthode, puis re définition de classe, etc ? Vu le non découpage, et sans commentaires, il n'y a pas de "blocs logiques" qui se détachent particulièrement, donc c'est difficile de comprendre la logique, et savoir s'il y a des choses à personnaliser ou si c'est à lancer tel quel et les définitions d'entitées/personnalisations/provider etc devant se définir ailleurs (dans d'autres fichiers, d'autres classes).

  • Comment (ou "peut-on bien") appeler proprement api-plateform depuis non pas un fichier racine du genre "index.php" comme ici, mais depuis l'intérieur d'une fonction/méthode ?
    En effet, si on part d'une appli/site existant, géré par un CMS ou autre framework, alors les URL sont déjà gérées par le système en place, et on veut donc forcément lancer api-platform seulement depuis un endroit précis, qui est toujours géré par le système en place donc depuis une fonction, un controleur etc. Ainsi ce n'est pas l'URL complète qu'il faut prendre en compte mais seulement le morceau pertinent à donner à manger au routeur ici utilisé.
    Exemple un peu plus concret :

    • monsite.tld/entrypoint.api/ est déjà gérée par une fonction de mon CMS moncontroleur_entrypoint()
    • c'est depuis l'intérieur de cette fonction qu'on voudra appeler api-platform en lui fournissant seulement ce qui dépasse de cette base !
    • monsite.tld/entrypoint.api/truc/bidule?param=val => c'est seulement /truc/bidule?param=val qu'on veut donner à api-platform, tout ce qui précède est du ressort du système en place.
      Est-ce que c'est dans ce code en bazar qu'il y a des choses à modifier/compléter directement pour dire quelle est la base etc ?
      Pour l'instant sans résultat, actuellement ça me fait toujours revenir à la racine monsite.tld, en virant le morceau entrypoint.api et tout ce qui suit s'il y a (avec pourtant aucune redirection, mais un retour 204 No Content).
  • De même pour DataProvider et DataPersister : à partir du moment où on intègre dans un site existant, on va utiliser le contenu de ce site, et non pas Doctrine ORM. Dans ce contexte là, où est-ce qu'on doit mettre nos propres implémentations ? Ailleurs ou bien c'est là dans ce code bazar qu'il y a des choses à personnaliser ?

  • De même pour les entitées à définir (Product, Offer etc dans les exemples de la doc), ces fichiers de classes doivent être à un endroit précis ? Ou il y a un truc à modifier au milieu de ce code pour dire où c'est ?

@rastapopougros
Copy link

Aussi il y a des fatal errors car des classes implémentées ne correspondent pas pile à l'interface, par ex FilterLocator a une méthode has dont l'arg doit être une string et retourner un bool, et si pas déclaré pareil, paf fatal.

@rastapopougros
Copy link

This code needs composer require symfony/validator to work, too

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