• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 16050929464

03 Jul 2025 12:51PM UTC coverage: 22.065% (+0.2%) from 21.821%
16050929464

push

github

soyuka
chore: todo improvement

11516 of 52192 relevant lines covered (22.06%)

22.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.5
/src/Metadata/Resource/Factory/LinkFactory.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\Exception\RuntimeException;
17
use ApiPlatform\Metadata\Link;
18
use ApiPlatform\Metadata\Metadata;
19
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
21
use ApiPlatform\Metadata\ResourceClassResolverInterface;
22
use ApiPlatform\Metadata\Util\TypeHelper;
23
use Symfony\Component\TypeInfo\Type;
24

25
/**
26
 * @internal
27
 */
28
final class LinkFactory implements LinkFactoryInterface, PropertyLinkFactoryInterface
29
{
30
    /**
31
     * @var array<class-string, string[]>
32
     */
33
    private $localIdentifiersPerResourceClassCache = [];
34

35
    public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceClassResolverInterface $resourceClassResolver)
36
    {
37
    }
590✔
38

39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function createLinkFromProperty(Metadata $operation, string $property): Link
43
    {
44
        $metadata = $this->propertyMetadataFactory->create($resourceClass = $operation->getClass(), $property);
×
45
        $relationClass = $this->getPropertyClassType($metadata->getNativeType());
×
46
        if (!$relationClass) {
×
47
            throw new RuntimeException(\sprintf('We could not find a class matching the uriVariable "%s" on "%s".', $property, $resourceClass));
×
48
        }
49

50
        $identifiers = $this->resourceClassResolver->isResourceClass($relationClass) ? $this->getIdentifiersFromResourceClass($relationClass) : ['id'];
×
51

52
        return new Link(fromClass: $relationClass, toProperty: $property, identifiers: $identifiers, parameterName: $property);
×
53
    }
54

55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function createLinksFromIdentifiers(Metadata $operation): array
59
    {
60
        $identifiers = $this->getIdentifiersFromResourceClass($resourceClass = $operation->getClass());
100✔
61

62
        if (!$identifiers) {
100✔
63
            return [];
26✔
64
        }
65

66
        $link = (new Link())->withFromClass($resourceClass)->withIdentifiers($identifiers);
82✔
67
        $parameterName = $identifiers[0];
82✔
68
        if ('value' === $parameterName && enum_exists($resourceClass)) {
82✔
69
            $parameterName = 'id';
2✔
70
        }
71

72
        if (1 < \count($identifiers)) {
82✔
73
            $parameterName = 'id';
2✔
74
            $link = $link->withCompositeIdentifier(true);
2✔
75
        }
76

77
        return [$link->withParameterName($parameterName)];
82✔
78
    }
79

80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function createLinksFromRelations(Metadata $operation): array
84
    {
85
        $links = [];
72✔
86
        foreach ($this->propertyNameCollectionFactory->create($resourceClass = $operation->getClass()) as $property) {
72✔
87
            $metadata = $this->propertyMetadataFactory->create($resourceClass, $property);
70✔
88

89
            if (!($relationClass = $this->getPropertyClassType($metadata->getNativeType())) || !$this->resourceClassResolver->isResourceClass($relationClass)) {
70✔
90
                continue;
70✔
91
            }
92

93
            $identifiers = $this->getIdentifiersFromResourceClass($resourceClass);
24✔
94

95
            $links[] = (new Link())->withFromProperty($property)->withFromClass($resourceClass)->withToClass($relationClass)->withIdentifiers($identifiers);
24✔
96
        }
97

98
        return $links;
72✔
99
    }
100

101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function createLinksFromAttributes(Metadata $operation): array
105
    {
106
        $links = [];
72✔
107
        try {
108
            $reflectionClass = new \ReflectionClass($resourceClass = $operation->getClass());
72✔
109
            foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
72✔
110
                $reflectionProperty = $reflectionClass->getProperty($property);
70✔
111

112
                foreach ($reflectionProperty->getAttributes(Link::class) as $attributeLink) {
70✔
113
                    $metadata = $this->propertyMetadataFactory->create($resourceClass, $property);
2✔
114

115
                    $attributeLink = $attributeLink->newInstance()
2✔
116
                        ->withFromProperty($property);
2✔
117

118
                    if (!$attributeLink->getFromClass()) {
2✔
119
                        $attributeLink = $attributeLink->withFromClass($resourceClass)->withToClass($this->getPropertyClassType($metadata->getNativeType()) ?? $resourceClass);
2✔
120
                    }
121

122
                    $links[] = $attributeLink;
2✔
123
                }
124
            }
125
        } catch (\ReflectionException) {
12✔
126
        }
127

128
        return $links;
72✔
129
    }
130

131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function completeLink(Link $link): Link
135
    {
136
        if (!$link->getIdentifiers()) {
44✔
137
            $link = $link->withIdentifiers($this->getIdentifiersFromResourceClass($link->getFromClass()));
18✔
138
        }
139

140
        if (1 < \count((array) $link->getIdentifiers())) {
44✔
141
            $link = $link->withCompositeIdentifier(true);
×
142
        }
143

144
        return $link;
44✔
145
    }
146

147
    /**
148
     * @param class-string $resourceClass
149
     *
150
     * @return string[]
151
     */
152
    private function getIdentifiersFromResourceClass(string $resourceClass): array
153
    {
154
        if (isset($this->localIdentifiersPerResourceClassCache[$resourceClass])) {
102✔
155
            return $this->localIdentifiersPerResourceClassCache[$resourceClass];
100✔
156
        }
157

158
        $hasIdProperty = false;
102✔
159
        $identifiers = [];
102✔
160
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
102✔
161
            $isIdentifier = $this->propertyMetadataFactory->create($resourceClass, $property)->isIdentifier();
88✔
162

163
            if (!$hasIdProperty && null === $isIdentifier) {
88✔
164
                $hasIdProperty = 'id' === $property;
80✔
165
            }
166

167
            if ($isIdentifier) {
88✔
168
                $identifiers[] = $property;
82✔
169
            }
170
        }
171

172
        if ($hasIdProperty && !$identifiers) {
102✔
173
            return $this->localIdentifiersPerResourceClassCache[$resourceClass] = ['id'];
×
174
        }
175

176
        if (!$hasIdProperty && !$identifiers && enum_exists($resourceClass)) {
102✔
177
            return $this->localIdentifiersPerResourceClassCache[$resourceClass] = ['value'];
4✔
178
        }
179

180
        return $this->localIdentifiersPerResourceClassCache[$resourceClass] = $identifiers;
100✔
181
    }
182

183
    private function getPropertyClassType(?Type $type): ?string
184
    {
185
        if (!$type) {
70✔
186
            return null;
10✔
187
        }
188

189
        if ($collectionValueType = TypeHelper::getCollectionValueType($type)) {
70✔
190
            return $this->getPropertyClassType($collectionValueType);
30✔
191
        }
192

193
        return TypeHelper::getClassName($type);
70✔
194
    }
195
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc