<?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 ApiPlatform\Core\Api;
use ApiPlatform\Api\IdentifiersExtractor as NewIdentifiersExtractor;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Util\ResourceClassInfoTrait;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* {@inheritdoc}
*
* @author Antoine Bluchet <soyuka@gmail.com>
*/
final class IdentifiersExtractor implements IdentifiersExtractorInterface
{
use ResourceClassInfoTrait;
private $propertyNameCollectionFactory;
private $propertyMetadataFactory;
private $propertyAccessor;
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null, bool $metadataBackwardCompatibilityLayer = null)
{
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
$this->propertyMetadataFactory = $propertyMetadataFactory;
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
$this->resourceClassResolver = $resourceClassResolver;
if (null === $this->resourceClassResolver) {
@trigger_error(sprintf('Not injecting %s in the IdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), \E_USER_DEPRECATED);
}
if ($metadataBackwardCompatibilityLayer) {
trigger_deprecation('api-platform/core', '2.7', sprintf('The service "%s" is deprecated, use %s instead.', self::class, NewIdentifiersExtractor::class));
}
}
/**
* {@inheritdoc}
*/
public function getIdentifiersFromResourceClass(string $resourceClass): array
{
$identifiers = [];
foreach ($properties = $this->propertyNameCollectionFactory->create($resourceClass) as $property) {
if ($this->propertyMetadataFactory->create($resourceClass, $property)->isIdentifier() ?? false) {
$identifiers[] = $property;
}
}
if (!$identifiers) {
if (\in_array('id', iterator_to_array($properties), true)) {
return ['id'];
}
throw new RuntimeException(sprintf('No identifier defined in "%s". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource."', $resourceClass));
}
return $identifiers;
}
/**
* {@inheritdoc}
*/
public function getIdentifiersFromItem($item): array
{
$identifiers = [];
$resourceClass = $this->getResourceClass($item, true);
$identifierProperties = $this->getIdentifiersFromResourceClass($resourceClass);
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
if (!\in_array($propertyName, $identifierProperties, true)) {
continue;
}
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
$identifier = $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
if (!\is_object($identifier)) {
continue;
}
if (null === $relatedResourceClass = $this->getResourceClass($identifier)) {
continue;
}
$relatedItem = $identifier;
unset($identifiers[$propertyName]);
foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
$propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
if ($propertyMetadata->isIdentifier()) {
if (isset($identifiers[$propertyName])) {
throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
}
$identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
}
}
if (!isset($identifiers[$propertyName])) {
throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
}
}
return $identifiers;
}
}