Skip to content

Instantly share code, notes, and snippets.

@soyuka
Created February 18, 2021 13:36
Show Gist options
  • Select an option

  • Save soyuka/4db97819e6bc3395648ba1e5b30ffd06 to your computer and use it in GitHub Desktop.

Select an option

Save soyuka/4db97819e6bc3395648ba1e5b30ffd06 to your computer and use it in GitHub Desktop.
API Platform use the Model namespace for the API and the Entity namespace for doctrine

Use the Model namespace for:

  • API-related attributes: #[ApiResource], #[ApiProperty]
  • Serialization groups
  • Validation assertions

Use the Entity namespace for Doctrine-related annotations.

By default Model inherits the Entity properties as far as the metadata is concern.

<?php
declare(strict_types=1);
namespace App\Metadata;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
final class PropertyMetadataFactory implements PropertyMetadataFactoryInterface
{
private PropertyMetadataFactoryInterface $decorated;
public function __construct(PropertyMetadataFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
{
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
$attributes = $propertyMetadata->getAttributes() ?? [];
$apiMetadataClassName = str_replace('Entity', 'Model', $resourceClass);
if (class_exists($apiMetadataClassName)) {
$modelMetadata = $this->decorated->create($apiMetadataClassName, $property, $options);
$attributes = array_merge($modelMetadata->getAttributes() ?? [], $attributes);
}
return $propertyMetadata->withAttributes($attributes);
}
}
<?php
declare(strict_types=1);
namespace App\Metadata;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
final class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
{
private PropertyNameCollectionFactoryInterface $decorated;
public function __construct(PropertyNameCollectionFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
public function create(string $resourceClass, array $options = []): PropertyNameCollection
{
return $this->decorated->create($resourceClass, $options);
}
}
<?php
namespace App\Metadata;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
final class ResourceMetadataFactory implements ResourceMetadataFactoryInterface
{
private ResourceMetadataFactoryInterface $decorated;
public function __construct(ResourceMetadataFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
*/
public function create(string $resourceClass): ResourceMetadata
{
$apiMetadataClassName = str_replace('Entity', 'Model', $resourceClass);
if (class_exists($apiMetadataClassName)) {
return $this->decorated->create($apiMetadataClassName);
}
return $this->decorated->create($resourceClass);
}
}
<?php
/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Metadata;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
final class ResourceNameCollectionFactory implements ResourceNameCollectionFactoryInterface
{
private ResourceNameCollectionFactoryInterface $decorated;
public function __construct(ResourceNameCollectionFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException
*/
public function create(): ResourceNameCollection
{
$classes = [];
foreach ($this->decorated->create() as $resourceClass) {
$key = str_replace('Model', 'Entity', $resourceClass);
$classes[$key] = true;
}
return new ResourceNameCollection(array_keys($classes));
}
}
<?php
namespace App\Metadata;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
final class SerializerClassMetadataFactory implements ClassMetadataFactoryInterface
{
private ClassMetadataFactoryInterface $decorated;
public function __construct(ClassMetadataFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
public function getMetadataFor($value)
{
/** @var ClassMetadata * */
$entityMetadata = $this->decorated->getMetadataFor($value);
$apiMetadataClassName = str_replace('Entity', 'Model', is_string($value) ? $value : get_class($value));
if (class_exists($apiMetadataClassName)) {
/** @var ClassMetadata * */
$apiMetadata = $this->decorated->getMetadataFor($apiMetadataClassName);
foreach ($apiMetadata->attributesMetadata as $property => $metadata) {
$entityMetadata->attributesMetadata[$property] = $metadata;
}
}
return $entityMetadata;
}
public function hasMetadataFor($value)
{
return $this->decorated->hasMetadataFor($value);
}
}
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
App\Metadata\ResourceMetadataFactory:
decorates: 'api_platform.metadata.resource.metadata_factory'
arguments:
$decorated: '@App\Metadata\ResourceMetadataFactory.inner'
App\Metadata\ResourceNameCollectionFactory:
decorates: 'api_platform.metadata.resource.name_collection_factory.cached'
arguments:
$decorated: '@App\Metadata\ResourceNameCollectionFactory.inner'
App\Metadata\PropertyNameCollectionFactory:
decorates: 'api_platform.metadata.property.name_collection_factory'
arguments:
$decorated: '@App\Metadata\PropertyNameCollectionFactory.inner'
App\Metadata\PropertyMetadataFactory:
decorates: 'api_platform.metadata.property.metadata_factory.cached'
arguments:
$decorated: '@App\Metadata\PropertyMetadataFactory.inner'
App\Metadata\SerializerClassMetadataFactory:
decorates: 'serializer.mapping.class_metadata_factory'
arguments:
$decorated: '@App\Metadata\SerializerClassMetadataFactory.inner'
App\Metadata\ValidatorClassMetadataFactory:
arguments:
$mappingCache: '@validator.mapping.cache.adapter'
<?php
namespace App\Metadata;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Cache\ArrayCache;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
final class ValidatorClassMetadataFactory implements MetadataFactoryInterface
{
private MetadataFactoryInterface $decorated;
public function __construct(CacheItemPoolInterface $mappingCache)
{
$annotationReader = new CachedReader(new AnnotationReader(), new ArrayCache());
$loader = new AnnotationLoader($annotationReader);
$this->decorated = new LazyLoadingMetadataFactory($loader, $mappingCache);
}
public function getMetadataFor($value)
{
/** @var ClassMetadata * */
$entityMetadata = $this->decorated->getMetadataFor($value);
$apiMetadataClassName = str_replace('Entity', 'Model', is_string($value) ? $value : get_class($value));
if (class_exists($apiMetadataClassName)) {
/** @var ClassMetadata * */
$apiMetadata = $this->decorated->getMetadataFor($apiMetadataClassName);
$entityMetadata->members = $apiMetadata->members;
$entityMetadata->properties = $apiMetadata->properties;
}
return $entityMetadata;
}
public function hasMetadataFor($value)
{
return $this->decorated->hasMetadataFor($value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment