<?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; 
 
/** 
 * @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)); 
        } 
    } 
 
    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; 
    } 
 
    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; 
    } 
}