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

api-platform / core / 10537652610

24 Aug 2024 10:04AM UTC coverage: 7.707%. Remained the same
10537652610

push

github

dunglas
cleanup

12490 of 162060 relevant lines covered (7.71%)

22.98 hits per line

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

94.83
/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.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\Property\Factory;
15

16
use ApiPlatform\Metadata\ApiProperty;
17
use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException;
18
use ApiPlatform\Metadata\ResourceClassResolverInterface;
19
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait;
20
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
21
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface as SerializerClassMetadataFactoryInterface;
22

23
/**
24
 * Populates read/write and link status using serialization groups.
25
 *
26
 * @author Kévin Dunglas <dunglas@gmail.com>
27
 * @author Teoh Han Hui <teohhanhui@gmail.com>
28
 */
29
final class SerializerPropertyMetadataFactory implements PropertyMetadataFactoryInterface
30
{
31
    use ResourceClassInfoTrait;
32

33
    public function __construct(private readonly SerializerClassMetadataFactoryInterface $serializerClassMetadataFactory, private readonly PropertyMetadataFactoryInterface $decorated, ?ResourceClassResolverInterface $resourceClassResolver = null)
34
    {
35
        $this->resourceClassResolver = $resourceClassResolver;
2,248✔
36
    }
37

38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function create(string $resourceClass, string $property, array $options = []): ApiProperty
42
    {
43
        $propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
529✔
44

45
        try {
46
            [$normalizationGroups, $denormalizationGroups] = $this->getEffectiveSerializerGroups($options);
529✔
47

48
            if ($normalizationGroups && !\is_array($normalizationGroups)) {
529✔
49
                $normalizationGroups = [$normalizationGroups];
×
50
            }
51

52
            if ($denormalizationGroups && !\is_array($denormalizationGroups)) {
529✔
53
                $denormalizationGroups = [$denormalizationGroups];
529✔
54
            }
55
        } catch (ResourceClassNotFoundException) {
×
56
            // TODO: for input/output classes, the serializer groups must be read from the actual resource class
57
            return $propertyMetadata;
×
58
        }
59

60
        $propertyMetadata = $this->transformReadWrite($propertyMetadata, $resourceClass, $property, $normalizationGroups, $denormalizationGroups);
529✔
61
        $types = $propertyMetadata->getBuiltinTypes() ?? [];
529✔
62

63
        if (!$this->isResourceClass($resourceClass) && $types) {
529✔
64
            foreach ($types as $builtinType) {
72✔
65
                if ($builtinType->isCollection()) {
72✔
66
                    return $propertyMetadata->withReadableLink(true)->withWritableLink(true);
13✔
67
                }
68
            }
69
        }
70

71
        return $this->transformLinkStatus($propertyMetadata, $normalizationGroups, $denormalizationGroups, $types);
529✔
72
    }
73

74
    /**
75
     * Sets readable/writable based on matching normalization/denormalization groups and property's ignorance.
76
     *
77
     * A false value is never reset as it could be unreadable/unwritable for other reasons.
78
     * If normalization/denormalization groups are not specified and the property is not ignored, the property is implicitly readable/writable.
79
     *
80
     * @param string[]|null $normalizationGroups
81
     * @param string[]|null $denormalizationGroups
82
     */
83
    private function transformReadWrite(ApiProperty $propertyMetadata, string $resourceClass, string $propertyName, ?array $normalizationGroups = null, ?array $denormalizationGroups = null): ApiProperty
84
    {
85
        $serializerAttributeMetadata = $this->getSerializerAttributeMetadata($resourceClass, $propertyName);
529✔
86
        $groups = $serializerAttributeMetadata ? $serializerAttributeMetadata->getGroups() : [];
529✔
87
        $ignored = $serializerAttributeMetadata && $serializerAttributeMetadata->isIgnored();
529✔
88

89
        if (false !== $propertyMetadata->isReadable()) {
529✔
90
            $propertyMetadata = $propertyMetadata->withReadable(!$ignored && (null === $normalizationGroups || array_intersect($normalizationGroups, $groups)));
512✔
91
        }
92

93
        if (false !== $propertyMetadata->isWritable()) {
529✔
94
            $propertyMetadata = $propertyMetadata->withWritable(!$ignored && (null === $denormalizationGroups || array_intersect($denormalizationGroups, $groups)));
486✔
95
        }
96

97
        return $propertyMetadata;
529✔
98
    }
99

100
    /**
101
     * Sets readableLink/writableLink based on matching normalization/denormalization groups.
102
     *
103
     * If normalization/denormalization groups are not specified,
104
     * set link status to false since embedding of resource must be explicitly enabled
105
     *
106
     * @param string[]|null $normalizationGroups
107
     * @param string[]|null $denormalizationGroups
108
     */
109
    private function transformLinkStatus(ApiProperty $propertyMetadata, ?array $normalizationGroups = null, ?array $denormalizationGroups = null, ?array $types = null): ApiProperty
110
    {
111
        // No need to check link status if property is not readable and not writable
112
        if (false === $propertyMetadata->isReadable() && false === $propertyMetadata->isWritable()) {
529✔
113
            return $propertyMetadata;
104✔
114
        }
115

116
        foreach ($types as $type) {
485✔
117
            if (
118
                $type->isCollection()
461✔
119
                && $collectionValueType = $type->getCollectionValueTypes()[0] ?? null
461✔
120
            ) {
121
                $relatedClass = $collectionValueType->getClassName();
116✔
122
            } else {
123
                $relatedClass = $type->getClassName();
461✔
124
            }
125

126
            // if property is not a resource relation, don't set link status (as it would have no meaning)
127
            if (null === $relatedClass || !$this->isResourceClass($relatedClass)) {
461✔
128
                continue;
448✔
129
            }
130

131
            // find the resource class
132
            // this prevents serializer groups on non-resource child class from incorrectly influencing the decision
133
            if (null !== $this->resourceClassResolver) {
225✔
134
                $relatedClass = $this->resourceClassResolver->getResourceClass(null, $relatedClass);
225✔
135
            }
136

137
            $relatedGroups = $this->getClassSerializerGroups($relatedClass);
225✔
138

139
            if (null === $propertyMetadata->isReadableLink()) {
225✔
140
                $propertyMetadata = $propertyMetadata->withReadableLink(null !== $normalizationGroups && !empty(array_intersect($normalizationGroups, $relatedGroups)));
225✔
141
            }
142

143
            if (null === $propertyMetadata->isWritableLink()) {
225✔
144
                $propertyMetadata = $propertyMetadata->withWritableLink(null !== $denormalizationGroups && !empty(array_intersect($denormalizationGroups, $relatedGroups)));
225✔
145
            }
146

147
            return $propertyMetadata;
225✔
148
        }
149

150
        return $propertyMetadata;
472✔
151
    }
152

153
    /**
154
     * Gets the effective serializer groups used in normalization/denormalization.
155
     *
156
     * Groups are extracted in the following order:
157
     *
158
     * - From the "serializer_groups" key of the $options array.
159
     * - From metadata of the given operation ("operation_name" key).
160
     * - From metadata of the current resource.
161
     *
162
     * @return (string[]|string|null)[]
163
     */
164
    private function getEffectiveSerializerGroups(array $options): array
165
    {
166
        if (isset($options['serializer_groups'])) {
529✔
167
            $groups = (array) $options['serializer_groups'];
229✔
168

169
            return [$groups, $groups];
229✔
170
        }
171

172
        if (\array_key_exists('normalization_groups', $options) && \array_key_exists('denormalization_groups', $options)) {
321✔
173
            return [$options['normalization_groups'] ?? null, $options['denormalization_groups'] ?? null];
288✔
174
        }
175

176
        return [null, null];
67✔
177
    }
178

179
    private function getSerializerAttributeMetadata(string $class, string $attribute): ?AttributeMetadataInterface
180
    {
181
        $serializerClassMetadata = $this->serializerClassMetadataFactory->getMetadataFor($class);
529✔
182

183
        foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
529✔
184
            if ($attribute === $serializerAttributeMetadata->getName()) {
528✔
185
                return $serializerAttributeMetadata;
512✔
186
            }
187
        }
188

189
        return null;
33✔
190
    }
191

192
    /**
193
     * Gets all serializer groups used in a class.
194
     *
195
     * @return string[]
196
     */
197
    private function getClassSerializerGroups(string $class): array
198
    {
199
        $serializerClassMetadata = $this->serializerClassMetadataFactory->getMetadataFor($class);
225✔
200

201
        $groups = [];
225✔
202
        foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
225✔
203
            $groups[] = $serializerAttributeMetadata->getGroups();
225✔
204
        }
205

206
        return array_unique(array_merge(...$groups));
225✔
207
    }
208
}
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

© 2025 Coveralls, Inc